diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2aace6fb41ebe..6963a39707860 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -142,6 +142,7 @@ /code/datums/armor/ @ZephyrTFA /code/modules/admin/verbs/ @ZephyrTFA /code/modules/logging/ @ZephyrTFA +/tools/ci/check_grep.sh @ZephyrTFA # CONTRIBUTORS diff --git a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql index a700ae8da9f25..89badb12275be 100644 --- a/SQL/tgstation_schema_prefixed.sql +++ b/SQL/tgstation_schema_prefixed.sql @@ -637,19 +637,6 @@ CREATE TABLE `SS13_discord_links` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB; --- --- Table structure for table `text_adventures` --- -DROP TABLE IF EXISTS `SS13_text_adventures`; -CREATE TABLE `SS13_text_adventures` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `adventure_data` LONGTEXT NOT NULL, - `uploader` VARCHAR(32) NOT NULL, - `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `approved` TINYINT(1) NOT NULL DEFAULT FALSE, - PRIMARY KEY (`id`) -) ENGINE=InnoDB; - -- -- Table structure for table `admin_connections` -- diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm index 6c6378c3e3cc8..08c11022d5eb7 100644 --- a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm +++ b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm @@ -2574,7 +2574,7 @@ dir = 4 }, /obj/effect/decal/cleanable/dirt, -/obj/structure/canister_frame/machine, +/obj/structure/fluff/broken_canister_frame, /turf/open/floor/plating/snowed/icemoon, /area/icemoon/underground/explored) "Tr" = ( diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm new file mode 100644 index 0000000000000..5db8dd37a24f2 --- /dev/null +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm @@ -0,0 +1,643 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"b" = ( +/obj/structure/bonfire/prelit, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"c" = ( +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"d" = ( +/obj/structure/railing{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"e" = ( +/obj/structure/flora/ash/fireblossom, +/mob/living/basic/mining/mook/worker/tribal_chief, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"f" = ( +/obj/structure/chair/wood{ + dir = 1 + }, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"g" = ( +/obj/structure/bed/maint, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"h" = ( +/turf/closed/wall/mineral/wood, +/area/lavaland/surface/outdoors) +"i" = ( +/obj/structure/chair/wood, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"j" = ( +/obj/structure/closet/cabinet, +/obj/item/food/grown/banana, +/obj/item/food/meat/slab/goliath, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"k" = ( +/obj/structure/table/wood/shuttle_bar, +/obj/item/flashlight/flare/candle/infinite, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"l" = ( +/obj/structure/flora/ash/fireblossom, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"m" = ( +/obj/item/flashlight/lantern{ + on = 1 + }, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"n" = ( +/obj/structure/flora/ash/fireblossom, +/mob/living/basic/mining/mook/worker, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"o" = ( +/obj/structure/railing/corner, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"p" = ( +/obj/structure/material_stand, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"q" = ( +/obj/item/fishing_rod, +/obj/structure/bed/maint, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"r" = ( +/turf/open/floor/carpet/lone, +/area/lavaland/surface/outdoors) +"s" = ( +/obj/effect/landmark/mook_village, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"t" = ( +/obj/structure/chair/pew/left, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"u" = ( +/obj/structure/railing, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"v" = ( +/obj/structure/railing/corner{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"w" = ( +/obj/structure/railing{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"x" = ( +/obj/structure/table/wood/shuttle_bar, +/obj/item/clothing/head/costume/garland/poppy, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"y" = ( +/turf/open/misc/grass/jungle, +/area/lavaland/surface/outdoors) +"z" = ( +/mob/living/basic/mining/mook/worker/bard, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"A" = ( +/obj/structure/chair/pew/right, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"C" = ( +/obj/structure/sign/nanotrasen, +/turf/closed/wall/mineral/wood, +/area/lavaland/surface/outdoors) +"E" = ( +/obj/structure/flora/grass/jungle/b, +/turf/open/misc/grass/jungle, +/area/lavaland/surface/outdoors) +"F" = ( +/obj/structure/chair/pew, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"G" = ( +/obj/structure/bed/pod, +/obj/item/bedsheet/nanotrasen, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"H" = ( +/obj/structure/railing/corner{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"I" = ( +/mob/living/basic/mining/mook, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"J" = ( +/obj/structure/railing{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"M" = ( +/obj/structure/chair/pew/right{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"O" = ( +/obj/structure/table/wood/shuttle_bar, +/obj/item/hatchet/wooden, +/obj/item/storage/belt/champion/wrestling, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"P" = ( +/mob/living/basic/mining/mook/worker, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"Q" = ( +/obj/structure/flora/tree/jungle/style_5, +/turf/open/misc/grass/jungle, +/area/lavaland/surface/outdoors) +"S" = ( +/obj/item/flashlight/lantern{ + on = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"V" = ( +/obj/structure/chair/pew{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"W" = ( +/obj/structure/flora/tree/jungle/style_3, +/turf/open/misc/grass/jungle, +/area/lavaland/surface/outdoors) +"Y" = ( +/obj/structure/chair/pew/left{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"Z" = ( +/obj/structure/railing/corner{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) + +(1,1,1) = {" +a +a +a +a +a +a +a +l +a +a +a +a +a +a +a +a +a +a +a +a +"} +(2,1,1) = {" +a +a +a +a +a +a +l +S +a +a +a +a +a +a +a +t +a +a +a +a +"} +(3,1,1) = {" +a +a +a +a +a +a +a +a +a +a +p +a +a +a +a +F +a +a +a +a +"} +(4,1,1) = {" +h +h +h +h +h +h +a +s +a +a +a +a +a +a +a +F +a +b +a +a +"} +(5,1,1) = {" +c +c +j +g +c +c +a +S +a +a +a +a +P +a +S +F +a +a +a +a +"} +(6,1,1) = {" +c +m +c +c +c +c +r +n +a +P +a +a +a +a +a +A +a +a +a +a +"} +(7,1,1) = {" +c +c +c +c +c +c +r +a +I +a +a +a +a +a +l +e +Y +V +V +M +"} +(8,1,1) = {" +c +i +k +f +c +c +a +a +a +o +w +w +w +Z +I +a +a +a +a +a +"} +(9,1,1) = {" +h +h +h +h +h +h +a +a +a +u +y +y +Q +d +a +a +a +P +a +a +"} +(10,1,1) = {" +a +a +a +a +a +a +a +I +a +u +y +y +E +d +a +a +l +a +a +a +"} +(11,1,1) = {" +a +a +l +l +a +a +a +a +a +u +y +W +E +d +a +a +a +a +a +a +"} +(12,1,1) = {" +a +a +a +a +a +P +a +a +a +u +y +y +Q +d +a +a +a +a +a +a +"} +(13,1,1) = {" +a +a +a +a +a +a +a +a +a +v +J +J +J +H +a +a +P +a +a +a +"} +(14,1,1) = {" +h +h +h +h +h +h +h +z +S +l +a +a +I +l +l +a +I +a +a +l +"} +(15,1,1) = {" +g +g +g +g +q +g +g +l +a +a +a +a +a +a +a +a +a +a +a +a +"} +(16,1,1) = {" +c +c +c +c +c +c +c +r +a +a +a +a +h +c +c +c +h +a +a +a +"} +(17,1,1) = {" +c +c +c +m +c +c +c +r +a +P +a +a +h +O +c +c +h +a +a +a +"} +(18,1,1) = {" +g +g +g +g +g +g +g +a +a +a +a +a +C +x +c +c +h +a +a +a +"} +(19,1,1) = {" +h +h +h +h +h +h +h +a +a +l +a +S +h +m +c +G +h +a +a +a +"} +(20,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +h +c +c +c +h +a +a +a +"} diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_watcher_grave.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_watcher_grave.dmm new file mode 100644 index 0000000000000..99af5bdced481 --- /dev/null +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_watcher_grave.dmm @@ -0,0 +1,345 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/closed/mineral/volcanic/lava_land_surface, +/area/lavaland/surface/outdoors) +"b" = ( +/obj/structure/stone_tile/surrounding_tile, +/obj/item/food/egg/watcher, +/turf/open/misc/asteroid/basalt/wasteland/basin, +/area/lavaland/surface/outdoors) +"c" = ( +/obj/structure/flora/ash/tall_shroom, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"f" = ( +/obj/structure/stone_tile/cracked{ + pixel_x = 16 + }, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"g" = ( +/obj/structure/stone_tile/block/cracked, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"h" = ( +/obj/structure/stone_tile/cracked, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"i" = ( +/obj/structure/stone_tile/cracked{ + pixel_y = -16 + }, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"j" = ( +/obj/effect/mapping_helpers/bombable_wall, +/turf/closed/indestructible/riveted/boss/wasteland, +/area/lavaland/surface/outdoors) +"l" = ( +/turf/closed/mineral/gibtonite/volcanic, +/area/lavaland/surface/outdoors) +"m" = ( +/turf/open/chasm/lavaland, +/area/lavaland/surface/outdoors) +"o" = ( +/obj/effect/mob_spawn/corpse/goliath/pierced, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"q" = ( +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"r" = ( +/obj/structure/stone_tile/slab/cracked, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"t" = ( +/obj/structure/stone_tile/cracked{ + pixel_x = 16 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"u" = ( +/obj/structure/flora/ash/stem_shroom, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"v" = ( +/obj/effect/mob_spawn/corpse/watcher/crushed, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"y" = ( +/obj/structure/stone_tile/block/cracked{ + pixel_y = -16 + }, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"A" = ( +/obj/structure/flora/rock/style_random, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"B" = ( +/obj/structure/stone_tile/slab, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"C" = ( +/obj/structure/flora/ash/cacti, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"D" = ( +/obj/structure/stone_tile/surrounding/cracked, +/obj/structure/stone_tile/center/cracked, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"E" = ( +/obj/structure/stone_tile/cracked, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"I" = ( +/turf/template_noop, +/area/template_noop) +"J" = ( +/obj/structure/stone_tile/slab/cracked{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"M" = ( +/obj/structure/flora/ash/fireblossom, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"R" = ( +/turf/closed/indestructible/riveted/boss/wasteland, +/area/lavaland/surface/outdoors) +"U" = ( +/obj/structure/stone_tile/slab/cracked, +/obj/effect/mob_spawn/corpse/watcher/goliath_chewed, +/turf/open/misc/asteroid/basalt/wasteland, +/area/lavaland/surface/outdoors) +"W" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) + +(1,1,1) = {" +I +I +I +I +I +I +I +I +I +I +I +I +I +"} +(2,1,1) = {" +I +I +I +I +a +I +W +W +M +I +I +I +I +"} +(3,1,1) = {" +I +I +I +l +a +a +a +t +a +a +I +I +I +"} +(4,1,1) = {" +I +I +a +a +R +R +R +j +R +a +a +I +I +"} +(5,1,1) = {" +I +a +a +R +R +u +R +q +R +m +a +I +I +"} +(6,1,1) = {" +I +a +a +R +A +f +q +f +m +m +m +I +I +"} +(7,1,1) = {" +I +I +a +R +D +U +h +q +C +m +m +m +I +"} +(8,1,1) = {" +I +l +a +R +y +b +g +B +q +q +m +m +I +"} +(9,1,1) = {" +I +a +a +R +c +i +o +r +q +R +a +a +I +"} +(10,1,1) = {" +I +I +a +R +R +A +u +h +c +R +a +l +I +"} +(11,1,1) = {" +I +I +a +a +R +R +R +q +R +R +a +I +I +"} +(12,1,1) = {" +I +I +I +a +a +a +R +j +R +a +a +I +I +"} +(13,1,1) = {" +I +I +I +I +I +W +M +J +E +W +I +I +I +"} +(14,1,1) = {" +I +I +I +I +I +I +t +v +W +I +I +I +I +"} +(15,1,1) = {" +I +I +I +I +I +I +I +W +W +I +I +I +I +"} diff --git a/_maps/RandomRuins/SpaceRuins/crashedship.dmm b/_maps/RandomRuins/SpaceRuins/crashedship.dmm index c419c508a1b8b..803e0afa28cd3 100644 --- a/_maps/RandomRuins/SpaceRuins/crashedship.dmm +++ b/_maps/RandomRuins/SpaceRuins/crashedship.dmm @@ -575,7 +575,7 @@ "xy" = ( /obj/effect/turf_decal/delivery, /obj/structure/closet/crate/large, -/obj/structure/canister_frame/machine/finished_canister_frame, +/obj/structure/fluff/broken_canister_frame, /obj/effect/turf_decal/tile/neutral/half/contrasted{ dir = 1 }, diff --git a/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm b/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm index b621fef4d2885..2383c4688e3eb 100644 --- a/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm +++ b/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm @@ -1984,10 +1984,7 @@ /obj/effect/decal/cleanable/glass, /obj/item/shard, /obj/item/stack/sheet/iron, -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit{ - AIStatus = 1; - stop_automated_movement = 0 - }, +/mob/living/basic/heretic_summon/rust_walker, /turf/open/floor/plating/rust, /area/ruin/space/has_grav/dangerous_research/lab) "zR" = ( @@ -3717,10 +3714,7 @@ /turf/open/floor/engine/airless, /area/ruin/space/has_grav/dangerous_research/maint) "Xh" = ( -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit{ - AIStatus = 1; - stop_automated_movement = 0 - }, +/mob/living/basic/heretic_summon/rust_walker, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/structure/cable, @@ -3862,10 +3856,7 @@ /turf/open/floor/iron/white, /area/ruin/space/has_grav/dangerous_research/medical) "YZ" = ( -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit{ - AIStatus = 1; - stop_automated_movement = 0 - }, +/mob/living/basic/heretic_summon/rust_walker, /turf/open/floor/plating/rust, /area/ruin/space/has_grav/dangerous_research/lab) "Zc" = ( diff --git a/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm b/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm index c7901e1421f8b..1a66fa098e05e 100644 --- a/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm +++ b/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm @@ -664,7 +664,7 @@ dir = 8 }, /obj/effect/decal/cleanable/robot_debris/limb, -/obj/structure/canister_frame/machine, +/obj/structure/fluff/broken_canister_frame, /obj/item/ammo_casing/spent, /obj/item/ammo_casing/spent, /turf/open/floor/mineral/plastitanium, diff --git a/_maps/RandomRuins/SpaceRuins/oldstation.dmm b/_maps/RandomRuins/SpaceRuins/oldstation.dmm index e134100000d32..5765faa89a9fb 100644 --- a/_maps/RandomRuins/SpaceRuins/oldstation.dmm +++ b/_maps/RandomRuins/SpaceRuins/oldstation.dmm @@ -5424,7 +5424,7 @@ /area/ruin/space/ancientstation/delta/rnd) "DK" = ( /obj/effect/decal/cleanable/dirt, -/obj/structure/canister_frame/machine/unfinished_canister_frame, +/obj/structure/fluff/broken_canister_frame, /turf/open/floor/engine/airless, /area/ruin/space/ancientstation/beta/supermatter) "DM" = ( diff --git a/_maps/RandomRuins/SpaceRuins/russian_derelict.dmm b/_maps/RandomRuins/SpaceRuins/russian_derelict.dmm index 953bde1f09441..76e7da25fa414 100644 --- a/_maps/RandomRuins/SpaceRuins/russian_derelict.dmm +++ b/_maps/RandomRuins/SpaceRuins/russian_derelict.dmm @@ -5532,7 +5532,7 @@ /obj/machinery/atmospherics/components/unary/portables_connector/visible{ dir = 1 }, -/obj/structure/canister_frame/machine/unfinished_canister_frame, +/obj/structure/fluff/broken_canister_frame, /obj/effect/decal/cleanable/dirt, /obj/effect/mapping_helpers/burnt_floor, /turf/open/floor/iron, @@ -8017,7 +8017,7 @@ /obj/machinery/atmospherics/components/unary/portables_connector/visible{ dir = 1 }, -/obj/structure/canister_frame/machine/unfinished_canister_frame, +/obj/structure/fluff/broken_canister_frame, /turf/open/floor/iron, /area/ruin/space/ks13/science/ordnance) "Zr" = ( diff --git a/_maps/RandomZLevels/snowdin.dmm b/_maps/RandomZLevels/snowdin.dmm index 5051bf26b10dc..b7befc1e9fe57 100644 --- a/_maps/RandomZLevels/snowdin.dmm +++ b/_maps/RandomZLevels/snowdin.dmm @@ -9729,7 +9729,7 @@ /area/awaymission/snowdin/post/mining_main) "IV" = ( /obj/structure/table, -/obj/structure/showcase/machinery/microwave, +/obj/machinery/microwave/engineering/cell_included, /obj/effect/turf_decal/tile/neutral/half/contrasted, /turf/open/floor/iron, /area/awaymission/snowdin/post/mining_main) diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm index 8e46be856e3a2..d0f86a7315c82 100644 --- a/_maps/map_files/Birdshot/birdshot.dmm +++ b/_maps/map_files/Birdshot/birdshot.dmm @@ -18755,7 +18755,7 @@ /obj/item/clothing/gloves/color/fyellow{ pixel_y = 7 }, -/obj/structure/canister_frame/machine, +/obj/structure/fluff/broken_canister_frame, /obj/machinery/camera/autoname/directional/north, /obj/item/radio/intercom/directional/north, /turf/open/floor/plating, @@ -20092,6 +20092,8 @@ "hln" = ( /obj/effect/decal/cleanable/dirt, /obj/effect/turf_decal/tile/yellow/opposingcorners, +/obj/structure/table, +/obj/machinery/microwave/engineering/cell_included, /turf/open/floor/iron, /area/station/maintenance/department/engine/atmos) "hlo" = ( @@ -21164,6 +21166,8 @@ /obj/effect/turf_decal/tile/yellow/opposingcorners, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/light/small/directional/north, +/obj/structure/table, +/obj/effect/spawner/random/food_or_drink/donkpockets, /turf/open/floor/iron, /area/station/maintenance/department/engine/atmos) "hDt" = ( @@ -25002,7 +25006,7 @@ /turf/open/floor/iron/dark/small, /area/station/ai_monitored/security/armory) "iYh" = ( -/obj/structure/canister_frame/machine, +/obj/structure/fluff/broken_canister_frame, /turf/open/floor/plating, /area/station/maintenance/department/bridge) "iYj" = ( @@ -26928,7 +26932,7 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/holopad, -/mob/living/simple_animal/sloth/citrus, +/mob/living/basic/sloth/citrus, /turf/open/floor/iron, /area/station/cargo/storage) "jHE" = ( @@ -32991,15 +32995,10 @@ /turf/open/floor/iron, /area/station/security/checkpoint/escape) "lLq" = ( -/obj/effect/turf_decal/box/corners{ - dir = 1 - }, -/obj/effect/turf_decal/box/corners{ - dir = 4 - }, /obj/machinery/firealarm/directional/east, /obj/effect/decal/cleanable/oil, -/obj/effect/landmark/bitrunning/station_reward_spawn, +/obj/machinery/byteforge, +/obj/effect/turf_decal/box, /turf/open/floor/iron/dark/smooth_large, /area/station/bitrunning/den) "lLr" = ( @@ -37938,13 +37937,10 @@ /turf/open/floor/circuit/telecomms/mainframe, /area/station/science/xenobiology) "nyf" = ( -/obj/effect/turf_decal/box/corners{ - dir = 8 - }, -/obj/effect/turf_decal/box/corners, /obj/machinery/camera/autoname/directional/east, /obj/effect/decal/cleanable/dirt, -/obj/effect/landmark/bitrunning/station_reward_spawn, +/obj/structure/table, +/obj/item/food/cornchips/green, /turf/open/floor/iron/dark/smooth_large, /area/station/bitrunning/den) "nyi" = ( @@ -53091,7 +53087,6 @@ /obj/structure/table/reinforced, /obj/machinery/door/firedoor, /obj/machinery/door/window/right/directional/east{ - dir = 8; name = "Pharmacy Desk"; req_access = list("pharmacy") }, @@ -58870,7 +58865,7 @@ "urm" = ( /obj/structure/table/glass, /obj/item/gun/energy/laser/practice, -/obj/item/gun/energy/laser/practice{ +/obj/item/gun/energy/laser/carbine/practice{ pixel_x = -2; pixel_y = 4 }, diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index 1524ca5adf9b9..77554e2d9573a 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -10151,12 +10151,9 @@ /turf/open/floor/iron/dark, /area/station/command/heads_quarters/hop) "cwd" = ( -/obj/effect/turf_decal/box/corners, -/obj/effect/turf_decal/box/corners{ - dir = 4 - }, /obj/machinery/light/directional/south, -/obj/effect/landmark/bitrunning/station_reward_spawn, +/obj/machinery/byteforge, +/obj/effect/turf_decal/box, /turf/open/floor/iron/dark/smooth_large, /area/station/bitrunning/den) "cwe" = ( @@ -20287,7 +20284,7 @@ /area/station/maintenance/space_hut/observatory) "eYo" = ( /obj/structure/table/reinforced, -/obj/item/gun/energy/laser/practice{ +/obj/item/gun/energy/laser/carbine/practice{ pixel_x = 3; pixel_y = -3 }, @@ -39286,15 +39283,15 @@ /turf/open/floor/iron/white, /area/station/command/heads_quarters/cmo) "jKY" = ( -/obj/effect/turf_decal/box/corners{ - dir = 8 - }, -/obj/effect/turf_decal/box/corners{ - dir = 1 - }, /obj/effect/decal/cleanable/oil/streak, /obj/machinery/camera/directional/south, -/obj/effect/landmark/bitrunning/station_reward_spawn, +/obj/structure/table, +/obj/item/storage/toolbox/mechanical{ + pixel_y = 7 + }, +/obj/item/reagent_containers/cup/soda_cans/space_mountain_wind{ + pixel_x = 5 + }, /turf/open/floor/iron/dark/smooth_large, /area/station/bitrunning/den) "jLa" = ( @@ -53614,7 +53611,7 @@ /area/station/commons/fitness/recreation) "ntU" = ( /obj/structure/table/reinforced, -/obj/machinery/microwave, +/obj/machinery/microwave/engineering/cell_included, /obj/structure/sign/poster/random/directional/west, /turf/open/floor/wood, /area/station/engineering/break_room) @@ -63487,7 +63484,8 @@ }, /obj/machinery/door/window/left/directional/west{ name = "Pharmacy Desk"; - req_access = list("pharmacy") + req_access = list("pharmacy"); + dir = 4 }, /turf/open/floor/iron/dark, /area/station/medical/pharmacy) @@ -68608,6 +68606,7 @@ name = "Pharmacy Shutters" }, /obj/machinery/smartfridge/chemistry/preloaded, +/obj/machinery/door/firedoor, /turf/open/floor/iron/white, /area/station/medical/pharmacy) "rjd" = ( @@ -84466,7 +84465,7 @@ /area/station/maintenance/port/fore) "vbT" = ( /obj/structure/rack, -/obj/item/gun/energy/laser/practice{ +/obj/item/gun/energy/laser/carbine/practice{ pixel_x = 3; pixel_y = -3 }, @@ -86574,10 +86573,6 @@ dir = 1 }, /obj/effect/decal/cleanable/dirt/dust, -/obj/item/storage/toolbox/mechanical{ - pixel_x = -8; - pixel_y = 17 - }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/structure/cable, /obj/machinery/holopad, @@ -95382,7 +95377,7 @@ /obj/structure/disposalpipe/segment{ dir = 10 }, -/mob/living/simple_animal/sloth/citrus, +/mob/living/basic/sloth/citrus, /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron, /area/station/command/heads_quarters/qm) diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index c082f4c344247..9c3f1d34bf194 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -2816,9 +2816,9 @@ "aTJ" = ( /obj/effect/spawner/structure/window/reinforced, /obj/machinery/door/poddoor/shutters/preopen{ - dir = 4; id = "pharmacy_shutters"; - name = "Pharmacy Shutters" + name = "Pharmacy Shutters"; + dir = 4 }, /turf/open/floor/plating, /area/station/medical/pharmacy) @@ -8252,7 +8252,6 @@ /obj/structure/table/reinforced, /obj/machinery/door/firedoor, /obj/machinery/door/window/right/directional/east{ - dir = 8; name = "Pharmacy Desk"; req_access = list("pharmacy") }, @@ -8267,9 +8266,9 @@ }, /obj/effect/turf_decal/tile/yellow/fourcorners, /obj/machinery/door/poddoor/shutters/preopen{ - dir = 4; id = "pharmacy_shutters"; - name = "Pharmacy Shutters" + name = "Pharmacy Shutters"; + dir = 4 }, /turf/open/floor/iron, /area/station/medical/pharmacy) @@ -8823,7 +8822,7 @@ /turf/open/floor/iron, /area/station/security/prison/workout) "cBJ" = ( -/obj/item/gun/energy/laser/practice{ +/obj/item/gun/energy/laser/carbine/practice{ pixel_y = 5 }, /obj/item/gun/energy/laser/practice, @@ -10091,15 +10090,10 @@ /area/mine/laborcamp) "cWa" = ( /obj/machinery/light_switch/directional/west, -/obj/effect/turf_decal/box/corners{ - dir = 1 - }, -/obj/effect/turf_decal/box/corners{ - dir = 4 - }, /obj/effect/decal/cleanable/cobweb, /obj/effect/decal/cleanable/dirt/dust, -/obj/effect/landmark/bitrunning/station_reward_spawn, +/obj/machinery/byteforge, +/obj/effect/turf_decal/box, /turf/open/floor/iron/dark/smooth_large, /area/station/bitrunning/den) "cWq" = ( @@ -18137,7 +18131,7 @@ /area/station/maintenance/starboard/aft) "fya" = ( /obj/structure/cable, -/mob/living/simple_animal/sloth/paperwork, +/mob/living/basic/sloth/paperwork, /turf/open/floor/iron, /area/station/cargo/storage) "fyc" = ( @@ -29744,6 +29738,16 @@ /obj/effect/turf_decal/tile/yellow/opposingcorners, /turf/open/floor/iron/white, /area/station/maintenance/port/fore) +"jho" = ( +/obj/machinery/smartfridge/chemistry/preloaded, +/obj/machinery/door/firedoor, +/obj/machinery/door/poddoor/shutters/preopen{ + id = "pharmacy_shutters2"; + name = "Pharmacy Shutters"; + dir = 4 + }, +/turf/open/floor/iron/white, +/area/station/medical/pharmacy) "jhy" = ( /obj/effect/turf_decal/tile/brown, /turf/open/floor/iron, @@ -37308,8 +37312,9 @@ /turf/open/floor/iron/dark/textured_large, /area/station/bitrunning/den) "lts" = ( -/obj/structure/extinguisher_cabinet/directional/south, /obj/effect/turf_decal/tile/blue, +/obj/machinery/light/directional/south, +/obj/structure/extinguisher_cabinet/directional/south, /turf/open/floor/iron, /area/station/hallway/primary/starboard) "ltE" = ( @@ -39560,8 +39565,8 @@ /obj/machinery/chem_dispenser, /obj/structure/window/reinforced/spawner/directional/west, /obj/machinery/light/directional/north, -/obj/structure/sign/warning/no_smoking/directional/north, /obj/effect/turf_decal/tile/yellow/full, +/obj/item/radio/intercom/directional/north, /turf/open/floor/iron/white/smooth_large, /area/station/medical/pharmacy) "meG" = ( @@ -41021,8 +41026,6 @@ /turf/open/floor/iron/dark, /area/station/ai_monitored/turret_protected/aisat/hallway) "mEi" = ( -/obj/machinery/firealarm/directional/north, -/obj/structure/tank_holder/extinguisher, /obj/machinery/camera{ c_tag = "Medbay Pharmacy"; dir = 9; @@ -41031,6 +41034,17 @@ /obj/effect/turf_decal/tile/yellow/half/contrasted{ dir = 1 }, +/obj/machinery/shower/directional/south, +/obj/structure/railing{ + dir = 4 + }, +/obj/structure/fluff{ + desc = "What, you think the water just magically soaks into the metallic flooring?"; + icon = 'icons/obj/mining_zones/survival_pod.dmi'; + icon_state = "fan_tiny"; + name = "shower drain" + }, +/obj/effect/turf_decal/stripes/white/end, /turf/open/floor/iron/white, /area/station/medical/pharmacy) "mEv" = ( @@ -41300,10 +41314,6 @@ dir = 4 }, /area/station/service/chapel) -"mJv" = ( -/obj/item/paper/fluff/ids_for_dummies, -/turf/open/genturf, -/area/icemoon/underground/unexplored/rivers/deep) "mJD" = ( /obj/structure/cable, /obj/effect/turf_decal/tile/neutral{ @@ -42076,7 +42086,7 @@ "mXq" = ( /obj/structure/table, /obj/machinery/recharger, -/obj/item/gun/energy/laser/practice, +/obj/item/gun/energy/laser/carbine/practice, /obj/item/gun/energy/laser/practice, /obj/machinery/newscaster/directional/south, /turf/open/floor/iron, @@ -42352,10 +42362,10 @@ "nbd" = ( /obj/machinery/disposal/bin, /obj/structure/disposalpipe/trunk, -/obj/item/radio/intercom/directional/north, /obj/effect/turf_decal/tile/yellow/half/contrasted{ dir = 1 }, +/obj/machinery/firealarm/directional/north, /turf/open/floor/iron/white, /area/station/medical/pharmacy) "nbi" = ( @@ -60782,6 +60792,7 @@ /obj/effect/turf_decal/tile/yellow/anticorner/contrasted{ dir = 8 }, +/obj/structure/sign/warning/no_smoking/directional/west, /turf/open/floor/iron/white, /area/station/medical/pharmacy) "sBi" = ( @@ -62098,11 +62109,7 @@ /turf/open/floor/plating/snowed/icemoon, /area/icemoon/underground/explored) "sVW" = ( -/obj/effect/turf_decal/box/corners{ - dir = 8 - }, -/obj/effect/turf_decal/box/corners, -/obj/effect/landmark/bitrunning/station_reward_spawn, +/obj/effect/decal/cleanable/dirt/dust, /turf/open/floor/iron/dark/smooth_large, /area/station/bitrunning/den) "sWl" = ( @@ -62584,6 +62591,11 @@ "tfp" = ( /obj/machinery/smartfridge/chemistry/preloaded, /obj/machinery/door/firedoor, +/obj/machinery/door/poddoor/shutters/preopen{ + id = "pharmacy_shutters"; + name = "Pharmacy Shutters"; + dir = 4 + }, /turf/open/floor/iron/white, /area/station/medical/pharmacy) "tfu" = ( @@ -62684,7 +62696,7 @@ "thc" = ( /obj/structure/table/reinforced, /obj/structure/extinguisher_cabinet/directional/south, -/obj/item/gun/energy/laser/practice{ +/obj/item/gun/energy/laser/carbine/practice{ pixel_x = 3; pixel_y = -3 }, @@ -63240,16 +63252,15 @@ /obj/machinery/door/firedoor, /obj/machinery/door/window/right/directional/east{ base_state = "left"; - dir = 8; icon_state = "left"; name = "Pharmacy Desk"; req_access = list("pharmacy") }, /obj/effect/turf_decal/tile/yellow/fourcorners, /obj/machinery/door/poddoor/shutters/preopen{ - dir = 4; id = "pharmacy_shutters2"; - name = "Pharmacy Shutters" + name = "Pharmacy Shutters"; + dir = 4 }, /turf/open/floor/iron, /area/station/medical/pharmacy) @@ -65828,6 +65839,7 @@ /obj/effect/turf_decal/tile/yellow/half/contrasted{ dir = 1 }, +/obj/structure/extinguisher_cabinet/directional/north, /turf/open/floor/iron/white, /area/station/medical/pharmacy) "uhs" = ( @@ -78257,7 +78269,7 @@ /area/station/engineering/main) "xZo" = ( /obj/structure/table/glass, -/obj/machinery/microwave, +/obj/machinery/microwave/engineering/cell_included, /obj/effect/turf_decal/tile/brown/anticorner/contrasted{ dir = 4 }, @@ -82027,7 +82039,7 @@ oSU oSU oSU oSU -mJv +oSU oSU oSU oSU @@ -244613,7 +244625,7 @@ sIm hmb lso dEV -lts +bai azw uhn fTC @@ -244870,7 +244882,7 @@ exw exw cwh dEV -jyp +lts azw mEi eyc @@ -245391,7 +245403,7 @@ dip bHO azw tpY -tfp +jho azw eiY tZm diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index f2a999d3f3849..fdb4c2f06c19d 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -3343,16 +3343,9 @@ /turf/open/floor/iron, /area/station/hallway/primary/starboard) "bje" = ( -/obj/effect/turf_decal/box/corners{ - dir = 8 - }, -/obj/effect/turf_decal/box/corners{ - dir = 1 - }, /obj/effect/decal/cleanable/oil/streak, /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4, /obj/structure/extinguisher_cabinet/directional/north, -/obj/effect/landmark/bitrunning/station_reward_spawn, /turf/open/floor/iron/dark/smooth_large, /area/station/bitrunning/den) "bjl" = ( @@ -18503,6 +18496,7 @@ /obj/structure/disposalpipe/sorting/mail/flip{ dir = 1 }, +/obj/effect/mapping_helpers/mail_sorting/supply/disposals, /turf/open/floor/plating, /area/station/maintenance/port/fore) "gQw" = ( @@ -22467,9 +22461,7 @@ /area/station/science/xenobiology) "imy" = ( /obj/structure/table/reinforced, -/obj/machinery/microwave{ - pixel_y = 6 - }, +/obj/machinery/microwave/engineering/cell_included, /obj/effect/turf_decal/tile/neutral/opposingcorners{ dir = 1 }, @@ -25335,7 +25327,7 @@ dir = 4 }, /obj/structure/disposalpipe/segment, -/mob/living/simple_animal/sloth/citrus, +/mob/living/basic/sloth/citrus, /turf/open/floor/iron, /area/station/cargo/storage) "jhD" = ( @@ -27284,7 +27276,7 @@ /area/station/medical/chemistry) "jOF" = ( /obj/structure/rack, -/obj/item/gun/energy/laser/practice{ +/obj/item/gun/energy/laser/carbine/practice{ pixel_x = 2; pixel_y = 5 }, @@ -29711,14 +29703,11 @@ /turf/open/floor/engine, /area/station/science/explab) "kHU" = ( -/obj/effect/turf_decal/box/corners, -/obj/effect/turf_decal/box/corners{ - dir = 4 - }, /obj/effect/decal/cleanable/dirt/dust, /obj/machinery/light_switch/directional/north, /obj/effect/decal/cleanable/dirt/dust, -/obj/effect/landmark/bitrunning/station_reward_spawn, +/obj/machinery/byteforge, +/obj/effect/turf_decal/box, /turf/open/floor/iron/dark/smooth_large, /area/station/bitrunning/den) "kIG" = ( @@ -40642,7 +40631,6 @@ pixel_x = -8 }, /obj/machinery/door/window/left/directional/north{ - dir = 2; name = "Pharmacy Desk"; req_access = list("pharmacy") }, @@ -43381,7 +43369,7 @@ /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "pDh" = ( -/obj/structure/showcase/machinery/microwave{ +/obj/effect/spawner/random/decoration/microwave{ dir = 1; pixel_y = 2 }, @@ -56393,9 +56381,7 @@ /area/station/security/office) "uew" = ( /obj/structure/table/reinforced, -/obj/machinery/microwave{ - pixel_y = 6 - }, +/obj/machinery/microwave/engineering/cell_included, /obj/effect/turf_decal/stripes/corner, /obj/effect/turf_decal/tile/yellow{ dir = 8 @@ -64576,7 +64562,6 @@ name = "Pharmacy Shutters" }, /obj/machinery/door/window/right/directional/east{ - dir = 8; name = "Pharmacy Desk"; req_access = list("pharmacy") }, diff --git a/_maps/map_files/NorthStar/north_star.dmm b/_maps/map_files/NorthStar/north_star.dmm index a9e18d02c3b5f..58a80c3942f31 100644 --- a/_maps/map_files/NorthStar/north_star.dmm +++ b/_maps/map_files/NorthStar/north_star.dmm @@ -17161,9 +17161,6 @@ /obj/item/cigbutt/cigarbutt{ pixel_x = 7 }, -/obj/effect/turf_decal/stripes{ - dir = 8 - }, /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ dir = 1 }, @@ -27650,7 +27647,7 @@ /turf/open/floor/iron/white, /area/station/science/ordnance/testlab) "hnH" = ( -/obj/machinery/microwave, +/obj/machinery/microwave/engineering/cell_included, /obj/structure/table/reinforced/rglass, /obj/effect/turf_decal/tile/blue/fourcorners, /turf/open/floor/iron/white/textured, @@ -30711,7 +30708,7 @@ /turf/open/floor/plating, /area/station/maintenance/floor3/starboard/fore) "idf" = ( -/mob/living/simple_animal/sloth/paperwork, +/mob/living/basic/sloth/paperwork, /obj/effect/turf_decal/tile/yellow, /obj/effect/turf_decal/tile/brown{ dir = 4 @@ -34844,12 +34841,10 @@ /turf/open/floor/pod/light, /area/station/maintenance/floor1/starboard/fore) "jhP" = ( -/obj/effect/turf_decal/box/corners, -/obj/effect/turf_decal/box/corners{ - dir = 4 - }, /obj/effect/decal/cleanable/dirt/dust, -/obj/effect/landmark/bitrunning/station_reward_spawn, +/obj/effect/turf_decal/stripes{ + dir = 8 + }, /turf/open/floor/iron/dark/smooth_large, /area/station/bitrunning/den) "jhU" = ( @@ -36511,7 +36506,7 @@ /turf/open/floor/iron/dark/side, /area/station/hallway/floor2/aft) "jEU" = ( -/obj/structure/canister_frame/machine, +/obj/structure/fluff/broken_canister_frame, /turf/open/floor/engine, /area/station/maintenance/floor1/port/aft) "jEX" = ( @@ -50980,9 +50975,6 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/effect/turf_decal/trimline/brown/line, -/obj/effect/turf_decal/stripes/corner{ - dir = 8 - }, /obj/structure/disposalpipe/segment{ dir = 4 }, @@ -55648,11 +55640,13 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/structure/cable, -/obj/effect/turf_decal/stripes, /obj/effect/turf_decal/trimline/brown/line, /obj/structure/disposalpipe/segment{ dir = 5 }, +/obj/effect/turf_decal/stripes/corner{ + dir = 8 + }, /turf/open/floor/iron/dark/smooth_half, /area/station/bitrunning/den) "owo" = ( @@ -71006,7 +71000,7 @@ "sEq" = ( /obj/structure/rack, /obj/effect/turf_decal/stripes, -/obj/item/gun/energy/laser/practice{ +/obj/item/gun/energy/laser/carbine/practice{ pixel_x = 3; pixel_y = -3 }, @@ -71937,7 +71931,7 @@ pixel_x = 3; pixel_y = -3 }, -/obj/item/gun/energy/laser/practice, +/obj/item/gun/energy/laser/carbine/practice, /obj/machinery/light/directional/north, /turf/open/floor/iron/dark, /area/station/security/range) @@ -78519,16 +78513,11 @@ /turf/open/floor/iron, /area/station/hallway/floor2/aft) "uDm" = ( -/obj/effect/turf_decal/box/corners{ - dir = 8 - }, -/obj/effect/turf_decal/box/corners{ - dir = 1 - }, /obj/machinery/firealarm/directional/south, /obj/machinery/light/directional/south, /obj/effect/decal/cleanable/oil/streak, -/obj/effect/landmark/bitrunning/station_reward_spawn, +/obj/machinery/byteforge, +/obj/effect/turf_decal/box, /turf/open/floor/iron/dark/smooth_large, /area/station/bitrunning/den) "uDr" = ( diff --git a/_maps/map_files/debug/runtimestation.dmm b/_maps/map_files/debug/runtimestation.dmm index 3b86ffcfc20d5..dff02b2abd728 100644 --- a/_maps/map_files/debug/runtimestation.dmm +++ b/_maps/map_files/debug/runtimestation.dmm @@ -2184,7 +2184,7 @@ /turf/open/floor/iron, /area/station/command/bridge) "FW" = ( -/obj/effect/landmark/bitrunning/station_reward_spawn, +/obj/machinery/byteforge, /turf/open/floor/circuit/green, /area/station/bitrunning/den) "Ht" = ( @@ -3196,7 +3196,7 @@ aa aa aa uI -FW +hU hU jR Oq @@ -3288,8 +3288,8 @@ aa aa aa uI -FW hU +FW jR Oq uI diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm index c93798ef02aad..393d05b37efd6 100644 --- a/_maps/map_files/generic/CentCom.dmm +++ b/_maps/map_files/generic/CentCom.dmm @@ -308,6 +308,12 @@ /obj/effect/turf_decal/tile/green, /turf/open/floor/iron, /area/centcom/central_command_areas/ferry) +"bo" = ( +/obj/effect/turf_decal/siding/dark_blue{ + dir = 9 + }, +/turf/open/floor/iron, +/area/centcom/central_command_areas/evacuation) "bp" = ( /obj/item/trash/sosjerky, /obj/effect/decal/cleanable/dirt, @@ -377,6 +383,10 @@ /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron/dark, /area/centcom/central_command_areas/armory) +"bJ" = ( +/obj/effect/turf_decal/siding/dark_blue/corner, +/turf/open/floor/iron, +/area/centcom/central_command_areas/evacuation) "bL" = ( /obj/item/kirbyplants/organic/plant21, /obj/effect/turf_decal/stripes/line{ @@ -658,11 +668,15 @@ /area/centcom/tdome/observation) "cX" = ( /obj/structure/table/reinforced, +/obj/item/paper_bin{ + pixel_y = 8; + pixel_x = -4 + }, /turf/open/floor/iron, /area/centcom/central_command_areas/supplypod) "cY" = ( -/obj/machinery/icecream_vat, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/machinery/griddle, /turf/open/floor/iron/dark, /area/centcom/tdome/observation) "cZ" = ( @@ -671,6 +685,12 @@ }, /turf/open/floor/iron, /area/centcom/tdome/observation) +"da" = ( +/obj/effect/turf_decal/siding/dark_blue/corner{ + dir = 8 + }, +/turf/open/floor/iron, +/area/centcom/central_command_areas/evacuation) "db" = ( /obj/structure/table/wood, /obj/item/folder/red, @@ -679,6 +699,15 @@ /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron/dark, /area/centcom/central_command_areas/briefing) +"dc" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/obj/effect/turf_decal/siding/dark_blue/corner{ + dir = 1 + }, +/turf/open/floor/iron, +/area/centcom/central_command_areas/evacuation) "dd" = ( /obj/structure/reagent_dispensers/watertank, /obj/effect/turf_decal/stripes/line{ @@ -691,11 +720,15 @@ /turf/closed/indestructible/start_area, /area/misc/start) "dh" = ( -/obj/effect/turf_decal/tile/blue/half/contrasted{ +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 4 + }, +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 4 + }, +/turf/open/floor/iron/white/side{ dir = 8 }, -/obj/machinery/light/floor, -/turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "di" = ( /obj/structure/table/reinforced, @@ -1516,14 +1549,9 @@ /turf/open/floor/iron/dark, /area/centcom/central_command_areas/briefing) "hv" = ( -/obj/structure/table/reinforced, -/obj/machinery/microwave{ - desc = "Cooks and boils stuff, somehow."; - pixel_x = -3; - pixel_y = 5 - }, /obj/machinery/barsign/all_access/directional/south, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/machinery/griddle, /turf/open/floor/iron/dark, /area/centcom/tdome/observation) "hx" = ( @@ -2007,9 +2035,16 @@ /area/centcom/central_command_areas/supply) "jq" = ( /obj/item/stack/package_wrap, -/obj/item/hand_labeler, +/obj/item/hand_labeler{ + pixel_y = 13; + pixel_x = 1 + }, /obj/structure/table, /obj/effect/turf_decal/bot, +/obj/item/hand_labeler_refill{ + pixel_x = -8; + pixel_y = 6 + }, /turf/open/floor/iron, /area/centcom/central_command_areas/supply) "jr" = ( @@ -2187,9 +2222,9 @@ /turf/open/floor/iron, /area/centcom/central_command_areas/supply) "jQ" = ( -/obj/structure/closet/secure_closet/quartermaster, /obj/machinery/airalarm/directional/east, /obj/effect/turf_decal/bot, +/obj/structure/closet/wardrobe/cargotech, /turf/open/floor/iron, /area/centcom/central_command_areas/supply) "jU" = ( @@ -2207,7 +2242,8 @@ /area/centcom/tdome/administration) "kh" = ( /obj/machinery/telecomms/allinone/nuclear, -/turf/open/indestructible/hierophant, +/obj/structure/sign/poster/contraband/syndicate_pistol/directional/north, +/turf/open/indestructible/dark, /area/centcom/central_command_areas/admin) "ki" = ( /obj/effect/turf_decal/tile/neutral/fourcorners, @@ -2240,6 +2276,13 @@ /obj/machinery/light/directional/west, /turf/open/floor/grass, /area/centcom/central_command_areas/control) +"kr" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/siding/yellow/corner{ + dir = 1 + }, +/turf/open/floor/iron, +/area/centcom/central_command_areas/supply) "ks" = ( /obj/machinery/modular_computer/preset/id/centcom, /obj/machinery/status_display/ai/directional/north, @@ -2579,9 +2622,8 @@ /turf/open/floor/iron/grimy, /area/centcom/central_command_areas/courtroom) "lZ" = ( -/obj/machinery/light/floor, -/turf/open/floor/iron, -/area/centcom/central_command_areas/evacuation) +/turf/closed/indestructible/riveted, +/area/space) "mc" = ( /obj/effect/light_emitter/podbay, /turf/open/floor/iron, @@ -3260,12 +3302,17 @@ /turf/closed/indestructible/riveted, /area/centcom/central_command_areas/courtroom) "pd" = ( -/obj/item/clipboard, +/obj/item/clipboard{ + pixel_y = 4; + pixel_x = -2 + }, /obj/item/stamp/denied{ - pixel_x = 3; - pixel_y = 3 + pixel_x = -2; + pixel_y = 12 + }, +/obj/item/stamp{ + pixel_y = 7 }, -/obj/item/stamp, /obj/structure/table/reinforced, /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron/dark, @@ -3725,8 +3772,9 @@ /turf/open/floor/iron, /area/centcom/central_command_areas/ferry) "rq" = ( -/obj/effect/turf_decal/tile/neutral/fourcorners, /obj/machinery/light/floor, +/obj/effect/turf_decal/tile/dark_blue/fourcorners, +/obj/effect/turf_decal/tile/dark_blue/fourcorners, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "rs" = ( @@ -3893,8 +3941,13 @@ /area/centcom/tdome/observation) "sa" = ( /obj/structure/table/wood, -/obj/item/lighter, -/obj/item/crowbar/power, +/obj/item/lighter{ + pixel_x = -6; + pixel_y = -2 + }, +/obj/item/crowbar/power{ + pixel_y = 15 + }, /turf/open/floor/iron/dark, /area/centcom/central_command_areas/supplypod) "sb" = ( @@ -4088,8 +4141,14 @@ /area/centcom/central_command_areas/evacuation) "sP" = ( /obj/structure/table, -/obj/item/paper_bin, -/obj/item/pen, +/obj/item/paper_bin{ + pixel_y = 7; + pixel_x = -4 + }, +/obj/item/pen{ + pixel_x = -5; + pixel_y = 9 + }, /obj/effect/turf_decal/stripes/line{ dir = 1 }, @@ -4112,7 +4171,9 @@ /area/centcom/central_command_areas/evacuation) "sS" = ( /obj/structure/table, -/obj/item/toy/katana, +/obj/item/toy/katana{ + pixel_y = 8 + }, /obj/item/toy/plush/carpplushie, /obj/effect/turf_decal/stripes/line{ dir = 1 @@ -4304,9 +4365,7 @@ /turf/open/floor/iron, /area/centcom/central_command_areas/prison) "tT" = ( -/obj/effect/turf_decal/siding/yellow{ - dir = 1 - }, +/obj/effect/turf_decal/siding/dark_blue, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "tU" = ( @@ -4320,7 +4379,8 @@ /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "tW" = ( -/turf/open/indestructible/hierophant/two, +/obj/structure/sign/poster/contraband/syndicate_recruitment/directional/north, +/turf/open/indestructible/dark, /area/centcom/central_command_areas/admin) "ub" = ( /obj/machinery/door/firedoor, @@ -4369,6 +4429,13 @@ /obj/structure/flora/bush/generic/style_random, /turf/open/floor/grass, /area/centcom/central_command_areas/prison) +"ul" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/siding/yellow/corner{ + dir = 4 + }, +/turf/open/floor/iron, +/area/centcom/central_command_areas/supply) "um" = ( /obj/machinery/computer/communications{ dir = 1 @@ -4390,7 +4457,9 @@ /turf/open/floor/iron/dark, /area/centcom/central_command_areas/control) "us" = ( -/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/siding/dark_blue{ + dir = 5 + }, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "ut" = ( @@ -4435,6 +4504,9 @@ /obj/effect/turf_decal/stripes/line{ dir = 8 }, +/obj/effect/turf_decal/siding/dark_blue/corner{ + dir = 4 + }, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "uE" = ( @@ -4452,9 +4524,14 @@ /turf/open/floor/grass, /area/centcom/tdome/observation) "uG" = ( -/obj/effect/turf_decal/siding/yellow, +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/obj/effect/turf_decal/siding/dark_blue{ + dir = 8 + }, /turf/open/floor/iron, -/area/centcom/central_command_areas/supply) +/area/centcom/central_command_areas/evacuation) "uM" = ( /obj/machinery/chem_master/condimaster{ name = "HoochMaster 2000" @@ -4558,18 +4635,45 @@ /turf/open/floor/iron, /area/centcom/central_command_areas/control) "ve" = ( -/obj/effect/turf_decal/tile/blue, -/turf/open/floor/iron, +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 8 + }, +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 8 + }, +/obj/effect/turf_decal/tile/dark_blue{ + dir = 4 + }, +/obj/effect/turf_decal/tile/dark_blue{ + dir = 4 + }, +/turf/open/floor/iron/white/corner, /area/centcom/central_command_areas/evacuation) "vf" = ( -/obj/effect/turf_decal/tile/blue/half/contrasted, -/turf/open/floor/iron, +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 1 + }, +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 1 + }, +/turf/open/floor/iron/white/side, /area/centcom/central_command_areas/evacuation) "vg" = ( -/obj/effect/turf_decal/tile/blue{ +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 4 + }, +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 4 + }, +/obj/effect/turf_decal/tile/dark_blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/dark_blue{ + dir = 1 + }, +/turf/open/floor/iron/white/corner{ dir = 8 }, -/turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "vm" = ( /obj/effect/turf_decal/delivery, @@ -4730,16 +4834,27 @@ /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "vW" = ( -/obj/effect/turf_decal/tile/neutral/anticorner/contrasted{ - dir = 8 +/obj/structure/table/reinforced, +/obj/machinery/door/firedoor, +/obj/structure/noticeboard/directional/south, +/obj/effect/turf_decal/bot, +/obj/item/storage/bag/tray, +/obj/item/kitchen/fork{ + pixel_x = -7; + pixel_y = 2 }, /turf/open/floor/iron, -/area/centcom/central_command_areas/evacuation) +/area/centcom/tdome/observation) "vX" = ( -/obj/effect/turf_decal/tile/blue/anticorner/contrasted{ - dir = 4 +/obj/effect/turf_decal/tile/dark_blue{ + dir = 8 + }, +/obj/effect/turf_decal/tile/dark_blue{ + dir = 8 + }, +/turf/open/floor/iron/white/side{ + dir = 5 }, -/turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "vY" = ( /obj/effect/turf_decal/tile/brown/half/contrasted{ @@ -4749,17 +4864,16 @@ /turf/open/floor/iron, /area/centcom/central_command_areas/supply) "vZ" = ( -/obj/effect/turf_decal/tile/blue/half/contrasted{ - dir = 4 +/obj/effect/turf_decal/siding/dark_blue{ + dir = 1 }, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "wa" = ( -/obj/effect/turf_decal/tile/blue/half/contrasted{ - dir = 8 - }, +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/siding/yellow/corner, /turf/open/floor/iron, -/area/centcom/central_command_areas/evacuation) +/area/centcom/central_command_areas/supply) "wb" = ( /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 1 @@ -4958,11 +5072,21 @@ /obj/effect/turf_decal/stripes/line{ dir = 8 }, +/obj/effect/turf_decal/siding/dark_blue{ + dir = 4 + }, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "wL" = ( -/obj/effect/turf_decal/tile/blue/anticorner/contrasted, -/turf/open/floor/iron, +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 8 + }, +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 8 + }, +/turf/open/floor/iron/white/side{ + dir = 4 + }, /area/centcom/central_command_areas/evacuation) "wN" = ( /obj/effect/turf_decal/siding/wood{ @@ -5082,12 +5206,14 @@ dir = 8 }, /obj/machinery/status_display/evac/directional/west, +/obj/effect/turf_decal/siding/dark_blue{ + dir = 4 + }, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "xt" = ( -/obj/effect/turf_decal/tile/neutral/anticorner/contrasted{ - dir = 4 - }, +/obj/effect/turf_decal/tile/dark_blue/fourcorners, +/obj/effect/turf_decal/tile/dark_blue/fourcorners, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "xv" = ( @@ -5188,16 +5314,14 @@ /turf/open/floor/iron/dark, /area/centcom/central_command_areas/courtroom) "xY" = ( -/obj/effect/turf_decal/tile/blue/anticorner/contrasted{ - dir = 1 +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/siding/yellow{ + dir = 4 }, /turf/open/floor/iron, -/area/centcom/central_command_areas/evacuation) +/area/centcom/central_command_areas/supply) "xZ" = ( -/obj/effect/turf_decal/tile/blue/anticorner/contrasted{ - dir = 8 - }, -/turf/open/floor/iron, +/turf/open/floor/iron/white, /area/centcom/central_command_areas/evacuation) "yb" = ( /obj/item/kirbyplants/organic/plant21, @@ -5314,10 +5438,17 @@ /turf/open/floor/iron, /area/centcom/tdome/arena) "yB" = ( -/obj/effect/turf_decal/tile/blue{ +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 8 + }, +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 8 + }, +/obj/effect/turf_decal/tile/dark_blue, +/obj/effect/turf_decal/tile/dark_blue, +/turf/open/floor/iron/white/corner{ dir = 4 }, -/turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "yC" = ( /obj/structure/filingcabinet/security, @@ -5463,16 +5594,28 @@ /turf/open/floor/iron, /area/centcom/central_command_areas/control) "zl" = ( -/obj/effect/turf_decal/tile/blue{ +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 4 + }, +/obj/effect/turf_decal/tile/dark_blue/half/contrasted{ + dir = 4 + }, +/obj/effect/turf_decal/tile/dark_blue{ + dir = 8 + }, +/obj/effect/turf_decal/tile/dark_blue{ + dir = 8 + }, +/turf/open/floor/iron/white/corner{ dir = 1 }, -/turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "zm" = ( -/obj/effect/turf_decal/tile/blue/half/contrasted{ +/obj/effect/turf_decal/tile/dark_blue/half/contrasted, +/obj/effect/turf_decal/tile/dark_blue/half/contrasted, +/turf/open/floor/iron/white/side{ dir = 1 }, -/turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "zn" = ( /obj/machinery/light/directional/north, @@ -5749,7 +5892,9 @@ /turf/open/floor/iron/white, /area/centcom/central_command_areas/control) "At" = ( -/obj/effect/turf_decal/siding/yellow, +/obj/effect/turf_decal/siding/dark_blue/corner{ + dir = 1 + }, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "Av" = ( @@ -5863,8 +6008,14 @@ /area/centcom/central_command_areas/evacuation) "AX" = ( /obj/structure/table, -/obj/item/toy/sword, -/obj/item/gun/ballistic/shotgun/toy/crossbow, +/obj/item/toy/sword{ + pixel_y = 8; + pixel_x = 14 + }, +/obj/item/gun/ballistic/shotgun/toy/crossbow{ + pixel_y = 11; + pixel_x = -2 + }, /obj/effect/turf_decal/stripes/line, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) @@ -5894,6 +6045,15 @@ }, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) +"Bd" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 4 + }, +/obj/effect/turf_decal/siding/dark_blue/corner{ + dir = 8 + }, +/turf/open/floor/iron, +/area/centcom/central_command_areas/evacuation) "Bh" = ( /obj/machinery/processor, /obj/effect/turf_decal/stripes/end, @@ -5943,9 +6103,11 @@ "Bs" = ( /obj/structure/table/reinforced, /obj/machinery/door/firedoor, -/obj/item/storage/bag/tray, -/obj/item/kitchen/fork, /obj/effect/turf_decal/bot, +/obj/item/food/mint{ + pixel_x = 6; + pixel_y = -4 + }, /turf/open/floor/iron, /area/centcom/tdome/observation) "Bu" = ( @@ -6361,6 +6523,10 @@ }, /turf/open/floor/iron, /area/centcom/central_command_areas/control) +"Dg" = ( +/obj/machinery/light/floor, +/turf/open/floor/iron/white, +/area/centcom/central_command_areas/evacuation) "Di" = ( /turf/closed/indestructible/riveted, /area/centcom/ai_multicam_room) @@ -6883,6 +7049,13 @@ /obj/effect/turf_decal/tile/blue/fourcorners, /turf/open/floor/iron/white, /area/centcom/central_command_areas/evacuation/ship) +"Gl" = ( +/obj/effect/turf_decal/tile/brown/half/contrasted{ + dir = 4 + }, +/obj/effect/turf_decal/bot, +/turf/open/floor/iron, +/area/centcom/central_command_areas/supply) "Gm" = ( /obj/structure/table/reinforced, /obj/item/paper_bin{ @@ -6942,6 +7115,12 @@ }, /turf/open/floor/iron, /area/centcom/central_command_areas/prison) +"GD" = ( +/obj/effect/turf_decal/siding/dark_blue{ + dir = 10 + }, +/turf/open/floor/iron, +/area/centcom/central_command_areas/evacuation) "GI" = ( /obj/effect/turf_decal/tile/red/half/contrasted{ dir = 1 @@ -7129,6 +7308,11 @@ /obj/machinery/light/directional/west, /turf/open/floor/iron/white, /area/centcom/central_command_areas/evacuation/ship) +"HV" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/structure/closet/secure_closet/quartermaster, +/turf/open/floor/iron/dark, +/area/centcom/central_command_areas/supply) "HY" = ( /obj/machinery/photocopier, /obj/machinery/button/door/indestructible{ @@ -7190,12 +7374,17 @@ }, /obj/item/reagent_containers/cup/glass/mug/britcup, /obj/effect/turf_decal/bot, +/obj/item/clothing/head/utility/chefhat{ + pixel_y = 11; + pixel_x = 5 + }, /turf/open/floor/iron, /area/centcom/tdome/observation) "Ie" = ( -/obj/structure/sign/poster/contraband/syndicate_recruitment, -/turf/closed/indestructible/riveted, -/area/centcom/central_command_areas/admin) +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/siding/yellow, +/turf/open/floor/iron, +/area/centcom/central_command_areas/supply) "If" = ( /obj/machinery/newscaster{ pixel_x = -32 @@ -7270,6 +7459,12 @@ /obj/machinery/light/directional/south, /turf/open/floor/grass, /area/centcom/tdome/administration) +"IX" = ( +/obj/effect/turf_decal/siding/dark_blue{ + dir = 6 + }, +/turf/open/floor/iron, +/area/centcom/central_command_areas/evacuation) "Jb" = ( /turf/closed/indestructible/riveted, /area/centcom/central_command_areas/prison/cells) @@ -7343,6 +7538,9 @@ dir = 8 }, /obj/machinery/light/directional/west, +/obj/effect/turf_decal/siding/dark_blue{ + dir = 4 + }, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "JV" = ( @@ -7623,6 +7821,12 @@ /obj/effect/spawner/structure/window/reinforced/shuttle, /turf/open/floor/plating, /area/centcom/central_command_areas/evacuation/ship) +"Lx" = ( +/obj/effect/turf_decal/siding/dark_blue/corner{ + dir = 4 + }, +/turf/open/floor/iron, +/area/centcom/central_command_areas/evacuation) "Lz" = ( /obj/structure/railing{ dir = 10; @@ -7878,7 +8082,10 @@ "MI" = ( /obj/machinery/light/directional/north, /obj/structure/table/wood, -/obj/structure/reagent_dispensers/beerkeg, +/obj/structure/reagent_dispensers/beerkeg{ + pixel_y = 6; + pixel_x = 5 + }, /turf/open/floor/iron/dark, /area/centcom/central_command_areas/supplypod) "MJ" = ( @@ -7955,14 +8162,23 @@ /area/centcom/central_command_areas/control) "Nh" = ( /obj/structure/table/wood, -/obj/item/storage/box/drinkingglasses, -/obj/item/reagent_containers/cup/glass/shaker, +/obj/item/storage/box/drinkingglasses{ + pixel_y = 14; + pixel_x = -4 + }, +/obj/item/reagent_containers/cup/glass/shaker{ + pixel_y = -8; + pixel_x = -10 + }, /turf/open/floor/iron/dark, /area/centcom/central_command_areas/supplypod) "Nk" = ( -/obj/structure/sign/poster/contraband/syndicate_pistol, -/turf/closed/indestructible/riveted, -/area/centcom/central_command_areas/admin) +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/siding/yellow{ + dir = 1 + }, +/turf/open/floor/iron, +/area/centcom/central_command_areas/supply) "Nm" = ( /obj/effect/turf_decal/trimline/blue/filled/line{ dir = 4 @@ -8038,12 +8254,8 @@ /turf/open/floor/iron, /area/centcom/central_command_areas/control) "NH" = ( -/obj/structure/table/reinforced, -/obj/item/food/mint, -/obj/item/reagent_containers/condiment/enzyme{ - pixel_y = 5 - }, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/machinery/oven/range, /turf/open/floor/iron/dark, /area/centcom/tdome/observation) "NI" = ( @@ -8289,15 +8501,30 @@ "OM" = ( /obj/structure/table/reinforced, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/item/papercutter{ + pixel_y = 6; + pixel_x = 2 + }, /turf/open/floor/iron/dark, /area/centcom/central_command_areas/supply) "OO" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, /obj/structure/table/reinforced, /obj/machinery/reagentgrinder{ desc = "Used to grind things up into raw materials and liquids."; pixel_y = 5 }, -/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/item/reagent_containers/condiment/saltshaker{ + pixel_x = -8; + pixel_y = 5 + }, +/obj/item/reagent_containers/condiment/peppermill{ + pixel_x = -8 + }, +/obj/item/knife/kitchen{ + pixel_x = 11; + pixel_y = -12 + }, /turf/open/floor/iron/dark, /area/centcom/tdome/observation) "OP" = ( @@ -8398,10 +8625,23 @@ "Pg" = ( /obj/machinery/computer/auxiliary_base/directional/north, /obj/structure/table/reinforced, -/obj/item/clipboard, -/obj/item/folder/yellow, -/obj/item/pen/red, +/obj/item/clipboard{ + pixel_y = 4; + pixel_x = 2 + }, +/obj/item/folder/yellow{ + pixel_y = 7; + pixel_x = 5 + }, +/obj/item/pen/red{ + pixel_y = 2; + pixel_x = 3 + }, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/item/paper_bin{ + pixel_x = -15; + pixel_y = 7 + }, /turf/open/floor/iron/dark, /area/centcom/central_command_areas/supply) "Pk" = ( @@ -8510,9 +8750,8 @@ /turf/closed/indestructible/riveted, /area/centcom/central_command_areas/admin) "PG" = ( -/obj/effect/turf_decal/tile/blue/half/contrasted{ - dir = 4 - }, +/obj/effect/turf_decal/tile/dark_blue/fourcorners, +/obj/effect/turf_decal/tile/dark_blue/fourcorners, /obj/machinery/light/floor, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) @@ -8703,8 +8942,9 @@ /turf/open/floor/iron, /area/centcom/tdome/observation) "Qx" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, /obj/effect/turf_decal/siding/yellow{ - dir = 1 + dir = 8 }, /turf/open/floor/iron, /area/centcom/central_command_areas/supply) @@ -8853,6 +9093,9 @@ /obj/effect/turf_decal/stripes/line{ dir = 8 }, +/obj/effect/turf_decal/siding/dark_blue{ + dir = 4 + }, /turf/open/floor/iron, /area/centcom/central_command_areas/evacuation) "Rh" = ( @@ -8910,9 +9153,12 @@ /area/centcom/central_command_areas/admin) "Ro" = ( /obj/structure/table/wood, -/obj/item/storage/box/donkpockets, +/obj/item/storage/box/donkpockets{ + pixel_y = 19; + pixel_x = 5 + }, /obj/item/storage/fancy/cigarettes/cigars/cohiba{ - pixel_y = 3 + pixel_y = 6 }, /turf/open/floor/iron/dark, /area/centcom/central_command_areas/supplypod) @@ -8944,17 +9190,9 @@ /turf/open/floor/iron/dark, /area/centcom/tdome/administration) "Rx" = ( -/obj/structure/table/reinforced, -/obj/item/reagent_containers/condiment/saltshaker{ - pixel_x = -8; - pixel_y = 5 - }, -/obj/item/reagent_containers/condiment/peppermill{ - pixel_x = -8 - }, -/obj/item/knife/kitchen, /obj/machinery/newscaster/directional/west, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/machinery/icecream_vat, /turf/open/floor/iron/dark, /area/centcom/tdome/observation) "Ry" = ( @@ -9080,6 +9318,16 @@ "Si" = ( /turf/open/floor/iron, /area/centcom/central_command_areas/supplypod/loading/two) +"Sj" = ( +/obj/structure/chair{ + dir = 4 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/obj/effect/turf_decal/siding/dark_blue/corner, +/turf/open/floor/iron, +/area/centcom/central_command_areas/evacuation) "Sk" = ( /obj/structure/table/reinforced, /obj/item/storage/medkit/regular, @@ -9255,10 +9503,12 @@ "Tj" = ( /obj/structure/table/reinforced, /obj/item/reagent_containers/cup/glass/bottle/whiskey{ - pixel_y = 5 + pixel_y = 19; + pixel_x = 7 }, /obj/item/clothing/mask/cigarette/cigar/havana{ - pixel_x = 2 + pixel_x = -6; + pixel_y = 5 }, /turf/open/floor/iron, /area/centcom/central_command_areas/supplypod) @@ -9286,10 +9536,16 @@ /area/centcom/central_command_areas/admin) "Tp" = ( /obj/structure/table/reinforced, -/obj/item/clothing/suit/apron/chef, -/obj/item/kitchen/rollingpin, /obj/machinery/status_display/evac/directional/west, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/item/clothing/suit/apron/chef, +/obj/item/kitchen/rollingpin{ + pixel_y = 2 + }, +/obj/item/reagent_containers/condiment/enzyme{ + pixel_y = 15; + pixel_x = -7 + }, /turf/open/floor/iron/dark, /area/centcom/tdome/observation) "Tq" = ( @@ -9382,10 +9638,22 @@ "TO" = ( /obj/machinery/keycard_auth/directional/south, /obj/structure/table/reinforced, -/obj/item/stack/package_wrap, -/obj/item/stack/cable_coil, -/obj/item/hand_labeler, +/obj/item/stack/package_wrap{ + pixel_y = 11; + pixel_x = -16 + }, +/obj/item/stack/cable_coil{ + pixel_y = 4; + pixel_x = -10 + }, +/obj/item/hand_labeler{ + pixel_y = 1 + }, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/item/hand_labeler_refill{ + pixel_x = 8; + pixel_y = 12 + }, /turf/open/floor/iron/dark, /area/centcom/central_command_areas/supply) "TS" = ( @@ -9404,7 +9672,9 @@ /area/centcom/central_command_areas/admin) "TT" = ( /obj/structure/table/reinforced, -/obj/item/storage/fancy/donut_box, +/obj/item/storage/fancy/donut_box{ + pixel_y = 13 + }, /turf/open/floor/iron, /area/centcom/central_command_areas/supplypod) "TU" = ( @@ -9535,7 +9805,7 @@ /turf/open/floor/iron/dark, /area/centcom/central_command_areas/supplypod) "Ux" = ( -/turf/open/indestructible/hierophant, +/turf/open/indestructible/dark, /area/centcom/central_command_areas/admin) "Uz" = ( /obj/effect/turf_decal/stripes/line{ @@ -9638,8 +9908,15 @@ /turf/open/floor/iron/dark, /area/centcom/central_command_areas/control) "Vd" = ( -/obj/effect/turf_decal/tile/blue/fourcorners, -/turf/open/floor/iron, +/obj/effect/turf_decal/tile/dark_blue{ + dir = 4 + }, +/obj/effect/turf_decal/tile/dark_blue{ + dir = 4 + }, +/turf/open/floor/iron/white/side{ + dir = 10 + }, /area/centcom/central_command_areas/evacuation) "Vf" = ( /obj/item/kirbyplants/organic/plant21{ @@ -9828,12 +10105,14 @@ /turf/open/floor/iron, /area/centcom/central_command_areas/supplypod/loading/four) "VT" = ( -/obj/structure/rack, -/obj/item/storage/box/donkpockets, -/obj/item/storage/box/donkpockets, -/obj/item/clothing/head/utility/chefhat, /obj/machinery/status_display/evac/directional/west, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/structure/table/reinforced, +/obj/machinery/microwave{ + desc = "Cooks and boils stuff, somehow."; + pixel_x = -3; + pixel_y = 5 + }, /turf/open/floor/iron/dark, /area/centcom/tdome/observation) "VY" = ( @@ -9913,6 +10192,13 @@ /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron/dark, /area/centcom/central_command_areas/courtroom) +"Ws" = ( +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/effect/turf_decal/siding/yellow/corner{ + dir = 8 + }, +/turf/open/floor/iron, +/area/centcom/central_command_areas/supply) "Wt" = ( /obj/effect/turf_decal/tile/brown/half/contrasted, /turf/open/floor/iron, @@ -49390,7 +49676,7 @@ aa aa aa aa -Ie +On tW Ux On @@ -49647,9 +49933,9 @@ aa aa aa aa -Nk +On kh -tW +Ux On On Oc @@ -51172,20 +51458,20 @@ aa aa aa aa -aa +lZ iF iQ -oJ -oJ -oJ -oJ -oJ -oJ -oJ -oJ -oJ -oJ -oJ +wa +xY +xY +xY +xY +xY +xY +xY +xY +xY +ul ln iN zn @@ -51429,20 +51715,20 @@ aa aa aa aa -aa +lZ iG iR -oJ -Qx +Ie iO -oJ iO oJ iO oJ iO -uG oJ +iO +iO +Nk lo lJ hz @@ -51686,23 +51972,23 @@ aa aa aa aa -aa +lZ iG iR -oJ -Qx +Ie jp -oJ jp oJ jp oJ jp -uG oJ +jp +jp +Nk lo iF -XA +HV mJ lq XK @@ -51943,20 +52229,20 @@ aa aa aa aa -aa +lZ iF iR -oJ -Qx +Ie jp -oJ jp oJ jp oJ jp -uG oJ +jp +jp +Nk EK iF iF @@ -52004,7 +52290,7 @@ eI zw Id Bs -di +vW QC QC xv @@ -52200,20 +52486,20 @@ aa aa aa aa -aa +lZ iG iR -oJ -Qx +Ie jp -oJ jp oJ jp oJ jp -uG oJ +jp +jp +Nk lp iF mi @@ -52457,20 +52743,20 @@ aa aa aa aa -aa +lZ iG iR -oJ -oJ -oJ -oJ -oJ -oJ -oJ -oJ -oJ -oJ -oJ +Ws +Qx +Qx +Qx +Qx +Qx +Qx +Qx +Qx +Qx +kr lo lK iR @@ -52714,11 +53000,11 @@ aa aa aa aa -aa +lZ iF iS iZ -iZ +Gl jq ju eq @@ -52726,8 +53012,8 @@ vY jN jQ jN -iZ -iZ +Gl +Gl lq iF mj @@ -59169,7 +59455,7 @@ qz cg sN bL -uA +Sj JU xr wK @@ -59425,17 +59711,17 @@ qz qy cg sO -tT -us -us -us -us -us -us -us -us +bJ +IX +xt +xt +xt +xt +xt +xt +xt us -At +Lx AW cg qz @@ -59683,16 +59969,16 @@ qy fm sP tT -us +xt ve -vW -us -us -us -us -us -us -At +xt +xt +xt +xt +xt +xt +xt +vZ AX fm Cd @@ -59940,16 +60226,16 @@ qz cg fE tT -us +xt vf vX -vW -us -us -us -us -us -At +xt +PG +xt +xt +xt +xt +vZ aP cg qy @@ -60197,16 +60483,16 @@ qz cg sQ tT -us +xt vf -Vd +xZ vX -vW -us -us -us -us -At +xt +xt +xt +xt +xt +vZ AY cg fm @@ -60454,16 +60740,16 @@ fm cg sQ tT -us +xt ve -vZ -vZ -PG -vZ -vZ +wL +wL +wL +wL +wL yB -us -At +xt +vZ AY cg pg @@ -60711,16 +60997,16 @@ qW rT sO tT -us +xt vg Vd -Vd -Vd -xY -wa +xZ +xZ +dh +dh zl -us -At +xt +vZ AW BN Cf @@ -60968,16 +61254,16 @@ qX rU sO tT -us -lZ -vg -Vd -Vd +xt +rq +xt Vd -yB -lZ -us -At +Dg +vX +xt +rq +xt +vZ DU BO Cg @@ -61225,16 +61511,16 @@ qY rV sO tT -us +xt ve -vZ wL -Vd -Vd -Vd +wL +xZ +xZ +vX yB -us -At +xt +vZ AW BP Cf @@ -61482,16 +61768,16 @@ fm cg sQ tT -us +xt vg -wa -wa dh -wa -wa +dh +dh +dh +dh zl -us -At +xt +vZ AY cg VY @@ -61739,16 +62025,16 @@ qy cg sR tT -us -us -us -us xt -xZ +xt +xt +xt +xt Vd +xZ zm -us -At +xt +vZ AZ cg fm @@ -61996,16 +62282,16 @@ qz cg fE tT -us -us -us -us -us xt -xZ +xt +xt +xt +xt +xt +Vd zm -us -At +xt +vZ aP cg qz @@ -62253,16 +62539,16 @@ qy fm sS tT -us -us -us -us +xt +xt +xt +xt rq -us +xt xt zl -us -At +xt +vZ Bb fm Cd @@ -62509,16 +62795,16 @@ qz qy cg sO -tT -us -us -us -us -us -us -us -us -us +da +GD +xt +xt +xt +xt +xt +xt +xt +bo At AW cg @@ -62767,15 +63053,15 @@ qy cg sT tU -tU -tU -tU -tU -tU -tU -tU -tU -tU +Bd +uG +uG +uG +uG +uG +uG +uG +dc tU Bc cg diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index 639dc110d02a0..e36591696eb35 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -2817,6 +2817,10 @@ /obj/structure/disposalpipe/segment{ dir = 6 }, +/obj/item/storage/box/donkpockets/donkpocketberry{ + pixel_y = 10; + pixel_x = -6 + }, /turf/open/floor/iron, /area/station/engineering/break_room) "amA" = ( @@ -8570,6 +8574,7 @@ id = "pharmacy_shutters_2"; name = "Pharmacy Shutters" }, +/obj/machinery/door/firedoor, /turf/open/floor/iron/white, /area/station/medical/pharmacy) "bSd" = ( @@ -10110,7 +10115,7 @@ layer = 3.1; linked_elevator_id = "tram_xeno_lift"; pixel_y = 2; - preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") + preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") }, /turf/closed/wall, /area/station/science/xenobiology) @@ -14689,8 +14694,10 @@ /obj/machinery/smartfridge/chemistry/preloaded, /obj/machinery/door/poddoor/shutters/preopen{ id = "pharmacy_shutters_2"; - name = "Pharmacy Shutters" + name = "Pharmacy Shutters"; + dir = 4 }, +/obj/machinery/door/firedoor, /turf/open/floor/plating, /area/station/medical/pharmacy) "dYm" = ( @@ -15157,7 +15164,7 @@ /area/station/service/library/lounge) "ejJ" = ( /obj/structure/rack, -/obj/item/gun/energy/laser/practice{ +/obj/item/gun/energy/laser/carbine/practice{ pixel_x = 2; pixel_y = 5 }, @@ -19727,7 +19734,7 @@ /area/station/security/processing) "fWK" = ( /obj/machinery/computer/atmos_control/oxygen_tank{ - atmos_chambers = list("o2ordance"="Oxygen Supply") + atmos_chambers = list("o2ordance"="Oxygen Supply") }, /obj/effect/turf_decal/stripes/line, /obj/machinery/airalarm/directional/north, @@ -20301,7 +20308,7 @@ }, /obj/machinery/elevator_control_panel/directional/west{ linked_elevator_id = "tram_sci_lift"; - preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") + preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") }, /turf/open/floor/plating/elevatorshaft, /area/station/science/lower) @@ -29165,7 +29172,7 @@ /obj/structure/industrial_lift/public, /obj/machinery/elevator_control_panel/directional/west{ linked_elevator_id = "tram_cargo_lift"; - preset_destination_names = list("2"="Lower Deck","3"="Upper Deck"); + preset_destination_names = list("2"="Lower Deck","3"="Upper Deck"); req_access = list("mining") }, /obj/effect/abstract/elevator_music_zone{ @@ -35035,7 +35042,7 @@ }, /obj/machinery/elevator_control_panel/directional/west{ linked_elevator_id = "tram_perma_lift"; - preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") + preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") }, /turf/open/floor/plating/elevatorshaft, /area/station/security/execution/transfer) @@ -38902,6 +38909,15 @@ }, /turf/open/floor/iron, /area/station/commons/storage/primary) +"mOJ" = ( +/obj/effect/spawner/structure/window/reinforced, +/obj/machinery/door/poddoor/shutters/preopen{ + id = "pharmacy_shutters_2"; + name = "Pharmacy Shutters"; + dir = 4 + }, +/turf/open/floor/plating, +/area/station/medical/pharmacy) "mOM" = ( /obj/effect/turf_decal/trimline/yellow/filled/line{ dir = 4 @@ -39125,7 +39141,7 @@ }, /obj/machinery/elevator_control_panel/directional/north{ linked_elevator_id = "tram_upper_center_lift"; - preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") + preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") }, /obj/effect/turf_decal/trimline/dark_red/warning{ dir = 1 @@ -42017,14 +42033,9 @@ /turf/open/floor/iron/white, /area/station/science/lobby) "nUF" = ( -/obj/effect/turf_decal/box/corners, -/obj/effect/turf_decal/box/corners{ - dir = 4 - }, /obj/effect/decal/cleanable/dirt/dust, /obj/machinery/light/directional/north, /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2, -/obj/effect/landmark/bitrunning/station_reward_spawn, /turf/open/floor/iron/dark/smooth_large, /area/station/bitrunning/den) "nUM" = ( @@ -44059,7 +44070,7 @@ layer = 3.1; linked_elevator_id = "tram_xeno_lift"; pixel_y = 2; - preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") + preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") }, /turf/closed/wall/r_wall, /area/station/science/xenobiology) @@ -45391,17 +45402,12 @@ /turf/open/floor/plating, /area/station/commons/vacant_room) "plH" = ( -/obj/effect/turf_decal/box/corners{ - dir = 8 - }, -/obj/effect/turf_decal/box/corners{ - dir = 1 - }, /obj/effect/decal/cleanable/oil/streak, /obj/structure/sign/poster/random/directional/north, /obj/machinery/power/apc/auto_name/directional/west, /obj/structure/cable, -/obj/effect/landmark/bitrunning/station_reward_spawn, +/obj/machinery/byteforge, +/obj/effect/turf_decal/box, /turf/open/floor/iron/dark/smooth_large, /area/station/bitrunning/den) "plQ" = ( @@ -51543,11 +51549,12 @@ }, /obj/machinery/door/poddoor/shutters/preopen{ id = "pharmacy_shutters_2"; - name = "Pharmacy Shutters" + name = "Pharmacy Shutters"; + dir = 4 }, /obj/machinery/door/firedoor, /obj/machinery/door/window/left/directional/south{ - dir = 8; + dir = 4; name = "Chemistry Desk"; req_access = list("pharmacy") }, @@ -53577,7 +53584,7 @@ /turf/open/floor/iron/white, /area/station/science/lower) "sha" = ( -/mob/living/simple_animal/sloth/paperwork, +/mob/living/basic/sloth/paperwork, /turf/open/floor/glass, /area/station/cargo/storage) "shw" = ( @@ -55624,7 +55631,7 @@ }, /obj/machinery/power/apc/auto_name/directional/north, /obj/structure/table/glass, -/obj/machinery/microwave, +/obj/machinery/microwave/engineering/cell_included, /obj/structure/cable, /obj/machinery/light/warm/directional/west, /turf/open/floor/iron, @@ -60272,7 +60279,7 @@ /obj/structure/industrial_lift/public, /obj/machinery/elevator_control_panel/directional/east{ linked_elevator_id = "tram_lower_center_lift"; - preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") + preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") }, /turf/open/floor/plating/elevatorshaft, /area/station/maintenance/tram/mid) @@ -61790,7 +61797,7 @@ /obj/structure/industrial_lift/public, /obj/machinery/elevator_control_panel/directional/south{ linked_elevator_id = "tram_dorm_lift"; - preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") + preset_destination_names = list("2"="Lower Deck","3"="Upper Deck") }, /obj/structure/railing, /turf/open/floor/plating/elevatorshaft, @@ -64522,9 +64529,7 @@ /area/station/hallway/primary/tram/right) "vPo" = ( /obj/structure/table/reinforced, -/obj/machinery/microwave{ - pixel_y = 6 - }, +/obj/machinery/microwave/engineering/cell_included, /obj/effect/turf_decal/tile/neutral/opposingcorners{ dir = 1 }, @@ -80997,7 +81002,7 @@ aac aac aac aac -aac +aaR aac aac vXM @@ -81254,7 +81259,7 @@ aac aac aac aac -aaR +aac aac aac vXM @@ -166314,7 +166319,7 @@ sBr jyH dYi rqE -bqg +mOJ jyH jyH jyH diff --git a/_maps/safehouses/bathroom.dmm b/_maps/safehouses/bathroom.dmm new file mode 100644 index 0000000000000..62e928ac0543b --- /dev/null +++ b/_maps/safehouses/bathroom.dmm @@ -0,0 +1,155 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"c" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/extinguisher_cabinet/directional/north, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"e" = ( +/obj/effect/spawner/structure/window, +/obj/effect/decal/cleanable/blood/splatter/over_window, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"q" = ( +/obj/structure/urinal/directional/west, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"s" = ( +/obj/effect/landmark/bitrunning/cache_goal_turf, +/obj/effect/turf_decal/loading_area{ + dir = 4 + }, +/obj/machinery/light/small/blacklight/directional/east, +/turf/open/floor/bitrunning_transport, +/area/virtual_domain/safehouse) +"v" = ( +/obj/machinery/light/small/blacklight/directional/east, +/obj/effect/decal/cleanable/blood, +/obj/effect/decal/cleanable/blood/tracks, +/obj/vehicle/ridden/wheelchair, +/obj/effect/spawner/random/medical/injector, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"x" = ( +/obj/structure/railing/corner/end{ + dir = 4 + }, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"y" = ( +/obj/effect/decal/cleanable/dirt, +/obj/machinery/light/small/blacklight/directional/west, +/obj/effect/spawner/random/medical/medkit, +/obj/effect/spawner/random/medical/medkit, +/obj/effect/spawner/random/medical/minor_healing, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"z" = ( +/obj/structure/sign/poster/abductor/random/directional/east, +/obj/effect/decal/cleanable/blood/tracks, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"D" = ( +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"G" = ( +/turf/closed/wall/mineral/silver, +/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) +"M" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/toilet{ + dir = 4 + }, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"N" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"T" = ( +/obj/effect/landmark/bitrunning/hololadder_spawn, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"V" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/extinguisher_cabinet/directional/north, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"X" = ( +/obj/structure/fans/tiny, +/obj/machinery/door/airlock/freezer, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) +"Z" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/light/small/blacklight/directional/west, +/turf/open/floor/iron/freezer, +/area/virtual_domain/safehouse) + +(1,1,1) = {" +G +G +G +G +G +G +"} +(2,1,1) = {" +G +Z +M +q +y +G +"} +(3,1,1) = {" +G +c +D +a +T +G +"} +(4,1,1) = {" +X +a +D +D +T +G +"} +(5,1,1) = {" +G +V +x +N +T +G +"} +(6,1,1) = {" +G +s +J +z +v +e +"} +(7,1,1) = {" +G +G +G +G +G +G +"} diff --git a/_maps/templates/holodeck_firingrange.dmm b/_maps/templates/holodeck_firingrange.dmm index 67b6cd5e0d38c..cb727801a052d 100644 --- a/_maps/templates/holodeck_firingrange.dmm +++ b/_maps/templates/holodeck_firingrange.dmm @@ -122,6 +122,7 @@ "N" = ( /obj/structure/rack, /obj/item/gun/energy/laser/practice, +/obj/item/gun/energy/laser/carbine/practice, /obj/item/clothing/ears/earmuffs, /obj/effect/turf_decal/tile/green/half/contrasted, /turf/open/floor/holofloor, diff --git a/_maps/virtual_domains/beach_bar.dmm b/_maps/virtual_domains/beach_bar.dmm index 408d3c0cda122..29fe04d82837f 100644 --- a/_maps/virtual_domains/beach_bar.dmm +++ b/_maps/virtual_domains/beach_bar.dmm @@ -159,7 +159,7 @@ pixel_x = -8; pixel_y = -1 }, -/obj/item/reagent_containers/cup/glass/drinkingglass/virtual_domain{ +/obj/item/reagent_containers/cup/glass/drinkingglass/filled/virtual_domain{ pixel_y = 8; pixel_x = 5 }, @@ -424,8 +424,8 @@ /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{ +/obj/item/reagent_containers/cup/glass/drinkingglass/filled/virtual_domain, +/obj/item/reagent_containers/cup/glass/drinkingglass/filled/virtual_domain{ pixel_x = -4; pixel_y = 8 }, @@ -878,11 +878,11 @@ /area/virtual_domain/powered) "Mp" = ( /obj/structure/table/wood, -/obj/item/reagent_containers/cup/glass/drinkingglass/virtual_domain{ +/obj/item/reagent_containers/cup/glass/drinkingglass/filled/virtual_domain{ pixel_y = 7; pixel_x = 4 }, -/obj/item/reagent_containers/cup/glass/drinkingglass/virtual_domain, +/obj/item/reagent_containers/cup/glass/drinkingglass/filled/virtual_domain, /turf/open/floor/wood, /area/virtual_domain/powered) "Mw" = ( diff --git a/_maps/virtual_domains/psyker_shuffle.dmm b/_maps/virtual_domains/psyker_shuffle.dmm new file mode 100644 index 0000000000000..9c846c650da61 --- /dev/null +++ b/_maps/virtual_domains/psyker_shuffle.dmm @@ -0,0 +1,1074 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/obj/structure/closet/crate/secure, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"b" = ( +/obj/item/restraints/legcuffs/beartrap/prearmed, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"e" = ( +/obj/item/gun/ballistic/shotgun/lethal, +/obj/structure/closet/crate/preopen, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"h" = ( +/obj/structure/closet/crate/preopen, +/obj/item/gun/ballistic/automatic/mini_uzi, +/obj/item/gun/ballistic/revolver{ + pixel_x = 2; + pixel_y = 3 + }, +/obj/item/gun/ballistic/revolver{ + pixel_x = -4; + pixel_y = 3 + }, +/obj/item/gun/ballistic/revolver{ + pixel_x = 1; + pixel_y = 1 + }, +/obj/item/gun/ballistic/revolver{ + pixel_x = -1; + pixel_y = 5 + }, +/obj/item/gun/ballistic/revolver{ + pixel_x = 5; + pixel_y = 3 + }, +/obj/item/gun/ballistic/revolver{ + pixel_x = -3 + }, +/obj/item/gun/ballistic/revolver{ + pixel_x = 5; + pixel_y = -2 + }, +/obj/item/gun/ballistic/revolver{ + pixel_x = -4; + pixel_y = 2 + }, +/obj/item/gun/ballistic/revolver, +/turf/template_noop, +/area/virtual_domain/safehouse) +"k" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/closed/indestructible/binary, +/area/ruin/space/has_grav/powered/virtual_domain) +"m" = ( +/obj/item/toy/gun{ + pixel_y = 3 + }, +/obj/structure/closet/crate/wooden, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"o" = ( +/turf/template_noop, +/area/template_noop) +"q" = ( +/obj/effect/landmark/bitrunning/crate_replacer, +/turf/closed/indestructible/binary, +/area/ruin/space/has_grav/powered/virtual_domain) +"r" = ( +/mob/living/simple_animal/hostile/mimic, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"s" = ( +/mob/living/simple_animal/hostile/mimic/crate, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"t" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"v" = ( +/obj/structure/closet/crate/hydroponics, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"x" = ( +/obj/item/gun/ballistic/shotgun/lethal, +/obj/item/gun/ballistic/revolver/mateba, +/obj/structure/closet/crate/preopen, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"B" = ( +/turf/template_noop, +/area/ruin/space/has_grav/powered/virtual_domain) +"F" = ( +/obj/structure/closet/crate/internals, +/obj/item/gun/ballistic/revolver/mateba, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"G" = ( +/obj/structure/closet/crate, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"K" = ( +/obj/structure/closet/crate/eva, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"M" = ( +/obj/machinery/door/airlock/abductor, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"N" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"O" = ( +/obj/structure/closet/crate/freezer/surplus_limbs, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"P" = ( +/obj/structure/closet/crate/wooden, +/obj/item/gun/ballistic/revolver/mateba, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"Q" = ( +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"R" = ( +/obj/structure/closet/crate/secure/loot, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"S" = ( +/obj/structure/closet/crate/secure/bitrunning/encrypted, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"U" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"X" = ( +/obj/item/gun/ballistic/shotgun/lethal, +/obj/structure/closet/crate/preopen, +/obj/projectile/bullet/shotgun_frag12, +/obj/projectile/bullet/shotgun_frag12, +/obj/projectile/bullet/shotgun_frag12, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"Y" = ( +/turf/closed/indestructible/binary, +/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 +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 +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 +o +o +o +o +o +o +o +"} +(4,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 +o +"} +(5,1,1) = {" +o +o +Y +Y +Y +Y +Y +Y +Y +Y +Y +Y +Y +Y +Y +Y +Y +k +o +o +o +o +o +o +o +o +"} +(6,1,1) = {" +o +o +Y +Y +r +Q +Q +Q +Q +Q +Q +Q +Q +Q +a +Y +Y +Y +o +o +o +o +o +o +o +o +"} +(7,1,1) = {" +o +o +Y +Y +Y +K +Q +a +Q +Q +Y +Q +Q +Q +Q +Q +Y +Y +o +o +o +o +o +o +o +o +"} +(8,1,1) = {" +o +o +Y +Y +Y +m +Q +Q +Q +Y +Y +Y +Q +Q +Q +Q +Y +Y +o +o +o +o +o +o +o +o +"} +(9,1,1) = {" +o +o +Y +X +Y +Y +Q +Q +Q +Q +K +Y +Q +r +Y +Q +Y +Y +o +o +o +o +o +o +o +o +"} +(10,1,1) = {" +o +o +Y +b +Q +Y +Y +Q +Q +Q +Q +Q +Q +Y +Y +Q +Q +Y +o +o +o +o +o +o +o +o +"} +(11,1,1) = {" +o +o +Y +Q +Q +Q +Q +Q +Q +Y +Q +Q +Q +Q +Q +Q +Q +Y +o +o +o +o +o +o +o +o +"} +(12,1,1) = {" +o +o +Y +a +Q +b +Q +O +Q +Y +Q +Q +S +Q +Q +Q +Q +Y +o +o +o +o +o +o +o +o +"} +(13,1,1) = {" +o +o +Y +b +Q +Y +Q +Q +Q +Q +Q +Q +Q +Q +Y +Y +Q +Y +Y +Y +Y +Y +Y +Y +Y +o +"} +(14,1,1) = {" +o +o +Y +a +a +Y +Q +Q +Q +Q +Q +Q +Q +Q +Y +Q +Q +Q +t +t +t +t +t +U +Y +Y +"} +(15,1,1) = {" +o +o +Y +Y +Y +Y +Q +G +Q +Y +Y +Y +Q +Q +Q +Q +Q +Q +t +t +t +t +t +t +B +Y +"} +(16,1,1) = {" +o +o +Y +r +Q +Q +Q +Q +Q +Y +Y +Y +a +Q +Q +Q +Q +Q +t +h +t +t +t +t +B +Y +"} +(17,1,1) = {" +o +o +Y +Q +Y +Q +Q +Q +O +Y +Y +Y +a +Q +Q +Q +Q +Q +t +t +t +t +t +t +B +Y +"} +(18,1,1) = {" +o +o +Y +Q +Y +Q +Y +Y +Y +Y +Y +Y +Q +Q +Q +Q +Q +Q +t +t +t +t +t +t +B +Y +"} +(19,1,1) = {" +o +o +Y +Q +Y +Q +Q +P +Y +Y +Q +Q +Q +Q +Y +Y +Q +Q +t +t +t +t +t +t +B +Y +"} +(20,1,1) = {" +o +o +Y +Q +Q +Q +Q +Q +Q +Q +Q +Q +Q +Q +Q +Y +Q +Q +t +t +t +t +t +N +B +Y +"} +(21,1,1) = {" +o +o +Y +a +Q +Q +Y +Q +Q +Y +Y +Q +Q +R +Q +Q +Q +Y +Y +Y +Y +Y +Y +Y +B +Y +"} +(22,1,1) = {" +o +o +Y +Y +Q +Q +Y +Q +Q +v +Q +Q +Q +Q +Q +Q +Q +Y +o +o +o +o +o +Y +B +Y +"} +(23,1,1) = {" +o +o +o +Y +Y +Q +Q +Q +Q +Q +Q +Q +Q +Q +Q +Q +Q +Y +o +o +o +o +o +Y +Y +Y +"} +(24,1,1) = {" +o +o +Y +Y +Y +Y +Q +Q +Q +Q +Q +Y +Q +Q +Q +Q +Q +Y +o +o +o +o +o +o +o +o +"} +(25,1,1) = {" +Y +Y +Y +a +Y +Y +Y +Y +Q +Q +Q +Y +Q +Y +Q +Q +Y +Y +o +o +o +o +o +o +o +o +"} +(26,1,1) = {" +Y +a +Q +e +Q +Y +a +Q +Q +Q +Q +Y +Q +Y +Y +a +Y +Y +o +o +o +o +o +o +o +o +"} +(27,1,1) = {" +Y +Q +Q +Q +s +M +Q +Q +Q +Q +Y +Y +Q +Y +Q +Q +Q +Y +Y +o +o +o +o +o +o +o +"} +(28,1,1) = {" +Y +Y +Q +Q +a +Y +Q +a +Q +Q +Q +F +Q +Y +s +Q +Q +a +Y +o +o +o +o +o +o +o +"} +(29,1,1) = {" +Y +Y +Q +Q +Q +Y +Y +Y +Y +Y +Y +Y +Y +Y +Q +K +Q +Q +Y +o +o +o +o +o +o +o +"} +(30,1,1) = {" +Y +Q +Q +Q +Q +Q +Q +Q +Q +Q +Q +Q +Q +a +Q +Q +Q +K +Y +o +o +o +o +o +o +o +"} +(31,1,1) = {" +Y +a +Q +s +Y +Y +Y +s +Q +Q +Q +Q +Y +Y +x +Y +Q +Y +Y +o +o +o +o +o +o +o +"} +(32,1,1) = {" +Y +a +Y +Y +Y +o +Y +Y +Q +a +Y +Y +Y +Y +Y +Y +Y +q +o +o +o +o +o +o +o +o +"} +(33,1,1) = {" +Y +Y +Y +o +o +o +o +Y +Y +Y +Y +o +o +o +o +o +o +o +o +o +o +o +o +o +o +o +"} diff --git a/_maps/virtual_domains/psyker_zombies.dmm b/_maps/virtual_domains/psyker_zombies.dmm new file mode 100644 index 0000000000000..339c4e15e4c62 --- /dev/null +++ b/_maps/virtual_domains/psyker_zombies.dmm @@ -0,0 +1,1215 @@ +//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) +"b" = ( +/obj/item/pizzabox/bomb/armed, +/obj/structure/rack, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"c" = ( +/obj/structure/sign/warning/directional/west, +/turf/open/chasm/lavaland, +/area/ruin/space/has_grav/powered/virtual_domain) +"h" = ( +/obj/structure/rack, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"i" = ( +/obj/structure/sign/warning/directional/east, +/turf/open/chasm/lavaland, +/area/ruin/space/has_grav/powered/virtual_domain) +"o" = ( +/turf/template_noop, +/area/template_noop) +"p" = ( +/obj/structure/rack, +/obj/item/reagent_containers/cup/glass/bottle/rootbeer{ + pixel_x = 3 + }, +/obj/item/reagent_containers/cup/glass/bottle/rootbeer{ + pixel_x = -3 + }, +/obj/item/reagent_containers/cup/glass/bottle/rootbeer{ + pixel_y = 1 + }, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"r" = ( +/obj/effect/baseturf_helper/virtual_domain, +/turf/template_noop, +/area/virtual_domain/safehouse) +"t" = ( +/turf/template_noop, +/area/virtual_domain/safehouse) +"x" = ( +/obj/effect/mapping_helpers/airlock/locked, +/obj/machinery/door/airlock/abductor, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"A" = ( +/obj/effect/spawner/random/trash/caution_sign, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"B" = ( +/obj/machinery/door/airlock/abductor, +/obj/effect/mapping_helpers/airlock/abandoned, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"F" = ( +/obj/structure/mystery_box/guns, +/turf/open/indestructible/dark, +/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/effect/mob_spawn/corpse/human/zombie, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"O" = ( +/obj/effect/landmark/bitrunning/safehouse_spawn, +/turf/template_noop, +/area/virtual_domain/safehouse) +"Q" = ( +/turf/open/chasm/lavaland, +/area/ruin/space/has_grav/powered/virtual_domain) +"R" = ( +/obj/effect/mine/explosive/light, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"T" = ( +/obj/machinery/door/airlock/abductor, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"U" = ( +/obj/effect/landmark/bitrunning/cache_spawn, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"V" = ( +/obj/structure/sign/warning/directional/east, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"W" = ( +/obj/effect/mob_spawn/corpse/human/cyber_police, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"X" = ( +/mob/living/simple_animal/hostile/zombie, +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) +"Y" = ( +/turf/open/indestructible/dark, +/area/ruin/space/has_grav/powered/virtual_domain) + +(1,1,1) = {" +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 +a +a +a +Y +Y +Y +Y +Y +Y +a +a +o +o +o +o +o +o +o +o +o +"} +(3,1,1) = {" +o +o +a +a +a +a +a +a +a +Y +Y +Y +Y +Y +Y +Y +Y +Y +a +a +K +o +o +o +o +o +o +o +"} +(4,1,1) = {" +o +o +a +W +X +Y +B +Y +Y +Y +Y +R +a +a +X +Y +Y +Y +Y +a +a +o +o +o +o +o +o +o +"} +(5,1,1) = {" +o +o +a +b +b +p +a +R +Y +Y +Y +a +a +a +a +X +Y +Y +Y +Y +a +o +o +o +o +o +o +o +"} +(6,1,1) = {" +o +o +a +a +a +a +a +Y +Y +Y +a +a +Y +Y +a +a +a +a +Y +Y +a +o +o +o +o +o +o +o +"} +(7,1,1) = {" +o +o +a +a +a +a +a +a +Y +Y +a +Y +Y +Y +a +a +a +Y +Y +Y +a +o +o +o +o +o +o +o +"} +(8,1,1) = {" +o +o +a +a +X +Y +Y +Y +Y +Y +a +Y +Y +Y +Y +Y +Y +Y +R +a +a +o +o +o +o +o +o +o +"} +(9,1,1) = {" +o +o +a +a +W +Y +Y +Y +A +Y +a +X +Y +Y +Y +Y +Y +Y +Y +a +o +o +o +o +o +o +o +o +"} +(10,1,1) = {" +o +o +a +a +Y +Q +Q +Q +Q +Y +a +a +Y +Q +Q +Q +Q +Q +Y +a +o +o +o +o +o +o +o +o +"} +(11,1,1) = {" +o +o +a +Y +Y +Q +Q +Q +Q +Y +Y +a +Y +Q +Q +Q +Q +Q +Y +a +o +o +o +o +o +o +o +o +"} +(12,1,1) = {" +o +o +a +Y +Y +Y +Y +Y +Y +Y +Y +a +Y +Y +Y +Y +A +Y +Y +a +o +o +o +o +o +o +o +o +"} +(13,1,1) = {" +o +o +a +Y +Y +Y +a +a +Y +Y +Y +a +Y +a +Y +Y +Y +Y +Y +a +o +o +o +o +o +o +o +o +"} +(14,1,1) = {" +o +o +a +Y +Y +Y +Y +a +Y +Y +a +a +Y +a +Y +Y +Y +Y +Y +a +o +o +o +o +o +o +o +o +"} +(15,1,1) = {" +o +o +a +Y +R +Y +a +a +Y +Y +a +a +Y +a +a +Y +Y +Y +X +a +o +o +o +o +o +o +o +o +"} +(16,1,1) = {" +o +o +a +Y +Y +Y +X +a +Y +Y +X +a +Y +Y +a +Y +a +a +a +a +a +a +a +a +a +a +a +o +"} +(17,1,1) = {" +o +o +a +Y +V +Y +a +a +Y +Y +Y +x +Y +Y +Y +Y +a +h +Y +Y +t +t +t +t +t +O +a +o +"} +(18,1,1) = {" +o +o +a +Y +a +Q +a +a +Y +Y +a +a +X +Y +Y +Y +a +F +Y +Y +t +t +t +t +t +t +a +o +"} +(19,1,1) = {" +o +o +a +Q +Q +Q +a +X +Y +Y +a +a +a +Y +Y +Y +a +Y +Y +Y +t +t +t +t +t +t +a +o +"} +(20,1,1) = {" +o +o +a +Q +Q +Q +a +Y +Y +Y +a +Y +Y +Y +Y +Y +T +Y +Y +Y +t +t +t +t +t +t +a +o +"} +(21,1,1) = {" +o +o +a +a +Q +Q +a +a +Y +Y +a +a +Y +R +Y +Y +a +Y +Y +Y +t +t +t +t +t +t +a +o +"} +(22,1,1) = {" +o +o +a +a +Q +a +a +a +Y +Y +a +a +Y +Y +Y +Y +a +F +Y +Y +t +t +t +t +t +t +a +o +"} +(23,1,1) = {" +o +o +a +a +Q +a +a +a +Y +Y +a +a +a +a +Y +Y +a +h +Y +Y +t +t +t +t +t +r +a +o +"} +(24,1,1) = {" +o +o +a +a +Q +a +a +a +Y +Y +a +a +a +a +a +Y +a +a +a +a +a +a +a +a +a +a +a +o +"} +(25,1,1) = {" +o +o +a +c +Q +a +a +a +T +a +a +a +a +a +a +Y +Y +R +a +a +o +o +o +o +o +o +o +o +"} +(26,1,1) = {" +o +o +a +Q +Q +a +a +W +Y +W +a +a +a +Y +Y +Y +Y +Y +a +o +o +o +o +o +o +o +o +o +"} +(27,1,1) = {" +o +o +a +Y +Y +T +Y +Y +Y +a +a +a +Y +Y +R +Y +Y +a +a +o +o +o +o +o +o +o +o +o +"} +(28,1,1) = {" +o +o +a +Y +X +a +U +X +X +a +X +a +Y +Y +Y +Y +R +a +o +o +o +o +o +o +o +o +o +o +"} +(29,1,1) = {" +o +o +a +Y +a +a +a +a +a +a +Y +Y +Y +Y +a +a +a +a +o +o +o +o +o +o +o +o +o +o +"} +(30,1,1) = {" +o +o +a +Y +Y +R +a +a +R +Y +Y +Y +Y +Y +Y +Y +X +a +o +o +o +o +o +o +o +o +o +o +"} +(31,1,1) = {" +o +o +a +Y +Y +Y +a +Y +Y +Y +a +a +Y +Y +Y +M +a +a +o +o +o +o +o +o +o +o +o +o +"} +(32,1,1) = {" +o +o +a +Y +Y +Y +a +Y +R +a +a +a +a +Y +Y +Y +a +a +a +a +a +o +o +o +o +o +o +o +"} +(33,1,1) = {" +o +o +a +a +R +Y +a +Y +a +a +Y +Y +R +Y +Y +Y +Y +Y +Q +Q +a +o +o +o +o +o +o +o +"} +(34,1,1) = {" +o +o +o +a +Y +Y +Y +Y +Y +Y +Y +a +a +a +Y +M +Y +W +i +Q +a +o +o +o +o +o +o +o +"} +(35,1,1) = {" +o +o +o +a +Y +Y +Y +Y +X +a +a +a +a +a +a +a +a +a +a +a +a +o +o +o +o +o +o +o +"} +(36,1,1) = {" +o +o +o +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 +"} +(37,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 +o +o +o +"} diff --git a/code/__DEFINES/_helpers.dm b/code/__DEFINES/_helpers.dm index fdbfde608225c..c6a5b860e9a2d 100644 --- a/code/__DEFINES/_helpers.dm +++ b/code/__DEFINES/_helpers.dm @@ -36,3 +36,8 @@ /// : because of the embedded typecheck #define text_ref(datum) (isdatum(datum) ? (datum:cached_ref ||= "\ref[datum]") : ("\ref[datum]")) #endif + +// Refs contain a type id within their string that can be used to identify byond types. +// Custom types that we define don't get a unique id, but this is useful for identifying +// types that don't normally have a way to run istype() on them. +#define TYPEID(thing) copytext(REF(thing), 4, 6) diff --git a/code/__DEFINES/ai/ai.dm b/code/__DEFINES/ai/ai.dm index 467fc9d7c0f57..83d7e7f6a5f16 100644 --- a/code/__DEFINES/ai/ai.dm +++ b/code/__DEFINES/ai/ai.dm @@ -32,6 +32,8 @@ #define CAN_ACT_WHILE_DEAD (1<<1) /// Stop processing while in a progress bar #define PAUSE_DURING_DO_AFTER (1<<2) +/// Continue processing while in stasis +#define CAN_ACT_IN_STASIS (1<<3) //Base Subtree defines diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index 24ecf0a19f625..079b052853f7a 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -11,6 +11,11 @@ ///How close a mob must be for us to select it as a target, if that is less than how far we can maintain it as a target #define BB_AGGRO_RANGE "BB_aggro_range" +/// Store a single or list of emotes at this key +#define BB_EMOTE_KEY "BB_emotes" +/// Chance to perform an emote per second +#define BB_EMOTE_CHANCE "BB_EMOTE_CHANCE" + ///Turf we want a mob to move to #define BB_TRAVEL_DESTINATION "BB_travel_destination" @@ -54,6 +59,9 @@ /// Generic key for a non-specific action #define BB_GENERIC_ACTION "BB_generic_action" +/// Generic key for a shapeshifting action +#define BB_SHAPESHIFT_ACTION "BB_shapeshift_action" + ///How long have we spent with no target? #define BB_TARGETLESS_TIME "BB_targetless_time" @@ -77,8 +85,13 @@ ///List of mobs who have damaged us #define BB_BASIC_MOB_RETALIATE_LIST "BB_basic_mob_shitlist" -/// Flag to set on or off if you want your mob to prioritise running away -#define BB_BASIC_MOB_FLEEING "BB_basic_fleeing" +/// Chance to randomly acquire a new target +#define BB_RANDOM_AGGRO_CHANCE "BB_random_aggro_chance" +/// Chance to randomly drop all of our targets +#define BB_RANDOM_DEAGGRO_CHANCE "BB_random_deaggro_chance" + +/// Flag to set on if you want your mob to STOP running away +#define BB_BASIC_MOB_STOP_FLEEING "BB_basic_stop_fleeing" ///list of foods this mob likes #define BB_BASIC_FOODS "BB_basic_foods" @@ -94,7 +107,9 @@ #define MOD_AI_RANGE 200 ///should we skip the faction check for the targetting datum? -#define BB_BASIC_MOB_SKIP_FACTION_CHECK "BB_basic_mob_skip_faction_check" +#define BB_ALWAYS_IGNORE_FACTION "BB_always_ignore_factions" +///are we in some kind of temporary state of ignoring factions when targetting? can result in volatile results if multiple behaviours touch this +#define BB_TEMPORARILY_IGNORE_FACTION "BB_temporarily_ignore_factions" ///currently only used by clowns, a list of what can the mob speak randomly #define BB_BASIC_MOB_SPEAK_LINES "BB_speech_lines" @@ -102,4 +117,4 @@ #define BB_EMOTE_HEAR "emote_hear" #define BB_EMOTE_SEE "emote_see" #define BB_EMOTE_SOUND "emote_sound" -#define BB_EMOTE_CHANCE "emote_chance" +#define BB_SPEAK_CHANCE "emote_chance" diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index be9a4be34cd15..2867ba4a6fc94 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -147,9 +147,45 @@ /// key holds the tray we will beam #define BB_BEAMABLE_HYDROPLANT_TARGET "beamable_hydroplant_target" +//ice demons +///the list of items we are afraid of +#define BB_LIST_SCARY_ITEMS "list_scary_items" +///our teleportation ability +#define BB_DEMON_TELEPORT_ABILITY "demon_teleport_ability" +///the destination of our teleport ability +#define BB_TELEPORT_DESTINATION "teleport_destination" +///the ability to clone ourself +#define BB_DEMON_CLONE_ABILITY "demon_clone_ability" +///our slippery ice ability +#define BB_DEMON_SLIP_ABILITY "demon_slip_ability" +///the turf we are escaping too +#define BB_ESCAPE_DESTINATION "escape_destination" +///how far away we will be from our target before teleporting +#define BB_MINIMUM_DISTANCE_RANGE "minimum_distance_range" + /// Corpse we have consumed #define BB_LEGION_CORPSE "legion_corpse" /// Things our target recently said #define BB_LEGION_RECENT_LINES "legion_recent_lines" /// The creator of our legion skull #define BB_LEGION_BROOD_CREATOR "legion_brood_creator" + +//mook keys +/// our home landmark +#define BB_HOME_VILLAGE "home_village" +/// maximum distance we can be from home during a storm +#define BB_MAXIMUM_DISTANCE_TO_VILLAGE "maximum_distance_to_village" +/// stand where we deposit our ores +#define BB_MATERIAL_STAND_TARGET "material_stand_target" +/// our jump ability +#define BB_MOOK_JUMP_ABILITY "mook_jump_ability" +/// our leap ability +#define BB_MOOK_LEAP_ABILITY "mook_leap_ability" +/// the chief we must obey +#define BB_MOOK_TRIBAL_CHIEF "mook_tribal_chief" +/// the injured mook we must heal +#define BB_INJURED_MOOK "injured_mook" +/// the player we will follow and play music for +#define BB_MOOK_MUSIC_AUDIENCE "music_audience" +/// the bonfire we will light up +#define BB_MOOK_BONFIRE_TARGET "bonfire_target" diff --git a/code/__DEFINES/basic_mobs.dm b/code/__DEFINES/basic_mobs.dm index 5a4aebaee23e6..6c8a3022e8fa7 100644 --- a/code/__DEFINES/basic_mobs.dm +++ b/code/__DEFINES/basic_mobs.dm @@ -19,3 +19,10 @@ /// Above this speed we stop gliding because it looks silly #define END_GLIDE_SPEED 10 + +///mook attack status flags +#define MOOK_ATTACK_NEUTRAL 0 +#define MOOK_ATTACK_WARMUP 1 +#define MOOK_ATTACK_ACTIVE 2 +#define MOOK_ATTACK_STRIKE 3 + diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index b14fd514b85e4..802abe2c34910 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -290,6 +290,7 @@ /// Icon filter that creates gaussian blur #define GAUSSIAN_BLUR(filter_size) filter(type="blur", size=filter_size) +// Colors related to items used in construction #define CABLE_COLOR_BLUE "blue" #define CABLE_HEX_COLOR_BLUE COLOR_STRONG_BLUE #define CABLE_COLOR_BROWN "brown" @@ -308,6 +309,9 @@ #define CABLE_HEX_COLOR_WHITE COLOR_WHITE #define CABLE_COLOR_YELLOW "yellow" #define CABLE_HEX_COLOR_YELLOW COLOR_YELLOW +//windows affected by Nar'Sie turn this color. +#define NARSIE_WINDOW_COLOUR "#7D1919" + #define COLOR_CARP_PURPLE "#aba2ff" #define COLOR_CARP_PINK "#da77a8" diff --git a/code/__DEFINES/construction.dm b/code/__DEFINES/construction.dm deleted file mode 100644 index 4e37baadffb49..0000000000000 --- a/code/__DEFINES/construction.dm +++ /dev/null @@ -1,227 +0,0 @@ -/*ALL DEFINES RELATED TO CONSTRUCTION, CONSTRUCTING THINGS, OR CONSTRUCTED OBJECTS GO HERE*/ - -//Defines for construction states - -//girder construction states -#define GIRDER_NORMAL 0 -#define GIRDER_REINF_STRUTS 1 -#define GIRDER_REINF 2 -#define GIRDER_DISPLACED 3 -#define GIRDER_DISASSEMBLED 4 -#define GIRDER_TRAM 5 - -//rwall construction states -#define INTACT 0 -#define SUPPORT_LINES 1 -#define COVER 2 -#define CUT_COVER 3 -#define ANCHOR_BOLTS 4 -#define SUPPORT_RODS 5 -#define SHEATH 6 - -//window construction states -#define WINDOW_OUT_OF_FRAME 0 -#define WINDOW_IN_FRAME 1 -#define WINDOW_SCREWED_TO_FRAME 2 - -//reinforced window construction states -#define RWINDOW_FRAME_BOLTED 3 -#define RWINDOW_BARS_CUT 4 -#define RWINDOW_POPPED 5 -#define RWINDOW_BOLTS_OUT 6 -#define RWINDOW_BOLTS_HEATED 7 -#define RWINDOW_SECURE 8 - -//airlock assembly construction states -#define AIRLOCK_ASSEMBLY_NEEDS_WIRES 0 -#define AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS 1 -#define AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER 2 - -//blast door (de)construction states -#define BLASTDOOR_NEEDS_WIRES 0 -#define BLASTDOOR_NEEDS_ELECTRONICS 1 -#define BLASTDOOR_FINISHED 2 - -//default_unfasten_wrench() return defines -#define CANT_UNFASTEN 0 -#define FAILED_UNFASTEN 1 -#define SUCCESSFUL_UNFASTEN 2 - -//ai core defines -#define EMPTY_CORE 0 -#define CIRCUIT_CORE 1 -#define SCREWED_CORE 2 -#define CABLED_CORE 3 -#define GLASS_CORE 4 -#define AI_READY_CORE 5 - -//Construction defines for the pinion airlock -#define GEAR_SECURE 1 -#define GEAR_LOOSE 2 - -//floodlights because apparently we use defines now -#define FLOODLIGHT_NEEDS_WIRES 0 -#define FLOODLIGHT_NEEDS_LIGHTS 1 -#define FLOODLIGHT_NEEDS_SECURING 2 - -// Stationary gas tanks -#define TANK_FRAME 0 -#define TANK_PLATING_UNSECURED 1 - -//other construction-related things - -//windows affected by Nar'Sie turn this color. -#define NARSIE_WINDOW_COLOUR "#7D1919" - -// Defines related to the custom materials used on objects. -///The amount of materials you get from a sheet of mineral like iron/diamond/glass etc. 100 Units. -#define SHEET_MATERIAL_AMOUNT 100 -///The amount of materials you get from half a sheet. Used in standard object quantities. 50 units. -#define HALF_SHEET_MATERIAL_AMOUNT (SHEET_MATERIAL_AMOUNT/2) -///The amount of materials used in the smallest of objects, like pens and screwdrivers. 10 units. -#define SMALL_MATERIAL_AMOUNT (HALF_SHEET_MATERIAL_AMOUNT/5) -///The amount of material that goes into a coin, which determines the value of the coin. -#define COIN_MATERIAL_AMOUNT (HALF_SHEET_MATERIAL_AMOUNT * 0.4) - -//The maximum size of a stack object. -#define MAX_STACK_SIZE 50 -//maximum amount of cable in a coil -#define MAXCOIL 30 - -//food/drink crafting defines -//When adding new defines, please make sure to also add them to the encompassing list -#define CAT_FOOD "Foods" -#define CAT_BREAD "Breads" -#define CAT_BURGER "Burgers" -#define CAT_CAKE "Cakes" -#define CAT_EGG "Egg-Based Food" -#define CAT_LIZARD "Lizard Food" -#define CAT_MEAT "Meats" -#define CAT_SEAFOOD "Seafood" -#define CAT_MARTIAN "Martian Food" -#define CAT_MISCFOOD "Misc. Food" -#define CAT_MEXICAN "Mexican Food" -#define CAT_MOTH "Mothic Food" -#define CAT_PASTRY "Pastries" -#define CAT_PIE "Pies" -#define CAT_PIZZA "Pizzas" -#define CAT_SALAD "Salads" -#define CAT_SANDWICH "Sandwiches" -#define CAT_SOUP "Soups" -#define CAT_SPAGHETTI "Spaghettis" -#define CAT_ICE "Frozen" -#define CAT_DRINK "Drinks" - -GLOBAL_LIST_INIT(crafting_category_food, list( - CAT_FOOD, - CAT_BREAD, - CAT_BURGER, - CAT_CAKE, - CAT_EGG, - CAT_LIZARD, - CAT_MEAT, - CAT_SEAFOOD, - CAT_MARTIAN, - CAT_MISCFOOD, - CAT_MEXICAN, - CAT_MOTH, - CAT_PASTRY, - CAT_PIE, - CAT_PIZZA, - CAT_SALAD, - CAT_SANDWICH, - CAT_SOUP, - CAT_SPAGHETTI, - CAT_ICE, - CAT_DRINK, -)) - -//crafting defines -//When adding new defines, please make sure to also add them to the encompassing list -#define CAT_WEAPON_RANGED "Weapons Ranged" -#define CAT_WEAPON_MELEE "Weapons Melee" -#define CAT_WEAPON_AMMO "Weapon Ammo" -#define CAT_ROBOT "Robotics" -#define CAT_MISC "Misc" -#define CAT_CLOTHING "Clothing" -#define CAT_CHEMISTRY "Chemistry" -#define CAT_ATMOSPHERIC "Atmospherics" -#define CAT_STRUCTURE "Structures" -#define CAT_TILES "Tiles" -#define CAT_WINDOWS "Windows" -#define CAT_DOORS "Doors" -#define CAT_FURNITURE "Furniture" -#define CAT_EQUIPMENT "Equipment" -#define CAT_CONTAINERS "Containers" -#define CAT_ENTERTAINMENT "Entertainment" -#define CAT_TOOLS "Tools" -#define CAT_CULT "Blood Cult" - -GLOBAL_LIST_INIT(crafting_category, list( - CAT_WEAPON_RANGED, - CAT_WEAPON_MELEE, - CAT_WEAPON_AMMO, - CAT_ROBOT, - CAT_MISC, - CAT_CLOTHING, - CAT_CHEMISTRY, - CAT_ATMOSPHERIC, - CAT_STRUCTURE, - CAT_TILES, - CAT_WINDOWS, - CAT_DOORS, - CAT_FURNITURE, - CAT_EQUIPMENT, - CAT_CONTAINERS, - CAT_ENTERTAINMENT, - CAT_TOOLS, - CAT_CULT, -)) - -//rcd modes -#define RCD_FLOORWALL 0 -#define RCD_AIRLOCK 1 -#define RCD_DECONSTRUCT 2 -#define RCD_WINDOWGRILLE 3 -#define RCD_MACHINE 4 -#define RCD_COMPUTER 5 -#define RCD_FURNISHING 6 -#define RCD_CATWALK 7 -#define RCD_FLOODLIGHT 8 -#define RCD_WALLFRAME 9 -#define RCD_REFLECTOR 10 -#define RCD_GIRDER 11 - - -#define RCD_UPGRADE_FRAMES (1<<0) -#define RCD_UPGRADE_SIMPLE_CIRCUITS (1<<1) -#define RCD_UPGRADE_SILO_LINK (1<<2) -#define RCD_UPGRADE_FURNISHING (1<<3) -#define RCD_UPGRADE_ANTI_INTERRUPT (1<<4) -#define RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN (1<<5) - -#define RPD_UPGRADE_UNWRENCH (1<<0) - -#define RCD_WINDOW_FULLTILE "full tile" -#define RCD_WINDOW_DIRECTIONAL "directional" -#define RCD_WINDOW_NORMAL "glass" -#define RCD_WINDOW_REINFORCED "reinforced glass" - -#define RCD_MEMORY_WALL 1 -#define RCD_MEMORY_WINDOWGRILLE 2 - -// How much faster to use the RCD when on a tile with memory -#define RCD_MEMORY_SPEED_BUFF 5 - -/// How much less resources the RCD uses when reconstructing -#define RCD_MEMORY_COST_BUFF 8 - -/// If set to TRUE in rcd_vals, will bypass the cooldown on slowing down frequent use -#define RCD_RESULT_BYPASS_FREQUENT_USE_COOLDOWN "bypass_frequent_use_cooldown" - -// Defines for the construction component -#define FORWARD 1 -#define BACKWARD -1 - -#define ITEM_DELETE "delete" -#define ITEM_MOVE_INSIDE "move_inside" diff --git a/code/__DEFINES/construction/actions.dm b/code/__DEFINES/construction/actions.dm new file mode 100644 index 0000000000000..9fe4fd157e310 --- /dev/null +++ b/code/__DEFINES/construction/actions.dm @@ -0,0 +1,11 @@ +//default_unfasten_wrench() return defines +#define CANT_UNFASTEN 0 +#define FAILED_UNFASTEN 1 +#define SUCCESSFUL_UNFASTEN 2 + +// Defines for the construction component +#define FORWARD 1 +#define BACKWARD -1 + +#define ITEM_DELETE "delete" +#define ITEM_MOVE_INSIDE "move_inside" diff --git a/code/__DEFINES/materials.dm b/code/__DEFINES/construction/material.dm similarity index 72% rename from code/__DEFINES/materials.dm rename to code/__DEFINES/construction/material.dm index 5fc5cc08ea205..dad575c58c391 100644 --- a/code/__DEFINES/materials.dm +++ b/code/__DEFINES/construction/material.dm @@ -1,31 +1,43 @@ +//Defines for amount of material retrived from sheets & other items +/// The amount of materials you get from a sheet of mineral like iron/diamond/glass etc. 100 Units. +#define SHEET_MATERIAL_AMOUNT 100 +/// The amount of materials you get from half a sheet. Used in standard object quantities. 50 units. +#define HALF_SHEET_MATERIAL_AMOUNT (SHEET_MATERIAL_AMOUNT / 2) +/// The amount of materials used in the smallest of objects, like pens and screwdrivers. 10 units. +#define SMALL_MATERIAL_AMOUNT (HALF_SHEET_MATERIAL_AMOUNT / 5) +/// The amount of material that goes into a coin, which determines the value of the coin. +#define COIN_MATERIAL_AMOUNT (HALF_SHEET_MATERIAL_AMOUNT * 0.4) + +//Cable related values +/// The maximum size of a stack object. +#define MAX_STACK_SIZE 50 +/// Maximum amount of cable in a coil +#define MAXCOIL 30 + +//Category of materials /// Is the material from an ore? currently unused but exists atm for categorizations sake #define MAT_CATEGORY_ORE "ore capable" - /// Hard materials, such as iron or silver #define MAT_CATEGORY_RIGID "rigid material" - /// Materials that can be used to craft items #define MAT_CATEGORY_ITEM_MATERIAL "item material" - -///Use this flag on TRUE if you want the basic recipes +/// Use this flag on TRUE if you want the basic recipes #define MAT_CATEGORY_BASE_RECIPES "basic recipes" +///Flags for map loaded materials /// Used to make a material initialize at roundstart. #define MATERIAL_INIT_MAPLOAD (1<<0) /// Used to make a material type able to be instantiated on demand after roundstart. #define MATERIAL_INIT_BESPOKE (1<<1) -/// Makes sure only integer values are used when consuming, removing & checking for mats -#define OPTIMAL_COST(cost)(max(1, round(cost))) - //Material Container Flags. ///If the container shows the amount of contained materials on examine. #define MATCONTAINER_EXAMINE (1<<0) ///If the container cannot have materials inserted through attackby(). #define MATCONTAINER_NO_INSERT (1<<1) -///if the user can insert mats into the container despite the intent. +///If the user can insert mats into the container despite the intent. #define MATCONTAINER_ANY_INTENT (1<<2) -///if the user won't receive a warning when attacking the container with an unallowed item. +///If the user won't receive a warning when attacking the container with an unallowed item. #define MATCONTAINER_SILENT (1<<3) // The following flags are for decomposing alloys. Should be expanded upon and diversified once someone gets around to reworking recycling. @@ -59,14 +71,12 @@ /// Applies the material greyscale color to the atom's greyscale color. #define MATERIAL_GREYSCALE (1<<4) -/// Wrapper for fetching material references. Exists exclusively so that people don't need to wrap everything in a list every time. -#define GET_MATERIAL_REF(arguments...) SSmaterials._GetMaterialRef(list(##arguments)) - -#define MATERIAL_SOURCE(mat) "[mat.name]_material" - -///Special return values of [/datum/component/material_container/insert_item] +//Special return values of [/datum/component/material_container/insert_item] +/// No material was found inside them item #define MATERIAL_INSERT_ITEM_NO_MATS -1 +/// The container does not have the space for the item #define MATERIAL_INSERT_ITEM_NO_SPACE -2 +/// The item material type was not accepted or other reasons #define MATERIAL_INSERT_ITEM_FAILURE 0 diff --git a/code/__DEFINES/construction/rcd.dm b/code/__DEFINES/construction/rcd.dm new file mode 100644 index 0000000000000..e8c9ae55f321a --- /dev/null +++ b/code/__DEFINES/construction/rcd.dm @@ -0,0 +1,50 @@ +//rcd constants for the design list +/// The mode of operation to design an specific type of rcd design +#define RCD_DESIGN_MODE "rcd_design_mode" + /// For changing turfs + #define RCD_TURF (1 << 0) + /// Full tile windows + #define RCD_WINDOWGRILLE (1 << 1) + /// Windoors & Airlocks + #define RCD_AIRLOCK (1 << 2) + /// Literarly anything that is spawned on top of a turf such as tables, machines etc + #define RCD_STRUCTURE (1 << 3) + /// For wallmounts like air alarms, fire alarms & apc + #define RCD_WALLFRAME (1 << 4) + /// For deconstructing an structure + #define RCD_DECONSTRUCT (1 << 5) +/// The typepath of the structure the rcd is trying to build +#define RCD_DESIGN_PATH "rcd_design_path" + +/// Time taken for an rcd hologram to disappear +#define RCD_HOLOGRAM_FADE_TIME (15 SECONDS) + +//All available upgrades +/// Upgrade for building machines +#define RCD_UPGRADE_FRAMES (1 << 0) +/// Upgrade for installing circuitboards in air alarms, fire alarms, apc & cells in them +#define RCD_UPGRADE_SIMPLE_CIRCUITS (1 << 1) +/// Upgrade for drawing iron from ore silo +#define RCD_UPGRADE_SILO_LINK (1 << 2) +/// Upgrade for building furnishing items +#define RCD_UPGRADE_FURNISHING (1 << 3) +/// Upgrade to stop construction effect from getting attacked +#define RCD_UPGRADE_ANTI_INTERRUPT (1 << 4) +/// Upgrade to disable delay multiplier when building multiple structures +#define RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN (1 << 5) +/// All upgrades packed in 1 flag +#define RCD_ALL_UPGRADES (RCD_UPGRADE_FRAMES | RCD_UPGRADE_SIMPLE_CIRCUITS | RCD_UPGRADE_SILO_LINK | RCD_UPGRADE_FURNISHING | RCD_UPGRADE_ANTI_INTERRUPT | RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN) +/// Upgrades for the Rapid Pipe Dispenser to unwrench pipes +#define RPD_UPGRADE_UNWRENCH (1 << 0) + +//Memory constants for faster construction speeds +/// The memory constant for a wall +#define RCD_MEMORY_WALL 1 +/// The memory constant for full tile windows +#define RCD_MEMORY_WINDOWGRILLE 2 +// How much faster to use the RCD when on a tile with memory +#define RCD_MEMORY_SPEED_BUFF 5 +/// How much less resources the RCD uses when reconstructing +#define RCD_MEMORY_COST_BUFF 8 +/// If set to TRUE in rcd_vals, will bypass the cooldown on slowing down frequent use +#define RCD_RESULT_BYPASS_FREQUENT_USE_COOLDOWN "bypass_frequent_use_cooldown" diff --git a/code/__DEFINES/construction/structures.dm b/code/__DEFINES/construction/structures.dm new file mode 100644 index 0000000000000..cda5b920a42ef --- /dev/null +++ b/code/__DEFINES/construction/structures.dm @@ -0,0 +1,62 @@ +//Defines for construction states + +//ai core defines +#define EMPTY_CORE 0 +#define CIRCUIT_CORE 1 +#define SCREWED_CORE 2 +#define CABLED_CORE 3 +#define GLASS_CORE 4 +#define AI_READY_CORE 5 + +//girder construction states +#define GIRDER_NORMAL 0 +#define GIRDER_REINF_STRUTS 1 +#define GIRDER_REINF 2 +#define GIRDER_DISPLACED 3 +#define GIRDER_DISASSEMBLED 4 +#define GIRDER_TRAM 5 + +//rwall construction states +#define INTACT 0 +#define SUPPORT_LINES 1 +#define COVER 2 +#define CUT_COVER 3 +#define ANCHOR_BOLTS 4 +#define SUPPORT_RODS 5 +#define SHEATH 6 + +//window construction states +#define WINDOW_OUT_OF_FRAME 0 +#define WINDOW_IN_FRAME 1 +#define WINDOW_SCREWED_TO_FRAME 2 + +//reinforced window construction states +#define RWINDOW_FRAME_BOLTED 3 +#define RWINDOW_BARS_CUT 4 +#define RWINDOW_POPPED 5 +#define RWINDOW_BOLTS_OUT 6 +#define RWINDOW_BOLTS_HEATED 7 +#define RWINDOW_SECURE 8 + +//airlock assembly construction states +#define AIRLOCK_ASSEMBLY_NEEDS_WIRES 0 +#define AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS 1 +#define AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER 2 + +//blast door (de)construction states +#define BLASTDOOR_NEEDS_WIRES 0 +#define BLASTDOOR_NEEDS_ELECTRONICS 1 +#define BLASTDOOR_FINISHED 2 + +//floodlights because apparently we use defines now +#define FLOODLIGHT_NEEDS_WIRES 0 +#define FLOODLIGHT_NEEDS_LIGHTS 1 +#define FLOODLIGHT_NEEDS_SECURING 2 + +//Construction defines for the pinion airlock +#define GEAR_SECURE 1 +#define GEAR_LOOSE 2 + +// Stationary gas tanks +#define TANK_FRAME 0 +#define TANK_PLATING_UNSECURED 1 diff --git a/code/__DEFINES/crafting.dm b/code/__DEFINES/crafting.dm index 75e8281678683..647278aa3ec0b 100644 --- a/code/__DEFINES/crafting.dm +++ b/code/__DEFINES/crafting.dm @@ -6,3 +6,48 @@ #define CRAFTING_MACHINERY_USE 0 ///If the structure is only "used" i.e. it checks to see if it's nearby and allows crafting, but doesn't delete it #define CRAFTING_STRUCTURE_USE 0 + +//food/drink crafting defines +//When adding new defines, please make sure to also add them to the encompassing list +#define CAT_FOOD "Foods" +#define CAT_BREAD "Breads" +#define CAT_BURGER "Burgers" +#define CAT_CAKE "Cakes" +#define CAT_EGG "Egg-Based Food" +#define CAT_LIZARD "Lizard Food" +#define CAT_MEAT "Meats" +#define CAT_SEAFOOD "Seafood" +#define CAT_MARTIAN "Martian Food" +#define CAT_MISCFOOD "Misc. Food" +#define CAT_MEXICAN "Mexican Food" +#define CAT_MOTH "Mothic Food" +#define CAT_PASTRY "Pastries" +#define CAT_PIE "Pies" +#define CAT_PIZZA "Pizzas" +#define CAT_SALAD "Salads" +#define CAT_SANDWICH "Sandwiches" +#define CAT_SOUP "Soups" +#define CAT_SPAGHETTI "Spaghettis" +#define CAT_ICE "Frozen" +#define CAT_DRINK "Drinks" + +//crafting defines +//When adding new defines, please make sure to also add them to the encompassing list +#define CAT_WEAPON_RANGED "Weapons Ranged" +#define CAT_WEAPON_MELEE "Weapons Melee" +#define CAT_WEAPON_AMMO "Weapon Ammo" +#define CAT_ROBOT "Robotics" +#define CAT_MISC "Misc" +#define CAT_CLOTHING "Clothing" +#define CAT_CHEMISTRY "Chemistry" +#define CAT_ATMOSPHERIC "Atmospherics" +#define CAT_STRUCTURE "Structures" +#define CAT_TILES "Tiles" +#define CAT_WINDOWS "Windows" +#define CAT_DOORS "Doors" +#define CAT_FURNITURE "Furniture" +#define CAT_EQUIPMENT "Equipment" +#define CAT_CONTAINERS "Containers" +#define CAT_ENTERTAINMENT "Entertainment" +#define CAT_TOOLS "Tools" +#define CAT_CULT "Blood Cult" diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm index 5e6d7491d9423..d26dd4e8c8682 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm @@ -66,14 +66,6 @@ #define COMSIG_MOVABLE_SET_ANCHORED "movable_set_anchored" ///from base of atom/movable/setGrabState(): (newstate) #define COMSIG_MOVABLE_SET_GRAB_STATE "living_set_grab_state" -///Called when the movable tries to change its dynamic light color setting, from base atom/movable/lighting_overlay_set_color(): (color) -#define COMSIG_MOVABLE_LIGHT_OVERLAY_SET_RANGE "movable_light_overlay_set_color" -///Called when the movable tries to change its dynamic light power setting, from base atom/movable/lighting_overlay_set_power(): (power) -#define COMSIG_MOVABLE_LIGHT_OVERLAY_SET_POWER "movable_light_overlay_set_power" -///Called when the movable tries to change its dynamic light range setting, from base atom/movable/lighting_overlay_set_range(): (range) -#define COMSIG_MOVABLE_LIGHT_OVERLAY_SET_COLOR "movable_light_overlay_set_range" -///Called when the movable tries to toggle its dynamic light LIGHTING_ON status, from base atom/movable/lighting_overlay_toggle_on(): (new_state) -#define COMSIG_MOVABLE_LIGHT_OVERLAY_TOGGLE_ON "movable_light_overlay_toggle_on" ///called when the movable's glide size is updated: (new_glide_size) #define COMSIG_MOVABLE_UPDATE_GLIDE_SIZE "movable_glide_size" ///Called when a movable is hit by a plunger in layer mode, from /obj/item/plunger/attack_atom() diff --git a/code/__DEFINES/dcs/signals/signals_bitrunning.dm b/code/__DEFINES/dcs/signals/signals_bitrunning.dm index 3d008449ee7b7..957e61ae303c2 100644 --- a/code/__DEFINES/dcs/signals/signals_bitrunning.dm +++ b/code/__DEFINES/dcs/signals/signals_bitrunning.dm @@ -29,3 +29,6 @@ /// from /obj/machinery/quantum_server/refreshParts(): (servo rating) #define COMSIG_BITRUNNER_SERVER_UPGRADED "bitrunner_server_upgraded" + +/// from /obj/machinery/quantum_server/scrub_vdom() +#define COMSIG_BITRUNNER_DOMAIN_SCRUBBED "bitrunner_domain_scrubbed" diff --git a/code/__DEFINES/dcs/signals/signals_global.dm b/code/__DEFINES/dcs/signals/signals_global.dm index c75b169b90dc5..9ccd3fd72670e 100644 --- a/code/__DEFINES/dcs/signals/signals_global.dm +++ b/code/__DEFINES/dcs/signals/signals_global.dm @@ -77,8 +77,8 @@ /// global signal sent when a nuclear device is detonating (/obj/machinery/nuclearbomb/nuke/exploding_nuke) #define COMSIG_GLOB_NUKE_DEVICE_DETONATING "!nuclear_device_detonating" -/// Global signal sent when a light mechanism is completed (try_id) -#define COMSIG_GLOB_LIGHT_MECHANISM_COMPLETED "!light_mechanism_completed" +/// Global signal sent when a puzzle piece is completed (light mechanism, etc.) (try_id) +#define COMSIG_GLOB_PUZZLE_COMPLETED "!puzzle_completed" /// Global signal called after the station changes its name. /// (new_name, old_name) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index ad0e6e359b1e7..1d5ab544d1003 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -122,6 +122,8 @@ // /mob/living/carbon/human signals +///Applied preferences to a human +#define COMSIG_HUMAN_PREFS_APPLIED "human_prefs_applied" ///Hit by successful disarm attack (mob/living/carbon/human/attacker,zone_targeted) #define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit" ///Whenever EquipRanked is called, called after job is set diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index d16adbe07a760..b70f7f9013dba 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -42,6 +42,8 @@ #define COMSIG_LIVING_TRY_SYRINGE "living_try_syringe" ///From living/Life(). (deltatime, times_fired) #define COMSIG_LIVING_LIFE "living_life" + /// Block the Life() proc from proceeding... this should really only be done in some really wacky situations. + #define COMPONENT_LIVING_CANCEL_LIFE_PROCESSING (1<<0) ///From living/set_resting(): (new_resting, silent, instant) #define COMSIG_LIVING_RESTING "living_resting" @@ -55,9 +57,34 @@ ///from base of element/bane/activate(): (item/weapon, mob/user) #define COMSIG_OBJECT_ON_BANING "obj_on_baning" -///from base of mob/living/on_damage_adjustment -#define COMSIG_LIVING_ADJUST_DAMAGE "living_adjust_damage" - #define COMPONENT_IGNORE_CHANGE (1<<0) +// adjust_x_loss messages sent from /mob/living/proc/adjust[x]Loss +/// Returned from all the following messages if you actually aren't going to apply any change +#define COMPONENT_IGNORE_CHANGE (1<<0) +// Each of these messages sends the damagetype even though it is inferred by the signal so you can pass all of them to the same proc if required +/// Send when bruteloss is modified (type, amount, forced) +#define COMSIG_LIVING_ADJUST_BRUTE_DAMAGE "living_adjust_brute_damage" +/// Send when fireloss is modified (type, amount, forced) +#define COMSIG_LIVING_ADJUST_BURN_DAMAGE "living_adjust_burn_damage" +/// Send when oxyloss is modified (type, amount, forced) +#define COMSIG_LIVING_ADJUST_OXY_DAMAGE "living_adjust_oxy_damage" +/// Send when toxloss is modified (type, amount, forced) +#define COMSIG_LIVING_ADJUST_TOX_DAMAGE "living_adjust_tox_damage" +/// Send when cloneloss is modified (type, amount, forced) +#define COMSIG_LIVING_ADJUST_CLONE_DAMAGE "living_adjust_clone_damage" +/// Send when staminaloss is modified (type, amount, forced) +#define COMSIG_LIVING_ADJUST_STAMINA_DAMAGE "living_adjust_stamina_damage" + +/// List of signals sent when you receive any damage except stamina +#define COMSIG_LIVING_ADJUST_STANDARD_DAMAGE_TYPES list(\ + COMSIG_LIVING_ADJUST_BRUTE_DAMAGE,\ + COMSIG_LIVING_ADJUST_BURN_DAMAGE,\ + COMSIG_LIVING_ADJUST_CLONE_DAMAGE,\ + COMSIG_LIVING_ADJUST_OXY_DAMAGE,\ + COMSIG_LIVING_ADJUST_TOX_DAMAGE,\ +) +/// List of signals sent when you receive any kind of damage at all +#define COMSIG_LIVING_ADJUST_ALL_DAMAGE_TYPES (COMSIG_LIVING_ADJUST_STANDARD_DAMAGE_TYPES + COMSIG_LIVING_ADJUST_STAMINA_DAMAGE) + /// from base of mob/living/updatehealth() #define COMSIG_LIVING_HEALTH_UPDATE "living_health_update" @@ -110,6 +137,10 @@ #define COMSIG_LIVING_UNARMED_ATTACK "living_unarmed_attack" ///From base of mob/living/MobBump() (mob/living) #define COMSIG_LIVING_MOB_BUMP "living_mob_bump" +///From base of mob/living/Bump() (turf/closed) +#define COMSIG_LIVING_WALL_BUMP "living_wall_bump" +///From base of turf/closed/Exited() (turf/closed) +#define COMSIG_LIVING_WALL_EXITED "living_wall_exited" ///From base of mob/living/ZImpactDamage() (mob/living, levels, turf/t) #define COMSIG_LIVING_Z_IMPACT "living_z_impact" #define NO_Z_IMPACT_DAMAGE (1<<0) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index 7dffe24331ac1..51211f5ad6d7b 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -31,6 +31,9 @@ /// From base of /mob/living/simple_animal/bot/proc/bot_step() #define COMSIG_MOB_BOT_STEP "mob_bot_step" +/// From base of /mob/proc/update_held_items +#define COMSIG_MOB_UPDATE_HELD_ITEMS "mob_update_held_items" + /// From base of /client/Move(): (list/move_args) #define COMSIG_MOB_CLIENT_PRE_LIVING_MOVE "mob_client_pre_living_move" /// Should we stop the current living movement attempt diff --git a/code/__DEFINES/drone.dm b/code/__DEFINES/drone.dm index 8907d172194cd..0d31e3caf411d 100644 --- a/code/__DEFINES/drone.dm +++ b/code/__DEFINES/drone.dm @@ -2,9 +2,8 @@ /// If drones are blacklisted from certain sensitive machines GLOBAL_VAR_INIT(drone_machine_blacklist_enabled, FALSE) -#define DRONE_HANDS_LAYER 1 -#define DRONE_HEAD_LAYER 2 -#define DRONE_TOTAL_LAYERS 2 +#define DRONE_HEAD_LAYER 1 +#define DRONE_TOTAL_LAYERS 1 /// Message displayed when new drone spawns in drone network #define DRONE_NET_CONNECT span_notice("DRONE NETWORK: [name] connected.") diff --git a/code/__DEFINES/footsteps.dm b/code/__DEFINES/footsteps.dm index d77a0e1cab347..c8b89b082a497 100644 --- a/code/__DEFINES/footsteps.dm +++ b/code/__DEFINES/footsteps.dm @@ -25,6 +25,7 @@ #define FOOTSTEP_MOB_SHOE "footstep_shoe" #define FOOTSTEP_MOB_HUMAN "footstep_human" //Warning: Only works on /mob/living/carbon/human #define FOOTSTEP_MOB_SLIME "footstep_slime" +#define FOOTSTEP_MOB_RUST "footstep_rust" #define FOOTSTEP_OBJ_MACHINE "footstep_machine" #define FOOTSTEP_OBJ_ROBOT "footstep_robot" diff --git a/code/__DEFINES/guardian_defines.dm b/code/__DEFINES/guardian_defines.dm index d7aae6965a756..e7961368feee0 100644 --- a/code/__DEFINES/guardian_defines.dm +++ b/code/__DEFINES/guardian_defines.dm @@ -3,6 +3,5 @@ #define GUARDIAN_THEME_CARP "carp" #define GUARDIAN_THEME_MINER "miner" -#define GUARDIAN_COLOR_LAYER 2 -#define GUARDIAN_HANDS_LAYER 1 -#define GUARDIAN_TOTAL_LAYERS 2 +#define GUARDIAN_COLOR_LAYER 1 +#define GUARDIAN_TOTAL_LAYERS 1 diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index b775b948c87c4..23d941585e6d2 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -11,8 +11,14 @@ #define isweakref(D) (istype(D, /datum/weakref)) +#define isimage(thing) (istype(thing, /image)) + GLOBAL_VAR_INIT(magic_appearance_detecting_image, new /image) // appearances are awful to detect safely, but this seems to be the best way ~ninjanomnom -#define isappearance(thing) (!ispath(thing) && istype(GLOB.magic_appearance_detecting_image, thing)) +#define isappearance(thing) (!isimage(thing) && !ispath(thing) && istype(GLOB.magic_appearance_detecting_image, thing)) + +// The filters list has the same ref type id as a filter, but isnt one and also isnt a list, so we have to check if the thing has Cut() instead +GLOBAL_VAR_INIT(refid_filter, TYPEID(filter(type="angular_blur"))) +#define isfilter(thing) (!hascall(thing, "Cut") && TYPEID(thing) == GLOB.refid_filter) #define isgenerator(A) (istype(A, /generator)) @@ -92,6 +98,7 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( #define isabductor(A) (is_species(A, /datum/species/abductor)) #define isgolem(A) (is_species(A, /datum/species/golem)) #define islizard(A) (is_species(A, /datum/species/lizard)) +#define isashwalker(A) (is_species(A, /datum/species/lizard/ashwalker)) #define isplasmaman(A) (is_species(A, /datum/species/plasmaman)) #define ispodperson(A) (is_species(A, /datum/species/pod)) #define isflyperson(A) (is_species(A, /datum/species/fly)) @@ -148,10 +155,13 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( #define ismining(A) (istype(A, /mob/living/simple_animal/hostile/asteroid) || istype(A, /mob/living/basic/mining)) +/// constructs, which are both simple and basic for now +#define isconstruct(A) (istype(A, /mob/living/simple_animal/hostile/construct) || istype(A, /mob/living/basic/construct)) + //Simple animals #define isanimal(A) (istype(A, /mob/living/simple_animal)) -#define isrevenant(A) (istype(A, /mob/living/simple_animal/revenant)) +#define isrevenant(A) (istype(A, /mob/living/basic/revenant)) #define isbot(A) (istype(A, /mob/living/simple_animal/bot)) @@ -175,13 +185,11 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( #define isguardian(A) (istype(A, /mob/living/simple_animal/hostile/guardian)) -#define isconstruct(A) (istype(A, /mob/living/simple_animal/hostile/construct)) - #define ismegafauna(A) (istype(A, /mob/living/simple_animal/hostile/megafauna)) #define isclown(A) (istype(A, /mob/living/basic/clown)) -#define isspider(A) (istype(A, /mob/living/basic/spider/giant)) +#define isspider(A) (istype(A, /mob/living/basic/spider)) //Misc mobs diff --git a/code/__DEFINES/projectiles.dm b/code/__DEFINES/projectiles.dm index 3bc745756aae3..a82ac8422c5ae 100644 --- a/code/__DEFINES/projectiles.dm +++ b/code/__DEFINES/projectiles.dm @@ -73,3 +73,9 @@ #define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED)) #define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT)) + +/// The amount of energy that a standard energy weapon cell can hold +#define STANDARD_CELL_CHARGE 1000 +/// Macro to turn a number of laser shots into an energy cost, based on the above define +/// e.g. LASER_SHOTS(12, STANDARD_CELL_CHARGE) means 12 shots +#define LASER_SHOTS(X, MAX_CHARGE) (((100 * MAX_CHARGE) - ((100 * MAX_CHARGE) % X)) / (100 * X)) // I wish I could just use round, but it can't be used in datum members diff --git a/code/__DEFINES/reagents.dm b/code/__DEFINES/reagents.dm index 3a32aec2fba0d..73c1fe43f6e33 100644 --- a/code/__DEFINES/reagents.dm +++ b/code/__DEFINES/reagents.dm @@ -85,12 +85,8 @@ #define DEFAULT_REAGENT_TEMPERATURE 300 //Used in holder.dm/equlibrium.dm to set values and volume limits -///stops floating point errors causing issues with checking reagent amounts +///the minimum volume of reagents than can be operated on. #define CHEMICAL_QUANTISATION_LEVEL 0.0001 -///The smallest amount of volume allowed - prevents tiny numbers -#define CHEMICAL_VOLUME_MINIMUM 0.001 -///Round to this, to prevent extreme decimal magic and to keep reagent volumes in line with perceived values. -#define CHEMICAL_VOLUME_ROUNDING 0.01 ///Default pH for reagents datum #define CHEMICAL_NORMAL_PH 7.000 ///The maximum temperature a reagent holder can attain @@ -120,6 +116,13 @@ #define REAGENT_CLEANS (1<<8) ///Does this reagent affect wounds? Used to check if some procs should be ran. #define REAGENT_AFFECTS_WOUNDS (1<<9) +/// If present, when metabolizing out of a mob, we divide by the mob's metabolism rather than multiply. +/// Without this flag: Higher metabolism means the reagent exits the system faster. +/// With this flag: Higher metabolism means the reagent exits the system slower. +#define REAGENT_REVERSE_METABOLISM (1<<10) +/// If present, this reagent will not be affected by the mob's metabolism at all, meaning it exits at a fixed rate for all mobs. +/// Supercedes [REAGENT_REVERSE_METABOLISM]. +#define REAGENT_UNAFFECTED_BY_METABOLISM (1<<11) //Chemical reaction flags, for determining reaction specialties ///Convert into impure/pure on reaction completion diff --git a/code/__DEFINES/research/anomalies.dm b/code/__DEFINES/research/anomalies.dm index 911a2958d89e3..12a114439c7d9 100644 --- a/code/__DEFINES/research/anomalies.dm +++ b/code/__DEFINES/research/anomalies.dm @@ -32,6 +32,7 @@ GLOBAL_LIST_INIT(bioscrambler_parts_blacklist, typecacheof(list( GLOBAL_LIST_INIT(bioscrambler_organs_blacklist, typecacheof(list ( /obj/item/organ/external/pod_hair, /obj/item/organ/external/spines, + /obj/item/organ/external/wings, /obj/item/organ/external/wings/functional, /obj/item/organ/internal/alien, /obj/item/organ/internal/brain, diff --git a/code/__DEFINES/song.dm b/code/__DEFINES/song.dm index 7af269fbe219b..782a7923ea14f 100644 --- a/code/__DEFINES/song.dm +++ b/code/__DEFINES/song.dm @@ -13,3 +13,5 @@ ///it's what monkeys play! #define MONKEY_SONG "BPM: 200\nC4/0,14,C,A4-F2,F3,A3,F-F2,A-F,F4,G4,F,D4-Bb2-G2\nD3,G3,D-G2,G3-G2,D,D4-G3,D,B4-B2,G,B3,G-B2,B3-B2\nG4,A4,G,E4-C3,E3,G3,E-C,G-C,E,E4-G,E,C5-E-A3,C4\nA-E3,C,E4-C3,A4-C4,B4-A3-A2,C5-C4,D5-F-B3,D4,B-F3\nD,F4-D3,D4,F-B-B2,G4-D,A4-C-F3,F,C/2,B3/2,A3-C3/2\nB/2,C4,E-C3,F4,G-C,F-F3,F-C,C4/2,B/2,A-A2/2,G3/2\nF/I" +///song played by the mook bard +#define MOOK_SONG "BPM: 240\nA5,B5,C#6,D6,E6/0.17,A/0.5,A/0.25,A3/0.25\nA4/0.25,C#5/0.25,E5/0.25,A/0.25,C#/0.25,E/0.12\nC#6/0.25,C#/0.25,E6/0.25,A3/0.25,A4/0.25\nC#5/0.25,E5/0.25,A/0.25,C#/0.25,E/0.25,D/0.25\nG6/0.25,D/0.17,F6/0.17,C#6/0.5,E6/0.5,D4/0.25\nA/0.25,D5/0.25,F5/0.25,A/0.25,D/0.25,F/0.25\nD6/0.08,F6/0.08,D4/0.25,A/0.25,D5/0.25,F5/0.25\nCn4/0.2,B/0.17,D6/0.17,G5/0.5,G/0.25,B3/0.25\nD4/0.25,G4/0.25,B4/0.25,D/0.25,G/0.25,B/0.12\nB5/0.25,B/0.25,D6/0.25,G3/0.25,G4/0.25,B4/0.25\nF/0.25,G/0.25,B/0.25,F/0.25,D/0.25,F6/0.25\nC6/0.17,E/0.17,B5/0.5,D#/0.5,C4/0.25,G/0.25\nC5/0.25,E5/0.25,G/0.25,C/0.25,E/0.25,C6/0.08\nE6/0.08,C4/0.25,Dn4/0.25,E4/0.25,A5/0.17,B/0.5\nC6/0.25,F5/0.08,F4/0.08,C5/0.08,E5/0.12,G5/0.12\nC6/0.25,E6/0.25,E4/0.08,C5/0.08,B/0.17,F6/0.17\nE6/0.5,B/0.25,E4/0.08,G#4/0.08,C6/0.17,D6/0.5\nE6/0.25,A3/0.25,E4/0.25,C5/0.25,Gn3/0.25\nF5/0.12,A5/0.12,A6/0.25,F3/0.25,F4/0.12,A4/0.12\nC/0.12,F6/0.17,A6/0.17,G#6/0.5,A/0.25,F3/0.25\nF4/0.12,A4/0.12,D#5/0.12,B/0.17,G#/0.17,B6/0.5\nB5/0.25,G#/0.25,E3/0.25,E4/0.12,G#4/0.12\nDn/0.12,E6/0.08,E3/0.25,F#3/0.25,G#3/0.25\nE5/0.17,A5/0.17,E/0.5,E/0.25,A3/0.25,C#4/0.25\nE4/0.25,C#/0.25,E/0.12,A5/0.5,B/0.5,C#6/0.5\nD6/0.5,A3/0.25,C#4/0.25,E/0.25,C#/0.25,E/0.25\nE6/0.08,Gn/0.25,E4/0.25,A4/0.25,C#5/0.25,E/0.25\nA/0.25,C#/0.25,E6/0.17,E/0.5,Fn6/0.5,G6/0.5\nG3/0.25,E4/0.25,A/0.25,C#/0.25,E/0.25,A/0.25\nC#/0.25,F/0.08,A6/0.08,F3/0.25,F4/0.25,A4/0.25\nCn/0.25,F/0.25,A/0.25,C/0.25,G6/0.12,A6/0.12\nG A G F6 G3/0.25 D4/0.25 G4/0.25 B4/0.25 D/0.25\nG/0.25 B/0.25 E6/0.12 G6/0.12 F/0.71 G/0.71 F/0.71\nE3/0.25 E4/0.25 G4/0.25 B/0.25 E/0.25 G/0.25 B/0.25\nA5/0.08 E6/0.08 A3/0.25 E4/0.25 A4/0.25 C#/0.25 E/0.25 A/0.25 C#/0.25 D6/0.17 E6/0.5 F/0.25 B3/0.25 D4/0.12 F4/0.12 B4/0.12 F6/0.25 E/0.25 D6/0.25 G#3/0.25 E4/0.12 G#4/0.12 B/0.12 Cn6/0.12 D/0.25 A3/0.25 A4/0.25 C5/0.25 E5/0.25 G#3/0.25 Gn/0.25 C4/0.25 E4/0.25 A/" diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 4af9f4396be59..f4d3c5aebca41 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -172,6 +172,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_DEFIB_BLACKLISTED "defib_blacklisted" #define TRAIT_BADDNA "baddna" #define TRAIT_CLUMSY "clumsy" +/// Trait that means you are capable of holding items in some form +#define TRAIT_CAN_HOLD_ITEMS "can_hold_items" +/// Trait which lets you clamber over a barrier +#define TRAIT_FENCE_CLIMBER "can_climb_fences" /// means that you can't use weapons with normal trigger guards. #define TRAIT_CHUNKYFINGERS "chunkyfingers" #define TRAIT_CHUNKYFINGERS_IGNORE_BATON "chunkyfingers_ignore_baton" @@ -522,6 +526,11 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai // and emit less heat. Present on /mob or /datum/mind #define TRAIT_SUPERMATTER_SOOTHER "supermatter_soother" +/// Trait added when a revenant is visible. +#define TRAIT_REVENANT_REVEALED "revenant_revealed" +/// Trait added when a revenant has been inhibited (typically by the bane of a holy weapon) +#define TRAIT_REVENANT_INHIBITED "revenant_inhibited" + /// Trait which prevents you from becoming overweight #define TRAIT_NOFAT "cant_get_fat" diff --git a/code/__DEFINES/wires.dm b/code/__DEFINES/wires.dm index 6f618ac824032..2b4c528abc212 100644 --- a/code/__DEFINES/wires.dm +++ b/code/__DEFINES/wires.dm @@ -37,6 +37,7 @@ #define WIRE_LIMIT "Limiter" #define WIRE_LOADCHECK "Load Check" #define WIRE_LOCKDOWN "Lockdown" +#define WIRE_MODE_SELECT "Mode Select" #define WIRE_MOTOR1 "Motor 1" #define WIRE_MOTOR2 "Motor 2" #define WIRE_OPEN "Open" diff --git a/code/__HELPERS/chat.dm b/code/__HELPERS/chat.dm index 31f9ad271d39c..fb07f2f107d01 100644 --- a/code/__HELPERS/chat.dm +++ b/code/__HELPERS/chat.dm @@ -92,8 +92,8 @@ it will be sent to all connected chats. var/link = FOLLOW_LINK(observer, source) to_chat(observer, "[link] [message]") -/// Sends a message to everyone with blob telepathy, and all observers -/proc/blob_telepathy(message, source) - for(var/mob/creature as anything in GLOB.blob_telepathy_mobs) +/// Sends a message to everyone within the list, as well as all observers. +/proc/relay_to_list_and_observers(message, list/mob_list, source) + for(var/mob/creature as anything in mob_list) to_chat(creature, message) send_to_observers(message, source) diff --git a/code/__HELPERS/construction.dm b/code/__HELPERS/construction.dm index 9d21362a87adb..9cc7d11253256 100644 --- a/code/__HELPERS/construction.dm +++ b/code/__HELPERS/construction.dm @@ -1,5 +1,18 @@ -/// Produces a new RCD result from the given one if it can be calculated that -/// the RCD should speed up with the remembered form. +/// Makes sure only integer values are used when consuming, removing & checking for mats +#define OPTIMAL_COST(cost)(max(1, round(cost))) + +/// Wrapper for fetching material references. Exists exclusively so that people don't need to wrap everything in a list every time. +#define GET_MATERIAL_REF(arguments...) SSmaterials._GetMaterialRef(list(##arguments)) + +// Wrapper to convert material name into its source name +#define MATERIAL_SOURCE(mat) "[mat.name]_material" + + +/** + * Produces a new RCD result from the given one if it can be calculated that + * the RCD should speed up with the remembered form. + * + */ /proc/rcd_result_with_memory(list/defaults, turf/place, expected_memory) if (place?.rcd_memory == expected_memory) return defaults + list( diff --git a/code/_globalvars/lists/crafting.dm b/code/_globalvars/lists/crafting.dm new file mode 100644 index 0000000000000..48b2b61a42bf8 --- /dev/null +++ b/code/_globalvars/lists/crafting.dm @@ -0,0 +1,44 @@ +GLOBAL_LIST_INIT(crafting_category_food, list( + CAT_FOOD, + CAT_BREAD, + CAT_BURGER, + CAT_CAKE, + CAT_EGG, + CAT_LIZARD, + CAT_MEAT, + CAT_SEAFOOD, + CAT_MARTIAN, + CAT_MISCFOOD, + CAT_MEXICAN, + CAT_MOTH, + CAT_PASTRY, + CAT_PIE, + CAT_PIZZA, + CAT_SALAD, + CAT_SANDWICH, + CAT_SOUP, + CAT_SPAGHETTI, + CAT_ICE, + CAT_DRINK, +)) + +GLOBAL_LIST_INIT(crafting_category, list( + CAT_WEAPON_RANGED, + CAT_WEAPON_MELEE, + CAT_WEAPON_AMMO, + CAT_ROBOT, + CAT_MISC, + CAT_CLOTHING, + CAT_CHEMISTRY, + CAT_ATMOSPHERIC, + CAT_STRUCTURE, + CAT_TILES, + CAT_WINDOWS, + CAT_DOORS, + CAT_FURNITURE, + CAT_EQUIPMENT, + CAT_CONTAINERS, + CAT_ENTERTAINMENT, + CAT_TOOLS, + CAT_CULT, +)) diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 5fc93a2494e30..5ee1f4e643954 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -55,6 +55,9 @@ GLOBAL_LIST_EMPTY(current_observers_list) /// All living mobs which can hear blob telepathy GLOBAL_LIST_EMPTY(blob_telepathy_mobs) +/// All "living" (because revenants are in between mortal planes or whatever) mobs that can hear revenants +GLOBAL_LIST_EMPTY(revenant_relay_mobs) + ///underages who have been reported to security for trying to buy things they shouldn't, so they can't spam GLOBAL_LIST_EMPTY(narcd_underages) diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index 2fd384e69a94f..8239dc29231f4 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -31,19 +31,6 @@ GLOBAL_LIST_EMPTY(deliverybeacontags) /// List of all singularity components that exist GLOBAL_LIST_EMPTY_TYPED(singularities, /datum/component/singularity) -/// list of all /datum/chemical_reaction datums indexed by their typepath. Use this for general lookup stuff -GLOBAL_LIST(chemical_reactions_list) -/// list of all /datum/chemical_reaction datums. Used during chemical reactions. Indexed by REACTANT types -GLOBAL_LIST(chemical_reactions_list_reactant_index) -/// list of all /datum/chemical_reaction datums. Used for the reaction lookup UI. Indexed by PRODUCT type -GLOBAL_LIST(chemical_reactions_list_product_index) /// list of all /datum/reagent datums indexed by reagent id. Used by chemistry stuff -GLOBAL_LIST_INIT(chemical_reagents_list, init_chemical_reagent_list()) -/// names of reagents used by plumbing UI. -GLOBAL_LIST_INIT(chemical_name_list, init_chemical_name_list()) -/// List of all reactions with their associated product and result ids. Used for reaction lookups -GLOBAL_LIST(chemical_reactions_results_lookup_list) -/// List of all reagents that are parent types used to define a bunch of children - but aren't used themselves as anything. -GLOBAL_LIST(fake_reagent_blacklist) /// list of all /datum/tech datums indexed by id. GLOBAL_LIST_EMPTY(tech_list) /// list of all surgeries by name, associated with their path. diff --git a/code/_globalvars/lists/rcd.dm b/code/_globalvars/lists/rcd.dm new file mode 100644 index 0000000000000..86f546df22307 --- /dev/null +++ b/code/_globalvars/lists/rcd.dm @@ -0,0 +1,90 @@ +///all stuff used by RCD for construction +GLOBAL_LIST_INIT(rcd_designs, list( + //1ST ROOT CATEGORY + "Construction" = list( //Stuff you use to make & decorate areas + //Walls & Windows + "Structures" = list( + list(RCD_DESIGN_MODE = RCD_TURF, RCD_DESIGN_PATH = /turf/open/floor/plating/rcd), + list(RCD_DESIGN_MODE = RCD_WINDOWGRILLE, RCD_DESIGN_PATH = /obj/structure/window), + list(RCD_DESIGN_MODE = RCD_WINDOWGRILLE, RCD_DESIGN_PATH = /obj/structure/window/reinforced), + list(RCD_DESIGN_MODE = RCD_WINDOWGRILLE, RCD_DESIGN_PATH = /obj/structure/window/fulltile), + list(RCD_DESIGN_MODE = RCD_WINDOWGRILLE, RCD_DESIGN_PATH = /obj/structure/window/reinforced/fulltile), + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/reflector/wrenched), + list(RCD_DESIGN_MODE = RCD_TURF, RCD_DESIGN_PATH = /obj/structure/lattice/catwalk), + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/girder), + ), + + //Computers & Machine Frames + "Machines" = list( + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/frame/machine/secured), + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/frame/computer/rcd/north), + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/frame/computer/rcd/south), + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/frame/computer/rcd/east), + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/frame/computer/rcd/west), + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/floodlight_frame/completed), + list(RCD_DESIGN_MODE = RCD_WALLFRAME, RCD_DESIGN_PATH = /obj/item/wallframe/apc), + list(RCD_DESIGN_MODE = RCD_WALLFRAME, RCD_DESIGN_PATH = /obj/item/wallframe/airalarm), + list(RCD_DESIGN_MODE = RCD_WALLFRAME, RCD_DESIGN_PATH = /obj/item/wallframe/firealarm), + ), + + //Interior Design[construction_mode = RCD_FURNISHING is implied] + "Furniture" = list( + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/chair), + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/chair/stool), + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/chair/stool/bar), + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/table), + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/table/glass), + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/rack), + list(RCD_DESIGN_MODE = RCD_STRUCTURE, RCD_DESIGN_PATH = /obj/structure/bed), + ), + ), + + //2ND ROOT CATEGORY[construction_mode = RCD_AIRLOCK is implied,"icon=closed"] + "Airlocks" = list( //used to seal/close areas + //Window Doors[airlock_glass = TRUE is implied] + "Windoors" = list( + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/window), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/window/brigdoor), + ), + + //Glass Airlocks[airlock_glass = TRUE is implied,do fill_closed overlay] + "Glass Airlocks" = list( + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/glass), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/public/glass), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/engineering/glass), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/atmos/glass), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/security/glass), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/command/glass), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/medical/glass), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/research/glass), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/virology/glass), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/mining/glass), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/maintenance/glass), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/external/glass), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/maintenance/external/glass), + ), + + //Solid Airlocks[airlock_glass = FALSE is implied,no fill_closed overlay] + "Solid Airlocks" = list( + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/public), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/engineering), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/atmos), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/security), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/command), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/medical), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/research), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/freezer), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/virology), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/mining), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/maintenance), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/external), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/maintenance/external), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/hatch), + list(RCD_DESIGN_MODE = RCD_AIRLOCK, RCD_DESIGN_PATH = /obj/machinery/door/airlock/maintenance_hatch), + ), + ), + + //3RD CATEGORY Airlock access,empty list cause airlock_electronics UI will be displayed when this tab is selected + "Airlock Access" = list() +)) diff --git a/code/_globalvars/lists/reagents.dm b/code/_globalvars/lists/reagents.dm new file mode 100644 index 0000000000000..dec2724cfebd1 --- /dev/null +++ b/code/_globalvars/lists/reagents.dm @@ -0,0 +1,154 @@ +/// list of all /datum/chemical_reaction datums indexed by their typepath. Use this for general lookup stuff +GLOBAL_LIST(chemical_reactions_list) +/// list of all /datum/chemical_reaction datums. Used during chemical reactions. Indexed by REACTANT types +GLOBAL_LIST(chemical_reactions_list_reactant_index) +/// list of all /datum/chemical_reaction datums. Used for the reaction lookup UI. Indexed by PRODUCT type +GLOBAL_LIST(chemical_reactions_list_product_index) +/// list of all /datum/reagent datums indexed by reagent id. Used by chemistry stuff +GLOBAL_LIST_INIT(chemical_reagents_list, init_chemical_reagent_list()) +/// list of all reactions with their associated product and result ids. Used for reaction lookups +GLOBAL_LIST(chemical_reactions_results_lookup_list) +/// list of all reagents that are parent types used to define a bunch of children - but aren't used themselves as anything. +GLOBAL_LIST(fake_reagent_blacklist) +/// Turfs metalgen cant touch +GLOBAL_LIST_INIT(blacklisted_metalgen_types, typecacheof(list( + /turf/closed/indestructible, //indestructible turfs should be indestructible, metalgen transmutation to plasma allows them to be destroyed + /turf/open/indestructible +))) +/// Names of human readable reagents used by plumbing UI. +GLOBAL_LIST_INIT(chemical_name_list, init_chemical_name_list()) +/// Map of reagent names to its datum path +GLOBAL_LIST_INIT(name2reagent, build_name2reagentlist()) + +/// Initialises all /datum/reagent into a list indexed by reagent id +/proc/init_chemical_reagent_list() + var/list/reagent_list = list() + + for(var/datum/reagent/path as anything in subtypesof(/datum/reagent)) + if(path in GLOB.fake_reagent_blacklist) + continue + var/datum/reagent/target_object = new path() + target_object.mass = rand(10, 800) + reagent_list[path] = target_object + + return reagent_list + +/// Creates an list which is indexed by reagent name . used by plumbing reaction chamber and chemical filter UI +/proc/init_chemical_name_list() + var/list/name_list = list() + + for(var/X in GLOB.chemical_reagents_list) + var/datum/reagent/Reagent = GLOB.chemical_reagents_list[X] + name_list += Reagent.name + + return sort_list(name_list) + +/** + * Chemical Reactions - Initialises all /datum/chemical_reaction into a list + * It is filtered into multiple lists within a list. + * For example: + * chemical_reactions_list_reactant_index[/datum/reagent/toxin/plasma] is a list of all reactions relating to plasma + * For chemical reaction list product index - indexes reactions based off the product reagent type - see get_recipe_from_reagent_product() in helpers + * For chemical reactions list lookup list - creates a bit list of info passed to the UI. This is saved to reduce lag from new windows opening, since it's a lot of data. + */ +/proc/build_chemical_reactions_lists() + if(GLOB.chemical_reactions_list_reactant_index) + return + + //Prevent these reactions from appearing in lookup tables (UI code) + var/list/blacklist = typecacheof(/datum/chemical_reaction/randomized) + + //Randomized need to go last since they need to check against conflicts with normal recipes + var/paths = subtypesof(/datum/chemical_reaction) - typesof(/datum/chemical_reaction/randomized) + subtypesof(/datum/chemical_reaction/randomized) + GLOB.chemical_reactions_list = list() //typepath to reaction list + GLOB.chemical_reactions_list_reactant_index = list() //reagents to reaction list + GLOB.chemical_reactions_results_lookup_list = list() //UI glob + GLOB.chemical_reactions_list_product_index = list() //product to reaction list + + var/list/datum/chemical_reaction/reactions = list() + for(var/path in paths) + var/datum/chemical_reaction/reaction = new path() + reactions += reaction + + // Ok so we're gonna do a thingTM here + // I want to distribute all our reactions such that each reagent id links to as few as possible + // I get the feeling there's a canonical way of doing this, but I don't know it + // So instead, we're gonna wing it + var/list/reagent_to_react_count = list() + for(var/datum/chemical_reaction/reaction as anything in reactions) + for(var/reagent_id as anything in reaction.required_reagents) + reagent_to_react_count[reagent_id] += 1 + + var/list/reaction_lookup = GLOB.chemical_reactions_list_reactant_index + // Create filters based on a random reagent id in the required reagents list - this is used to speed up handle_reactions() + // Basically, we only really need to care about ONE reagent, at least when initially filtering, since any others are ignorable + // Doing this separately because it relies on the loop above, and this is easier to parse + for(var/datum/chemical_reaction/reaction as anything in reactions) + var/preferred_id = null + for(var/reagent_id as anything in reaction.required_reagents) + if(isnull(preferred_id)) + preferred_id = reagent_id + continue + // If we would have less then they would, take it + if(length(reaction_lookup[reagent_id]) < length(reaction_lookup[preferred_id])) + preferred_id = reagent_id + continue + // If they potentially have more then us, we take it + if(reagent_to_react_count[reagent_id] < reagent_to_react_count[preferred_id]) + preferred_id = reagent_id + continue + if (!isnull(preferred_id)) + if(!reaction_lookup[preferred_id]) + reaction_lookup[preferred_id] = list() + reaction_lookup[preferred_id] += reaction + + for(var/datum/chemical_reaction/reaction as anything in reactions) + var/list/product_ids = list() + var/list/reagents = list() + var/list/product_names = list() + var/bitflags = reaction.reaction_tags + + if(!length(reaction.required_reagents)) //Skip impossible reactions + continue + + GLOB.chemical_reactions_list[reaction.type] = reaction + + for(var/reagent_path in reaction.required_reagents) + var/datum/reagent/reagent = find_reagent_object_from_type(reagent_path) + if(!istype(reagent)) + stack_trace("Invalid reagent found in [reaction] required_reagents: [reagent_path]") + continue + reagents += list(list("name" = reagent.name, "id" = reagent.type)) + + for(var/product in reaction.results) + var/datum/reagent/reagent = find_reagent_object_from_type(product) + if(!istype(reagent)) + stack_trace("Invalid reagent found in [reaction] results: [product]") + continue + product_names += reagent.name + product_ids += product + + var/product_name + if(!length(product_names)) + var/list/names = splittext("[reaction.type]", "/") + product_name = names[names.len] + else + product_name = product_names[1] + + if(!is_type_in_typecache(reaction.type, blacklist)) + //Master list of ALL reactions that is used in the UI lookup table. This is expensive to make, and we don't want to lag the server by creating it on UI request, so it's cached to send to UIs instantly. + GLOB.chemical_reactions_results_lookup_list += list(list("name" = product_name, "id" = reaction.type, "bitflags" = bitflags, "reactants" = reagents)) + + // Create filters based on each reagent id in the required reagents list - this is specifically for finding reactions from product(reagent) ids/typepaths. + for(var/id in product_ids) + if(!GLOB.chemical_reactions_list_product_index[id]) + GLOB.chemical_reactions_list_product_index[id] = list() + GLOB.chemical_reactions_list_product_index[id] += reaction + +/// Builds map of reagent name to its datum path +/proc/build_name2reagentlist() + . = list() + for (var/datum/reagent/R as anything in subtypesof(/datum/reagent)) + var/name = initial(R.name) + if (length(name)) + .[ckey(name)] = R diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm index a913410214da7..b37c61bc6815f 100644 --- a/code/_globalvars/phobias.dm +++ b/code/_globalvars/phobias.dm @@ -72,7 +72,6 @@ GLOBAL_LIST_INIT(phobia_mobs, list( "doctors" = typecacheof(list(/mob/living/simple_animal/bot/medbot)), "heresy" = typecacheof(list( /mob/living/basic/heretic_summon, - /mob/living/simple_animal/hostile/heretic_summon, )), "insects" = typecacheof(list( /mob/living/basic/cockroach, @@ -92,18 +91,18 @@ GLOBAL_LIST_INIT(phobia_mobs, list( "the supernatural" = typecacheof(list( /mob/dead/observer, /mob/living/basic/bat, + /mob/living/basic/construct, /mob/living/basic/demon, /mob/living/basic/faithless, /mob/living/basic/ghost, /mob/living/basic/heretic_summon, + /mob/living/basic/revenant, /mob/living/simple_animal/bot/mulebot/paranormal, /mob/living/simple_animal/hostile/construct, /mob/living/simple_animal/hostile/dark_wizard, - /mob/living/simple_animal/hostile/heretic_summon, /mob/living/simple_animal/hostile/skeleton, /mob/living/simple_animal/hostile/wizard, /mob/living/simple_animal/hostile/zombie, - /mob/living/simple_animal/revenant, /mob/living/simple_animal/shade, )), )) diff --git a/code/_globalvars/rcd.dm b/code/_globalvars/rcd.dm new file mode 100644 index 0000000000000..9e674273bf94f --- /dev/null +++ b/code/_globalvars/rcd.dm @@ -0,0 +1,13 @@ +GLOBAL_VAR_INIT(icon_holographic_wall, init_holographic_wall()) +GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) + +/proc/init_holographic_wall() + return icon('icons/turf/walls/wall.dmi', "wall-0") + +/proc/init_holographic_window() + var/icon/grille_icon = icon('icons/obj/structures.dmi', "grille") + var/icon/window_icon = icon('icons/obj/smooth_structures/window.dmi', "window-0") + + grille_icon.Blend(window_icon, ICON_OVERLAY) + + return grille_icon diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index 512b528f86fbc..dc64545a58e84 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -30,6 +30,8 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CHUNKYFINGERS" = TRAIT_CHUNKYFINGERS, "TRAIT_CHUNKYFINGERS_IGNORE_BATON" = TRAIT_CHUNKYFINGERS_IGNORE_BATON, "TRAIT_FIST_MINING" = TRAIT_FIST_MINING, + "TRAIT_CAN_HOLD_ITEMS" = TRAIT_CAN_HOLD_ITEMS, + "TRAIT_FENCE_CLIMBER" = TRAIT_FENCE_CLIMBER, "TRAIT_DUMB" = TRAIT_DUMB, "TRAIT_ADVANCEDTOOLUSER" = TRAIT_ADVANCEDTOOLUSER, "TRAIT_DISCOORDINATED_TOOL_USER" = TRAIT_DISCOORDINATED_TOOL_USER, diff --git a/code/_onclick/hud/generic_dextrous.dm b/code/_onclick/hud/generic_dextrous.dm index bf09fa3371713..64ad896d57a1f 100644 --- a/code/_onclick/hud/generic_dextrous.dm +++ b/code/_onclick/hud/generic_dextrous.dm @@ -43,7 +43,7 @@ using.icon = ui_style static_inventory += using - mymob.canon_client.clear_screen() + mymob.canon_client?.clear_screen() for(var/atom/movable/screen/inventory/inv in (static_inventory + toggleable_inventory)) if(inv.slot_id) diff --git a/code/_onclick/hud/screentip.dm b/code/_onclick/hud/screentip.dm index 107f4ce1be523..94b8f591f650f 100644 --- a/code/_onclick/hud/screentip.dm +++ b/code/_onclick/hud/screentip.dm @@ -14,7 +14,7 @@ /atom/movable/screen/screentip/proc/update_view(datum/source) SIGNAL_HANDLER - if(!hud || !hud.mymob.canon_client.view_size) //Might not have been initialized by now + if(!hud || !hud.mymob.canon_client?.view_size) //Might not have been initialized by now return maptext_width = view_to_pixels(hud.mymob.canon_client.view_size.getView())[1] diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index 1868057d82c18..aeb42361c0573 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -287,34 +287,13 @@ /atom/proc/attack_pai_secondary(mob/user, list/modifiers) return SECONDARY_ATTACK_CALL_NORMAL -/* - Simple animals -*/ - -/mob/living/simple_animal/resolve_unarmed_attack(atom/attack_target, list/modifiers) - if(dextrous && (isitem(attack_target) || !combat_mode)) - attack_target.attack_hand(src, modifiers) - update_held_items() - else - return ..() - -/mob/living/simple_animal/resolve_right_click_attack(atom/target, list/modifiers) - if(dextrous && (isitem(target) || !combat_mode)) - . = target.attack_hand_secondary(src, modifiers) - update_held_items() - else - return ..() - /* Hostile animals */ /mob/living/simple_animal/hostile/resolve_unarmed_attack(atom/attack_target, list/modifiers) GiveTarget(attack_target) - if(dextrous && (isitem(attack_target) || !combat_mode)) - return ..() - else - INVOKE_ASYNC(src, PROC_REF(AttackingTarget), attack_target) + INVOKE_ASYNC(src, PROC_REF(AttackingTarget), attack_target) #undef LIVING_UNARMED_ATTACK_BLOCKED diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index 488debb48e5a8..1de3f900c8ca0 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -452,7 +452,7 @@ SUBSYSTEM_DEF(air) border += item net.air.volume += item.volume - item.parent = net + item.replace_pipenet(item.parent, net) if(item.air_temporary) net.air.merge(item.air_temporary) diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm index 7f0b52d471bc8..1cc3468fb7db4 100644 --- a/code/controllers/subsystem/research.dm +++ b/code/controllers/subsystem/research.dm @@ -313,6 +313,8 @@ SUBSYSTEM_DEF(research) */ /datum/controller/subsystem/research/proc/get_available_servers(turf/location) var/list/local_servers = list() + if(!location) + return local_servers for (var/datum/techweb/individual_techweb as anything in techwebs) var/list/servers = find_valid_servers(location, individual_techweb) if(length(servers)) diff --git a/code/datums/ai/basic_mobs/base_basic_controller.dm b/code/datums/ai/basic_mobs/base_basic_controller.dm index 3eb79a815ad50..cd025b28bcb2b 100644 --- a/code/datums/ai/basic_mobs/base_basic_controller.dm +++ b/code/datums/ai/basic_mobs/base_basic_controller.dm @@ -18,7 +18,10 @@ if(!isliving(pawn)) return var/mob/living/living_pawn = pawn - if(!(ai_traits & CAN_ACT_WHILE_DEAD) && IS_DEAD_OR_INCAP(living_pawn)) + var/incap_flags = NONE + if (ai_traits & CAN_ACT_IN_STASIS) + incap_flags |= IGNORE_STASIS + if(!(ai_traits & CAN_ACT_WHILE_DEAD) && (living_pawn.incapacitated(incap_flags) || living_pawn.stat)) return FALSE if(ai_traits & PAUSE_DURING_DO_AFTER && LAZYLEN(living_pawn.do_afters)) return FALSE 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 deleted file mode 100644 index ad5749c916111..0000000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/find_mineable_wall.dm +++ /dev/null @@ -1,33 +0,0 @@ -//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_ai_behaviors/run_away_from_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm index bd86260ee89ce..551cb12f3b145 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm @@ -19,13 +19,13 @@ /datum/ai_behavior/run_away_from_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) . = ..() - if (!controller.blackboard[BB_BASIC_MOB_FLEEING]) + if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) return var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] if (QDELETED(target) || !can_see(controller.pawn, target, run_distance)) finish_action(controller, succeeded = TRUE, target_key = target_key, hiding_location_key = hiding_location_key) return - if (get_dist(controller.pawn, controller.current_movement_target) >= required_distance) + if (get_dist(controller.pawn, controller.current_movement_target) > required_distance) return // Still heading over if (plot_path_away_from(controller, target)) return diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm index 04cb9b171ddb6..ba167b34f2951 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm @@ -11,7 +11,8 @@ finish_action(controller, FALSE, ability_key, target_key) return var/mob/pawn = controller.pawn - var/result = ability.InterceptClickOn(pawn, null, target) + pawn.face_atom(target) + var/result = ability.Trigger(target = target) finish_action(controller, result, ability_key, target_key) /datum/ai_behavior/targeted_mob_ability/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm index 0b9e31db667e1..376f62a5855b5 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm @@ -23,7 +23,8 @@ var/aggro_range = controller.blackboard[aggro_range_key] || vision_range controller.clear_blackboard_key(target_key) - var/list/potential_targets = hearers(aggro_range, controller.pawn) - living_mob //Remove self, so we don't suicide + + var/list/potential_targets = hearers(aggro_range, get_turf(controller.pawn)) - living_mob //Remove self, so we don't suicide for(var/HM in typecache_filter_list(range(aggro_range, living_mob), hostile_machines)) //Can we see any hostile machines? if(can_see(living_mob, HM, aggro_range)) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm b/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm new file mode 100644 index 0000000000000..47f4155ac4552 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm @@ -0,0 +1,69 @@ +/// Add or remove people to our retaliation shitlist just on an arbitrary whim +/datum/ai_planning_subtree/capricious_retaliate + /// Blackboard key which tells us how to select valid targets + var/targetting_datum_key = BB_TARGETTING_DATUM + /// Whether we should skip checking faction for our decision + var/ignore_faction = TRUE + +/datum/ai_planning_subtree/capricious_retaliate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + controller.queue_behavior(/datum/ai_behavior/capricious_retaliate, targetting_datum_key, ignore_faction) + +/// Add or remove people to our retaliation shitlist just on an arbitrary whim +/datum/ai_behavior/capricious_retaliate + action_cooldown = 1 SECONDS + +/datum/ai_behavior/capricious_retaliate/perform(seconds_per_tick, datum/ai_controller/controller, targetting_datum_key, ignore_faction) + . = ..() + var/atom/pawn = controller.pawn + if (controller.blackboard_key_exists(BB_BASIC_MOB_RETALIATE_LIST)) + var/deaggro_chance = controller.blackboard[BB_RANDOM_DEAGGRO_CHANCE] || 10 + if (!SPT_PROB(deaggro_chance, seconds_per_tick)) + finish_action(controller, TRUE, ignore_faction) // "true" here means "don't clear our ignoring factions status" + return + pawn.visible_message(span_notice("[pawn] calms down.")) // We can blackboard key this if anyone else actually wants to customise it + controller.clear_blackboard_key(BB_BASIC_MOB_RETALIATE_LIST) + finish_action(controller, FALSE, ignore_faction) + controller.CancelActions() // Otherwise they will try and get one last kick in + return + + var/aggro_chance = controller.blackboard[BB_RANDOM_AGGRO_CHANCE] || 0.5 + if (!SPT_PROB(aggro_chance, seconds_per_tick)) + finish_action(controller, FALSE, ignore_faction) + return + + var/aggro_range = controller.blackboard[BB_AGGRO_RANGE] || 9 + var/list/potential_targets = hearers(aggro_range, get_turf(pawn)) - pawn + if (!length(potential_targets)) + failed_targetting(controller, pawn, ignore_faction) + return + + var/datum/targetting_datum/target_helper = controller.blackboard[targetting_datum_key] + + var/mob/living/final_target = null + if (ignore_faction) + controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, TRUE) + while (isnull(final_target) && length(potential_targets)) + var/mob/living/test_target = pick_n_take(potential_targets) + if (target_helper.can_attack(pawn, test_target, vision_range = aggro_range)) + final_target = test_target + + if (isnull(final_target)) + failed_targetting(controller, pawn, ignore_faction) + return + + controller.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, final_target) + pawn.visible_message(span_warning("[pawn] glares grumpily at [final_target]!")) + finish_action(controller, TRUE, ignore_faction) + +/// Called if we try but fail to target something +/datum/ai_behavior/capricious_retaliate/proc/failed_targetting(datum/ai_controller/controller, atom/pawn, ignore_faction) + finish_action(controller, FALSE, ignore_faction) + pawn.visible_message(span_notice("[pawn] grumbles.")) // We're pissed off but with no outlet to vent our frustration upon + +/datum/ai_behavior/capricious_retaliate/finish_action(datum/ai_controller/controller, succeeded, ignore_faction) + . = ..() + if (succeeded || !ignore_faction) + return + var/usually_ignores_faction = controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || FALSE + controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, usually_ignores_faction) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm b/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm index 1beab6ec90745..bad349030f1b4 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm @@ -1,4 +1,5 @@ /datum/ai_planning_subtree/climb_trees + operational_datums = list(/datum/component/tree_climber) ///chance to climb a tree var/climb_chance = 35 diff --git a/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm index 8d1391f7c7dda..4f901745eeea5 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm @@ -10,7 +10,7 @@ /datum/ai_planning_subtree/flee_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() var/atom/flee_from = controller.blackboard[target_key] - if (!controller.blackboard[BB_BASIC_MOB_FLEEING] || QDELETED(flee_from)) + if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING] || QDELETED(flee_from)) return var/flee_distance = controller.blackboard[BB_BASIC_MOB_FLEE_DISTANCE] || DEFAULT_BASIC_FLEE_DISTANCE if (get_dist(controller.pawn, flee_from) >= flee_distance) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm b/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm index c09e7cdbf75f9..2a85e9e902b2c 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm @@ -8,6 +8,8 @@ var/maximum_distance = 6 /// How far do we look for our target? var/view_distance = 10 + /// the run away behavior we will use + var/run_away_behavior = /datum/ai_behavior/step_away /datum/ai_planning_subtree/maintain_distance/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() @@ -16,12 +18,15 @@ 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) + controller.queue_behavior(run_away_behavior, target_key, minimum_distance) return if (range > maximum_distance) controller.queue_behavior(/datum/ai_behavior/pursue_to_range, target_key, maximum_distance) return +/datum/ai_planning_subtree/maintain_distance/cover_minimum_distance + run_away_behavior = /datum/ai_behavior/cover_minimum_distance + /// Take one step away /datum/ai_behavior/step_away behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION @@ -80,3 +85,32 @@ if (!QDELETED(current_target) && get_dist(controller.pawn, current_target) > range) return finish_action(controller, succeeded = TRUE) + +///instead of taking a single step, we cover the entire distance +/datum/ai_behavior/cover_minimum_distance + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + required_distance = 0 + action_cooldown = 0.2 SECONDS + +/datum/ai_behavior/cover_minimum_distance/setup(datum/ai_controller/controller, target_key, minimum_distance) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + var/required_distance = minimum_distance - get_dist(controller.pawn, target) //the distance we need to move + var/distance = 0 + var/turf/chosen_turf + for(var/turf/open/potential_turf in oview(required_distance, controller.pawn)) + var/new_distance_from_target = get_dist(potential_turf, target) + if(potential_turf.is_blocked_turf()) + continue + if(new_distance_from_target > distance) + chosen_turf = potential_turf + distance = new_distance_from_target + if(isnull(chosen_turf)) + return FALSE + set_movement_target(controller, target = chosen_turf) + +/datum/ai_behavior/cover_minimum_distance/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + finish_action(controller, succeeded = TRUE) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm b/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm new file mode 100644 index 0000000000000..3c03702b69947 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm @@ -0,0 +1,73 @@ +//behavior to find mineable mineral walls + +/datum/ai_planning_subtree/mine_walls + var/find_wall_behavior = /datum/ai_behavior/find_mineral_wall + +/datum/ai_planning_subtree/mine_walls/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(find_wall_behavior, 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(QDELETED(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/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/ranged_skirmish.dm b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm index 1ff752d925ffa..be395f3dfe49d 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm @@ -4,28 +4,28 @@ /// 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 + var/attack_behavior = /datum/ai_behavior/ranged_skirmish + /// 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_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) + controller.queue_behavior(attack_behavior, target_key, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION, max_range, min_range) /// 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) +/datum/ai_behavior/ranged_skirmish/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key, max_range, min_range) . = ..() 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) +/datum/ai_behavior/ranged_skirmish/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key, max_range, min_range) . = ..() var/atom/target = controller.blackboard[target_key] if (QDELETED(target)) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm b/code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm new file mode 100644 index 0000000000000..6f2f5cdc2035c --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm @@ -0,0 +1,33 @@ +/// Intermittently run an emote +/datum/ai_planning_subtree/run_emote + var/emote_key = BB_EMOTE_KEY + var/emote_chance_key = BB_EMOTE_CHANCE + +/datum/ai_planning_subtree/run_emote/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/emote_chance = controller.blackboard[emote_chance_key] || 0 + if (!SPT_PROB(emote_chance, seconds_per_tick)) + return + controller.queue_behavior(/datum/ai_behavior/run_emote, emote_key) + +/// Emote from a blackboard key +/datum/ai_behavior/run_emote + +/datum/ai_behavior/run_emote/perform(seconds_per_tick, datum/ai_controller/controller, emote_key) + var/mob/living/living_pawn = controller.pawn + if (!isliving(living_pawn)) + finish_action(controller, FALSE) + return + + var/list/emote_list = controller.blackboard[emote_key] + var/emote + if (islist(emote_list)) + emote = length(emote_list) ? pick(emote_list) : null + else + emote = emote_list + + if(isnull(emote)) + finish_action(controller, FALSE) + return + + living_pawn.emote(emote) + finish_action(controller, TRUE) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm b/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm new file mode 100644 index 0000000000000..ff01eb804ff71 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm @@ -0,0 +1,41 @@ +/// Shapeshift when we have no target, until someone has been nearby for long enough +/datum/ai_planning_subtree/shapechange_ambush + operational_datums = list(/datum/component/ai_target_timer) + /// Key where we keep our ability + var/ability_key = BB_SHAPESHIFT_ACTION + /// Key where we keep our target + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + /// How long to lull our target into a false sense of security + var/minimum_target_time = 8 SECONDS + +/datum/ai_planning_subtree/shapechange_ambush/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + var/is_shifted = ismob(living_pawn.loc) + var/has_target = controller.blackboard_key_exists(target_key) + var/datum/action/cooldown/using_action = controller.blackboard[ability_key] + + if (!is_shifted) + if (has_target) + return // We're busy + + if (using_action?.IsAvailable()) + controller.queue_behavior(/datum/ai_behavior/use_mob_ability/shapeshift, BB_SHAPESHIFT_ACTION) // Shift + return SUBTREE_RETURN_FINISH_PLANNING + + if (!has_target || !using_action?.IsAvailable()) + return SUBTREE_RETURN_FINISH_PLANNING // Lie in wait + var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 + if (time_on_target < minimum_target_time) + return // Wait a bit longer + controller.queue_behavior(/datum/ai_behavior/use_mob_ability/shapeshift, BB_SHAPESHIFT_ACTION) // Surprise! + +/// Selects a random shapeshift ability before shifting +/datum/ai_behavior/use_mob_ability/shapeshift + +/datum/ai_behavior/use_mob_ability/shapeshift/setup(datum/ai_controller/controller, ability_key) + var/datum/action/cooldown/spell/shapeshift/using_action = controller.blackboard[ability_key] + if (!using_action?.IsAvailable()) + return FALSE + if (isnull(using_action.shapeshift_type)) // If we don't have a shape then pick one, AI can't use context wheels + using_action.shapeshift_type = pick(using_action.possible_shapes) + return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm index 42a361c25cd86..3fe1ada33ba99 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm @@ -3,7 +3,7 @@ /datum/ai_planning_subtree/simple_find_nearest_target_to_flee/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - if (!controller.blackboard[BB_BASIC_MOB_FLEEING]) + if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) return controller.queue_behavior(/datum/ai_behavior/find_potential_targets/nearest, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) @@ -13,7 +13,7 @@ /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - if (!controller.blackboard[BB_BASIC_MOB_FLEEING]) + if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) return controller.queue_behavior(/datum/ai_behavior/target_from_retaliate_list/nearest, BB_BASIC_MOB_RETALIATE_LIST, BB_BASIC_MOB_CURRENT_TARGET, targeting_key, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm index 52f4a3459bf91..7a3d5470b1a43 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm @@ -228,6 +228,6 @@ emote_see = speech_lines[BB_EMOTE_SEE] || list() emote_hear = speech_lines[BB_EMOTE_HEAR] || list() sound = speech_lines[BB_EMOTE_SOUND] || list() - speech_chance = speech_lines[BB_EMOTE_CHANCE] ? speech_lines[BB_EMOTE_CHANCE] : initial(speech_chance) + speech_chance = speech_lines[BB_SPEAK_CHANCE] ? speech_lines[BB_SPEAK_CHANCE] : initial(speech_chance) return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm b/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm index 6133759183ded..701f911d27325 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm @@ -41,23 +41,27 @@ if(!targetting_datum) CRASH("No target datum was supplied in the blackboard for [controller.pawn]") + var/list/shitlist = controller.blackboard[shitlist_key] + var/atom/existing_target = controller.blackboard[target_key] - var/list/enemies_list = list() + if (!check_faction) + controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, TRUE) - for(var/mob/living/potential_target as anything in controller.blackboard[shitlist_key]) - if(!targetting_datum.can_attack(living_mob, potential_target, vision_range, check_faction)) + if (!QDELETED(existing_target) && (locate(existing_target) in shitlist) && targetting_datum.can_attack(living_mob, existing_target, vision_range)) + finish_action(controller, succeeded = TRUE, check_faction = check_faction) + return + + var/list/enemies_list = list() + for(var/mob/living/potential_target as anything in shitlist) + if(!targetting_datum.can_attack(living_mob, potential_target, vision_range)) continue enemies_list += potential_target - if(!length(enemies_list)) + controller.clear_blackboard_key(target_key) finish_action(controller, succeeded = FALSE, check_faction = check_faction) return - if (controller.blackboard[target_key] in enemies_list) // Don't bother changing - finish_action(controller, succeeded = TRUE, check_faction = check_faction) - return - var/atom/new_target = pick_final_target(controller, enemies_list) controller.set_blackboard_key(target_key, new_target) @@ -72,9 +76,9 @@ /datum/ai_behavior/target_from_retaliate_list/proc/pick_final_target(datum/ai_controller/controller, list/enemies_list) return pick(enemies_list) - -/datum/ai_behavior/target_from_retaliate_list/finish_action(datum/ai_controller/controller, succeeded, target_key, check_faction) +/datum/ai_behavior/target_from_retaliate_list/finish_action(datum/ai_controller/controller, succeeded, check_faction) . = ..() - if(check_faction) + if (succeeded || check_faction) return - controller.set_blackboard_key(BB_BASIC_MOB_SKIP_FACTION_CHECK, succeeded) + var/usually_ignores_faction = controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || FALSE + controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, usually_ignores_faction) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm new file mode 100644 index 0000000000000..dadba992e9f10 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm @@ -0,0 +1,56 @@ +///behavior to activate ability to escape from target +/datum/ai_planning_subtree/teleport_away_from_target + ///minimum distance away from the target before we execute behavior + var/minimum_distance = 2 + ///the ability we will execute + var/ability_key + +/datum/ai_planning_subtree/teleport_away_from_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + return + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + var/distance_from_target = get_dist(target, controller.pawn) + if(distance_from_target >= minimum_distance) + controller.clear_blackboard_key(BB_ESCAPE_DESTINATION) + return + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + if(!ability?.IsAvailable()) + return + var/turf/location_turf = controller.blackboard[BB_ESCAPE_DESTINATION] + + if(isnull(location_turf)) + controller.queue_behavior(/datum/ai_behavior/find_furthest_turf_from_target, BB_BASIC_MOB_CURRENT_TARGET, BB_ESCAPE_DESTINATION, minimum_distance) + return SUBTREE_RETURN_FINISH_PLANNING + + if(get_dist(location_turf, target) < minimum_distance || !can_see(controller.pawn, location_turf)) //target moved close too close or we moved too far since finding the target turf + controller.clear_blackboard_key(BB_ESCAPE_DESTINATION) + return + + controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target, ability_key, BB_ESCAPE_DESTINATION) + +///find furtherst turf target so we may teleport to it +/datum/ai_behavior/find_furthest_turf_from_target + +/datum/ai_behavior/find_furthest_turf_from_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, set_key, range) + var/mob/living/living_target = controller.blackboard[target_key] + if(QDELETED(living_target)) + return + + var/distance = 0 + var/turf/chosen_turf + for(var/turf/open/potential_destination in oview(range, living_target)) + if(potential_destination.is_blocked_turf()) + continue + var/new_distance_to_target = get_dist(potential_destination, living_target) + if(new_distance_to_target > distance) + chosen_turf = potential_destination + distance = new_distance_to_target + if(distance == range) + break //we have already found the max distance + + if(isnull(chosen_turf)) + finish_action(controller, FALSE) + return + + controller.set_blackboard_key(set_key, chosen_turf) + finish_action(controller, TRUE) diff --git a/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm b/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm index 570088ce4d6e2..bc4a554bc024b 100644 --- a/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm +++ b/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm @@ -20,13 +20,13 @@ ///Whether we care for seeing the target or not var/ignore_sight = FALSE -/datum/targetting_datum/basic/can_attack(mob/living/living_mob, atom/the_target, vision_range, check_faction = TRUE) +/datum/targetting_datum/basic/can_attack(mob/living/living_mob, atom/the_target, vision_range) var/datum/ai_controller/basic_controller/our_controller = living_mob.ai_controller if(isnull(our_controller)) return FALSE - if(isturf(the_target) || !the_target) // bail out on invalids + if(isturf(the_target) || isnull(the_target)) // bail out on invalids return FALSE if(isobj(the_target.loc)) @@ -35,6 +35,8 @@ return FALSE if(ismob(the_target)) //Target is in godmode, ignore it. + if(living_mob.loc == the_target) + return FALSE // We've either been eaten or are shapeshifted, let's assume the latter because we're still alive var/mob/M = the_target if(M.status_flags & GODMODE) return FALSE @@ -45,13 +47,12 @@ if(living_mob.see_invisible < the_target.invisibility) //Target's invisible to us, forget it return FALSE - if(isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech + if(isturf(living_mob.loc) && isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech or pawn is shapeshifted or jaunting return FALSE if(isliving(the_target)) //Targeting vs living mobs var/mob/living/living_target = the_target - var/bypass_faction_check = !check_faction || our_controller.blackboard[BB_BASIC_MOB_SKIP_FACTION_CHECK] - if(faction_check(living_mob, living_target) && !bypass_faction_check) + if(faction_check(our_controller, living_mob, living_target)) return FALSE if(living_target.stat > stat_attack) return FALSE @@ -77,26 +78,21 @@ return FALSE /// Returns true if the mob and target share factions -/datum/targetting_datum/basic/proc/faction_check(mob/living/living_mob, mob/living/the_target) +/datum/targetting_datum/basic/proc/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) + if (controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || controller.blackboard[BB_TEMPORARILY_IGNORE_FACTION]) + return FALSE return living_mob.faction_check_mob(the_target, exact_match = check_factions_exactly) /// Subtype more forgiving for items. /// Careful, this can go wrong and keep a mob hyper-focused on an item it can't lose aggro on /datum/targetting_datum/basic/allow_items -/datum/targetting_datum/basic/allow_items/can_attack(mob/living/living_mob, atom/the_target) +/datum/targetting_datum/basic/allow_items/can_attack(mob/living/living_mob, atom/the_target, vision_range) . = ..() if(isitem(the_target)) // trust fall exercise return TRUE -/// Subtype which doesn't care about faction -/// Mobs which retaliate but don't otherwise target seek should just attack anything which annoys them -/datum/targetting_datum/basic/ignore_faction - -/datum/targetting_datum/basic/ignore_faction/faction_check(mob/living/living_mob, mob/living/the_target) - return FALSE - /// Subtype which searches for mobs of a size relative to ours /datum/targetting_datum/basic/of_size /// If true, we will return mobs which are smaller than us. If false, larger. @@ -104,7 +100,7 @@ /// If true, we will return mobs which are the same size as us. var/inclusive = TRUE -/datum/targetting_datum/basic/of_size/can_attack(mob/living/owner, atom/target) +/datum/targetting_datum/basic/of_size/can_attack(mob/living/owner, atom/target, vision_range) if(!isliving(target)) return FALSE . = ..() diff --git a/code/datums/ai/basic_mobs/targetting_datums/dont_target_friends.dm b/code/datums/ai/basic_mobs/targetting_datums/dont_target_friends.dm index 3556e11900f70..f1bd411fd298e 100644 --- a/code/datums/ai/basic_mobs/targetting_datums/dont_target_friends.dm +++ b/code/datums/ai/basic_mobs/targetting_datums/dont_target_friends.dm @@ -6,7 +6,7 @@ var/attack_closed_turf = FALSE ///Returns true or false depending on if the target can be attacked by the mob -/datum/targetting_datum/not_friends/can_attack(mob/living/living_mob, atom/target) +/datum/targetting_datum/not_friends/can_attack(mob/living/living_mob, atom/target, vision_range) if (!target) return FALSE if (attack_closed_turf) @@ -43,7 +43,7 @@ /// Subtype that allows us to target items while deftly avoiding attacking our allies. Be careful when it comes to targetting items as an AI could get trapped targetting something it can't destroy. /datum/targetting_datum/basic/not_friends/allow_items -/datum/targetting_datum/basic/not_friends/allow_items/can_attack(mob/living/living_mob, atom/the_target) +/datum/targetting_datum/basic/not_friends/allow_items/can_attack(mob/living/living_mob, atom/the_target, vision_range) . = ..() if(isitem(the_target)) // trust fall exercise diff --git a/code/datums/ai/basic_mobs/targetting_datums/with_object.dm b/code/datums/ai/basic_mobs/targetting_datums/with_object.dm new file mode 100644 index 0000000000000..91e99bb4a221c --- /dev/null +++ b/code/datums/ai/basic_mobs/targetting_datums/with_object.dm @@ -0,0 +1,39 @@ +/** + * Find mobs who are holding the configurable object type + * + * This is an extension of basic targeting behaviour, that allows you to + * only target the mob if they have a specific item in their hand. + * + */ +/datum/targetting_datum/basic/holding_object + // We will find mobs who are holding this object in their hands + var/object_type_path = null + +/** + * Create an instance of the holding object targeting datum + * + * * object_type_path Pass an object type path, this will be compared to the items + * in targets hands to filter the target list. + */ +/datum/targetting_datum/basic/holding_object/New(object_type_path) + if (!ispath(object_type_path)) + stack_trace("trying to create an item targeting datum with no valid typepath") + // Leaving object type as null will make this basically a noop + return + src.object_type_path = object_type_path + +///Returns true or false depending on if the target can be attacked by the mob +/datum/targetting_datum/basic/holding_object/can_attack(mob/living/living_mob, atom/target, vision_range) + if (object_type_path == null) + return FALSE // no op + if(!ismob(target)) + return FALSE // no hands no problems + + // Look at me, type casting like a grown up + var/mob/targetmob = target + // Check if our parent behaviour agrees we can attack this target (we ignore faction by default) + var/can_attack = ..() + if(can_attack && targetmob.is_holding_item_of_type(object_type_path)) + return TRUE // they have the item + // No valid target + return FALSE diff --git a/code/datums/ai/dog/dog_controller.dm b/code/datums/ai/dog/dog_controller.dm index 5a42cb43a1eb2..6883642b68918 100644 --- a/code/datums/ai/dog/dog_controller.dm +++ b/code/datums/ai/dog/dog_controller.dm @@ -20,6 +20,8 @@ BB_DOG_HARASS_HARM = TRUE, BB_VISION_RANGE = AI_DOG_VISION_RANGE, BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(), + // Find nearby mobs with tongs in hand. + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/holding_object(/obj/item/kitchen/tongs), BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/pet/dog), BB_BABIES_CHILD_TYPES = list(/mob/living/basic/pet/dog/corgi/puppy = 95, /mob/living/basic/pet/dog/corgi/puppy/void = 5), ) @@ -29,6 +31,10 @@ /datum/ai_planning_subtree/make_babies, // Ian WILL prioritise sex over following your instructions /datum/ai_planning_subtree/pet_planning, /datum/ai_planning_subtree/dog_harassment, + // Find targets to run away from (uses the targetting datum from above) + /datum/ai_planning_subtree/simple_find_target, + // Flee from that target + /datum/ai_planning_subtree/flee_target, ) /datum/ai_controller/basic_controller/dog/corgi/get_access() diff --git a/code/datums/ai/generic/find_and_set.dm b/code/datums/ai/generic/find_and_set.dm index eb09506a282fa..dc941ec33fae1 100644 --- a/code/datums/ai/generic/find_and_set.dm +++ b/code/datums/ai/generic/find_and_set.dm @@ -9,6 +9,7 @@ /datum/ai_behavior/find_and_set/perform(seconds_per_tick, datum/ai_controller/controller, set_key, locate_path, search_range) . = ..() if (controller.blackboard_key_exists(set_key)) + finish_action(controller, TRUE) return var/find_this_thing = search_tactic(controller, locate_path, search_range) if(find_this_thing) diff --git a/code/datums/ai/hunting_behavior/hunting_behaviors.dm b/code/datums/ai/hunting_behavior/hunting_behaviors.dm index ba7c7f2ffba67..468cfed33fb98 100644 --- a/code/datums/ai/hunting_behavior/hunting_behaviors.dm +++ b/code/datums/ai/hunting_behavior/hunting_behaviors.dm @@ -94,8 +94,6 @@ var/atom/hunted = controller.blackboard[hunting_target_key] if(QDELETED(hunted)) - //Target is gone for some reason. forget about this task! - controller[hunting_target_key] = null finish_action(controller, FALSE, hunting_target_key) else target_caught(hunter, hunted) @@ -136,7 +134,7 @@ /datum/ai_behavior/hunt_target/use_ability_on_target/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) var/datum/action/cooldown/ability = controller.blackboard[ability_key] - if(QDELETED(ability) || !ability.IsAvailable()) + if(!ability?.IsAvailable()) finish_action(controller, FALSE, hunting_target_key) return ..() diff --git a/code/datums/ai/idle_behaviors/idle_random_walk.dm b/code/datums/ai/idle_behaviors/idle_random_walk.dm index d5a3972a3d027..d99957f419bb1 100644 --- a/code/datums/ai/idle_behaviors/idle_random_walk.dm +++ b/code/datums/ai/idle_behaviors/idle_random_walk.dm @@ -24,3 +24,44 @@ if (!controller.blackboard_key_exists(target_key)) return return ..() + +/// walk randomly however stick near a target +/datum/idle_behavior/walk_near_target + /// chance to walk + var/walk_chance = 25 + /// distance we are to target + var/minimum_distance = 20 + /// key that holds target + var/target_key + +/datum/idle_behavior/walk_near_target/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) + . = ..() + var/mob/living/living_pawn = controller.pawn + if(LAZYLEN(living_pawn.do_afters)) + return + + if(!SPT_PROB(walk_chance, seconds_per_tick) || !(living_pawn.mobility_flags & MOBILITY_MOVE) || !isturf(living_pawn.loc) || living_pawn.pulledby) + return + + var/atom/target = controller.blackboard[target_key] + var/distance = get_dist(target, living_pawn) + if(isnull(target) || distance > minimum_distance) //if we are too far away from target, just walk randomly + var/move_dir = pick(GLOB.alldirs) + living_pawn.Move(get_step(living_pawn, move_dir), move_dir) + return + + var/list/possible_turfs = list() + for(var/direction in GLOB.alldirs) + var/turf/possible_step = get_step(living_pawn, direction) + if(get_dist(possible_step, target) > minimum_distance) + continue + if(possible_step.is_blocked_turf()) + continue + possible_turfs += possible_step + + if(!length(possible_turfs)) + return + + var/turf/picked_turf = pick(possible_turfs) + + living_pawn.Move(picked_turf, get_dir(living_pawn, picked_turf)) diff --git a/code/datums/alarm.dm b/code/datums/alarm.dm index b266362f3ca95..e25f96b95a5b0 100644 --- a/code/datums/alarm.dm +++ b/code/datums/alarm.dm @@ -201,4 +201,5 @@ for(var/area_name as anything in alarms_of_type) var/list/alarm_packet = alarms_of_type[area_name] var/list/cameras = alarm_packet[2] - cameras -= source // REF FOUND AND CLEARED BOYSSSS + if(cameras) + cameras -= source // REF FOUND AND CLEARED BOYSSSS diff --git a/code/datums/components/bakeable.dm b/code/datums/components/bakeable.dm index 1b56a0f9bd4a0..b4cde3c5752e9 100644 --- a/code/datums/components/bakeable.dm +++ b/code/datums/components/bakeable.dm @@ -14,7 +14,10 @@ /// REF() to the mind which placed us in an oven var/who_baked_us -/datum/component/bakeable/Initialize(bake_result, required_bake_time, positive_result, use_large_steam_sprite) + /// Reagents that should be added to the result + var/list/added_reagents + +/datum/component/bakeable/Initialize(bake_result, required_bake_time, positive_result, use_large_steam_sprit, list/added_reagents) . = ..() if(!isitem(parent)) //Only items support baking at the moment return COMPONENT_INCOMPATIBLE @@ -22,6 +25,7 @@ src.bake_result = bake_result src.required_bake_time = required_bake_time src.positive_result = positive_result + src.added_reagents = added_reagents // Inherit the new values passed to the component /datum/component/bakeable/InheritComponent(datum/component/bakeable/new_comp, original, bake_result, required_bake_time, positive_result, use_large_steam_sprite) @@ -70,6 +74,8 @@ if(baked_result.reagents && positive_result) //make space and tranfer reagents if it has any & the resulting item isn't bad food or other bad baking result baked_result.reagents.clear_reagents() original_object.reagents.trans_to(baked_result, original_object.reagents.total_volume) + if(added_reagents) // Add any new reagents that should be added + baked_result.reagents.add_reagent_list(added_reagents) if(who_baked_us) ADD_TRAIT(baked_result, TRAIT_FOOD_CHEF_MADE, who_baked_us) diff --git a/code/datums/components/basic_inhands.dm b/code/datums/components/basic_inhands.dm new file mode 100644 index 0000000000000..ac50f618861f2 --- /dev/null +++ b/code/datums/components/basic_inhands.dm @@ -0,0 +1,50 @@ +/** + * Basic handling for showing held items in a mob's hands + */ +/datum/component/basic_inhands + /// Layer index we show our inhands upon + var/display_layer + /// Y offset to apply to inhands + var/y_offset + /// X offset to apply to inhands, is inverted for the left hand + var/x_offset + /// What overlays are we currently showing? + var/list/cached_overlays + +/datum/component/basic_inhands/Initialize(display_layer = 1, y_offset = 0, x_offset = 0) + . = ..() + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE + src.display_layer = display_layer + src.y_offset = y_offset + src.x_offset = x_offset + cached_overlays = list() + +/datum/component/basic_inhands/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_updated_overlays)) + RegisterSignal(parent, COMSIG_MOB_UPDATE_HELD_ITEMS, PROC_REF(on_updated_held_items)) + +/datum/component/basic_inhands/UnregisterFromParent() + . = ..() + UnregisterSignal(parent, list(COMSIG_ATOM_UPDATE_OVERLAYS, COMSIG_MOB_UPDATE_HELD_ITEMS)) + +/// When your overlays update, add your held overlays +/datum/component/basic_inhands/proc/on_updated_overlays(atom/parent_atom, list/overlays) + SIGNAL_HANDLER + overlays += cached_overlays + +/// When your number of held items changes, regenerate held icons +/datum/component/basic_inhands/proc/on_updated_held_items(mob/living/holding_mob) + SIGNAL_HANDLER + var/list/held_overlays = list() + for(var/obj/item/held in holding_mob.held_items) + var/is_right = holding_mob.get_held_index_of_item(held) % 2 == 0 + var/icon_file = is_right ? held.righthand_file : held.lefthand_file + var/mutable_appearance/held_overlay = held.build_worn_icon(default_layer = HANDS_LAYER, default_icon_file = icon_file, isinhands = TRUE) + held_overlay.pixel_y += y_offset + held_overlay.pixel_x += x_offset * (is_right ? 1 : -1) + held_overlays += held_overlay + + cached_overlays = held_overlays + holding_mob.update_appearance(UPDATE_OVERLAYS) diff --git a/code/datums/components/blob_minion.dm b/code/datums/components/blob_minion.dm index 41f58231e2d1a..8261a7ad1fdc3 100644 --- a/code/datums/components/blob_minion.dm +++ b/code/datums/components/blob_minion.dm @@ -141,7 +141,7 @@ SIGNAL_HANDLER var/spanned_message = minion.say_quote(message) var/rendered = span_blob("\[Blob Telepathy\] [minion.real_name] [spanned_message]") - blob_telepathy(rendered, minion) + relay_to_list_and_observers(rendered, GLOB.blob_telepathy_mobs, minion) return COMPONENT_CANNOT_SPEAK /// Called when a blob minion is transformed into something else, hopefully a spore into a zombie diff --git a/code/datums/components/cult_ritual_item.dm b/code/datums/components/cult_ritual_item.dm index 13d1ab8d921c5..7565268cef6df 100644 --- a/code/datums/components/cult_ritual_item.dm +++ b/code/datums/components/cult_ritual_item.dm @@ -357,7 +357,7 @@ if(!check_if_in_ritual_site(cultist, cult_team)) return FALSE var/area/summon_location = get_area(cultist) - priority_announce("Figments from an eldritch god are being summoned by [cultist.real_name] into [summon_location.get_original_area_name()] from an unknown dimension. Disrupt the ritual at all costs!", "Central Command Higher Dimensional Affairs", ANNOUNCER_SPANOMALIES, has_important_message = TRUE) + priority_announce("Figments from an eldritch god are being summoned by [cultist.real_name] into [summon_location.get_original_area_name()] from an unknown dimension. Disrupt the ritual at all costs!", "Central Command Higher Dimensional Affairs", sound = 'sound/ambience/antag/bloodcult/bloodcult_scribe.ogg', has_important_message = TRUE) for(var/shielded_turf in spiral_range_turfs(1, cultist, 1)) LAZYADD(shields, new /obj/structure/emergency_shield/cult/narsie(shielded_turf)) diff --git a/code/datums/components/fullauto.dm b/code/datums/components/fullauto.dm index d02f090ae202f..8663de5adc691 100644 --- a/code/datums/components/fullauto.dm +++ b/code/datums/components/fullauto.dm @@ -8,8 +8,12 @@ var/turf/target_loc //For dealing with locking on targets due to BYOND engine limitations (the mouse input only happening when mouse moves). var/autofire_stat = AUTOFIRE_STAT_IDLE var/mouse_parameters - var/autofire_shot_delay = 0.3 SECONDS //Time between individual shots. - var/mouse_status = AUTOFIRE_MOUSEUP //This seems hacky but there can be two MouseDown() without a MouseUp() in between if the user holds click and uses alt+tab, printscreen or similar. + /// Time between individual shots. + var/autofire_shot_delay = 0.3 SECONDS + /// This seems hacky but there can be two MouseDown() without a MouseUp() in between if the user holds click and uses alt+tab, printscreen or similar. + var/mouse_status = AUTOFIRE_MOUSEUP + /// Should dual wielding be allowed? + var/allow_akimbo ///windup autofire vars ///Whether the delay between shots increases over time, simulating a spooling weapon @@ -26,7 +30,7 @@ var/timerid COOLDOWN_DECLARE(next_shot_cd) -/datum/component/automatic_fire/Initialize(autofire_shot_delay, windup_autofire, windup_autofire_reduction_multiplier, windup_autofire_cap, windup_spindown) +/datum/component/automatic_fire/Initialize(autofire_shot_delay, windup_autofire, windup_autofire_reduction_multiplier, windup_autofire_cap, windup_spindown, allow_akimbo = TRUE) . = ..() if(!isgun(parent)) return COMPONENT_INCOMPATIBLE @@ -34,6 +38,7 @@ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(wake_up)) if(autofire_shot_delay) src.autofire_shot_delay = autofire_shot_delay + src.allow_akimbo = allow_akimbo if(windup_autofire) src.windup_autofire = windup_autofire src.windup_autofire_reduction_multiplier = windup_autofire_reduction_multiplier @@ -256,7 +261,7 @@ if(HAS_TRAIT(shooter, TRAIT_DOUBLE_TAP)) next_delay = round(next_delay * 0.5) COOLDOWN_START(src, next_shot_cd, next_delay) - if(SEND_SIGNAL(parent, COMSIG_AUTOFIRE_SHOT, target, shooter, mouse_parameters) & COMPONENT_AUTOFIRE_SHOT_SUCCESS) + if(SEND_SIGNAL(parent, COMSIG_AUTOFIRE_SHOT, target, shooter, allow_akimbo, mouse_parameters) & COMPONENT_AUTOFIRE_SHOT_SUCCESS) return TRUE stop_autofiring() return FALSE @@ -288,21 +293,21 @@ return COMPONENT_AUTOFIRE_ONMOUSEDOWN_BYPASS -/obj/item/gun/proc/do_autofire(datum/source, atom/target, mob/living/shooter, params) +/obj/item/gun/proc/do_autofire(datum/source, atom/target, mob/living/shooter, allow_akimbo, params) SIGNAL_HANDLER if(semicd || shooter.incapacitated()) return NONE if(!can_shoot()) shoot_with_empty_chamber(shooter) return NONE - INVOKE_ASYNC(src, PROC_REF(do_autofire_shot), source, target, shooter, params) + INVOKE_ASYNC(src, PROC_REF(do_autofire_shot), source, target, shooter, allow_akimbo, params) return COMPONENT_AUTOFIRE_SHOT_SUCCESS //All is well, we can continue shooting. -/obj/item/gun/proc/do_autofire_shot(datum/source, atom/target, mob/living/shooter, params) +/obj/item/gun/proc/do_autofire_shot(datum/source, atom/target, mob/living/shooter, allow_akimbo, params) var/obj/item/gun/akimbo_gun = shooter.get_inactive_held_item() var/bonus_spread = 0 - if(istype(akimbo_gun) && weapon_weight < WEAPON_MEDIUM) + if(istype(akimbo_gun) && weapon_weight < WEAPON_MEDIUM && allow_akimbo) if(akimbo_gun.weapon_weight < WEAPON_MEDIUM && akimbo_gun.can_trigger_gun(shooter)) bonus_spread = dual_wield_spread addtimer(CALLBACK(akimbo_gun, TYPE_PROC_REF(/obj/item/gun, process_fire), target, shooter, TRUE, params, null, bonus_spread), 1) diff --git a/code/datums/components/grillable.dm b/code/datums/components/grillable.dm index 8458fa25e62d5..f584808a1f3bd 100644 --- a/code/datums/components/grillable.dm +++ b/code/datums/components/grillable.dm @@ -12,8 +12,10 @@ var/use_large_steam_sprite = FALSE /// REF() to the mind which placed us on the griddle var/who_placed_us + /// Reagents that should be added to the result + var/list/added_reagents -/datum/component/grillable/Initialize(cook_result, required_cook_time, positive_result, use_large_steam_sprite) +/datum/component/grillable/Initialize(cook_result, required_cook_time, positive_result, use_large_steam_sprite, list/added_reagents) . = ..() if(!isitem(parent)) //Only items support grilling at the moment return COMPONENT_INCOMPATIBLE @@ -22,6 +24,7 @@ src.required_cook_time = required_cook_time src.positive_result = positive_result src.use_large_steam_sprite = use_large_steam_sprite + src.added_reagents = added_reagents /datum/component/grillable/RegisterWithParent() RegisterSignal(parent, COMSIG_ITEM_GRILL_PLACED, PROC_REF(on_grill_placed)) @@ -103,6 +106,8 @@ BLACKBOX_LOG_FOOD_MADE(grilled_result.type) grilled_result.reagents.clear_reagents() original_object.reagents?.trans_to(grilled_result, original_object.reagents.total_volume) + if(added_reagents) // Add any new reagents that should be added + grilled_result.reagents.add_reagent_list(added_reagents) SEND_SIGNAL(parent, COMSIG_ITEM_GRILLED, grilled_result) if(who_placed_us) diff --git a/code/datums/components/healing_touch.dm b/code/datums/components/healing_touch.dm index 4b953fc628946..029b0f660ef33 100644 --- a/code/datums/components/healing_touch.dm +++ b/code/datums/components/healing_touch.dm @@ -32,6 +32,10 @@ var/action_text /// Text to print when action completes, replaces %SOURCE% with healer and %TARGET% with healed mob var/complete_text + /// Whether to print the target's remaining health after healing (for non-carbon targets only) + var/show_health + /// Color for the healing effect + var/heal_color /datum/component/healing_touch/Initialize( heal_brute = 20, @@ -46,6 +50,8 @@ self_targetting = HEALING_TOUCH_NOT_SELF, action_text = "%SOURCE% begins healing %TARGET%", complete_text = "%SOURCE% finishes healing %TARGET%", + show_health = FALSE, + heal_color = COLOR_HEALING_CYAN, ) if (!isliving(parent)) return COMPONENT_INCOMPATIBLE @@ -62,6 +68,8 @@ src.self_targetting = self_targetting src.action_text = action_text src.complete_text = complete_text + src.show_health = show_health + src.heal_color = heal_color RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(try_healing)) // Players RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(try_healing)) // NPCs @@ -147,7 +155,11 @@ healer.visible_message(span_notice("[format_string(complete_text, healer, target)]")) target.heal_overall_damage(brute = heal_brute, burn = heal_burn, stamina = heal_stamina, required_bodytype = required_bodytype) - new /obj/effect/temp_visual/heal(get_turf(target), COLOR_HEALING_CYAN) + new /obj/effect/temp_visual/heal(get_turf(target), heal_color) + + if(show_health && !iscarbon(target)) + var/formatted_string = format_string("%TARGET% now has [target.health]/[target.maxHealth] health.", healer, target) + healer.visible_message(span_danger(formatted_string)) /// Reformats the passed string with the replacetext keys /datum/component/healing_touch/proc/format_string(string, atom/source, atom/target) diff --git a/code/datums/components/joint_damage.dm b/code/datums/components/joint_damage.dm new file mode 100644 index 0000000000000..5397bd307cab9 --- /dev/null +++ b/code/datums/components/joint_damage.dm @@ -0,0 +1,35 @@ +/* + * A component given to mobs to damage a linked mob + */ +/datum/component/joint_damage + ///the mob we will damage + var/datum/weakref/overlord_mob + ///our last health count + var/previous_health_count + +/datum/component/joint_damage/Initialize(mob/overlord_mob) + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + var/mob/living/parent_mob = parent + previous_health_count = parent_mob.health + if(overlord_mob) + src.overlord_mob = WEAKREF(overlord_mob) + +/datum/component/joint_damage/RegisterWithParent() + RegisterSignal(parent, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(damage_overlord)) + RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(damage_overlord)) + +/datum/component/joint_damage/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_LIVING_HEALTH_UPDATE, COMSIG_LIVING_DEATH)) + +/datum/component/joint_damage/Destroy() + overlord_mob = null + return ..() + +/datum/component/joint_damage/proc/damage_overlord(mob/living/source) + SIGNAL_HANDLER + + var/mob/living/overlord_to_damage = overlord_mob?.resolve() + if(!isnull(overlord_to_damage)) + overlord_to_damage.adjustBruteLoss(previous_health_count - source.health) ///damage or heal overlord + previous_health_count = source.health diff --git a/code/datums/components/mob_chain.dm b/code/datums/components/mob_chain.dm index a2b16a849a576..8312d9d550476 100644 --- a/code/datums/components/mob_chain.dm +++ b/code/datums/components/mob_chain.dm @@ -25,6 +25,8 @@ if (!isnull(front)) SEND_SIGNAL(front, COMSIG_MOB_GAINED_CHAIN_TAIL, parent) parent.AddComponent(/datum/component/leash, owner = front, distance = 1) // Handles catching up gracefully + var/mob/living/living_parent = parent + living_parent.set_glide_size(front.glide_size) /datum/component/mob_chain/Destroy(force, silent) if (!isnull(front)) @@ -42,11 +44,13 @@ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) RegisterSignal(parent, COMSIG_ATOM_CAN_BE_PULLED, PROC_REF(on_pulled)) RegisterSignals(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, COMSIG_MOB_ATTACK_RANGED), PROC_REF(on_attack)) + RegisterSignal(parent, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(on_glide_size_changed)) if (vary_icon_state) RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON_STATE, PROC_REF(on_update_icon_state)) update_mob_appearance() if (pass_damage_back) - RegisterSignal(parent, COMSIG_LIVING_ADJUST_DAMAGE, PROC_REF(on_adjust_damage)) + RegisterSignals(parent, COMSIG_LIVING_ADJUST_STANDARD_DAMAGE_TYPES, PROC_REF(on_adjust_damage)) + RegisterSignal(parent, COMSIG_LIVING_ADJUST_STAMINA_DAMAGE, PROC_REF(on_adjust_stamina)) RegisterSignal(parent, COMSIG_CARBON_LIMB_DAMAGED, PROC_REF(on_limb_damage)) var/datum/action/cooldown/worm_contract/shrink = new(parent) @@ -58,14 +62,20 @@ COMSIG_ATOM_UPDATE_ICON_STATE, COMSIG_CARBON_LIMB_DAMAGED, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, - COMSIG_LIVING_ADJUST_DAMAGE, + COMSIG_LIVING_ADJUST_BRUTE_DAMAGE, + COMSIG_LIVING_ADJUST_BURN_DAMAGE, + COMSIG_LIVING_ADJUST_CLONE_DAMAGE, COMSIG_LIVING_DEATH, + COMSIG_LIVING_ADJUST_OXY_DAMAGE, + COMSIG_LIVING_ADJUST_STAMINA_DAMAGE, + COMSIG_LIVING_ADJUST_TOX_DAMAGE, COMSIG_LIVING_UNARMED_ATTACK, COMSIG_MOB_ATTACK_RANGED, COMSIG_MOB_CHAIN_CONTRACT, COMSIG_MOB_GAINED_CHAIN_TAIL, COMSIG_MOB_LOST_CHAIN_TAIL, COMSIG_MOVABLE_MOVED, + COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, COMSIG_QDELETING, )) qdel(parent.GetComponent(/datum/component/leash)) @@ -153,14 +163,23 @@ return INVOKE_ASYNC(back, TYPE_PROC_REF(/mob, ClickOn), target) +/// Maintain glide size backwards +/datum/component/mob_chain/proc/on_glide_size_changed(mob/living/our_mob, new_size) + SIGNAL_HANDLER + back?.set_glide_size(new_size) + +/// On gain or lose stamina, adjust our tail too +/datum/component/mob_chain/proc/on_adjust_stamina(mob/living/our_mob, type, amount, forced) + SIGNAL_HANDLER + if (forced) + return + back?.adjustStaminaLoss(amount, forced = forced) + /// On damage or heal, affect our furthest segment /datum/component/mob_chain/proc/on_adjust_damage(mob/living/our_mob, type, amount, forced) SIGNAL_HANDLER if (isnull(back) || forced) return - if (type == STAMINA) - back.adjustStaminaLoss(amount, forced = forced) - return // Pass stamina changes all the way along so we maintain consistent speed switch (type) if(BRUTE) back.adjustBruteLoss(amount, forced = forced) diff --git a/code/datums/components/pet_commands/fetch.dm b/code/datums/components/pet_commands/fetch.dm index a8723f8bd4bd1..ae33983e1d00d 100644 --- a/code/datums/components/pet_commands/fetch.dm +++ b/code/datums/components/pet_commands/fetch.dm @@ -18,6 +18,8 @@ /datum/pet_command/point_targetting/fetch/New(mob/living/parent) . = ..() + if(isnull(parent)) + return parent.AddElement(/datum/element/ai_held_item) // We don't remove this on destroy because they might still be holding something /datum/pet_command/point_targetting/fetch/add_new_friend(mob/living/tamer) diff --git a/code/datums/components/plumbing/_plumbing.dm b/code/datums/components/plumbing/_plumbing.dm index b140b003e6c9a..af27b4bbb4611 100644 --- a/code/datums/components/plumbing/_plumbing.dm +++ b/code/datums/components/plumbing/_plumbing.dm @@ -73,8 +73,8 @@ /datum/component/plumbing/process() if(!demand_connects || !reagents) - STOP_PROCESSING(SSplumbing, src) - return + return PROCESS_KILL + if(reagents.total_volume < reagents.maximum_volume) for(var/D in GLOB.cardinals) if(D & demand_connects) @@ -93,23 +93,29 @@ ///called from in process(). only calls process_request(), but can be overwritten for children with special behaviour /datum/component/plumbing/proc/send_request(dir) - process_request(amount = MACHINE_REAGENT_TRANSFER, reagent = null, dir = dir) + process_request(dir = dir) ///check who can give us what we want, and how many each of them will give us -/datum/component/plumbing/proc/process_request(amount, reagent, dir) - var/list/valid_suppliers = list() +/datum/component/plumbing/proc/process_request(amount = MACHINE_REAGENT_TRANSFER, reagent, dir) + //find the duct to take from var/datum/ductnet/net if(!ducts.Find(num2text(dir))) return net = ducts[num2text(dir)] + + //find all valid suppliers in the duct + var/list/valid_suppliers = list() for(var/datum/component/plumbing/supplier as anything in net.suppliers) if(supplier.can_give(amount, reagent, net)) valid_suppliers += supplier - // Need to ask for each in turn very carefully, making sure we get the total volume. This is to avoid a division that would always round down and become 0 - var/targetVolume = reagents.total_volume + amount var/suppliersLeft = valid_suppliers.len + if(!suppliersLeft) + return + + //take an equal amount from each supplier + var/currentRequest for(var/datum/component/plumbing/give as anything in valid_suppliers) - var/currentRequest = (targetVolume - reagents.total_volume) / suppliersLeft + currentRequest = amount / suppliersLeft give.transfer_to(src, currentRequest, reagent, net) suppliersLeft-- @@ -122,7 +128,7 @@ for(var/datum/reagent/contained_reagent as anything in reagents.reagent_list) if(contained_reagent.type == reagent) return TRUE - else if(reagents.total_volume > 0) //take whatever + else if(reagents.total_volume) //take whatever return TRUE ///this is where the reagent is actually transferred and is thus the finish point of our process() @@ -132,7 +138,7 @@ if(reagent) reagents.trans_id_to(target.recipient_reagents_holder, reagent, amount) else - reagents.trans_to(target.recipient_reagents_holder, amount, round_robin = TRUE, methods = methods)//we deal with alot of precise calculations so we round_robin=TRUE. Otherwise we get floating point errors, 1 != 1 and 2.5 + 2.5 = 6 + reagents.trans_to(target.recipient_reagents_holder, amount, methods = methods) ///We create our luxurious piping overlays/underlays, to indicate where we do what. only called once if use_overlays = TRUE in Initialize() /datum/component/plumbing/proc/create_overlays(atom/movable/parent_movable, list/overlays) diff --git a/code/datums/components/plumbing/reaction_chamber.dm b/code/datums/components/plumbing/reaction_chamber.dm index c750fda714255..f728ce315e6ee 100644 --- a/code/datums/components/plumbing/reaction_chamber.dm +++ b/code/datums/components/plumbing/reaction_chamber.dm @@ -18,16 +18,20 @@ if(chamber.emptying) return + var/present_amount + var/diff for(var/required_reagent in chamber.required_reagents) - var/has_reagent = FALSE + //find how much amount is already present if at all + present_amount = 0 for(var/datum/reagent/containg_reagent as anything in reagents.reagent_list) if(required_reagent == containg_reagent.type) - has_reagent = TRUE - if(containg_reagent.volume + CHEMICAL_QUANTISATION_LEVEL < chamber.required_reagents[required_reagent]) - process_request(min(chamber.required_reagents[required_reagent] - containg_reagent.volume, MACHINE_REAGENT_TRANSFER) , required_reagent, dir) - return - if(!has_reagent) - process_request(min(chamber.required_reagents[required_reagent], MACHINE_REAGENT_TRANSFER), required_reagent, dir) + present_amount = containg_reagent.volume + break + + //compute how much more is needed and round it + diff = chamber.required_reagents[required_reagent] - present_amount + if(diff > CHEMICAL_QUANTISATION_LEVEL) + process_request(min(diff, MACHINE_REAGENT_TRANSFER), required_reagent, dir) return reagents.flags &= ~NO_REACT @@ -45,16 +49,15 @@ ducting_layer = SECOND_DUCT_LAYER /datum/component/plumbing/acidic_input/send_request(dir) - process_request(amount = MACHINE_REAGENT_TRANSFER, reagent = /datum/reagent/reaction_agent/acidic_buffer, dir = dir) + process_request(reagent = /datum/reagent/reaction_agent/acidic_buffer, dir = dir) ///Special connect that we currently use for reaction chambers. Being used so we can keep certain inputs separate, like into a special internal base container /datum/component/plumbing/alkaline_input demand_connects = EAST demand_color = COLOR_VIBRANT_LIME - ducting_layer = FOURTH_DUCT_LAYER /datum/component/plumbing/alkaline_input/send_request(dir) - process_request(amount = MACHINE_REAGENT_TRANSFER, reagent = /datum/reagent/reaction_agent/basic_buffer, dir = dir) + process_request(reagent = /datum/reagent/reaction_agent/basic_buffer, dir = dir) diff --git a/code/datums/components/supermatter_crystal.dm b/code/datums/components/supermatter_crystal.dm index 6608713438867..38968d1e3d1f5 100644 --- a/code/datums/components/supermatter_crystal.dm +++ b/code/datums/components/supermatter_crystal.dm @@ -4,6 +4,11 @@ var/datum/callback/tool_act_callback ///Callback used by the SM to get the damage and matter power increase/decrease var/datum/callback/consume_callback + // A whitelist of items that can interact with the SM without dusting the user + var/static/list/sm_item_whitelist = typecacheof(list( + /obj/item/melee/roastingstick, + /obj/item/toy/crayon/spraycan + )) /datum/component/supermatter_crystal/Initialize(datum/callback/tool_act_callback, datum/callback/consume_callback) @@ -15,6 +20,7 @@ RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(hand_hit)) RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(attackby_hit)) RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_WRENCH), PROC_REF(tool_hit)) + RegisterSignal(parent, COMSIG_ATOM_SECONDARY_TOOL_ACT(TOOL_WRENCH), PROC_REF(tool_hit)) RegisterSignal(parent, COMSIG_ATOM_BUMPED, PROC_REF(bumped_hit)) RegisterSignal(parent, COMSIG_ATOM_INTERCEPT_Z_FALL, PROC_REF(intercept_z_fall)) RegisterSignal(parent, COMSIG_ATOM_ON_Z_IMPACT, PROC_REF(on_z_impact)) @@ -37,6 +43,7 @@ COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_TOOL_ACT(TOOL_WRENCH), + COMSIG_ATOM_SECONDARY_TOOL_ACT(TOOL_WRENCH), COMSIG_ATOM_BUMPED, COMSIG_ATOM_INTERCEPT_Z_FALL, COMSIG_ATOM_ON_Z_IMPACT, @@ -152,9 +159,7 @@ var/atom/atom_source = source if(!istype(item) || (item.item_flags & ABSTRACT) || !istype(user)) return - if(istype(item, /obj/item/melee/roastingstick)) - return FALSE - if(istype(item, /obj/item/toy/crayon/spraycan)) + if(is_type_in_typecache(item, sm_item_whitelist)) return FALSE if(istype(item, /obj/item/clothing/mask/cigarette)) var/obj/item/clothing/mask/cigarette/cig = item diff --git a/code/datums/components/temporary_description.dm b/code/datums/components/temporary_description.dm new file mode 100644 index 0000000000000..1ff5e6dccdba3 --- /dev/null +++ b/code/datums/components/temporary_description.dm @@ -0,0 +1,44 @@ +/** + * Adds examine text to something which is removed when receiving specified signals, by default the revive signal. + * The default settings are set up to be applied to a corpse to add some kind of immersive storytelling text which goes away upon revival. + */ +/datum/component/temporary_description + /// What do we display on examine? + var/description_text = "" + /// What do we display if examined by a clown? Usually only applied if this is put on a corpse, but go nuts. + var/naive_description = "" + /// When are we removed? + var/list/removal_signals + +/datum/component/temporary_description/Initialize( + description_text = "There's something unusual about them.", + naive_description = "", + list/removal_signals = list(COMSIG_LIVING_REVIVE), +) + . = ..() + if (!isatom(parent)) + return COMPONENT_INCOMPATIBLE + if (!description_text) + stack_trace("[type] applied to [parent] with empty description, which is pointless.") + src.description_text = description_text + src.naive_description = naive_description + if (length(removal_signals)) + src.removal_signals = removal_signals + +/datum/component/temporary_description/RegisterWithParent() + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined)) + RegisterSignals(parent, removal_signals, PROC_REF(remove_component)) + +/datum/component/temporary_description/UnregisterFromParent() + UnregisterSignal(parent, removal_signals + COMSIG_ATOM_EXAMINE) + +/datum/component/temporary_description/proc/on_examined(atom/corpse, mob/thing_inspector, list/examine_list) + SIGNAL_HANDLER + if (naive_description && HAS_MIND_TRAIT(thing_inspector, TRAIT_NAIVE)) + examine_list += span_notice(naive_description) + return + examine_list += span_notice(description_text) + +/datum/component/temporary_description/proc/remove_component() + SIGNAL_HANDLER + qdel(src) // It wouldn't be immersive if the circumstances of my grisly death remained after I was revived diff --git a/code/datums/components/tree_climber.dm b/code/datums/components/tree_climber.dm index b3c70153c719c..9f506ae516f8a 100644 --- a/code/datums/components/tree_climber.dm +++ b/code/datums/components/tree_climber.dm @@ -12,6 +12,8 @@ return COMPONENT_INCOMPATIBLE src.climbing_distance = climbing_distance + ADD_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type) + /datum/component/tree_climber/RegisterWithParent() RegisterSignals(parent, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_LIVING_CLIMB_TREE), PROC_REF(climb_tree)) RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) diff --git a/code/datums/elements/ai_flee_while_injured.dm b/code/datums/elements/ai_flee_while_injured.dm index fc1a2e3328168..eca709dbee5da 100644 --- a/code/datums/elements/ai_flee_while_injured.dm +++ b/code/datums/elements/ai_flee_while_injured.dm @@ -19,6 +19,7 @@ src.stop_fleeing_at = stop_fleeing_at src.start_fleeing_below = start_fleeing_below RegisterSignal(target, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_health_changed)) + on_health_changed(target) /datum/element/ai_flee_while_injured/Detach(datum/source) . = ..() @@ -28,18 +29,18 @@ /datum/element/ai_flee_while_injured/proc/on_health_changed(mob/living/source) SIGNAL_HANDLER - if (!source.ai_controller) + if (isnull(source.ai_controller)) return var/current_health_percentage = source.health / source.maxHealth - if (source.ai_controller.blackboard[BB_BASIC_MOB_FLEEING]) - if (current_health_percentage < stop_fleeing_at) + if (source.ai_controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) + if (current_health_percentage > start_fleeing_below) return - source.ai_controller.CancelActions() // Stop fleeing go back to whatever you were doing - source.ai_controller.set_blackboard_key(BB_BASIC_MOB_FLEEING, FALSE) + source.ai_controller.CancelActions() + source.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) return - if (current_health_percentage > start_fleeing_below) + if (current_health_percentage < stop_fleeing_at) return - source.ai_controller.CancelActions() - source.ai_controller.set_blackboard_key(BB_BASIC_MOB_FLEEING, TRUE) + source.ai_controller.CancelActions() // Stop fleeing go back to whatever you were doing + source.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) diff --git a/code/datums/elements/amputating_limbs.dm b/code/datums/elements/amputating_limbs.dm index f7e3d08fcc4fa..16a99e96f6c19 100644 --- a/code/datums/elements/amputating_limbs.dm +++ b/code/datums/elements/amputating_limbs.dm @@ -1,4 +1,4 @@ -/// This component will intercept bare-handed attacks by the owner on critically injured carbons and amputate random limbs instead +/// This component will intercept bare-handed attacks by the owner on sufficiently injured carbons and amputate random limbs instead /datum/element/amputating_limbs element_flags = ELEMENT_BESPOKE argument_hash_start_idx = 2 @@ -68,7 +68,7 @@ /// Chop one off /datum/element/amputating_limbs/proc/amputate(mob/living/surgeon, mob/living/carbon/victim, obj/item/bodypart/to_remove) - surgeon.visible_message(span_warning("[surgeon] begins [surgery_verb] [to_remove] off of [victim]!")) + surgeon.visible_message(span_warning("[surgeon] [surgery_verb] [to_remove] off of [victim]!")) if (surgery_time > 0 && !do_after(surgeon, delay = surgery_time, target = victim)) return to_remove.dismember() diff --git a/code/datums/elements/climbable.dm b/code/datums/elements/climbable.dm index e953766571c2e..b26990c59119b 100644 --- a/code/datums/elements/climbable.dm +++ b/code/datums/elements/climbable.dm @@ -114,15 +114,13 @@ ///Handles climbing onto the atom when you click-drag /datum/element/climbable/proc/mousedrop_receive(atom/climbed_thing, atom/movable/dropped_atom, mob/user, params) SIGNAL_HANDLER - if(user == dropped_atom && isliving(dropped_atom)) - var/mob/living/living_target = dropped_atom - if(isanimal(living_target)) - var/mob/living/simple_animal/animal = dropped_atom - if (!animal.dextrous) - return - if(living_target.mobility_flags & MOBILITY_MOVE) - INVOKE_ASYNC(src, PROC_REF(climb_structure), climbed_thing, living_target, params) - return + if(user != dropped_atom || !isliving(dropped_atom)) + return + if(!HAS_TRAIT(dropped_atom, TRAIT_FENCE_CLIMBER) && !HAS_TRAIT(dropped_atom, TRAIT_CAN_HOLD_ITEMS)) // If you can hold items you can probably climb a fence + return + var/mob/living/living_target = dropped_atom + if(living_target.mobility_flags & MOBILITY_MOVE) + INVOKE_ASYNC(src, PROC_REF(climb_structure), climbed_thing, living_target, params) ///Tries to climb onto the target if the forced movement of the mob allows it /datum/element/climbable/proc/try_speedrun(datum/source, mob/bumpee) diff --git a/code/datums/elements/dextrous.dm b/code/datums/elements/dextrous.dm new file mode 100644 index 0000000000000..335c7c196d1ce --- /dev/null +++ b/code/datums/elements/dextrous.dm @@ -0,0 +1,69 @@ +/** + * Sets up the attachee to have hands and manages things like dropping items on death and displaying them on examine + * Actual hand performance is managed by code on /living/ and not encapsulated here, we just enable it + */ +/datum/element/dextrous + +/datum/element/dextrous/Attach(datum/target, hands_count = 2, hud_type = /datum/hud/dextrous) + . = ..() + if (!isliving(target) || iscarbon(target)) + return ELEMENT_INCOMPATIBLE // Incompatible with the carbon typepath because that already has its own hand handling and doesn't need hand holding + + var/mob/living/mob_parent = target + set_available_hands(mob_parent, hands_count) + mob_parent.set_hud_used(new hud_type(target)) + mob_parent.hud_used.show_hud(mob_parent.hud_used.hud_version) + ADD_TRAIT(target, TRAIT_CAN_HOLD_ITEMS, REF(src)) + RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + RegisterSignal(target, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_hand_clicked)) + RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined)) + +/datum/element/dextrous/Detach(datum/source) + . = ..() + var/mob/living/mob_parent = source + set_available_hands(mob_parent, initial(mob_parent.default_num_hands)) + var/initial_hud = initial(mob_parent.hud_type) + mob_parent.set_hud_used(new initial_hud(source)) + mob_parent.hud_used.show_hud(mob_parent.hud_used.hud_version) + REMOVE_TRAIT(source, TRAIT_CAN_HOLD_ITEMS, REF(src)) + UnregisterSignal(source, list( + COMSIG_ATOM_EXAMINE, + COMSIG_LIVING_DEATH, + COMSIG_LIVING_UNARMED_ATTACK, + )) + +/// Set up how many hands we should have +/datum/element/dextrous/proc/set_available_hands(mob/living/hand_owner, hands_count) + hand_owner.drop_all_held_items() + var/held_items = list() + for (var/i in 1 to hands_count) + held_items += null + hand_owner.held_items = held_items + hand_owner.set_num_hands(hands_count) + hand_owner.set_usable_hands(hands_count) + +/// Drop our shit when we die +/datum/element/dextrous/proc/on_death(mob/living/died, gibbed) + SIGNAL_HANDLER + died.drop_all_held_items() + +/// Try picking up items +/datum/element/dextrous/proc/on_hand_clicked(mob/living/hand_haver, atom/target, proximity, modifiers) + SIGNAL_HANDLER + if (!isitem(target) && hand_haver.combat_mode) + return + if (LAZYACCESS(modifiers, RIGHT_CLICK)) + INVOKE_ASYNC(target, TYPE_PROC_REF(/atom, attack_hand_secondary), hand_haver, modifiers) + else + INVOKE_ASYNC(target, TYPE_PROC_REF(/atom, attack_hand), hand_haver, modifiers) + INVOKE_ASYNC(hand_haver, TYPE_PROC_REF(/mob, update_held_items)) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/// Tell people what we are holding +/datum/element/dextrous/proc/on_examined(mob/living/examined, mob/user, list/examine_list) + SIGNAL_HANDLER + for(var/obj/item/held_item in examined.held_items) + if(held_item.item_flags & (ABSTRACT|EXAMINE_SKIP|HAND_ITEM)) + continue + examine_list += span_info("[examined.p_They()] [examined.p_have()] [held_item.get_examine_string(user)] in [examined.p_their()] \ + [examined.get_held_index_name(examined.get_held_index_of_item(held_item))].") diff --git a/code/datums/elements/food/microwavable.dm b/code/datums/elements/food/microwavable.dm index 037e3359ac724..8e7305545c0b0 100644 --- a/code/datums/elements/food/microwavable.dm +++ b/code/datums/elements/food/microwavable.dm @@ -6,14 +6,18 @@ var/atom/default_typepath = /obj/item/food/badrecipe /// Resulting atom typepath on a completed microwave. var/atom/result_typepath + /// Reagents that should be added to the result + var/list/added_reagents -/datum/element/microwavable/Attach(datum/target, microwave_type) +/datum/element/microwavable/Attach(datum/target, microwave_type, list/reagents) . = ..() if(!isitem(target)) return ELEMENT_INCOMPATIBLE result_typepath = microwave_type || default_typepath + added_reagents = reagents + RegisterSignal(target, COMSIG_ITEM_MICROWAVE_ACT, PROC_REF(on_microwaved)) if(!ispath(result_typepath, default_typepath)) @@ -45,6 +49,8 @@ BLACKBOX_LOG_FOOD_MADE(result.type) result.reagents.clear_reagents() source.reagents?.trans_to(result, source.reagents.total_volume) + if(added_reagents) // Add any new reagents that should be added + result.reagents.add_reagent_list(added_reagents) if(microwaver && microwaver.mind) ADD_TRAIT(result, TRAIT_FOOD_CHEF_MADE, REF(microwaver.mind)) diff --git a/code/datums/elements/footstep.dm b/code/datums/elements/footstep.dm index c9623c9ed4a26..3a0a9961843c4 100644 --- a/code/datums/elements/footstep.dm +++ b/code/datums/elements/footstep.dm @@ -40,6 +40,8 @@ footstep_sounds = GLOB.heavyfootstep if(FOOTSTEP_MOB_SHOE) footstep_sounds = GLOB.footstep + if(FOOTSTEP_MOB_RUST) + footstep_sounds = 'sound/effects/footstep/rustystep1.ogg' if(FOOTSTEP_MOB_SLIME) footstep_sounds = 'sound/effects/footstep/slime1.ogg' if(FOOTSTEP_OBJ_MACHINE) diff --git a/code/datums/elements/structure_repair.dm b/code/datums/elements/structure_repair.dm new file mode 100644 index 0000000000000..d3b26eed815be --- /dev/null +++ b/code/datums/elements/structure_repair.dm @@ -0,0 +1,45 @@ +/// Intercepts attacks from mobs with this component to instead repair specified structures. +/datum/element/structure_repair + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// How much to heal structures by + var/heal_amount + /// Typecache of types of structures to repair + var/list/structure_types_typecache + +/datum/element/structure_repair/Attach( + datum/target, + heal_amount = 5, + structure_types_typecache = typecacheof(list(/obj/structure)), +) + . = ..() + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + + src.heal_amount = heal_amount + src.structure_types_typecache = structure_types_typecache + RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(try_repair)) + +/datum/element/structure_repair/Detach(datum/source) + UnregisterSignal(source, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET)) + return ..() + +/// If the target is of a valid type, interrupt the attack chain to repair it instead +/datum/element/structure_repair/proc/try_repair(mob/living/fixer, atom/target) + SIGNAL_HANDLER + + if (!is_type_in_typecache(target, structure_types_typecache)) + return + + if (target.get_integrity() >= target.max_integrity) + target.balloon_alert(fixer, "not damaged!") + return COMPONENT_CANCEL_ATTACK_CHAIN + + target.repair_damage(heal_amount) + fixer.Beam(target, icon_state = "sendbeam", time = 0.4 SECONDS) + fixer.visible_message( + span_danger("[fixer] repairs [target]."), + span_danger("You repair [target], leaving it at [round(target.get_integrity() * 100 / target.max_integrity)]% stability."), + ) + + return COMPONENT_CANCEL_ATTACK_CHAIN diff --git a/code/datums/elements/wall_walker.dm b/code/datums/elements/wall_walker.dm new file mode 100644 index 0000000000000..92ac3318c1287 --- /dev/null +++ b/code/datums/elements/wall_walker.dm @@ -0,0 +1,49 @@ +/// This element will allow the mob it's attached to to pass through a specified type of wall, and drag anything through it. +/datum/element/wall_walker + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// What kind of walls can we pass through? + var/wall_type + +/datum/element/wall_walker/Attach( + datum/target, + wall_type = /turf/closed/wall, +) + . = ..() + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + + src.wall_type = wall_type + RegisterSignal(target, COMSIG_LIVING_WALL_BUMP, PROC_REF(try_pass_wall)) + RegisterSignal(target, COMSIG_LIVING_WALL_EXITED, PROC_REF(exit_wall)) + +/datum/element/wall_walker/Detach(datum/source) + UnregisterSignal(source, list(COMSIG_LIVING_WALL_BUMP, COMSIG_LIVING_WALL_EXITED)) + return ..() + +/// If the wall is of the proper type, pass into it and keep hold on whatever you're pulling +/datum/element/wall_walker/proc/try_pass_wall(mob/living/passing_mob, turf/closed/bumped_wall) + if(!istype(bumped_wall, wall_type)) + return + + var/atom/movable/stored_pulling = passing_mob.pulling + if(stored_pulling) //force whatever you're pulling to come with you + stored_pulling.setDir(get_dir(stored_pulling.loc, passing_mob.loc)) + stored_pulling.forceMove(passing_mob.loc) + passing_mob.forceMove(bumped_wall) + + if(stored_pulling) //don't drop them because we went into a wall + passing_mob.start_pulling(stored_pulling, supress_message = TRUE) + +/// If the wall is of the proper type, pull whatever you're pulling into it +/datum/element/wall_walker/proc/exit_wall(mob/living/passing_mob, turf/closed/exited_wall) + if(!istype(exited_wall, wall_type)) + return + + var/atom/movable/stored_pulling = passing_mob.pulling + if(isnull(stored_pulling)) + return + + stored_pulling.setDir(get_dir(stored_pulling.loc, passing_mob.loc)) + stored_pulling.forceMove(exited_wall) + passing_mob.start_pulling(stored_pulling, supress_message = TRUE) diff --git a/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm b/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm index d247117f3a176..a4c7c372525bf 100644 --- a/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm +++ b/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm @@ -34,3 +34,8 @@ name = "Garden Gnome" icon_file = 'icons/mob/simple/garden_gnome.dmi' json_config = 'code/datums/greyscale/json_configs/garden_gnome.json' + +/datum/greyscale_config/pony + name = "Pony" + icon_file = 'icons/mob/simple/animal.dmi' + json_config = 'code/datums/greyscale/json_configs/pony.json' diff --git a/code/datums/greyscale/json_configs/pony.json b/code/datums/greyscale/json_configs/pony.json new file mode 100644 index 0000000000000..a08437c7cb664 --- /dev/null +++ b/code/datums/greyscale/json_configs/pony.json @@ -0,0 +1,30 @@ +{ + "pony": [ + { + "type": "icon_state", + "icon_state": "pony", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "pony_hair", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ], + "pony_dead": [ + { + "type": "icon_state", + "icon_state": "pony_dead", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "pony_hair_dead", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/code/datums/looping_sounds/burning.dm b/code/datums/looping_sounds/burning.dm new file mode 100644 index 0000000000000..191ae88db8924 --- /dev/null +++ b/code/datums/looping_sounds/burning.dm @@ -0,0 +1,9 @@ +/// Soundloop for the fire (bonfires, fireplaces, etc.) +/datum/looping_sound/burning + start_sound = 'sound/items/match_strike.ogg' + start_length = 3 SECONDS + mid_sounds = 'sound/effects/comfyfire.ogg' + mid_length = 5 SECONDS + volume = 50 + vary = TRUE + extra_range = MEDIUM_RANGE_SOUND_EXTRARANGE diff --git a/code/datums/looping_sounds/vents.dm b/code/datums/looping_sounds/vents.dm new file mode 100644 index 0000000000000..2d0a3443631df --- /dev/null +++ b/code/datums/looping_sounds/vents.dm @@ -0,0 +1,7 @@ +/datum/looping_sound/vent_pump_overclock + start_sound = 'sound/machines/fan_start.ogg' + start_length = 1.5 SECONDS + end_sound = 'sound/machines/fan_stop.ogg' + end_sound = 1.5 SECONDS + mid_sounds = 'sound/machines/fan_loop.ogg' + mid_length = 2 SECONDS diff --git a/code/datums/mapgen/Cavegens/IcemoonCaves.dm b/code/datums/mapgen/Cavegens/IcemoonCaves.dm index 7d7437ccda6c8..b0fcd471db2ff 100644 --- a/code/datums/mapgen/Cavegens/IcemoonCaves.dm +++ b/code/datums/mapgen/Cavegens/IcemoonCaves.dm @@ -62,12 +62,12 @@ weighted_closed_turf_types = list(/turf/closed/mineral/random/snow/underground = 1) weighted_mob_spawn_list = list( SPAWN_MEGAFAUNA = 1, + /mob/living/basic/mining/ice_demon = 100, /mob/living/basic/mining/ice_whelp = 60, /mob/living/basic/mining/legion/snow = 100, - /mob/living/simple_animal/hostile/asteroid/ice_demon = 100, /obj/structure/spawner/ice_moon/demonic_portal = 6, - /obj/structure/spawner/ice_moon/demonic_portal/snowlegion = 6, /obj/structure/spawner/ice_moon/demonic_portal/ice_whelp = 6, + /obj/structure/spawner/ice_moon/demonic_portal/snowlegion = 6, ) weighted_megafauna_spawn_list = list(/mob/living/simple_animal/hostile/megafauna/colossus = 1) weighted_flora_spawn_list = list( diff --git a/code/datums/memory/_memory.dm b/code/datums/memory/_memory.dm index e3f161d5f0e4b..2b3e250a3fbf2 100644 --- a/code/datums/memory/_memory.dm +++ b/code/datums/memory/_memory.dm @@ -257,6 +257,7 @@ /mob/living/basic/cow/wisdom, /mob/living/basic/crab, /mob/living/basic/goat, + /mob/living/basic/gorilla, /mob/living/basic/headslug, /mob/living/basic/killer_tomato, /mob/living/basic/lizard, @@ -274,7 +275,6 @@ /mob/living/basic/statue, /mob/living/basic/stickman, /mob/living/basic/stickman/dog, - /mob/living/simple_animal/hostile/gorilla, /mob/living/simple_animal/hostile/megafauna/dragon/lesser, /mob/living/simple_animal/parrot, /mob/living/simple_animal/pet/cat, diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm index 30be64e13f4f1..cb9bf7a5b1ad5 100644 --- a/code/datums/mutations/body.dm +++ b/code/datums/mutations/body.dm @@ -254,9 +254,9 @@ name = "Anti-Glow" desc = "Your skin seems to attract and absorb nearby light creating 'darkness' around you." text_gain_indication = "The light around you seems to disappear." - glow = -1.5 conflicts = list(/datum/mutation/human/glow) locked = TRUE + glow_power = -1.5 /datum/mutation/human/glow/anti/get_glow_color() return COLOR_BLACK diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm index e88456fe60ac7..1dcf535c32903 100644 --- a/code/datums/ruins/lavaland.dm +++ b/code/datums/ruins/lavaland.dm @@ -283,3 +283,19 @@ suffix = "lavaland_battle_site.dmm" allow_duplicates = TRUE cost = 3 + +/datum/map_template/ruin/lavaland/watcher_grave + name = "Watchers' Grave" + id = "watcher-grave" + description = "A lonely cave where an orphaned child awaits a new parent." + suffix = "lavaland_surface_watcher_grave.dmm" + cost = 5 + allow_duplicates = FALSE + +/datum/map_template/ruin/lavaland/mook_village + name = "Mook Village" + id = "mook_village" + description = "A village hosting a community of friendly mooks!" + suffix = "lavaland_surface_mookvillage.dmm" + allow_duplicates = FALSE + cost = 5 diff --git a/code/datums/station_traits/neutral_traits.dm b/code/datums/station_traits/neutral_traits.dm index f57f6c2e9dd64..8402c70236baa 100644 --- a/code/datums/station_traits/neutral_traits.dm +++ b/code/datums/station_traits/neutral_traits.dm @@ -148,7 +148,7 @@ show_in_report = FALSE // Selective attention test. Did you spot the gorilla? /// The gorilla we created, we only hold this ref until the round starts. - var/mob/living/simple_animal/hostile/gorilla/cargo_domestic/cargorilla + var/mob/living/basic/gorilla/cargorilla/cargorilla /datum/station_trait/cargorilla/New() . = ..() @@ -158,8 +158,8 @@ /datum/station_trait/cargorilla/proc/replace_cargo(datum/source) SIGNAL_HANDLER - var/mob/living/simple_animal/sloth/cargo_sloth = GLOB.cargo_sloth - if(!cargo_sloth) + var/mob/living/basic/sloth/cargo_sloth = GLOB.cargo_sloth + if(isnull(cargo_sloth)) return cargorilla = new(cargo_sloth.loc) @@ -189,7 +189,7 @@ cargorilla = null /// Get us a ghost for the gorilla. -/datum/station_trait/cargorilla/proc/get_ghost_for_gorilla(mob/living/simple_animal/hostile/gorilla/cargo_domestic/gorilla) +/datum/station_trait/cargorilla/proc/get_ghost_for_gorilla(mob/living/basic/gorilla/cargorilla/gorilla) if(QDELETED(gorilla)) return diff --git a/code/datums/status_effects/debuffs/fire_stacks.dm b/code/datums/status_effects/debuffs/fire_stacks.dm index 112e8bd861409..6f31faaefaae6 100644 --- a/code/datums/status_effects/debuffs/fire_stacks.dm +++ b/code/datums/status_effects/debuffs/fire_stacks.dm @@ -147,7 +147,10 @@ if(!on_fire) return TRUE - adjust_stacks(owner.fire_stack_decay_rate * seconds_between_ticks) + if(HAS_TRAIT(owner, TRAIT_HUSK)) + adjust_stacks(-2 * seconds_between_ticks) + else + adjust_stacks(owner.fire_stack_decay_rate * seconds_between_ticks) if(stacks <= 0) qdel(src) diff --git a/code/datums/status_effects/drug_effects.dm b/code/datums/status_effects/drug_effects.dm index d01a92743b589..1d37c8f0e43eb 100644 --- a/code/datums/status_effects/drug_effects.dm +++ b/code/datums/status_effects/drug_effects.dm @@ -70,7 +70,7 @@ /datum/status_effect/stoned/on_apply() if(!ishuman(owner)) - CRASH("[type] status effect added to non-human owner: [owner ? owner.type : "null owner"]") + return FALSE var/mob/living/carbon/human/human_owner = owner original_eye_color_left = human_owner.eye_color_left original_eye_color_right = human_owner.eye_color_right @@ -85,7 +85,7 @@ /datum/status_effect/stoned/on_remove() if(!ishuman(owner)) - stack_trace("[type] status effect being removed from non-human owner: [owner ? owner.type : "null owner"]") + return var/mob/living/carbon/human/human_owner = owner human_owner.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/cannabis) human_owner.eye_color_left = original_eye_color_left diff --git a/code/datums/storage/subtypes/surgery_tray.dm b/code/datums/storage/subtypes/surgery_tray.dm index 358865813188b..42b369b4ce922 100644 --- a/code/datums/storage/subtypes/surgery_tray.dm +++ b/code/datums/storage/subtypes/surgery_tray.dm @@ -6,6 +6,7 @@ /datum/storage/surgery_tray/New() . = ..() set_holdable(list( + /obj/item/autopsy_scanner, /obj/item/blood_filter, /obj/item/bonesetter, /obj/item/cautery, diff --git a/code/datums/wires/microwave.dm b/code/datums/wires/microwave.dm index abce90e8de5e2..e3efabac08c4d 100644 --- a/code/datums/wires/microwave.dm +++ b/code/datums/wires/microwave.dm @@ -4,7 +4,8 @@ /datum/wires/microwave/New(atom/holder) wires = list( - WIRE_ACTIVATE + WIRE_ACTIVATE, + WIRE_MODE_SELECT ) ..() @@ -12,18 +13,23 @@ if(!..()) return FALSE . = FALSE - var/obj/machinery/microwave/M = holder - if(M.panel_open) + var/obj/machinery/microwave/mw = holder + if(mw.panel_open) . = TRUE /datum/wires/microwave/on_pulse(wire) - var/obj/machinery/microwave/M = holder + var/obj/machinery/microwave/mw = holder switch(wire) if(WIRE_ACTIVATE) - M.cook() + mw.cook() + if(WIRE_MODE_SELECT) + if(mw.vampire_charging_capable) + mw.vampire_charging_enabled = !mw.vampire_charging_enabled /datum/wires/microwave/on_cut(wire, mend, source) - var/obj/machinery/microwave/M = holder + var/obj/machinery/microwave/mw = holder switch(wire) if(WIRE_ACTIVATE) - M.wire_disabled = !mend + mw.wire_disabled = !mend + if(WIRE_MODE_SELECT) + mw.wire_mode_swap = !mend diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm index dfba10ee6f872..f6d31787c9e4f 100644 --- a/code/datums/wounds/_wounds.dm +++ b/code/datums/wounds/_wounds.dm @@ -185,7 +185,7 @@ * * attack_direction: For bloodsplatters, if relevant * * wound_source: The source of the wound, such as a weapon. */ -/datum/wound/proc/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE, attack_direction = null, wound_source = "Unknown") +/datum/wound/proc/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE, attack_direction = null, wound_source = "Unknown", replacing = FALSE) if (!can_be_applied_to(L, old_wound)) qdel(src) @@ -198,7 +198,7 @@ src.wound_source = wound_source set_victim(L.owner) - set_limb(L) + set_limb(L, replacing) LAZYADD(victim.all_wounds, src) LAZYADD(limb.wounds, src) update_descriptions() @@ -371,7 +371,7 @@ already_scarred = TRUE var/obj/item/bodypart/cached_limb = limb // remove_wound() nulls limb so we have to track it locally remove_wound(replaced=TRUE) - new_wound.apply_wound(cached_limb, old_wound = src, smited = smited, attack_direction = attack_direction, wound_source = wound_source) + new_wound.apply_wound(cached_limb, old_wound = src, smited = smited, attack_direction = attack_direction, wound_source = wound_source, replacing = TRUE) . = new_wound qdel(src) diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index 333d743eae9d1..9b8da5177ca17 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -385,7 +385,7 @@ threshold_minimum = 115 // doesn't make much sense for "a" bone to stick out of your head -/datum/wound/blunt/bone/critical/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE, attack_direction = null, wound_source = "Unknown") +/datum/wound/blunt/bone/critical/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE, attack_direction = null, wound_source = "Unknown", replacing = FALSE) if(L.body_zone == BODY_ZONE_HEAD) occur_text = "splits open, exposing a bare, cracked skull through the flesh and blood" examine_desc = "has an unsettling indent, with bits of skull poking out" diff --git a/code/datums/wounds/burns.dm b/code/datums/wounds/burns.dm index ac2e5d44c7bc2..39fce37280207 100644 --- a/code/datums/wounds/burns.dm +++ b/code/datums/wounds/burns.dm @@ -49,7 +49,7 @@ for(var/datum/reagent/reagent as anything in victim.reagents.reagent_list) if(reagent.chemical_flags & REAGENT_AFFECTS_WOUNDS) - reagent.on_burn_wound_processing() + reagent.on_burn_wound_processing(src) if(HAS_TRAIT(victim, TRAIT_VIRUS_RESISTANCE)) sanitization += 0.9 diff --git a/code/game/area/areas/centcom.dm b/code/game/area/areas/centcom.dm index dedc4984f8e1f..45a67de4c1c16 100644 --- a/code/game/area/areas/centcom.dm +++ b/code/game/area/areas/centcom.dm @@ -217,7 +217,7 @@ base_lighting_alpha = 255 has_gravity = STANDARD_GRAVITY flags_1 = NONE - area_flags = UNIQUE_AREA | NOTELEPORT | NO_DEATH_MESSAGE + area_flags = UNIQUE_AREA | NOTELEPORT | NO_DEATH_MESSAGE | BLOCK_SUICIDE /area/centcom/ctf/control_room name = "Control Room A" diff --git a/code/game/atom_defense.dm b/code/game/atom_defense.dm index bced6cb3bf143..f5759110a6d9a 100644 --- a/code/game/atom_defense.dm +++ b/code/game/atom_defense.dm @@ -51,6 +51,13 @@ if(atom_integrity == new_value) return atom_integrity = new_value + on_update_integrity(old_value, new_value) + return new_value + +/// Handle updates to your atom's integrity +/atom/proc/on_update_integrity(old_value, new_value) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(TRUE) SEND_SIGNAL(src, COMSIG_ATOM_INTEGRITY_CHANGED, old_value, new_value) /// This mostly exists to keep atom_integrity private. Might be useful in the future. @@ -58,6 +65,11 @@ SHOULD_BE_PURE(TRUE) return atom_integrity +/// Similar to get_integrity, but returns the percentage as [0-1] instead. +/atom/proc/get_integrity_percentage() + SHOULD_BE_PURE(TRUE) + return round(atom_integrity / max_integrity, 0.01) + ///returns the damage value of the attack after processing the atom's various armor protections /atom/proc/run_atom_armor(damage_amount, damage_type, damage_flag = 0, attack_dir, armour_penetration = 0) if(!uses_integrity) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index be6cf0436a7d4..edca877d90511 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -670,11 +670,11 @@ var/reagent_sigreturn = SEND_SIGNAL(src, COMSIG_ATOM_REAGENT_EXAMINE, user, ., user_sees_reagents) if(!(reagent_sigreturn & STOP_GENERIC_REAGENT_EXAMINE)) if(reagents.flags & TRANSPARENT) - if(reagents.total_volume > 0) + if(reagents.total_volume) . += "It contains [round(reagents.total_volume, 0.01)] units of various reagents[user_sees_reagents ? ":" : "."]" - if(user_sees_reagents) //Show each individual reagent + if(user_sees_reagents) //Show each individual reagent for detailed examination for(var/datum/reagent/current_reagent as anything in reagents.reagent_list) - . += "• [round(current_reagent.volume, 0.01)] units of [current_reagent.name]" + . += "• [FLOOR(current_reagent.volume, CHEMICAL_QUANTISATION_LEVEL)] units of [current_reagent.name]" if(reagents.is_reacting) . += span_warning("It is currently reacting!") . += span_notice("The solution's pH is [round(reagents.ph, 0.01)] and has a temperature of [reagents.chem_temp]K.") @@ -1032,8 +1032,8 @@ * * Default behaviour is to send [COMSIG_ATOM_RCD_ACT] and return FALSE */ -/atom/proc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - SEND_SIGNAL(src, COMSIG_ATOM_RCD_ACT, user, the_rcd, passed_mode) +/atom/proc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + SEND_SIGNAL(src, COMSIG_ATOM_RCD_ACT, user, the_rcd, rcd_data["[RCD_DESIGN_MODE]"]) return FALSE /** diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 93912e06899d8..dda198db431c9 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -729,7 +729,7 @@ . = ..() /datum/dynamic_ruleset/midround/from_ghosts/revenant/generate_ruleset_body(mob/applicant) - var/mob/living/simple_animal/revenant/revenant = new(pick(spawn_locs)) + var/mob/living/basic/revenant/revenant = new(pick(spawn_locs)) revenant.key = applicant.key message_admins("[ADMIN_LOOKUPFLW(revenant)] has been made into a revenant by the midround ruleset.") log_game("[key_name(revenant)] was spawned as a revenant by the midround ruleset.") diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index d245a0a317d81..444eff1700677 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -591,13 +591,7 @@ if(!isliving(user)) return FALSE //no ghosts allowed, sorry - var/is_dextrous = FALSE - if(isanimal(user)) - var/mob/living/simple_animal/user_as_animal = user - if (user_as_animal.dextrous) - is_dextrous = TRUE - - if(!issilicon(user) && !is_dextrous && !user.can_hold_items()) + if(!issilicon(user) && !user.can_hold_items()) return FALSE //spiders gtfo if(issilicon(user)) // If we are a silicon, make sure the machine allows silicons to interact with it diff --git a/code/game/machinery/canister_frame.dm b/code/game/machinery/canister_frame.dm deleted file mode 100644 index 2e00aed027330..0000000000000 --- a/code/game/machinery/canister_frame.dm +++ /dev/null @@ -1,103 +0,0 @@ -//Canister Frames -/obj/structure/canister_frame - name = "canister frame" - icon = 'icons/obj/pipes_n_cables/atmos.dmi' - icon_state = "frame_0" - density = TRUE - -/obj/structure/canister_frame/examine(user) - . = ..() - -/obj/structure/canister_frame/machine - name = "canister frame" - desc = "A frame used to build different kinds of canisters." - - /// The previous canister frame tier path - var/obj/structure/canister_frame/machine/prev_tier - /// The next canister frame tier path - var/obj/structure/canister_frame/machine/next_tier - /// The required item for going to next tier. Must be set if next_tier is set. - var/obj/item/stack/next_tier_reqitem - /// The amount of items required in the stack of the required item. Must be set if next_tier is set. - var/next_tier_reqitem_am - /// The finished usable canister path - var/atom/finished_obj - -/obj/structure/canister_frame/machine/deconstruct(disassembled = TRUE) - if (!(flags_1 & NODECONSTRUCT_1)) - // Spawn 5 sheets for the tier 0 frame - new /obj/item/stack/sheet/iron(loc, 5) - - // Loop backwards in the tiers and spawn the requirement for each tier - var/obj/structure/canister_frame/machine/i_prev = prev_tier - while(ispath(i_prev)) - var/obj/item/stack/prev_tier_reqitem = initial(i_prev.next_tier_reqitem) - var/prev_tier_reqitem_am = initial(i_prev.next_tier_reqitem_am) - new prev_tier_reqitem(loc, prev_tier_reqitem_am) - i_prev = initial(i_prev.prev_tier) - qdel(src) - -/obj/structure/canister_frame/machine/unfinished_canister_frame - name = "unfinished canister frame" - icon_state = "frame_0" - - next_tier = /obj/structure/canister_frame/machine/finished_canister_frame - next_tier_reqitem = /obj/item/stack/sheet/iron - next_tier_reqitem_am = 5 - -/obj/structure/canister_frame/machine/finished_canister_frame - name = "finished canister frame" - icon_state = "frame_1" - - prev_tier = /obj/structure/canister_frame/machine/unfinished_canister_frame - finished_obj = /obj/machinery/portable_atmospherics/canister - -/obj/structure/canister_frame/machine/examine(user) - . = ..() - . += span_notice("It can be dismantled by removing the bolts.") - - if(ispath(next_tier)) - var/item_name = initial(next_tier_reqitem.singular_name) - if(!item_name) - item_name = initial(next_tier_reqitem.name) - if(next_tier_reqitem_am > 1) - . += span_notice("It can be improved using [next_tier_reqitem_am] [item_name]\s.") - else - . += span_notice("It can be improved using \a [item_name].") - - if(ispath(finished_obj)) - . += span_notice("It can be finished off by screwing it together.") - -/obj/structure/canister_frame/machine/attackby(obj/item/S, mob/user, params) - if (ispath(next_tier) && istype(S, next_tier_reqitem)) - var/obj/item/stack/ST = S - var/reqitem_name = ST.singular_name ? ST.singular_name : ST.name - to_chat(user, span_notice("You start adding [next_tier_reqitem_am] [reqitem_name]\s to the frame...")) - if (ST.use_tool(src, user, 2 SECONDS, amount=next_tier_reqitem_am, volume=50)) - to_chat(user, span_notice("You added [next_tier_reqitem_am] [reqitem_name]\s to the frame, turning it into \a [initial(next_tier.name)].")) - new next_tier(drop_location()) - qdel(src) - return - return ..() - -/obj/structure/canister_frame/machine/screwdriver_act(mob/living/user, obj/item/I) - . = TRUE - if(..()) - return - if(ispath(finished_obj)) - to_chat(user, span_notice("You start tightening the screws on \the [src].")) - if (I.use_tool(src, user, 2 SECONDS, volume=50)) - to_chat(user, span_notice("You tighten the last screws on \the [src].")) - new finished_obj(drop_location()) - qdel(src) - return - return FALSE - -/obj/structure/canister_frame/machine/wrench_act(mob/living/user, obj/item/I) - . = TRUE - if(..()) - return - to_chat(user, span_notice("You start to dismantle \the [src]...")) - if (I.use_tool(src, user, 2 SECONDS, volume=50)) - to_chat(user, span_notice("You dismantle \the [src].")) - deconstruct() diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm index 5230dbe9e0946..a252e1247233b 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -234,3 +234,37 @@ if(state >= 3) new /obj/item/stack/cable_coil(drop_location(), 5) ..() + +/// Helpers for rcd +/obj/structure/frame/computer/rcd + icon = 'icons/hud/radial.dmi' + icon_state = "cnorth" + +/obj/structure/frame/computer/rcd/Initialize(mapload) + name = "computer frame" + icon = 'icons/obj/assemblies/stock_parts.dmi' + icon_state = "0" + + . = ..() + + set_anchored(TRUE) + +/obj/structure/frame/computer/rcd/north + dir = NORTH + name = "Computer North" + icon_state = "cnorth" + +/obj/structure/frame/computer/rcd/south + dir = SOUTH + name = "Computer South" + icon_state = "csouth" + +/obj/structure/frame/computer/rcd/east + dir = EAST + name = "Computer East" + icon_state = "ceast" + +/obj/structure/frame/computer/rcd/west + dir = WEST + name = "Computer West" + icon_state = "cwest" diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index 5a604cd969b7d..54e622fb12d38 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -82,17 +82,18 @@ /obj/machinery/computer/security/ui_data() var/list/data = list() - data["network"] = network data["activeCamera"] = null if(active_camera) data["activeCamera"] = list( name = active_camera.c_tag, + ref = REF(active_camera), status = active_camera.status, ) return data /obj/machinery/computer/security/ui_static_data() var/list/data = list() + data["network"] = network data["mapRef"] = cam_screen.assigned_map var/list/cameras = get_camera_list(network) data["cameras"] = list() @@ -100,6 +101,7 @@ var/obj/machinery/camera/C = cameras[i] data["cameras"] += list(list( name = C.c_tag, + ref = REF(C), )) return data @@ -110,13 +112,11 @@ return if(action == "switch_camera") - var/c_tag = params["name"] - var/list/cameras = get_camera_list(network) - var/obj/machinery/camera/selected_camera = cameras[c_tag] + var/obj/machinery/camera/selected_camera = locate(params["camera"]) in GLOB.cameranet.cameras active_camera = selected_camera playsound(src, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE) - if(!selected_camera) + if(isnull(active_camera)) return TRUE update_active_camera_screen() diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index bfdcdc45064ee..568a65fb98175 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -125,6 +125,7 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) JOB_QUARTERMASTER = 50, JOB_SHAFT_MINER = 51, JOB_CARGO_TECHNICIAN = 52, + JOB_BITRUNNER = 53, // 60+: Civilian/other JOB_HEAD_OF_PERSONNEL = 60, JOB_BARTENDER = 61, diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index 3bf44a9aa9e2d..228f849b4aede 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -428,3 +428,12 @@ new physical_object_type(drop_location()) else stack_trace("Invalid component [component] was found in constructable frame") + +/obj/structure/frame/machine/secured + state = 2 + icon_state = "box_1" + +/obj/structure/frame/machine/secured/Initialize(mapload) + . = ..() + + set_anchored(TRUE) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index a1fa73b1f8b49..33ad787116c0f 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -1563,13 +1563,12 @@ if(security_level != AIRLOCK_SECURITY_NONE) to_chat(user, span_notice("[src]'s reinforcement needs to be removed first.")) return FALSE - return list("mode" = RCD_DECONSTRUCT, "delay" = 5 SECONDS, "cost" = 32) + return list("delay" = 5 SECONDS, "cost" = 32) return FALSE -/obj/machinery/door/airlock/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) +/obj/machinery/door/airlock/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + switch(rcd_data["[RCD_DESIGN_MODE]"]) if(RCD_DECONSTRUCT) - to_chat(user, span_notice("You deconstruct the airlock.")) qdel(src) return TRUE return FALSE @@ -1806,24 +1805,29 @@ // Station Airlocks Regular /obj/machinery/door/airlock/command + name = "command airlock" icon = 'icons/obj/doors/airlocks/station/command.dmi' assemblytype = /obj/structure/door_assembly/door_assembly_com normal_integrity = 450 /obj/machinery/door/airlock/security + name = "security airlock" icon = 'icons/obj/doors/airlocks/station/security.dmi' assemblytype = /obj/structure/door_assembly/door_assembly_sec normal_integrity = 450 /obj/machinery/door/airlock/engineering + name = "engineering airlock" icon = 'icons/obj/doors/airlocks/station/engineering.dmi' assemblytype = /obj/structure/door_assembly/door_assembly_eng /obj/machinery/door/airlock/medical + name = "medical airlock" icon = 'icons/obj/doors/airlocks/station/medical.dmi' assemblytype = /obj/structure/door_assembly/door_assembly_med /obj/machinery/door/airlock/hydroponics //Hydroponics front doors! + name = "hydroponics airlock" icon = 'icons/obj/doors/airlocks/station/hydroponics.dmi' assemblytype = /obj/structure/door_assembly/door_assembly_hydro @@ -1849,6 +1853,7 @@ assemblytype = /obj/structure/door_assembly/door_assembly_atmo /obj/machinery/door/airlock/research + name = "research airlock" icon = 'icons/obj/doors/airlocks/station/research.dmi' assemblytype = /obj/structure/door_assembly/door_assembly_research @@ -1858,16 +1863,19 @@ assemblytype = /obj/structure/door_assembly/door_assembly_fre /obj/machinery/door/airlock/science + name = "science airlock" icon = 'icons/obj/doors/airlocks/station/science.dmi' assemblytype = /obj/structure/door_assembly/door_assembly_science /obj/machinery/door/airlock/virology + name = "virology airlock" icon = 'icons/obj/doors/airlocks/station/virology.dmi' assemblytype = /obj/structure/door_assembly/door_assembly_viro // Station Airlocks Glass /obj/machinery/door/airlock/glass + name = "glass airlock" opacity = FALSE glass = TRUE @@ -1885,11 +1893,13 @@ id_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_EXTERIOR /obj/machinery/door/airlock/command/glass + name = "command glass airlock" opacity = FALSE glass = TRUE normal_integrity = 400 /obj/machinery/door/airlock/engineering/glass + name = "engineering glass airlock" opacity = FALSE glass = TRUE @@ -1897,19 +1907,23 @@ critical_machine = TRUE //stops greytide virus from opening & bolting doors in critical positions, such as the SM chamber. /obj/machinery/door/airlock/security/glass + name = "security glass airlock" opacity = FALSE glass = TRUE normal_integrity = 400 /obj/machinery/door/airlock/medical/glass + name = "medical glass airlock" opacity = FALSE glass = TRUE /obj/machinery/door/airlock/hydroponics/glass //Uses same icon as medical/glass, maybe update it with its own unique icon one day? + name = "hydroponics glass airlock" opacity = FALSE glass = TRUE /obj/machinery/door/airlock/research/glass + name = "research glass airlock" opacity = FALSE glass = TRUE @@ -1926,10 +1940,12 @@ id_tag = INCINERATOR_ORDMIX_AIRLOCK_EXTERIOR /obj/machinery/door/airlock/mining/glass + name = "mining glass airlock" opacity = FALSE glass = TRUE /obj/machinery/door/airlock/atmos/glass + name = "atmospheric glass airlock" opacity = FALSE glass = TRUE @@ -1937,18 +1953,22 @@ critical_machine = TRUE //stops greytide virus from opening & bolting doors in critical positions, such as the SM chamber. /obj/machinery/door/airlock/science/glass + name = "science glass airlock" opacity = FALSE glass = TRUE /obj/machinery/door/airlock/virology/glass + name = "virology glass airlock" opacity = FALSE glass = TRUE /obj/machinery/door/airlock/maintenance/glass + name = "maintainence glass airlock" opacity = FALSE glass = TRUE /obj/machinery/door/airlock/maintenance/external/glass + name = "maintainence external glass airlock" opacity = FALSE glass = TRUE normal_integrity = 200 @@ -2108,11 +2128,13 @@ // Public Airlocks /obj/machinery/door/airlock/public + name = "public airlock" icon = 'icons/obj/doors/airlocks/public/glass.dmi' overlays_file = 'icons/obj/doors/airlocks/public/overlays.dmi' assemblytype = /obj/structure/door_assembly/door_assembly_public /obj/machinery/door/airlock/public/glass + name = "public glass airlock" opacity = FALSE glass = TRUE @@ -2183,6 +2205,7 @@ /obj/machinery/door/airlock/external/ruin /obj/machinery/door/airlock/external/glass + name = "external glass airlock" opacity = FALSE glass = TRUE diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index b8568863600d2..d7254e7f6b162 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -889,21 +889,19 @@ /obj/structure/firelock_frame/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) if(the_rcd.mode == RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 5 SECONDS, "cost" = 16) + return list("delay" = 5 SECONDS, "cost" = 16) else if((constructionStep == CONSTRUCTION_NO_CIRCUIT) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS)) - return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 2 SECONDS, "cost" = 1) + return list("delay" = 2 SECONDS, "cost" = 1) return FALSE -/obj/structure/firelock_frame/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) +/obj/structure/firelock_frame/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + switch(rcd_data["[RCD_DESIGN_MODE]"]) if(RCD_UPGRADE_SIMPLE_CIRCUITS) - user.visible_message(span_notice("[user] fabricates a circuit and places it into [src]."), \ - span_notice("You adapt a firelock circuit and slot it into the assembly.")) + user.balloon_alert(user, "circuit installed") constructionStep = CONSTRUCTION_PANEL_OPEN update_appearance() return TRUE if(RCD_DECONSTRUCT) - to_chat(user, span_notice("You deconstruct [src].")) qdel(src) return TRUE return FALSE diff --git a/code/game/machinery/doors/poddoor.dm b/code/game/machinery/doors/poddoor.dm index 0924bcea7124e..5b3e2322d615f 100644 --- a/code/game/machinery/doors/poddoor.dm +++ b/code/game/machinery/doors/poddoor.dm @@ -49,7 +49,7 @@ return if (deconstruction != BLASTDOOR_FINISHED) return - var/change_id = tgui_input_number(user, "Set the door controllers ID", "Door Controller ID", id, 100) + var/change_id = tgui_input_number(user, "Set the door controllers ID (Current: [id])", "Door Controller ID", isnum(id) ? id : null, 100) if(!change_id || QDELETED(usr) || QDELETED(src) || !usr.can_perform_action(src, FORBID_TELEKINESIS_REACH)) return id = change_id diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 35044d5a81a47..30b4e04c4cc3f 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -441,15 +441,13 @@ /obj/machinery/door/window/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) switch(the_rcd.mode) if(RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 5 SECONDS, "cost" = 32) + return list("delay" = 5 SECONDS, "cost" = 32) return FALSE -/obj/machinery/door/window/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_DECONSTRUCT) - to_chat(user, span_notice("You deconstruct the windoor.")) - qdel(src) - return TRUE +/obj/machinery/door/window/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(rcd_data["[RCD_DESIGN_MODE]"] == RCD_DECONSTRUCT) + qdel(src) + return TRUE return FALSE /obj/machinery/door/window/brigdoor diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm index 82c5ce56a16c5..ad822a7171a15 100644 --- a/code/game/machinery/firealarm.dm +++ b/code/game/machinery/firealarm.dm @@ -413,14 +413,13 @@ /obj/machinery/firealarm/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) if((buildstage == FIRE_ALARM_BUILD_NO_CIRCUIT) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS)) - return list("mode" = RCD_WALLFRAME, "delay" = 2 SECONDS, "cost" = 1) + return list("delay" = 2 SECONDS, "cost" = 1) return FALSE -/obj/machinery/firealarm/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) +/obj/machinery/firealarm/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + switch(rcd_data["[RCD_DESIGN_MODE]"]) if(RCD_WALLFRAME) - user.visible_message(span_notice("[user] fabricates a circuit and places it into [src]."), \ - span_notice("You adapt a fire alarm circuit and slot it into the assembly.")) + balloon_alert(user, "circuit installed") buildstage = FIRE_ALARM_BUILD_NO_WIRES update_appearance() return TRUE diff --git a/code/game/machinery/syndicatebeacon.dm b/code/game/machinery/syndicatebeacon.dm index 188f3b4f52ee9..9015eaacedc69 100644 --- a/code/game/machinery/syndicatebeacon.dm +++ b/code/game/machinery/syndicatebeacon.dm @@ -146,3 +146,7 @@ /obj/item/sbeacondrop/clownbomb desc = "A label on it reads: Warning: Activating this device will send a silly explosive to your location." droptype = /obj/machinery/syndicatebomb/badmin/clown + +/obj/item/sbeacondrop/horse + desc = "A label on it reads: Warning: Activating this device will send a live horse to your location." + droptype = /mob/living/basic/pony/syndicate diff --git a/code/game/objects/effects/rcd.dm b/code/game/objects/effects/rcd.dm new file mode 100644 index 0000000000000..17ea3e44a10a1 --- /dev/null +++ b/code/game/objects/effects/rcd.dm @@ -0,0 +1,43 @@ +/// How many tiles within player radius does it perform a rcd scan in +#define RCD_DESTRUCTIVE_SCAN_RANGE 10 + +/** + * Global proc that generates RCD hologram in a range. + * + * Arguments: + * * source - The atom the scans originate from + * * scan_range - The range of turfs we grab from the source + * * fade_time - The time for RCD holograms to fade + */ +/proc/rcd_scan(atom/source, scan_range = RCD_DESTRUCTIVE_SCAN_RANGE, fade_time = RCD_HOLOGRAM_FADE_TIME) + playsound(source, 'sound/items/rcdscan.ogg', 50, vary = TRUE, pressure_affected = FALSE) + + var/turf/source_turf = get_turf(source) + for(var/turf/open/surrounding_turf as anything in RANGE_TURFS(scan_range, source_turf)) + var/rcd_memory = surrounding_turf.rcd_memory + if(!rcd_memory) + continue + + var/skip_to_next_turf = FALSE + + for(var/atom/content_of_turf as anything in surrounding_turf.contents) + if (content_of_turf.density) + skip_to_next_turf = TRUE + break + + if(skip_to_next_turf) + continue + + var/hologram_icon + switch(rcd_memory) + if(RCD_MEMORY_WALL) + hologram_icon = GLOB.icon_holographic_wall + if(RCD_MEMORY_WINDOWGRILLE) + hologram_icon = GLOB.icon_holographic_window + + var/obj/effect/rcd_hologram/hologram = new(surrounding_turf) + hologram.icon = hologram_icon + hologram.makeHologram() + animate(hologram, alpha = 0, time = fade_time, easing = CIRCULAR_EASING | EASE_IN) + +#undef RCD_DESTRUCTIVE_SCAN_RANGE diff --git a/code/game/objects/effects/spawners/random/decoration.dm b/code/game/objects/effects/spawners/random/decoration.dm index 1beafc37df563..6116d22873317 100644 --- a/code/game/objects/effects/spawners/random/decoration.dm +++ b/code/game/objects/effects/spawners/random/decoration.dm @@ -93,6 +93,14 @@ loot_type_path = /obj/structure/showcase loot = list() +/obj/effect/spawner/random/decoration/microwave + name = "microwave showcase spawner" + icon_state = "showcase" + loot = list( + /obj/structure/showcase/machinery/microwave, + /obj/structure/showcase/machinery/microwave_engineering, + ) + /obj/effect/spawner/random/decoration/glowstick name = "random colored glowstick" icon_state = "glowstick" diff --git a/code/game/objects/effects/spawners/random/techstorage.dm b/code/game/objects/effects/spawners/random/techstorage.dm index c1f9304039654..4bfdeec27d180 100644 --- a/code/game/objects/effects/spawners/random/techstorage.dm +++ b/code/game/objects/effects/spawners/random/techstorage.dm @@ -34,6 +34,7 @@ /obj/item/circuitboard/machine/ore_redemption, /obj/item/circuitboard/computer/order_console/mining, /obj/item/circuitboard/machine/microwave, + /obj/item/circuitboard/machine/microwave/engineering, /obj/item/circuitboard/machine/deep_fryer, /obj/item/circuitboard/machine/griddle, /obj/item/circuitboard/machine/reagentgrinder, diff --git a/code/game/objects/effects/spiderwebs.dm b/code/game/objects/effects/spiderwebs.dm index 3bdac03396fa1..9b44d3507db2e 100644 --- a/code/game/objects/effects/spiderwebs.dm +++ b/code/game/objects/effects/spiderwebs.dm @@ -174,11 +174,11 @@ /obj/structure/spider/sticky/CanAllowThrough(atom/movable/mover, border_dir) . = ..() - if(isspider(mover)) + if(HAS_TRAIT(mover, TRAIT_WEB_SURFER)) return TRUE if(!isliving(mover)) return - if(isspider(mover.pulledby)) + if(!isnull(mover.pulledby) && HAS_TRAIT(mover.pulledby, TRAIT_WEB_SURFER)) return TRUE loc.balloon_alert(mover, "stuck in web!") return FALSE diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index a1a40dcad12df..2e4de954f37f2 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -32,6 +32,7 @@ lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' w_class = WEIGHT_CLASS_TINY + /// Cached icon that has been built for this card. Intended to be displayed in chat. Cardboards IDs and actual IDs use it. var/icon/cached_flat_icon @@ -752,10 +753,10 @@ break /obj/item/card/id/examine_more(mob/user) + . = ..() if(!user.can_read(src)) return - . = ..() . += span_notice("You examine [src] closer, and note the following...") if(registered_age) diff --git a/code/game/objects/items/circuitboards/computer_circuitboards.dm b/code/game/objects/items/circuitboards/computer_circuitboards.dm index 0338da6b4fa64..79efe62a2250d 100644 --- a/code/game/objects/items/circuitboards/computer_circuitboards.dm +++ b/code/game/objects/items/circuitboards/computer_circuitboards.dm @@ -605,8 +605,3 @@ name = "Medical Order" greyscale_colors = CIRCUIT_COLOR_SUPPLY build_path = /obj/machinery/computer/department_orders/medical - -/obj/item/circuitboard/computer/quantum_console - name = "Quantum Server Console" - greyscale_colors = CIRCUIT_COLOR_SUPPLY - build_path = /obj/machinery/computer/quantum_console diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index 4bea5b1335c5b..bd09efd02ca1c 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -1245,10 +1245,22 @@ req_components = list( /datum/stock_part/micro_laser = 1, /datum/stock_part/matter_bin = 1, + /datum/stock_part/capacitor = 1, /obj/item/stack/cable_coil = 2, /obj/item/stack/sheet/glass = 2) needs_anchored = FALSE +/obj/item/circuitboard/machine/microwave/engineering + name = "Wireless Microwave Oven" + greyscale_colors = CIRCUIT_COLOR_SERVICE + build_path = /obj/machinery/microwave/engineering + req_components = list( + /datum/stock_part/micro_laser = 1, + /datum/stock_part/matter_bin = 1, + /datum/stock_part/capacitor/tier2 = 1, + /obj/item/stack/cable_coil = 4, + /obj/item/stack/sheet/glass = 2) + /obj/item/circuitboard/machine/processor name = "Food Processor" greyscale_colors = CIRCUIT_COLOR_SERVICE @@ -1529,21 +1541,3 @@ /obj/item/stack/sheet/plasteel = 2, ) -/obj/item/circuitboard/machine/quantum_server - name = "Quantum Server" - greyscale_colors = CIRCUIT_COLOR_SUPPLY - build_path = /obj/machinery/quantum_server - req_components = list( - /datum/stock_part/servo = 2, - /datum/stock_part/scanning_module = 1, - /datum/stock_part/capacitor = 1, - ) - -/obj/item/circuitboard/machine/netpod - name = "Netpod" - greyscale_colors = CIRCUIT_COLOR_SUPPLY - build_path = /obj/machinery/netpod - req_components = list( - /datum/stock_part/servo = 1, - /datum/stock_part/matter_bin = 2, - ) diff --git a/code/game/objects/items/cosmetics.dm b/code/game/objects/items/cosmetics.dm index 662585dd273fe..04efe42e55ecc 100644 --- a/code/game/objects/items/cosmetics.dm +++ b/code/game/objects/items/cosmetics.dm @@ -1,3 +1,7 @@ +#define UPPER_LIP "Upper" +#define MIDDLE_LIP "Middle" +#define LOWER_LIP "Lower" + /obj/item/lipstick gender = PLURAL name = "red lipstick" @@ -9,6 +13,8 @@ var/open = FALSE /// Actual color of the lipstick, also gets applied to the human var/lipstick_color = COLOR_RED + /// The style of lipstick. Upper, middle, or lower lip. Default is middle. + var/style = "lipstick" /// A trait that's applied while someone has this lipstick applied, and is removed when the lipstick is removed var/lipstick_trait @@ -22,6 +28,10 @@ if(vname == NAMEOF(src, open)) update_appearance(UPDATE_ICON) +/obj/item/lipstick/examine(mob/user) + . = ..() + . += "Alt-click to change the style." + /obj/item/lipstick/update_icon_state() icon_state = "lipstick[open ? "_uncap" : null]" inhand_icon_state = "lipstick[open ? "open" : null]" @@ -35,6 +45,42 @@ colored_overlay.color = lipstick_color . += colored_overlay +/obj/item/lipstick/AltClick(mob/user) + . = ..() + if(.) + return TRUE + + if(!user.can_perform_action(src, NEED_DEXTERITY|NEED_HANDS|ALLOW_RESTING)) + return FALSE + + return display_radial_menu(user) + +/obj/item/lipstick/proc/display_radial_menu(mob/living/carbon/human/user) + var/style_options = list( + UPPER_LIP = icon('icons/hud/radial.dmi', UPPER_LIP), + MIDDLE_LIP = icon('icons/hud/radial.dmi', MIDDLE_LIP), + LOWER_LIP = icon('icons/hud/radial.dmi', LOWER_LIP), + ) + var/pick = show_radial_menu(user, src, style_options, custom_check = CALLBACK(src, PROC_REF(check_menu), user), radius = 36, require_near = TRUE) + if(!pick) + return TRUE + + switch(pick) + if(MIDDLE_LIP) + style = "lipstick" + if(LOWER_LIP) + style = "lipstick_lower" + if(UPPER_LIP) + style = "lipstick_upper" + return TRUE + +/obj/item/lipstick/proc/check_menu(mob/living/user) + if(!istype(user)) + return FALSE + if(user.incapacitated() || !user.is_holding(src)) + return FALSE + return TRUE + /obj/item/lipstick/purple name = "purple lipstick" lipstick_color = COLOR_PURPLE @@ -106,7 +152,7 @@ if(target == user) user.visible_message(span_notice("[user] does [user.p_their()] lips with \the [src]."), \ span_notice("You take a moment to apply \the [src]. Perfect!")) - target.update_lips("lipstick", lipstick_color, lipstick_trait) + target.update_lips(style, lipstick_color, lipstick_trait) return user.visible_message(span_warning("[user] begins to do [target]'s lips with \the [src]."), \ @@ -115,7 +161,7 @@ return user.visible_message(span_notice("[user] does [target]'s lips with \the [src]."), \ span_notice("You apply \the [src] on [target]'s lips.")) - target.update_lips("lipstick", lipstick_color, lipstick_trait) + target.update_lips(style, lipstick_color, lipstick_trait) //you can wipe off lipstick with paper! /obj/item/paper/attack(mob/M, mob/user) @@ -283,3 +329,7 @@ /obj/item/razor/surgery/get_surgery_tool_overlay(tray_extended) return "razor" + +#undef UPPER_LIP +#undef MIDDLE_LIP +#undef LOWER_LIP diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm index 7cba8e9bbace9..2456f80c01822 100644 --- a/code/game/objects/items/devices/radio/intercom.dm +++ b/code/game/objects/items/devices/radio/intercom.dm @@ -20,7 +20,7 @@ unscrewed = TRUE /obj/item/radio/intercom/prison - name = "prison intercom" + name = "receive-only intercom" desc = "A station intercom. It looks like it has been modified to not broadcast." /obj/item/radio/intercom/prison/Initialize(mapload, ndir, building) diff --git a/code/game/objects/items/food/_food.dm b/code/game/objects/items/food/_food.dm index 69cfdde4668bc..0fb85e67589e4 100644 --- a/code/game/objects/items/food/_food.dm +++ b/code/game/objects/items/food/_food.dm @@ -115,7 +115,7 @@ ///Set decomp_req_handle to TRUE to only make it decompose when someone picks it up. ///Requires /datum/component/germ_sensitive to detect exposure /obj/item/food/proc/make_germ_sensitive(mapload) - if(istype(src, /obj/item/food/bowled) || istype(src, /obj/item/food/canned) || !isnull(trash_type)) + if(!isnull(trash_type)) return // You don't eat the package and it protects from decomposing AddComponent(/datum/component/germ_sensitive, mapload) if(!preserved_food) diff --git a/code/game/objects/items/food/donkpocket.dm b/code/game/objects/items/food/donkpocket.dm index f2495e0ee52ff..d4b4636f15c9b 100644 --- a/code/game/objects/items/food/donkpocket.dm +++ b/code/game/objects/items/food/donkpocket.dm @@ -20,12 +20,16 @@ var/baking_time_short = 25 SECONDS /// The upper end for how long it takes to bake var/baking_time_long = 30 SECONDS + /// The reagents added when microwaved. Needed since microwaving ignores food_reagents + var/static/list/added_reagents = list(/datum/reagent/medicine/omnizine = 6) + /// The reagents that most child types add when microwaved. Needed because you can't override static lists. + var/static/list/child_added_reagents = list(/datum/reagent/medicine/omnizine = 2) /obj/item/food/donkpocket/make_bakeable() - AddComponent(/datum/component/bakeable, warm_type, rand(baking_time_short, baking_time_long), TRUE, TRUE) + AddComponent(/datum/component/bakeable, warm_type, rand(baking_time_short, baking_time_long), TRUE, TRUE, added_reagents) /obj/item/food/donkpocket/make_microwaveable() - AddElement(/datum/element/microwavable, warm_type) + AddElement(/datum/element/microwavable, warm_type, added_reagents) /obj/item/food/donkpocket/warm name = "warm Donk-pocket" @@ -69,6 +73,12 @@ foodtypes = GRAIN warm_type = /obj/item/food/donkpocket/warm/spicy +/obj/item/food/donkpocket/spicy/make_bakeable() + AddComponent(/datum/component/bakeable, warm_type, rand(baking_time_short, baking_time_long), TRUE, TRUE, child_added_reagents) + +/obj/item/food/donkpocket/spicy/make_microwaveable() + AddElement(/datum/element/microwavable, warm_type, child_added_reagents) + /obj/item/food/donkpocket/warm/spicy name = "warm Spicy-pocket" desc = "The classic snack food, now maybe a bit too spicy." @@ -95,6 +105,12 @@ foodtypes = GRAIN warm_type = /obj/item/food/donkpocket/warm/teriyaki +/obj/item/food/donkpocket/teriyaki/make_bakeable() + AddComponent(/datum/component/bakeable, warm_type, rand(baking_time_short, baking_time_long), TRUE, TRUE, child_added_reagents) + +/obj/item/food/donkpocket/teriyaki/make_microwaveable() + AddElement(/datum/element/microwavable, warm_type, child_added_reagents) + /obj/item/food/donkpocket/warm/teriyaki name = "warm Teriyaki-pocket" desc = "An east-asian take on the classic stationside snack, now steamy and warm." @@ -121,6 +137,12 @@ foodtypes = GRAIN warm_type = /obj/item/food/donkpocket/warm/pizza +/obj/item/food/donkpocket/pizza/make_bakeable() + AddComponent(/datum/component/bakeable, warm_type, rand(baking_time_short, baking_time_long), TRUE, TRUE, child_added_reagents) + +/obj/item/food/donkpocket/pizza/make_microwaveable() + AddElement(/datum/element/microwavable, warm_type, child_added_reagents) + /obj/item/food/donkpocket/warm/pizza name = "warm Pizza-pocket" desc = "Delicious, cheesy, and even better when hot." @@ -146,6 +168,16 @@ foodtypes = GRAIN warm_type = /obj/item/food/donkpocket/warm/honk crafting_complexity = FOOD_COMPLEXITY_3 + var/static/list/honk_added_reagents = list( + /datum/reagent/medicine/omnizine = 2, + /datum/reagent/consumable/laughter = 6, + ) + +/obj/item/food/donkpocket/honk/make_bakeable() + AddComponent(/datum/component/bakeable, warm_type, rand(baking_time_short, baking_time_long), TRUE, TRUE, honk_added_reagents) + +/obj/item/food/donkpocket/honk/make_microwaveable() + AddElement(/datum/element/microwavable, warm_type, honk_added_reagents) /obj/item/food/donkpocket/warm/honk name = "warm Honk-pocket" @@ -173,6 +205,12 @@ foodtypes = GRAIN warm_type = /obj/item/food/donkpocket/warm/berry +/obj/item/food/donkpocket/berry/make_bakeable() + AddComponent(/datum/component/bakeable, warm_type, rand(baking_time_short, baking_time_long), TRUE, TRUE, child_added_reagents) + +/obj/item/food/donkpocket/berry/make_microwaveable() + AddElement(/datum/element/microwavable, warm_type, child_added_reagents) + /obj/item/food/donkpocket/warm/berry name = "warm Berry-pocket" desc = "A relentlessly sweet donk-pocket, now warm and delicious." @@ -198,6 +236,16 @@ foodtypes = GRAIN warm_type = /obj/item/food/donkpocket/warm/gondola + var/static/list/gondola_added_reagents = list( + /datum/reagent/medicine/omnizine = 2, + /datum/reagent/gondola_mutation_toxin = 5, + ) + +/obj/item/food/donkpocket/gondola/make_bakeable() + AddComponent(/datum/component/bakeable, warm_type, rand(baking_time_short, baking_time_long), TRUE, TRUE, gondola_added_reagents) + +/obj/item/food/donkpocket/gondola/make_microwaveable() + AddElement(/datum/element/microwavable, warm_type, gondola_added_reagents) /obj/item/food/donkpocket/warm/gondola name = "warm Gondola-pocket" diff --git a/code/game/objects/items/food/egg.dm b/code/game/objects/items/food/egg.dm index 555b16e24b69e..60bbb58e3ac87 100644 --- a/code/game/objects/items/food/egg.dm +++ b/code/game/objects/items/food/egg.dm @@ -28,6 +28,7 @@ GLOBAL_VAR_INIT(chicks_from_eggs, 0) ant_attracting = FALSE decomp_type = /obj/item/food/egg/rotten decomp_req_handle = TRUE //so laid eggs can actually become chickens + /// How likely is it that a chicken will come out of here if we throw it? var/chick_throw_prob = 13 /obj/item/food/egg/make_bakeable() @@ -68,24 +69,25 @@ GLOBAL_VAR_INIT(chicks_from_eggs, 0) var/turf/hit_turf = get_turf(hit_atom) new /obj/effect/decal/cleanable/food/egg_smudge(hit_turf) - //Chicken code uses this MAX_CHICKENS variable, so I figured that I'd use it again here. Even this check and the check in chicken code both use the MAX_CHICKENS variable, they use independent counter variables and thus are independent of each other. - if(GLOB.chicks_from_eggs < MAX_CHICKENS) //Roughly a 1/8 (12.5%) chance to make a chick, as in Minecraft, with a 1/256 (~0.39%) chance to make four chicks instead. - var/chance = rand(0, 255) - switch(chance) - if(0 to 30) - new /mob/living/basic/chick(hit_turf) - GLOB.chicks_from_eggs++ - visible_message(span_notice("A chick comes out of the cracked egg!")) - if(31) - var/spawned_chickens = min(4, MAX_CHICKENS - GLOB.chicks_from_eggs) // We don't want to go over the limit - visible_message(span_notice("[spawned_chickens] chicks come out of the egg! Jackpot!")) - for(var/i in 1 to spawned_chickens) - new /mob/living/basic/chick(hit_turf) - GLOB.chicks_from_eggs++ - + if (prob(chick_throw_prob)) + spawn_impact_chick(hit_turf) reagents.expose(hit_atom, TOUCH) qdel(src) +/// Spawn a baby chicken from throwing an egg +/obj/item/food/egg/proc/spawn_impact_chick(turf/spawn_turf) + var/chickens_remaining = MAX_CHICKENS - GLOB.chicks_from_eggs + if (chickens_remaining < 1) + return + var/spawned_chickens = prob(97) ? 1 : min(4, chickens_remaining) // We don't want to go over the limit + if (spawned_chickens > 1) // Chicken jackpot! + visible_message(span_notice("[spawned_chickens] chicks come out of the egg! Jackpot!")) + else + visible_message(span_notice("A chick comes out of the cracked egg!")) + for(var/i in 1 to spawned_chickens) + new /mob/living/basic/chick(spawn_turf) + GLOB.chicks_from_eggs++ + /obj/item/food/egg/attackby(obj/item/item, mob/user, params) if(istype(item, /obj/item/toy/crayon)) var/obj/item/toy/crayon/crayon = item diff --git a/code/game/objects/items/food/misc.dm b/code/game/objects/items/food/misc.dm index 115d1fd5968b2..8d7e75edcedef 100644 --- a/code/game/objects/items/food/misc.dm +++ b/code/game/objects/items/food/misc.dm @@ -3,6 +3,7 @@ /obj/item/food/watermelonslice name = "watermelon slice" desc = "A slice of watery goodness." + icon = 'icons/obj/service/hydroponics/harvest.dmi' icon_state = "watermelonslice" food_reagents = list( /datum/reagent/water = 1, @@ -15,6 +16,22 @@ juice_typepath = /datum/reagent/consumable/watermelonjuice w_class = WEIGHT_CLASS_SMALL +/obj/item/food/appleslice + name = "apple slice" + desc = "The perfect after-school snack." + icon = 'icons/obj/service/hydroponics/harvest.dmi' + icon_state = "appleslice" + food_reagents = list( + /datum/reagent/consumable/applejuice = 1, + /datum/reagent/consumable/nutriment/vitamin = 0.2, + /datum/reagent/consumable/nutriment = 1, + ) + tastes = list("apple" = 1) + foodtypes = FRUIT + food_flags = FOOD_FINGER_FOOD + juice_typepath = /datum/reagent/consumable/applejuice + w_class = WEIGHT_CLASS_SMALL + /obj/item/food/hugemushroomslice name = "huge mushroom slice" desc = "A slice from a huge mushroom." diff --git a/code/game/objects/items/food/monkeycube.dm b/code/game/objects/items/food/monkeycube.dm index 0013fbcdadddb..ffc9b63c62f09 100644 --- a/code/game/objects/items/food/monkeycube.dm +++ b/code/game/objects/items/food/monkeycube.dm @@ -62,7 +62,7 @@ /datum/reagent/medicine/strange_reagent = 5, ) tastes = list("the jungle" = 1, "bananas" = 1, "jimmies" = 1) - spawned_mob = /mob/living/simple_animal/hostile/gorilla + spawned_mob = /mob/living/basic/gorilla /obj/item/food/monkeycube/chicken name = "chicken cube" diff --git a/code/game/objects/items/food/moth.dm b/code/game/objects/items/food/moth.dm index 3bb3c8738d41f..41c2d8ec3a166 100644 --- a/code/game/objects/items/food/moth.dm +++ b/code/game/objects/items/food/moth.dm @@ -522,7 +522,7 @@ desc = "A salad with added cotton and a basic dressing. Presumably either moths are around, or the South's risen again." icon = 'icons/obj/food/moth.dmi' icon_state = "cotton_salad" - food_reagents = list(, + food_reagents = list( /datum/reagent/consumable/nutriment = 8, /datum/reagent/consumable/nutriment/vitamin = 14, ) diff --git a/code/game/objects/items/food/packaged.dm b/code/game/objects/items/food/packaged.dm index edcc0bd09ed61..1dabec57cdd1a 100644 --- a/code/game/objects/items/food/packaged.dm +++ b/code/game/objects/items/food/packaged.dm @@ -16,6 +16,9 @@ w_class = WEIGHT_CLASS_SMALL preserved_food = TRUE +/obj/item/food/canned/make_germ_sensitive(mapload) + return // It's in a can + /obj/item/food/canned/proc/open_can(mob/user) to_chat(user, span_notice("You pull back the tab of \the [src].")) playsound(user.loc, 'sound/items/foodcanopen.ogg', 50) @@ -209,11 +212,14 @@ /// What type of ready-donk are we warmed into? var/warm_type = /obj/item/food/ready_donk/warm + /// What reagents should be added when this item is warmed? + var/static/list/added_reagents = list(/datum/reagent/medicine/omnizine = 3) + /obj/item/food/ready_donk/make_bakeable() - AddComponent(/datum/component/bakeable, warm_type, rand(15 SECONDS, 20 SECONDS), TRUE, TRUE) + AddComponent(/datum/component/bakeable, warm_type, rand(15 SECONDS, 20 SECONDS), TRUE, TRUE, added_reagents) /obj/item/food/ready_donk/make_microwaveable() - AddElement(/datum/element/microwavable, warm_type) + AddElement(/datum/element/microwavable, warm_type, added_reagents) /obj/item/food/ready_donk/examine_more(mob/user) . = ..() diff --git a/code/game/objects/items/food/soup.dm b/code/game/objects/items/food/soup.dm index 08eb86ebaa0fe..e1482b17a817b 100644 --- a/code/game/objects/items/food/soup.dm +++ b/code/game/objects/items/food/soup.dm @@ -7,6 +7,9 @@ eatverbs = list("slurp", "sip", "inhale", "drink") venue_value = FOOD_PRICE_CHEAP +/obj/item/food/bowled/make_germ_sensitive(mapload) + return // It's in a bowl + /obj/item/food/bowled/wish name = "wish soup" desc = "I wish this was soup." diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index 5dfb27e77eaad..786824e7af273 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -266,6 +266,12 @@ span_notice("You slap [slapped] in the face!"), span_hear("You hear a slap."), ) + else if(user.zone_selected == BODY_ZONE_L_ARM || user.zone_selected == BODY_ZONE_R_ARM) + user.visible_message( + span_danger("[user] gives [slapped] a slap on the wrist!"), + span_notice("You give [slapped] a slap on the wrist!"), + span_hear("You hear a slap."), + ) else user.visible_message( span_danger("[user] slaps [slapped]!"), diff --git a/code/game/objects/items/puzzle_pieces.dm b/code/game/objects/items/puzzle_pieces.dm index fc80355a7cc59..9bf33e36f2fe9 100644 --- a/code/game/objects/items/puzzle_pieces.dm +++ b/code/game/objects/items/puzzle_pieces.dm @@ -61,6 +61,19 @@ fire = 100 acid = 100 +/obj/machinery/door/puzzle/Initialize(mapload) + . = ..() + RegisterSignal(SSdcs, COMSIG_GLOB_PUZZLE_COMPLETED, PROC_REF(try_signal)) + +/obj/machinery/door/puzzle/Destroy(force) + . = ..() + UnregisterSignal(SSdcs, COMSIG_GLOB_PUZZLE_COMPLETED) + +/obj/machinery/door/puzzle/proc/try_signal(datum/source, try_id) + SIGNAL_HANDLER + + INVOKE_ASYNC(src, PROC_REF(try_puzzle_open), try_id) + /obj/machinery/door/puzzle/Bumped(atom/movable/AM) return !density && ..() @@ -111,15 +124,6 @@ /obj/machinery/door/puzzle/light desc = "This door only opens when a linked mechanism is powered. It looks virtually indestructible." -/obj/machinery/door/puzzle/light/Initialize(mapload) - . = ..() - RegisterSignal(SSdcs, COMSIG_GLOB_LIGHT_MECHANISM_COMPLETED, PROC_REF(check_mechanism)) - -/obj/machinery/door/puzzle/light/proc/check_mechanism(datum/source, try_id) - SIGNAL_HANDLER - - INVOKE_ASYNC(src, PROC_REF(try_puzzle_open), try_id) - //************************* //***Box Pushing Puzzles*** //************************* @@ -268,5 +272,5 @@ return visible_message(span_boldnotice("[src] becomes fully charged!")) powered = TRUE - SEND_GLOBAL_SIGNAL(COMSIG_GLOB_LIGHT_MECHANISM_COMPLETED, puzzle_id) + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PUZZLE_COMPLETED, puzzle_id) playsound(src, 'sound/machines/synth_yes.ogg', 100, TRUE) diff --git a/code/game/objects/items/rcd/RCD.dm b/code/game/objects/items/rcd/RCD.dm index 27c04aa7479ce..e2a391a22785b 100644 --- a/code/game/objects/items/rcd/RCD.dm +++ b/code/game/objects/items/rcd/RCD.dm @@ -1,27 +1,9 @@ -#define RCD_DESTRUCTIVE_SCAN_RANGE 10 -#define RCD_HOLOGRAM_FADE_TIME (15 SECONDS) -#define RCD_DESTRUCTIVE_SCAN_COOLDOWN (RCD_HOLOGRAM_FADE_TIME + 1 SECONDS) - -///each define maps to a variable used for construction in the RCD -#define CONSTRUCTION_MODE "construction_mode" -#define WINDOW_TYPE "window_type" -#define COMPUTER_DIR "computer_dir" -#define WALLFRAME_TYPE "wallframe_type" -#define FURNISH_TYPE "furnish_type" -#define AIRLOCK_TYPE "airlock_type" - -///flags to be sent to UI -#define TITLE "title" -#define ICON "icon" - -///flags for creating icons shared by an entire category -#define CATEGORY_ICON_STATE "category_icon_state" -#define CATEGORY_ICON_SUFFIX "category_icon_suffix" -#define TITLE_ICON "ICON=TITLE" - -///multiplier applied on construction & deconstruction time when building multiple structures +/// Multiplier applied on construction & deconstruction time when building multiple structures #define FREQUENT_USE_DEBUFF_MULTIPLIER 3 +/// Delay before another rcd scan can be performed in the UI +#define RCD_DESTRUCTIVE_SCAN_COOLDOWN (RCD_HOLOGRAM_FADE_TIME + 1 SECONDS) + //RAPID CONSTRUCTION DEVICE /obj/item/construction/rcd @@ -38,123 +20,23 @@ has_ammobar = TRUE actions_types = list(/datum/action/item_action/rcd_scan) - ///all stuff used by RCD for construction - var/static/list/root_categories = list( - //1ST ROOT CATEGORY - "Construction" = list( //Stuff you use to make & decorate areas - //Walls & Windows - "Structures" = list( - list(CONSTRUCTION_MODE = RCD_FLOORWALL, ICON = "wallfloor", TITLE = "Wall/Floor"), - list(CONSTRUCTION_MODE = RCD_WINDOWGRILLE, WINDOW_TYPE = /obj/structure/window, ICON = "windowsize", TITLE = "Directional Window"), - list(CONSTRUCTION_MODE = RCD_WINDOWGRILLE, WINDOW_TYPE = /obj/structure/window/reinforced, ICON = "windowtype", TITLE = "Directional Reinforced Window"), - list(CONSTRUCTION_MODE = RCD_WINDOWGRILLE, WINDOW_TYPE = /obj/structure/window/fulltile, ICON = "window0", TITLE = "Full Tile Window"), - list(CONSTRUCTION_MODE = RCD_WINDOWGRILLE, WINDOW_TYPE = /obj/structure/window/reinforced/fulltile, ICON = "rwindow0", TITLE = "Full Tile Reinforced Window"), - list(CONSTRUCTION_MODE = RCD_CATWALK, ICON = "catwalk-0", TITLE = "Catwalk"), - list(CONSTRUCTION_MODE = RCD_REFLECTOR, ICON = "reflector_base", TITLE = "Reflector"), - list(CONSTRUCTION_MODE = RCD_GIRDER, ICON = "girder", TITLE = "Girder"), - ), - - //Computers & Machine Frames - "Machines" = list( - list(CONSTRUCTION_MODE = RCD_MACHINE, ICON = "box_1", TITLE = "Machine Frame"), - list(CONSTRUCTION_MODE = RCD_COMPUTER, COMPUTER_DIR = NORTH, ICON = "cnorth", TITLE = "Computer North"), - list(CONSTRUCTION_MODE = RCD_COMPUTER, COMPUTER_DIR = SOUTH, ICON = "csouth", TITLE = "Computer South"), - list(CONSTRUCTION_MODE = RCD_COMPUTER, COMPUTER_DIR = EAST, ICON = "ceast", TITLE = "Computer East"), - list(CONSTRUCTION_MODE = RCD_COMPUTER, COMPUTER_DIR = WEST, ICON = "cwest", TITLE = "Computer West"), - list(CONSTRUCTION_MODE = RCD_FLOODLIGHT, ICON = "floodlight_c1", TITLE = "FloodLight Frame"), - list(CONSTRUCTION_MODE = RCD_WALLFRAME, WALLFRAME_TYPE = /obj/item/wallframe/apc, ICON = "apc", TITLE = "APC WallFrame"), - list(CONSTRUCTION_MODE = RCD_WALLFRAME, WALLFRAME_TYPE = /obj/item/wallframe/airalarm, ICON = "alarm_bitem", TITLE = "AirAlarm WallFrame"), - list(CONSTRUCTION_MODE = RCD_WALLFRAME, WALLFRAME_TYPE = /obj/item/wallframe/firealarm, ICON = "fire_bitem", TITLE = "FireAlarm WallFrame"), - ), - - //Interior Design[construction_mode = RCD_FURNISHING is implied] - "Furniture" = list( - list(FURNISH_TYPE = /obj/structure/chair, ICON = "chair", TITLE = "Chair"), - list(FURNISH_TYPE = /obj/structure/chair/stool, ICON = "stool", TITLE = "Stool"), - list(FURNISH_TYPE = /obj/structure/chair/stool/bar, ICON = "bar", TITLE = "Bar Stool"), - list(FURNISH_TYPE = /obj/structure/table, ICON = "table",TITLE = "Table"), - list(FURNISH_TYPE = /obj/structure/table/glass, ICON = "glass_table", TITLE = "Glass Table"), - list(FURNISH_TYPE = /obj/structure/rack, ICON = "rack", TITLE = "Rack"), - list(FURNISH_TYPE = /obj/structure/bed, ICON = "bed", TITLE = "Bed"), - ), - ), - - //2ND ROOT CATEGORY[construction_mode = RCD_AIRLOCK is implied,"icon=closed"] - "Airlocks" = list( //used to seal/close areas - //Window Doors[airlock_glass = TRUE is implied] - "Windoors" = list( - list(AIRLOCK_TYPE = /obj/machinery/door/window, ICON = "windoor", TITLE = "Windoor"), - list(AIRLOCK_TYPE = /obj/machinery/door/window/brigdoor, ICON = "secure_windoor", TITLE = "Secure Windoor"), - ), - - //Glass Airlocks[airlock_glass = TRUE is implied,do fill_closed overlay] - "Glass Airlocks" = list( - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/glass, TITLE = "Standard", CATEGORY_ICON_STATE = TITLE_ICON, CATEGORY_ICON_SUFFIX = "Glass"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/public/glass, TITLE = "Public"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/engineering/glass, TITLE = "Engineering"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/atmos/glass, TITLE = "Atmospherics"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/security/glass, TITLE = "Security"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/command/glass, TITLE = "Command"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/medical/glass, TITLE = "Medical"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/research/glass, TITLE = "Research"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/virology/glass, TITLE = "Virology"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/mining/glass, TITLE = "Mining"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/maintenance/glass, TITLE = "Maintenance"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/external/glass, TITLE = "External"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/maintenance/external/glass, TITLE = "External Maintenance"), - ), - - //Solid Airlocks[airlock_glass = FALSE is implied,no fill_closed overlay] - "Solid Airlocks" = list( - list(AIRLOCK_TYPE = /obj/machinery/door/airlock, TITLE = "Standard", CATEGORY_ICON_STATE = TITLE_ICON), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/public, TITLE = "Public"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/engineering, TITLE = "Engineering"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/atmos, TITLE = "Atmospherics"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/security, TITLE = "Security"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/command, TITLE = "Command"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/medical, TITLE = "Medical"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/research, TITLE = "Research"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/freezer, TITLE = "Freezer"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/virology, TITLE = "Virology"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/mining, TITLE = "Mining"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/maintenance, TITLE = "Maintenance"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/external, TITLE = "External"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/maintenance/external, TITLE = "External Maintenance"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/hatch, TITLE = "Airtight Hatch"), - list(AIRLOCK_TYPE = /obj/machinery/door/airlock/maintenance_hatch, TITLE = "Maintenance Hatch"), - ), - ), - - //3RD CATEGORY Airlock access,empty list cause airlock_electronics UI will be displayed when this tab is selected - "Airlock Access" = list() - ) - - /// name of currently selected design - var/design_title = "Wall/Floor" - /// category of currently selected design - var/design_category = "Structures" /// main category of currently selected design[Structures, Airlocks, Airlock Access] - var/root_category = "Construction" - /// owner of this rcd. It can either be an construction console or an player - var/owner + var/root_category + /// category of currently selected design + var/design_category + /// name of currently selected design + var/design_title /// type of structure being built, used to decide what variables are used to build what structure - var/mode = RCD_FLOORWALL + var/mode /// temporary holder of mode, used to restore mode original value after rcd deconstruction act - var/construction_mode = RCD_FLOORWALL + var/construction_mode + /// The path of the structure the rcd is currently creating + var/atom/movable/rcd_design_path + + /// owner of this rcd. It can either be an construction console or an player + var/owner /// used by arcd, can this rcd work from a range var/ranged = FALSE - /// direction which computer frames are constructed in - var/computer_dir = NORTH - /// type of airlock/windoor being built[mode = RCD_AIRLOCK] - var/airlock_type = /obj/machinery/door/airlock - /// are we building glass/solid airlocks - var/airlock_glass = FALSE - /// are we building normal/reinforced directional/fulltile windows - var/obj/structure/window/window_type = /obj/structure/window/fulltile - /// type of wallmount airalarm,firealarm,apc we are trying to build - var/obj/item/wallframe/wallframe_type = /obj/item/wallframe/apc - /// type of furniture tables,chairs etc we are trying to build - var/furnish_type = /obj/structure/chair /// delay multiplier for all construction types var/delay_mod = 1 /// variable for R walls to deconstruct them @@ -167,19 +49,35 @@ ///number of active rcd effects in use e.g. when building multiple walls at once this value increases var/current_active_effects = 0 -GLOBAL_VAR_INIT(icon_holographic_wall, init_holographic_wall()) -GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) +/obj/effect/rcd_hologram + name = "hologram" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/effect/rcd_hologram/Initialize(mapload) + . = ..() + QDEL_IN(src, RCD_HOLOGRAM_FADE_TIME) + +/obj/item/construction/rcd/Initialize(mapload) + . = ..() + airlock_electronics = new(src) + airlock_electronics.name = "Access Control" + airlock_electronics.holder = src -/proc/init_holographic_wall() - return icon('icons/turf/walls/wall.dmi', "wall-0") + root_category = GLOB.rcd_designs[1] + design_category = GLOB.rcd_designs[root_category][1] + var/list/design = GLOB.rcd_designs[root_category][design_category][1] -/proc/init_holographic_window() - var/icon/grille_icon = icon('icons/obj/structures.dmi', "grille") - var/icon/window_icon = icon('icons/obj/smooth_structures/window.dmi', "window-0") + rcd_design_path = design["[RCD_DESIGN_PATH]"] + design_title = initial(rcd_design_path.name) + mode = design["[RCD_DESIGN_MODE]"] + construction_mode = mode - grille_icon.Blend(window_icon, ICON_OVERLAY) + GLOB.rcd_list += src - return grille_icon +/obj/item/construction/rcd/Destroy() + QDEL_NULL(airlock_electronics) + GLOB.rcd_list -= src + . = ..() /obj/item/construction/rcd/ui_action_click(mob/user, actiontype) if (!COOLDOWN_FINISHED(src, destructive_scan_cooldown)) @@ -189,57 +87,6 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) COOLDOWN_START(src, destructive_scan_cooldown, RCD_DESTRUCTIVE_SCAN_COOLDOWN) rcd_scan(src) -/** - * Global proc that generates RCD hologram in a range. - * - * Arguments: - * * source - The atom the scans originate from - * * scan_range - The range of turfs we grab from the source - * * fade_time - The time for RCD holograms to fade - */ -/proc/rcd_scan(atom/source, scan_range = RCD_DESTRUCTIVE_SCAN_RANGE, fade_time = RCD_HOLOGRAM_FADE_TIME) - playsound(source, 'sound/items/rcdscan.ogg', 50, vary = TRUE, pressure_affected = FALSE) - - var/turf/source_turf = get_turf(source) - for(var/turf/open/surrounding_turf in RANGE_TURFS(scan_range, source_turf)) - var/rcd_memory = surrounding_turf.rcd_memory - if(!rcd_memory) - continue - - var/skip_to_next_turf = FALSE - - for(var/atom/content_of_turf as anything in surrounding_turf.contents) - if (content_of_turf.density) - skip_to_next_turf = TRUE - break - - if(skip_to_next_turf) - continue - - var/hologram_icon - switch(rcd_memory) - if(RCD_MEMORY_WALL) - hologram_icon = GLOB.icon_holographic_wall - if(RCD_MEMORY_WINDOWGRILLE) - hologram_icon = GLOB.icon_holographic_window - - var/obj/effect/rcd_hologram/hologram = new(surrounding_turf) - hologram.icon = hologram_icon - hologram.makeHologram() - animate(hologram, alpha = 0, time = fade_time, easing = CIRCULAR_EASING | EASE_IN) - -/obj/effect/rcd_hologram - name = "hologram" - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/effect/rcd_hologram/Initialize(mapload) - . = ..() - QDEL_IN(src, RCD_HOLOGRAM_FADE_TIME) - -#undef RCD_DESTRUCTIVE_SCAN_COOLDOWN -#undef RCD_DESTRUCTIVE_SCAN_RANGE -#undef RCD_HOLOGRAM_FADE_TIME - /obj/item/construction/rcd/suicide_act(mob/living/user) var/turf/T = get_turf(user) @@ -247,15 +94,15 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) user.visible_message(span_suicide("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!")) return BRUTELOSS - mode = RCD_FLOORWALL + mode = RCD_TURF user.visible_message(span_suicide("[user] sets the RCD to 'Wall' and points it down [user.p_their()] throat! It looks like [user.p_theyre()] trying to commit suicide!")) if(checkResource(16, user)) // It takes 16 resources to construct a wall - var/success = T.rcd_act(user, src, RCD_FLOORWALL) + var/success = T.rcd_act(user, src, list("[RCD_DESIGN_MODE]" = RCD_TURF, "[RCD_DESIGN_PATH]" = /turf/open/floor/plating/rcd)) T = get_turf(user) // If the RCD placed a floor instead of a wall, having a wall without plating under it is cursed // There isn't an easy programmatical way to check if rcd_act will place a floor or a wall, so just repeat using it for free if(success && isopenturf(T)) - T.rcd_act(user, src, RCD_FLOORWALL) + T.rcd_act(user, src, list("[RCD_DESIGN_MODE]" = RCD_TURF, "[RCD_DESIGN_PATH]" = /turf/open/floor/plating/rcd)) useResource(16, user) activate() playsound(loc, 'sound/machines/click.ogg', 50, 1) @@ -274,15 +121,18 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) * * [mob][user]- the user */ /obj/item/construction/rcd/proc/can_place(atom/target, list/rcd_results, mob/user) + var/rcd_mode = rcd_results["[RCD_DESIGN_MODE]"] + var/atom/movable/rcd_structure = rcd_results["[RCD_DESIGN_PATH]"] /** *For anything that does not go an a wall we have to make sure that turf is clear for us to put the structure on it *If we are just trying to destory something then this check is not nessassary - *RCD_WALLFRAME is also returned as the mode when upgrading apc, airalarm, firealarm using simple circuits upgrade + *RCD_WALLFRAME is also returned as the rcd_mode when upgrading apc, airalarm, firealarm using simple circuits upgrade */ - if(rcd_results["mode"] != RCD_WALLFRAME && rcd_results["mode"] != RCD_DECONSTRUCT) + if(rcd_mode != RCD_WALLFRAME && rcd_mode != RCD_DECONSTRUCT) var/turf/target_turf = get_turf(target) //if we are trying to build a window we check for specific edge cases - if(rcd_results["mode"] == RCD_WINDOWGRILLE) + if(rcd_mode == RCD_WINDOWGRILLE) + var/obj/structure/window/window_type = rcd_structure var/is_full_tile = initial(window_type.fulltile) var/list/structures_to_ignore @@ -304,7 +154,7 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) * if we are trying to create plating on turf which is not a proper floor then dont check for objects on top of the turf just allow that turf to be converted into plating. e.g. create plating beneath a player or underneath a machine frame/any dense object * if we are trying to finish a wall girder then let it finish then make sure no one/nothing is stuck in the girder */ - else if(rcd_results["mode"] == RCD_FLOORWALL && (!istype(target_turf, /turf/open/floor) || istype(target, /obj/structure/girder))) + else if(rcd_mode == RCD_TURF && rcd_structure == /turf/open/floor/plating/rcd && (!istype(target_turf, /turf/open/floor) || istype(target, /obj/structure/girder))) //if a player builds a wallgirder on top of himself manually with iron sheets he can't finish the wall if he is still on the girder. Exclude the girder itself when checking for other dense objects on the turf if(istype(target, /obj/structure/girder) && target_turf.is_blocked_turf(exclude_mobs = FALSE, source_atom = null, ignore_atoms = list(target))) playsound(loc, 'sound/machines/click.ogg', 50, TRUE) @@ -315,27 +165,29 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) else //structures which are small enough to fit on turfs containing directional windows. var/static/list/small_structures = list( - RCD_AIRLOCK, - RCD_COMPUTER, - RCD_FLOODLIGHT, - RCD_FURNISHING, - RCD_MACHINE, - RCD_REFLECTOR, - RCD_WINDOWGRILLE, + /obj/machinery/door, + /obj/structure/frame/computer/rcd, + /obj/structure/floodlight_frame/completed, + /obj/structure/chair, + /obj/structure/table, + /obj/structure/rack, + /obj/structure/bed, + /obj/structure/frame/machine/secured, + /obj/structure/reflector, ) //edge cases for what we can/can't ignore var/ignore_mobs = FALSE var/list/ignored_types - if(rcd_results["mode"] in small_structures) + if(rcd_mode == RCD_WINDOWGRILLE || is_path_in_list(rcd_structure, small_structures)) ignored_types = list(/obj/structure/window) //if we are trying to create grills/windoors we can go ahead and further ignore other windoors on the turf - if(rcd_results["mode"] == RCD_WINDOWGRILLE || (rcd_results["mode"] == RCD_AIRLOCK && ispath(airlock_type, /obj/machinery/door/window))) + if(rcd_mode == RCD_WINDOWGRILLE || (rcd_mode == RCD_AIRLOCK && ispath(rcd_structure, /obj/machinery/door/window))) //only ignore mobs if we are trying to create windoors and not grills. We dont want to drop a grill on top of somebody - ignore_mobs = rcd_results["mode"] == RCD_AIRLOCK + ignore_mobs = rcd_mode == RCD_AIRLOCK ignored_types += /obj/machinery/door/window //if we are trying to create full airlock doors then we do the regular checks and make sure we have the full space for them. i.e. dont ignore anything dense on the turf - else if(rcd_results["mode"] == RCD_AIRLOCK) + else if(rcd_mode == RCD_AIRLOCK) ignored_types = list() //check if the structure can fit on this turf @@ -358,6 +210,8 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) var/list/rcd_results = target.rcd_vals(user, src) if(!rcd_results) return FALSE + rcd_results["[RCD_DESIGN_MODE]"] = mode + rcd_results["[RCD_DESIGN_PATH]"] = rcd_design_path var/delay = rcd_results["delay"] * delay_mod if ( @@ -381,7 +235,7 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) * * rcd_results- list of params which contains the cost & build mode to create the structure */ /obj/item/construction/rcd/proc/_rcd_create_effect(atom/target, mob/user, delay, list/rcd_results) - var/obj/effect/constructing_effect/rcd_effect = new(get_turf(target), delay, src.mode, upgrade) + var/obj/effect/constructing_effect/rcd_effect = new(get_turf(target), delay, rcd_results["[RCD_DESIGN_MODE]"], upgrade) //resource & structure placement sanity checks before & after delay along with beam effects if(!checkResource(rcd_results["cost"], user) || !can_place(target, rcd_results, user)) @@ -389,7 +243,7 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) return FALSE var/beam if(ranged) - beam = user.Beam(target,icon_state="rped_upgrade", time = delay) + beam = user.Beam(target, icon_state = "rped_upgrade", time = delay) if(!do_after(user, delay, target = target)) qdel(rcd_effect) if(!isnull(beam)) @@ -405,25 +259,13 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) qdel(rcd_effect) return FALSE activate() - if(!target.rcd_act(user, src, rcd_results["mode"])) + if(!target.rcd_act(user, src, rcd_results)) qdel(rcd_effect) return FALSE playsound(loc, 'sound/machines/click.ogg', 50, TRUE) rcd_effect.end_animation() return TRUE -/obj/item/construction/rcd/Initialize(mapload) - . = ..() - airlock_electronics = new(src) - airlock_electronics.name = "Access Control" - airlock_electronics.holder = src - GLOB.rcd_list += src - -/obj/item/construction/rcd/Destroy() - QDEL_NULL(airlock_electronics) - GLOB.rcd_list -= src - . = ..() - /obj/item/construction/rcd/ui_assets(mob/user) return list( get_asset_datum(/datum/asset/spritesheet/rcd), @@ -439,27 +281,20 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) ui.open() /obj/item/construction/rcd/ui_static_data(mob/user) - return airlock_electronics.ui_static_data(user) - -/obj/item/construction/rcd/ui_data(mob/user) var/list/data = ..() - //main categories - data["selected_root"] = root_category + var/list/electronics_data = airlock_electronics.ui_static_data(user) + for(var/key in electronics_data) + data[key] = electronics_data[key] + data["root_categories"] = list() - for(var/category in root_categories) + for(var/category in GLOB.rcd_designs) data["root_categories"] += category - //create the category list - data["selected_category"] = design_category - data["selected_design"] = design_title data["categories"] = list() - - var/category_icon_state - var/category_icon_suffix - for(var/sub_category as anything in root_categories[root_category]) - var/list/target_category = root_categories[root_category][sub_category] - if(target_category.len == 0) + for(var/sub_category as anything in GLOB.rcd_designs[root_category]) + var/list/target_category = GLOB.rcd_designs[root_category][sub_category] + if(!length(target_category)) continue //skip category if upgrades were not installed for these @@ -467,35 +302,26 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) continue if(sub_category == "Furniture" && !(upgrade & RCD_UPGRADE_FURNISHING)) continue - category_icon_state = "" - category_icon_suffix = "" var/list/designs = list() //initialize all designs under this category - for(var/i in 1 to target_category.len) - var/list/design = target_category[i] - - //check for special icon flags - if(design[CATEGORY_ICON_STATE] != null) - category_icon_state = design[CATEGORY_ICON_STATE] - if(design[CATEGORY_ICON_SUFFIX] != null) - category_icon_suffix = design[CATEGORY_ICON_SUFFIX] - - //get icon or create it from pre defined flags - var/icon_state - if(design[ICON] != null) - icon_state = design[ICON] - else - icon_state = category_icon_state - if(icon_state == TITLE_ICON) - icon_state = design[TITLE] - icon_state = "[icon_state][category_icon_suffix]" - - //sanitize them so you dont go insane when icon names contain spaces in them - icon_state = sanitize_css_class_name(icon_state) - - designs += list(list(TITLE = design[TITLE], ICON = icon_state)) + for(var/list/design as anything in target_category) + var/atom/movable/design_path = design[RCD_DESIGN_PATH] + + var/design_name = initial(design_path.name) + + designs += list(list("title" = design_name, "icon" = sanitize_css_class_name(design_name))) data["categories"] += list(list("cat_name" = sub_category, "designs" = designs)) + return data + +/obj/item/construction/rcd/ui_data(mob/user) + var/list/data = ..() + + //main categories + data["selected_root"] = root_category + data["selected_category"] = design_category + data["selected_design"] = design_title + //merge airlock_electronics ui data with this var/list/airlock_data = airlock_electronics.ui_data(user) for(var/key in airlock_data) @@ -503,22 +329,19 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) return data -/obj/item/construction/rcd/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) - . = ..() - if(.) - return +/obj/item/construction/rcd/handle_ui_act(action, params, datum/tgui/ui, datum/ui_state/state) switch(action) if("root_category") var/new_root = params["root_category"] - if(root_categories[new_root] != null) //is a valid category + if(GLOB.rcd_designs[new_root] != null) //is a valid category root_category = new_root if("design") + //read and validate params from UI var/category_name = params["category"] var/index = params["index"] - - var/list/root = root_categories[root_category] + var/list/root = GLOB.rcd_designs[root_category] if(root == null) //not a valid root return TRUE var/list/category = root[category_name] @@ -536,31 +359,15 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) if(category == "Furniture" && !(upgrade & RCD_UPGRADE_FURNISHING)) return TRUE + //use UI params to set variables var/list/design = category[index] if(design == null) //not a valid design return TRUE - design_category = category_name - design_title = design["title"] - - if(category_name == "Structures") - construction_mode = design[CONSTRUCTION_MODE] - if(design[WINDOW_TYPE] != null) - window_type = design[WINDOW_TYPE] - else if(category_name == "Machines") - construction_mode = design[CONSTRUCTION_MODE] - if(design[COMPUTER_DIR] != null) - computer_dir = design[COMPUTER_DIR] - if(design[WALLFRAME_TYPE] != null) - wallframe_type = design[WALLFRAME_TYPE] - else if(category_name == "Furniture") - construction_mode = RCD_FURNISHING - furnish_type = design[FURNISH_TYPE] - - if(root_category == "Airlocks") - construction_mode = RCD_AIRLOCK - airlock_glass = (category_name != "Solid Airlocks") - airlock_type = design[AIRLOCK_TYPE] + mode = design["[RCD_DESIGN_MODE]"] + construction_mode = mode + rcd_design_path = design["[RCD_DESIGN_PATH]"] + design_title = initial(rcd_design_path.name) else airlock_electronics.do_action(action, params) @@ -658,16 +465,7 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) matter = 160 /obj/item/construction/rcd/loaded/upgraded - upgrade = RCD_UPGRADE_FRAMES | RCD_UPGRADE_SIMPLE_CIRCUITS | RCD_UPGRADE_FURNISHING | RCD_UPGRADE_ANTI_INTERRUPT | RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN - -/obj/item/construction/rcd/combat - name = "industrial RCD" - icon_state = "ircd" - inhand_icon_state = "ircd" - max_matter = 500 - matter = 500 - canRturf = TRUE - upgrade = RCD_UPGRADE_FRAMES | RCD_UPGRADE_SIMPLE_CIRCUITS | RCD_UPGRADE_FURNISHING | RCD_UPGRADE_ANTI_INTERRUPT | RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN + upgrade = RCD_ALL_UPGRADES /obj/item/construction/rcd/ce name = "professional RCD" @@ -682,21 +480,37 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) 0.0, 0.0, 0.0, 0.0, ) -#undef FREQUENT_USE_DEBUFF_MULTIPLIER +/obj/item/construction/rcd/combat + name = "industrial RCD" + icon_state = "ircd" + inhand_icon_state = "ircd" + max_matter = 500 + matter = 500 + canRturf = TRUE + upgrade = RCD_ALL_UPGRADES -#undef CONSTRUCTION_MODE -#undef WINDOW_TYPE -#undef COMPUTER_DIR -#undef WALLFRAME_TYPE -#undef FURNISH_TYPE -#undef AIRLOCK_TYPE +/obj/item/construction/rcd/combat/admin + name = "admin RCD" + max_matter = INFINITY + matter = INFINITY + upgrade = RCD_ALL_UPGRADES & ~RCD_UPGRADE_SILO_LINK -#undef TITLE -#undef ICON +// Ranged RCD +/obj/item/construction/rcd/arcd + name = "advanced rapid-construction-device (ARCD)" + desc = "A prototype RCD with ranged capability and infinite capacity." + max_matter = INFINITY + matter = INFINITY + canRturf = TRUE + delay_mod = 0.6 + ranged = TRUE + icon_state = "arcd" + inhand_icon_state = "oldrcd" + has_ammobar = FALSE + upgrade = RCD_ALL_UPGRADES & ~RCD_UPGRADE_SILO_LINK -#undef CATEGORY_ICON_STATE -#undef CATEGORY_ICON_SUFFIX -#undef TITLE_ICON +#undef FREQUENT_USE_DEBUFF_MULTIPLIER +#undef RCD_DESTRUCTIVE_SCAN_COOLDOWN /obj/item/rcd_ammo name = "RCD matter cartridge" @@ -712,23 +526,3 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) /obj/item/rcd_ammo/large custom_materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*24, /datum/material/glass=SHEET_MATERIAL_AMOUNT*16) ammoamt = 160 - -/obj/item/construction/rcd/combat/admin - name = "admin RCD" - max_matter = INFINITY - matter = INFINITY - upgrade = RCD_UPGRADE_FRAMES | RCD_UPGRADE_SIMPLE_CIRCUITS | RCD_UPGRADE_FURNISHING | RCD_UPGRADE_ANTI_INTERRUPT | RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN - - -// Ranged RCD -/obj/item/construction/rcd/arcd - name = "advanced rapid-construction-device (ARCD)" - desc = "A prototype RCD with ranged capability and infinite capacity." - max_matter = INFINITY - matter = INFINITY - delay_mod = 0.6 - ranged = TRUE - icon_state = "arcd" - inhand_icon_state = "oldrcd" - has_ammobar = FALSE - upgrade = RCD_UPGRADE_FRAMES | RCD_UPGRADE_SIMPLE_CIRCUITS | RCD_UPGRADE_FURNISHING | RCD_UPGRADE_ANTI_INTERRUPT | RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN diff --git a/code/game/objects/items/rcd/RHD.dm b/code/game/objects/items/rcd/RHD.dm index 92a6ab7764129..d9c4c7cd9b771 100644 --- a/code/game/objects/items/rcd/RHD.dm +++ b/code/game/objects/items/rcd/RHD.dm @@ -104,6 +104,7 @@ silo_mats = AddComponent(/datum/component/remote_materials, FALSE, FALSE) playsound(loc, 'sound/machines/click.ogg', 50, TRUE) qdel(design_disk) + update_static_data_for_all_viewers() /// Inserts matter into the RCD allowing it to build /obj/item/construction/proc/insert_matter(obj/item, mob/user) @@ -184,6 +185,11 @@ silo_mats.use_materials(list(/datum/material/iron = SILO_USE_AMOUNT), multiplier = amount, action = "build", name = "consume") return TRUE +/obj/item/construction/ui_static_data(mob/user) + . = list() + + .["silo_upgraded"] = !!(upgrade & RCD_UPGRADE_SILO_LINK) + ///shared data for rcd,rld & plumbing /obj/item/construction/ui_data(mob/user) var/list/data = list() @@ -194,8 +200,6 @@ total_matter = 0 data["matterLeft"] = total_matter - //silo details - data["silo_upgraded"] = !!(upgrade & RCD_UPGRADE_SILO_LINK) data["silo_enabled"] = silo_link return data @@ -223,6 +227,15 @@ toggle_silo(ui.user) return TRUE + var/update = handle_ui_act(action, params, ui, state) + if(isnull(update)) + update = FALSE + return update + +/// overwrite to insert custom ui handling for subtypes +/obj/item/construction/proc/handle_ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + return null + /obj/item/construction/proc/checkResource(amount, mob/user) if(!silo_mats || !silo_mats.mat_container || !silo_link) if(silo_link) diff --git a/code/game/objects/items/rcd/RLD.dm b/code/game/objects/items/rcd/RLD.dm index c5853d2997a2e..dea4200058489 100644 --- a/code/game/objects/items/rcd/RLD.dm +++ b/code/game/objects/items/rcd/RLD.dm @@ -1,8 +1,19 @@ // RAPID LIGHTING DEVICE -#define GLOW_MODE 3 +// modes of operation +#define GLOW_MODE 1 #define LIGHT_MODE 2 -#define REMOVE_MODE 1 +#define REMOVE_MODE 3 + +// operation costs +#define LIGHT_TUBE_COST 10 +#define FLOOR_LIGHT_COST 15 +#define GLOW_STICK_COST 5 +#define DECONSTRUCT_COST 10 + +//operation delays +#define BUILD_DELAY 10 +#define REMOVE_DELAY 15 /obj/item/construction/rld name = "Rapid Lighting Device" @@ -17,20 +28,13 @@ slot_flags = ITEM_SLOT_BELT has_ammobar = TRUE ammo_sections = 6 - ///it does not make sense why any of these should be installed - banned_upgrades = RCD_UPGRADE_FRAMES | RCD_UPGRADE_SIMPLE_CIRCUITS | RCD_UPGRADE_FURNISHING | RCD_UPGRADE_ANTI_INTERRUPT | RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN + banned_upgrades = RCD_ALL_UPGRADES & ~RCD_UPGRADE_SILO_LINK + /// mode of operation see above defines var/mode = LIGHT_MODE - var/wallcost = 10 - var/floorcost = 15 - var/launchcost = 5 - var/deconcost = 10 - - var/condelay = 10 - var/decondelay = 15 ///reference to thr original icons - var/list/original_options = list( + var/static/list/original_options = list( "Color Pick" = icon(icon = 'icons/hud/radial.dmi', icon_state = "omni"), "Glow Stick" = icon(icon = 'icons/obj/lighting.dmi', icon_state = "glowstick"), "Deconstruct" = icon(icon = 'icons/obj/tools.dmi', icon_state = "wrench"), @@ -49,7 +53,7 @@ . = ..() if((upgrade & RCD_UPGRADE_SILO_LINK) && display_options["Silo Link"] == null) //silo upgrade instaled but option was not updated then update it just one - display_options["Silo Link"] = icon(icon = 'icons/obj/mining.dmi', icon_state = "silo") + display_options["Silo Link"] = icon(icon = 'icons/obj/machines/ore_silo.dmi', icon_state = "silo") var/choice = show_radial_menu(user, src, display_options, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE) if(!check_menu(user)) @@ -95,17 +99,17 @@ return FALSE //resource sanity checks before & after delay - if(!checkResource(deconcost, user)) + if(!checkResource(DECONSTRUCT_COST, user)) return FALSE var/beam = user.Beam(A,icon_state="light_beam", time = 15) playsound(loc, 'sound/machines/click.ogg', 50, TRUE) - if(!do_after(user, decondelay, target = A)) + if(!do_after(user, REMOVE_DELAY, target = A)) qdel(beam) return FALSE - if(!checkResource(deconcost, user)) + if(!checkResource(DECONSTRUCT_COST, user)) return FALSE - if(!useResource(deconcost, user)) + if(!useResource(DECONSTRUCT_COST, user)) return FALSE activate() qdel(A) @@ -113,15 +117,17 @@ if(LIGHT_MODE) //resource sanity checks before & after delay - if(!checkResource(floorcost, user)) + var/cost = iswallturf(A) ? LIGHT_TUBE_COST : FLOOR_LIGHT_COST + + if(!checkResource(cost, user)) return FALSE - var/beam = user.Beam(A,icon_state="light_beam", time = condelay) + var/beam = user.Beam(A,icon_state="light_beam", time = BUILD_DELAY) playsound(loc, 'sound/machines/click.ogg', 50, TRUE) playsound(loc, 'sound/effects/light_flicker.ogg', 50, FALSE) - if(!do_after(user, condelay, target = A)) + if(!do_after(user, BUILD_DELAY, target = A)) qdel(beam) return FALSE - if(!checkResource(floorcost, user)) + if(!checkResource(cost, user)) return FALSE if(iswallturf(A)) @@ -155,7 +161,7 @@ balloon_alert(user, "no valid target!") return FALSE - if(!useResource(wallcost, user)) + if(!useResource(cost, user)) return FALSE activate() var/obj/machinery/light/L = new /obj/machinery/light(get_turf(winner)) @@ -170,7 +176,7 @@ if(istype(dupe)) return FALSE - if(!useResource(floorcost, user)) + if(!useResource(cost, user)) return FALSE activate() var/obj/machinery/light/floor/FL = new /obj/machinery/light/floor(target) @@ -179,7 +185,7 @@ return TRUE if(GLOW_MODE) - if(!useResource(launchcost, user)) + if(!useResource(GLOW_STICK_COST, user)) return FALSE activate() var/obj/item/flashlight/glowstick/G = new /obj/item/flashlight/glowstick(start) @@ -201,6 +207,14 @@ matter = 100 max_matter = 100 +#undef LIGHT_TUBE_COST +#undef FLOOR_LIGHT_COST +#undef GLOW_STICK_COST +#undef DECONSTRUCT_COST + +#undef BUILD_DELAY +#undef REMOVE_DELAY + #undef GLOW_MODE #undef LIGHT_MODE #undef REMOVE_MODE diff --git a/code/game/objects/items/rcd/RPLD.dm b/code/game/objects/items/rcd/RPLD.dm index d3f763bb21a5a..7f03fad1a9c32 100644 --- a/code/game/objects/items/rcd/RPLD.dm +++ b/code/game/objects/items/rcd/RPLD.dm @@ -9,15 +9,16 @@ worn_icon_state = "plumbing" icon = 'icons/obj/tools.dmi' slot_flags = ITEM_SLOT_BELT - ///it does not make sense why any of these should be installed. - banned_upgrades = RCD_UPGRADE_FRAMES | RCD_UPGRADE_SIMPLE_CIRCUITS | RCD_UPGRADE_FURNISHING | RCD_UPGRADE_ANTI_INTERRUPT | RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN + banned_upgrades = RCD_ALL_UPGRADES & ~RCD_UPGRADE_SILO_LINK matter = 200 max_matter = 200 + ///category of design selected + var/selected_category ///type of the plumbing machine var/obj/machinery/blueprint = null ///This list that holds all the plumbing design types the plumberer can construct. Its purpose is to make it easy to make new plumberer subtypes with a different selection of machines. - var/list/plumbing_design_types + var/list/plumbing_design_types = null ///Current selected layer var/current_layer = "Default Layer" ///Current selected color, for ducts @@ -68,14 +69,15 @@ /obj/item/construction/plumbing/Initialize(mapload) . = ..() - plumbing_design_types = general_design_types + if(isnull(plumbing_design_types)) + plumbing_design_types = general_design_types + selected_category = plumbing_design_types[1] /** - * plumbing_design_types[1] = "Synthesizers" * plumbing_design_types["Synthesizers"] = * [1] = */ - blueprint = plumbing_design_types[plumbing_design_types[1]][1] + blueprint = plumbing_design_types[selected_category][1] /obj/item/construction/plumbing/equipped(mob/user, slot, initial) . = ..() @@ -104,7 +106,7 @@ /obj/item/construction/plumbing/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, "PlumbingService", name) + ui = new(user, src, "RapidPlumbingDevice", name) ui.open() /obj/item/construction/plumbing/ui_assets(mob/user) @@ -113,15 +115,9 @@ ) /obj/item/construction/plumbing/ui_static_data(mob/user) - return list("paint_colors" = GLOB.pipe_paint_colors) - -/obj/item/construction/plumbing/ui_data(mob/user) var/list/data = ..() - data["piping_layer"] = name_to_number[current_layer] //maps layer name to layer number's 1,2,3,4,5 - data["selected_color"] = current_color - data["layer_icon"] = "plumbing_layer[GLOB.plumbing_layers[current_layer]]" - data["selected_recipe"] = initial(blueprint.name) + data["paint_colors"] = GLOB.pipe_paint_colors var/category_list = list() for(var/category_name in plumbing_design_types) @@ -140,21 +136,24 @@ "name" = initial(recipe.name), )) - //Set selected category - if(blueprint == recipe) - data["selected_category"] = category_name - data["categories"] = list() for(var/category_name in category_list) data["categories"] += list(category_list[category_name]) return data -/obj/item/construction/plumbing/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) - . = ..() - if(.) - return +/obj/item/construction/plumbing/ui_data(mob/user) + var/list/data = ..() + data["piping_layer"] = name_to_number[current_layer] //maps layer name to layer number's 1,2,3,4,5 + data["selected_color"] = current_color + data["layer_icon"] = "plumbing_layer[GLOB.plumbing_layers[current_layer]]" + data["selected_category"] = selected_category + data["selected_recipe"] = initial(blueprint.name) + + return data + +/obj/item/construction/plumbing/handle_ui_act(action, params, datum/tgui/ui, datum/ui_state/state) switch(action) if("color") var/color = params["paint_color"] @@ -168,10 +167,13 @@ current_layer = layer if("recipe") var/category = params["category"] - if(!plumbing_design_types[category]) + + var/list/designs = plumbing_design_types[category] + if(!length(designs)) return FALSE + selected_category = category - var/design = plumbing_design_types[category][text2num(params["id"]) + 1] + var/design = designs[text2num(params["id"]) + 1] if(!design) return FALSE blueprint = design @@ -329,10 +331,10 @@ ) /obj/item/construction/plumbing/research/Initialize(mapload) - . = ..() - plumbing_design_types = research_design_types + . = ..() + /obj/item/construction/plumbing/service name = "service plumbing constructor" desc = "A type of plumbing constructor designed to rapidly deploy the machines needed to make a brewery." @@ -370,7 +372,7 @@ ) /obj/item/construction/plumbing/service/Initialize(mapload) - . = ..() - plumbing_design_types = service_design_types + . = ..() + diff --git a/code/game/objects/items/rcd/RTD.dm b/code/game/objects/items/rcd/RTD.dm index baa3b8eb47b3a..69000054f86de 100644 --- a/code/game/objects/items/rcd/RTD.dm +++ b/code/game/objects/items/rcd/RTD.dm @@ -23,7 +23,7 @@ slot_flags = ITEM_SLOT_BELT item_flags = NO_MAT_REDEMPTION | NOBLUDGEON has_ammobar = TRUE - banned_upgrades = RCD_UPGRADE_FRAMES | RCD_UPGRADE_SIMPLE_CIRCUITS | RCD_UPGRADE_FURNISHING | RCD_UPGRADE_ANTI_INTERRUPT | RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN + banned_upgrades = RCD_ALL_UPGRADES & ~RCD_UPGRADE_SILO_LINK /// main category for tile design var/root_category = "Conventional" @@ -165,21 +165,16 @@ . = ..() ui_interact(user) -/obj/item/construction/rtd/ui_data(mob/user) +/obj/item/construction/rtd/ui_static_data(mob/user) var/list/data = ..() - var/floor_designs = GLOB.floor_designs - data["selected_root"] = root_category data["root_categories"] = list() - for(var/category in floor_designs) + for(var/category in GLOB.floor_designs) data["root_categories"] += category - data["selected_category"] = design_category - - selected_design.fill_ui_data(data) data["categories"] = list() - for(var/sub_category as anything in floor_designs[root_category]) - var/list/target_category = floor_designs[root_category][sub_category] + for(var/sub_category as anything in GLOB.floor_designs[root_category]) + var/list/target_category = GLOB.floor_designs[root_category][sub_category] var/list/designs = list() //initialize all designs under this category for(var/list/design as anything in target_category) @@ -190,11 +185,16 @@ return data -/obj/item/construction/rtd/ui_act(action, params) - . = ..() - if(.) - return +/obj/item/construction/rtd/ui_data(mob/user) + var/list/data = ..() + + data["selected_root"] = root_category + data["selected_category"] = design_category + selected_design.fill_ui_data(data) + + return data +/obj/item/construction/rtd/handle_ui_act(action, params, datum/tgui/ui, datum/ui_state/state) var/floor_designs = GLOB.floor_designs switch(action) if("root_category") @@ -264,7 +264,7 @@ return TRUE var/delay = CONSTRUCTION_TIME(selected_design.cost) - var/obj/effect/constructing_effect/rcd_effect = new(floor, delay, RCD_FLOORWALL) + var/obj/effect/constructing_effect/rcd_effect = new(floor, delay, RCD_TURF) //resource sanity check before & after delay along with special effects if(!checkResource(selected_design.cost, user)) diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 1ad86868212f5..e9da67e9cc256 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -73,7 +73,7 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \ new/datum/stack_recipe("rack parts", /obj/item/rack_parts, category = CAT_FURNITURE), \ new/datum/stack_recipe("closet", /obj/structure/closet, 2, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ null, \ - new/datum/stack_recipe("unfinished canister frame", /obj/structure/canister_frame/machine/unfinished_canister_frame, 5, time = 0.8 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ATMOSPHERIC), \ + new/datum/stack_recipe("atmos canister", /obj/machinery/portable_atmospherics/canister, 10, time = 3 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ATMOSPHERIC), \ null, \ new/datum/stack_recipe("floor tile", /obj/item/stack/tile/iron/base, 1, 4, 20, category = CAT_TILES), \ new/datum/stack_recipe("iron rod", /obj/item/stack/rods, 1, 2, 60, category = CAT_MISC), \ diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index 4718471e87a5d..1c223c50f3d39 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -670,6 +670,7 @@ atom_storage.max_slots = 6 atom_storage.max_specific_storage = WEIGHT_CLASS_NORMAL // Set to this so the light replacer can fit. atom_storage.set_holdable(list( + /obj/item/access_key, /obj/item/assembly/mousetrap, /obj/item/clothing/gloves, /obj/item/flashlight, diff --git a/code/game/objects/items/storage/holsters.dm b/code/game/objects/items/storage/holsters.dm index cb44de3485dbd..cb722469a2c03 100644 --- a/code/game/objects/items/storage/holsters.dm +++ b/code/game/objects/items/storage/holsters.dm @@ -184,3 +184,18 @@ /obj/item/ammo_casing, // For shotgun shells, rockets, launcher grenades, and a few other things. /obj/item/grenade, // All regular grenades, the big grenade launcher fires these. )) + +/obj/item/storage/belt/holster/nukie/cowboy + desc = "A deep shoulder holster capable of holding almost any form of small firearm and its ammo. This one's specialized for handguns." + +/obj/item/storage/belt/holster/nukie/cowboy/Initialize(mapload) + . = ..() + atom_storage.max_slots = 3 + atom_storage.max_specific_storage = WEIGHT_CLASS_NORMAL + +/obj/item/storage/belt/holster/nukie/cowboy/full/PopulateContents() + generate_items_inside(list( + /obj/item/gun/ballistic/revolver/syndicate/cowboy = 1, + /obj/item/ammo_box/a357 = 2, + ), src) + diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index 51082eabc95c2..14f166208aa10 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -789,6 +789,21 @@ for(var/i in 1 to poster_count) new /obj/item/poster/traitor(src) +/obj/item/storage/box/syndie_kit/cowboy + name = "western outlaw pack" + desc = "Contains everything you'll need to be the rootin' tootin' cowboy you always wanted. Either play the Lone Ranger or go in with your posse of outlaws." + +/obj/item/storage/box/syndie_kit/cowboy/PopulateContents() + generate_items_inside(list( + /obj/item/clothing/shoes/cowboy/black/syndicate= 1, + /obj/item/clothing/head/cowboy/black/syndicate = 1, + /obj/item/storage/belt/holster/nukie/cowboy/full = 1, + /obj/item/clothing/under/costume/dutch/syndicate = 1, + /obj/item/lighter/skull = 1, + /obj/item/sbeacondrop/horse = 1, + /obj/item/food/grown/apple = 1, + ), src) + #undef KIT_RECON #undef KIT_BLOODY_SPAI #undef KIT_STEALTHY diff --git a/code/game/objects/items/surgery_tray.dm b/code/game/objects/items/surgery_tray.dm index daa95915698a5..6ce150c66f9cb 100644 --- a/code/game/objects/items/surgery_tray.dm +++ b/code/game/objects/items/surgery_tray.dm @@ -6,6 +6,7 @@ /datum/storage/surgery_tray/New() . = ..() set_holdable(list( + /obj/item/autopsy_scanner, /obj/item/blood_filter, /obj/item/bonesetter, /obj/item/cautery, diff --git a/code/game/objects/items/tail_pin.dm b/code/game/objects/items/tail_pin.dm index 3052075c94d14..de3148dd06dea 100644 --- a/code/game/objects/items/tail_pin.dm +++ b/code/game/objects/items/tail_pin.dm @@ -2,7 +2,7 @@ icon = 'icons/obj/poster.dmi' icon_state = "tailpin" name = "tail pin" - desc = "Offically branded 'pin the tail on the corgi' style party implement. Not intended to be used on people." + desc = "Officially branded 'pin the tail on the corgi' style party implement. Not intended to be used on people." force = 0 w_class = WEIGHT_CLASS_SMALL throwforce = 0 diff --git a/code/game/objects/structures/bonfire.dm b/code/game/objects/structures/bonfire.dm index 7f39aeb0a794e..b3c17c7ec983b 100644 --- a/code/game/objects/structures/bonfire.dm +++ b/code/game/objects/structures/bonfire.dm @@ -17,19 +17,14 @@ anchored = TRUE buckle_lying = 0 pass_flags_self = PASSTABLE | LETPASSTHROW - ///is the bonfire lit? + /// is the bonfire lit? var/burning = FALSE - ///icon for the bonfire while on. for a softer more burning embers icon, use "bonfire_warm" + /// icon for the bonfire while on. for a softer more burning embers icon, use "bonfire_warm" var/burn_icon = "bonfire_on_fire" - ///if the bonfire has a grill attached + /// if the bonfire has a grill attached var/grill = FALSE - -/obj/structure/bonfire/dense - density = TRUE - -/obj/structure/bonfire/prelit/Initialize(mapload) - . = ..() - start_burning() + /// the looping sound effect that is played while burning + var/datum/looping_sound/burning/burning_loop /obj/structure/bonfire/Initialize(mapload) . = ..() @@ -37,6 +32,12 @@ COMSIG_ATOM_ENTERED = PROC_REF(on_entered), ) AddElement(/datum/element/connect_loc, loc_connections) + burning_loop = new(src) + +/obj/structure/bonfire/Destroy() + STOP_PROCESSING(SSobj, src) + QDEL_NULL(burning_loop) + . = ..() /obj/structure/bonfire/attackby(obj/item/used_item, mob/living/user, params) if(istype(used_item, /obj/item/stack/rods) && !can_buckle && !grill) @@ -77,7 +78,6 @@ else return ..() - /obj/structure/bonfire/attack_hand(mob/user, list/modifiers) . = ..() if(.) @@ -107,6 +107,8 @@ /obj/structure/bonfire/proc/start_burning() if(burning || !check_oxygen()) return + + burning_loop.start() icon_state = burn_icon burning = TRUE set_light(6) @@ -164,6 +166,8 @@ . = ..() if(!burning) return + + burning_loop.stop() icon_state = "bonfire" burning = FALSE set_light(0) @@ -178,4 +182,11 @@ if(..()) buckled_mob.pixel_y -= 13 +/obj/structure/bonfire/dense + density = TRUE + +/obj/structure/bonfire/prelit/Initialize(mapload) + . = ..() + start_burning() + #undef BONFIRE_FIRE_STACK_STRENGTH diff --git a/code/game/objects/structures/construction_console/construction_actions.dm b/code/game/objects/structures/construction_console/construction_actions.dm index e190312c16726..fc014d14318bd 100644 --- a/code/game/objects/structures/construction_console/construction_actions.dm +++ b/code/game/objects/structures/construction_console/construction_actions.dm @@ -52,11 +52,11 @@ var/atom/rcd_target = target_turf //Find airlocks and other shite for(var/obj/S in target_turf) - if(LAZYLEN(S.rcd_vals(owner,base_console.internal_rcd))) + if(LAZYLEN(S.rcd_vals(owner, base_console.internal_rcd))) rcd_target = S //If we don't break out of this loop we'll get the last placed thing owner.changeNext_move(CLICK_CD_RANGE) check_rcd() - base_console.internal_rcd.afterattack(rcd_target, owner, TRUE, "") //Activate the RCD and force it to work remotely! + base_console.internal_rcd.rcd_create(rcd_target, owner) //Activate the RCD and force it to work remotely! playsound(target_turf, 'sound/items/deconstruct.ogg', 60, TRUE) /datum/action/innate/construction/configure_mode diff --git a/code/game/objects/structures/door_assembly.dm b/code/game/objects/structures/door_assembly.dm index 62638f44eeb77..e90567a331873 100644 --- a/code/game/objects/structures/door_assembly.dm +++ b/code/game/objects/structures/door_assembly.dm @@ -384,13 +384,11 @@ /obj/structure/door_assembly/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) if(the_rcd.mode == RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 5 SECONDS, "cost" = 16) + return list("delay" = 5 SECONDS, "cost" = 16) return FALSE -/obj/structure/door_assembly/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_DECONSTRUCT) - to_chat(user, span_notice("You deconstruct [src].")) - qdel(src) - return TRUE +/obj/structure/door_assembly/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(rcd_data["[RCD_DESIGN_MODE]"] == RCD_DECONSTRUCT) + qdel(src) + return TRUE return FALSE diff --git a/code/game/objects/structures/fireaxe.dm b/code/game/objects/structures/fireaxe.dm index 59a00618b0e91..31ba1cd47aa59 100644 --- a/code/game/objects/structures/fireaxe.dm +++ b/code/game/objects/structures/fireaxe.dm @@ -211,6 +211,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/fireaxecabinet, 32) /obj/structure/fireaxecabinet/empty populate_contents = FALSE +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/fireaxecabinet/empty, 32) + /obj/item/wallframe/fireaxecabinet name = "fire axe cabinet" desc = "Home to a window's greatest nightmare. Apply to wall to use." @@ -238,6 +240,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/fireaxecabinet/mechremoval, 32) /obj/structure/fireaxecabinet/mechremoval/empty populate_contents = FALSE +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/fireaxecabinet/mechremoval/empty, 32) + /obj/item/wallframe/fireaxecabinet/mechremoval name = "mech removal tool cabinet" desc = "Home to a very special crowbar. Apply to wall to use." diff --git a/code/game/objects/structures/fireplace.dm b/code/game/objects/structures/fireplace.dm index 132415e2632f7..c5bed8caaca37 100644 --- a/code/game/objects/structures/fireplace.dm +++ b/code/game/objects/structures/fireplace.dm @@ -14,17 +14,22 @@ light_color = LIGHT_COLOR_FIRE light_angle = 170 light_flags = LIGHT_IGNORE_OFFSET + /// is the fireplace lit? var/lit = FALSE - + /// the amount of fuel for the fire var/fuel_added = 0 + /// how much time is left before fire runs out of fuel var/flame_expiry_timer + /// the looping sound effect that is played while burning + var/datum/looping_sound/burning/burning_loop /obj/structure/fireplace/Initialize(mapload) . = ..() - START_PROCESSING(SSobj, src) + burning_loop = new(src) /obj/structure/fireplace/Destroy() STOP_PROCESSING(SSobj, src) + QDEL_NULL(burning_loop) . = ..() /obj/structure/fireplace/setDir(newdir) @@ -126,7 +131,6 @@ put_out() return - playsound(src, 'sound/effects/comfyfire.ogg',50,FALSE, FALSE, TRUE) var/turf/T = get_turf(src) T.hotspot_expose(700, 2.5 * seconds_per_tick) update_appearance() @@ -155,6 +159,8 @@ return max(0, fuel_added) /obj/structure/fireplace/proc/ignite() + START_PROCESSING(SSobj, src) + burning_loop.start() lit = TRUE desc = "A large stone brick fireplace, warm and cozy." flame_expiry_timer = world.time + fuel_added @@ -163,6 +169,8 @@ adjust_light() /obj/structure/fireplace/proc/put_out() + STOP_PROCESSING(SSobj, src) + burning_loop.stop() lit = FALSE update_appearance() adjust_light() diff --git a/code/game/objects/structures/fluff.dm b/code/game/objects/structures/fluff.dm index bf39a3f3cca86..78085b37d5503 100644 --- a/code/game/objects/structures/fluff.dm +++ b/code/game/objects/structures/fluff.dm @@ -284,3 +284,10 @@ /obj/structure/fluff/tram_rail/anchor name = "tram rail anchor" icon_state = "anchor" + +/obj/structure/fluff/broken_canister_frame + name = "broken canister frame" + icon_state = "broken_canister" + anchored = FALSE + density = TRUE + deconstructible = TRUE diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm index 33cab1d37ad35..0310d903c4865 100644 --- a/code/game/objects/structures/girders.dm +++ b/code/game/objects/structures/girders.dm @@ -444,19 +444,25 @@ /obj/structure/girder/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) switch(the_rcd.mode) - if(RCD_FLOORWALL) + if(RCD_TURF) + if(the_rcd.rcd_design_path != /turf/open/floor/plating/rcd) + return FALSE + return rcd_result_with_memory( - list("mode" = RCD_FLOORWALL, "delay" = 2 SECONDS, "cost" = 8), + list("delay" = 2 SECONDS, "cost" = 8), get_turf(src), RCD_MEMORY_WALL, ) if(RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 2 SECONDS, "cost" = 13) + return list("delay" = 2 SECONDS, "cost" = 13) return FALSE -/obj/structure/girder/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - var/turf/T = get_turf(src) - switch(passed_mode) - if(RCD_FLOORWALL) +/obj/structure/girder/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + switch(rcd_data["[RCD_DESIGN_MODE]"]) + if(RCD_TURF) + if(the_rcd.rcd_design_path != /turf/open/floor/plating/rcd) + return FALSE + + var/turf/T = get_turf(src) T.PlaceOnTop(/turf/closed/wall) qdel(src) return TRUE diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index c1941e834d08b..bfe7f0b4a882d 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -60,29 +60,29 @@ /obj/structure/grille/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) switch(the_rcd.mode) if(RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 2 SECONDS, "cost" = 5) + return list("delay" = 2 SECONDS, "cost" = 5) if(RCD_WINDOWGRILLE) var/cost = 0 var/delay = 0 - if(the_rcd.window_type == /obj/structure/window/fulltile) + + if(the_rcd.rcd_design_path == /obj/structure/window/fulltile) cost = 8 delay = 3 SECONDS - else if(the_rcd.window_type == /obj/structure/window/reinforced/fulltile) + else if(the_rcd.rcd_design_path == /obj/structure/window/reinforced/fulltile) cost = 12 delay = 4 SECONDS if(!cost) return FALSE return rcd_result_with_memory( - list("mode" = RCD_WINDOWGRILLE, "delay" = delay, "cost" = cost), + list("delay" = delay, "cost" = cost), get_turf(src), RCD_MEMORY_WINDOWGRILLE, ) return FALSE -/obj/structure/grille/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) +/obj/structure/grille/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + switch(rcd_data["[RCD_DESIGN_MODE]"]) if(RCD_DECONSTRUCT) - to_chat(user, span_notice("You deconstruct the grille.")) qdel(src) return TRUE if(RCD_WINDOWGRILLE) @@ -95,12 +95,12 @@ if(!clear_tile(user)) return FALSE - var/obj/structure/window/window_path = the_rcd.window_type + var/obj/structure/window/window_path = rcd_data["[RCD_DESIGN_PATH]"] if(!ispath(window_path)) CRASH("Invalid window path type in RCD: [window_path]") if(!initial(window_path.fulltile)) //only fulltile windows can be built here return FALSE - var/obj/structure/window/WD = new the_rcd.window_type(T, user.dir) + var/obj/structure/window/WD = new window_path(T, user.dir) WD.set_anchored(TRUE) return TRUE return FALSE diff --git a/code/game/objects/structures/icemoon/cave_entrance.dm b/code/game/objects/structures/icemoon/cave_entrance.dm index 7393c20758e24..add1f278569fc 100644 --- a/code/game/objects/structures/icemoon/cave_entrance.dm +++ b/code/game/objects/structures/icemoon/cave_entrance.dm @@ -20,6 +20,9 @@ GLOBAL_LIST_INIT(ore_probability, list( mob_types = list(/mob/living/simple_animal/hostile/asteroid/wolf) move_resist = INFINITY anchored = TRUE + scanner_taggable = TRUE + mob_gps_id = "WF" // wolf + spawner_gps_id = "Animal Den" /obj/structure/spawner/ice_moon/Initialize(mapload) . = ..() @@ -65,6 +68,7 @@ GLOBAL_LIST_INIT(ore_probability, list( max_mobs = 1 spawn_time = 60 SECONDS mob_types = list(/mob/living/simple_animal/hostile/asteroid/polarbear) + mob_gps_id = "BR" // bear /obj/structure/spawner/ice_moon/polarbear/clear_rock() for(var/turf/potential in RANGE_TURFS(1, src)) @@ -76,9 +80,11 @@ GLOBAL_LIST_INIT(ore_probability, list( name = "demonic portal" desc = "A portal that goes to another world, normal creatures couldn't survive there." icon_state = "nether" - mob_types = list(/mob/living/simple_animal/hostile/asteroid/ice_demon) + mob_types = list(/mob/living/basic/mining/ice_demon) light_range = 1 light_color = COLOR_SOFT_RED + mob_gps_id = "WT|B" // watcher | bluespace + spawner_gps_id = "Netheric Distortion" /obj/structure/spawner/ice_moon/demonic_portal/Initialize(mapload) . = ..() @@ -100,9 +106,11 @@ GLOBAL_LIST_INIT(ore_probability, list( /obj/structure/spawner/ice_moon/demonic_portal/ice_whelp mob_types = list(/mob/living/basic/mining/ice_whelp) + mob_gps_id = "ID|W" // ice drake | whelp /obj/structure/spawner/ice_moon/demonic_portal/snowlegion mob_types = list(/mob/living/basic/mining/legion/snow/spawner_made) + mob_gps_id = "LG|S" // legion | snow /obj/effect/collapsing_demonic_portal name = "collapsing demonic portal" diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm index 9b460cc125cf7..f6d5b933494c3 100644 --- a/code/game/objects/structures/lattice.dm +++ b/code/game/objects/structures/lattice.dm @@ -63,25 +63,24 @@ qdel(src) /obj/structure/lattice/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - if(the_rcd.mode == RCD_FLOORWALL) - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 1) - if(the_rcd.mode == RCD_CATWALK) - return list("mode" = RCD_CATWALK, "delay" = 0, "cost" = 2) - -/obj/structure/lattice/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - if(passed_mode == RCD_FLOORWALL) - to_chat(user, span_notice("You build a floor.")) - var/turf/T = src.loc - if(isgroundlessturf(T)) - T.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) + if(the_rcd.mode == RCD_TURF) + return list("delay" = 0, "cost" = the_rcd.rcd_design_path == /obj/structure/lattice/catwalk ? 2 : 1) + return FALSE + +/obj/structure/lattice/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(rcd_data["[RCD_DESIGN_MODE]"] == RCD_TURF) + var/design_structure = rcd_data["[RCD_DESIGN_PATH]"] + if(design_structure == /turf/open/floor/plating) + var/turf/T = src.loc + if(isgroundlessturf(T)) + T.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) + qdel(src) + return TRUE + if(design_structure == /obj/structure/lattice/catwalk) + var/turf/turf = loc qdel(src) + new /obj/structure/lattice/catwalk(turf) return TRUE - if(passed_mode == RCD_CATWALK) - to_chat(user, span_notice("You build a catwalk.")) - var/turf/turf = loc - qdel(src) - new /obj/structure/lattice/catwalk(turf) - return TRUE return FALSE /obj/structure/lattice/singularity_pull(S, current_size) diff --git a/code/game/objects/structures/lavaland/necropolis_tendril.dm b/code/game/objects/structures/lavaland/necropolis_tendril.dm index 22f7f0422cd19..6d6b2e6af37af 100644 --- a/code/game/objects/structures/lavaland/necropolis_tendril.dm +++ b/code/game/objects/structures/lavaland/necropolis_tendril.dm @@ -14,19 +14,22 @@ move_resist=INFINITY // just killing it tears a massive hole in the ground, let's not move it anchored = TRUE resistance_flags = FIRE_PROOF | LAVA_PROOF - - var/gps = null var/obj/effect/light_emitter/tendril/emitted_light - + scanner_taggable = TRUE + mob_gps_id = "WT" + spawner_gps_id = "Necropolis Tendril" /obj/structure/spawner/lavaland/goliath mob_types = list(/mob/living/basic/mining/goliath) + mob_gps_id = "GL" /obj/structure/spawner/lavaland/legion mob_types = list(/mob/living/basic/mining/legion/spawner_made) + mob_gps_id = "LG" /obj/structure/spawner/lavaland/icewatcher mob_types = list(/mob/living/basic/mining/watcher/icewing) + mob_gps_id = "WT|I" // icewing GLOBAL_LIST_INIT(tendrils, list()) /obj/structure/spawner/lavaland/Initialize(mapload) @@ -63,7 +66,6 @@ GLOBAL_LIST_INIT(tendrils, list()) L.client.give_award(/datum/award/score/tendril_score, L) //Progresses score by one GLOB.tendrils -= src QDEL_NULL(emitted_light) - QDEL_NULL(gps) return ..() /obj/effect/light_emitter/tendril diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 7c143df3ca55b..264a4b17bb6b1 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -155,7 +155,17 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) if(!selectable_races[racechoice]) return TRUE - var/datum/species/newrace = selectable_races[racechoice] + + var/datum/species/newrace = new selectable_races[racechoice] + + var/attributes_desc = newrace.get_physical_attributes() + qdel(newrace) + + var/answer = tgui_alert(race_changer, attributes_desc, "Become a [newrace]?", list("Yes", "No")) + if(answer != "Yes") + change_race(race_changer) // try again + return + race_changer.set_species(newrace, icon_update = FALSE) if(HAS_TRAIT(race_changer, TRAIT_USES_SKINTONES)) var/new_s_tone = tgui_input_list(race_changer, "Choose your skin tone", "Race change", GLOB.skin_tones) @@ -175,8 +185,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) to_chat(race_changer, span_notice("Invalid color. Your color is not bright enough.")) return TRUE - race_changer.update_body(is_creating = TRUE) - race_changer.update_mutations_overlay() // no hulk lizard + race_changer.update_body(is_creating = TRUE) + race_changer.update_mutations_overlay() // no hulk lizard // possible Genders: MALE, FEMALE, PLURAL, NEUTER // possible Physique: MALE, FEMALE @@ -205,8 +215,9 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) sexy.physique = (chosen_physique == "Warlock Physique") ? MALE : FEMALE sexy.dna.update_ui_block(DNA_GENDER_BLOCK) - sexy.update_body() + sexy.update_body(is_creating = TRUE) // or else physique won't change properly sexy.update_mutations_overlay() //(hulk male/female) + sexy.update_clothing(ITEM_SLOT_ICLOTHING) // update gender shaped clothing /obj/structure/mirror/proc/change_eyes(mob/living/carbon/human/user) var/new_eye_color = input(user, "Choose your eye color", "Eye Color", user.eye_color_left) as color|null @@ -319,7 +330,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) selectable_races = sort_list(selectable_races) //Magic mirrors can change hair color as well -/obj/structure/mirror/magic/mirror/change_hair(mob/living/carbon/human/user) +/obj/structure/mirror/magic/change_hair(mob/living/carbon/human/user) var/hairchoice = tgui_alert(user, "Hairstyle or hair color?", "Change Hair", list("Style", "Color")) if(hairchoice == "Style") //So you just want to use a mirror then? return ..() @@ -336,6 +347,20 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) user.dna.update_ui_block(DNA_FACIAL_HAIR_COLOR_BLOCK) user.update_body_parts() +/obj/structure/mirror/magic/attack_hand(mob/living/carbon/human/user) + . = ..() + if(!.) + return TRUE + + if(HAS_TRAIT(user, TRAIT_ADVANCEDTOOLUSER) && HAS_TRAIT(user, TRAIT_LITERATE)) + return TRUE + + to_chat(user, span_alert("You feel quite intelligent.")) + // Prevents wizards from being soft locked out of everything + // If this stays after the species was changed once more, well, the magic mirror did it. It's magic i aint gotta explain shit + ADD_TRAIT(user, list(TRAIT_LITERATE, TRAIT_ADVANCEDTOOLUSER), SPECIES_TRAIT) + return TRUE + /obj/structure/mirror/magic/lesser/Initialize(mapload) // Roundstart species don't have a flag, so it has to be set on Initialize. selectable_races = get_selectable_species().Copy() diff --git a/code/game/objects/structures/reflector.dm b/code/game/objects/structures/reflector.dm index 57b5ff1c49208..22d40c01412ba 100644 --- a/code/game/objects/structures/reflector.dm +++ b/code/game/objects/structures/reflector.dm @@ -330,3 +330,10 @@ if(!isnull(new_angle)) set_angle(SIMPLIFY_DEGREES(new_angle)) return TRUE + +/obj/structure/reflector/wrenched + +/obj/structure/reflector/wrenched/Initialize(mapload) + . = ..() + + set_anchored(TRUE) diff --git a/code/game/objects/structures/showcase.dm b/code/game/objects/structures/showcase.dm index 7d434419f146f..760ac9fb9ac41 100644 --- a/code/game/objects/structures/showcase.dm +++ b/code/game/objects/structures/showcase.dm @@ -83,7 +83,13 @@ name = "\improper Nanotrasen-brand microwave" desc = "The famous Nanotrasen-brand microwave, the multi-purpose cooking appliance every station needs! This one appears to be drawn onto a cardboard box." icon = 'icons/obj/machines/microwave.dmi' - icon_state = "map_icon" + icon_state = "mw_complete" + +/obj/structure/showcase/machinery/microwave_engineering + name = "\improper Nanotrasen Wave(tm) microwave" + desc = "Just when everyone thought Nanotrasen couldn't improve on their famous microwave, this 2563 model features Wave™! A Nanotrasen exclusive, Wave™ allows your PDA to be charged wirelessly through microwave frequencies. Because nothing says 'future' like charging your PDA while overcooking your leftovers. Nanotrasen Wave™ - Multitasking, redefined." + icon = 'icons/obj/machines/microwave.dmi' + icon_state = "engi_mw_complete" /obj/structure/showcase/machinery/cloning_pod name = "cloning pod exhibit" diff --git a/code/game/objects/structures/spawner.dm b/code/game/objects/structures/spawner.dm index a4f40d49c8d6b..a36a4b927a2a2 100644 --- a/code/game/objects/structures/spawner.dm +++ b/code/game/objects/structures/spawner.dm @@ -14,6 +14,52 @@ var/spawn_text = "emerges from" var/faction = list(FACTION_HOSTILE) var/spawner_type = /datum/component/spawner + /// Is this spawner taggable with something? + var/scanner_taggable = FALSE + /// If this spawner's taggable, what can we tag it with? + var/static/list/scanner_types = list(/obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner) + /// If this spawner's taggable, what's the text we use to describe what we can tag it with? + var/scanner_descriptor = "mining analyzer" + /// Has this spawner been tagged/analyzed by a mining scanner? + var/gps_tagged = FALSE + /// A short identifier for the mob it spawns. Keep around 3 characters or less? + var/mob_gps_id = "???" + /// A short identifier for what kind of spawner it is, for use in putting together its GPS tag. + var/spawner_gps_id = "Creature Nest" + /// A complete identifier. Generated on tag (if tagged), used for its examine. + var/assigned_tag + +/obj/structure/spawner/examine(mob/user) + . = ..() + if(!scanner_taggable) + return + if(gps_tagged) + . += span_notice("A holotag's been attached, projecting \"[assigned_tag]\".") + else + . += span_notice("It looks like you could probably scan and tag it with a [scanner_descriptor].") + +/obj/structure/spawner/attackby(obj/item/item, mob/user, params) + . = ..() + if(.) + return TRUE + if(scanner_taggable && is_type_in_list(item, scanner_types)) + gps_tag(user) + return TRUE + +/// Tag the spawner, prefixing its GPS entry with an identifier - or giving it one, if nonexistent. +/obj/structure/spawner/proc/gps_tag(mob/user) + if(gps_tagged) + to_chat(user, span_warning("[src] already has a holotag attached!")) + return + to_chat(user, span_notice("You affix a holotag to [src].")) + playsound(src, 'sound/machines/twobeep.ogg', 100) + gps_tagged = TRUE + assigned_tag = "\[[mob_gps_id]-[rand(100,999)]\] " + spawner_gps_id + var/datum/component/gps/our_gps = GetComponent(/datum/component/gps) + if(our_gps) + our_gps.gpstag = assigned_tag + return + AddComponent(/datum/component/gps, assigned_tag) /obj/structure/spawner/Initialize(mapload) . = ..() @@ -32,6 +78,8 @@ spawn_text = "warps in from" mob_types = list(/mob/living/basic/syndicate/ranged) faction = list(ROLE_SYNDICATE) + mob_gps_id = "SYN" // syndicate + spawner_gps_id = "Hostile Warp Beacon" /obj/structure/spawner/skeleton name = "bone pit" @@ -44,6 +92,8 @@ mob_types = list(/mob/living/simple_animal/hostile/skeleton) spawn_text = "climbs out of" faction = list(FACTION_SKELETON) + mob_gps_id = "SKL" // skeletons + spawner_gps_id = "Bone Pit" /obj/structure/spawner/clown name = "Laughing Larry" @@ -67,6 +117,8 @@ ) spawn_text = "climbs out of" faction = list(FACTION_CLOWN) + mob_gps_id = "???" // clowns + spawner_gps_id = "Clown Planet Distortion" /obj/structure/spawner/mining name = "monster den" @@ -80,7 +132,7 @@ /mob/living/basic/mining/basilisk, /mob/living/basic/mining/goldgrub, /mob/living/basic/mining/goliath/ancient, - /mob/living/basic/mining/legion, + /mob/living/basic/mining/hivelord, /mob/living/basic/wumborian_fugu, ) faction = list(FACTION_MINING) @@ -89,26 +141,31 @@ name = "goldgrub den" desc = "A den housing a nest of goldgrubs, annoying but arguably much better than anything else you'll find in a nest." mob_types = list(/mob/living/basic/mining/goldgrub) + mob_gps_id = "GG" /obj/structure/spawner/mining/goliath name = "goliath den" desc = "A den housing a nest of goliaths, oh god why?" mob_types = list(/mob/living/basic/mining/goliath/ancient) + mob_gps_id = "GL|A" /obj/structure/spawner/mining/hivelord name = "hivelord den" desc = "A den housing a nest of hivelords." mob_types = list(/mob/living/basic/mining/hivelord) + mob_gps_id = "HL" /obj/structure/spawner/mining/basilisk name = "basilisk den" desc = "A den housing a nest of basilisks, bring a coat." mob_types = list(/mob/living/basic/mining/basilisk) + mob_gps_id = "BK" /obj/structure/spawner/mining/wumborian name = "wumborian fugu den" desc = "A den housing a nest of wumborian fugus, how do they all even fit in there?" mob_types = list(/mob/living/basic/wumborian_fugu) + mob_gps_id = "WF" /obj/structure/spawner/nether name = "netherworld link" @@ -125,6 +182,9 @@ /mob/living/basic/migo, ) faction = list(FACTION_NETHER) + scanner_taggable = TRUE + mob_gps_id = "?!?" + spawner_gps_id = "Netheric Distortion" /obj/structure/spawner/nether/Initialize(mapload) . = ..() diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index f25239fe41801..4da8b734bb4da 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -309,17 +309,14 @@ qdel(src) /obj/structure/table/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - switch(the_rcd.mode) - if(RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 2.4 SECONDS, "cost" = 16) + if(the_rcd.mode == RCD_DECONSTRUCT) + return list("delay" = 2.4 SECONDS, "cost" = 16) return FALSE -/obj/structure/table/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_DECONSTRUCT) - to_chat(user, span_notice("You deconstruct the table.")) - qdel(src) - return TRUE +/obj/structure/table/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(rcd_data["[RCD_DESIGN_MODE]"] == RCD_DECONSTRUCT) + qdel(src) + return TRUE return FALSE /obj/structure/table/proc/table_carbon(datum/source, mob/living/carbon/shover, mob/living/carbon/target, shove_blocked) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 77ac1f1174032..71e97bdf32c95 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -1,6 +1,6 @@ /obj/structure/window name = "window" - desc = "A window." + desc = "A directional window." icon_state = "window" density = TRUE layer = ABOVE_OBJ_LAYER //Just above doors @@ -91,16 +91,14 @@ . += span_notice("The window is unscrewed from the floor, and could be deconstructed by wrenching.") /obj/structure/window/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - switch(the_rcd.mode) - if(RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 2 SECONDS, "cost" = 5) + if(the_rcd.mode == RCD_DECONSTRUCT) + return list("delay" = 2 SECONDS, "cost" = 5) return FALSE -/obj/structure/window/rcd_act(mob/user, obj/item/construction/rcd/the_rcd) - switch(the_rcd.mode) - if(RCD_DECONSTRUCT) - qdel(src) - return TRUE +/obj/structure/window/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(rcd_data["[RCD_DESIGN_MODE]"] == RCD_DECONSTRUCT) + qdel(src) + return TRUE return FALSE /obj/structure/window/narsie_act() @@ -477,9 +475,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/window/unanchored/spawner, 0) acid = 100 /obj/structure/window/reinforced/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - switch(the_rcd.mode) - if(RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 3 SECONDS, "cost" = 15) + if(the_rcd.mode == RCD_DECONSTRUCT) + return list("delay" = 3 SECONDS, "cost" = 15) return FALSE /obj/structure/window/reinforced/attackby_secondary(obj/item/tool, mob/user, params) @@ -655,6 +652,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/window/reinforced/tinted/frosted/spaw /* Full Tile Windows (more atom_integrity) */ /obj/structure/window/fulltile + name = "full tile window" + desc = "A full tile window." icon = 'icons/obj/smooth_structures/window.dmi' icon_state = "window-0" base_icon_state = "window" @@ -668,9 +667,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/window/reinforced/tinted/frosted/spaw glass_amount = 2 /obj/structure/window/fulltile/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - switch(the_rcd.mode) - if(RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 2.5 SECONDS, "cost" = 10) + if(the_rcd.mode == RCD_DECONSTRUCT) + return list("delay" = 2.5 SECONDS, "cost" = 10) return FALSE /obj/structure/window/fulltile/unanchored @@ -711,6 +709,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/window/reinforced/tinted/frosted/spaw state = WINDOW_OUT_OF_FRAME /obj/structure/window/reinforced/fulltile + name = "full tile reinforced window" + desc = "A full tile reinforced window" icon = 'icons/obj/smooth_structures/reinforced_window.dmi' icon_state = "reinforced_window-0" base_icon_state = "reinforced_window" @@ -725,9 +725,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/window/reinforced/tinted/frosted/spaw glass_amount = 2 /obj/structure/window/reinforced/fulltile/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - switch(the_rcd.mode) - if(RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 4 SECONDS, "cost" = 20) + if(the_rcd.mode == RCD_DECONSTRUCT) + return list("mode" = RCD_DECONSTRUCT, "delay" = 4 SECONDS, "cost" = 20) return FALSE /obj/structure/window/reinforced/fulltile/unanchored diff --git a/code/game/turfs/closed/indestructible.dm b/code/game/turfs/closed/indestructible.dm index 44717faaf6918..a1320946a455d 100644 --- a/code/game/turfs/closed/indestructible.dm +++ b/code/game/turfs/closed/indestructible.dm @@ -308,6 +308,9 @@ INITIALIZE_IMMEDIATE(/turf/closed/indestructible/splashscreen) explosive_resistance = 50 baseturfs = /turf/closed/indestructible/riveted/boss +/turf/closed/indestructible/riveted/boss/wasteland + baseturfs = /turf/open/misc/asteroid/basalt/wasteland + /turf/closed/indestructible/riveted/boss/see_through opacity = FALSE diff --git a/code/game/turfs/closed/wall/misc_walls.dm b/code/game/turfs/closed/wall/misc_walls.dm index a26e456971d88..9fbce09ace274 100644 --- a/code/game/turfs/closed/wall/misc_walls.dm +++ b/code/game/turfs/closed/wall/misc_walls.dm @@ -18,16 +18,6 @@ /turf/closed/wall/mineral/cult/devastate_wall() new sheet_type(get_turf(src), sheet_amount) -/turf/closed/wall/mineral/cult/Exited(atom/movable/gone, direction) - . = ..() - if(istype(gone, /mob/living/simple_animal/hostile/construct/harvester)) //harvesters can go through cult walls, dragging something with - var/mob/living/simple_animal/hostile/construct/harvester/H = gone - var/atom/movable/stored_pulling = H.pulling - if(stored_pulling) - stored_pulling.setDir(direction) - stored_pulling.forceMove(src) - H.start_pulling(stored_pulling, supress_message = TRUE) - /turf/closed/wall/mineral/cult/artificer name = "runed stone wall" desc = "A cold stone wall engraved with indecipherable symbols. Studying them causes your head to pound." diff --git a/code/game/turfs/closed/wall/reinf_walls.dm b/code/game/turfs/closed/wall/reinf_walls.dm index e3e088d2c29f1..8b996c66ff6f6 100644 --- a/code/game/turfs/closed/wall/reinf_walls.dm +++ b/code/game/turfs/closed/wall/reinf_walls.dm @@ -215,8 +215,8 @@ return ..() -/turf/closed/wall/r_wall/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - if(the_rcd.canRturf || passed_mode == RCD_WALLFRAME) +/turf/closed/wall/r_wall/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(the_rcd.canRturf || rcd_data["[RCD_DESIGN_MODE]"] == RCD_WALLFRAME) return ..() /turf/closed/wall/r_wall/rust_heretic_act() diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm index 57d3505fcdbf9..e47784b98aa2e 100644 --- a/code/game/turfs/closed/walls.dm +++ b/code/game/turfs/closed/walls.dm @@ -315,15 +315,16 @@ /turf/closed/wall/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) switch(the_rcd.mode) if(RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 4 SECONDS, "cost" = 26) + return list("delay" = 4 SECONDS, "cost" = 26) if(RCD_WALLFRAME) - return list("mode" = RCD_WALLFRAME, "delay" = 1 SECONDS, "cost" = 8) + return list("delay" = 1 SECONDS, "cost" = 8) return FALSE -/turf/closed/wall/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) +/turf/closed/wall/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + switch(rcd_data["[RCD_DESIGN_MODE]"]) if(RCD_WALLFRAME) - var/obj/item/wallframe/new_wallmount = new the_rcd.wallframe_type(user.drop_location()) + var/obj/item/wallframe/wallmount = rcd_data["[RCD_DESIGN_PATH]"] + var/obj/item/wallframe/new_wallmount = new wallmount(user.drop_location()) return try_wallmount(new_wallmount, user, src) if(RCD_DECONSTRUCT) ScrapeAway() @@ -363,5 +364,13 @@ /turf/closed/wall/metal_foam_base girder_type = /obj/structure/foamedmetal +/turf/closed/wall/Bumped(atom/movable/bumped_atom) + . = ..() + SEND_SIGNAL(bumped_atom, COMSIG_LIVING_WALL_BUMP, src) + +/turf/closed/wall/Exited(atom/movable/gone, direction) + . = ..() + SEND_SIGNAL(gone, COMSIG_LIVING_WALL_EXITED, src) + #undef MAX_DENT_DECALS #undef LEANING_OFFSET diff --git a/code/game/turfs/open/chasm.dm b/code/game/turfs/open/chasm.dm index 49f6663d09720..3af4d12b176a3 100644 --- a/code/game/turfs/open/chasm.dm +++ b/code/game/turfs/open/chasm.dm @@ -36,17 +36,14 @@ return /turf/open/chasm/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - switch(the_rcd.mode) - if(RCD_FLOORWALL) - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3) + if(the_rcd.mode == RCD_TURF && the_rcd.rcd_design_path == /turf/open/floor/plating/rcd) + return list("delay" = 0, "cost" = 3) return FALSE -/turf/open/chasm/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_FLOORWALL) - to_chat(user, span_notice("You build a floor.")) - PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) - return TRUE +/turf/open/chasm/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(rcd_data["[RCD_DESIGN_MODE]"] == RCD_TURF && rcd_data["[RCD_DESIGN_PATH]"] == /turf/open/floor/plating/rcd) + PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) + return TRUE return FALSE /turf/open/chasm/rust_heretic_act() diff --git a/code/game/turfs/open/floor.dm b/code/game/turfs/open/floor.dm index 42e4305c3d6be..d945fc301e923 100644 --- a/code/game/turfs/open/floor.dm +++ b/code/game/turfs/open/floor.dm @@ -204,89 +204,103 @@ /// if you are updating this make to to update /turf/open/misc/rcd_vals() too /turf/open/floor/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) switch(the_rcd.mode) - if(RCD_FLOORWALL) + if(RCD_TURF) + if(the_rcd.rcd_design_path != /turf/open/floor/plating/rcd) + return FALSE + var/obj/structure/girder/girder = locate() in src if(girder) return girder.rcd_vals(user, the_rcd) + return rcd_result_with_memory( - list("mode" = RCD_FLOORWALL, "delay" = 2 SECONDS, "cost" = 16), + list("delay" = 2 SECONDS, "cost" = 16), src, RCD_MEMORY_WALL, ) - if(RCD_REFLECTOR) - return list("mode" = RCD_REFLECTOR, "delay" = 2 SECONDS, "cost" = 20) - if(RCD_AIRLOCK) - if(the_rcd.airlock_glass) - return list("mode" = RCD_AIRLOCK, "delay" = 5 SECONDS, "cost" = 20) - else - return list("mode" = RCD_AIRLOCK, "delay" = 5 SECONDS, "cost" = 16) - if(RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 5 SECONDS, "cost" = 33) if(RCD_WINDOWGRILLE) //default cost for building a grill for fulltile windows var/cost = 4 var/delay = 1 SECONDS - if(the_rcd.window_type == /obj/structure/window) + if(the_rcd.rcd_design_path == /obj/structure/window) cost = 4 delay = 2 SECONDS - else if(the_rcd.window_type == /obj/structure/window/reinforced) + else if(the_rcd.rcd_design_path == /obj/structure/window/reinforced) cost = 6 delay = 2.5 SECONDS return rcd_result_with_memory( - list("mode" = RCD_WINDOWGRILLE, "delay" = delay, "cost" = cost), + list("delay" = delay, "cost" = cost), src, RCD_MEMORY_WINDOWGRILLE, ) - if(RCD_MACHINE) - return list("mode" = RCD_MACHINE, "delay" = 2 SECONDS, "cost" = 20) - if(RCD_COMPUTER) - return list("mode" = RCD_COMPUTER, "delay" = 2 SECONDS, "cost" = 20) - if(RCD_FLOODLIGHT) - return list("mode" = RCD_FLOODLIGHT, "delay" = 3 SECONDS, "cost" = 20) - if(RCD_GIRDER) - return list("mode" = RCD_GIRDER, "delay" = 1.3 SECONDS, "cost" = 8) - if(RCD_FURNISHING) - var/cost = 0 - var/delay = 0 - if(the_rcd.furnish_type == /obj/structure/chair || the_rcd.furnish_type == /obj/structure/chair/stool) - cost = 4 - delay = 1 SECONDS - else if(the_rcd.furnish_type == /obj/structure/chair/stool/bar) - cost = 4 - delay = 0.5 SECONDS - else if(the_rcd.furnish_type == /obj/structure/table) - cost = 8 - delay = 2 SECONDS - else if(the_rcd.furnish_type == /obj/structure/table/glass) - cost = 8 - delay = 2 SECONDS - else if(the_rcd.furnish_type == /obj/structure/rack) - cost = 4 - delay = 2.5 SECONDS - else if(the_rcd.furnish_type == /obj/structure/bed) - cost = 8 - delay = 1.5 SECONDS - if(cost == 0) - return FALSE - return list("mode" = RCD_FURNISHING, "delay" = cost, "cost" = delay) + if(RCD_AIRLOCK) + if(ispath(the_rcd.rcd_design_path, /obj/machinery/door/airlock/glass)) + return list("delay" = 5 SECONDS, "cost" = 20) + else + return list("delay" = 5 SECONDS, "cost" = 16) + if(RCD_STRUCTURE) + var/static/list/structure_costs = list( + /obj/structure/reflector = list("delay" = 2 SECONDS, "cost" = 20), + /obj/structure/girder = list("delay" = 1.3 SECONDS, "cost" = 8), + /obj/structure/frame/machine/secured = list("delay" = 2 SECONDS, "cost" = 20), + /obj/structure/frame/computer/rcd = list("delay" = 2 SECONDS, "cost" = 20), + /obj/structure/floodlight_frame = list("delay" = 3 SECONDS, "cost" = 20), + /obj/structure/chair = list("delay" = 1 SECONDS, "cost" = 4), + /obj/structure/chair/stool/bar = list("delay" = 0.5 SECONDS, "cost" = 4), + /obj/structure/table = list("delay" = 2 SECONDS, "cost" = 8), + /obj/structure/bed = list("delay" = 2.5 SECONDS, "cost" = 8), + /obj/structure/rack = list("delay" = 2.5 SECONDS, "cost" = 4), + ) + + var/list/design_data = structure_costs[the_rcd.rcd_design_path] + if(!isnull(design_data)) + return design_data + + for(var/structure in structure_costs) + if(ispath(the_rcd.rcd_design_path, structure)) + return structure_costs[structure] + + return FALSE + if(RCD_DECONSTRUCT) + return list("mode" = RCD_DECONSTRUCT, "delay" = 5 SECONDS, "cost" = 33) + return FALSE /// if you are updating this make to to update /turf/open/misc/rcd_act() too -/turf/open/floor/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_FLOORWALL) +/turf/open/floor/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + switch(rcd_data["[RCD_DESIGN_MODE]"]) + if(RCD_TURF) + if(rcd_data["[RCD_DESIGN_PATH]"] != /turf/open/floor/plating/rcd) + return FALSE + var/obj/structure/girder/girder = locate() in src if(girder) - return girder.rcd_act(user, the_rcd, passed_mode) + return girder.rcd_act(user, the_rcd, rcd_data) PlaceOnTop(/turf/closed/wall) return TRUE - if(RCD_REFLECTOR) - if(locate(/obj/structure/reflector) in src) + if(RCD_WINDOWGRILLE) + //check if we are building a window + var/obj/structure/window/window_path = rcd_data["[RCD_DESIGN_PATH]"] + if(!ispath(window_path)) + CRASH("Invalid window path type in RCD: [window_path]") + + //allow directional windows to be built without grills + if(!initial(window_path.fulltile)) + if(!valid_build_direction(src, user.dir, is_fulltile = FALSE)) + balloon_alert(user, "window already here!") + return FALSE + var/obj/structure/window/WD = new window_path(src, user.dir) + WD.set_anchored(TRUE) + return TRUE + + //build grills to deal with full tile windows + if(locate(/obj/structure/grille) in src) return FALSE - var/obj/structure/reflector/reflector_base = new(src) - reflector_base.set_anchored(TRUE) + var/obj/structure/grille/new_grille = new(src) + new_grille.set_anchored(TRUE) return TRUE if(RCD_AIRLOCK) - if(ispath(the_rcd.airlock_type, /obj/machinery/door/window)) + var/obj/machinery/door/airlock_type = rcd_data["[RCD_DESIGN_PATH]"] + + if(ispath(airlock_type, /obj/machinery/door/window)) if(!valid_build_direction(src, user.dir, is_fulltile = FALSE)) balloon_alert(user, "there's already a windoor!") return FALSE @@ -296,8 +310,8 @@ balloon_alert(user, "there's already a door!") return FALSE //create the assembly and let it finish itself - var/obj/structure/windoor_assembly/assembly = new /obj/structure/windoor_assembly(src, user.dir) - assembly.secure = ispath(the_rcd.airlock_type, /obj/machinery/door/window/brigdoor) + var/obj/structure/windoor_assembly/assembly = new (src, user.dir) + assembly.secure = ispath(airlock_type, /obj/machinery/door/window/brigdoor) assembly.electronics = the_rcd.airlock_electronics.create_copy(assembly) assembly.finish_door() return TRUE @@ -309,14 +323,38 @@ return FALSE //create the assembly and let it finish itself var/obj/structure/door_assembly/assembly = new (src) - if(ispath(the_rcd.airlock_type, /obj/machinery/door/airlock/glass)) + if(initial(airlock_type.glass)) assembly.glass = TRUE - assembly.glass_type = the_rcd.airlock_type + assembly.glass_type = airlock_type else - assembly.airlock_type = the_rcd.airlock_type + assembly.airlock_type = airlock_type assembly.electronics = the_rcd.airlock_electronics.create_copy(assembly) assembly.finish_door() return TRUE + if(RCD_STRUCTURE) + var/atom/movable/design_type = rcd_data["[RCD_DESIGN_PATH]"] + + //map absolute types to basic subtypes + var/atom/movable/locate_type = design_type + if(ispath(locate_type, /obj/structure/frame/machine/secured)) + locate_type = /obj/structure/frame/machine + if(ispath(locate_type, /obj/structure/frame/computer/rcd)) + locate_type = /obj/structure/frame/computer + if(ispath(locate_type, /obj/structure/floodlight_frame/completed)) + locate_type = /obj/structure/floodlight_frame + if(locate(locate_type) in src) + return FALSE + + var/atom/movable/design = new design_type(src) + var/static/list/dir_types = list( + /obj/structure/chair, + /obj/structure/table, + /obj/structure/rack, + /obj/structure/bed, + ) + if(is_path_in_list(locate_type, dir_types)) + design.setDir(user.dir) + return TRUE if(RCD_DECONSTRUCT) if(rcd_proof) balloon_alert(user, "it's too thick!") @@ -324,63 +362,6 @@ if(!ScrapeAway(flags = CHANGETURF_INHERIT_AIR)) return FALSE return TRUE - if(RCD_WINDOWGRILLE) - //check if we are building a window - var/obj/structure/window/window_path = the_rcd.window_type - if(!ispath(window_path)) - CRASH("Invalid window path type in RCD: [window_path]") - - //allow directional windows to be built without grills - if(!initial(window_path.fulltile)) - if(!valid_build_direction(src, user.dir, is_fulltile = FALSE)) - balloon_alert(user, "window already here!") - return FALSE - var/obj/structure/window/WD = new the_rcd.window_type(src, user.dir) - WD.set_anchored(TRUE) - return TRUE - - //build grills to deal with full tile windows - if(locate(/obj/structure/grille) in src) - return FALSE - var/obj/structure/grille/new_grille = new(src) - new_grille.set_anchored(TRUE) - return TRUE - if(RCD_MACHINE) - if(locate(/obj/structure/frame/machine) in src) - return FALSE - var/obj/structure/frame/machine/new_machine = new(src) - new_machine.state = 2 - new_machine.icon_state = "box_1" - new_machine.set_anchored(TRUE) - return TRUE - if(RCD_COMPUTER) - if(locate(/obj/structure/frame/computer) in src) - return FALSE - var/obj/structure/frame/computer/new_computer = new(src) - new_computer.set_anchored(TRUE) - new_computer.state = 1 - new_computer.setDir(the_rcd.computer_dir) - return TRUE - if(RCD_FLOODLIGHT) - if(locate(/obj/structure/floodlight_frame) in src) - return FALSE - var/obj/structure/floodlight_frame/new_floodlight = new(src) - new_floodlight.name = "secured [new_floodlight.name]" - new_floodlight.desc = "A bare metal frame that looks like a floodlight. Requires a light tube to complete." - new_floodlight.icon_state = "floodlight_c3" - new_floodlight.state = FLOODLIGHT_NEEDS_LIGHTS - return TRUE - if(RCD_GIRDER) - if(locate(/obj/structure/girder) in src) - return FALSE - new /obj/structure/girder(src) - return TRUE - if(RCD_FURNISHING) - if(locate(the_rcd.furnish_type) in src) - return FALSE - var/atom/new_furnish = new the_rcd.furnish_type(src) - new_furnish.setDir(user.dir) - return TRUE return FALSE /turf/open/floor/material diff --git a/code/game/turfs/open/floor/plating.dm b/code/game/turfs/open/floor/plating.dm index 1ff83fec0c36c..eee4797887bca 100644 --- a/code/game/turfs/open/floor/plating.dm +++ b/code/game/turfs/open/floor/plating.dm @@ -163,12 +163,11 @@ to_chat(user, span_danger("You hit [src], to no effect!")) /turf/open/floor/plating/foam/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - if(the_rcd.mode == RCD_FLOORWALL) - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 1) + if(the_rcd.mode == RCD_TURF && the_rcd.rcd_design_path == /turf/open/floor/plating/rcd) + return list("delay" = 0, "cost" = 1) -/turf/open/floor/plating/foam/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - if(passed_mode == RCD_FLOORWALL) - to_chat(user, span_notice("You build a floor.")) +/turf/open/floor/plating/foam/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(rcd_data["[RCD_DESIGN_MODE]"] == RCD_TURF && rcd_data["[RCD_DESIGN_PATH]"] == /turf/open/floor/plating/rcd) ChangeTurf(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) return TRUE return FALSE @@ -308,6 +307,13 @@ new /obj/effect/decal/cleanable/glass/plastitanium/screws(below_turf) playsound(src, 'sound/effects/structure_stress/pop3.ogg', 100, vary = TRUE) +///not an actual turf its used just for rcd ui purposes +/turf/open/floor/plating/rcd + name = "Floor/Wall" + icon = 'icons/hud/radial.dmi' + icon_state = "wallfloor" + + #undef PLATE_INTACT #undef PLATE_BOLTS_LOOSENED #undef PLATE_CUT diff --git a/code/game/turfs/open/lava.dm b/code/game/turfs/open/lava.dm index b8e0494966204..623da55f10409 100644 --- a/code/game/turfs/open/lava.dm +++ b/code/game/turfs/open/lava.dm @@ -154,17 +154,14 @@ STOP_PROCESSING(SSobj, src) /turf/open/lava/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - switch(the_rcd.mode) - if(RCD_FLOORWALL) - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3) + if(the_rcd.mode == RCD_TURF && the_rcd.rcd_design_path == /turf/open/floor/plating/rcd) + return list("delay" = 0, "cost" = 3) return FALSE -/turf/open/lava/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_FLOORWALL) - to_chat(user, span_notice("You build a floor.")) - PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) - return TRUE +/turf/open/lava/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(rcd_data["[RCD_DESIGN_MODE]"] == RCD_TURF && rcd_data["[RCD_DESIGN_PATH]"] == /turf/open/floor/plating/rcd) + PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) + return TRUE return FALSE /turf/open/lava/rust_heretic_act() diff --git a/code/game/turfs/open/misc.dm b/code/game/turfs/open/misc.dm index cbbae3081a221..edf9e92c8a933 100644 --- a/code/game/turfs/open/misc.dm +++ b/code/game/turfs/open/misc.dm @@ -77,71 +77,100 @@ /turf/open/misc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) switch(the_rcd.mode) - if(RCD_FLOORWALL) - var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) - if(L) - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 1) - else - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3) - if(RCD_REFLECTOR) - return list("mode" = RCD_REFLECTOR, "delay" = 2 SECONDS, "cost" = 20) - if(RCD_AIRLOCK) - if(the_rcd.airlock_glass) - return list("mode" = RCD_AIRLOCK, "delay" = 5 SECONDS, "cost" = 20) - else - return list("mode" = RCD_AIRLOCK, "delay" = 5 SECONDS, "cost" = 16) - if(RCD_WINDOWGRILLE) + if(RCD_TURF) + if(the_rcd.rcd_design_path != /turf/open/floor/plating/rcd) + return FALSE + + var/obj/structure/girder/girder = locate() in src + if(girder) + return girder.rcd_vals(user, the_rcd) + return rcd_result_with_memory( - list("mode" = RCD_WINDOWGRILLE, "delay" = 1 SECONDS, "cost" = 4), - src, RCD_MEMORY_WINDOWGRILLE, + list("delay" = 2 SECONDS, "cost" = 16), + src, RCD_MEMORY_WALL, ) - if(RCD_MACHINE) - return list("mode" = RCD_MACHINE, "delay" = 2 SECONDS, "cost" = 20) - if(RCD_COMPUTER) - return list("mode" = RCD_COMPUTER, "delay" = 2 SECONDS, "cost" = 20) - if(RCD_FLOODLIGHT) - return list("mode" = RCD_FLOODLIGHT, "delay" = 3 SECONDS, "cost" = 20) - if(RCD_GIRDER) - return list("mode" = RCD_GIRDER, "delay" = 1.3 SECONDS, "cost" = 8) - if(RCD_FURNISHING) - var/cost = 0 - var/delay = 0 - if(the_rcd.furnish_type == /obj/structure/chair || the_rcd.furnish_type == /obj/structure/chair/stool) - cost = 4 - delay = 1 SECONDS - else if(the_rcd.furnish_type == /obj/structure/chair/stool/bar) + if(RCD_WINDOWGRILLE) + //default cost for building a grill for fulltile windows + var/cost = 4 + var/delay = 1 SECONDS + if(the_rcd.rcd_design_path == /obj/structure/window) cost = 4 - delay = 0.5 SECONDS - else if(the_rcd.furnish_type == /obj/structure/table) - cost = 8 - delay = 2 SECONDS - else if(the_rcd.furnish_type == /obj/structure/table/glass) - cost = 8 delay = 2 SECONDS - else if(the_rcd.furnish_type == /obj/structure/rack) - cost = 4 + else if(the_rcd.rcd_design_path == /obj/structure/window/reinforced) + cost = 6 delay = 2.5 SECONDS - else if(the_rcd.furnish_type == /obj/structure/bed) - cost = 8 - delay = 1.5 SECONDS - if(!cost) - return FALSE - return list("mode" = RCD_FURNISHING, "delay" = cost, "cost" = delay) + return rcd_result_with_memory( + list("delay" = delay, "cost" = cost), + src, RCD_MEMORY_WINDOWGRILLE, + ) + if(RCD_AIRLOCK) + if(ispath(the_rcd.rcd_design_path, /obj/machinery/door/airlock/glass)) + return list("delay" = 5 SECONDS, "cost" = 20) + else + return list("delay" = 5 SECONDS, "cost" = 16) + if(RCD_STRUCTURE) + var/static/list/structure_costs = list( + /obj/structure/reflector = list("delay" = 2 SECONDS, "cost" = 20), + /obj/structure/girder = list("delay" = 1.3 SECONDS, "cost" = 8), + /obj/structure/frame/machine/secured = list("delay" = 2 SECONDS, "cost" = 20), + /obj/structure/frame/computer/rcd = list("delay" = 2 SECONDS, "cost" = 20), + /obj/structure/floodlight_frame = list("delay" = 3 SECONDS, "cost" = 20), + /obj/structure/chair = list("delay" = 1 SECONDS, "cost" = 4), + /obj/structure/chair/stool/bar = list("delay" = 0.5 SECONDS, "cost" = 4), + /obj/structure/table = list("delay" = 2 SECONDS, "cost" = 8), + /obj/structure/bed = list("delay" = 2.5 SECONDS, "cost" = 8), + /obj/structure/rack = list("delay" = 2.5 SECONDS, "cost" = 4), + ) + + var/list/design_data = structure_costs[the_rcd.rcd_design_path] + if(!isnull(design_data)) + return design_data + + for(var/structure in structure_costs) + if(ispath(the_rcd.rcd_design_path, structure)) + return structure_costs[structure] + + return FALSE + return FALSE -/turf/open/misc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_FLOORWALL) - PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) +/turf/open/misc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + switch(rcd_data["[RCD_DESIGN_MODE]"]) + if(RCD_TURF) + if(rcd_data["[RCD_DESIGN_PATH]"] != /turf/open/floor/plating/rcd) + return FALSE + + var/obj/structure/girder/girder = locate() in src + if(girder) + return girder.rcd_act(user, the_rcd, rcd_data) + + PlaceOnTop(/turf/closed/wall) return TRUE - if(RCD_REFLECTOR) - if(locate(/obj/structure/reflector) in src) + if(RCD_WINDOWGRILLE) + //check if we are building a window + var/obj/structure/window/window_path = rcd_data["[RCD_DESIGN_PATH]"] + if(!ispath(window_path)) + CRASH("Invalid window path type in RCD: [window_path]") + + //allow directional windows to be built without grills + if(!initial(window_path.fulltile)) + if(!valid_build_direction(src, user.dir, is_fulltile = FALSE)) + balloon_alert(user, "window already here!") + return FALSE + var/obj/structure/window/WD = new window_path(src, user.dir) + WD.set_anchored(TRUE) + return TRUE + + //build grills to deal with full tile windows + if(locate(/obj/structure/grille) in src) return FALSE - var/obj/structure/reflector/reflector_base = new(src) - reflector_base.set_anchored(TRUE) + var/obj/structure/grille/new_grille = new(src) + new_grille.set_anchored(TRUE) return TRUE if(RCD_AIRLOCK) - if(ispath(the_rcd.airlock_type, /obj/machinery/door/window)) + var/obj/machinery/door/airlock_type = rcd_data["[RCD_DESIGN_PATH]"] + + if(ispath(airlock_type, /obj/machinery/door/window)) if(!valid_build_direction(src, user.dir, is_fulltile = FALSE)) balloon_alert(user, "there's already a windoor!") return FALSE @@ -150,15 +179,11 @@ continue balloon_alert(user, "there's already a door!") return FALSE - var/obj/machinery/door/window/new_window = new the_rcd.airlock_type(src, user.dir, the_rcd.airlock_electronics?.unres_sides) - if(the_rcd.airlock_electronics) - new_window.name = the_rcd.airlock_electronics.passed_name || initial(new_window.name) - if(the_rcd.airlock_electronics.one_access) - new_window.req_one_access = the_rcd.airlock_electronics.accesses.Copy() - else - new_window.req_access = the_rcd.airlock_electronics.accesses.Copy() - new_window.autoclose = TRUE - new_window.update_appearance() + //create the assembly and let it finish itself + var/obj/structure/windoor_assembly/assembly = new (src, user.dir) + assembly.secure = ispath(airlock_type, /obj/machinery/door/window/brigdoor) + assembly.electronics = the_rcd.airlock_electronics.create_copy(assembly) + assembly.finish_door() return TRUE for(var/obj/machinery/door/door in src) @@ -166,70 +191,38 @@ continue balloon_alert(user, "there's already a door!") return FALSE - var/obj/machinery/door/airlock/new_airlock = new the_rcd.airlock_type(src) - new_airlock.electronics = new /obj/item/electronics/airlock(new_airlock) - if(the_rcd.airlock_electronics) - new_airlock.electronics.accesses = the_rcd.airlock_electronics.accesses.Copy() - new_airlock.electronics.one_access = the_rcd.airlock_electronics.one_access - new_airlock.electronics.unres_sides = the_rcd.airlock_electronics.unres_sides - new_airlock.electronics.passed_name = the_rcd.airlock_electronics.passed_name - new_airlock.electronics.passed_cycle_id = the_rcd.airlock_electronics.passed_cycle_id - new_airlock.electronics.shell = the_rcd.airlock_electronics.shell - if(new_airlock.electronics.one_access) - new_airlock.req_one_access = new_airlock.electronics.accesses + //create the assembly and let it finish itself + var/obj/structure/door_assembly/assembly = new (src) + if(initial(airlock_type.glass)) + assembly.glass = TRUE + assembly.glass_type = airlock_type else - new_airlock.req_access = new_airlock.electronics.accesses - if(new_airlock.electronics.unres_sides) - new_airlock.unres_sides = new_airlock.electronics.unres_sides - new_airlock.unres_sensor = TRUE - if(new_airlock.electronics.passed_name) - new_airlock.name = sanitize(new_airlock.electronics.passed_name) - if(new_airlock.electronics.passed_cycle_id) - new_airlock.closeOtherId = new_airlock.electronics.passed_cycle_id - new_airlock.update_other_id() - new_airlock.autoclose = TRUE - new_airlock.update_appearance() - return TRUE - if(RCD_WINDOWGRILLE) - if(locate(/obj/structure/grille) in src) - return FALSE - var/obj/structure/grille/new_grille = new(src) - new_grille.set_anchored(TRUE) - return TRUE - if(RCD_MACHINE) - if(locate(/obj/structure/frame/machine) in src) - return FALSE - var/obj/structure/frame/machine/new_machine = new(src) - new_machine.state = 2 - new_machine.icon_state = "box_1" - new_machine.set_anchored(TRUE) - return TRUE - if(RCD_COMPUTER) - if(locate(/obj/structure/frame/computer) in src) - return FALSE - var/obj/structure/frame/computer/new_computer = new(src) - new_computer.set_anchored(TRUE) - new_computer.state = 1 - new_computer.setDir(the_rcd.computer_dir) + assembly.airlock_type = airlock_type + assembly.electronics = the_rcd.airlock_electronics.create_copy(assembly) + assembly.finish_door() return TRUE - if(RCD_FLOODLIGHT) - if(locate(/obj/structure/floodlight_frame) in src) + if(RCD_STRUCTURE) + var/atom/movable/design_type = rcd_data["[RCD_DESIGN_PATH]"] + + //map absolute types to basic subtypes + var/atom/movable/locate_type = design_type + if(ispath(locate_type, /obj/structure/frame/machine/secured)) + locate_type = /obj/structure/frame/machine + if(ispath(locate_type, /obj/structure/frame/computer/rcd)) + locate_type = /obj/structure/frame/computer + if(ispath(locate_type, /obj/structure/floodlight_frame/completed)) + locate_type = /obj/structure/floodlight_frame + if(locate(locate_type) in src) return FALSE - var/obj/structure/floodlight_frame/new_floodlight = new(src) - new_floodlight.name = "secured [new_floodlight.name]" - new_floodlight.desc = "A bare metal frame that looks like a floodlight. Requires a light tube to complete." - new_floodlight.icon_state = "floodlight_c3" - new_floodlight.state = FLOODLIGHT_NEEDS_LIGHTS - return TRUE - if(RCD_GIRDER) - if(locate(/obj/structure/girder) in src) - return FALSE - new /obj/structure/girder(src) - return TRUE - if(RCD_FURNISHING) - if(locate(the_rcd.furnish_type) in src) - return FALSE - var/atom/new_furnish = new the_rcd.furnish_type(src) - new_furnish.setDir(user.dir) + + var/atom/movable/design = new design_type(src) + var/static/list/dir_types = list( + /obj/structure/chair, + /obj/structure/table, + /obj/structure/rack, + /obj/structure/bed, + ) + if(is_path_in_list(locate_type, dir_types)) + design.setDir(user.dir) return TRUE return FALSE diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm index 91437e727a79b..77a2259763ac5 100644 --- a/code/game/turfs/open/openspace.dm +++ b/code/game/turfs/open/openspace.dm @@ -132,21 +132,19 @@ if(!CanBuildHere()) return FALSE - switch(the_rcd.mode) - if(RCD_FLOORWALL) - var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) - if(L) - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 1) - else - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3) + if(the_rcd.mode == RCD_TURF && the_rcd.rcd_design_path == /turf/open/floor/plating/rcd) + var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) + if(L) + return list("delay" = 0, "cost" = 1) + else + return list("delay" = 0, "cost" = 3) + return FALSE -/turf/open/openspace/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_FLOORWALL) - to_chat(user, span_notice("You build a floor.")) - PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) - return TRUE +/turf/open/openspace/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(rcd_data["[RCD_DESIGN_MODE]"] == RCD_TURF && rcd_data["[RCD_DESIGN_PATH]"] == /turf/open/floor/plating/rcd) + PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) + return TRUE return FALSE /turf/open/openspace/rust_heretic_act() diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm index 8152f910c01ac..fc20d9a11b15f 100644 --- a/code/game/turfs/open/space/space.dm +++ b/code/game/turfs/open/space/space.dm @@ -157,34 +157,38 @@ GLOBAL_VAR_INIT(starlight_color, COLOR_STARLIGHT) if(!CanBuildHere()) return FALSE - switch(the_rcd.mode) - if(RCD_FLOORWALL) + if(the_rcd.mode == RCD_TURF) + if(the_rcd.rcd_design_path == /turf/open/floor/plating/rcd) var/obj/structure/lattice/lattice = locate(/obj/structure/lattice, src) if(lattice) - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 1) + return list("delay" = 0, "cost" = 1) else - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3) - if(RCD_CATWALK) + return list("delay" = 0, "cost" = 3) + else if(the_rcd.rcd_design_path == /obj/structure/lattice/catwalk) var/obj/structure/lattice/lattice = locate(/obj/structure/lattice, src) if(lattice) - return list("mode" = RCD_CATWALK, "delay" = 0, "cost" = 2) + return list("delay" = 0, "cost" = 2) else - return list("mode" = RCD_CATWALK, "delay" = 0, "cost" = 4) + return list("delay" = 0, "cost" = 4) + else + return FALSE + return FALSE -/turf/open/space/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_FLOORWALL) - to_chat(user, span_notice("You build a floor.")) +/turf/open/space/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(the_rcd.mode == RCD_TURF) + if(rcd_data["[RCD_DESIGN_PATH]"] == /turf/open/floor/plating/rcd) PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) return TRUE - if(RCD_CATWALK) - to_chat(user, span_notice("You build a catwalk.")) + else if(rcd_data["[RCD_DESIGN_PATH]"] == /obj/structure/lattice/catwalk) var/obj/structure/lattice/lattice = locate(/obj/structure/lattice, src) if(lattice) qdel(lattice) new /obj/structure/lattice/catwalk(src) return TRUE + else + return FALSE + return FALSE /turf/open/space/rust_heretic_act() diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index c5b9813c5547c..600bc75a83d5f 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -230,6 +230,7 @@ GLOBAL_PROTECT(admin_verbs_debug) /client/proc/unload_ctf, /client/proc/validate_cards, /client/proc/validate_puzzgrids, + /client/proc/GeneratePipeSpritesheet, /client/proc/view_runtimes, ) GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release)) diff --git a/code/modules/admin/callproc/callproc.dm b/code/modules/admin/callproc/callproc.dm index 326c1e664cb52..22a6b43b17ade 100644 --- a/code/modules/admin/callproc/callproc.dm +++ b/code/modules/admin/callproc/callproc.dm @@ -277,7 +277,7 @@ GLOBAL_PROTECT(LastAdminCalledProc) if(named_arg) named_args[named_arg] = value["value"] else - . += value["value"] + . += LIST_VALUE_WRAP_LISTS(value["value"]) if(LAZYLEN(named_args)) . += named_args diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm index 4ff53a8e9e6f1..fcf9693731e05 100644 --- a/code/modules/admin/create_mob.dm +++ b/code/modules/admin/create_mob.dm @@ -28,6 +28,6 @@ human.dna.species.randomize_active_underwear_only(human) // Needs to be called towards the end to update all the UIs just set above human.dna.initialize_dna(newblood_type = random_blood_type(), create_mutation_blocks = randomize_mutations, randomize_features = TRUE) - // Snowflake stuff (ethereals) - human.dna.species.spec_updatehealth(human) + // Snowflake for Ethereals + human.updatehealth() human.updateappearance(mutcolor_update = TRUE) diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2.dm b/code/modules/admin/verbs/SDQL2/SDQL_2.dm index 0702183bfe2ea..a124627399c6e 100644 --- a/code/modules/admin/verbs/SDQL2/SDQL_2.dm +++ b/code/modules/admin/verbs/SDQL2/SDQL_2.dm @@ -730,7 +730,11 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/sdql2_vv_all, new(null var/atom/A = object var/turf/T = A.loc var/area/a - if(istype(T)) + if(isturf(A)) + a = A.loc + T = A //this should prevent the "inside" part + text_list += " at [ADMIN_COORDJMP(A)]" + else if(istype(T)) text_list += " at [T] [ADMIN_COORDJMP(T)]" a = T.loc else diff --git a/code/modules/admin/verbs/admin_newscaster.dm b/code/modules/admin/verbs/admin_newscaster.dm index e6616e2539b8c..0a25ebaca6559 100644 --- a/code/modules/admin/verbs/admin_newscaster.dm +++ b/code/modules/admin/verbs/admin_newscaster.dm @@ -304,7 +304,7 @@ return TRUE var/choice = tgui_alert(usr, "Please confirm feed channel creation","Network Channel Handler", list("Confirm","Cancel")) if(choice == "Confirm") - GLOB.news_network.create_feed_channel(channel_name, "Centcom Offical", channel_desc, locked = channel_locked) + GLOB.news_network.create_feed_channel(channel_name, "Centcom Official", channel_desc, locked = channel_locked) SSblackbox.record_feedback("text", "newscaster_channels", 1, "[channel_name]") creating_channel = FALSE @@ -316,7 +316,7 @@ creating_comment = FALSE return TRUE var/datum/feed_comment/new_feed_comment = new /datum/feed_comment - new_feed_comment.author = "Centcom Offical" + new_feed_comment.author = "Centcom Official" new_feed_comment.body = comment_text new_feed_comment.time_stamp = station_time_timestamp() current_message.comments += new_feed_comment diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm index 5fbcf3489c8ed..90060aeeefe77 100644 --- a/code/modules/admin/verbs/mapping.dm +++ b/code/modules/admin/verbs/mapping.dm @@ -389,7 +389,7 @@ GLOBAL_VAR_INIT(say_disabled, FALSE) var/list/foodcount = list() for(var/obj/item/food/fuck_me in world) var/turf/location = get_turf(fuck_me) - if(!location || SSmapping.level_trait(location.z, ZTRAIT_STATION)) + if(!location || !SSmapping.level_trait(location.z, ZTRAIT_STATION)) continue LAZYADDASSOC(foodcount, fuck_me.type, 1) @@ -412,7 +412,7 @@ GLOBAL_VAR_INIT(say_disabled, FALSE) var/list/stackcount = list() for(var/obj/item/stack/fuck_me in world) var/turf/location = get_turf(fuck_me) - if(!location || SSmapping.level_trait(location.z, ZTRAIT_STATION)) + if(!location || !SSmapping.level_trait(location.z, ZTRAIT_STATION)) continue LAZYADDASSOC(stackcount, fuck_me.type, fuck_me.amount) diff --git a/code/modules/admin/view_variables/debug_variables.dm b/code/modules/admin/view_variables/debug_variables.dm index 5196d83b124f0..ea119597ffa68 100644 --- a/code/modules/admin/view_variables/debug_variables.dm +++ b/code/modules/admin/view_variables/debug_variables.dm @@ -1,107 +1,120 @@ #define VV_HTML_ENCODE(thing) ( sanitize ? html_encode(thing) : thing ) /// Get displayed variable in VV variable list -/proc/debug_variable(name, value, level, datum/D, sanitize = TRUE, display_flags = NONE) //if D is a list, name will be index, and value will be assoc value. - var/header - if(D) - if(islist(D)) +/proc/debug_variable(name, value, level, datum/owner, sanitize = TRUE, display_flags = NONE) //if D is a list, name will be index, and value will be assoc value. + if(owner) + if(islist(owner)) var/index = name if (value) - name = D[name] //name is really the index until this line + name = owner[name] //name is really the index until this line else - value = D[name] - header = "
  • ([VV_HREF_TARGET_1V(D, VV_HK_LIST_EDIT, "E", index)]) ([VV_HREF_TARGET_1V(D, VV_HK_LIST_CHANGE, "C", index)]) ([VV_HREF_TARGET_1V(D, VV_HK_LIST_REMOVE, "-", index)]) " + value = owner[name] + . = "
  • ([VV_HREF_TARGET_1V(owner, VV_HK_LIST_EDIT, "E", index)]) ([VV_HREF_TARGET_1V(owner, VV_HK_LIST_CHANGE, "C", index)]) ([VV_HREF_TARGET_1V(owner, VV_HK_LIST_REMOVE, "-", index)]) " else - header = "
  • ([VV_HREF_TARGET_1V(D, VV_HK_BASIC_EDIT, "E", name)]) ([VV_HREF_TARGET_1V(D, VV_HK_BASIC_CHANGE, "C", name)]) ([VV_HREF_TARGET_1V(D, VV_HK_BASIC_MASSEDIT, "M", name)]) " + . = "
  • ([VV_HREF_TARGET_1V(owner, VV_HK_BASIC_EDIT, "E", name)]) ([VV_HREF_TARGET_1V(owner, VV_HK_BASIC_CHANGE, "C", name)]) ([VV_HREF_TARGET_1V(owner, VV_HK_BASIC_MASSEDIT, "M", name)]) " else - header = "
  • " + . = "
  • " - var/item var/name_part = VV_HTML_ENCODE(name) - if(level > 0 || islist(D)) //handling keys in assoc lists + if(level > 0 || islist(owner)) //handling keys in assoc lists if(istype(name,/datum)) name_part = "[VV_HTML_ENCODE(name)] [REF(name)]" else if(islist(name)) - var/list/L = name - name_part = " /list ([length(L)]) [REF(name)]" + var/list/list_value = name + name_part = " /list ([length(list_value)]) [REF(name)]" - if (isnull(value)) - item = "[name_part] = null" + . = "[.][name_part] = " - else if (istext(value)) - item = "[name_part] = \"[VV_HTML_ENCODE(value)]\"" + var/item = _debug_variable_value(name, value, level, owner, sanitize, display_flags) - else if (isicon(value)) + return "[.][item]
  • " + +// This is split into a seperate proc mostly to make errors that happen not break things too much +/proc/_debug_variable_value(name, value, level, datum/owner, sanitize, display_flags) + . = "DISPLAY_ERROR" + + if(isnull(value)) + return "null" + + if(istext(value)) + return "\"[VV_HTML_ENCODE(value)]\"" + + if(isicon(value)) #ifdef VARSICON - var/icon/I = icon(value) + var/icon/icon_value = icon(value) var/rnd = rand(1,10000) - var/rname = "tmp[REF(I)][rnd].png" - usr << browse_rsc(I, rname) - item = "[name_part] = ([value]) " + var/rname = "tmp[REF(icon_value)][rnd].png" + usr << browse_rsc(icon_value, rname) + return "([value]) " #else - item = "[name_part] = /icon ([value])" + return "/icon ([value])" #endif - else if(isappearance(value)) + if(isappearance(value)) var/image/actually_an_appearance = value - item = "[name_part] = /appearance ([actually_an_appearance.icon])" + return "/appearance ([actually_an_appearance.icon])" - else if (isfile(value)) - item = "[name_part] = '[value]'" + if(isfilter(value)) + var/datum/filter_value = value + return "/filter ([filter_value.type] [REF(filter_value)])" - else if(istype(value,/matrix)) // Needs to be before datum - var/matrix/M = value - item = {"[name_part] = -
      - - - - - - -
    [M.a][M.d]0
    [M.b][M.e]0
    [M.c][M.f]1
     
    "} //TODO link to modify_transform wrapper for all matrices + if(isfile(value)) + return "'[value]'" - else if (isdatum(value)) - var/datum/DV = value - if ("[DV]" != "[DV.type]") //if the thing as a name var, lets use it. - item = "[name_part] = [DV] [DV.type] [REF(value)]" - else - item = "[name_part] = [DV.type] [REF(value)]" - if(istype(value,/datum/weakref)) - var/datum/weakref/weakref = value - item += " (Resolve)" + if(isdatum(value)) + var/datum/datum_value = value + return datum_value.debug_variable_value(name, level, owner, sanitize, display_flags) - else if (islist(value)) - var/list/L = value + if(islist(value) || hascall(value, "Cut")) // Some special lists arent detectable as a list through istype, so we check if it has a list proc instead + var/list/list_value = value var/list/items = list() - if (!(display_flags & VV_ALWAYS_CONTRACT_LIST) && L.len > 0 && !(name == "underlays" || name == "overlays" || L.len > (IS_NORMAL_LIST(L) ? VV_NORMAL_LIST_NO_EXPAND_THRESHOLD : VV_SPECIAL_LIST_NO_EXPAND_THRESHOLD))) - for (var/i in 1 to L.len) - var/key = L[i] + if (!(display_flags & VV_ALWAYS_CONTRACT_LIST) && list_value.len > 0 && list_value.len <= (IS_NORMAL_LIST(list_value) ? VV_NORMAL_LIST_NO_EXPAND_THRESHOLD : VV_SPECIAL_LIST_NO_EXPAND_THRESHOLD)) + for (var/i in 1 to list_value.len) + var/key = list_value[i] var/val - if (IS_NORMAL_LIST(L) && !isnum(key)) - val = L[key] + if (IS_NORMAL_LIST(list_value) && !isnum(key)) + val = list_value[key] if (isnull(val)) // we still want to display non-null false values, such as 0 or "" val = key key = i items += debug_variable(key, val, level + 1, sanitize = sanitize) - item = "[name_part] = /list ([L.len])
      [items.Join()]
    " + return "/list ([list_value.len])
      [items.Join()]
    " else - item = "[name_part] = /list ([L.len])" + return "/list ([list_value.len])" - else if (name in GLOB.bitfields) + if(name in GLOB.bitfields) var/list/flags = list() for (var/i in GLOB.bitfields[name]) if (value & GLOB.bitfields[name][i]) flags += i if(length(flags)) - item = "[name_part] = [VV_HTML_ENCODE(jointext(flags, ", "))]" + return "[VV_HTML_ENCODE(jointext(flags, ", "))]" else - item = "[name_part] = NONE" + return "NONE" + else + return "[VV_HTML_ENCODE(value)]" + +/datum/proc/debug_variable_value(name, level, datum/owner, sanitize, display_flags) + if("[src]" != "[type]") // If we have a name var, let's use it. + return "[src] [type] [REF(src)]" else - item = "[name_part] = [VV_HTML_ENCODE(value)]" + return "[type] [REF(src)]" - return "[header][item]" +/datum/weakref/debug_variable_value(name, level, datum/owner, sanitize, display_flags) + . = ..() + return "[.] (Resolve)" + +/matrix/debug_variable_value(name, level, datum/owner, sanitize, display_flags) + return {" +
      + + + + + + +
    [a][d]0
    [b][e]0
    [c][f]1
     
    "} //TODO link to modify_transform wrapper for all matrices #undef VV_HTML_ENCODE diff --git a/code/modules/admin/view_variables/topic.dm b/code/modules/admin/view_variables/topic.dm index e805d55a8afac..6edc9cf2e6a77 100644 --- a/code/modules/admin/view_variables/topic.dm +++ b/code/modules/admin/view_variables/topic.dm @@ -11,7 +11,10 @@ else if(islist(target)) vv_do_list(target, href_list) if(href_list["Vars"]) - debug_variables(locate(href_list["Vars"])) + var/datum/vars_target = locate(href_list["Vars"]) + if(href_list["special_varname"]) // Some special vars can't be located even if you have their ref, you have to use this instead + vars_target = vars_target.vars[href_list["special_varname"]] + debug_variables(vars_target) //Stuff below aren't in dropdowns/etc. diff --git a/code/modules/admin/view_variables/view_variables.dm b/code/modules/admin/view_variables/view_variables.dm index 2db59be5b38d6..9f0d86e04f656 100644 --- a/code/modules/admin/view_variables/view_variables.dm +++ b/code/modules/admin/view_variables/view_variables.dm @@ -1,4 +1,4 @@ -/client/proc/debug_variables(datum/D in world) +/client/proc/debug_variables(datum/thing in world) set category = "Debug" set name = "View Variables" //set src in world @@ -8,54 +8,58 @@ to_chat(usr, span_danger("You need to be an administrator to access this."), confidential = TRUE) return - if(!D) + if(!thing) return var/datum/asset/asset_cache_datum = get_asset_datum(/datum/asset/simple/vv) asset_cache_datum.send(usr) - var/islist = islist(D) - if(!islist && !istype(D)) + var/islist = islist(thing) || (!isdatum(thing) && hascall(thing, "Cut")) // Some special lists dont count as lists, but can be detected by if they have list procs + if(!islist && !isdatum(thing)) return var/title = "" - var/refid = REF(D) + var/refid = REF(thing) var/icon/sprite var/hash - var/type = islist? /list : D.type + var/type = islist? /list : thing.type var/no_icon = FALSE - if(istype(D, /atom)) - sprite = getFlatIcon(D) - if(sprite) - hash = md5(sprite) - src << browse_rsc(sprite, "vv[hash].png") - else + if(isatom(thing)) + sprite = getFlatIcon(thing) + if(!sprite) no_icon = TRUE - title = "[D] ([REF(D)]) = [type]" - var/formatted_type = replacetext("[type]", "/", "/") + else if(isimage(thing)) + var/image/image_object = thing + sprite = icon(image_object.icon, image_object.icon_state) var/sprite_text if(sprite) - sprite_text = no_icon? "\[NO ICON\]" : "" - var/list/header = islist(D)? list("/list") : D.vv_get_header() + hash = md5(sprite) + src << browse_rsc(sprite, "vv[hash].png") + sprite_text = no_icon ? "\[NO ICON\]" : "" + + title = "[thing] ([REF(thing)]) = [type]" + var/formatted_type = replacetext("[type]", "/", "/") + + var/list/header = islist ? list("/list") : thing.vv_get_header() var/ref_line = "@[copytext(refid, 2, -1)]" // get rid of the brackets, add a @ prefix for copy pasting in asay var/marked_line - if(holder && holder.marked_datum && holder.marked_datum == D) + if(holder && holder.marked_datum && holder.marked_datum == thing) marked_line = VV_MSG_MARKED var/tagged_line - if(holder && LAZYFIND(holder.tagged_datums, D)) - var/tag_index = LAZYFIND(holder.tagged_datums, D) + if(holder && LAZYFIND(holder.tagged_datums, thing)) + var/tag_index = LAZYFIND(holder.tagged_datums, thing) tagged_line = VV_MSG_TAGGED(tag_index) var/varedited_line - if(!islist && (D.datum_flags & DF_VAR_EDITED)) + if(!islist && (thing.datum_flags & DF_VAR_EDITED)) varedited_line = VV_MSG_EDITED var/deleted_line - if(!islist && D.gc_destroyed) + if(!islist && thing.gc_destroyed) deleted_line = VV_MSG_DELETED var/list/dropdownoptions @@ -75,28 +79,29 @@ var/link = dropdownoptions[name] dropdownoptions[i] = "" else - dropdownoptions = D.vv_get_dropdown() + dropdownoptions = thing.vv_get_dropdown() var/list/names = list() if(!islist) - for(var/V in D.vars) - names += V + for(var/varname in thing.vars) + names += varname + sleep(1 TICKS) var/list/variable_html = list() if(islist) - var/list/L = D - for(var/i in 1 to L.len) - var/key = L[i] + var/list/list_value = thing + for(var/i in 1 to list_value.len) + var/key = list_value[i] var/value - if(IS_NORMAL_LIST(L) && IS_VALID_ASSOC_KEY(key)) - value = L[key] - variable_html += debug_variable(i, value, 0, L) + if(IS_NORMAL_LIST(list_value) && IS_VALID_ASSOC_KEY(key)) + value = list_value[key] + variable_html += debug_variable(i, value, 0, list_value) else names = sort_list(names) - for(var/V in names) - if(D.can_vv_get(V)) - variable_html += D.vv_get_var(V) + for(var/varname in names) + if(thing.can_vv_get(varname)) + variable_html += thing.vv_get_var(varname) var/html = {" @@ -274,5 +279,5 @@ datumrefresh=[refid];[HrefToken()]'>Refresh "} src << browse(html, "window=variables[refid];size=475x650") -/client/proc/vv_update_display(datum/D, span, content) - src << output("[span]:[content]", "variables[REF(D)].browser:replace_span") +/client/proc/vv_update_display(datum/thing, span, content) + src << output("[span]:[content]", "variables[REF(thing)].browser:replace_span") diff --git a/code/modules/antagonists/blob/overmind.dm b/code/modules/antagonists/blob/overmind.dm index 38e88817de285..d87574c092dcf 100644 --- a/code/modules/antagonists/blob/overmind.dm +++ b/code/modules/antagonists/blob/overmind.dm @@ -294,7 +294,7 @@ GLOBAL_LIST_EMPTY(blob_nodes) if(client.prefs.muted & MUTE_IC) to_chat(src, span_boldwarning("You cannot send IC messages (muted).")) return - if (!(ignore_spam || forced) && src.client.handle_spam_prevention(message,MUTE_IC)) + if (!(ignore_spam || forced) && src.client.handle_spam_prevention(message, MUTE_IC)) return if (stat) @@ -313,7 +313,7 @@ GLOBAL_LIST_EMPTY(blob_nodes) var/message_a = say_quote(message) var/rendered = span_big(span_blob("\[Blob Telepathy\] [name]([blobstrain.name]) [message_a]")) - blob_telepathy(rendered, src) + relay_to_list_and_observers(rendered, GLOB.blob_telepathy_mobs, src) /mob/camera/blob/blob_act(obj/structure/blob/B) return diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm index 171468e3d7e49..de47610564c72 100644 --- a/code/modules/antagonists/changeling/changeling.dm +++ b/code/modules/antagonists/changeling/changeling.dm @@ -350,12 +350,11 @@ /datum/antagonist/changeling/proc/regain_powers() emporium_action.Grant(owner.current) for(var/datum/action/changeling/power as anything in innate_powers) - if(power.needs_button) - power.Grant(owner.current) + power.Grant(owner.current) for(var/power_path in purchased_powers) var/datum/action/changeling/power = purchased_powers[power_path] - if(istype(power) && power.needs_button) + if(istype(power)) power.Grant(owner.current) /* @@ -1048,9 +1047,6 @@ /datum/outfit/changeling_space name = "Changeling (Space)" - - head = /obj/item/clothing/head/helmet/space/changeling - suit = /obj/item/clothing/suit/space/changeling l_hand = /obj/item/melee/arm_blade #undef FORMAT_CHEM_CHARGES_TEXT diff --git a/code/modules/antagonists/changeling/changeling_power.dm b/code/modules/antagonists/changeling/changeling_power.dm index f4949306dedc8..1e2d54eb4563f 100644 --- a/code/modules/antagonists/changeling/changeling_power.dm +++ b/code/modules/antagonists/changeling/changeling_power.dm @@ -7,8 +7,6 @@ background_icon_state = "bg_changeling" overlay_icon_state = "bg_changeling_border" button_icon = 'icons/mob/actions/actions_changeling.dmi' - /// For passive abilities like hivemind that dont need an action button - var/needs_button = TRUE /// Details displayed in fine print within the changling emporium var/helptext = "" /// How many changeling chems it costs to use @@ -44,8 +42,7 @@ the same goes for Remove(). if you override Remove(), call parent or else your p /datum/action/changeling/proc/on_purchase(mob/user, is_respec) if(!is_respec) SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, name) - if(needs_button) - Grant(user)//how powers are added rather than the checks in mob.dm + Grant(user)//how powers are added rather than the checks in mob.dm /datum/action/changeling/Trigger(trigger_flags) var/mob/user = owner diff --git a/code/modules/antagonists/changeling/powers/defib_grasp.dm b/code/modules/antagonists/changeling/powers/defib_grasp.dm index 20ff3049c8fdd..135b9b243f721 100644 --- a/code/modules/antagonists/changeling/powers/defib_grasp.dm +++ b/code/modules/antagonists/changeling/powers/defib_grasp.dm @@ -4,7 +4,7 @@ we will snatch their arms off and instantly finalize our stasis." helptext = "This ability is passive, and will trigger when a defibrillator paddle is applied to our chest \ while we are dead or in stasis. Will also stun cyborgs momentarily." - needs_button = FALSE + owner_has_control = FALSE dna_cost = 0 /// Flags to pass to fully heal when we get zapped diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm index bf4f8c2b3da3e..54027b7d8a24f 100644 --- a/code/modules/antagonists/changeling/powers/mutations.dm +++ b/code/modules/antagonists/changeling/powers/mutations.dm @@ -480,78 +480,6 @@ remaining_uses-- return ..() - -/***************************************\ -|*********SPACE SUIT + HELMET***********| -\***************************************/ -/datum/action/changeling/suit/organic_space_suit - name = "Organic Space Suit" - desc = "We grow an organic suit to protect ourselves from space exposure, including regulation of temperature and oxygen needs. Costs 20 chemicals." - helptext = "We must constantly repair our form to make it space-proof, reducing chemical production while we are protected. Cannot be used in lesser form." - button_icon_state = "organic_suit" - chemical_cost = 20 - dna_cost = 2 - req_human = TRUE - - suit_type = /obj/item/clothing/suit/space/changeling - helmet_type = /obj/item/clothing/head/helmet/space/changeling - suit_name_simple = "flesh shell" - helmet_name_simple = "space helmet" - recharge_slowdown = 0.25 - blood_on_castoff = 1 - -/obj/item/clothing/suit/space/changeling - name = "flesh mass" - icon_state = "lingspacesuit_t" - icon = 'icons/obj/clothing/suits/costume.dmi' - worn_icon = 'icons/mob/clothing/suits/costume.dmi' - desc = "A huge, bulky mass of pressure and temperature-resistant organic tissue, evolved to facilitate space travel." - item_flags = DROPDEL - clothing_flags = STOPSPRESSUREDAMAGE //Not THICKMATERIAL because it's organic tissue, so if somebody tries to inject something into it, it still ends up in your blood. (also balance but muh fluff) - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/oxygen) - armor_type = /datum/armor/space_changeling - actions_types = list() - cell = null - show_hud = FALSE - -/datum/armor/space_changeling - bio = 100 - fire = 90 - acid = 90 - -/obj/item/clothing/suit/space/changeling/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - if(ismob(loc)) - loc.visible_message(span_warning("[loc.name]\'s flesh rapidly inflates, forming a bloated mass around [loc.p_their()] body!"), span_warning("We inflate our flesh, creating a spaceproof suit!"), span_hear("You hear organic matter ripping and tearing!")) - START_PROCESSING(SSobj, src) - -// seal the cell door -/obj/item/clothing/suit/space/changeling/toggle_spacesuit_cell(mob/user) - return - -/obj/item/clothing/suit/space/changeling/process(seconds_per_tick) - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.reagents.add_reagent(/datum/reagent/medicine/salbutamol, REAGENTS_METABOLISM * (seconds_per_tick / SSMOBS_DT)) - H.adjust_bodytemperature(temperature_setting - H.bodytemperature) // force changelings to normal temp step mode played badly - -/obj/item/clothing/head/helmet/space/changeling - name = "flesh mass" - icon = 'icons/obj/clothing/head/costume.dmi' - worn_icon = 'icons/mob/clothing/head/costume.dmi' - icon_state = "lingspacehelmet" - inhand_icon_state = null - desc = "A covering of pressure and temperature-resistant organic tissue with a glass-like chitin front." - item_flags = DROPDEL - clothing_flags = STOPSPRESSUREDAMAGE | HEADINTERNALS - armor_type = /datum/armor/space_changeling - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - -/obj/item/clothing/head/helmet/space/changeling/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - /***************************************\ |*****************ARMOR*****************| \***************************************/ diff --git a/code/modules/antagonists/changeling/powers/void_adaption.dm b/code/modules/antagonists/changeling/powers/void_adaption.dm new file mode 100644 index 0000000000000..76c0eeffc972d --- /dev/null +++ b/code/modules/antagonists/changeling/powers/void_adaption.dm @@ -0,0 +1,68 @@ +/datum/action/changeling/void_adaption + name = "Void Adaption" + desc = "We prepare our cells to resist the hostile environment outside of the station. We may freely travel wherever we wish." + helptext = "This ability is passive, and will automatically protect you in situations of extreme cold or vacuum, \ + as well as removing your need to breathe. While it is actively protecting you from temperature or pressure \ + it reduces your chemical regeneration rate." + owner_has_control = FALSE + dna_cost = 2 + + /// Traits we apply to become immune to the environment + var/static/list/gain_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD, TRAIT_RESISTLOWPRESSURE, TRAIT_SNOWSTORM_IMMUNE) + /// How much we slow chemical regeneration while active, in chems per second + var/recharge_slowdown = 0.25 + /// Are we currently protecting our user? + var/currently_active = FALSE + +/datum/action/changeling/void_adaption/on_purchase(mob/user, is_respec) + . = ..() + user.add_traits(gain_traits, REF(src)) + RegisterSignal(user, COMSIG_LIVING_LIFE, PROC_REF(check_environment)) + +/datum/action/changeling/void_adaption/Remove(mob/remove_from) + remove_from.remove_traits(gain_traits, REF(src)) + UnregisterSignal(remove_from, COMSIG_LIVING_LIFE) + if (currently_active) + on_removed_adaption(remove_from, "Our cells relax, despite the danger!") + return ..() + +/// Checks if we would be providing any useful benefit at present +/datum/action/changeling/void_adaption/proc/check_environment(mob/living/void_adapted) + SIGNAL_HANDLER + + var/list/active_reasons = list() + + var/datum/gas_mixture/environment = void_adapted.loc.return_air() + if (!isnull(environment)) + var/vulnerable_temperature = void_adapted.get_body_temp_cold_damage_limit() + var/affected_temperature = environment.return_temperature() + if (ishuman(void_adapted)) + var/mob/living/carbon/human/special_boy = void_adapted + var/cold_protection = special_boy.get_cold_protection(affected_temperature) + vulnerable_temperature *= (1 - cold_protection) + + var/affected_pressure = special_boy.calculate_affecting_pressure(environment.return_pressure()) + if (affected_pressure < HAZARD_LOW_PRESSURE) + active_reasons += "vacuum" + + if (affected_temperature < vulnerable_temperature) + active_reasons += "cold" + + var/should_be_active = !!length(active_reasons) + if (currently_active == should_be_active) + return + + if (!should_be_active) + on_removed_adaption(void_adapted, "Our cells relax in safer air.") + return + var/datum/antagonist/changeling/changeling_data = void_adapted.mind?.has_antag_datum(/datum/antagonist/changeling) + to_chat(void_adapted, span_changeling("Our cells harden themselves against the [pick(active_reasons)].")) + changeling_data?.chem_recharge_slowdown -= recharge_slowdown + currently_active = TRUE + +/// Called when we stop being adapted +/datum/action/changeling/void_adaption/proc/on_removed_adaption(mob/living/former, message) + var/datum/antagonist/changeling/changeling_data = former.mind?.has_antag_datum(/datum/antagonist/changeling) + to_chat(former, span_changeling(message)) + changeling_data?.chem_recharge_slowdown += recharge_slowdown + currently_active = FALSE diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index b0d877820d903..0f43b5ae29ef4 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -23,7 +23,7 @@ /datum/antagonist/cult/greet() . = ..() - owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/bloodcult.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)//subject to change + owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/bloodcult/bloodcult_gain.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)//subject to change owner.announce_objectives() /datum/antagonist/cult/on_gain() diff --git a/code/modules/antagonists/cult/cult_team.dm b/code/modules/antagonists/cult/cult_team.dm index 6254ded64a030..1d199a113f5d2 100644 --- a/code/modules/antagonists/cult/cult_team.dm +++ b/code/modules/antagonists/cult/cult_team.dm @@ -48,7 +48,7 @@ if(ratio > CULT_RISEN && !cult_risen) for(var/datum/mind/mind as anything in members) if(mind.current) - SEND_SOUND(mind.current, 'sound/hallucinations/i_see_you2.ogg') + SEND_SOUND(mind.current, 'sound/ambience/antag/bloodcult/bloodcult_eyes.ogg') to_chat(mind.current, span_cultlarge(span_warning("The veil weakens as your cult grows, your eyes begin to glow..."))) mind.current.AddElement(/datum/element/cult_eyes) cult_risen = TRUE @@ -57,7 +57,7 @@ if(ratio > CULT_ASCENDENT && !cult_ascendent) for(var/datum/mind/mind as anything in members) if(mind.current) - SEND_SOUND(mind.current, 'sound/hallucinations/im_here1.ogg') + SEND_SOUND(mind.current, 'sound/ambience/antag/bloodcult/bloodcult_halos.ogg') to_chat(mind.current, span_cultlarge(span_warning("Your cult is ascendent and the red harvest approaches - you cannot hide your true nature for much longer!!"))) mind.current.AddElement(/datum/element/cult_halo) cult_ascendent = TRUE diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 30cb3d4467c68..d343e331fad8d 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -646,7 +646,7 @@ structure_check() searches for nearby cultist structures required for the invoca else fail_invoke() return - SEND_SOUND(mob_to_revive, 'sound/ambience/antag/bloodcult.ogg') + SEND_SOUND(mob_to_revive, 'sound/ambience/antag/bloodcult/bloodcult_gain.ogg') to_chat(mob_to_revive, span_cultlarge("\"PASNAR SAVRAE YAM'TOTH. Arise.\"")) mob_to_revive.visible_message(span_warning("[mob_to_revive] draws in a huge breath, red light shining from [mob_to_revive.p_their()] eyes."), \ span_cultlarge("You awaken suddenly from the void. You're alive!")) @@ -892,6 +892,12 @@ structure_check() searches for nearby cultist structures required for the invoca visible_message(span_warning("A cloud of red mist forms above [src], and from within steps... a [new_human.gender == FEMALE ? "wo":""]man.")) to_chat(user, span_cultitalic("Your blood begins flowing into [src]. You must remain in place and conscious to maintain the forms of those summoned. This will hurt you slowly but surely...")) var/obj/structure/emergency_shield/cult/weak/N = new(T) + if(ghost_to_spawn.mind && ghost_to_spawn.mind.current) + new_human.AddComponent( \ + /datum/component/temporary_body, \ + old_mind = ghost_to_spawn.mind, \ + old_body = ghost_to_spawn.mind.current, \ + ) new_human.key = ghost_to_spawn.key var/datum/antagonist/cult/created_cultist = new_human.mind?.add_antag_datum(/datum/antagonist/cult) created_cultist?.silent = TRUE @@ -1017,7 +1023,7 @@ structure_check() searches for nearby cultist structures required for the invoca add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/noncult, "human_apoc", A, NONE) addtimer(CALLBACK(M, TYPE_PROC_REF(/atom/, remove_alt_appearance),"human_apoc",TRUE), duration) images += A - SEND_SOUND(M, pick(sound('sound/ambience/antag/bloodcult.ogg'),sound('sound/voice/ghost_whisper.ogg'),sound('sound/misc/ghosty_wind.ogg'))) + SEND_SOUND(M, pick(sound('sound/ambience/antag/bloodcult/bloodcult_gain.ogg'),sound('sound/voice/ghost_whisper.ogg'),sound('sound/misc/ghosty_wind.ogg'))) else var/construct = pick("wraith","artificer","juggernaut") var/image/B = image('icons/mob/nonhuman-player/cult.dmi',M,construct, ABOVE_MOB_LAYER) diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index 260f56540d042..1f408698f3cfa 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -530,6 +530,7 @@ /datum/heretic_knowledge/summon/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) var/mob/living/summoned = new mob_to_summon(loc) + summoned.ai_controller?.set_ai_status(AI_STATUS_OFF) // Fade in the summon while the ghost poll is ongoing. // Also don't let them mess with the summon while waiting summoned.alpha = 0 diff --git a/code/modules/antagonists/heretic/heretic_living_heart.dm b/code/modules/antagonists/heretic/heretic_living_heart.dm index 4af93c0da68c8..1766cb4cd765e 100644 --- a/code/modules/antagonists/heretic/heretic_living_heart.dm +++ b/code/modules/antagonists/heretic/heretic_living_heart.dm @@ -99,7 +99,7 @@ return TRUE -/datum/action/cooldown/track_target/Trigger(trigger_flags) +/datum/action/cooldown/track_target/Trigger(trigger_flags, atom/target) right_clicked = !!(trigger_flags & TRIGGER_SECONDARY_ACTION) return ..() diff --git a/code/modules/antagonists/heretic/items/heretic_armor.dm b/code/modules/antagonists/heretic/items/heretic_armor.dm index 93ab613190b1c..502c52c17fb89 100644 --- a/code/modules/antagonists/heretic/items/heretic_armor.dm +++ b/code/modules/antagonists/heretic/items/heretic_armor.dm @@ -105,12 +105,12 @@ . = ..() UnregisterSignal(user, list(COMSIG_MOB_UNEQUIPPED_ITEM, COMSIG_MOB_EQUIPPED_ITEM)) -/obj/item/clothing/suit/hooded/cultrobes/void/proc/hide_item(obj/item/item, slot) +/obj/item/clothing/suit/hooded/cultrobes/void/proc/hide_item(datum/source, obj/item/item, slot) SIGNAL_HANDLER if(slot & ITEM_SLOT_SUITSTORE) ADD_TRAIT(item, TRAIT_NO_STRIP, REF(src)) // i'd use examine hide but its a flag and yeah -/obj/item/clothing/suit/hooded/cultrobes/void/proc/show_item(obj/item/item, slot) +/obj/item/clothing/suit/hooded/cultrobes/void/proc/show_item(datum/source, obj/item/item, slot) SIGNAL_HANDLER REMOVE_TRAIT(item, TRAIT_NO_STRIP, REF(src)) diff --git a/code/modules/antagonists/heretic/knowledge/blade_lore.dm b/code/modules/antagonists/heretic/knowledge/blade_lore.dm index 84e266c837458..f2f3b156a2f70 100644 --- a/code/modules/antagonists/heretic/knowledge/blade_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/blade_lore.dm @@ -13,6 +13,9 @@ * Mark of the Blade * Ritual of Knowledge * Realignment + * > Sidepaths: + * Lionhunter Rifle + * * Stance of the Scarred Duelist * > Sidepaths: * Carving Knife @@ -22,7 +25,7 @@ * Furious Steel * > Sidepaths: * Maid in the Mirror - * Lionhunter Rifle + * Rust Charge * * Maelstrom of Silver */ diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm index cd1a7ace5407d..c38fcc1a85629 100644 --- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm @@ -294,7 +294,7 @@ /obj/item/pen = 1, /obj/item/paper = 1, ) - mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/stalker + mob_to_summon = /mob/living/basic/heretic_summon/stalker cost = 1 route = PATH_FLESH poll_ignore_define = POLL_IGNORE_STALKER diff --git a/code/modules/antagonists/heretic/knowledge/rust_lore.dm b/code/modules/antagonists/heretic/knowledge/rust_lore.dm index 966134710b432..84f128b5cfcf9 100644 --- a/code/modules/antagonists/heretic/knowledge/rust_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/rust_lore.dm @@ -13,6 +13,9 @@ * Mark of Rust * Ritual of Knowledge * Rust Construction + * > Sidepaths: + * Lionhunter Rifle + * * Aggressive Spread * > Sidepaths: * Curse of Corrosion @@ -22,7 +25,7 @@ * Entropic Plume * > Sidepaths: * Rusted Ritual - * Blood Cleave + * Rust Charge * * Rustbringer's Oath */ diff --git a/code/modules/antagonists/heretic/knowledge/side_ash_flesh.dm b/code/modules/antagonists/heretic/knowledge/side_ash_flesh.dm index f7f3c175b2fb0..bf840d6ed27ea 100644 --- a/code/modules/antagonists/heretic/knowledge/side_ash_flesh.dm +++ b/code/modules/antagonists/heretic/knowledge/side_ash_flesh.dm @@ -71,7 +71,7 @@ /obj/item/bodypart/head = 1, /obj/item/book = 1, ) - mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/ash_spirit + mob_to_summon = /mob/living/basic/heretic_summon/ash_spirit cost = 1 route = PATH_SIDE poll_ignore_define = POLL_IGNORE_ASH_SPIRIT diff --git a/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm b/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm index a8fb031fed262..d7dfd75a14462 100644 --- a/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm +++ b/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm @@ -46,8 +46,8 @@ gain_text = "I met an old man in an anique shop who wielded a very unusual weapon. \ I could not purchase it at the time, but they showed me how they made it ages ago." next_knowledge = list( - /datum/heretic_knowledge/spell/furious_steel, - /datum/heretic_knowledge/spell/entropic_plume, + /datum/heretic_knowledge/spell/realignment, + /datum/heretic_knowledge/spell/rust_construction, /datum/heretic_knowledge/rifle_ammo, ) required_atoms = list( @@ -100,6 +100,10 @@ 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." gain_text = "The hills sparkled now, as I neared them my mind began to wander. I quickly regained my resolve and pushed forward, this last leg would be the most treacherous." + next_knowledge = list( + /datum/heretic_knowledge/spell/furious_steel, + /datum/heretic_knowledge/spell/entropic_plume, + ) spell_to_add = /datum/action/cooldown/mob_cooldown/charge/rust cost = 1 route = PATH_SIDE diff --git a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm index 8aecfe06e1f16..2dbb44ea4eb7e 100644 --- a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm +++ b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm @@ -88,7 +88,7 @@ /obj/item/book = 1, /obj/item/bodypart/head = 1, ) - mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/rust_spirit + mob_to_summon = /mob/living/basic/heretic_summon/rust_walker cost = 1 route = PATH_SIDE poll_ignore_define = POLL_IGNORE_RUST_SPIRIT diff --git a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm index 92e6e381222e4..643fd434af7b5 100644 --- a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm +++ b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm @@ -159,5 +159,5 @@ ) cost = 1 route = PATH_SIDE - mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror + mob_to_summon = /mob/living/basic/heretic_summon/maid_in_the_mirror poll_ignore_define = POLL_IGNORE_MAID_IN_MIRROR diff --git a/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm index 4848635b443c2..f1d6de56e399f 100644 --- a/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm +++ b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm @@ -6,10 +6,10 @@ cooldown_time = 20 SECONDS die_with_shapeshifted_form = FALSE possible_shapes = list( + /mob/living/basic/heretic_summon/ash_spirit, /mob/living/basic/heretic_summon/raw_prophet/ascended, - /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, + /mob/living/basic/heretic_summon/rust_walker, + /mob/living/basic/heretic_summon/stalker, ) /datum/action/cooldown/spell/shapeshift/eldritch/ascension/do_shapeshift(mob/living/caster) diff --git a/code/modules/antagonists/heretic/structures/knock_final.dm b/code/modules/antagonists/heretic/structures/knock_final.dm index 28a8f90b9f395..f7d0d1a9ea7b0 100644 --- a/code/modules/antagonists/heretic/structures/knock_final.dm +++ b/code/modules/antagonists/heretic/structures/knock_final.dm @@ -28,7 +28,7 @@ . = ..() 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 + monster_types = 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)) diff --git a/code/modules/antagonists/malf_ai/malf_ai_modules.dm b/code/modules/antagonists/malf_ai/malf_ai_modules.dm index 2462139044d40..bf213f0b3def0 100644 --- a/code/modules/antagonists/malf_ai/malf_ai_modules.dm +++ b/code/modules/antagonists/malf_ai/malf_ai_modules.dm @@ -455,15 +455,15 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module)) return FALSE caller.playsound_local(caller, 'sound/misc/interference.ogg', 50, FALSE, use_reverb = FALSE) - adjust_uses(-1) - - if(uses) - desc = "[initial(desc)] It has [uses] use\s remaining." - build_all_button_icons() clicked_machine.audible_message(span_userdanger("You hear a loud electrical buzzing sound coming from [clicked_machine]!")) addtimer(CALLBACK(src, PROC_REF(animate_machine), caller, clicked_machine), 5 SECONDS) //kabeep! unset_ranged_ability(caller, span_danger("Sending override signal...")) + adjust_uses(-1) //adjust after we unset the active ability since we may run out of charges, thus deleting the ability + + if(uses) + desc = "[initial(desc)] It has [uses] use\s remaining." + build_all_button_icons() return TRUE /datum/action/innate/ai/ranged/override_machine/proc/animate_machine(mob/living/caller, obj/machinery/to_animate) diff --git a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm index 57eb95a978c52..3ea6488b2d42d 100644 --- a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm +++ b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm @@ -217,9 +217,13 @@ var/cargo_hold_id ///Interface name for the ui_interact call for different subtypes. var/interface_type = "CargoHoldTerminal" + ///Typecache of things that shouldn't be sold and shouldn't have their contents sold. + var/static/list/nosell_typecache /obj/machinery/computer/piratepad_control/Initialize(mapload) ..() + if(isnull(nosell_typecache)) + nosell_typecache = typecacheof(/mob/living/silicon/robot) return INITIALIZE_HINT_LATELOAD /obj/machinery/computer/piratepad_control/multitool_act(mob/living/user, obj/item/multitool/I) @@ -285,7 +289,7 @@ for(var/atom/movable/AM in get_turf(pad)) if(AM == pad) continue - export_item_and_contents(AM, apply_elastic = FALSE, dry_run = TRUE, external_report = report) + export_item_and_contents(AM, apply_elastic = FALSE, dry_run = TRUE, external_report = report, ignore_typecache = nosell_typecache) for(var/datum/export/exported_datum in report.total_amount) status_report += exported_datum.total_printout(report,notes = FALSE) @@ -306,7 +310,7 @@ for(var/atom/movable/item_on_pad in get_turf(pad)) if(item_on_pad == pad) continue - export_item_and_contents(item_on_pad, apply_elastic = FALSE, delete_unsold = FALSE, external_report = report) + export_item_and_contents(item_on_pad, apply_elastic = FALSE, delete_unsold = FALSE, external_report = report, ignore_typecache = nosell_typecache) status_report = "Sold: " var/value = 0 diff --git a/code/modules/antagonists/spiders/spiders.dm b/code/modules/antagonists/spiders/spiders.dm index 6d0b86d24d70a..d42f1aea7b391 100644 --- a/code/modules/antagonists/spiders/spiders.dm +++ b/code/modules/antagonists/spiders/spiders.dm @@ -14,7 +14,7 @@ /datum/antagonist/spider/on_gain() forge_objectives(directive) - . = ..() + return ..() /datum/antagonist/spider/greet() . = ..() @@ -35,3 +35,23 @@ var/datum/objective/spider/objective = new(directive) objective.owner = owner objectives += objective + +/// Subtype for flesh spiders who don't have a queen +/datum/antagonist/spider/flesh + name = "Flesh Spider" + +/datum/antagonist/spider/flesh/forge_objectives() + var/datum/objective/custom/destroy = new() + destroy.owner = owner + destroy.explanation_text = "Wreak havoc and consume living flesh." + objectives += destroy + + var/datum/objective/survive/dont_die = new() + dont_die.owner = owner + objectives += dont_die + +/datum/antagonist/spider/flesh/greet() + . = ..() + to_chat(owner, span_boldwarning("An abomination of flesh set upon the station by changelings, \ + you are aggressive to all living beings outside of your species and know no loyalties... even to your creator. \ +
    Your malleable flesh quickly regenerates if you can avoid taking damage for a few seconds.")) diff --git a/code/modules/antagonists/traitor/objectives/kill_pet.dm b/code/modules/antagonists/traitor/objectives/kill_pet.dm index 51a54d99e300a..ae28f5dbf4c08 100644 --- a/code/modules/antagonists/traitor/objectives/kill_pet.dm +++ b/code/modules/antagonists/traitor/objectives/kill_pet.dm @@ -26,9 +26,9 @@ JOB_CHIEF_MEDICAL_OFFICER = /mob/living/simple_animal/pet/cat/runtime, JOB_CHIEF_ENGINEER = /mob/living/simple_animal/parrot/poly, JOB_QUARTERMASTER = list( - /mob/living/simple_animal/sloth/citrus, - /mob/living/simple_animal/sloth/paperwork, - /mob/living/simple_animal/hostile/gorilla/cargo_domestic, + /mob/living/basic/gorilla/cargorilla, + /mob/living/basic/sloth/citrus, + /mob/living/basic/sloth/paperwork, ) ) /// The head that we are targetting diff --git a/code/modules/art/paintings.dm b/code/modules/art/paintings.dm index e2448c1aaccf2..9a18a2b026951 100644 --- a/code/modules/art/paintings.dm +++ b/code/modules/art/paintings.dm @@ -249,7 +249,7 @@ painting_metadata.patron_name = user.real_name painting_metadata.credit_value = offer_amount last_patron = WEAKREF(user.mind) - to_chat(user, span_notice("Nanotrasen Trust Foundation thanks you for your contribution. You're now offical patron of this painting.")) + to_chat(user, span_notice("Nanotrasen Trust Foundation thanks you for your contribution. You're now an official patron of this painting.")) var/list/possible_frames = SSpersistent_paintings.get_available_frames(offer_amount) if(possible_frames.len <= 1) // Not much room for choices here. return diff --git a/code/modules/asset_cache/assets/rcd.dm b/code/modules/asset_cache/assets/rcd.dm index e4c54929dae19..16dee49c60fea 100644 --- a/code/modules/asset_cache/assets/rcd.dm +++ b/code/modules/asset_cache/assets/rcd.dm @@ -2,60 +2,44 @@ name = "rcd-tgui" /datum/asset/spritesheet/rcd/create_spritesheets() - //We load airlock icons seperatly from other icons cause they need overlays - - //load all category essential icon_states. format is icon_file = list of icon states we need from that file - var/list/essentials = list( - 'icons/obj/chairs.dmi' = list("bar"), - 'icons/obj/machines/wallmounts.dmi' = list("apc", "alarm_bitem", "fire_bitem"), - 'icons/obj/lighting.dmi' = list("floodlight_c1"), - 'icons/obj/assemblies/stock_parts.dmi' = list("box_1"), - 'icons/obj/bed.dmi' = list("bed"), - 'icons/obj/smooth_structures/catwalk.dmi' = list("catwalk-0"), - 'icons/hud/radial.dmi' = list("cnorth", "csouth", "ceast", "cwest", "chair", "secure_windoor", "stool", "wallfloor", "windowsize", "windowtype", "windoor"), - 'icons/obj/structures.dmi' = list("glass_table", "rack", "rwindow0", "reflector_base", "table", "window0", "girder"), - ) - - var/icon/icon - for(var/icon_file as anything in essentials) - for(var/icon_state as anything in essentials[icon_file]) - icon = icon(icon = icon_file, icon_state = icon_state) - if(icon_state == "window0" || icon_state == "rwindow0") - icon.Blend(icon(icon = 'icons/obj/structures.dmi', icon_state = "grille"), ICON_UNDERLAY) - Insert(sprite_name = sanitize_css_class_name(icon_state), I = icon) - - //for each airlock type we create its overlayed version with the suffix Glass in the sprite name - var/list/airlocks = list( - "Standard" = 'icons/obj/doors/airlocks/station/public.dmi', - "Public" = 'icons/obj/doors/airlocks/public/glass.dmi', - "Engineering" = 'icons/obj/doors/airlocks/station/engineering.dmi', - "Atmospherics" = 'icons/obj/doors/airlocks/station/atmos.dmi', - "Security" = 'icons/obj/doors/airlocks/station/security.dmi', - "Command" = 'icons/obj/doors/airlocks/station/command.dmi', - "Medical" = 'icons/obj/doors/airlocks/station/medical.dmi', - "Research" = 'icons/obj/doors/airlocks/station/research.dmi', - "Freezer" = 'icons/obj/doors/airlocks/station/freezer.dmi', - "Virology" = 'icons/obj/doors/airlocks/station/virology.dmi', - "Mining" = 'icons/obj/doors/airlocks/station/mining.dmi', - "Maintenance" = 'icons/obj/doors/airlocks/station/maintenance.dmi', - "External" = 'icons/obj/doors/airlocks/external/external.dmi', - "External Maintenance" = 'icons/obj/doors/airlocks/station/maintenanceexternal.dmi', - "Airtight Hatch" = 'icons/obj/doors/airlocks/hatch/centcom.dmi', - "Maintenance Hatch" = 'icons/obj/doors/airlocks/hatch/maintenance.dmi' - ) - //these 3 types dont have glass doors - var/list/exclusion = list("Freezer", "Airtight Hatch", "Maintenance Hatch") - - for(var/airlock_name in airlocks) - //solid door with overlay - icon = icon(icon = airlocks[airlock_name] , icon_state = "closed" , dir = SOUTH) - icon.Blend(icon(icon = airlocks[airlock_name], icon_state = "fill_closed", dir = SOUTH), ICON_OVERLAY) - Insert(sprite_name = sanitize_css_class_name(airlock_name), I = icon) - - //exclude these glass types - if(airlock_name in exclusion) + for(var/root_category in GLOB.rcd_designs) + + var/list/category_designs = GLOB.rcd_designs[root_category] + if(!length(category_designs)) continue - //glass door no overlay - icon = icon(airlocks[airlock_name] , "closed" , SOUTH) - Insert(sprite_name = sanitize_css_class_name("[airlock_name]Glass"), I = icon) + for(var/category in category_designs) + var/list/designs = category_designs[category] + + var/sprite_name + var/icon/sprite_icon + for(var/list/design as anything in designs) + var/atom/movable/path = design[RCD_DESIGN_PATH] + if(!ispath(path)) + continue + sprite_name = initial(path.name) + + //icon for windows are blended with grills if required and loaded from radial menu + if(ispath(path, /obj/structure/window)) + if(path == /obj/structure/window) + sprite_icon = icon(icon = 'icons/hud/radial.dmi', icon_state = "windowsize") + else if(path == /obj/structure/window/reinforced) + sprite_icon = icon(icon = 'icons/hud/radial.dmi', icon_state = "windowtype") + else if(path == /obj/structure/window/fulltile || path == /obj/structure/window/reinforced/fulltile) + sprite_icon = icon(icon = initial(path.icon), icon_state = initial(path.icon_state)) + sprite_icon.Blend(icon(icon = 'icons/obj/structures.dmi', icon_state = "grille"), ICON_UNDERLAY) + + //icons for solid airlocks have an added solid overlay on top of their glass icons + else if(ispath(path, /obj/machinery/door/airlock)) + var/obj/machinery/door/airlock/airlock_path = path + var/airlock_icon = initial(airlock_path.icon) + + sprite_icon = icon(icon = airlock_icon, icon_state = "closed") + if(!initial(airlock_path.glass)) + sprite_icon.Blend(icon(icon = airlock_icon, icon_state = "fill_closed"), ICON_OVERLAY) + + //for all other icons we load the paths default icon & icon state + else + sprite_icon = icon(icon = initial(path.icon), icon_state = initial(path.icon_state)) + + Insert(sanitize_css_class_name(sprite_name), sprite_icon) diff --git a/code/modules/atmospherics/gasmixtures/gas_types.dm b/code/modules/atmospherics/gasmixtures/gas_types.dm index ae3367ace5932..060b5a12ae9a9 100644 --- a/code/modules/atmospherics/gasmixtures/gas_types.dm +++ b/code/modules/atmospherics/gasmixtures/gas_types.dm @@ -64,6 +64,8 @@ ///How does a single mole of this gas sell for? Formula to calculate maximum value is in code\modules\cargo\exports\large_objects.dm. Doesn't matter for roundstart gasses. var/base_value = 0 var/desc + ///RGB code for use when a generic color representing the gas is needed. Colors taken from contants.ts + var/primary_color /datum/gas/oxygen @@ -74,6 +76,7 @@ purchaseable = TRUE base_value = 0.2 desc = "The gas most life forms need to be able to survive. Also an oxidizer." + primary_color = "#0000ff" /datum/gas/nitrogen id = GAS_N2 @@ -83,6 +86,7 @@ purchaseable = TRUE base_value = 0.1 desc = "A very common gas that used to pad artifical atmospheres to habitable pressure." + primary_color = "#ffff00" /datum/gas/carbon_dioxide //what the fuck is this? id = GAS_CO2 @@ -93,6 +97,7 @@ purchaseable = TRUE base_value = 0.2 desc = "What the fuck is carbon dioxide?" + primary_color = "#808080" /datum/gas/plasma id = GAS_PLASMA @@ -104,6 +109,7 @@ rarity = 800 base_value = 1.5 desc = "A flammable gas with many other curious properties. It's research is one of NT's primary objective." + primary_color = "#ffc0cb" /datum/gas/water_vapor id = GAS_WATER_VAPOR @@ -116,6 +122,7 @@ purchaseable = TRUE base_value = 0.5 desc = "Water, in gas form. Makes things slippery." + primary_color = "#b0c4de" /datum/gas/hypernoblium id = GAS_HYPER_NOBLIUM @@ -127,6 +134,7 @@ rarity = 50 base_value = 2.5 desc = "The most noble gas of them all. High quantities of hyper-noblium actively prevents reactions from occuring." + primary_color = "#008080" /datum/gas/nitrous_oxide id = GAS_N2O @@ -140,6 +148,7 @@ purchaseable = TRUE base_value = 1.5 desc = "Causes drowsiness, euphoria, and eventually unconsciousness." + primary_color = "#ffe4c4" /datum/gas/nitrium id = GAS_NITRIUM @@ -152,6 +161,7 @@ rarity = 1 base_value = 6 desc = "An experimental performance enhancing gas. Nitrium can have amplified effects as more of it gets into your bloodstream." + primary_color = "#a52a2a" /datum/gas/tritium id = GAS_TRITIUM @@ -164,6 +174,7 @@ rarity = 300 base_value = 2.5 desc = "A highly flammable and radioctive gas." + primary_color = "#32cd32" /datum/gas/bz id = GAS_BZ @@ -175,6 +186,7 @@ purchaseable = TRUE base_value = 1.5 desc = "A powerful hallucinogenic nerve agent able to induce cognitive damage." + primary_color = "#9370db" /datum/gas/pluoxium id = GAS_PLUOXIUM @@ -184,6 +196,7 @@ rarity = 200 base_value = 2.5 desc = "A gas that could supply even more oxygen to the bloodstream when inhaled, without being an oxidizer." + primary_color = "#7b68ee" /datum/gas/miasma id = GAS_MIASMA @@ -195,6 +208,7 @@ rarity = 250 base_value = 1 desc = "Not necessarily a gas, miasma refers to biological pollutants found in the atmosphere." + primary_color = "#808000" /datum/gas/freon id = GAS_FREON @@ -207,6 +221,7 @@ rarity = 10 base_value = 5 desc = "A coolant gas. Mainly used for it's endothermic reaction with oxygen." + primary_color = "#afeeee" /datum/gas/hydrogen id = GAS_HYDROGEN @@ -217,6 +232,7 @@ rarity = 600 base_value = 1 desc = "A highly flammable gas." + primary_color = "#ffffff" /datum/gas/healium id = GAS_HEALIUM @@ -228,6 +244,7 @@ rarity = 300 base_value = 5.5 desc = "Causes deep, regenerative sleep." + primary_color = "#fa8072" /datum/gas/proto_nitrate id = GAS_PROTO_NITRATE @@ -239,6 +256,7 @@ rarity = 200 base_value = 2.5 desc = "A very volatile gas that reacts differently with various gases." + primary_color = "#adff2f" /datum/gas/zauker id = GAS_ZAUKER @@ -250,6 +268,7 @@ rarity = 1 base_value = 7 desc = "A highly toxic gas, it's production is highly regulated on top of being difficult. It also breaks down when in contact with nitrogen." + primary_color = "#006400" /datum/gas/halon id = GAS_HALON @@ -261,6 +280,7 @@ rarity = 300 base_value = 4 desc = "A potent fire supressant. Removes oxygen from high temperature fires and cools down the area" + primary_color = "#800080" /datum/gas/helium id = GAS_HELIUM @@ -270,6 +290,7 @@ rarity = 50 base_value = 3.5 desc = "A very inert gas produced by the fusion of hydrogen and it's derivatives." + primary_color = "#f0f8ff" /datum/gas/antinoblium id = GAS_ANTINOBLIUM @@ -282,6 +303,7 @@ rarity = 1 base_value = 10 desc = "We still don't know what it does, but it sells for a lot." + primary_color = "#800000" /obj/effect/overlay/gas icon = 'icons/effects/atmospherics.dmi' diff --git a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm index e629c14e0fef0..24d1e63e42a25 100644 --- a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm +++ b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm @@ -287,6 +287,8 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) "refID" = REF(vent), "long_name" = sanitize(vent.name), "power" = vent.on, + "overclock" = vent.fan_overclocked, + "integrity" = vent.get_integrity_percentage(), "checks" = vent.pressure_checks, "excheck" = vent.pressure_checks & ATMOS_EXTERNAL_BOUND, "incheck" = vent.pressure_checks & ATMOS_INTERNAL_BOUND, @@ -356,6 +358,14 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) powering.on = !!params["val"] powering.atmos_conditions_changed() powering.update_appearance(UPDATE_ICON) + + if("overclock") + if(isnull(vent)) + return TRUE + vent.toggle_overclock() + vent.update_appearance(UPDATE_ICON) + return TRUE + if ("direction") if (isnull(vent)) return TRUE diff --git a/code/modules/atmospherics/machinery/air_alarm/air_alarm_interact.dm b/code/modules/atmospherics/machinery/air_alarm/air_alarm_interact.dm index 3ec8fbdd99b37..3ac90db0c1852 100644 --- a/code/modules/atmospherics/machinery/air_alarm/air_alarm_interact.dm +++ b/code/modules/atmospherics/machinery/air_alarm/air_alarm_interact.dm @@ -47,17 +47,15 @@ /obj/machinery/airalarm/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) if((buildstage == AIR_ALARM_BUILD_NO_CIRCUIT) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS)) - return list("mode" = RCD_WALLFRAME, "delay" = 2 SECONDS, "cost" = 1) + return list("delay" = 2 SECONDS, "cost" = 1) return FALSE -/obj/machinery/airalarm/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_WALLFRAME) - user.visible_message(span_notice("[user] fabricates a circuit and places it into [src]."), \ - span_notice("You adapt an air alarm circuit and slot it into the assembly.")) - buildstage = AIR_ALARM_BUILD_NO_WIRES - update_appearance() - return TRUE +/obj/machinery/airalarm/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(rcd_data["[RCD_DESIGN_MODE]"] == RCD_WALLFRAME) + balloon_alert(user, "circuit installed") + buildstage = AIR_ALARM_BUILD_NO_WIRES + update_appearance() + return TRUE return FALSE /obj/machinery/airalarm/attack_hand_secondary(mob/user, list/modifiers) diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index 417e5055de755..62c2b562d5129 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -352,9 +352,9 @@ return /** - * Similar to set_pipenet() but instead of setting a network to a pipeline, it replaces the old pipeline with a new one, called by Merge() in datum_pipeline.dm + * Replaces the connection to the old_pipenet with the new_pipenet */ -/obj/machinery/atmospherics/proc/replace_pipenet() +/obj/machinery/atmospherics/proc/replace_pipenet(datum/pipeline/old_pipenet, datum/pipeline/new_pipenet) return /** diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm index 3422f3e3adfa5..25713e55e37f4 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm @@ -15,6 +15,8 @@ shift_underlay_only = FALSE pipe_state = "uvent" vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED + // vents are more complex machinery and so are less resistant to damage + max_integrity = 100 ///Direction of pumping the gas (ATMOS_DIRECTION_RELEASING or ATMOS_DIRECTION_SIPHONING) var/pump_direction = ATMOS_DIRECTION_RELEASING @@ -34,6 +36,18 @@ ///area this vent is assigned to var/area/assigned_area + /// Is this vent currently overclocked, removing pressure limits but damaging the fan? + var/fan_overclocked = FALSE + + /// Rate of damage per atmos process to the fan when overclocked. Set to 0 to disable damage. + var/fan_damage_rate = 0.5 + + /// The cached string we show for examine that lets you know how fucked up the fan is. + var/examine_condition + + /// Datum for managing the overclock sound loop + var/datum/looping_sound/vent_pump_overclock/sound_loop + /obj/machinery/atmospherics/components/unary/vent_pump/Initialize(mapload) if(!id_tag) id_tag = assign_random_name() @@ -42,16 +56,44 @@ tool_screentips = string_assoc_nested_list(list( TOOL_MULTITOOL = list( SCREENTIP_CONTEXT_LMB = "Log to link later with air sensor", - ) + ), + TOOL_SCREWDRIVER = list( + SCREENTIP_CONTEXT_LMB = "Repair", + ), )) AddElement(/datum/element/contextual_screentip_tools, tool_screentips) . = ..() + sound_loop = new(src) assign_to_area() +/obj/machinery/atmospherics/components/unary/vent_pump/on_update_integrity(old_value, new_value) + . = ..() + var/condition_string + switch(get_integrity_percentage()) + if(1) + condition_string = "perfect" + if(0.75 to 0.99) + condition_string = "good" + if(0.50 to 0.74) + condition_string = "okay" + if(0.25 to 0.49) + condition_string = "bad" + else + condition_string = "terrible" + examine_condition = "The fan is in [condition_string] condition." + /obj/machinery/atmospherics/components/unary/vent_pump/examine(mob/user) . = ..() . += span_notice("You can link it with an air sensor using a multitool.") + if(fan_overclocked) + . += span_warning("It is currently overclocked causing it to take damage over time.") + + if(get_integrity() > 0) + . += span_notice(examine_condition) + else + . += span_warning("The fan is broken.") + /obj/machinery/atmospherics/components/unary/vent_pump/multitool_act(mob/living/user, obj/item/multitool/multi_tool) if(istype(multi_tool.buffer, /obj/machinery/air_sensor)) var/obj/machinery/air_sensor/sensor = multi_tool.buffer @@ -63,8 +105,33 @@ multi_tool.set_buffer(src) return TOOL_ACT_TOOLTYPE_SUCCESS +/obj/machinery/atmospherics/components/unary/vent_pump/screwdriver_act(mob/living/user, obj/item/tool) + var/time_to_repair = (10 SECONDS) * (1 - get_integrity_percentage()) + if(!time_to_repair) + return FALSE + + balloon_alert(user, "repairing vent...") + if(do_after(user, time_to_repair, src)) + balloon_alert(user, "vent repaired") + repair_damage(max_integrity) + + else + balloon_alert(user, "interrupted!") + return TOOL_ACT_TOOLTYPE_SUCCESS + +/obj/machinery/atmospherics/components/unary/vent_pump/atom_fix() + set_is_operational(TRUE) + update_appearance() + return ..() + +/obj/machinery/atmospherics/components/unary/vent_pump/atom_break(damage_flag) + set_is_operational(FALSE) + update_appearance() + return ..() + /obj/machinery/atmospherics/components/unary/vent_pump/Destroy() disconnect_from_area() + QDEL_NULL(sound_loop) var/area/vent_area = get_area(src) if(vent_area) @@ -107,6 +174,17 @@ . = ..() disconnect_from_area(area_to_unregister) +/obj/machinery/atmospherics/components/unary/vent_pump/update_overlays() + . = ..() + if(!powered()) + return + + if(get_integrity() <= 0) + . += mutable_appearance(icon, "broken") + + else if(fan_overclocked) + . += mutable_appearance(icon, "overclocked") + /obj/machinery/atmospherics/components/unary/vent_pump/update_icon_nopipes() cut_overlays() if(showpipe) @@ -144,6 +222,20 @@ else // pump_direction == SIPHONING icon_state = "vent_in" +/obj/machinery/atmospherics/components/unary/vent_pump/proc/toggle_overclock(from_break = FALSE) + fan_overclocked = !fan_overclocked + + if(from_break) + playsound(src, 'sound/machines/fan_break.ogg', 100) + fan_overclocked = FALSE + + if(fan_overclocked) + sound_loop.start() + else + sound_loop.stop() + + update_appearance() + /obj/machinery/atmospherics/components/unary/vent_pump/process_atmos() if(!is_operational) return @@ -154,6 +246,17 @@ var/turf/open/us = loc if(!istype(us)) return + + var/current_integrity = get_integrity() + if(fan_overclocked) + take_damage(fan_damage_rate, sound_effect=FALSE) + if(current_integrity == 0) + on = FALSE + set_is_operational(FALSE) + toggle_overclock(from_break = TRUE) + return + + var/percent_integrity = get_integrity_percentage() var/datum/gas_mixture/air_contents = airs[1] var/datum/gas_mixture/environment = us.return_air() var/environment_pressure = environment.return_pressure() @@ -168,9 +271,13 @@ if(pressure_delta > 0) if(air_contents.temperature > 0) - if(environment_pressure >= 50 * ONE_ATMOSPHERE) + if(!fan_overclocked && (environment_pressure >= 50 * ONE_ATMOSPHERE)) return FALSE + var/transfer_moles = (pressure_delta * environment.volume) / (air_contents.temperature * R_IDEAL_GAS_EQUATION) + if(!fan_overclocked && (percent_integrity < 1)) + transfer_moles *= percent_integrity + var/datum/gas_mixture/removed = air_contents.remove(transfer_moles) if(!removed || !removed.total_moles()) @@ -187,9 +294,12 @@ pressure_delta = min(pressure_delta, (internal_pressure_bound - air_contents.return_pressure())) if(pressure_delta > 0 && environment.temperature > 0) - if(air_contents.return_pressure() >= 50 * ONE_ATMOSPHERE) + if(!fan_overclocked && (air_contents.return_pressure() >= 50 * ONE_ATMOSPHERE)) return FALSE + var/transfer_moles = (pressure_delta * air_contents.volume) / (environment.temperature * R_IDEAL_GAS_EQUATION) + if(!fan_overclocked && (percent_integrity < 1)) + transfer_moles *= percent_integrity var/datum/gas_mixture/removed = loc.remove_air(transfer_moles) diff --git a/code/modules/atmospherics/machinery/datum_pipeline.dm b/code/modules/atmospherics/machinery/datum_pipeline.dm index b8ad06e25abb1..77a29b5461d4f 100644 --- a/code/modules/atmospherics/machinery/datum_pipeline.dm +++ b/code/modules/atmospherics/machinery/datum_pipeline.dm @@ -1,5 +1,7 @@ /datum/pipeline + /// The gases contained within this pipeline var/datum/gas_mixture/air + /// The gas_mixtures of objects directly connected to this pipeline var/list/datum/gas_mixture/other_airs var/list/obj/machinery/atmospherics/pipe/members @@ -8,6 +10,10 @@ /// We're essentially caching this to avoid needing to filter over it when processing our machines var/list/obj/machinery/atmospherics/components/require_custom_reconcilation + /// The weighted color blend of the gas mixture in this pipeline + var/gasmix_color + /// A named list of icon_file:overlay_object that gets automatically colored when the gasmix_color updates + var/list/gas_visuals ///Should we equalize air amoung all our members? var/update = TRUE @@ -19,6 +25,7 @@ members = list() other_atmos_machines = list() require_custom_reconcilation = list() + gas_visuals = list() SSair.networks += src /datum/pipeline/Destroy() @@ -28,7 +35,7 @@ if(air?.volume) temporarily_store_air() for(var/obj/machinery/atmospherics/pipe/considered_pipe in members) - considered_pipe.parent = null + considered_pipe.replace_pipenet(considered_pipe.parent, null) if(QDELETED(considered_pipe)) continue SSair.add_to_rebuild_queue(considered_pipe) @@ -42,6 +49,13 @@ reconcile_air() //Only react if the mix has changed, and don't keep updating if it hasn't update = air.react(src) + CalculateGasmixColor(air) + +/datum/pipeline/proc/set_air(datum/gas_mixture/new_air) + if(new_air == air) + return + air = new_air + CalculateGasmixColor(air) ///Preps a pipeline for rebuilding, insterts it into the rebuild queue /datum/pipeline/proc/build_pipeline(obj/machinery/atmospherics/base) @@ -52,13 +66,13 @@ volume = considered_pipe.volume members += considered_pipe if(considered_pipe.air_temporary) - air = considered_pipe.air_temporary + set_air(considered_pipe.air_temporary) considered_pipe.air_temporary = null else add_machinery_member(base) if(!air) - air = new + set_air(new /datum/gas_mixture) air.volume = volume SSair.add_to_expansion(src, base) @@ -71,13 +85,13 @@ volume = considered_pipe.volume members += considered_pipe if(considered_pipe.air_temporary) - air = considered_pipe.air_temporary + set_air(considered_pipe.air_temporary) considered_pipe.air_temporary = null else add_machinery_member(base) if(!air) - air = new + set_air(new /datum/gas_mixture) var/list/possible_expansions = list(base) while(possible_expansions.len) for(var/obj/machinery/atmospherics/borderline in possible_expansions) @@ -105,7 +119,7 @@ possible_expansions += item volume += item.volume - item.parent = src + item.replace_pipenet(item.parent, src) if(item.air_temporary) air.merge(item.air_temporary) @@ -142,7 +156,7 @@ var/obj/machinery/atmospherics/pipe/reference_pipe = reference_device if(reference_pipe.parent) merge(reference_pipe.parent) - reference_pipe.parent = src + reference_pipe.replace_pipenet(reference_pipe.parent, src) var/list/adjacent = reference_pipe.pipeline_expansion() for(var/obj/machinery/atmospherics/pipe/adjacent_pipe in adjacent) if(adjacent_pipe.parent == src) @@ -159,7 +173,7 @@ air.volume += parent_pipeline.air.volume members.Add(parent_pipeline.members) for(var/obj/machinery/atmospherics/pipe/reference_pipe in parent_pipeline.members) - reference_pipe.parent = src + reference_pipe.replace_pipenet(reference_pipe.parent, src) air.merge(parent_pipeline.air) for(var/obj/machinery/atmospherics/components/reference_component in parent_pipeline.other_atmos_machines) reference_component.replace_pipenet(parent_pipeline, src) @@ -280,3 +294,64 @@ //Update individual gas_mixtures by volume ratio for(var/datum/gas_mixture/gas_mixture as anything in gas_mixture_list) gas_mixture.copy_from_ratio(total_gas_mixture, gas_mixture.volume / volume_sum) + +//-------------------- +// GAS VISUALS STUFF +// +// If I could have gotten layer filters to obey the RESET_COLOR appearance flag I would have used that here +// so that only a single overlay object needs to exist for all pipelines per icon file. It shouldn't be too +// hard to switch over to that if it becomes possible in the future or some other equivalent feature is added. + +/** + * Used to create and/or get the gas visual overlay created using the given icon file. + * The color is automatically kept up to date and expected to be used as a vis_contents object. + */ +/datum/pipeline/proc/GetGasVisual(icon/icon_file) + if(gas_visuals[icon_file]) + return gas_visuals[icon_file] + + var/obj/effect/abstract/new_overlay = new + new_overlay.icon = icon_file + new_overlay.appearance_flags = RESET_COLOR | KEEP_APART + new_overlay.vis_flags = VIS_INHERIT_ICON_STATE | VIS_INHERIT_LAYER | VIS_INHERIT_PLANE | VIS_INHERIT_ID + new_overlay.color = gasmix_color + + gas_visuals[icon_file] = new_overlay + return new_overlay + +/// Called when the gasmix color has changed and the gas visuals need to be updated. +/datum/pipeline/proc/UpdateGasVisuals() + for(var/icon/source as anything in gas_visuals) + var/obj/effect/abstract/overlay = gas_visuals[source] + animate(overlay, time=5, color=gasmix_color) + +/// After updating, this proc handles looking at the new gas mixture and blends the colors together according to percentage of the gas mix. +/datum/pipeline/proc/CalculateGasmixColor(datum/gas_mixture/source) + SIGNAL_HANDLER + + var/current_weight = 0 + var/current_color + for(var/datum/gas/gas_path as anything in air.gases) + var/gas_weight = air.gases[gas_path][MOLES] + if(!gas_weight) + continue + var/gas_color = RGBtoHSV(initial(gas_path.primary_color)) + current_weight += gas_weight + if(!current_color) + current_color = gas_color + else + current_color = BlendHSV(current_color, gas_color, gas_weight / current_weight) + + if(!current_color) + current_color = "#000000" + else + // Empty weight is prety much arbitrary, just tuned to make the color change from black reasonably quickly without hitting max color immediately + var/empty_weight = (air.volume * 1.5 - current_weight) / 10 + if(empty_weight > 0) + current_color = BlendHSV("#000000", current_color, current_weight / (empty_weight + current_weight)) + + current_color = HSVtoRGB(current_color) + + if(gasmix_color != current_color) + gasmix_color = current_color + UpdateGasVisuals() diff --git a/code/modules/atmospherics/machinery/pipes/bridge_pipe.dm b/code/modules/atmospherics/machinery/pipes/bridge_pipe.dm index 9cda298ccd4c9..472cb3ed2034a 100644 --- a/code/modules/atmospherics/machinery/pipes/bridge_pipe.dm +++ b/code/modules/atmospherics/machinery/pipes/bridge_pipe.dm @@ -13,6 +13,8 @@ construction_type = /obj/item/pipe/binary pipe_state = "bridge_center" + has_gas_visuals = FALSE + /obj/machinery/atmospherics/pipe/bridge_pipe/set_init_directions() switch(dir) if(NORTH, SOUTH) diff --git a/code/modules/atmospherics/machinery/pipes/color_adapter.dm b/code/modules/atmospherics/machinery/pipes/color_adapter.dm index 02c550fd55859..06812aaa6496f 100644 --- a/code/modules/atmospherics/machinery/pipes/color_adapter.dm +++ b/code/modules/atmospherics/machinery/pipes/color_adapter.dm @@ -16,6 +16,8 @@ paintable = FALSE hide = FALSE + has_gas_visuals = FALSE + ///cache for the icons var/static/list/mutable_appearance/center_cache = list() @@ -34,7 +36,7 @@ . = ..() var/mutable_appearance/center = center_cache["[piping_layer]"] if(!center) - center = mutable_appearance(icon, "adapter_center") + center = mutable_appearance(initial(icon), "adapter_center") PIPING_LAYER_DOUBLE_SHIFT(center, piping_layer) center_cache["[piping_layer]"] = center . += center diff --git a/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm b/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm index fd43e315527b4..72c9c74278123 100644 --- a/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm +++ b/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm @@ -8,6 +8,8 @@ hide = FALSE + has_gas_visuals = FALSE + /obj/machinery/atmospherics/pipe/heat_exchanging/Initialize(mapload) . = ..() diff --git a/code/modules/atmospherics/machinery/pipes/heat_exchange/junction.dm b/code/modules/atmospherics/machinery/pipes/heat_exchange/junction.dm index 00ef058333a40..a6bfcc533d240 100644 --- a/code/modules/atmospherics/machinery/pipes/heat_exchange/junction.dm +++ b/code/modules/atmospherics/machinery/pipes/heat_exchange/junction.dm @@ -15,6 +15,8 @@ construction_type = /obj/item/pipe/directional pipe_state = "junction" + has_gas_visuals = FALSE + /obj/machinery/atmospherics/pipe/heat_exchanging/junction/set_init_directions() switch(dir) if(NORTH, SOUTH) diff --git a/code/modules/atmospherics/machinery/pipes/heat_exchange/manifold.dm b/code/modules/atmospherics/machinery/pipes/heat_exchange/manifold.dm index e340d7f54ccf1..5841486ba9bbc 100644 --- a/code/modules/atmospherics/machinery/pipes/heat_exchange/manifold.dm +++ b/code/modules/atmospherics/machinery/pipes/heat_exchange/manifold.dm @@ -16,6 +16,8 @@ construction_type = /obj/item/pipe/trinary pipe_state = "he_manifold" + has_gas_visuals = FALSE + /obj/machinery/atmospherics/pipe/heat_exchanging/manifold/set_init_directions() initialize_directions = ALL_CARDINALS initialize_directions &= ~dir diff --git a/code/modules/atmospherics/machinery/pipes/heat_exchange/manifold4w.dm b/code/modules/atmospherics/machinery/pipes/heat_exchange/manifold4w.dm index 03ef32b435453..83870ee210b67 100644 --- a/code/modules/atmospherics/machinery/pipes/heat_exchange/manifold4w.dm +++ b/code/modules/atmospherics/machinery/pipes/heat_exchange/manifold4w.dm @@ -15,6 +15,8 @@ construction_type = /obj/item/pipe/quaternary pipe_state = "he_manifold4w" + has_gas_visuals = FALSE + /obj/machinery/atmospherics/pipe/heat_exchanging/manifold4w/set_init_directions() initialize_directions = initial(initialize_directions) diff --git a/code/modules/atmospherics/machinery/pipes/layermanifold.dm b/code/modules/atmospherics/machinery/pipes/layermanifold.dm index bdfbda9ba6c78..09c8a3a9367ae 100644 --- a/code/modules/atmospherics/machinery/pipes/layermanifold.dm +++ b/code/modules/atmospherics/machinery/pipes/layermanifold.dm @@ -12,6 +12,7 @@ construction_type = /obj/item/pipe/binary pipe_state = "manifoldlayer" paintable = TRUE + has_gas_visuals = FALSE ///Reference to all the nodes in the front var/list/front_nodes diff --git a/code/modules/atmospherics/machinery/pipes/multiz.dm b/code/modules/atmospherics/machinery/pipes/multiz.dm index cfc24ab82912b..7e14b8a98063e 100644 --- a/code/modules/atmospherics/machinery/pipes/multiz.dm +++ b/code/modules/atmospherics/machinery/pipes/multiz.dm @@ -15,6 +15,8 @@ construction_type = /obj/item/pipe/directional pipe_state = "multiz" + has_gas_visuals = FALSE + ///Our central icon var/mutable_appearance/center = null ///The pipe icon diff --git a/code/modules/atmospherics/machinery/pipes/pipe_spritesheet_helper.dm b/code/modules/atmospherics/machinery/pipes/pipe_spritesheet_helper.dm new file mode 100644 index 0000000000000..9642442a9733f --- /dev/null +++ b/code/modules/atmospherics/machinery/pipes/pipe_spritesheet_helper.dm @@ -0,0 +1,148 @@ +/client/proc/GeneratePipeSpritesheet() + set name = "Generate Pipe Spritesheet" + set category = "Debug" + + var/datum/pipe_icon_generator/generator = new + generator.Start() + fcopy(generator.generated_icons, "icons/obj/pipes_n_cables/!pipes_bitmask.dmi") + + generator.Start("-gas") + fcopy(generator.generated_icons, "icons/obj/pipes_n_cables/!pipe_gas_overlays.dmi") + +/datum/pipe_icon_generator + var/static/icon/template_pieces = icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi') + var/static/list/icon/damage_masks = list( + "[NORTH]"=icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "damage_mask", NORTH), + "[EAST]"=icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "damage_mask", EAST), + "[SOUTH]"=icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "damage_mask", SOUTH), + "[WEST]"=icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "damage_mask", WEST), + ) + + var/icon/generated_icons + +/datum/pipe_icon_generator/proc/Start(icon_state_suffix="") + var/list/outputs = list() + for(var/layer in 1 to 5) + // Since dirs are bitflags, if we want to iterate over every possible direction + // combination we just need to iterate over every number that can be contained in 4 bits. + // + // I wrote this all the hard way originally >.> + for(var/combined_dirs in 1 to 15) + switch(combined_dirs) + if(NORTH, EAST, SOUTH, WEST) + continue + + outputs += GeneratePipeDir(icon_state_suffix, layer, combined_dirs) + + generated_icons = icon('icons/testing/greyscale_error.dmi') + for(var/icon/generated_icon as anything in outputs) + var/pending_icon_state = outputs[generated_icon] + generated_icons.Insert(generated_icon, pending_icon_state) + +/datum/pipe_icon_generator/proc/GeneratePipeDir(icon_state_suffix, layer, combined_dirs) + var/list/output + + switch(combined_dirs) + if(NORTH | SOUTH, EAST | WEST) + output = GeneratePipeStraight(icon_state_suffix, layer, combined_dirs) + if(NORTH | EAST, SOUTH | EAST, SOUTH | WEST, NORTH | WEST) + output = GeneratePipeElbow(icon_state_suffix, layer, combined_dirs) + if(NORTH | EAST | SOUTH, EAST | SOUTH | WEST, SOUTH | WEST | NORTH, WEST | NORTH | EAST) + output = GeneratePipeTJunction(icon_state_suffix, layer, combined_dirs) + if(NORTH | EAST | SOUTH | WEST) + output = GeneratePipeCross(icon_state_suffix, layer, combined_dirs) + + var/shift_amount = (layer - 1) * 5 + for(var/icon/sprite as anything in output) + if(shift_amount > 0) + sprite.Shift(EAST, shift_amount) + sprite.Shift(NORTH, shift_amount) + sprite.Crop(33, 33, 64, 64) + + return output + +/// Generates all variants of damaged pipe from a given icon and the dirs that can be broken +/datum/pipe_icon_generator/proc/GenerateDamaged(icon/working, layer, dirs, x_offset=1, y_offset=1) + var/outputs = list() + var/completed = list() + for(var/combined_dirs in 1 to 15) + combined_dirs &= dirs + + var/completion_key = "[combined_dirs]" + if(completed[completion_key] || (combined_dirs == NONE)) + continue + completed[completion_key] = TRUE + + var/icon/damaged = icon(working) + for(var/i in 0 to 3) + var/dir = 1 << i + if(!(combined_dirs & dir)) + continue + var/icon/damage_mask = damage_masks["[dir]"] + damaged.Blend(damage_mask, ICON_MULTIPLY, x_offset, y_offset) + + var/icon_state_dirs = (dirs & ~combined_dirs) | CARDINAL_TO_SHORTPIPES(combined_dirs) + outputs[damaged] = "[icon_state_dirs]_[layer]" + return outputs + + +/datum/pipe_icon_generator/proc/GeneratePipeStraight(icon_state_suffix, layer, combined_dirs) + var/list/output = list() + var/north_or_east = combined_dirs & (NORTH | EAST) + var/icon/working = icon(template_pieces, "straight[icon_state_suffix]", north_or_east) + + output[working] = "[combined_dirs]_[layer]" + + var/offset = 1 + (5-layer) * 2 + switch(combined_dirs) + if(NORTH | SOUTH) + output += GenerateDamaged(working, layer, combined_dirs, y_offset=offset) + if(EAST | WEST) + output += GenerateDamaged(working, layer, combined_dirs, x_offset=offset) + + return output + +/datum/pipe_icon_generator/proc/GeneratePipeElbow(icon_state_suffix, layer, combined_dirs) + var/list/output = list() + var/icon/working + switch(combined_dirs) + if(NORTH | EAST) + working = icon(template_pieces, "elbow[icon_state_suffix]", NORTH) + if(NORTH | WEST) + working = icon(template_pieces, "elbow[icon_state_suffix]", WEST) + if(SOUTH | EAST) + working = icon(template_pieces, "elbow[icon_state_suffix]", EAST) + if(SOUTH | WEST) + working = icon(template_pieces, "elbow[icon_state_suffix]", SOUTH) + + output[working] = "[combined_dirs]_[layer]" + output += GenerateDamaged(working, layer, combined_dirs) + + return output + +/datum/pipe_icon_generator/proc/GeneratePipeTJunction(icon_state_suffix, layer, combined_dirs) + var/list/output = list() + var/icon/working + switch(combined_dirs) + if(WEST | NORTH | EAST) + working = icon(template_pieces, "tee[icon_state_suffix]", NORTH) + if(NORTH | EAST | SOUTH) + working = icon(template_pieces, "tee[icon_state_suffix]", EAST) + if(EAST | SOUTH | WEST) + working = icon(template_pieces, "tee[icon_state_suffix]", SOUTH) + if(SOUTH | WEST | NORTH) + working = icon(template_pieces, "tee[icon_state_suffix]", WEST) + + output[working] = "[combined_dirs]_[layer]" + output += GenerateDamaged(working, layer, combined_dirs) + + return output + +/datum/pipe_icon_generator/proc/GeneratePipeCross(icon_state_suffix, layer, combined_dirs) + var/list/output = list() + var/icon/working = icon(template_pieces, "cross[icon_state_suffix]") + + output[working] = "[combined_dirs]_[layer]" + output += GenerateDamaged(working, layer, combined_dirs) + + return output diff --git a/code/modules/atmospherics/machinery/pipes/pipes.dm b/code/modules/atmospherics/machinery/pipes/pipes.dm index c1c128c2e808a..c94caf3117476 100644 --- a/code/modules/atmospherics/machinery/pipes/pipes.dm +++ b/code/modules/atmospherics/machinery/pipes/pipes.dm @@ -1,15 +1,22 @@ /obj/machinery/atmospherics/pipe - icon = 'icons/obj/pipes_n_cables/pipes_bitmask.dmi' + icon = 'icons/obj/pipes_n_cables/!pipes_bitmask.dmi' damage_deflection = 12 - var/datum/gas_mixture/air_temporary //used when reconstructing a pipeline that broke + /// Temporary holder for gases in the absence of a pipeline + var/datum/gas_mixture/air_temporary + + /// The gas capacity this pipe contributes to a pipeline var/volume = 0 use_power = NO_POWER_USE can_unwrench = 1 + /// The pipeline this pipe is a member of var/datum/pipeline/parent = null paintable = TRUE + /// Determines if this pipe will be given gas visuals + var/has_gas_visuals = TRUE + //Buckling can_buckle = TRUE buckle_requires_restraints = TRUE @@ -27,6 +34,23 @@ if(hide) AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) //if changing this, change the subtypes RemoveElements too, because thats how bespoke works +/obj/machinery/atmospherics/pipe/Destroy() + QDEL_NULL(parent) + + releaseAirToTurf() + + var/turf/local_turf = loc + for(var/obj/machinery/meter/meter in local_turf) + if(meter.target != src) + continue + var/obj/item/pipe_meter/meter_object = new (local_turf) + meter.transfer_fingerprints_to(meter_object) + qdel(meter) + return ..() + +//----------------- +// PIPENET STUFF + /obj/machinery/atmospherics/pipe/nullify_node(i) var/obj/machinery/atmospherics/old_node = nodes[i] . = ..() @@ -39,7 +63,7 @@ /obj/machinery/atmospherics/pipe/get_rebuild_targets() if(!QDELETED(parent)) return - parent = new + replace_pipenet(parent, new /datum/pipeline) return list(parent) /obj/machinery/atmospherics/pipe/proc/releaseAirToTurf() @@ -73,25 +97,33 @@ /obj/machinery/atmospherics/pipe/return_pipenet() return parent -/obj/machinery/atmospherics/pipe/set_pipenet(datum/pipeline/pipenet_to_set) - parent = pipenet_to_set +/obj/machinery/atmospherics/pipe/replace_pipenet(datum/pipeline/old_pipenet, datum/pipeline/new_pipenet) + if(parent && has_gas_visuals) + vis_contents -= parent.GetGasVisual('icons/obj/pipes_n_cables/!pipe_gas_overlays.dmi') -/obj/machinery/atmospherics/pipe/Destroy() - QDEL_NULL(parent) + parent = new_pipenet - releaseAirToTurf() + if(parent && has_gas_visuals) // null is a valid argument here + vis_contents += parent.GetGasVisual('icons/obj/pipes_n_cables/!pipe_gas_overlays.dmi') - var/turf/local_turf = loc - for(var/obj/machinery/meter/meter in local_turf) - if(meter.target != src) - continue - var/obj/item/pipe_meter/meter_object = new (local_turf) - meter.transfer_fingerprints_to(meter_object) - qdel(meter) +/obj/machinery/atmospherics/pipe/return_pipenets() + . = list(parent) + +//-------------------- +// APPEARANCE STUFF + +/obj/machinery/atmospherics/pipe/update_icon() + update_pipe_icon() + update_layer() return ..() /obj/machinery/atmospherics/pipe/proc/update_pipe_icon() - icon = 'icons/obj/pipes_n_cables/pipes_bitmask.dmi' + switch(initialize_directions) + if(NORTH, EAST, SOUTH, WEST) // Pipes with only a single connection aren't handled by this system + icon = null + return + else + icon = 'icons/obj/pipes_n_cables/!pipes_bitmask.dmi' var/connections = NONE var/bitfield = NONE for(var/i in 1 to device_type) @@ -104,11 +136,6 @@ bitfield |= CARDINAL_TO_SHORTPIPES(initialize_directions & ~connections) icon_state = "[bitfield]_[piping_layer]" -/obj/machinery/atmospherics/pipe/update_icon() - update_pipe_icon() - update_layer() - return ..() - /obj/machinery/atmospherics/proc/update_node_icon() for(var/i in 1 to device_type) if(!nodes[i]) @@ -116,9 +143,6 @@ var/obj/machinery/atmospherics/current_node = nodes[i] current_node.update_icon() -/obj/machinery/atmospherics/pipe/return_pipenets() - . = list(parent) - /obj/machinery/atmospherics/pipe/paint(paint_color) if(paintable) add_atom_colour(paint_color, FIXED_COLOUR_PRIORITY) diff --git a/code/modules/atmospherics/machinery/pipes/smart.dm b/code/modules/atmospherics/machinery/pipes/smart.dm index 95e5839469fd6..7c530bace5fcf 100644 --- a/code/modules/atmospherics/machinery/pipes/smart.dm +++ b/code/modules/atmospherics/machinery/pipes/smart.dm @@ -14,7 +14,7 @@ GLOBAL_LIST_INIT(atmos_components, typecacheof(list(/obj/machinery/atmospherics) var/connections = NONE /obj/machinery/atmospherics/pipe/smart/update_pipe_icon() - icon = 'icons/obj/pipes_n_cables/pipes_bitmask.dmi' + icon = 'icons/obj/pipes_n_cables/!pipes_bitmask.dmi' //find all directions this pipe is connected with other nodes connections = NONE diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 3f28631726758..28c0bd93f2554 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -34,8 +34,8 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) desc = "A canister for the storage of gas." icon = 'icons/obj/pipes_n_cables/canisters.dmi' icon_state = "#mapme" - greyscale_config = /datum/greyscale_config/canister/hazard - greyscale_colors = "#ffff00#000000" + greyscale_config = /datum/greyscale_config/canister + greyscale_colors = "#6b6b80" density = TRUE volume = 2000 armor_type = /datum/armor/portable_atmospherics_canister @@ -156,7 +156,7 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) gas_type = /datum/gas/antinoblium filled = 1 greyscale_config = /datum/greyscale_config/canister/double_stripe - greyscale_colors = "#9b5d7f#368bff" + greyscale_colors = "#333333#fefb30" /obj/machinery/portable_atmospherics/canister/bz name = "\improper BZ canister" @@ -167,8 +167,8 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister/carbon_dioxide name = "Carbon dioxide canister" gas_type = /datum/gas/carbon_dioxide - greyscale_config = /datum/greyscale_config/canister - greyscale_colors = "#4e4c48" + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#4e4c48#eaeaea" /obj/machinery/portable_atmospherics/canister/freon name = "Freon canister" @@ -202,8 +202,8 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) name = "Hydrogen canister" gas_type = /datum/gas/hydrogen filled = 1 - greyscale_config = /datum/greyscale_config/canister/stripe - greyscale_colors = "#bdc2c0#ffffff" + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#eaeaea#be3455" /obj/machinery/portable_atmospherics/canister/miasma name = "Miasma canister" @@ -215,8 +215,8 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister/nitrogen name = "Nitrogen canister" gas_type = /datum/gas/nitrogen - greyscale_config = /datum/greyscale_config/canister - greyscale_colors = "#d41010" + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#e9ff5c#f4fce8" /obj/machinery/portable_atmospherics/canister/nitrous_oxide name = "Nitrous oxide canister" diff --git a/code/modules/bitrunning/components/avatar_connection.dm b/code/modules/bitrunning/components/avatar_connection.dm index 2a151d05066d0..24f42d8f3e519 100644 --- a/code/modules/bitrunning/components/avatar_connection.dm +++ b/code/modules/bitrunning/components/avatar_connection.dm @@ -68,7 +68,7 @@ /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_LIVING_DEATH, PROC_REF(on_sever_connection)) RegisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_linked_damage)) /datum/component/avatar_connection/UnregisterFromParent() @@ -79,7 +79,9 @@ /// Disconnects the avatar and returns the mind to the old_body. /datum/component/avatar_connection/proc/full_avatar_disconnect(forced = FALSE, datum/source) +#ifndef UNIT_TESTS return_to_old_body() +#endif var/obj/machinery/netpod/hosting_netpod = netpod_ref?.resolve() if(isnull(hosting_netpod) && istype(source, /obj/machinery/netpod)) diff --git a/code/modules/bitrunning/designs.dm b/code/modules/bitrunning/designs.dm new file mode 100644 index 0000000000000..4e7bca1c1a8dd --- /dev/null +++ b/code/modules/bitrunning/designs.dm @@ -0,0 +1,87 @@ +// Quantum server + +/obj/item/circuitboard/machine/quantum_server + name = "Quantum Server" + greyscale_colors = CIRCUIT_COLOR_SUPPLY + build_path = /obj/machinery/quantum_server + req_components = list( + /datum/stock_part/servo = 2, + /datum/stock_part/scanning_module = 1, + /datum/stock_part/capacitor = 1, + ) + +/** + * quantum server design + * are you absolutely sure?? + */ + +// Netpod + +/obj/item/circuitboard/machine/netpod + name = "Netpod" + greyscale_colors = CIRCUIT_COLOR_SUPPLY + build_path = /obj/machinery/netpod + req_components = list( + /datum/stock_part/servo = 1, + /datum/stock_part/matter_bin = 2, + ) + +/datum/design/board/netpod + name = "Netpod Board" + desc = "The circuit board for a netpod." + id = "netpod" + build_path = /obj/item/circuitboard/machine/netpod + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_CARGO + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + +// Quantum console + +/obj/item/circuitboard/computer/quantum_console + name = "Quantum Console" + greyscale_colors = CIRCUIT_COLOR_SUPPLY + build_path = /obj/machinery/computer/quantum_console + +/datum/design/board/quantum_console + name = "Quantum Console Board" + desc = "Allows for the construction of circuit boards used to build a Quantum Console." + id = "quantum_console" + build_path = /obj/item/circuitboard/computer/quantum_console + category = list( + RND_CATEGORY_COMPUTER + RND_SUBCATEGORY_COMPUTER_CARGO + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + +// Byteforge + +/obj/item/circuitboard/machine/byteforge + name = "Byteforge" + greyscale_colors = CIRCUIT_COLOR_SUPPLY + build_path = /obj/machinery/byteforge + req_components = list( + /datum/stock_part/micro_laser = 1, + ) + +/datum/design/board/byteforge + name = "Byteforge Board" + desc = "Allows for the construction of circuit boards used to build a Byteforge." + id = "byteforge" + build_path = /obj/item/circuitboard/machine/byteforge + category = list( + RND_CATEGORY_COMPUTER + RND_SUBCATEGORY_COMPUTER_CARGO + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + + +/datum/techweb_node/bitrunning + id = "bitrunning" + display_name = "Bitrunning Technology" + description = "Bluespace technology has led to the development of quantum-scale computing, which unlocks the means to materialize atomic structures while executing advanced programs." + prereq_ids = list("practical_bluespace") + design_ids = list( + "byteforge", + "quantum_console", + "netpod", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) diff --git a/code/modules/bitrunning/objects/byteforge.dm b/code/modules/bitrunning/objects/byteforge.dm new file mode 100644 index 0000000000000..e4543601ce9d1 --- /dev/null +++ b/code/modules/bitrunning/objects/byteforge.dm @@ -0,0 +1,55 @@ +/obj/machinery/byteforge + name = "byteforge" + + circuit = /obj/item/circuitboard/machine/byteforge + desc = "A machine used by the quantum server. Quantum code converges here, materializing decrypted assets from the virtual abyss." + icon = 'icons/obj/machines/bitrunning.dmi' + icon_state = "byteforge" + obj_flags = BLOCKS_CONSTRUCTION + /// Idle particles + var/mutable_appearance/byteforge_particles + +/obj/machinery/byteforge/Initialize(mapload) + . = ..() + + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/byteforge/LateInitialize() + . = ..() + + byteforge_particles = mutable_appearance(initial(icon), "on_particles", ABOVE_MOB_LAYER) + setup_particles() + +/obj/machinery/byteforge/update_appearance(updates) + . = ..() + + setup_particles() + +/// Adds the particle overlays to the byteforge +/obj/machinery/byteforge/proc/setup_particles() + cut_overlays() + + if(is_operational) + add_overlay(byteforge_particles) + +/// Begins spawning the crate - lights, overlays, etc +/obj/machinery/byteforge/proc/start_to_spawn(obj/structure/closet/crate/secure/bitrunning/encrypted/cache) + addtimer(CALLBACK(src, PROC_REF(spawn_crate), cache), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) + + var/mutable_appearance/lighting = mutable_appearance(initial(icon), "on_overlay") + flick_overlay_view(lighting, 1 SECONDS) + + set_light(l_range = 2, l_power = 1.5, l_color = LIGHT_COLOR_BABY_BLUE, l_on = TRUE) + +/// Sparks, moves the crate to the location +/obj/machinery/byteforge/proc/spawn_crate(obj/structure/closet/crate/secure/bitrunning/encrypted/cache) + if(QDELETED(cache)) + return + + playsound(src, 'sound/magic/blink.ogg', 50, TRUE) + var/datum/effect_system/spark_spread/quantum/sparks = new() + sparks.set_up(5, 1, loc) + sparks.start() + + cache.forceMove(loc) + set_light(l_on = FALSE) diff --git a/code/modules/bitrunning/objects/host_monitor.dm b/code/modules/bitrunning/objects/host_monitor.dm index f59ca61cbd080..c35edea6319f8 100644 --- a/code/modules/bitrunning/objects/host_monitor.dm +++ b/code/modules/bitrunning/objects/host_monitor.dm @@ -2,10 +2,10 @@ 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." + desc = "A complex electronic that will analyze the connection health between host and avatar." flags_1 = CONDUCT_1 icon = 'icons/obj/device.dmi' - icon_state = "gps-b" + icon_state = "host_monitor" inhand_icon_state = "electronic" item_flags = NOBLUDGEON lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' diff --git a/code/modules/bitrunning/objects/landmarks.dm b/code/modules/bitrunning/objects/landmarks.dm index d78283c6a8b23..d727025c92864 100644 --- a/code/modules/bitrunning/objects/landmarks.dm +++ b/code/modules/bitrunning/objects/landmarks.dm @@ -44,11 +44,6 @@ 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" @@ -68,3 +63,41 @@ /obj/effect/landmark/bitrunning/safehouse_spawn name = "Bitrunning safehouse spawn" icon_state = "safehouse" + +///Swaps the locations of an encrypted crate in the area with another randomly selected crate. +///Randomizes names, so you have to inspect crates manually. +/obj/effect/landmark/bitrunning/crate_replacer + name = "Bitrunning Goal Crate Randomizer" + icon_state = "crate" + +/obj/effect/landmark/bitrunning/crate_replacer/Initialize(mapload) + . = ..() + + #ifndef UNIT_TESTS + var/list/crate_list = list() + var/obj/structure/closet/crate/secure/bitrunning/encrypted/encrypted_crate + var/area/my_area = get_area(src) + + for(var/turf/area_turf as anything in my_area.get_contained_turfs()) + for(var/obj/structure/closet/crate/crate_to_check in area_turf) + if(istype(crate_to_check, /obj/structure/closet/crate/secure/bitrunning/encrypted)) + encrypted_crate = crate_to_check + crate_to_check.desc += span_hypnophrase(" This feels like the crate we're looking for!") + else + crate_list += crate_to_check + crate_to_check.name = "Unidentified Crate" + + if(!encrypted_crate) + stack_trace("Bitrunning Goal Crate Randomizer failed to find an encrypted crate to swap positions for.") + return + if(!length(crate_list)) + stack_trace("Bitrunning Goal Crate Randomizer failed to find any NORMAL crates to swap positions for.") + return + + var/original_location = encrypted_crate.loc + var/obj/structure/closet/crate/selected_crate = pick(crate_list) + + encrypted_crate.abstract_move(selected_crate.loc) + selected_crate.abstract_move(original_location) + + #endif diff --git a/code/modules/bitrunning/objects/netpod.dm b/code/modules/bitrunning/objects/netpod.dm index 349a304f04b78..d92da961b86a3 100644 --- a/code/modules/bitrunning/objects/netpod.dm +++ b/code/modules/bitrunning/objects/netpod.dm @@ -189,7 +189,7 @@ return TRUE /obj/machinery/netpod/ui_interact(mob/user, datum/tgui/ui) - if(!is_operational) + if(!is_operational || occupant) return ui = SStgui.try_update_ui(user, src, ui) @@ -242,20 +242,12 @@ 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 - */ +/// Handles occupant post-disconnection effects like damage, sounds, etc /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) + var/mob/living/mob_occupant = occupant + if(isnull(occupant) || !isliving(occupant) || mob_occupant.stat == DEAD) open_machine() return @@ -347,8 +339,9 @@ 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) + RegisterSignal(server, COMSIG_BITRUNNER_SERVER_UPGRADED, PROC_REF(on_server_upgraded)) + RegisterSignal(server, COMSIG_BITRUNNER_DOMAIN_COMPLETE, PROC_REF(on_domain_complete)) + RegisterSignal(server, COMSIG_BITRUNNER_DOMAIN_SCRUBBED, PROC_REF(on_domain_scrubbed)) return server @@ -395,6 +388,7 @@ account.bitrunning_points += reward_points * 100 +/// User inspects the machine /obj/machinery/netpod/proc/on_examine(datum/source, mob/examiner, list/examine_text) SIGNAL_HANDLER @@ -409,7 +403,15 @@ 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.") +/// The domain has been fully purged, so we should double check our avatar is deleted +/obj/machinery/netpod/proc/on_domain_scrubbed(datum/source) + SIGNAL_HANDLER + + var/mob/living/current_avatar = avatar_ref?.resolve() + if(isnull(current_avatar)) + return + QDEL_NULL(current_avatar) /// When the server is upgraded, drops brain damage a little /obj/machinery/netpod/proc/on_server_upgraded(datum/source, servo_rating) diff --git a/code/modules/bitrunning/objects/bit_vendor.dm b/code/modules/bitrunning/objects/vendor.dm similarity index 100% rename from code/modules/bitrunning/objects/bit_vendor.dm rename to code/modules/bitrunning/objects/vendor.dm diff --git a/code/modules/bitrunning/antagonists/outfit.dm b/code/modules/bitrunning/outfits.dm similarity index 74% rename from code/modules/bitrunning/antagonists/outfit.dm rename to code/modules/bitrunning/outfits.dm index db57af561f8ad..27ef8029a18ed 100644 --- a/code/modules/bitrunning/antagonists/outfit.dm +++ b/code/modules/bitrunning/outfits.dm @@ -41,3 +41,17 @@ officer_uniform.has_sensor = NO_SENSORS officer_uniform.sensor_mode = SENSOR_OFF user.update_suit_sensors() + +/datum/outfit/echolocator + name = "Bitrunning Echolocator" + glasses = /obj/item/clothing/glasses/blindfold + ears = /obj/item/radio/headset/psyker //Navigating without these is horrible. + uniform = /obj/item/clothing/under/abductor + gloves = /obj/item/clothing/gloves/fingerless + shoes = /obj/item/clothing/shoes/jackboots + suit = /obj/item/clothing/suit/jacket/trenchcoat + id = /obj/item/card/id/advanced + +/datum/outfit/echolocator/post_equip(mob/living/carbon/human/user, visualsOnly) + . = ..() + user.psykerize() diff --git a/code/modules/bitrunning/server/loot.dm b/code/modules/bitrunning/server/loot.dm index 29b730aae784f..8b3af95607c64 100644 --- a/code/modules/bitrunning/server/loot.dm +++ b/code/modules/bitrunning/server/loot.dm @@ -16,14 +16,16 @@ /// Generates a reward based on the given domain /obj/machinery/quantum_server/proc/generate_loot() - if(!length(receive_turfs) && !locate_receive_turfs()) + var/list/obj/machinery/byteforge/nearby_forges = get_nearby_forges() + if(isnull(nearby_forges)) + say(src, "No nearby byteforges detected.") 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)) + var/obj/machinery/byteforge/chosen_forge = pick(nearby_forges) + if(isnull(chosen_forge)) stack_trace("Failed to find a turf to spawn loot crate on.") return FALSE @@ -34,11 +36,11 @@ 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) + var/obj/structure/closet/crate/secure/bitrunning/decrypted/reward_crate = new(src, generated_domain, bonus) reward_crate.manifest = certificate reward_crate.update_appearance() - spark_at_location(reward_crate) + chosen_forge.start_to_spawn(reward_crate) return TRUE /// Returns the markdown text containing domain completion information diff --git a/code/modules/bitrunning/server/map_handling.dm b/code/modules/bitrunning/server/map_handling.dm index 02126c290f774..741fad476f0a8 100644 --- a/code/modules/bitrunning/server/map_handling.dm +++ b/code/modules/bitrunning/server/map_handling.dm @@ -1,3 +1,4 @@ +#define ONLY_TURF 1 /// Gives all current occupants a notification that the server is going down /obj/machinery/quantum_server/proc/begin_shutdown(mob/user) @@ -119,7 +120,6 @@ 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() @@ -160,7 +160,8 @@ /// Deletes all the tile contents /obj/machinery/quantum_server/proc/scrub_vdom() - SEND_SIGNAL(src, COMSIG_BITRUNNER_SEVER_AVATAR) // just in case + SEND_SIGNAL(src, COMSIG_BITRUNNER_SEVER_AVATAR) /// just in case someone's connected + SEND_SIGNAL(src, COMSIG_BITRUNNER_DOMAIN_SCRUBBED) // avatar cleanup just in case if(length(generated_domain.reservations)) var/datum/turf_reservation/res = generated_domain.reservations[1] diff --git a/code/modules/bitrunning/server/quantum_server.dm b/code/modules/bitrunning/server/quantum_server.dm index 404a31cca6a77..b869fb7f02e2a 100644 --- a/code/modules/bitrunning/server/quantum_server.dm +++ b/code/modules/bitrunning/server/quantum_server.dm @@ -48,8 +48,6 @@ 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) . = ..() @@ -84,18 +82,17 @@ 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) + set_light(l_on = FALSE) return ..() set_light_color(is_ready ? LIGHT_COLOR_BABY_BLUE : LIGHT_COLOR_FIRE) - set_light(2, 1.5) + set_light(l_range = 2, l_power = 1.5, l_on = TRUE) return ..() diff --git a/code/modules/bitrunning/server/util.dm b/code/modules/bitrunning/server/util.dm index c4f1319cd12dd..f4dbada9ef6f0 100644 --- a/code/modules/bitrunning/server/util.dm +++ b/code/modules/bitrunning/server/util.dm @@ -107,14 +107,14 @@ 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) +/// Locates any turfs with forges on them +/obj/machinery/quantum_server/proc/get_nearby_forges() + var/list/obj/machinery/byteforge/nearby_forges = list() - return length(receive_turfs) > 0 + for(var/obj/machinery/byteforge/forge in oview(MAX_DISTANCE, src)) + nearby_forges += forge + + return nearby_forges /// Finds any mobs with minds in the zones and gives them the bad news /obj/machinery/quantum_server/proc/notify_spawned_threats() @@ -132,10 +132,10 @@ 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) +/obj/machinery/quantum_server/proc/spark_at_location(obj/cache) + playsound(cache, 'sound/magic/blink.ogg', 50, TRUE) var/datum/effect_system/spark_spread/quantum/sparks = new() - sparks.set_up(5, 1, get_turf(crate)) + sparks.set_up(5, 1, get_turf(cache)) sparks.start() #undef REDACTED diff --git a/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm b/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm index 871c2cb1338e2..a6fb3e921e054 100644 --- a/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm +++ b/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm @@ -8,12 +8,12 @@ map_name = "beach_bar" safehouse_path = /datum/map_template/safehouse/mine -/obj/item/reagent_containers/cup/glass/drinkingglass/virtual_domain +/obj/item/reagent_containers/cup/glass/drinkingglass/filled/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) +/obj/item/reagent_containers/cup/glass/drinkingglass/filled/virtual_domain/Initialize(mapload, vol) . = ..() AddComponent(/datum/component/bitrunning_points, \ diff --git a/code/modules/bitrunning/virtual_domain/domains/gondola_asteroid.dm b/code/modules/bitrunning/virtual_domain/domains/gondola_asteroid.dm index 4deacb4f9c592..01d58e3980381 100644 --- a/code/modules/bitrunning/virtual_domain/domains/gondola_asteroid.dm +++ b/code/modules/bitrunning/virtual_domain/domains/gondola_asteroid.dm @@ -28,11 +28,7 @@ /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) + gondola_disease = /datum/disease/transformation/gondola/virtual_domain /datum/disease/transformation/gondola/virtual_domain stage_prob = 9 diff --git a/code/modules/bitrunning/virtual_domain/domains/psyker_shuffle.dm b/code/modules/bitrunning/virtual_domain/domains/psyker_shuffle.dm new file mode 100644 index 0000000000000..2ca32bce98340 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/psyker_shuffle.dm @@ -0,0 +1,12 @@ +/datum/lazy_template/virtual_domain/psyker_shuffle + name = "Crate Chaos" + cost = BITRUNNER_COST_LOW + desc = "Sneak into an abandoned corner of the virtual world, where they store all of the crates. \ + Warning -- Virtual domain does not support visual display. This mission must be completed using echolocation." + difficulty = BITRUNNER_DIFFICULTY_MEDIUM + help_text = "Getting used to echolocation may be difficult. Remember to walk slowly, and carefully inspect every crate you come across." + key = "psyker_shuffle" + map_name = "psyker_shuffle" + reward_points = BITRUNNER_REWARD_MEDIUM + safehouse_path = /datum/map_template/safehouse/bathroom + forced_outfit = /datum/outfit/echolocator diff --git a/code/modules/bitrunning/virtual_domain/domains/psyker_zombies.dm b/code/modules/bitrunning/virtual_domain/domains/psyker_zombies.dm new file mode 100644 index 0000000000000..6d545f7c652f1 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/psyker_zombies.dm @@ -0,0 +1,14 @@ +/datum/lazy_template/virtual_domain/psyker_zombies + name = "Infected Domain" + cost = BITRUNNER_COST_MEDIUM + desc = "Another neglected corner of the virtual world. This one had to be abandoned due to zombie virus. \ + Warning -- Virtual domain does not support visual display. This mission must be completed using echolocation." + difficulty = BITRUNNER_DIFFICULTY_MEDIUM + help_text = "This once-beloved virtual domain has been corrupted by a virus, rendering it unstable, full of holes, and full of ZOMBIES! \ + There should be a Mystery Box nearby to help get you armed. Get armed, and finish what the cyber-police started!" + key = "psyker_zombies" + map_name = "psyker_zombies" + reward_points = BITRUNNER_REWARD_HIGH + safehouse_path = /datum/map_template/safehouse/bathroom + forced_outfit = /datum/outfit/echolocator + extra_loot = list(/obj/item/radio/headset/psyker = 1) //Looks cool, might make your local burdened chaplain happy. diff --git a/code/modules/bitrunning/virtual_domain/safehouses.dm b/code/modules/bitrunning/virtual_domain/safehouses.dm index bb42f690ac7ec..6504d447f28c4 100644 --- a/code/modules/bitrunning/virtual_domain/safehouses.dm +++ b/code/modules/bitrunning/virtual_domain/safehouses.dm @@ -46,6 +46,9 @@ /datum/map_template/safehouse/ice filename = "ice.dmm" +/datum/map_template/safehouse/bathroom + filename = "bathroom.dmm" + /** * Your safehouse here * /datum/map_template/safehouse/your_type diff --git a/code/modules/capture_the_flag/ctf_equipment.dm b/code/modules/capture_the_flag/ctf_equipment.dm index 5ec412f9017e9..e822ae2dbb156 100644 --- a/code/modules/capture_the_flag/ctf_equipment.dm +++ b/code/modules/capture_the_flag/ctf_equipment.dm @@ -165,7 +165,7 @@ /obj/item/ammo_casing/energy/instakill projectile_type = /obj/projectile/beam/instakill - e_cost = 0 + e_cost = 0 // Not possible to use the macro select_name = "DESTROY" /obj/projectile/beam/instakill diff --git a/code/modules/capture_the_flag/ctf_game.dm b/code/modules/capture_the_flag/ctf_game.dm index 4ab831f706094..1f6c44c293a95 100644 --- a/code/modules/capture_the_flag/ctf_game.dm +++ b/code/modules/capture_the_flag/ctf_game.dm @@ -151,6 +151,13 @@ new_team_member.prefs.safe_transfer_prefs_to(player_mob, is_antag = TRUE) if(player_mob.dna.species.outfit_important_for_life) player_mob.set_species(/datum/species/human) + + player_mob.AddComponent( \ + /datum/component/temporary_body, \ + old_mind = new_team_member.mob.mind, \ + old_body = new_team_member.mob.mind.current, \ + ) + player_mob.ckey = new_team_member.ckey if(isnull(ctf_player_component)) var/datum/component/ctf_player/player_component = player_mob.mind.AddComponent(/datum/component/ctf_player, team, ctf_game, ammo_type) diff --git a/code/modules/cargo/exports.dm b/code/modules/cargo/exports.dm index a9d408c8699b9..44a628740bd52 100644 --- a/code/modules/cargo/exports.dm +++ b/code/modules/cargo/exports.dm @@ -35,12 +35,13 @@ Then the player gets the profit from selling his own wasted time. ** delete_unsold: if the items that were not sold should be deleted ** dry_run: if the item should be actually sold, or if its just a pirce test ** external_report: works as "transaction" object, pass same one in if you're doing more than one export in single go + ** ignore_typecache: typecache containing types that should be completely ignored */ -/proc/export_item_and_contents(atom/movable/exported_atom, apply_elastic = TRUE, delete_unsold = TRUE, dry_run = FALSE, datum/export_report/external_report) +/proc/export_item_and_contents(atom/movable/exported_atom, apply_elastic = TRUE, delete_unsold = TRUE, dry_run = FALSE, datum/export_report/external_report, list/ignore_typecache) if(!GLOB.exports_list.len) setupExports() - var/list/contents = exported_atom.get_all_contents() + var/list/contents = exported_atom.get_all_contents_ignoring(ignore_typecache) var/datum/export_report/report = external_report diff --git a/code/modules/cargo/gondolapod.dm b/code/modules/cargo/gondolapod.dm index 5ee9f856823e1..f3dca7a3dca10 100644 --- a/code/modules/cargo/gondolapod.dm +++ b/code/modules/cargo/gondolapod.dm @@ -65,11 +65,13 @@ /mob/living/simple_animal/pet/gondola/gondolapod/setOpened() opened = TRUE + SET_PLANE_IMPLICIT(src, GAME_PLANE) update_appearance() addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/, setClosed)), 50) /mob/living/simple_animal/pet/gondola/gondolapod/setClosed() opened = FALSE + SET_PLANE_IMPLICIT(src, GAME_PLANE_FOV_HIDDEN) update_appearance() /mob/living/simple_animal/pet/gondola/gondolapod/death() diff --git a/code/modules/cargo/materials_market.dm b/code/modules/cargo/materials_market.dm index dd56128241d4e..e2bedd2f19abf 100644 --- a/code/modules/cargo/materials_market.dm +++ b/code/modules/cargo/materials_market.dm @@ -11,7 +11,7 @@ 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( + var/static/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, @@ -130,12 +130,18 @@ data["canOrderCargo"] = can_buy_via_budget return data -/obj/machinery/materials_market/ui_act(action, params) +/obj/machinery/materials_market/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) . = ..() if(.) return - if(!isliving(usr)) + + //You must have an ID to be able to do something + var/mob/living/living_user = ui.user + var/obj/item/card/id/used_id_card = living_user.get_idcard(TRUE) + if(isnull(used_id_card)) + say("No ID Found") return + switch(action) if("buy") var/material_str = params["material"] @@ -149,32 +155,41 @@ 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) + + //if multiple users open the UI some of them may not have the required access so we recheck + var/is_ordering_private = ordering_private + if(!(ACCESS_CARGO in used_id_card.GetAccess())) //no cargo access then force private purchase + is_ordering_private = TRUE + + var/datum/bank_account/account_payable + if(is_ordering_private) 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 + if(!account_payable) + say("No bank account detected!") + return sheet_to_buy = initial(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 + var/cost = SSstock_market.materials_prices[material_bought] * quantity 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 + // 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") + // Must be a Galactic Materials Market order and payed by the null account(if ordered via cargo budget) or by correct user for private purchase + if(order.orderer_rank == "Galactic Materials Market" && ( \ + (!is_ordering_private && order.paying_account == null) || \ + (is_ordering_private && order.paying_account != null && order.orderer == living_user) \ + )) + // Check if this order exceeded its limit 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) @@ -182,29 +197,24 @@ 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 + // Append to this order 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, \ + purchaser = is_ordering_private ? living_user : "Cargo", \ + cost = cost, \ 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, + paying_account = is_ordering_private ? account_payable : null, cost_type = "credit", - can_be_cancelled = FALSE, + can_be_cancelled = FALSE ) say("Thank you for your purchase! It will arrive on the next cargo shuttle!") SSshuttle.shopping_list += new_order @@ -214,7 +224,6 @@ 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." diff --git a/code/modules/cargo/packs/security.dm b/code/modules/cargo/packs/security.dm index 7abe5f7601e62..784b870fea084 100644 --- a/code/modules/cargo/packs/security.dm +++ b/code/modules/cargo/packs/security.dm @@ -225,6 +225,14 @@ crate_name = "energy gun crate" crate_type = /obj/structure/closet/crate/secure/plasma +/datum/supply_pack/security/armory/laser_carbine + name = "Laser Carbine Crate" + desc = "Contains three laser carbines, capable of rapidly firing weak lasers." + cost = CARGO_CRATE_VALUE * 9 + contains = list(/obj/item/gun/energy/laser/carbine = 3) + crate_name = "laser carbine crate" + crate_type = /obj/structure/closet/crate/secure/plasma + /datum/supply_pack/security/armory/exileimp name = "Exile Implants Crate" desc = "Contains five Exile implants." diff --git a/code/modules/cargo/packs/stock_market_items.dm b/code/modules/cargo/packs/stock_market_items.dm index 04b2eac4acf74..9744bdf7400ea 100644 --- a/code/modules/cargo/packs/stock_market_items.dm +++ b/code/modules/cargo/packs/stock_market_items.dm @@ -14,7 +14,7 @@ var/amount /datum/supply_pack/market_materials/get_cost() - for(var/datum/material/mat in SSstock_market.materials_prices) + for(var/datum/material/mat as anything in SSstock_market.materials_prices) if(material == mat) return SSstock_market.materials_prices[mat] * amount diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 4373699495988..99e6472791bbb 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -222,6 +222,9 @@ stay_after_drop = FALSE holder.pixel_z = initial(holder.pixel_z) holder.alpha = initial(holder.alpha) + if (holder != src) + contents |= holder.contents + qdel(holder) var/shippingLane = GLOB.areas_by_type[/area/centcom/central_command_areas/supplypod/supplypod_temp_holding] forceMove(shippingLane) //Move to the centcom-z-level until the pod_landingzone says we can drop back down again if (!reverse_dropoff_coords) //If we're centcom-launched, the reverse dropoff turf will be a centcom loading bay. If we're an extraction pod, it should be the ninja jail. Thus, this shouldn't ever really happen. @@ -288,6 +291,8 @@ if (style == STYLE_GONDOLA) //Checks if we are supposed to be a gondola pod. If so, create a gondolapod mob, and move this pod to nullspace. I'd like to give a shout out, to my man oranges var/mob/living/simple_animal/pet/gondola/gondolapod/benis = new(turf_underneath, src) benis.contents |= contents //Move the contents of this supplypod into the gondolapod mob. + for (var/mob/living/mob_in_pod in benis.contents) + mob_in_pod.reset_perspective(null) moveToNullspace() addtimer(CALLBACK(src, PROC_REF(open_pod), benis), delays[POD_OPENING]) //After the opening delay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob else if (style == STYLE_SEETHROUGH) @@ -310,7 +315,7 @@ playsound(get_turf(holder), openingSound, soundVolume, FALSE, FALSE) //Special admin sound to play for (var/turf_type in turfs_in_cargo) turf_underneath.PlaceOnTop(turf_type) - for (var/cargo in contents) + for (var/cargo in holder.contents) var/atom/movable/movable_cargo = cargo movable_cargo.forceMove(turf_underneath) if (!effectQuiet && !openingSound && style != STYLE_SEETHROUGH && !(pod_flags & FIRST_SOUNDS)) //If we aren't being quiet, play the default pod open sound @@ -482,7 +487,8 @@ . = ..() if(same_z_layer) return - SET_PLANE_EXPLICIT(glow_effect, ABOVE_GAME_PLANE, src) + if(glow_effect) + SET_PLANE_EXPLICIT(glow_effect, ABOVE_GAME_PLANE, src) /obj/structure/closet/supplypod/proc/endGlow() if(!glow_effect) @@ -532,7 +538,7 @@ /obj/effect/supplypod_smoke/proc/drawSelf(amount) alpha = max(0, 255-(amount*20)) -/obj/effect/supplypod_rubble //This is the object that forceMoves the supplypod to it's location +/obj/effect/supplypod_rubble name = "debris" desc = "A small crater of rubble. Closer inspection reveals the debris to be made primarily of space-grade metal fragments. You're pretty sure that this will disperse before too long." icon = 'icons/obj/supplypods.dmi' diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index bc643cc9d45a9..dab23c78e17ff 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -509,6 +509,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) character.icon_render_keys = list() character.update_body(is_creating = TRUE) + SEND_SIGNAL(character, COMSIG_HUMAN_PREFS_APPLIED) /// Returns whether the parent mob should have the random hardcore settings enabled. Assumes it has a mind. /datum/preferences/proc/should_be_random_hardcore(datum/job/job, datum/mind/mind) diff --git a/code/modules/clothing/head/hat.dm b/code/modules/clothing/head/hat.dm index ccc8e903f3394..89244adcf31bc 100644 --- a/code/modules/clothing/head/hat.dm +++ b/code/modules/clothing/head/hat.dm @@ -135,6 +135,10 @@ worn_icon_state = "cowboy_hat_black" inhand_icon_state = "cowboy_hat_black" +/// More likely to intercept bullets, since you're likely to not be wearing your modsuit with this on +/obj/item/clothing/head/cowboy/black/syndicate + deflect_chance = 25 + /obj/item/clothing/head/cowboy/white name = "ten-gallon hat" desc = "There are two kinds of people in the world: those with guns and those that dig. You dig?" diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm index f8cd88923ec19..2960260255fbc 100644 --- a/code/modules/clothing/head/helmet.dm +++ b/code/modules/clothing/head/helmet.dm @@ -178,7 +178,7 @@ toggle_message = "You pull the visor down on" alt_toggle_message = "You push the visor up on" armor_type = /datum/armor/toggleable_riot - flags_inv = HIDEEARS|HIDEFACE|HIDESNOUT + flags_inv = HIDEHAIR|HIDEEARS|HIDEFACE|HIDESNOUT strip_delay = 80 actions_types = list(/datum/action/item_action/toggle) visor_flags_inv = HIDEFACE|HIDESNOUT diff --git a/code/modules/clothing/shoes/cowboy.dm b/code/modules/clothing/shoes/cowboy.dm index 05792a72cbd96..0aa518bc1364d 100644 --- a/code/modules/clothing/shoes/cowboy.dm +++ b/code/modules/clothing/shoes/cowboy.dm @@ -6,6 +6,10 @@ custom_price = PAYCHECK_CREW var/max_occupants = 4 can_be_tied = FALSE + /// Do these boots have spur sounds? + var/has_spurs = FALSE + /// The jingle jangle jingle of our spurs + var/list/spur_sound = list('sound/effects/footstep/spurs1.ogg'=1,'sound/effects/footstep/spurs2.ogg'=1,'sound/effects/footstep/spurs3.ogg'=1) /datum/armor/shoes_cowboy bio = 90 @@ -19,6 +23,9 @@ //There's a snake in my boot new /mob/living/basic/snake(src) + if(has_spurs) + LoadComponent(/datum/component/squeak, spur_sound, 50, falloff_exponent = 20) + /obj/item/clothing/shoes/cowboy/equipped(mob/living/carbon/user, slot) . = ..() @@ -97,3 +104,10 @@ name = "\improper Hugs-The-Feet lizard skin boots" desc = "A pair of masterfully crafted lizard skin boots. Finally a good application for the station's most bothersome inhabitants." icon_state = "lizardboots_blue" + +/// Shoes for the nuke-ops cowboy fit +/obj/item/clothing/shoes/cowboy/black/syndicate + name = "black spurred cowboy boots" + desc = "And they sing, oh, ain't you glad you're single? And that song ain't so very far from wrong." + armor_type = /datum/armor/shoes_combat + has_spurs = TRUE diff --git a/code/modules/clothing/suits/wintercoats.dm b/code/modules/clothing/suits/wintercoats.dm index c970c7f9b0e11..7ade6df6f7c37 100644 --- a/code/modules/clothing/suits/wintercoats.dm +++ b/code/modules/clothing/suits/wintercoats.dm @@ -247,8 +247,10 @@ icon_state = "coatjanitor" inhand_icon_state = null allowed = list( + /obj/item/access_key, /obj/item/grenade/chem_grenade, /obj/item/holosign_creator, + /obj/item/key/janitor, /obj/item/reagent_containers/cup/beaker, /obj/item/reagent_containers/cup/bottle, /obj/item/reagent_containers/cup/tube, diff --git a/code/modules/clothing/under/costume.dm b/code/modules/clothing/under/costume.dm index d4e41696219d8..d3ef481557bec 100644 --- a/code/modules/clothing/under/costume.dm +++ b/code/modules/clothing/under/costume.dm @@ -359,6 +359,11 @@ inhand_icon_state = null can_adjust = FALSE +// For the nuke-ops cowboy fit. Sadly no Lone Ranger fit & I don't wanna bloat costume files further. +/obj/item/clothing/under/costume/dutch/syndicate + desc = "You can feel a god damn plan coming on, and the armor lining in this suit'll do wonders in makin' it work." + armor_type = /datum/armor/clothing_under/syndicate + /obj/item/clothing/under/costume/osi name = "O.S.I. jumpsuit" icon_state = "osi_jumpsuit" diff --git a/code/modules/events/ghost_role/revenant_event.dm b/code/modules/events/ghost_role/revenant_event.dm index 27f3597a7ad2a..f739a3e13d46b 100644 --- a/code/modules/events/ghost_role/revenant_event.dm +++ b/code/modules/events/ghost_role/revenant_event.dm @@ -54,11 +54,12 @@ if(!spawn_locs.len) //If we can't find THAT, then just give up and cry return MAP_ERROR - var/mob/living/simple_animal/revenant/revvie = new(pick(spawn_locs)) - revvie.key = selected.key + var/mob/living/basic/revenant/revvie = new(pick(spawn_locs)) + selected.mind.transfer_to(revvie) message_admins("[ADMIN_LOOKUPFLW(revvie)] has been made into a revenant by an event.") revvie.log_message("was spawned as a revenant by an event.", LOG_GAME) spawned_mobs += revvie + qdel(selected) return SUCCESSFUL_SPAWN #undef REVENANT_SPAWN_THRESHOLD diff --git a/code/modules/events/ghost_role/sentience.dm b/code/modules/events/ghost_role/sentience.dm index 6cdc66472c1a7..abc57d33a0758 100644 --- a/code/modules/events/ghost_role/sentience.dm +++ b/code/modules/events/ghost_role/sentience.dm @@ -11,13 +11,13 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list( /mob/living/basic/pig, /mob/living/basic/rabbit, /mob/living/basic/sheep, + /mob/living/basic/sloth, /mob/living/basic/snake, /mob/living/basic/spider/giant/sgt_araneus, /mob/living/simple_animal/bot/secbot/beepsky, /mob/living/simple_animal/hostile/retaliate/goose/vomit, /mob/living/simple_animal/parrot, /mob/living/simple_animal/pet, - /mob/living/simple_animal/sloth, ))) /datum/round_event_control/sentience diff --git a/code/modules/events/wizard/petsplosion.dm b/code/modules/events/wizard/petsplosion.dm index 70248bf0ebb6a..e670fa910a1f0 100644 --- a/code/modules/events/wizard/petsplosion.dm +++ b/code/modules/events/wizard/petsplosion.dm @@ -13,12 +13,12 @@ GLOBAL_LIST_INIT(petsplosion_candidates, typecacheof(list( /mob/living/basic/pig, /mob/living/basic/rabbit, /mob/living/basic/sheep, + /mob/living/basic/sloth, /mob/living/basic/snake, /mob/living/basic/spider/giant/sgt_araneus, /mob/living/simple_animal/hostile/retaliate/goose/vomit, /mob/living/simple_animal/parrot, /mob/living/simple_animal/pet, - /mob/living/simple_animal/sloth, ))) /datum/round_event_control/wizard/petsplosion //the horror diff --git a/code/modules/experisci/experiment/types/scanning_fish.dm b/code/modules/experisci/experiment/types/scanning_fish.dm index 8397801086994..52e58c9104ccb 100644 --- a/code/modules/experisci/experiment/types/scanning_fish.dm +++ b/code/modules/experisci/experiment/types/scanning_fish.dm @@ -58,7 +58,7 @@ GLOBAL_LIST_EMPTY(scanned_fish_by_techweb) 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 += "\n[initial(fish_path.name)]" message += "" examine_list += message diff --git a/code/modules/fishing/fishing_minigame.dm b/code/modules/fishing/fishing_minigame.dm index 06ac03d64aceb..71617b4de07b3 100644 --- a/code/modules/fishing/fishing_minigame.dm +++ b/code/modules/fishing/fishing_minigame.dm @@ -176,7 +176,7 @@ difficulty += comp.fish_source.calculate_difficulty(reward_path, rod, user, src) difficulty = clamp(round(difficulty), 1, 100) - if(HAS_TRAIT(user, TRAIT_REVEAL_FISH)) + if(HAS_TRAIT(user, TRAIT_REVEAL_FISH) || (user.mind && HAS_TRAIT(user.mind, TRAIT_REVEAL_FISH))) fish_icon = GLOB.specific_fish_icons[reward_path] || "fish" /** @@ -203,6 +203,8 @@ if(!completed) complete(win = FALSE) if(fishing_line) + //Stops the line snapped message from appearing everytime the minigame is over. + UnregisterSignal(fishing_line, COMSIG_QDELETING) QDEL_NULL(fishing_line) if(lure) QDEL_NULL(lure) @@ -242,7 +244,8 @@ /datum/fishing_challenge/proc/on_line_deleted(datum/source) SIGNAL_HANDLER fishing_line = null - send_alert(user.is_holding(used_rod) ? "line snapped" : "rod dropped") + ///The lure may be out of sight if the user has moed around a corner, so the message should be displayed over him instead. + user.balloon_alert(user.is_holding(used_rod) ? "line snapped" : "rod dropped") interrupt() /datum/fishing_challenge/proc/handle_click(mob/source, atom/target, modifiers) @@ -309,7 +312,7 @@ phase = BITING_PHASE // Trashing animation playsound(lure, 'sound/effects/fish_splash.ogg', 100) - if(HAS_TRAIT(user, TRAIT_REVEAL_FISH)) + if(HAS_TRAIT(user, TRAIT_REVEAL_FISH) || (user.mind && HAS_TRAIT(user.mind, TRAIT_REVEAL_FISH))) switch(fish_icon) if(FISH_ICON_DEF) send_alert("fish!!!") diff --git a/code/modules/food_and_drinks/machinery/deep_fryer.dm b/code/modules/food_and_drinks/machinery/deep_fryer.dm index 73114812c2dc8..9b5d5b006988f 100644 --- a/code/modules/food_and_drinks/machinery/deep_fryer.dm +++ b/code/modules/food_and_drinks/machinery/deep_fryer.dm @@ -5,12 +5,13 @@ /// Global typecache of things which should never be fried. GLOBAL_LIST_INIT(oilfry_blacklisted_items, typecacheof(list( - /obj/item/reagent_containers/cup, - /obj/item/reagent_containers/syringe, - /obj/item/reagent_containers/condiment, + /obj/item/bodybag/bluespace, /obj/item/delivery, /obj/item/his_grace, - /obj/item/bodybag/bluespace, + /obj/item/mod/control, + /obj/item/reagent_containers/condiment, + /obj/item/reagent_containers/cup, + /obj/item/reagent_containers/syringe, ))) /obj/machinery/deepfryer diff --git a/code/modules/food_and_drinks/machinery/microwave.dm b/code/modules/food_and_drinks/machinery/microwave.dm index 8060bf150d36e..d52e2213a5b28 100644 --- a/code/modules/food_and_drinks/machinery/microwave.dm +++ b/code/modules/food_and_drinks/machinery/microwave.dm @@ -14,11 +14,15 @@ /// The max amount of dirtiness a microwave can be #define MAX_MICROWAVE_DIRTINESS 100 +/// For the wireless version, and display fluff +#define TIER_1_CELL_CHARGE_RATE 250 + /obj/machinery/microwave name = "microwave oven" desc = "Cooks and boils stuff." icon = 'icons/obj/machines/microwave.dmi' - icon_state = "map_icon" + base_icon_state = "" + icon_state = "mw_complete" appearance_flags = KEEP_TOGETHER | LONG_GLIDE | PIXEL_SCALE layer = BELOW_OBJ_LAYER density = TRUE @@ -27,30 +31,63 @@ light_color = LIGHT_COLOR_DIM_YELLOW light_power = 3 anchored_tabletop_offset = 6 - var/wire_disabled = FALSE // is its internal wire cut? + /// Is its function wire cut? + var/wire_disabled = FALSE + /// Wire cut to run mode backwards + var/wire_mode_swap = FALSE var/operating = FALSE /// How dirty is it? var/dirty = 0 var/dirty_anim_playing = FALSE /// How broken is it? NOT_BROKEN, KINDA_BROKEN, REALLY_BROKEN var/broken = NOT_BROKEN + /// Microwave door position var/open = FALSE + /// Microwave max capacity var/max_n_of_items = 10 + /// Microwave efficiency (power) based on the stock components var/efficiency = 0 + /// If we use a cell instead of powernet + var/cell_powered = FALSE + /// The cell we charge with + var/obj/item/stock_parts/cell/cell + /// The cell we're charging + var/obj/item/stock_parts/cell/vampire_cell + /// Capable of vampire charging PDAs + var/vampire_charging_capable = FALSE + /// Charge contents of microwave instead of cook + var/vampire_charging_enabled = FALSE var/datum/looping_sound/microwave/soundloop - var/list/ingredients = list() // may only contain /atom/movables - + /// May only contain /atom/movables + var/list/ingredients = list() + /// When this is the nth ingredient, whats its pixel_x? + var/list/ingredient_shifts_x = list( + -2, + 1, + -5, + 2, + -6, + 0, + -4, + ) + /// When this is the nth ingredient, whats its pixel_y? + var/list/ingredient_shifts_y = list( + -4, + -2, + -3, + ) var/static/radial_examine = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_examine") var/static/radial_eject = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_eject") - var/static/radial_use = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_use") + var/static/radial_cook = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_cook") + var/static/radial_charge = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_charge") // we show the button even if the proc will not work - var/static/list/radial_options = list("eject" = radial_eject, "use" = radial_use) - var/static/list/ai_radial_options = list("eject" = radial_eject, "use" = radial_use, "examine" = radial_examine) + var/static/list/radial_options = list("eject" = radial_eject, "cook" = radial_cook, "charge" = radial_charge) + var/static/list/ai_radial_options = list("eject" = radial_eject, "cook" = radial_cook, "charge" = radial_charge, "examine" = radial_examine) /obj/machinery/microwave/Initialize(mapload) . = ..() - + register_context() set_wires(new /datum/wires/microwave(src)) create_reagents(100) soundloop = new(src, FALSE) @@ -66,7 +103,6 @@ itemized_ingredient.pixel_y = itemized_ingredient.base_pixel_y + rand(-5, 6) return ..() - /obj/machinery/microwave/on_deconstruction() eject() return ..() @@ -75,21 +111,70 @@ QDEL_LIST(ingredients) QDEL_NULL(wires) QDEL_NULL(soundloop) + if(!isnull(cell)) + QDEL_NULL(cell) return ..() +/obj/machinery/microwave/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(cell_powered) + if(!isnull(cell)) + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Remove cell" + else if(held_item && istype(held_item, /obj/item/stock_parts/cell)) + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Insert cell" + + if(!anchored && held_item?.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_LMB] = "Install/Secure" + return CONTEXTUAL_SCREENTIP_SET + + if(broken > NOT_BROKEN) + if(broken == REALLY_BROKEN && held_item?.tool_behaviour == TOOL_WIRECUTTER) + context[SCREENTIP_CONTEXT_LMB] = "Repair" + return CONTEXTUAL_SCREENTIP_SET + + else if(broken == KINDA_BROKEN && held_item?.tool_behaviour == TOOL_WELDER) + context[SCREENTIP_CONTEXT_LMB] = "Repair" + return CONTEXTUAL_SCREENTIP_SET + + context[SCREENTIP_CONTEXT_LMB] = "Show menu" + + if(vampire_charging_capable) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Change to [vampire_charging_enabled ? "cook" : "charge"]" + + if(length(ingredients) != 0) + context[SCREENTIP_CONTEXT_RMB] = "Start [vampire_charging_enabled ? "charging" : "cooking"]" + + return CONTEXTUAL_SCREENTIP_SET + /obj/machinery/microwave/RefreshParts() . = ..() efficiency = 0 + vampire_charging_capable = FALSE for(var/datum/stock_part/micro_laser/micro_laser in component_parts) efficiency += micro_laser.tier for(var/datum/stock_part/matter_bin/matter_bin in component_parts) max_n_of_items = 10 * matter_bin.tier break + for(var/datum/stock_part/capacitor/capacitor in component_parts) + if(capacitor.tier >= 2) + vampire_charging_capable = TRUE + visible_message(span_notice("The [EXAMINE_HINT("Charge Ready")] light on \the [src] flickers to life.")) + break /obj/machinery/microwave/examine(mob/user) . = ..() + if(vampire_charging_capable) + . += span_info("This model features Wave™: a Nanotrasen exclusive. Our latest and greatest, Wave™ allows your PDA to be charged wirelessly through microwave frequencies! You can Wave-charge your device by placing it inside and selecting the charge mode.") + . += span_info("Because nothing says 'future' like charging your PDA while overcooking your leftovers. Nanotrasen Wave™ - Multitasking, redefined.") + + if(cell_powered) + . += span_notice("This model is wireless, powered by portable cells. [isnull(cell) ? "The cell slot is empty." : "[EXAMINE_HINT("Ctrl-click")] to remove the power cell."]") + if(!operating) - . += span_notice("Right-click [src] to turn it on.") + if(!operating && vampire_charging_capable) + . += span_notice("[EXAMINE_HINT("Alt-click")] to change default mode.") + + . += span_notice("[EXAMINE_HINT("Right-click")] to start [vampire_charging_enabled ? "charging" : "cooking"] cycle.") if(!in_range(user, src) && !issilicon(user) && !isobserver(user)) . += span_warning("You're too far away to examine [src]'s contents and display!") @@ -106,44 +191,37 @@ var/list/items_counts = new for(var/i in ingredients) if(isstack(i)) - var/obj/item/stack/S = i - items_counts[S.name] += S.amount + var/obj/item/stack/item_stack = i + items_counts[item_stack.name] += item_stack.amount else - var/atom/movable/AM = i - items_counts[AM.name]++ - for(var/O in items_counts) - . += span_notice("- [items_counts[O]]x [O].") + var/atom/movable/single_item = i + items_counts[single_item.name]++ + for(var/item in items_counts) + . += span_notice("- [items_counts[item]]x [item].") else . += span_notice("\The [src] is empty.") if(!(machine_stat & (NOPOWER|BROKEN))) . += "[span_notice("The status display reads:")]\n"+\ + "[span_notice("- Mode: [vampire_charging_enabled ? "Charge" : "Cook"].")]\n"+\ "[span_notice("- Capacity: [max_n_of_items] items.")]\n"+\ - span_notice("- Cook time reduced by [(efficiency - 1) * 25]%.") + span_notice("- Power: [efficiency * TIER_1_CELL_CHARGE_RATE]W.") + + if(cell_powered) + . += span_notice("- Charge: [isnull(cell) ? "INSERT CELL" : "[round(cell.percent())]%"].") #define MICROWAVE_INGREDIENT_OVERLAY_SIZE 24 /obj/machinery/microwave/update_overlays() - // When this is the nth ingredient, whats its pixel_x? - var/static/list/ingredient_shifts = list( - 0, - 3, - -3, - 4, - -4, - 2, - -2, - ) - . = ..() // All of these will use a full icon state instead - if (panel_open || dirty == MAX_MICROWAVE_DIRTINESS || broken || dirty_anim_playing) + if(panel_open || dirty == MAX_MICROWAVE_DIRTINESS || broken || dirty_anim_playing) return . var/ingredient_count = 0 - for (var/atom/movable/ingredient as anything in ingredients) + for(var/atom/movable/ingredient as anything in ingredients) var/image/ingredient_overlay = image(ingredient, src) var/list/icon_dimensions = get_icon_dimensions(ingredient.icon) @@ -152,11 +230,11 @@ MICROWAVE_INGREDIENT_OVERLAY_SIZE / icon_dimensions["height"], ) - ingredient_overlay.pixel_y = -4 + ingredient_overlay.pixel_x = ingredient_shifts_x[(ingredient_count % ingredient_shifts_x.len) + 1] + ingredient_overlay.pixel_y = ingredient_shifts_y[(ingredient_count % ingredient_shifts_y.len) + 1] ingredient_overlay.layer = FLOAT_LAYER ingredient_overlay.plane = FLOAT_PLANE ingredient_overlay.blend_mode = BLEND_INSET_OVERLAY - ingredient_overlay.pixel_x = ingredient_shifts[(ingredient_count % ingredient_shifts.len) + 1] ingredient_count += 1 @@ -165,46 +243,67 @@ var/border_icon_state var/door_icon_state - if (open) - door_icon_state = "door_open" - border_icon_state = "mwo" - else if (operating) - door_icon_state = "door_on" - border_icon_state = "mw1" + if(open) + door_icon_state = "[base_icon_state]door_open" + border_icon_state = "[base_icon_state]mwo" + else if(operating) + if(vampire_charging_enabled) + door_icon_state = "[base_icon_state]door_charge" + else + door_icon_state = "[base_icon_state]door_on" + border_icon_state = "[base_icon_state]mw1" else - door_icon_state = "door_off" - border_icon_state = "mw" + door_icon_state = "[base_icon_state]door_off" + border_icon_state = "[base_icon_state]mw" + . += mutable_appearance( icon, door_icon_state, - alpha = ingredients.len > 0 ? 128 : 255, ) . += border_icon_state - if (!open) - . += "door_handle" + if(!open) + . += "[base_icon_state]door_handle" + + if(!(machine_stat & NOPOWER) || cell_powered) + . += emissive_appearance(icon, "emissive_[border_icon_state]", src, alpha = src.alpha) + + if(cell_powered && !isnull(cell)) + switch(cell.percent()) + if(75 to 100) + . += mutable_appearance(icon, "[base_icon_state]cell_100") + . += emissive_appearance(icon, "[base_icon_state]cell_100", src, alpha = src.alpha) + if(50 to 75) + . += mutable_appearance(icon, "[base_icon_state]cell_75") + . += emissive_appearance(icon, "[base_icon_state]cell_75", src, alpha = src.alpha) + if(25 to 50) + . += mutable_appearance(icon, "[base_icon_state]cell_25") + . += emissive_appearance(icon, "[base_icon_state]cell_25", src, alpha = src.alpha) + else + . += mutable_appearance(icon, "[base_icon_state]cell_0") + . += emissive_appearance(icon, "[base_icon_state]cell_0", src, alpha = src.alpha) return . #undef MICROWAVE_INGREDIENT_OVERLAY_SIZE /obj/machinery/microwave/update_icon_state() - if (broken) - icon_state = "mwb" - else if (dirty_anim_playing) - icon_state = "mwbloody1" - else if (dirty == MAX_MICROWAVE_DIRTINESS) - icon_state = open ? "mwbloodyo" : "mwbloody" + if(broken) + icon_state = "[base_icon_state]mwb" + else if(dirty_anim_playing) + icon_state = "[base_icon_state]mwbloody1" + else if(dirty == MAX_MICROWAVE_DIRTINESS) + icon_state = open ? "[base_icon_state]mwbloodyo" : "[base_icon_state]mwbloody" else if(operating) - icon_state = "back_on" + icon_state = "[base_icon_state]back_on" else if(open) - icon_state = "back_open" + icon_state = "[base_icon_state]back_open" else if(panel_open) - icon_state = "mw-o" + icon_state = "[base_icon_state]mw-o" else - icon_state = "back_off" + icon_state = "[base_icon_state]back_off" return ..() @@ -232,23 +331,23 @@ update_appearance() return TOOL_ACT_TOOLTYPE_SUCCESS -/obj/machinery/microwave/attackby(obj/item/O, mob/living/user, params) +/obj/machinery/microwave/attackby(obj/item/item, mob/living/user, params) if(operating) return - if(panel_open && is_wire_tool(O)) + if(panel_open && is_wire_tool(item)) wires.interact(user) return TRUE if(broken > NOT_BROKEN) - if(broken == REALLY_BROKEN && O.tool_behaviour == TOOL_WIRECUTTER) // If it's broken and they're using a TOOL_WIRECUTTER + if(broken == REALLY_BROKEN && item.tool_behaviour == TOOL_WIRECUTTER) // If it's broken and they're using a TOOL_WIRECUTTER user.visible_message(span_notice("[user] starts to fix part of \the [src]."), span_notice("You start to fix part of \the [src]...")) - if(O.use_tool(src, user, 20)) + if(item.use_tool(src, user, 20)) user.visible_message(span_notice("[user] fixes part of \the [src]."), span_notice("You fix part of \the [src].")) broken = KINDA_BROKEN // Fix it a bit - else if(broken == KINDA_BROKEN && O.tool_behaviour == TOOL_WELDER) // If it's broken and they're doing the wrench + else if(broken == KINDA_BROKEN && item.tool_behaviour == TOOL_WELDER) // If it's broken and they're doing the wrench user.visible_message(span_notice("[user] starts to fix part of \the [src]."), span_notice("You start to fix part of \the [src]...")) - if(O.use_tool(src, user, 20)) + if(item.use_tool(src, user, 20)) user.visible_message(span_notice("[user] fixes \the [src]."), span_notice("You fix \the [src].")) broken = NOT_BROKEN update_appearance() @@ -258,8 +357,9 @@ return TRUE return - if(istype(O, /obj/item/reagent_containers/spray)) - var/obj/item/reagent_containers/spray/clean_spray = O + if(istype(item, /obj/item/reagent_containers/spray)) + var/obj/item/reagent_containers/spray/clean_spray = item + open(autoclose = 2 SECONDS) if(clean_spray.reagents.has_reagent(/datum/reagent/space_cleaner, clean_spray.amount_per_transfer_from_this)) clean_spray.reagents.remove_reagent(/datum/reagent/space_cleaner, clean_spray.amount_per_transfer_from_this,1) playsound(loc, 'sound/effects/spray3.ogg', 50, TRUE, -6) @@ -270,56 +370,83 @@ to_chat(user, span_warning("You need more space cleaner!")) return TRUE - if(istype(O, /obj/item/soap) || istype(O, /obj/item/reagent_containers/cup/rag)) + if(istype(item, /obj/item/soap) || istype(item, /obj/item/reagent_containers/cup/rag)) var/cleanspeed = 50 - if(istype(O, /obj/item/soap)) - var/obj/item/soap/used_soap = O + if(istype(item, /obj/item/soap)) + var/obj/item/soap/used_soap = item cleanspeed = used_soap.cleanspeed user.visible_message(span_notice("[user] starts to clean \the [src]."), span_notice("You start to clean \the [src]...")) + open(autoclose = cleanspeed + 1 SECONDS) if(do_after(user, cleanspeed, target = src)) user.visible_message(span_notice("[user] cleans \the [src]."), span_notice("You clean \the [src].")) dirty = 0 update_appearance() return TRUE + if(istype(item, /obj/item/stock_parts/cell) && cell_powered) + var/swapped = FALSE + if(!isnull(cell)) + cell.forceMove(drop_location()) + if(!issilicon(user) && Adjacent(user)) + user.put_in_hands(cell) + cell = null + swapped = TRUE + if(!user.transferItemToLoc(item, src)) + update_appearance() + return TRUE + cell = item + balloon_alert(user, "[swapped ? "swapped" : "inserted"] cell") + update_appearance() + return TRUE + + if(!anchored) + balloon_alert(user, "not secured!") + return ..() + if(dirty >= MAX_MICROWAVE_DIRTINESS) // The microwave is all dirty so can't be used! balloon_alert(user, "it's too dirty!") return TRUE - if(istype(O, /obj/item/storage)) - var/obj/item/storage/T = O + if(vampire_charging_capable && istype(item, /obj/item/modular_computer/pda) && ingredients.len > 0) + balloon_alert(user, "max 1 pda!") + return FALSE + + if(istype(item, /obj/item/storage)) + var/obj/item/storage/tray = item var/loaded = 0 - if(!istype(O, /obj/item/storage/bag/tray)) + if(!istype(item, /obj/item/storage/bag/tray)) // Non-tray dumping requires a do_after - to_chat(user, span_notice("You start dumping out the contents of [O] into [src]...")) - if(!do_after(user, 2 SECONDS, target = T)) + to_chat(user, span_notice("You start dumping out the contents of [item] into [src]...")) + if(!do_after(user, 2 SECONDS, target = tray)) return - for(var/obj/S in T.contents) - if(!IS_EDIBLE(S)) + for(var/obj/tray_item in tray.contents) + if(!IS_EDIBLE(tray_item)) continue if(ingredients.len >= max_n_of_items) balloon_alert(user, "it's full!") return TRUE - if(T.atom_storage.attempt_remove(S, src)) + if(tray.atom_storage.attempt_remove(tray_item, src)) loaded++ - ingredients += S + ingredients += tray_item if(loaded) + open(autoclose = 0.6 SECONDS) to_chat(user, span_notice("You insert [loaded] items into \the [src].")) update_appearance() return - if(O.w_class <= WEIGHT_CLASS_NORMAL && !istype(O, /obj/item/storage) && !user.combat_mode) + if(item.w_class <= WEIGHT_CLASS_NORMAL && !istype(item, /obj/item/storage) && !user.combat_mode) if(ingredients.len >= max_n_of_items) balloon_alert(user, "it's full!") return TRUE - if(!user.transferItemToLoc(O, src)) + if(!user.transferItemToLoc(item, src)) balloon_alert(user, "it's stuck to your hand!") return FALSE - ingredients += O - user.visible_message(span_notice("[user] adds \a [O] to \the [src]."), span_notice("You add [O] to \the [src].")) + ingredients += item + open(autoclose = 0.6 SECONDS) + user.visible_message(span_notice("[user] adds \a [item] to \the [src]."), span_notice("You add [item] to \the [src].")) update_appearance() return @@ -330,13 +457,34 @@ if(!length(ingredients)) balloon_alert(user, "it's empty!") return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN - cook(user) + + start_cycle(user) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN +/obj/machinery/microwave/AltClick(mob/user, list/modifiers) + if(user.can_perform_action(src, ALLOW_SILICON_REACH)) + if(!vampire_charging_capable) + return + + vampire_charging_enabled = !vampire_charging_enabled + balloon_alert(user, "set to [vampire_charging_enabled ? "charge" : "cook"]") + +/obj/machinery/microwave/CtrlClick(mob/user) + . = ..() + if(cell_powered && !isnull(cell) && anchored) + user.put_in_hands(cell) + balloon_alert(user, "removed cell") + cell = null + update_appearance() + /obj/machinery/microwave/ui_interact(mob/user) . = ..() - if(operating || panel_open || !anchored || !user.can_perform_action(src, ALLOW_SILICON_REACH)) + if(!anchored) + balloon_alert(user, "not secured!") + return + if(operating || panel_open || !user.can_perform_action(src, ALLOW_SILICON_REACH)) return if(isAI(user) && (machine_stat & NOPOWER)) return @@ -351,17 +499,21 @@ var/choice = show_radial_menu(user, src, isAI(user) ? ai_radial_options : radial_options, require_near = !issilicon(user)) // post choice verification - if(operating || panel_open || !anchored || !user.can_perform_action(src, ALLOW_SILICON_REACH)) + if(operating || panel_open || (!vampire_charging_capable && !anchored) || !user.can_perform_action(src, ALLOW_SILICON_REACH)) return if(isAI(user) && (machine_stat & NOPOWER)) return - usr.set_machine(src) + user.set_machine(src) switch(choice) if("eject") eject() - if("use") - cook(user) + if("cook") + vampire_charging_enabled = FALSE + start_cycle(user) + if("charge") + vampire_charging_enabled = TRUE + start_cycle(user) if("examine") examine(user) @@ -369,8 +521,20 @@ var/atom/drop_loc = drop_location() for(var/atom/movable/movable_ingredient as anything in ingredients) movable_ingredient.forceMove(drop_loc) - open() - playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) + open(autoclose = 1.4 SECONDS) + +/obj/machinery/microwave/proc/start_cycle(mob/user) + if(wire_mode_swap) + spark() + if(vampire_charging_enabled) + cook(user) + else + charge(user) + + else if(vampire_charging_enabled) + charge(user) + else + cook(user) /** * Begins the process of cooking the included ingredients. @@ -380,6 +544,7 @@ /obj/machinery/microwave/proc/cook(mob/cooker) if(machine_stat & (NOPOWER|BROKEN)) return + if(operating || broken > 0 || panel_open || !anchored || dirty >= MAX_MICROWAVE_DIRTINESS) return @@ -388,9 +553,15 @@ playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) return + if(cell_powered && cell?.charge < TIER_1_CELL_CHARGE_RATE * efficiency) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + balloon_alert(cooker, "replace cell!") + return + if(cooker && HAS_TRAIT(cooker, TRAIT_CURSED) && prob(7)) muck() return + if(prob(max((5 / efficiency) - 5, dirty * 5))) //a clean unupgraded microwave has no risk of failure muck() return @@ -411,16 +582,18 @@ /obj/machinery/microwave/proc/wzhzhzh() visible_message(span_notice("\The [src] turns on."), null, span_hear("You hear a microwave humming.")) operating = TRUE + if(cell_powered && !isnull(cell)) + cell.use(TIER_1_CELL_CHARGE_RATE * efficiency) - set_light(1.5) + set_light(l_range = 1.5, l_power = 1.2, l_on = TRUE) soundloop.start() update_appearance() /obj/machinery/microwave/proc/spark() visible_message(span_warning("Sparks fly around [src]!")) - var/datum/effect_system/spark_spread/s = new - s.set_up(2, 1, src) - s.start() + var/datum/effect_system/spark_spread/sparks = new + sparks.set_up(2, 1, src) + sparks.start() /** * The start of the cook loop @@ -429,7 +602,7 @@ */ /obj/machinery/microwave/proc/start(mob/cooker) wzhzhzh() - loop(MICROWAVE_NORMAL, 10, cooker = cooker) + cook_loop(type = MICROWAVE_NORMAL, cycles = 10, cooker = cooker) /** * The start of the cook loop, but can fail (result in a splat / dirty microwave) @@ -438,29 +611,29 @@ */ /obj/machinery/microwave/proc/start_can_fail(mob/cooker) wzhzhzh() - loop(MICROWAVE_PRE, 4, cooker = cooker) + cook_loop(type = MICROWAVE_PRE, cycles = 4, cooker = cooker) /obj/machinery/microwave/proc/muck() wzhzhzh() playsound(loc, 'sound/effects/splat.ogg', 50, TRUE) dirty_anim_playing = TRUE update_appearance() - loop(MICROWAVE_MUCK, 4) + cook_loop(type = MICROWAVE_MUCK, cycles = 4) /** * The actual cook loop started via [proc/start] or [proc/start_can_fail] * - * * type - the type of cooking, determined via how this iteration of loop is called, and determines the result + * * type - the type of cooking, determined via how this iteration of cook_loop is called, and determines the result * * time - how many loops are left, base case for recursion * * wait - deciseconds between loops * * cooker - The mob that initiated the cook cycle, can be null if no apparent mob triggered it (such as via emp) */ -/obj/machinery/microwave/proc/loop(type, time, wait = max(12 - 2 * efficiency, 2), mob/cooker) // standard wait is 10 +/obj/machinery/microwave/proc/cook_loop(type, cycles, wait = max(12 - 2 * efficiency, 2), mob/cooker) // standard wait is 10 if((machine_stat & BROKEN) && type == MICROWAVE_PRE) pre_fail() return - if(time <= 0 || !length(ingredients)) + if(cycles <= 0 || !length(ingredients)) switch(type) if(MICROWAVE_NORMAL) loop_finish(cooker) @@ -469,18 +642,21 @@ if(MICROWAVE_PRE) pre_success(cooker) return - time-- + cycles-- use_power(active_power_usage) - addtimer(CALLBACK(src, PROC_REF(loop), type, time, wait, cooker), wait) + addtimer(CALLBACK(src, PROC_REF(cook_loop), type, cycles, wait, cooker), wait) /obj/machinery/microwave/power_change() . = ..() + if(cell_powered) + return + if((machine_stat & NOPOWER) && operating) pre_fail() eject() /** - * Called when the loop is done successfully, no dirty mess or whatever + * Called when the cook_loop is done successfully, no dirty mess or whatever * * * cooker - The mob that initiated the cook cycle, can be null if no apparent mob triggered it (such as via emp) */ @@ -490,6 +666,11 @@ var/cursed_chef = cooker && HAS_TRAIT(cooker, TRAIT_CURSED) var/metal_amount = 0 for(var/obj/item/cooked_item in ingredients) + if(istype(cooked_item, /obj/item/modular_computer/pda) && prob(75)) + spark() + broken = REALLY_BROKEN + explosion(src, heavy_impact_range = 1, light_impact_range = 2, flame_range = 1) + var/sigreturn = cooked_item.microwave_act(src, cooker, randomize_pixel_offset = ingredients.len) if(sigreturn & COMPONENT_MICROWAVE_SUCCESS) if(isstack(cooked_item)) @@ -500,7 +681,7 @@ metal_amount += (cooked_item.custom_materials?[GET_MATERIAL_REF(/datum/material/iron)] || 0) - if(cursed_chef && prob(5)) + if(cursed_chef && (metal_amount || prob(5))) // If we're unlucky and have metal, we're guaranteed to explode spark() broken = REALLY_BROKEN explosion(src, light_impact_range = 2, flame_range = 1) @@ -508,10 +689,8 @@ if(metal_amount) spark() broken = REALLY_BROKEN - if(cursed_chef || prob(max(metal_amount / 2, 33))) // If we're unlucky and have metal, we're guaranteed to explode + if(prob(max(metal_amount / 2, 33))) explosion(src, heavy_impact_range = 1, light_impact_range = 2) - else - dump_inventory_contents() after_finish_loop() @@ -522,7 +701,7 @@ after_finish_loop() /obj/machinery/microwave/proc/pre_success(mob/cooker) - loop(MICROWAVE_NORMAL, 10, cooker = cooker) + cook_loop(type = MICROWAVE_NORMAL, cycles = 10, cooker = cooker) /obj/machinery/microwave/proc/muck_finish() visible_message(span_warning("\The [src] gets covered in muck!")) @@ -534,19 +713,121 @@ after_finish_loop() /obj/machinery/microwave/proc/after_finish_loop() - set_light(0) + set_light(l_on = FALSE) soundloop.stop() - open() + eject() + open(autoclose = 2 SECONDS) -/obj/machinery/microwave/proc/open() +/obj/machinery/microwave/proc/open(autoclose = 2 SECONDS) open = TRUE + playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) update_appearance() - addtimer(CALLBACK(src, PROC_REF(close)), 0.8 SECONDS) + addtimer(CALLBACK(src, PROC_REF(close)), autoclose) /obj/machinery/microwave/proc/close() open = FALSE update_appearance() +/** + * The start of the charge loop + * + * * cooker - The mob that initiated the cook cycle, can be null if no apparent mob triggered it (such as via emp) + */ +/obj/machinery/microwave/proc/vampire(mob/cooker) + wzhzhzh() + var/obj/item/modular_computer/pda/vampire_pda = LAZYACCESS(ingredients, 1) + if(isnull(vampire_pda)) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + after_finish_loop() + return + + vampire_cell = vampire_pda.internal_cell + if(isnull(vampire_pda)) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + after_finish_loop() + return + + var/vampire_charge_amount = vampire_cell.maxcharge - vampire_cell.charge + charge_loop(vampire_charge_amount, cooker = cooker) + +/obj/machinery/microwave/proc/charge(mob/cooker) + if(!vampire_charging_capable) + balloon_alert(cooker, "needs upgrade!") + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + return + + if(operating || broken > 0 || panel_open || dirty >= MAX_MICROWAVE_DIRTINESS) + return + + if(wire_disabled) + audible_message("[src] buzzes.") + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + return + + // We should only be charging PDAs + for(var/atom/movable/potential_item as anything in ingredients) + if(!istype(potential_item, /obj/item/modular_computer/pda)) + balloon_alert(cooker, "pda only!") + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + eject() + return + + vampire(cooker) + +/** + * The actual cook loop started via [proc/start] or [proc/start_can_fail] + * + * * type - the type of charging, determined via how this iteration of cook_loop is called, and determines the result + * * time - how many loops are left, base case for recursion + * * wait - deciseconds between loops + * * cooker - The mob that initiated the cook cycle, can be null if no apparent mob triggered it (such as via emp) + */ +/obj/machinery/microwave/proc/charge_loop(vampire_charge_amount, wait = max(12 - 2 * efficiency, 2), mob/cooker) // standard wait is 10 + if(machine_stat & BROKEN) + pre_fail() + return + + if(!vampire_charge_amount || !length(ingredients) || (!isnull(cell) && !cell.charge) || vampire_charge_amount < 25) + vampire_cell = null + charge_loop_finish(cooker) + return + + var/charge_rate = vampire_cell.chargerate * (1 + ((efficiency - 1) * 0.25)) + if(charge_rate > vampire_charge_amount) + charge_rate = vampire_charge_amount + + if(cell_powered && !cell.use(charge_rate)) + charge_loop_finish(cooker) + + vampire_cell.give(charge_rate * (0.85 + (efficiency * 0.5))) // we lose a tiny bit of power in the transfer as heat + use_power(charge_rate) + + vampire_charge_amount = vampire_cell.maxcharge - vampire_cell.charge + + addtimer(CALLBACK(src, PROC_REF(charge_loop), vampire_charge_amount, wait, cooker), wait) + +/obj/machinery/microwave/power_change() + . = ..() + if((machine_stat & NOPOWER) && operating) + pre_fail() + eject() + +/** + * Called when the charge_loop is done successfully, no dirty mess or whatever + * + * * cooker - The mob that initiated the cook cycle, can be null if no apparent mob triggered it (such as via emp) + */ +/obj/machinery/microwave/proc/charge_loop_finish(mob/cooker) + operating = FALSE + var/cursed_chef = cooker && HAS_TRAIT(cooker, TRAIT_CURSED) + if(cursed_chef && prob(5)) + spark() + broken = REALLY_BROKEN + explosion(src, light_impact_range = 2, flame_range = 1) + + // playsound(src, 'sound/machines/chime.ogg', 50, FALSE) + after_finish_loop() + /// Type of microwave that automatically turns it self on erratically. Probably don't use this outside of the holodeck program "Microwave Paradise". /// You could also live your life with a microwave that will continously run in the background of everything while also not having any power draw. I think the former makes more sense. /obj/machinery/microwave/hell @@ -562,13 +843,48 @@ //The microwave should turn off asynchronously from any other microwaves that initialize at the same time. Keep in mind this will not turn off, since there is nothing to call the proc that ends this microwave's looping addtimer(CALLBACK(src, PROC_REF(wzhzhzh)), rand(0.5 SECONDS, 3 SECONDS)) +/obj/machinery/microwave/engineering + name = "wireless microwave oven" + desc = "For the hard-working tradesperson who's in the middle of nowhere and just wants to warm up their pastry-based savoury item from an overpriced vending machine." + base_icon_state = "engi_" + icon_state = "engi_mw_complete" + circuit = /obj/item/circuitboard/machine/microwave/engineering + light_color = LIGHT_COLOR_BABY_BLUE + // We don't use area power, we always use the cell + use_power = NO_POWER_USE + cell_powered = TRUE + vampire_charging_capable = TRUE + ingredient_shifts_x = list( + 0, + 5, + -5, + 3, + -3, + ) + ingredient_shifts_y = list( + 0, + 2, + -2, + ) + +/obj/machinery/microwave/engineering/Initialize(mapload) + . = ..() + if(mapload) + cell = new /obj/item/stock_parts/cell/upgraded/plus + update_appearance() + +/obj/machinery/microwave/engineering/cell_included/Initialize(mapload) + . = ..() + cell = new /obj/item/stock_parts/cell/upgraded/plus + update_appearance() + #undef MICROWAVE_NORMAL #undef MICROWAVE_MUCK #undef MICROWAVE_PRE - #undef NOT_BROKEN #undef KINDA_BROKEN #undef REALLY_BROKEN #undef MAX_MICROWAVE_DIRTINESS +#undef TIER_1_CELL_CHARGE_RATE diff --git a/code/modules/food_and_drinks/recipes/soup_mixtures.dm b/code/modules/food_and_drinks/recipes/soup_mixtures.dm index ad2caa84ca625..b35bc128d321e 100644 --- a/code/modules/food_and_drinks/recipes/soup_mixtures.dm +++ b/code/modules/food_and_drinks/recipes/soup_mixtures.dm @@ -85,7 +85,7 @@ if(!length(required_ingredients)) return - // If a food item is supposed to be made, remove relevant ingredients from the pot, then make the item + // If a food item is supposed to be made, remove relevant ingredients from the pot, then make the item if(!isnull(resulting_food_path)) var/list/tracked_ingredients LAZYINITLIST(tracked_ingredients) @@ -112,7 +112,7 @@ for(var/reagent_path as anything in required_reagents) holder.add_reagent(reagent_path,(required_reagents[reagent_path])*(created_volume-ingredient_max_multiplier)) - + // This only happens if we're being instant reacted so let's just skip to what we really want if(isnull(reaction)) testing("Soup reaction of type [type] instant reacted, cleaning up.") @@ -851,9 +851,9 @@ /datum/chemical_reaction/food/soup/monkey required_reagents = list( - /datum/reagent/water = 25, + /datum/reagent/water = 20, /datum/reagent/consumable/flour = 5, - /datum/reagent/consumable/salt = 5, + /datum/reagent/water/salt = 10, /datum/reagent/consumable/blackpepper = 5, ) required_ingredients = list( @@ -2103,7 +2103,7 @@ name = "\improper Hong Kong macaroni soup" icon = 'icons/obj/food/martian.dmi' icon_state = "hong_kong_macaroni" - drink_type = MEAT | VEGETABLES | GRAIN + drink_type = MEAT | VEGETABLES | GRAIN /datum/chemical_reaction/food/soup/hong_kong_macaroni required_reagents = list( diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_guide.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_guide.dm index ea6d10cb6686b..c02a7243368bd 100644 --- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_guide.dm +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_guide.dm @@ -365,6 +365,11 @@ result = /obj/item/food/watermelonslice category = CAT_SALAD +/datum/crafting_recipe/food/knife/appleslice + reqs = list(/obj/item/food/grown/apple = 1) + result = /obj/item/food/appleslice + category = CAT_SALAD + /datum/crafting_recipe/food/knife/kamaboko_slice reqs = list(/obj/item/food/kamaboko = 1) result = /obj/item/food/kamaboko_slice diff --git a/code/modules/hallucination/fake_sound.dm b/code/modules/hallucination/fake_sound.dm index 0a783df3adadf..ec578f101d376 100644 --- a/code/modules/hallucination/fake_sound.dm +++ b/code/modules/hallucination/fake_sound.dm @@ -167,7 +167,7 @@ sound_vary = FALSE no_source = TRUE sound_type = list( - 'sound/ambience/antag/bloodcult.ogg', + 'sound/ambience/antag/bloodcult/bloodcult_gain.ogg', 'sound/ambience/antag/clockcultalr.ogg', 'sound/ambience/antag/ecult_op.ogg', 'sound/ambience/antag/ling_alert.ogg', diff --git a/code/modules/hallucination/station_message.dm b/code/modules/hallucination/station_message.dm index 30b537afbc7e8..fa51e103cca33 100644 --- a/code/modules/hallucination/station_message.dm +++ b/code/modules/hallucination/station_message.dm @@ -78,7 +78,7 @@ to_chat(hallucinator, ALERT_BODY("Figments from an eldritch god are being summoned by [totally_real_cult_leader.real_name] \ into [fake_summon_area] from an unknown dimension. Disrupt the ritual at all costs!")) - SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_SPANOMALIES])) + SEND_SOUND(hallucinator, 'sound/ambience/antag/bloodcult/bloodcult_scribe.ogg') return ..() /datum/hallucination/station_message/meteors diff --git a/code/modules/hydroponics/grown/apple.dm b/code/modules/hydroponics/grown/apple.dm index b994749c2f812..0079f63ec9023 100644 --- a/code/modules/hydroponics/grown/apple.dm +++ b/code/modules/hydroponics/grown/apple.dm @@ -26,6 +26,9 @@ tastes = list("apple" = 1) distill_reagent = /datum/reagent/consumable/ethanol/hcider +/obj/item/food/grown/apple/make_processable() + AddElement(/datum/element/processable, TOOL_KNIFE, /obj/item/food/appleslice, 5, 20, screentip_verb = "Slice") + // Gold Apple /obj/item/seeds/apple/gold name = "pack of golden apple seeds" @@ -41,6 +44,9 @@ reagents_add = list(/datum/reagent/gold = 0.2, /datum/reagent/consumable/nutriment/vitamin = 0.04, /datum/reagent/consumable/nutriment = 0.1) rarity = 40 // Alchemy! +/obj/item/food/grown/apple/gold/make_processable() + return // You're going to break your knife! + /obj/item/food/grown/apple/gold seed = /obj/item/seeds/apple/gold name = "golden apple" diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index ebb22cc63a0af..22ea8d5334e3b 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -247,11 +247,7 @@ // So we'll let it leak in, and move the water over. set_recipient_reagents_holder(nutri_reagents) reagents = nutri_reagents - process_request( - amount = MACHINE_REAGENT_TRANSFER, - reagent = null, - dir = dir - ) + process_request(dir = dir) // Move the leaked water from nutrients to... water var/leaking_water_amount = nutri_reagents.get_reagent_amount(/datum/reagent/water) diff --git a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm index a214eb48e0cc9..805c72a326749 100644 --- a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm +++ b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm @@ -31,7 +31,7 @@ on_clear_callback = CALLBACK(src, PROC_REF(on_cult_rune_removed)), \ effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune, /obj/effect/cosmic_rune), \ ) - AddElement(/datum/element/bane, target_type = /mob/living/simple_animal/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE) + AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE) if(!GLOB.holy_weapon_type && type == /obj/item/nullrod) var/list/rods = list() diff --git a/code/modules/library/bibles.dm b/code/modules/library/bibles.dm index 5d3c6e276b429..8a10a058341fe 100644 --- a/code/modules/library/bibles.dm +++ b/code/modules/library/bibles.dm @@ -369,7 +369,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list( tip_text = "Clear rune", \ effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune, /obj/effect/cosmic_rune), \ ) - AddElement(/datum/element/bane, target_type = /mob/living/simple_animal/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE) + AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE) /obj/item/book/bible/syndicate/attack_self(mob/living/carbon/human/user, modifiers) if(!uses || !istype(user)) diff --git a/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm b/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm index 5e459758ee405..aa201292f72a2 100644 --- a/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm +++ b/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm @@ -60,6 +60,12 @@ if(prob(floor_variance)) icon_state = "[base_icon_state][rand(0,6)]" +/turf/open/misc/asteroid/basalt/wasteland/basin + icon_state = "wasteland_dug" + base_icon_state = "wasteland_dug" + floor_variance = 0 + dug = TRUE + /turf/closed/mineral/strong/wasteland name = "ancient dry rock" color = "#B5651D" diff --git a/code/modules/mapfluff/ruins/lavalandruin_code/watcher_grave.dm b/code/modules/mapfluff/ruins/lavalandruin_code/watcher_grave.dm new file mode 100644 index 0000000000000..75ac48c2467c4 --- /dev/null +++ b/code/modules/mapfluff/ruins/lavalandruin_code/watcher_grave.dm @@ -0,0 +1,263 @@ +/obj/effect/mob_spawn/corpse/goliath/pierced + corpse_description = "Seems to have been pierced through the heart by a Watcher spike." + naive_corpse_description = "It's got a pretty big boo-boo, might need one of the large plasters." + +/obj/effect/mob_spawn/corpse/watcher/goliath_chewed + corpse_description = "Prior to its death, it was badly mangled by the jaws of a Goliath." + naive_corpse_description = "It's all tuckered out after playing rough with a Goliath." + +/obj/effect/mob_spawn/corpse/watcher/crushed + corpse_description = "Crushed by a rockslide, it seemed to have been scraping frantically at the rocks even as it perished." + naive_corpse_description = "All of those rocks probably don't make a comfortable blanket." + + +#define WATCHER_EGG_LIVELY_MOD 0.75 +#define WATCHER_EGG_ACTIVE_MOD 0.5 + +/// Egg which hatches into a helpful pet. Or you can eat it if you want. +/obj/item/food/egg/watcher + name = "watcher egg" + desc = "A lonely egg still pulsing with life, somehow untouched by the corruption of the Necropolis." + icon_state = "egg_watcher" + chick_throw_prob = 100 + tastes = list("ocular fluid" = 6, "loneliness" = 1) + preserved_food = TRUE + /// How far have we moved? + var/steps_travelled = 0 + /// How far should we travel to hatch? + var/steps_to_hatch = 600 + /// Datum used to measure our steps + var/datum/movement_detector/pedometer + +/obj/item/food/egg/watcher/Initialize(mapload) + . = ..() + pedometer = new(src, CALLBACK(src, PROC_REF(on_stepped))) + +/obj/item/food/egg/watcher/Destroy(force) + . = ..() + QDEL_NULL(pedometer) + +/obj/item/food/egg/watcher/spawn_impact_chick(turf/spawn_turf) + new /obj/effect/spawner/random/lavaland_mob/watcher(spawn_turf) + +/obj/item/food/egg/watcher/examine(mob/user) + return ..() + span_notice("Watch it more closely to see how it is doing...") + +/obj/item/food/egg/watcher/examine_more(mob/user) + . = ..() + if (steps_travelled < (steps_to_hatch * WATCHER_EGG_ACTIVE_MOD)) + return . + span_notice("Something stirs listlessly inside.") + if (steps_travelled < steps_to_hatch * WATCHER_EGG_LIVELY_MOD) + return . + span_notice("Something is moving actively inside.") + return . + span_boldnotice("It's jiggling wildly, it's about to hatch!") + +/// Called when we are moved, whether inside an inventory or by ourself somehow +/obj/item/food/egg/watcher/proc/on_stepped(atom/movable/egg, atom/mover, atom/old_loc, direction) + var/new_loc = get_turf(egg) + if (isnull(new_loc) || new_loc == get_turf(old_loc)) + return // Didn't actually go anywhere + steps_travelled++ + if (steps_travelled == steps_to_hatch * WATCHER_EGG_ACTIVE_MOD) + jiggle() + return + if (steps_travelled < steps_to_hatch) + return + visible_message(span_boldnotice("[src] splits and unfurls into a baby Watcher!")) + playsound(new_loc, 'sound/effects/splat.ogg', 50, TRUE) + new /obj/effect/decal/cleanable/greenglow(new_loc) + new /obj/item/watcher_hatchling(new_loc) + qdel(src) + +/// Animate the egg +/obj/item/food/egg/watcher/proc/jiggle() + var/animation = isturf(loc) ? rand(1, 3) : 1 // Pixel_x/y animations don't work in an inventory + switch(animation) + if (1) + animate(src, transform = transform.Scale(1.3), time = 1 SECONDS, easing = BOUNCE_EASING) + animate(transform = matrix(), time = 0.5 SECONDS, easing = SINE_EASING | EASE_IN) + if (2) + animate(src, pixel_y = 8, time = 0.5 SECONDS, easing = SINE_EASING | EASE_OUT) + animate(pixel_y = 0, time = 0.5 SECONDS, easing = SINE_EASING | EASE_IN) + animate(pixel_y = 4, time = 0.5 SECONDS, easing = SINE_EASING | EASE_OUT) + animate(pixel_y = 0, time = 0.5 SECONDS, easing = BOUNCE_EASING | EASE_IN) + if (3) + Shake(pixelshiftx = 2, pixelshifty = 0, shake_interval = 0.3 SECONDS) + var/next_jiggle = rand(5 SECONDS, 10 SECONDS) / (steps_travelled >= steps_to_hatch * WATCHER_EGG_LIVELY_MOD ? 2 : 1) + addtimer(CALLBACK(src, PROC_REF(jiggle)), next_jiggle, TIMER_DELETE_ME) + +#undef WATCHER_EGG_LIVELY_MOD +#undef WATCHER_EGG_ACTIVE_MOD + + +/// A cute pet who will occasionally attack lavaland mobs for you +/obj/item/watcher_hatchling + name = "watcher hatchling" + desc = "A newly born watcher, apparently free of the Necropolis' corruption. Perhaps one of the last." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "watcher_baby" + w_class = WEIGHT_CLASS_SMALL + /// The effect we create when out and about + var/obj/effect/watcher_orbiter/orbiter + /// Who are we orbiting? + var/mob/living/owner + +/obj/item/watcher_hatchling/attack_self(mob/user, modifiers) + . = ..() + if (!isnull(orbiter)) + watcher_return() + return + orbiter = new (get_turf(src)) + orbiter.follow(user) + owner = user + RegisterSignal(owner, COMSIG_QDELETING, PROC_REF(remove_owner)) + RegisterSignal(orbiter, COMSIG_QDELETING, PROC_REF(remove_orbiter)) + +/obj/item/watcher_hatchling/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() + if (isnull(orbiter)) + return + var/mob/holder = recursive_loc_check(src, /mob) + if (holder != owner) + watcher_return() + +/// If the guy we are orbiting is deleted but somehow we aren't +/obj/item/watcher_hatchling/proc/remove_owner() + SIGNAL_HANDLER + UnregisterSignal(owner, COMSIG_QDELETING) + owner = null + +/// In the more likely event that our orbiter is deleted, stop holding a reference to it +/obj/item/watcher_hatchling/proc/remove_orbiter() + SIGNAL_HANDLER + orbiter = null // No need to unregister signal because we only call this when it deletes + +/// Get back in your ball pikachu +/obj/item/watcher_hatchling/proc/watcher_return() + qdel(orbiter) + remove_owner() + + +/// Orbiting visual which shoots at mining mobs +/obj/effect/watcher_orbiter + name = "watcher hatchling" + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "watcher_baby" + layer = EDGED_TURF_LAYER // Don't render under lightbulbs + plane = GAME_PLANE_UPPER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + pixel_y = 22 + alpha = 0 + /// Who are we following? + var/atom/parent + /// Datum which keeps us hanging out with our parent + var/datum/movement_detector/tracker + /// Type of projectile we fire + var/projectile_type = /obj/projectile/baby_watcher_blast + /// Sound to make when we shoot + var/projectile_sound = 'sound/weapons/pierce.ogg' + /// Time between taking potshots at goliaths + var/fire_delay = 5 SECONDS + /// How much faster do we shoot when avenging our parent? + var/on_death_multiplier = 5 + /// Time taken between shots + COOLDOWN_DECLARE(shot_cooldown) + /// Types of mobs to attack + var/list/target_faction = list(FACTION_MINING) + +/obj/effect/watcher_orbiter/Initialize(mapload) + . = ..() + START_PROCESSING(SSobj, src) + +// Shuttle rotation fucks with our position, we just want to stick with our guy +/obj/effect/watcher_orbiter/shuttleRotate(rotation, params) + return + +/obj/effect/watcher_orbiter/Destroy(force) + STOP_PROCESSING(SSobj, src) + QDEL_NULL(tracker) + return ..() + +/obj/effect/watcher_orbiter/process(seconds_per_tick) + if (!COOLDOWN_FINISHED(src, shot_cooldown)) + return + for (var/mob/living/potential_target in oview(5, src)) + if (!ismining(potential_target) || potential_target.stat == DEAD) + continue + if (!faction_check(target_faction, potential_target.faction)) + continue + shoot_at(potential_target) + return + +/// Take a shot +/obj/effect/watcher_orbiter/proc/shoot_at(atom/target) + COOLDOWN_START(src, shot_cooldown, fire_delay) + fire_projectile(projectile_type, target, projectile_sound, ignore_targets = list(parent)) + +/// Set ourselves up to track and orbit around a guy +/obj/effect/watcher_orbiter/proc/follow(atom/movable/target) + parent = target + glide_size = target.glide_size + animate(src, pixel_y = 26, alpha = 255, time = 0.5 SECONDS) + addtimer(CALLBACK(src, PROC_REF(orbit_animation)), 0.5 SECONDS, TIMER_DELETE_ME) + tracker = new(target, CALLBACK(src, PROC_REF(on_parent_moved))) + RegisterSignal(target, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(on_glide_size_changed)) + RegisterSignal(target, COMSIG_QDELETING, PROC_REF(on_parent_deleted)) + RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_parent_died)) + RegisterSignal(target, COMSIG_LIVING_REVIVE, PROC_REF(on_parent_revived)) + +/// Do our orbiting animation +/obj/effect/watcher_orbiter/proc/orbit_animation() + animate(src, pixel_y = 26, time = 1 SECONDS, loop = -1, easing = SINE_EASING, flags = ANIMATION_PARALLEL) + animate(pixel_y = 18, time = 1 SECONDS, easing = SINE_EASING) + animate(src, pixel_x = 20, time = 0.5 SECONDS, loop = -1, easing = SINE_EASING | EASE_OUT, flags = ANIMATION_PARALLEL) + animate(pixel_x = 0, time = 0.5 SECONDS, easing = SINE_EASING | EASE_IN) + animate(pixel_x = -20, time = 0.5 SECONDS, easing = SINE_EASING | EASE_OUT) + animate(pixel_x = 0, time = 0.5 SECONDS, easing = SINE_EASING | EASE_IN) + +/// Follow our parent +/obj/effect/watcher_orbiter/proc/on_parent_moved(atom/movable/parent, atom/mover, atom/old_loc, direction) + if(parent.loc == old_loc) + return + var/turf/new_turf = get_turf(parent) + if(isnull(new_turf)) + qdel(src) + return + if (loc != new_turf) + abstract_move(new_turf) + +/// Make sure we glide at the same speed as our parent +/obj/effect/watcher_orbiter/proc/on_glide_size_changed(atom/source, new_glide_size) + SIGNAL_HANDLER + glide_size = new_glide_size + +/// Called if the guy we're tracking is deleted somehow +/obj/effect/watcher_orbiter/proc/on_parent_deleted() + SIGNAL_HANDLER + parent = null + qdel(src) + +/// We must guard this corpse +/obj/effect/watcher_orbiter/proc/on_parent_died(mob/living/parent) + SIGNAL_HANDLER + visible_message(span_notice("[src] emits a piteous keening in mourning of [parent]!")) + fire_delay /= on_death_multiplier + +/// Exit hyperactive mode +/obj/effect/watcher_orbiter/proc/on_parent_revived(mob/living/parent) + SIGNAL_HANDLER + visible_message(span_notice("[src] chirps happily as [parent] suddenly gasps for breath!")) + fire_delay *= on_death_multiplier + + +/// Beam fired by a baby watcher, doesn't actually do less damage than its parent +/obj/projectile/baby_watcher_blast + name = "hatchling beam" + icon_state = "ice_2" + damage = 10 + damage_type = BRUTE // Mining mobs don't take a lot of burn damage so we'll pretend + speed = 1 + pixel_speed_multiplier = 0.5 + +/obj/projectile/baby_watcher_blast/Initialize(mapload) + . = ..() + transform = transform.Scale(0.5) diff --git a/code/modules/meteors/meteor_spawning.dm b/code/modules/meteors/meteor_spawning.dm index eac365bc2a83e..97c359d03bfba 100644 --- a/code/modules/meteors/meteor_spawning.dm +++ b/code/modules/meteors/meteor_spawning.dm @@ -109,7 +109,7 @@ new_changeling.log_message("was spawned as a midround space changeling by an event.", LOG_GAME) var/datum/antagonist/changeling/changeling_datum = locate() in player_mind.antag_datums - changeling_datum.give_power(/datum/action/changeling/suit/organic_space_suit) + changeling_datum.give_power(/datum/action/changeling/void_adaption) changeling_datum.give_power(/datum/action/changeling/weapon/arm_blade) new_changeling.equipOutfit(/datum/outfit/changeling_space) diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm index caee1bbac8d2b..4ceddb09854ca 100644 --- a/code/modules/mining/equipment/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher.dm @@ -437,3 +437,36 @@ chaser.monster_damage_boost = FALSE // Weaker cuz no cooldown chaser.damage = 20 log_combat(user, target, "fired a chaser at", src) + +/obj/item/crusher_trophy/ice_demon_cube + name = "demonic cube" + desc = "A stone cold cube dropped from an ice demon." + icon_state = "ice_demon_cube" + denied_type = /obj/item/crusher_trophy/ice_demon_cube + ///how many will we summon? + var/summon_amount = 2 + ///cooldown to summon demons upon the target + COOLDOWN_DECLARE(summon_cooldown) + +/obj/item/crusher_trophy/ice_demon_cube/effect_desc() + return "mark detonation to unleash demonic ice clones upon the target" + +/obj/item/crusher_trophy/ice_demon_cube/on_mark_detonation(mob/living/target, mob/living/user) + if(isnull(target) || !COOLDOWN_FINISHED(src, summon_cooldown)) + return + for(var/i in 1 to summon_amount) + var/turf/drop_off = find_dropoff_turf(target, user) + var/mob/living/basic/mining/demon_afterimage/crusher/friend = new(drop_off) + friend.faction = list(FACTION_NEUTRAL) + friend.befriend(user) + friend.ai_controller?.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target) + COOLDOWN_START(src, summon_cooldown, 30 SECONDS) + +///try to make them spawn all around the target to surround him +/obj/item/crusher_trophy/ice_demon_cube/proc/find_dropoff_turf(mob/living/target, mob/living/user) + var/list/turfs_list = get_adjacent_open_turfs(target) + for(var/turf/possible_turf in turfs_list) + if(possible_turf.is_blocked_turf()) + continue + return possible_turf + return get_turf(user) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 7e288ebcee560..4660a1b054651 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -286,6 +286,10 @@ I.dropped(src) return FALSE +/// Returns true if a mob is holding something +/mob/proc/is_holding_items() + return !!locate(/obj/item) in held_items + /mob/proc/drop_all_held_items() . = FALSE for(var/obj/item/I in held_items) diff --git a/code/modules/mob/living/basic/basic.dm b/code/modules/mob/living/basic/basic.dm index aebb54770d702..4ad94270837ea 100644 --- a/code/modules/mob/living/basic/basic.dm +++ b/code/modules/mob/living/basic/basic.dm @@ -117,13 +117,23 @@ if(speak_emote) speak_emote = string_list(speak_emote) - if(unsuitable_atmos_damage != 0) - //String assoc list returns a cached list, so this is like a static list to pass into the element below. - habitable_atmos = string_assoc_list(habitable_atmos) - AddElement(/datum/element/atmos_requirements, habitable_atmos, unsuitable_atmos_damage) + apply_atmos_requirements() + apply_temperature_requirements() + +/// Ensures this mob can take atmospheric damage if it's supposed to +/mob/living/basic/proc/apply_atmos_requirements() + if(unsuitable_atmos_damage == 0) + return + //String assoc list returns a cached list, so this is like a static list to pass into the element below. + habitable_atmos = string_assoc_list(habitable_atmos) + AddElement(/datum/element/atmos_requirements, habitable_atmos, unsuitable_atmos_damage) + +/// Ensures this mob can take temperature damage if it's supposed to +/mob/living/basic/proc/apply_temperature_requirements() + if(unsuitable_cold_damage == 0 && unsuitable_heat_damage == 0) + return + AddElement(/datum/element/basic_body_temp_sensitive, minimum_survivable_temperature, maximum_survivable_temperature, unsuitable_cold_damage, unsuitable_heat_damage) - if(unsuitable_cold_damage != 0 && unsuitable_heat_damage != 0) - AddElement(/datum/element/basic_body_temp_sensitive, minimum_survivable_temperature, maximum_survivable_temperature, unsuitable_cold_damage, unsuitable_heat_damage) /mob/living/basic/Life(seconds_per_tick = SSMOBS_DT, times_fired) . = ..() @@ -138,6 +148,7 @@ /mob/living/basic/death(gibbed) . = ..() if(basic_mob_flags & DEL_ON_DEATH) + ghostize(can_reenter_corpse = FALSE) qdel(src) else health = 0 @@ -191,7 +202,7 @@ . = ..() if(stat != DEAD) return - . += span_deadsay("Upon closer examination, [p_they()] appear[p_s()] to be [HAS_TRAIT(user.mind, TRAIT_NAIVE) ? "asleep" : "dead"].") + . += span_deadsay("Upon closer examination, [p_they()] appear[p_s()] to be [HAS_MIND_TRAIT(user, TRAIT_NAIVE) ? "asleep" : "dead"].") /mob/living/basic/proc/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE) face_atom(target) @@ -207,10 +218,24 @@ melee_attack(attack_target, modifiers) /mob/living/basic/vv_edit_var(vname, vval) + switch(vname) + if(NAMEOF(src, habitable_atmos), NAMEOF(src, unsuitable_atmos_damage)) + RemoveElement(/datum/element/atmos_requirements, habitable_atmos, unsuitable_atmos_damage) + . = TRUE + if(NAMEOF(src, minimum_survivable_temperature), NAMEOF(src, maximum_survivable_temperature), NAMEOF(src, unsuitable_cold_damage), NAMEOF(src, unsuitable_heat_damage)) + RemoveElement(/datum/element/basic_body_temp_sensitive, minimum_survivable_temperature, maximum_survivable_temperature, unsuitable_cold_damage, unsuitable_heat_damage) + . = TRUE + . = ..() - if(vname == NAMEOF(src, speed)) - datum_flags |= DF_VAR_EDITED - set_varspeed(vval) + + switch(vname) + if(NAMEOF(src, habitable_atmos), NAMEOF(src, unsuitable_atmos_damage)) + apply_atmos_requirements() + if(NAMEOF(src, minimum_survivable_temperature), NAMEOF(src, maximum_survivable_temperature), NAMEOF(src, unsuitable_cold_damage), NAMEOF(src, unsuitable_heat_damage)) + apply_temperature_requirements() + if(NAMEOF(src, speed)) + datum_flags |= DF_VAR_EDITED + set_varspeed(vval) /mob/living/basic/proc/set_varspeed(var_value) speed = var_value @@ -260,3 +285,25 @@ else if(on_fire && !isnull(last_icon_state)) return last_icon_state return null + +/mob/living/basic/put_in_hands(obj/item/I, del_on_fail = FALSE, merge_stacks = TRUE, ignore_animation = TRUE) + . = ..() + if (.) + update_held_items() + +/mob/living/basic/update_held_items() + . = ..() + if(isnull(client) || isnull(hud_used) || hud_used.hud_version == HUD_STYLE_NOHUD) + return + var/turf/our_turf = get_turf(src) + for(var/obj/item/held in held_items) + var/index = get_held_index_of_item(held) + SET_PLANE(held, ABOVE_HUD_PLANE, our_turf) + held.screen_loc = ui_hand_position(index) + client.screen |= held + +/mob/living/basic/get_body_temp_heat_damage_limit() + return maximum_survivable_temperature + +/mob/living/basic/get_body_temp_cold_damage_limit() + return minimum_survivable_temperature diff --git a/code/modules/mob/living/basic/clown/clown.dm b/code/modules/mob/living/basic/clown/clown.dm index 7871536135608..5682edf933907 100644 --- a/code/modules/mob/living/basic/clown/clown.dm +++ b/code/modules/mob/living/basic/clown/clown.dm @@ -37,7 +37,7 @@ 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, + BB_SPEAK_CHANCE = 5, ) ///do we waddle (honk) var/waddles = TRUE @@ -150,9 +150,9 @@ ), BB_EMOTE_HEAR = list("honks", "contemplates its existence"), BB_EMOTE_SEE = list("sweats", "jiggles"), - BB_EMOTE_CHANCE = 5, + BB_SPEAK_CHANCE = 5, ) - + /mob/living/basic/clown/fleshclown/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) @@ -185,7 +185,7 @@ emotes = list( BB_EMOTE_SAY = list("YA-HONK!!!"), BB_EMOTE_HEAR = list("honks", "squeaks"), - BB_EMOTE_CHANCE = 60, + BB_SPEAK_CHANCE = 60, ) /mob/living/basic/clown/clownhulk @@ -221,7 +221,7 @@ BB_EMOTE_SAY = list("HONK", "Honk!", "HAUAUANK!!!", "GUUURRRRAAAHHH!!!"), BB_EMOTE_HEAR = list("honks", "grunts"), BB_EMOTE_SEE = list("sweats"), - BB_EMOTE_CHANCE = 5, + BB_SPEAK_CHANCE = 5, ) /mob/living/basic/clown/clownhulk/chlown @@ -252,7 +252,7 @@ emotes = list( BB_EMOTE_SAY = list("HONK", "Honk!", "Bruh", "cheeaaaahhh?"), BB_EMOTE_SEE = list("asserts his dominance", "emasculates everyone implicitly"), - BB_EMOTE_CHANCE = 5, + BB_SPEAK_CHANCE = 5, ) /mob/living/basic/clown/clownhulk/honkmunculus @@ -318,7 +318,7 @@ 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, + BB_SPEAK_CHANCE = 5, ) /mob/living/basic/clown/mutant @@ -354,7 +354,7 @@ emotes = list( BB_EMOTE_SAY = list("aaaaaahhhhuuhhhuhhhaaaaa", "AAAaaauuuaaAAAaauuhhh", "huuuuuh... hhhhuuuooooonnnnkk", "HuaUAAAnKKKK"), BB_EMOTE_SEE = list("squirms", "writhes", "pulsates", "froths", "oozes"), - BB_EMOTE_CHANCE = 10, + BB_SPEAK_CHANCE = 10, ) /mob/living/basic/clown/mutant/slow diff --git a/code/modules/mob/living/basic/constructs/_construct.dm b/code/modules/mob/living/basic/constructs/_construct.dm new file mode 100644 index 0000000000000..f2e55cceb86b3 --- /dev/null +++ b/code/modules/mob/living/basic/constructs/_construct.dm @@ -0,0 +1,156 @@ +/mob/living/basic/construct + icon = 'icons/mob/nonhuman-player/cult.dmi' + gender = NEUTER + basic_mob_flags = DEL_ON_DEATH + combat_mode = TRUE + mob_biotypes = MOB_MINERAL | MOB_SPECIAL + faction = list(FACTION_CULT) + unsuitable_atmos_damage = 0 + minimum_survivable_temperature = 0 + maximum_survivable_temperature = INFINITY + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + pressure_resistance = 100 + speed = 0 + unique_name = TRUE + initial_language_holder = /datum/language_holder/construct + death_message = "collapses in a shattered heap." + + speak_emote = list("hisses") + 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 = "punches" + response_harm_simple = "punch" + + // Vivid red, cause cult theme + lighting_cutoff_red = 30 + lighting_cutoff_green = 5 + lighting_cutoff_blue = 20 + + /// List of spells that this construct can cast + var/list/construct_spells = list() + /// Flavor text shown to players when they spawn as this construct + var/playstyle_string = "You are a generic construct. Your job is to not exist, and you should probably adminhelp this." + /// The construct's master + var/master = null + /// Whether this construct is currently seeking nar nar + var/seeking = FALSE + /// Whether this construct can repair other constructs or cult buildings. Gets the healing_touch component if so. + var/can_repair = FALSE + /// Whether this construct can repair itself. Works independently of can_repair. + var/can_repair_self = FALSE + /// Theme controls color. THEME_CULT is red THEME_WIZARD is purple and THEME_HOLY is blue + var/theme = THEME_CULT + /// What flavor of gunk does this construct drop on death? + var/static/list/remains = list(/obj/item/ectoplasm/construct) + /// Can this construct smash walls? Gets the wall_smasher element if so. + var/smashes_walls = FALSE + +/mob/living/basic/construct/Initialize(mapload) + . = ..() + AddElement(/datum/element/simple_flying) + if(length(remains)) + AddElement(/datum/element/death_drops, remains) + if(smashes_walls) + AddElement(/datum/element/wall_smasher, strength_flag = ENVIRONMENT_SMASH_WALLS) + if(can_repair) + AddComponent(\ + /datum/component/healing_touch,\ + heal_brute = 5,\ + heal_burn = 0,\ + heal_time = 0,\ + valid_targets_typecache = typecacheof(list(/mob/living/basic/construct, /mob/living/simple_animal/hostile/construct, /mob/living/simple_animal/shade)),\ + self_targetting = can_repair_self ? HEALING_TOUCH_ANYONE : HEALING_TOUCH_NOT_SELF,\ + action_text = "%SOURCE% begins repairing %TARGET%'s dents.",\ + complete_text = "%TARGET%'s dents are repaired.",\ + show_health = TRUE,\ + heal_color = COLOR_CULT_RED,\ + ) + var/static/list/structure_types = typecacheof(list(/obj/structure/destructible/cult)) + AddElement(\ + /datum/element/structure_repair,\ + structure_types_typecache = structure_types,\ + ) + add_traits(list(TRAIT_HEALS_FROM_CULT_PYLONS, TRAIT_SPACEWALK), INNATE_TRAIT) + for(var/spell in construct_spells) + var/datum/action/new_spell = new spell(src) + new_spell.Grant(src) + + var/spell_count = 1 + for(var/datum/action/spell as anything in actions) + if(!(spell.type in construct_spells)) + continue + + var/pos = 2 + spell_count * 31 + if(construct_spells.len >= 4) + pos -= 31 * (construct_spells.len - 4) + spell.default_button_position = "6:[pos],4:-2" // Set the default position to this random position + spell_count++ + update_action_buttons() + + if(icon_state) + add_overlay("glow_[icon_state]_[theme]") + +/mob/living/basic/construct/Login() + . = ..() + if(!. || !client) + return FALSE + to_chat(src, span_bold(playstyle_string)) + +/mob/living/basic/construct/examine(mob/user) + var/text_span + switch(theme) + if(THEME_CULT) + text_span = "cult" + if(THEME_WIZARD) + text_span = "purple" + if(THEME_HOLY) + text_span = "blue" + . = list("This is [icon2html(src, user)] \a [src]!\n[desc]") + if(health < maxHealth) + if(health >= maxHealth/2) + . += span_warning("[p_They()] look[p_s()] slightly dented.") + else + . += span_warning(span_bold("[p_They()] look[p_s()] severely dented!")) + . += "" + return . + +/mob/living/basic/construct/narsie_act() + return + +/mob/living/basic/construct/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE) + return FALSE + +// Allows simple constructs to repair basic constructs. +/mob/living/basic/construct/attack_animal(mob/living/simple_animal/user, list/modifiers) + if(!isconstruct(user)) + if(src != user) + return ..() + return + + if(src == user) //basic constructs use the healing hands component instead + return + + var/mob/living/simple_animal/hostile/construct/doll = user + if(!doll.can_repair || (doll == src && !doll.can_repair_self)) + return ..() + if(theme != doll.theme) + return ..() + + if(health >= maxHealth) + to_chat(user, span_cult("You cannot repair [src]'s dents, as [p_they()] [p_have()] none!")) + return + + heal_overall_damage(brute = 5) + + Beam(user, icon_state = "sendbeam", time = 4) + user.visible_message( + span_danger("[user] repairs some of \the [src]'s dents."), + span_cult("You repair some of [src]'s dents, leaving [src] at [health]/[maxHealth] health."), + ) + +/// Construct ectoplasm. Largely a placeholder, since the death drop element needs a unique list. +/obj/item/ectoplasm/construct + name = "blood-red ectoplasm" + desc = "Has a pungent metallic smell." diff --git a/code/modules/mob/living/simple_animal/hostile/constructs/harvester.dm b/code/modules/mob/living/basic/constructs/harvester.dm similarity index 69% rename from code/modules/mob/living/simple_animal/hostile/constructs/harvester.dm rename to code/modules/mob/living/basic/constructs/harvester.dm index 8c5fc8eae37b3..30b3099487282 100644 --- a/code/modules/mob/living/simple_animal/hostile/constructs/harvester.dm +++ b/code/modules/mob/living/basic/constructs/harvester.dm @@ -1,4 +1,4 @@ -/mob/living/simple_animal/hostile/construct/harvester +/mob/living/basic/construct/harvester name = "Harvester" real_name = "Harvester" desc = "A long, thin construct built to herald Nar'Sie's rise. It'll be all over soon." @@ -24,58 +24,33 @@ can_repair = TRUE slowed_by_drag = FALSE - -/mob/living/simple_animal/hostile/construct/harvester/Bump(atom/thing) +/mob/living/basic/construct/harvester/Initialize(mapload) . = ..() - if(!istype(thing, /turf/closed/wall/mineral/cult) || thing == loc) - return // we can go through cult walls - var/atom/movable/stored_pulling = pulling - - if(stored_pulling) - stored_pulling.setDir(get_dir(stored_pulling.loc, loc)) - stored_pulling.forceMove(loc) - forceMove(thing) - - if(stored_pulling) - start_pulling(stored_pulling, supress_message = TRUE) //drag anything we're pulling through the wall with us by magic + AddElement(\ + /datum/element/amputating_limbs,\ + surgery_time = 0,\ + surgery_verb = "slicing",\ + minimum_stat = CONSCIOUS,\ + ) + AddElement(/datum/element/wall_walker, /turf/closed/wall/mineral/cult) + var/datum/action/innate/seek_prey/seek = new(src) + seek.Grant(src) + seek.Activate() -/mob/living/simple_animal/hostile/construct/harvester/AttackingTarget() - if(!iscarbon(target)) +/// If the attack is a limbless carbon, abort the attack, paralyze them, and get a special message from Nar'Sie. +/mob/living/basic/construct/harvester/resolve_unarmed_attack(atom/attack_target, list/modifiers) + if(!iscarbon(attack_target)) return ..() + var/mob/living/carbon/carbon_target = attack_target - var/mob/living/carbon/victim = target - if(HAS_TRAIT(victim, TRAIT_NODISMEMBER)) - return ..() //ATTACK! - - var/list/parts = list() - var/strong_limbs = 0 - - for(var/obj/item/bodypart/limb as anything in victim.bodyparts) + for(var/obj/item/bodypart/limb as anything in carbon_target.bodyparts) if(limb.body_part == HEAD || limb.body_part == CHEST) continue - if(!(limb.bodypart_flags & BODYPART_UNREMOVABLE)) - parts += limb - else - strong_limbs++ - - if(!LAZYLEN(parts)) - if(strong_limbs) // they have limbs we can't remove, and no parts we can, attack! - return ..() - victim.Paralyze(60) - visible_message(span_danger("[src] knocks [victim] down!")) - to_chat(src, span_cultlarge("\"Bring [victim.p_them()] to me.\"")) - return FALSE - - do_attack_animation(victim) - var/obj/item/bodypart/limb = pick(parts) - limb.dismember() - return FALSE - -/mob/living/simple_animal/hostile/construct/harvester/Initialize(mapload) - . = ..() - var/datum/action/innate/seek_prey/seek = new() - seek.Grant(src) - seek.Activate() + return ..() //if any arms or legs exist, attack + + carbon_target.Paralyze(6 SECONDS) + visible_message(span_danger("[src] knocks [carbon_target] down!")) + to_chat(src, span_cultlarge("\"Bring [carbon_target.p_them()] to me.\"")) /datum/action/innate/seek_master name = "Seek your Master" @@ -89,7 +64,7 @@ /// Where is nar nar? Are we even looking? var/tracking = FALSE /// The construct we're attached to - var/mob/living/simple_animal/hostile/construct/the_construct + var/mob/living/basic/construct/the_construct /datum/action/innate/seek_master/Grant(mob/living/player) the_construct = player @@ -132,7 +107,7 @@ /datum/action/innate/seek_prey/Activate() if(GLOB.cult_narsie == null) return - var/mob/living/simple_animal/hostile/construct/harvester/the_construct = owner + var/mob/living/basic/construct/harvester/the_construct = owner if(the_construct.seeking) desc = "None can hide from Nar'Sie, activate to track a survivor attempting to flee the red harvest!" diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm index 017eac3cb51db..3c8b018cc935e 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm @@ -85,7 +85,7 @@ /datum/targetting_datum/basic/bee -/datum/targetting_datum/basic/bee/can_attack(mob/living/owner, atom/target) +/datum/targetting_datum/basic/bee/can_attack(mob/living/owner, atom/target, vision_range) if(!isliving(target)) return FALSE . = ..() diff --git a/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm b/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm index 89f5174f15e0d..99ff6b5274829 100644 --- a/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm +++ b/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm @@ -88,8 +88,6 @@ GLOBAL_VAR_INIT(chicken_count, 0) 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/random_speech/chicken, ) diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm index cdfb0868f9e02..7db589334ffc3 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm @@ -62,7 +62,7 @@ ///moonicorns will not attack people holding something that could tame them. /datum/targetting_datum/basic/allow_items/moonicorn -/datum/targetting_datum/basic/allow_items/moonicorn/can_attack(mob/living/living_mob, atom/the_target) +/datum/targetting_datum/basic/allow_items/moonicorn/can_attack(mob/living/living_mob, atom/the_target, vision_range) . = ..() if(!.) return FALSE diff --git a/code/modules/mob/living/basic/farm_animals/deer.dm b/code/modules/mob/living/basic/farm_animals/deer.dm index 445ad831e5951..7907e6684431f 100644 --- a/code/modules/mob/living/basic/farm_animals/deer.dm +++ b/code/modules/mob/living/basic/farm_animals/deer.dm @@ -34,9 +34,8 @@ /datum/ai_controller/basic_controller/deer blackboard = list( - BB_BASIC_MOB_FLEEING = TRUE, BB_STATIONARY_MOVE_TO_TARGET = TRUE, - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction, + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/farm_animals/goat/_goat.dm b/code/modules/mob/living/basic/farm_animals/goat/_goat.dm index f698e5015e8c7..db49ae8df9ffd 100644 --- a/code/modules/mob/living/basic/farm_animals/goat/_goat.dm +++ b/code/modules/mob/living/basic/farm_animals/goat/_goat.dm @@ -34,7 +34,10 @@ blood_volume = BLOOD_VOLUME_NORMAL ai_controller = /datum/ai_controller/basic_controller/goat - + /// How often will we develop an evil gleam in our eye? + var/gleam_delay = 20 SECONDS + /// Time until we can next gleam evilly + COOLDOWN_DECLARE(gleam_cooldown) /// List of stuff (flora) that we want to eat var/static/list/edibles = list( /obj/structure/alien/resin/flower_bud, @@ -85,15 +88,14 @@ return COMPONENT_HOSTILE_NO_ATTACK -/// If we are being attacked by someone who we are already retaliating against, give a nice fluff message. +/// If we are being attacked by someone, give a nice fluff message. But only once in a while. /mob/living/basic/goat/proc/on_attacked(datum/source, atom/attacker, attack_flags) - var/is_attacker_shitlisted = locate(attacker) in ai_controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST] - if(!is_attacker_shitlisted) + if (!COOLDOWN_FINISHED(src, gleam_cooldown)) return - visible_message( span_danger("[src] gets an evil-looking gleam in [p_their()] eye."), ) + COOLDOWN_START(src, gleam_cooldown, gleam_delay) /// Handles automagically eating a plant when we move into a turf that has one. /mob/living/basic/goat/proc/on_move(datum/source, atom/entering_loc) diff --git a/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm b/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm index 41fc448a3b6ac..f8463b1967537 100644 --- a/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm +++ b/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm @@ -9,6 +9,7 @@ idle_behavior = /datum/idle_behavior/idle_random_walk planning_subtrees = list( + /datum/ai_planning_subtree/capricious_retaliate, // Capricious like Capra, get it? /datum/ai_planning_subtree/target_retaliate, /datum/ai_planning_subtree/find_food, /datum/ai_planning_subtree/basic_melee_attack_subtree, diff --git a/code/modules/mob/living/basic/farm_animals/goat/goat_subtypes.dm b/code/modules/mob/living/basic/farm_animals/goat/goat_subtypes.dm index 0638163d03248..19d50fb38097a 100644 --- a/code/modules/mob/living/basic/farm_animals/goat/goat_subtypes.dm +++ b/code/modules/mob/living/basic/farm_animals/goat/goat_subtypes.dm @@ -6,7 +6,7 @@ . = ..() var/area/goat_area = get_area(src) if((bodytemperature < T20C) || istype(goat_area, /area/station/service/kitchen/coldroom)) - . = span_notice("[p_They()] [p_do()]n't seem to be too bothered about the cold.") // special for pete + . += span_notice("[p_They()] [p_do()]n't seem to be too bothered about the cold.") // special for pete /mob/living/basic/goat/pete/add_udder() return //no thank you diff --git a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm new file mode 100644 index 0000000000000..b0926b41811a1 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm @@ -0,0 +1,181 @@ +/// Where do we draw gorilla held overlays? +#define GORILLA_HANDS_LAYER 1 + +/** + * Like a bigger monkey + * They make a lot of noise and punch limbs off unconscious folks + */ +/mob/living/basic/gorilla + name = "Gorilla" + desc = "A ground-dwelling, predominantly herbivorous ape which usually inhabits the forests of central Africa but today is quite far away from there." + icon = 'icons/mob/simple/gorilla.dmi' + icon_state = "crawling" + icon_living = "crawling" + icon_dead = "dead" + health_doll_icon = "crawling" + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + maxHealth = 220 + health = 220 + response_help_continuous = "prods" + response_help_simple = "prod" + response_disarm_continuous = "challenges" + response_disarm_simple = "challenge" + response_harm_continuous = "thumps" + response_harm_simple = "thump" + speed = 0.5 + melee_damage_lower = 15 + melee_damage_upper = 18 + damage_coeff = list(BRUTE = 1, BURN = 1.5, TOX = 1.5, CLONE = 0, STAMINA = 0, OXY = 1.5) + obj_damage = 20 + attack_verb_continuous = "pummels" + attack_verb_simple = "pummel" + attack_sound = 'sound/weapons/punch1.ogg' + unique_name = TRUE + ai_controller = /datum/ai_controller/basic_controller/gorilla + faction = list(FACTION_MONKEY, FACTION_JUNGLE) + butcher_results = list(/obj/item/food/meat/slab/gorilla = 4, /obj/effect/gibspawner/generic/animal = 1) + /// How likely our meaty fist is to stun someone + var/paralyze_chance = 20 + /// A counter for when we can scream again + var/oogas = 0 + /// Types of things we want to find and eat + var/static/list/gorilla_food = list( + /obj/item/food/bread/banana, + /obj/item/food/breadslice/banana, + /obj/item/food/cnds/banana_honk, + /obj/item/food/grown/banana, + /obj/item/food/popsicle/topsicle/banana, + /obj/item/food/salad/fruit, + /obj/item/food/salad/jungle, + /obj/item/food/sundae, + ) + +/mob/living/basic/gorilla/Initialize(mapload) + . = ..() + add_traits(list(TRAIT_ADVANCEDTOOLUSER, TRAIT_CAN_STRIP), ROUNDSTART_TRAIT) + AddElement(/datum/element/wall_smasher) + AddElement(/datum/element/dextrous) + AddElement(/datum/element/footstep, FOOTSTEP_MOB_BAREFOOT) + AddElement(/datum/element/basic_eating, heal_amt = 10, food_types = gorilla_food) + AddElement( + /datum/element/amputating_limbs, \ + surgery_time = 0 SECONDS, \ + surgery_verb = "punches",\ + ) + AddComponent(/datum/component/personal_crafting) + AddComponent(/datum/component/basic_inhands, y_offset = -1) + ai_controller?.set_blackboard_key(BB_BASIC_FOODS, gorilla_food) + +/mob/living/basic/gorilla/update_overlays() + . = ..() + if (is_holding_items()) + . += "standing_overlay" + +/mob/living/basic/gorilla/update_icon_state() + . = ..() + if (stat == DEAD) + return + icon_state = is_holding_items() ? "standing" : "crawling" + +/mob/living/basic/gorilla/update_held_items() + . = ..() + update_appearance(UPDATE_ICON) + if (is_holding_items()) + add_movespeed_modifier(/datum/movespeed_modifier/gorilla_standing) + else + remove_movespeed_modifier(/datum/movespeed_modifier/gorilla_standing) + +/mob/living/basic/gorilla/melee_attack(mob/living/target, list/modifiers, ignore_cooldown) + . = ..() + if (!. || !isliving(target)) + return + ooga_ooga() + if (prob(paralyze_chance)) + target.Paralyze(2 SECONDS) + visible_message(span_danger("[src] knocks [target] down!")) + else + target.throw_at(get_edge_target_turf(target, dir), range = rand(1, 2), speed = 7, thrower = src) + +/mob/living/basic/gorilla/gib(drop_bitflags = DROP_BRAIN) + if(!(drop_bitflags & DROP_BRAIN)) + return ..() + var/mob/living/brain/gorilla_brain = new(drop_location()) + gorilla_brain.name = real_name + gorilla_brain.real_name = real_name + mind?.transfer_to(gorilla_brain) + return ..() + +/mob/living/basic/gorilla/can_use_guns(obj/item/gun) + to_chat(src, span_warning("Your meaty finger is much too large for the trigger guard!")) + return FALSE + +/// Assert your dominance with audio cues +/mob/living/basic/gorilla/proc/ooga_ooga() + if (isnull(client)) + return // Sorry NPCs + oogas -= 1 + if(oogas > 0) + return + oogas = rand(2,6) + emote("ooga") + +/// Gorillas are slower when carrying something +/datum/movespeed_modifier/gorilla_standing + blacklisted_movetypes = (FLYING|FLOATING) + multiplicative_slowdown = 0.5 + +/// A smaller gorilla summoned via magic +/mob/living/basic/gorilla/lesser + name = "lesser Gorilla" + desc = "An adolescent Gorilla. It may not be fully grown but, much like a banana, that just means it's sturdier and harder to chew!" + maxHealth = 120 + health = 120 + speed = 0.35 + melee_damage_lower = 10 + melee_damage_upper = 15 + obj_damage = 15 + ai_controller = /datum/ai_controller/basic_controller/gorilla/lesser + butcher_results = list(/obj/item/food/meat/slab/gorilla = 2) + +/mob/living/basic/gorilla/lesser/Initialize(mapload) + . = ..() + transform *= 0.75 + +/// Cargo's wonderful mascot, the tranquil box-carrying ape +/mob/living/basic/gorilla/cargorilla + name = "Cargorilla" // Overriden, normally + icon = 'icons/mob/simple/cargorillia.dmi' + desc = "Cargo's pet gorilla. They seem to have an 'I love Mom' tattoo." + maxHealth = 200 + health = 200 + faction = list(FACTION_NEUTRAL, FACTION_MONKEY, FACTION_JUNGLE) + unique_name = FALSE + ai_controller = null + +/mob/living/basic/gorilla/cargorilla/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_PACIFISM, INNATE_TRAIT) + AddComponent(/datum/component/crate_carrier) + +/** + * Poll ghosts for control of the gorilla. Not added in init because we only want to poll when the round starts. + * Preferably in future we can replace this with a popup on the lobby to queue to become a gorilla. + */ +/mob/living/basic/gorilla/cargorilla/proc/poll_for_gorilla() + AddComponent(\ + /datum/component/ghost_direct_control,\ + poll_candidates = TRUE,\ + poll_length = 30 SECONDS,\ + role_name = "Cargorilla",\ + assumed_control_message = "You are Cargorilla, a pacifist friend of the station and carrier of freight.",\ + poll_ignore_key = POLL_IGNORE_CARGORILLA,\ + after_assumed_control = CALLBACK(src, PROC_REF(became_player_controlled)),\ + ) + +/// Called once a ghost assumes control +/mob/living/basic/gorilla/cargorilla/proc/became_player_controlled() + mind.set_assigned_role(SSjob.GetJobType(/datum/job/cargo_technician)) + mind.special_role = "Cargorilla" + to_chat(src, span_notice("You can pick up crates by clicking on them, and drop them by clicking on the ground.")) + +#undef GORILLA_HANDS_LAYER diff --git a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_accessories.dm b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_accessories.dm new file mode 100644 index 0000000000000..814e56487bf54 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_accessories.dm @@ -0,0 +1,5 @@ +/// Cargorilla's ID card +/obj/item/card/id/advanced/cargo_gorilla + name = "cargorilla ID" + desc = "A card used to provide ID and determine access across the station. A gorilla-sized ID for a gorilla-sized cargo technician." + trim = /datum/id_trim/job/cargo_technician diff --git a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm new file mode 100644 index 0000000000000..c62307542777d --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm @@ -0,0 +1,35 @@ +/// Pretty basic, just click people to death. Also hunt and eat bananas. +/datum/ai_controller/basic_controller/gorilla + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items/gorilla, + BB_EMOTE_KEY = "ooga", + BB_EMOTE_CHANCE = 40, + ) + + 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/run_emote, + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path/gorilla, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/datum/targetting_datum/basic/allow_items/gorilla + stat_attack = UNCONSCIOUS + +/datum/ai_planning_subtree/attack_obstacle_in_path/gorilla + attack_behaviour = /datum/ai_behavior/attack_obstructions/gorilla + +/datum/ai_behavior/attack_obstructions/gorilla + can_attack_turfs = TRUE + +/datum/ai_controller/basic_controller/gorilla/lesser + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items, + BB_EMOTE_KEY = "ooga", + BB_EMOTE_CHANCE = 60, + ) diff --git a/code/modules/mob/living/simple_animal/hostile/gorilla/emotes.dm b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_emotes.dm similarity index 78% rename from code/modules/mob/living/simple_animal/hostile/gorilla/emotes.dm rename to code/modules/mob/living/basic/farm_animals/gorilla/gorilla_emotes.dm index 20166d8139191..94133336c4d49 100644 --- a/code/modules/mob/living/simple_animal/hostile/gorilla/emotes.dm +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_emotes.dm @@ -1,5 +1,5 @@ /datum/emote/gorilla - mob_type_allowed_typecache = /mob/living/simple_animal/hostile/gorilla + mob_type_allowed_typecache = /mob/living/basic/gorilla mob_type_blacklist_typecache = list() /datum/emote/gorilla/ooga @@ -9,4 +9,3 @@ message_param = "oogas at %t." emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE sound = 'sound/creatures/gorilla.ogg' - diff --git a/code/modules/mob/living/basic/farm_animals/pig.dm b/code/modules/mob/living/basic/farm_animals/pig.dm index 46655e503411a..c0ad3f6b349be 100644 --- a/code/modules/mob/living/basic/farm_animals/pig.dm +++ b/code/modules/mob/living/basic/farm_animals/pig.dm @@ -29,11 +29,11 @@ ai_controller = /datum/ai_controller/basic_controller/pig /mob/living/basic/pig/Initialize(mapload) + . = ..() AddElement(/datum/element/pet_bonus, "oinks!") AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/ai_flee_while_injured) make_tameable() - . = ..() ///wrapper for the tameable component addition so you can have non tamable cow subtypes /mob/living/basic/pig/proc/make_tameable() @@ -47,7 +47,7 @@ /datum/ai_controller/basic_controller/pig blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED diff --git a/code/modules/mob/living/basic/farm_animals/pony.dm b/code/modules/mob/living/basic/farm_animals/pony.dm index 4bc09391cb718..e1765f0fdbac6 100644 --- a/code/modules/mob/living/basic/farm_animals/pony.dm +++ b/code/modules/mob/living/basic/farm_animals/pony.dm @@ -24,15 +24,24 @@ gold_core_spawnable = FRIENDLY_SPAWN blood_volume = BLOOD_VOLUME_NORMAL ai_controller = /datum/ai_controller/basic_controller/pony + /// Do we register a unique rider? + var/unique_tamer = FALSE + /// The person we've been tamed by + var/datum/weakref/my_owner + + greyscale_config = /datum/greyscale_config/pony + /// Greyscale color config; 1st color is body, 2nd is mane + var/list/ponycolors = list("#cc8c5d", "#cc8c5d") /mob/living/basic/pony/Initialize(mapload) . = ..() + apply_colour() AddElement(/datum/element/pet_bonus, "whickers.") AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/ai_flee_while_injured) AddElement(/datum/element/waddling) - AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/apple), tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed))) + AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/apple), tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed)), unique = unique_tamer) /mob/living/basic/pony/proc/tamed(mob/living/tamer) can_buckle = TRUE @@ -40,6 +49,7 @@ playsound(src, 'sound/creatures/pony/snort.ogg', 50) AddElement(/datum/element/ridable, /datum/component/riding/creature/pony) visible_message(span_notice("[src] snorts happily.")) + new /obj/effect/temp_visual/heart(loc) ai_controller.replace_planning_subtrees(list( /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, @@ -47,6 +57,30 @@ /datum/ai_planning_subtree/random_speech/pony/tamed )) + if(unique_tamer) + my_owner = WEAKREF(tamer) + RegisterSignal(src, COMSIG_MOVABLE_PREBUCKLE, PROC_REF(on_prebuckle)) + +/mob/living/basic/pony/Destroy() + UnregisterSignal(src, COMSIG_MOVABLE_PREBUCKLE) + my_owner = null + return ..() + +/// Only let us get ridden if the buckler is our owner, if we have a unique owner. +/mob/living/basic/pony/proc/on_prebuckle(mob/source, mob/living/buckler, force, buckle_mob_flags) + SIGNAL_HANDLER + var/mob/living/tamer = my_owner?.resolve() + if(!unique_tamer || (isnull(tamer) && unique_tamer)) + return + if(buckler != tamer) + whinny_angrily() + return COMPONENT_BLOCK_BUCKLE + +/mob/living/basic/pony/proc/apply_colour() + if(!greyscale_config) + return + set_greyscale(colors = ponycolors) + /mob/living/basic/pony/proc/whinny_angrily() manual_emote("whinnies ANGRILY!") @@ -72,7 +106,7 @@ /datum/ai_controller/basic_controller/pony blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction, + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED @@ -86,3 +120,35 @@ /datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/random_speech/pony ) + +// A stronger horse is required for our strongest cowboys. +/mob/living/basic/pony/syndicate + health = 300 + maxHealth = 300 + desc = "A special breed of horse engineered by the syndicate to be capable of surviving in the deep reaches of space. A modern outlaw's best friend." + faction = list(ROLE_SYNDICATE) + ponycolors = list("#5d566f", COLOR_RED) + pressure_resistance = 200 + 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 + unique_tamer = TRUE + +/mob/living/basic/pony/syndicate/Initialize(mapload) + . = ..() + // Help discern your horse from your allies + var/mane_colors = list( + COLOR_RED=6, + COLOR_BLUE=6, + COLOR_PINK=3, + COLOR_GREEN=3, + COLOR_BLACK=3, + COLOR_YELLOW=2, + COLOR_ORANGE=1, + COLOR_WHITE=1, + COLOR_DARK_BROWN=1, + ) + ponycolors = list("#5d566f", pick_weight(mane_colors)) + name = pick("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday") + // Only one person can tame these fellas, and they only need one apple + AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/apple), tame_chance = 100, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed)), unique = unique_tamer) diff --git a/code/modules/mob/living/basic/farm_animals/rabbit.dm b/code/modules/mob/living/basic/farm_animals/rabbit.dm index 92d40e0228e2d..19fd0120ba7e6 100644 --- a/code/modules/mob/living/basic/farm_animals/rabbit.dm +++ b/code/modules/mob/living/basic/farm_animals/rabbit.dm @@ -49,8 +49,7 @@ /datum/ai_controller/basic_controller/rabbit blackboard = list( - BB_BASIC_MOB_FLEEING = TRUE, - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/farm_animals/sheep.dm b/code/modules/mob/living/basic/farm_animals/sheep.dm index 691f1db14e3ce..ac320d8080419 100644 --- a/code/modules/mob/living/basic/farm_animals/sheep.dm +++ b/code/modules/mob/living/basic/farm_animals/sheep.dm @@ -81,8 +81,7 @@ /datum/ai_controller/basic_controller/sheep blackboard = list( - BB_BASIC_MOB_FLEEING = TRUE, - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/health_adjustment.dm b/code/modules/mob/living/basic/health_adjustment.dm index 4ae129d9d6616..9644a1a629905 100644 --- a/code/modules/mob/living/basic/health_adjustment.dm +++ b/code/modules/mob/living/basic/health_adjustment.dm @@ -18,9 +18,7 @@ return . - bruteloss /mob/living/basic/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) - if(!forced && (status_flags & GODMODE)) - return 0 - if(on_damage_adjustment(BRUTE, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_brute_loss(amount, forced, required_bodytype)) return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) @@ -28,9 +26,7 @@ . = adjust_health(amount * damage_coeff[BRUTE] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/basic/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) - if(!forced && (status_flags & GODMODE)) - return 0 - if(on_damage_adjustment(BURN, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_fire_loss(amount, forced, required_bodytype)) return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) @@ -38,9 +34,7 @@ . = adjust_health(amount * damage_coeff[BURN] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/basic/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype, required_respiration_type) - if(!forced && (status_flags & GODMODE)) - return 0 - if(on_damage_adjustment(OXY, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_oxy_loss(amount, forced, required_biotype, required_respiration_type)) return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) @@ -48,9 +42,7 @@ . = adjust_health(amount * damage_coeff[OXY] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/basic/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) - if(!forced && (status_flags & GODMODE)) - return 0 - if(on_damage_adjustment(TOX, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_tox_loss(amount, forced, required_biotype)) return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) @@ -58,7 +50,7 @@ . = adjust_health(amount * damage_coeff[TOX] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/basic/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) - if(on_damage_adjustment(CLONE, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_clone_loss(amount, forced, required_biotype)) return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) @@ -66,7 +58,7 @@ . = adjust_health(amount * damage_coeff[CLONE] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/basic/adjustStaminaLoss(amount, updating_stamina = TRUE, forced = FALSE, required_biotype) - if(on_damage_adjustment(STAMINA, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_stamina_loss(amount, forced, required_biotype)) return 0 . = staminaloss if(forced) diff --git a/code/modules/mob/living/basic/heretic/ash_spirit.dm b/code/modules/mob/living/basic/heretic/ash_spirit.dm new file mode 100644 index 0000000000000..b2d4d8b4d2947 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/ash_spirit.dm @@ -0,0 +1,25 @@ +/** + * Player-only mob which is fast, can jaunt a short distance, and is dangerous at close range + */ +/mob/living/basic/heretic_summon/ash_spirit + name = "Ash Spirit" + real_name = "Ashy" + desc = "A manifestation of ash, trailing a perpetual cloud of short-lived cinders." + icon_state = "ash_walker" + icon_living = "ash_walker" + maxHealth = 75 + health = 75 + melee_damage_lower = 15 + melee_damage_upper = 20 + sight = SEE_TURFS + +/mob/living/basic/heretic_summon/ash_spirit/Initialize(mapload) + . = ..() + var/static/list/actions_to_add = list( + /datum/action/cooldown/spell/fire_sworn, + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash, + /datum/action/cooldown/spell/pointed/cleave, + ) + for (var/action in actions_to_add) + var/datum/action/cooldown/new_action = new action(src) + new_action.Grant(src) diff --git a/code/modules/mob/living/basic/heretic/flesh_stalker.dm b/code/modules/mob/living/basic/heretic/flesh_stalker.dm new file mode 100644 index 0000000000000..6f31b3ce7c523 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/flesh_stalker.dm @@ -0,0 +1,46 @@ +/// Durable ambush mob with an EMP ability +/mob/living/basic/heretic_summon/stalker + name = "Flesh Stalker" + real_name = "Flesh Stalker" + desc = "An abomination cobbled together from varied remains. Its appearance changes slightly every time you blink." + icon_state = "stalker" + icon_living = "stalker" + maxHealth = 150 + health = 150 + melee_damage_lower = 15 + melee_damage_upper = 20 + sight = SEE_MOBS + ai_controller = /datum/ai_controller/basic_controller/stalker + /// Associative list of action types we would like to have, and what blackboard key (if any) to put it in + var/static/list/actions_to_add = list( + /datum/action/cooldown/spell/emp/eldritch = BB_GENERIC_ACTION, + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash = null, + /datum/action/cooldown/spell/shapeshift/eldritch = BB_SHAPESHIFT_ACTION, + ) + +/mob/living/basic/heretic_summon/stalker/Initialize(mapload) + . = ..() + AddComponent(/datum/component/ai_target_timer) + for (var/action_type in actions_to_add) + var/datum/action/new_action = new action_type(src) + new_action.Grant(src) + var/blackboard_key = actions_to_add[action_type] + if (!isnull(blackboard_key)) + ai_controller?.set_blackboard_key(blackboard_key, new_action) + +/// Changes shape and lies in wait when it has no target, uses EMP and attacks once it does +/datum/ai_controller/basic_controller/stalker + ai_traits = CAN_ACT_IN_STASIS + 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/shapechange_ambush, + /datum/ai_planning_subtree/use_mob_ability, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) diff --git a/code/modules/mob/living/basic/heretic/flesh_worm.dm b/code/modules/mob/living/basic/heretic/flesh_worm.dm index 05ef707faf4cf..3c60a9b653c32 100644 --- a/code/modules/mob/living/basic/heretic/flesh_worm.dm +++ b/code/modules/mob/living/basic/heretic/flesh_worm.dm @@ -38,7 +38,7 @@ AddElement(\ /datum/element/amputating_limbs,\ surgery_time = 0 SECONDS,\ - surgery_verb = "tearing",\ + surgery_verb = "tears",\ minimum_stat = CONSCIOUS,\ snip_chance = 10,\ target_zones = GLOB.arm_zones,\ diff --git a/code/modules/mob/living/basic/heretic/heretic_summon.dm b/code/modules/mob/living/basic/heretic/heretic_summon.dm index cf1bcf80aad83..a99508ff65968 100644 --- a/code/modules/mob/living/basic/heretic/heretic_summon.dm +++ b/code/modules/mob/living/basic/heretic/heretic_summon.dm @@ -22,6 +22,10 @@ response_harm_simple = "tear" death_message = "implodes into itself." + unsuitable_atmos_damage = 0 + unsuitable_cold_damage = 0 + unsuitable_heat_damage = 0 + combat_mode = TRUE ai_controller = null speak_emote = list("screams") diff --git a/code/modules/antagonists/heretic/mobs/maid_in_mirror.dm b/code/modules/mob/living/basic/heretic/maid_in_the_mirror.dm similarity index 60% rename from code/modules/antagonists/heretic/mobs/maid_in_mirror.dm rename to code/modules/mob/living/basic/heretic/maid_in_the_mirror.dm index b53bbe147d3df..0976576255305 100644 --- a/code/modules/antagonists/heretic/mobs/maid_in_mirror.dm +++ b/code/modules/mob/living/basic/heretic/maid_in_the_mirror.dm @@ -1,11 +1,11 @@ -// A summon which floats around the station incorporeally, and can appear in any mirror -/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror +/// Scout and assassin who can appear and disappear from glass surfaces. Damaged by being examined. +/mob/living/basic/heretic_summon/maid_in_the_mirror name = "Maid in the Mirror" real_name = "Maid in the Mirror" desc = "A floating and flowing wisp of chilled air. Glancing at it causes it to shimmer slightly." icon = 'icons/mob/simple/mob.dmi' icon_state = "stand" - icon_living = "stand" // Placeholder sprite + icon_living = "stand" // Placeholder sprite... still speak_emote = list("whispers") movement_type = FLOATING status_flags = CANSTUN | CANPUSH @@ -16,36 +16,34 @@ melee_damage_upper = 16 sight = SEE_MOBS | SEE_OBJS | SEE_TURFS death_message = "shatters and vanishes, releasing a gust of cold air." - loot = list( - /obj/item/shard, + /// Whether we take damage when someone looks at us + var/harmed_by_examine = TRUE + /// How often being examined by a specific mob can hurt us + var/recent_examine_damage_cooldown = 10 SECONDS + /// A list of REFs to people who recently examined us + var/list/recent_examiner_refs = list() + +/mob/living/basic/heretic_summon/maid_in_the_mirror/Initialize(mapload) + . = ..() + var/static/list/loot = list( /obj/effect/decal/cleanable/ash, /obj/item/clothing/suit/armor/vest, /obj/item/organ/internal/lungs, + /obj/item/shard, ) - actions_to_add = list(/datum/action/cooldown/spell/jaunt/mirror_walk) + AddElement(/datum/element/death_drops, loot) + var/datum/action/cooldown/spell/jaunt/mirror_walk/jaunt = new (src) + jaunt.Grant(src) - /// Whether we take damage when we're examined - var/weak_on_examine = TRUE - /// The cooldown after being examined that the same mob cannot trigger it again - var/recent_examine_damage_cooldown = 10 SECONDS - /// A list of REFs to people who recently examined us - var/list/recent_examiner_refs = list() - -/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror/death(gibbed) +/mob/living/basic/heretic_summon/maid_in_the_mirror/death(gibbed) var/turf/death_turf = get_turf(src) - death_turf.TakeTemperature(-40) + death_turf.TakeTemperature(-40) // Spooky return ..() // Examining them will harm them, on a cooldown. -/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror/examine(mob/user) +/mob/living/basic/heretic_summon/maid_in_the_mirror/examine(mob/user) . = ..() - if(!weak_on_examine) - return - - if(!isliving(user) || user.stat == DEAD) - return - - if(IS_HERETIC_OR_MONSTER(user) || user == src) + if(!harmed_by_examine || user == src || user.stat == DEAD || !isliving(user) || IS_HERETIC_OR_MONSTER(user)) return var/user_ref = REF(user) @@ -62,7 +60,9 @@ recent_examiner_refs += user_ref apply_damage(maxHealth * 0.1) // We take 10% of our health as damage upon being examined playsound(src, 'sound/effects/ghost2.ogg', 40, TRUE) - addtimer(CALLBACK(src, PROC_REF(clear_recent_examiner), user_ref), recent_examine_damage_cooldown) + addtimer(CALLBACK(src, PROC_REF(clear_recent_examiner), user_ref), recent_examine_damage_cooldown, TIMER_DELETE_ME) + animate(src, alpha = 120, time = 0.5 SECONDS, easing = ELASTIC_EASING, loop = 2, flags = ANIMATION_PARALLEL) + animate(alpha = 255, time = 0.5 SECONDS, easing = ELASTIC_EASING) // If we're examined on low enough health we die straight up else @@ -73,7 +73,7 @@ death() -/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror/proc/clear_recent_examiner(mob_ref) +/mob/living/basic/heretic_summon/maid_in_the_mirror/proc/clear_recent_examiner(mob_ref) if(!(mob_ref in recent_examiner_refs)) return diff --git a/code/modules/mob/living/basic/heretic/rust_walker.dm b/code/modules/mob/living/basic/heretic/rust_walker.dm new file mode 100644 index 0000000000000..070326c0281ae --- /dev/null +++ b/code/modules/mob/living/basic/heretic/rust_walker.dm @@ -0,0 +1,87 @@ +/// Pretty simple mob which creates areas of rust and has a rust-creating projectile spell +/mob/living/basic/heretic_summon/rust_walker + name = "Rust Walker" + real_name = "Rusty" + desc = "A grinding, clanking construct which leaches life from its surroundings with every armoured step." + icon_state = "rust_walker_s" + base_icon_state = "rust_walker" + icon_living = "rust_walker_s" + maxHealth = 75 + health = 75 + melee_damage_lower = 15 + melee_damage_upper = 20 + sight = SEE_TURFS + speed = 1 + ai_controller = /datum/ai_controller/basic_controller/rust_walker + +/mob/living/basic/heretic_summon/rust_walker/Initialize(mapload) + . = ..() + AddElement(/datum/element/footstep, FOOTSTEP_MOB_RUST) + var/datum/action/cooldown/spell/aoe/rust_conversion/small/conversion = new(src) + conversion.Grant(src) + ai_controller?.set_blackboard_key(BB_GENERIC_ACTION, conversion) + + var/datum/action/cooldown/spell/basic_projectile/rust_wave/short/wave = new(src) + wave.Grant(src) + ai_controller?.set_blackboard_key(BB_TARGETTED_ACTION, wave) + +/mob/living/basic/heretic_summon/rust_walker/setDir(newdir) + . = ..() + update_appearance(UPDATE_ICON_STATE) + +/mob/living/basic/heretic_summon/rust_walker/update_icon_state() + . = ..() + if(stat == DEAD) // We usually delete on death but just in case + return + if(dir & NORTH) + icon_state = "[base_icon_state]_n" + else if(dir & SOUTH) + icon_state = "[base_icon_state]_s" + icon_living = icon_state + +/mob/living/basic/heretic_summon/rust_walker/Life(seconds_per_tick = SSMOBS_DT, times_fired) + if(stat == DEAD) + return ..() + var/turf/our_turf = get_turf(src) + if(HAS_TRAIT(our_turf, TRAIT_RUSTY)) + adjustBruteLoss(-3 * seconds_per_tick) + + return ..() + +/// Converts unconverted terrain, sprays pocket sand around +/datum/ai_controller/basic_controller/rust_walker + 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/rust + planning_subtrees = list( + /datum/ai_planning_subtree/use_mob_ability/rust_walker, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Moves a lot if healthy and on rust (to find more tiles to rust) or unhealthy and not on rust (to find healing rust) +/// Still moving in random directions though we're not really seeking it out +/datum/idle_behavior/idle_random_walk/rust + +/datum/idle_behavior/idle_random_walk/rust/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/our_mob = controller.pawn + var/turf/our_turf = get_turf(our_mob) + if (HAS_TRAIT(our_turf, TRAIT_RUSTY)) + walk_chance = (our_mob.health < our_mob.maxHealth) ? 10 : 50 + else + walk_chance = (our_mob.health < our_mob.maxHealth) ? 50 : 10 + return ..() + +/// Use if we're not stood on rust right now +/datum/ai_planning_subtree/use_mob_ability/rust_walker + +/datum/ai_planning_subtree/use_mob_ability/rust_walker/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/turf/our_turf = get_turf(controller.pawn) + if (HAS_TRAIT(our_turf, TRAIT_RUSTY)) + return + return ..() diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm new file mode 100644 index 0000000000000..960f875365bfa --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm @@ -0,0 +1,89 @@ +/mob/living/basic/mining/ice_demon + name = "demonic watcher" + desc = "A creature formed entirely out of ice, bluespace energy emanates from inside of it." + icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + icon_state = "ice_demon" + icon_living = "ice_demon" + icon_gib = "syndicate_gib" + mob_biotypes = MOB_ORGANIC|MOB_BEAST + mouse_opacity = MOUSE_OPACITY_ICON + basic_mob_flags = DEL_ON_DEATH + speed = 2 + maxHealth = 150 + health = 150 + obj_damage = 40 + melee_damage_lower = 15 + melee_damage_upper = 15 + attack_verb_continuous = "slices" + attack_verb_simple = "slice" + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH + move_force = MOVE_FORCE_VERY_STRONG + move_resist = MOVE_FORCE_VERY_STRONG + pull_force = MOVE_FORCE_VERY_STRONG + crusher_loot = /obj/item/crusher_trophy/ice_demon_cube + ai_controller = /datum/ai_controller/basic_controller/ice_demon + death_message = "fades as the energies that tied it to this world dissipate." + death_sound = 'sound/magic/demon_dies.ogg' + +/mob/living/basic/mining/ice_demon/Initialize(mapload) + . = ..() + var/datum/action/cooldown/mob_cooldown/slippery_ice_floors/ice_floor = new(src) + ice_floor.Grant(src) + ai_controller.set_blackboard_key(BB_DEMON_SLIP_ABILITY, ice_floor) + var/datum/action/cooldown/mob_cooldown/ice_demon_teleport/demon_teleport = new(src) + demon_teleport.Grant(src) + ai_controller.set_blackboard_key(BB_DEMON_TELEPORT_ABILITY, demon_teleport) + var/datum/action/cooldown/spell/conjure/create_afterimages/afterimage = new(src) + afterimage.Grant(src) + ai_controller.set_blackboard_key(BB_DEMON_CLONE_ABILITY, afterimage) + AddComponent(\ + /datum/component/ranged_attacks,\ + projectile_type = /obj/projectile/temp/ice_demon,\ + projectile_sound = 'sound/weapons/pierce.ogg',\ + ) + var/static/list/death_loot = list(/obj/item/stack/ore/bluespace_crystal = 3) + AddElement(/datum/element/death_drops, death_loot) + AddElement(/datum/element/simple_flying) + +/mob/living/basic/mining/ice_demon/death(gibbed) + if(prob(5)) + new /obj/item/raw_anomaly_core/bluespace(loc) + return ..() + +/mob/living/basic/mining/demon_afterimage + name = "afterimage demonic watcher" + desc = "Is this some sort of illusion?" + icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + icon_state = "ice_demon" + icon_living = "ice_demon" + icon_gib = "syndicate_gib" + mob_biotypes = MOB_ORGANIC|MOB_BEAST + mouse_opacity = MOUSE_OPACITY_ICON + basic_mob_flags = DEL_ON_DEATH + speed = 5 + maxHealth = 20 + health = 20 + melee_damage_lower = 5 + melee_damage_upper = 5 + attack_verb_continuous = "slices" + attack_verb_simple = "slice" + attack_sound = 'sound/weapons/bladeslice.ogg' + alpha = 80 + ai_controller = /datum/ai_controller/basic_controller/ice_demon/afterimage + ///how long do we exist for + var/existence_period = 15 SECONDS + +/mob/living/basic/mining/demon_afterimage/Initialize(mapload) + . = ..() + AddElement(/datum/element/simple_flying) + AddElement(/datum/element/temporary_atom, life_time = existence_period) + +///afterimage subtypes summoned by the crusher +/mob/living/basic/mining/demon_afterimage/crusher + speed = 2 + health = 60 + maxHealth = 60 + melee_damage_lower = 10 + melee_damage_upper = 10 + existence_period = 7 SECONDS diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm new file mode 100644 index 0000000000000..79c9ee9bd583e --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm @@ -0,0 +1,117 @@ +/obj/projectile/temp/ice_demon + name = "ice blast" + icon_state = "ice_2" + damage = 5 + damage_type = BURN + armor_flag = ENERGY + speed = 1 + pixel_speed_multiplier = 0.25 + temperature = -75 + +/datum/action/cooldown/mob_cooldown/ice_demon_teleport + name = "Bluespace Teleport" + desc = "Teleport towards a destination target!" + button_icon = 'icons/obj/ore.dmi' + button_icon_state = "bluespace_crystal" + cooldown_time = 3 SECONDS + melee_cooldown_time = 0 SECONDS + ///time delay before teleport + var/time_delay = 0.5 SECONDS + +/datum/action/cooldown/mob_cooldown/ice_demon_teleport/Activate(atom/target_atom) + if(isclosedturf(get_turf(target_atom))) + owner.balloon_alert(owner, "blocked!") + return FALSE + animate(owner, transform = matrix().Scale(0.8), time = time_delay, easing = SINE_EASING) + addtimer(CALLBACK(src, PROC_REF(teleport_to_turf), target_atom), time_delay) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/ice_demon_teleport/proc/teleport_to_turf(atom/target) + animate(owner, transform = matrix(), time = 0.5 SECONDS, easing = SINE_EASING) + do_teleport(teleatom = owner, destination = target, channel = TELEPORT_CHANNEL_BLUESPACE, forced = TRUE) + +/datum/action/cooldown/mob_cooldown/slippery_ice_floors + name = "Iced Floors" + desc = "Summon slippery ice floors all around!" + button_icon = 'icons/turf/floors/ice_turf.dmi' + button_icon_state = "ice_turf-6" + cooldown_time = 2 SECONDS + click_to_activate = FALSE + melee_cooldown_time = 0 SECONDS + ///perimeter we will spawn the iced floors on + var/radius = 1 + ///intervals we will spawn the ice floors in + var/spread_duration = 0.2 SECONDS + +/datum/action/cooldown/mob_cooldown/slippery_ice_floors/Activate(atom/target_atom) + for(var/i in 0 to radius) + var/list/list_of_turfs = border_diamond_range_turfs(owner, i) + addtimer(CALLBACK(src, PROC_REF(spawn_icy_floors), list_of_turfs), i * spread_duration) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/slippery_ice_floors/proc/spawn_icy_floors(list/list_of_turfs) + if(!length(list_of_turfs)) + return + for(var/turf/location in list_of_turfs) + if(isnull(location)) + continue + if(isclosedturf(location) || isspaceturf(location)) + continue + new /obj/effect/temp_visual/slippery_ice(location) + +/obj/effect/temp_visual/slippery_ice + name = "slippery acid" + icon = 'icons/turf/floors/ice_turf.dmi' + icon_state = "ice_turf-6" + layer = BELOW_MOB_LAYER + plane = GAME_PLANE + anchored = TRUE + duration = 3 SECONDS + alpha = 100 + /// how long does it take for the effect to phase in + var/phase_in_period = 2 SECONDS + +/obj/effect/temp_visual/slippery_ice/Initialize(mapload) + . = ..() + animate(src, alpha = 160, time = phase_in_period) + animate(alpha = 0, time = duration - phase_in_period) /// slowly fade out of existence + addtimer(CALLBACK(src, PROC_REF(add_slippery_component), phase_in_period)) //only become slippery after we phased in + +/obj/effect/temp_visual/slippery_ice/proc/add_slippery_component() + AddComponent(/datum/component/slippery, 2 SECONDS) + +/datum/action/cooldown/spell/conjure/create_afterimages + name = "Create After Images" + button_icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + button_icon_state = "ice_demon" + spell_requirements = NONE + cooldown_time = 1 MINUTES + summon_type = list(/mob/living/basic/mining/demon_afterimage) + summon_radius = 1 + summon_amount = 2 + ///max number of after images + var/max_afterimages = 2 + ///How many clones do we have summoned + var/number_of_afterimages = 0 + +/datum/action/cooldown/spell/conjure/create_afterimages/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(number_of_afterimages >= max_afterimages) + return FALSE + return TRUE + +/datum/action/cooldown/spell/conjure/create_afterimages/post_summon(atom/summoned_object, atom/cast_on) + var/mob/living/basic/created_copy = summoned_object + created_copy.AddComponent(/datum/component/joint_damage, overlord_mob = owner) + RegisterSignals(created_copy, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(delete_copy)) + number_of_afterimages++ + +/datum/action/cooldown/spell/conjure/create_afterimages/proc/delete_copy(mob/source) + SIGNAL_HANDLER + + UnregisterSignal(source, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH)) + number_of_afterimages-- diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm new file mode 100644 index 0000000000000..28ddd38324ac6 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm @@ -0,0 +1,117 @@ +/datum/ai_controller/basic_controller/ice_demon + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_LIST_SCARY_ITEMS = list( + /obj/item/weldingtool, + /obj/item/flashlight/flare, + ), + BB_MINIMUM_DISTANCE_RANGE = 3, + ) + + 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/flee_target/ice_demon, + /datum/ai_planning_subtree/ranged_skirmish/ice_demon, + /datum/ai_planning_subtree/maintain_distance/cover_minimum_distance/ice_demon, + /datum/ai_planning_subtree/teleport_away_from_target, + /datum/ai_planning_subtree/find_and_hunt_target/teleport_destination, + /datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages, + ) + + +/datum/ai_planning_subtree/maintain_distance/cover_minimum_distance/ice_demon + maximum_distance = 7 + +/datum/ai_planning_subtree/teleport_away_from_target + ability_key = BB_DEMON_TELEPORT_ABILITY + +/datum/ai_planning_subtree/find_and_hunt_target/teleport_destination + target_key = BB_TELEPORT_DESTINATION + hunting_behavior = /datum/ai_behavior/hunt_target/use_ability_on_target/demon_teleport + finding_behavior = /datum/ai_behavior/find_valid_teleport_location + hunt_targets = list(/turf/open) + hunt_range = 3 + finish_planning = FALSE + +/datum/ai_planning_subtree/find_and_hunt_target/teleport_destination/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + return + if(controller.blackboard_key_exists(BB_ESCAPE_DESTINATION)) + controller.clear_blackboard_key(BB_TELEPORT_DESTINATION) + return + var/datum/action/cooldown/ability = controller.blackboard[BB_DEMON_TELEPORT_ABILITY] + if(!ability?.IsAvailable()) + return + return ..() + +/datum/ai_behavior/find_valid_teleport_location + +/datum/ai_behavior/find_valid_teleport_location/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range) + . = ..() + var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + var/list/possible_turfs = list() + + if(QDELETED(target)) + finish_action(controller, FALSE) + return + + for(var/turf/open/potential_turf in oview(hunt_range, target)) //we check for turfs around the target + if(potential_turf.is_blocked_turf()) + continue + if(!can_see(target, potential_turf, hunt_range)) + continue + possible_turfs += potential_turf + + if(!length(possible_turfs)) + finish_action(controller, FALSE) + return + + controller.set_blackboard_key(hunting_target_key, pick(possible_turfs)) + finish_action(controller, TRUE) + +/datum/ai_behavior/hunt_target/use_ability_on_target/demon_teleport + hunt_cooldown = 2 SECONDS + ability_key = BB_DEMON_TELEPORT_ABILITY + behavior_flags = NONE + +/datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages + ability_key = BB_DEMON_CLONE_ABILITY + +/datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(living_pawn.health / living_pawn.maxHealth > 0.5) //only use this ability when under half health + return + return ..() + +/datum/ai_planning_subtree/flee_target/ice_demon + +/datum/ai_planning_subtree/flee_target/ice_demon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if(QDELETED(target)) + return + if(!iscarbon(target)) + return + var/mob/living/carbon/human_target = target + + for(var/obj/held_item in human_target.held_items) + if(!is_type_in_list(held_item, controller.blackboard[BB_LIST_SCARY_ITEMS])) + continue + if(!held_item.light_on) + continue + var/datum/action/cooldown/slip_ability = controller.blackboard[BB_DEMON_SLIP_ABILITY] + if(slip_ability?.IsAvailable()) + controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_DEMON_SLIP_ABILITY) + return ..() + +/datum/ai_planning_subtree/ranged_skirmish/ice_demon + min_range = 0 + +/datum/ai_controller/basic_controller/ice_demon/afterimage + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/flee_target/ice_demon, //even the afterimages are afraid of flames! + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + 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 index c88178135dc54..8964ebf837ec1 100644 --- 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 @@ -1,7 +1,6 @@ /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, ) @@ -37,7 +36,7 @@ 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]) + if(controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) return var/datum/action/cooldown/slip_acid = controller.blackboard[BB_ARACHNID_SLIP] diff --git a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm b/code/modules/mob/living/basic/jungle/venus_human_trap.dm similarity index 62% rename from code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm rename to code/modules/mob/living/basic/jungle/venus_human_trap.dm index c489d6c888d4a..a997a8d266b1a 100644 --- a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm +++ b/code/modules/mob/living/basic/jungle/venus_human_trap.dm @@ -120,13 +120,14 @@ * The result of a kudzu flower bud, these enemies use vines to drag prey close to them for attack. * * A carnivorious plant which uses vines to catch and ensnare prey. Spawns from kudzu flower buds. - * Each one has a maximum of four vines, which can be attached to a variety of things. Carbons are stunned when a vine is attached to them, and movable entities are pulled closer over time. + * Each one can attach up to two temporary vines to objects or mobs and drag them around with it. * Attempting to attach a vine to something with a vine already attached to it will pull all movable targets closer on command. * Once the prey is in melee range, melee attacks from the venus human trap heals itself for 10% of its max health, assuming the target is alive. * Akin to certain spiders, venus human traps can also be possessed and controlled by ghosts. * */ -/mob/living/simple_animal/hostile/venus_human_trap + +/mob/living/basic/venus_human_trap name = "venus human trap" desc = "Now you know how the fly feels." icon = 'icons/mob/spacevines.dmi' @@ -135,24 +136,21 @@ mob_biotypes = MOB_ORGANIC | MOB_PLANT layer = SPACEVINE_MOB_LAYER plane = GAME_PLANE_UPPER_FOV_HIDDEN - health = 50 - maxHealth = 50 - ranged = TRUE - harm_intent_damage = 5 + health = 100 + maxHealth = 100 obj_damage = 60 - melee_damage_lower = 20 + melee_damage_lower = 10 melee_damage_upper = 20 - minbodytemp = 100 + minimum_survivable_temperature = 100 combat_mode = TRUE - ranged_cooldown_time = 4 SECONDS - del_on_death = TRUE + basic_mob_flags = DEL_ON_DEATH death_message = "collapses into bits of plant matter." attacked_sound = 'sound/creatures/venus_trap_hurt.ogg' death_sound = 'sound/creatures/venus_trap_death.ogg' attack_sound = 'sound/creatures/venus_trap_hit.ogg' unsuitable_heat_damage = 5 // heat damage is different from cold damage since coldmos is significantly more common than plasmafires unsuitable_cold_damage = 2 // they now do take cold damage, but this should be sufficiently small that it does not cause major issues - atmos_requirements = 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) + 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) unsuitable_atmos_damage = 0 /// copied over from the code from eyeballs (the mob) to make it easier for venus human traps to see in kudzu that doesn't have the transparency mutation sight = SEE_SELF|SEE_MOBS|SEE_OBJS|SEE_TURFS @@ -163,74 +161,82 @@ faction = list(FACTION_HOSTILE,FACTION_VINES,FACTION_PLANTS) initial_language_holder = /datum/language_holder/venus unique_name = TRUE - /// A list of all the plant's vines - var/list/vines = list() - /// The maximum amount of vines a plant can have at one time - var/max_vines = 4 - /// How far away a plant can attach a vine to something - var/vine_grab_distance = 5 - /// Whether or not this plant is ghost possessable - var/playable_plant = TRUE + speed = 1.2 + melee_attack_cooldown = 1.2 SECONDS + ai_controller = /datum/ai_controller/basic_controller/human_trap + ///how much damage we take out of weeds + var/no_weed_damage = 20 + ///how much do we heal in weeds + var/weed_heal = 10 + ///if the balloon alert was shown atleast once, reset after healing in weeds + var/alert_shown = FALSE -/mob/living/simple_animal/hostile/venus_human_trap/Life(seconds_per_tick = SSMOBS_DT, times_fired) +/mob/living/basic/venus_human_trap/Initialize(mapload) . = ..() - pull_vines() + AddElement(/datum/element/lifesteal, 5) + var/datum/action/cooldown/vine_tangle/tangle = new(src) + tangle.Grant(src) + ai_controller.set_blackboard_key(BB_TARGETTED_ACTION, tangle) -/mob/living/simple_animal/hostile/venus_human_trap/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - . = ..() - pixel_x = base_pixel_x + (dir & (NORTH|WEST) ? 2 : -2) +/mob/living/basic/venus_human_trap/RangedAttack(atom/victim) + if(!combat_mode) + return + var/datum/action/cooldown/mob_cooldown/tangle_ability = ai_controller.blackboard[BB_TARGETTED_ACTION] + if(!istype(tangle_ability)) + return + tangle_ability.Trigger(target = victim) -/mob/living/simple_animal/hostile/venus_human_trap/AttackingTarget() +/mob/living/basic/venus_human_trap/Life(seconds_per_tick = SSMOBS_DT, times_fired) . = ..() - if(isliving(target)) - var/mob/living/L = target - if(L.stat != DEAD) - adjustHealth(-maxHealth * 0.1) - -/mob/living/simple_animal/hostile/venus_human_trap/OpenFire(atom/the_target) - for(var/datum/beam/B in vines) - if(B.target == the_target) - pull_vines() - ranged_cooldown = world.time + (ranged_cooldown_time * 0.5) - return - if(get_dist(src,the_target) > vine_grab_distance || vines.len >= max_vines) + if(!.) + return FALSE + + var/vines_in_range = locate(/obj/structure/spacevine) in range(2, src) + if(!vines_in_range && !alert_shown) + alert_shown = TRUE + balloon_alert(src, "do not leave vines!") + else if(vines_in_range) + alert_shown = FALSE + + apply_damage(vines_in_range ? weed_heal : no_weed_damage, BRUTE) //every life tick take 20 brute if not near vines or heal 10 if near vines, 5 times out of weeds = u ded + +/datum/action/cooldown/vine_tangle + name = "Tangle" + button_icon = 'icons/mob/spacevines.dmi' + button_icon_state = "Light1" + desc = "Grabs a target with a sticky vine, allowing you to pull it alongside you." + cooldown_time = 8 SECONDS + ///how many vines can we handle + var/max_vines = 2 + /// An assoc list of all the plant's vines (beam = leash) + var/list/datum/beam/vines = list() + /// How far away a plant can attach a vine to something + var/vine_grab_distance = 4 + /// how long does a vine attached to something last (and its leash) (lasts twice as long on nonliving things) + var/vine_duration = 2 SECONDS + +/datum/action/cooldown/vine_tangle/Remove(mob/remove_from) + QDEL_LIST(vines) + return ..() + +/datum/action/cooldown/vine_tangle/Activate(atom/target_atom) + if(isturf(target_atom) || istype(target_atom, /obj/structure/spacevine)) + return + if(length(vines) >= max_vines || get_dist(owner, target_atom) > vine_grab_distance) return - for(var/turf/T in get_line(src,target)) - if (T.density) + for(var/turf/blockage in get_line(owner, target_atom)) + if(blockage.is_blocked_turf(exclude_mobs = TRUE)) return - for(var/obj/O in T) - if(O.density) - return - - var/datum/beam/newVine = Beam(the_target, icon_state = "vine", maxdistance = vine_grab_distance, beam_type=/obj/effect/ebeam/vine, emissive = FALSE) - RegisterSignal(newVine, COMSIG_QDELETING, PROC_REF(remove_vine), newVine) - vines += newVine - if(isliving(the_target)) - var/mob/living/L = the_target - L.apply_damage(85, STAMINA, BODY_ZONE_CHEST) - L.Knockdown(1 SECONDS) - ranged_cooldown = world.time + ranged_cooldown_time - -/mob/living/simple_animal/hostile/venus_human_trap/Destroy() - for(var/datum/beam/vine as anything in vines) - qdel(vine) // reference is automatically deleted by remove_vine - return ..() -/** - * Manages how the vines should affect the things they're attached to. - * - * Pulls all movable targets of the vines closer to the plant - * If the target is on the same tile as the plant, destroy the vine - * Removes any QDELETED vines from the vines list. - */ -/mob/living/simple_animal/hostile/venus_human_trap/proc/pull_vines() - for(var/datum/beam/B in vines) - if(istype(B.target, /atom/movable)) - var/atom/movable/AM = B.target - if(!AM.anchored) - step(AM, get_dir(AM, src)) - if(get_dist(src, B.target) == 0) - qdel(B) + var/datum/beam/new_vine = owner.Beam(target_atom, icon_state = "vine", time = vine_duration * (ismob(target_atom) ? 1 : 2), beam_type = /obj/effect/ebeam/vine, emissive = FALSE) + var/component = target_atom.AddComponent(/datum/component/leash, owner, vine_grab_distance) + RegisterSignal(new_vine, COMSIG_QDELETING, PROC_REF(remove_vine), new_vine) + vines[new_vine] = component + if(isliving(target_atom)) + var/mob/living/victim = target_atom + victim.Knockdown(2 SECONDS) + StartCooldown() + return TRUE /** * Removes a vine from the list. @@ -240,9 +246,24 @@ * Arguments: * * datum/beam/vine - The vine to be removed from the list. */ -/mob/living/simple_animal/hostile/venus_human_trap/proc/remove_vine(datum/beam/vine) +/datum/action/cooldown/vine_tangle/proc/remove_vine(datum/beam/vine) SIGNAL_HANDLER + qdel(vines[vine]) vines -= vine +/datum/ai_controller/basic_controller/human_trap + 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/targeted_mob_ability/continue_planning, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + #undef FINAL_BUD_GROWTH_ICON diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm index 58efaf1f81b46..fe1c4150315b0 100644 --- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm @@ -3,7 +3,6 @@ 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, ) @@ -14,9 +13,9 @@ /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/hunt_ores, /datum/ai_planning_subtree/find_and_hunt_target/baby_egg, - /datum/ai_planning_subtree/grub_mine, + /datum/ai_planning_subtree/mine_walls, ) /datum/ai_controller/basic_controller/babygrub @@ -25,7 +24,6 @@ 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, ) @@ -34,29 +32,29 @@ 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/find_and_hunt_target/hunt_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 +/datum/ai_planning_subtree/find_and_hunt_target/hunt_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 + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/hunt_ores + finding_behavior = /datum/ai_behavior/find_hunt_target/hunt_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/hunt_ores -/datum/ai_behavior/find_hunt_target/consume_ores/valid_dinner(mob/living/basic/source, obj/item/stack/ore/target, radius) +/datum/ai_behavior/find_hunt_target/hunt_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) + if(!isturf(target.loc)) return FALSE var/obj/item/pet_target = source.ai_controller.blackboard[BB_CURRENT_PET_TARGET] @@ -65,7 +63,7 @@ return can_see(source, target, radius) -/datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores +/datum/ai_behavior/hunt_target/unarmed_attack_target/hunt_ores always_reset_target = TRUE ///find our child's egg and pull it! @@ -123,45 +121,6 @@ /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." diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm b/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm index 6b3525cb32ab6..310bbeb708ebb 100644 --- a/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm +++ b/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm @@ -2,7 +2,6 @@ /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, ) @@ -32,7 +31,7 @@ /// 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) +/datum/targetting_datum/basic/attack_until_dead/legion/faction_check(datum/ai_controller/controller, 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)) diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm index d47ae15b9759c..a048fe77ab146 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm @@ -39,7 +39,7 @@ AddElement(/datum/element/basic_eating, food_types = target_foods) AddElement(\ /datum/element/amputating_limbs,\ - surgery_verb = "snipping",\ + surgery_verb = "begins snipping",\ target_zones = GLOB.arm_zones,\ ) charge = new(src) @@ -74,7 +74,7 @@ var/mob/living/basic/basic_source = source var/mob/living/living_target = target basic_source.melee_attack(living_target, ignore_cooldown = TRUE) - basic_source.ai_controller?.set_blackboard_key(BB_BASIC_MOB_FLEEING, FALSE) + basic_source.ai_controller?.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) basic_source.start_pulling(living_target) /datum/action/cooldown/mob_cooldown/charge/basic_charge/lobster/do_charge(atom/movable/charger, atom/target_atom, delay, past) diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm index 8e4dfe9e29463..7b6926fca04e7 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm @@ -2,7 +2,6 @@ blackboard = list( BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/lobster, BB_LOBSTROSITY_EXPLOIT_TRAITS = list(TRAIT_INCAPACITATED, TRAIT_FLOORED, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT), - BB_BASIC_MOB_FLEEING = TRUE, BB_LOBSTROSITY_FINGER_LUST = 0 ) @@ -26,7 +25,7 @@ melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/lobster /datum/ai_planning_subtree/basic_melee_attack_subtree/lobster/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (controller.blackboard[BB_BASIC_MOB_FLEEING]) + if (!controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) return if (!isnull(controller.blackboard[BB_LOBSTROSITY_TARGET_LIMB])) return @@ -48,8 +47,8 @@ is_vulnerable = TRUE break if (!is_vulnerable) - controller.set_blackboard_key(BB_BASIC_MOB_FLEEING, TRUE) - if (controller.blackboard[BB_BASIC_MOB_FLEEING]) + controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) + if (!controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) finish_action(controller = controller, succeeded = TRUE, target_key = target_key) // We don't want to clear our target return return ..() @@ -57,6 +56,12 @@ /datum/ai_planning_subtree/flee_target/lobster flee_behaviour = /datum/ai_behavior/run_away_from_target/lobster +/datum/ai_planning_subtree/flee_target/lobster/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/datum/action/cooldown/using_action = controller.blackboard[BB_TARGETTED_ACTION] + if (using_action?.IsAvailable()) + return + return ..() + /datum/ai_behavior/run_away_from_target/lobster clear_failed_targets = FALSE @@ -64,10 +69,11 @@ var/atom/target = controller.blackboard[target_key] if(isnull(target)) return ..() + for (var/trait in controller.blackboard[BB_LOBSTROSITY_EXPLOIT_TRAITS]) if (!HAS_TRAIT(target, trait)) continue - controller.set_blackboard_key(BB_BASIC_MOB_FLEEING, FALSE) + controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) finish_action(controller, succeeded = FALSE) return diff --git a/code/modules/mob/living/basic/lavaland/mook/mook.dm b/code/modules/mob/living/basic/lavaland/mook/mook.dm new file mode 100644 index 0000000000000..da833437715c6 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook.dm @@ -0,0 +1,273 @@ +//Fragile but highly aggressive wanderers that pose a large threat in numbers. +//They'll attempt to leap at their target from afar using their hatchets. +/mob/living/basic/mining/mook + name = "wanderer" + desc = "This unhealthy looking primitive seems to be talented at administiring health care." + icon = 'icons/mob/simple/jungle/mook.dmi' + icon_state = "mook" + icon_living = "mook" + icon_dead = "mook_dead" + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + gender = FEMALE + maxHealth = 150 + faction = list(FACTION_MINING, FACTION_NEUTRAL) + health = 150 + move_resist = MOVE_FORCE_OVERPOWERING + melee_damage_lower = 8 + melee_damage_upper = 8 + pass_flags_self = LETPASSTHROW + attack_sound = 'sound/weapons/rapierhit.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH + death_sound = 'sound/voice/mook_death.ogg' + ai_controller = /datum/ai_controller/basic_controller/mook/support + speed = 5 + + pixel_x = -16 + base_pixel_x = -16 + pixel_y = -16 + base_pixel_y = -16 + + ///the state of combat we are in + var/attack_state = MOOK_ATTACK_NEUTRAL + ///are we a healer? + var/is_healer = TRUE + ///the ore we are holding if any + var/obj/held_ore + ///overlay for neutral stance + var/mutable_appearance/neutral_stance + ///overlay for attacking stance + var/mutable_appearance/attack_stance + ///overlay when we hold an ore + var/mutable_appearance/ore_overlay + ///commands we obey + var/list/pet_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/point_targetting/attack, + /datum/pet_command/point_targetting/fetch, + ) + +/mob/living/basic/mining/mook/Initialize(mapload) + . = ..() + AddComponent(/datum/component/ai_retaliate_advanced, CALLBACK(src, PROC_REF(attack_intruder))) + var/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump/jump = new(src) + jump.Grant(src) + ai_controller.set_blackboard_key(BB_MOOK_JUMP_ABILITY, jump) + + ore_overlay = mutable_appearance(icon, "mook_ore_overlay") + + AddComponent(/datum/component/ai_listen_to_weather) + AddElement(/datum/element/wall_smasher) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + RegisterSignal(src, COMSIG_KB_MOB_DROPITEM_DOWN, PROC_REF(drop_ore)) + + if(is_healer) + grant_healer_abilities() + + AddComponent(/datum/component/obeys_commands, pet_commands) + +/mob/living/basic/mining/mook/proc/grant_healer_abilities() + AddComponent(\ + /datum/component/healing_touch,\ + heal_brute = melee_damage_upper,\ + heal_burn = melee_damage_upper,\ + heal_time = 0,\ + valid_targets_typecache = typecacheof(list(/mob/living/basic/mining/mook)),\ + ) + +/mob/living/basic/mining/mook/Entered(atom/movable/mover) + if(istype(mover, /obj/item/stack/ore)) + held_ore = mover + update_appearance(UPDATE_OVERLAYS) + + return ..() + +/mob/living/basic/mining/mook/Exited(atom/movable/mover) + . = ..() + if(held_ore != mover) + return + held_ore = null + update_appearance(UPDATE_OVERLAYS) + +/mob/living/basic/mining/mook/proc/pre_attack(mob/living/attacker, atom/target) + SIGNAL_HANDLER + + return attack_sequence(target) + +/mob/living/basic/mining/mook/proc/attack_sequence(atom/target) + if(istype(target, /obj/item/stack/ore) && isnull(held_ore)) + var/obj/item/ore_target = target + ore_target.forceMove(src) + return COMPONENT_HOSTILE_NO_ATTACK + + if(istype(target, /obj/structure/material_stand)) + if(held_ore) + held_ore.forceMove(target) + return COMPONENT_HOSTILE_NO_ATTACK + + if(istype(target, /obj/structure/bonfire)) + var/obj/structure/bonfire/fire_target = target + if(!fire_target.burning) + fire_target.start_burning() + return COMPONENT_HOSTILE_NO_ATTACK + +/mob/living/basic/mining/mook/proc/change_combatant_state(state) + attack_state = state + update_appearance() + +/mob/living/basic/mining/mook/Destroy() + QDEL_NULL(held_ore) + return ..() + +/mob/living/basic/mining/mook/update_icon_state() + . = ..() + if(stat == DEAD) + return + switch(attack_state) + if(MOOK_ATTACK_NEUTRAL) + icon_state = "mook" + if(MOOK_ATTACK_WARMUP) + icon_state = "mook_warmup" + if(MOOK_ATTACK_ACTIVE) + icon_state = "mook_leap" + if(MOOK_ATTACK_STRIKE) + icon_state = "mook_strike" + +/mob/living/basic/mining/mook/update_overlays() + . = ..() + if(stat == DEAD) + return + + if(attack_state != MOOK_ATTACK_NEUTRAL || isnull(held_ore)) + return + + . += ore_overlay + +/mob/living/basic/mining/mook/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) + change_combatant_state(state = MOOK_ATTACK_ACTIVE) + return ..() + +/mob/living/basic/mining/mook/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + change_combatant_state(state = MOOK_ATTACK_NEUTRAL) + +/mob/living/basic/mining/mook/CanAllowThrough(atom/movable/mover, border_dir) + . = ..() + + if(!istype(mover, /mob/living/basic/mining/mook)) + return FALSE + + var/mob/living/basic/mining/mook/mook_moover = mover + if(mook_moover.attack_state == MOOK_ATTACK_ACTIVE) + return TRUE + +/mob/living/basic/mining/mook/proc/drop_ore(mob/living/user) + SIGNAL_HANDLER + + if(isnull(held_ore)) + return + dropItemToGround(held_ore) + return COMSIG_KB_ACTIVATED + +/mob/living/basic/mining/mook/death() + desc = "A deceased primitive. Upon closer inspection, it was suffering from severe cellular degeneration and its garments are machine made..." //Can you guess the twist + return ..() + +/mob/living/basic/mining/mook/proc/attack_intruder(mob/living/intruder) + if(istype(intruder, /mob/living/basic/mining/mook)) + return + for(var/mob/living/basic/mining/mook/villager in oview(src, 9)) + villager.ai_controller?.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, intruder) + + +/mob/living/basic/mining/mook/worker + desc = "This unhealthy looking primitive is wielding a rudimentary hatchet, swinging it with wild abandon. One isn't much of a threat, but in numbers they can quickly overwhelm a superior opponent." + gender = MALE + melee_damage_lower = 15 + melee_damage_upper = 15 + ai_controller = /datum/ai_controller/basic_controller/mook + is_healer = FALSE + +/mob/living/basic/mining/mook/worker/Initialize(mapload) + . = ..() + neutral_stance = mutable_appearance(icon, "mook_axe_overlay") + attack_stance = mutable_appearance(icon, "axe_strike_overlay") + update_appearance() + var/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/leap = new(src) + leap.Grant(src) + ai_controller.set_blackboard_key(BB_MOOK_LEAP_ABILITY, leap) + +/mob/living/basic/mining/mook/worker/attack_sequence(atom/target) + . = ..() + if(. & COMPONENT_HOSTILE_NO_ATTACK) + return + + if(attack_state == MOOK_ATTACK_STRIKE) + return COMPONENT_HOSTILE_NO_ATTACK + + change_combatant_state(state = MOOK_ATTACK_STRIKE) + addtimer(CALLBACK(src, PROC_REF(change_combatant_state), MOOK_ATTACK_NEUTRAL), 0.3 SECONDS) + +/mob/living/basic/mining/mook/worker/update_overlays() + . = ..() + if(stat == DEAD) + return + + switch(attack_state) + if(MOOK_ATTACK_STRIKE) + . += attack_stance + if(MOOK_ATTACK_NEUTRAL) + . += neutral_stance + +/mob/living/basic/mining/mook/worker/bard + desc = "It's holding a guitar?" + melee_damage_lower = 10 + melee_damage_upper = 10 + gender = MALE + attack_sound = 'sound/weapons/stringsmash.ogg' + death_sound = 'sound/voice/mook_death.ogg' + ai_controller = /datum/ai_controller/basic_controller/mook/bard + ///our guitar + var/obj/item/instrument/guitar/held_guitar + +/mob/living/basic/mining/mook/worker/bard/Initialize(mapload) + . = ..() + neutral_stance = mutable_appearance(icon, "bard_overlay") + attack_stance = mutable_appearance(icon, "bard_strike") + held_guitar = new(src) + ai_controller.set_blackboard_key(BB_SONG_INSTRUMENT, held_guitar) + update_appearance() + +/mob/living/basic/mining/mook/worker/tribal_chief + name = "tribal chief" + desc = "Acknowledge him!" + gender = MALE + melee_damage_lower = 20 + melee_damage_upper = 20 + ai_controller = /datum/ai_controller/basic_controller/mook/tribal_chief + ///overlay in our neutral state + var/static/mutable_appearance/chief_neutral = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief") + ///overlay in our striking state + var/static/mutable_appearance/chief_strike = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief_strike") + ///overlay in our active state + var/static/mutable_appearance/chief_active = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief_leap") + ///overlay in our warmup state + var/static/mutable_appearance/chief_warmup = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief_warmup") + +/mob/living/basic/mining/mook/worker/tribal_chief/Initialize(mapload) + . = ..() + update_appearance() + +/mob/living/basic/mining/mook/worker/tribal_chief/update_overlays() + . = ..() + if(stat == DEAD) + return + switch(attack_state) + if(MOOK_ATTACK_NEUTRAL) + . += chief_neutral + if(MOOK_ATTACK_WARMUP) + . += chief_warmup + if(MOOK_ATTACK_ACTIVE) + . += chief_active + if(MOOK_ATTACK_STRIKE) + . += chief_strike diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_abilities.dm b/code/modules/mob/living/basic/lavaland/mook/mook_abilities.dm new file mode 100644 index 0000000000000..cfc359bd54fcc --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook_abilities.dm @@ -0,0 +1,140 @@ +/datum/action/cooldown/mob_cooldown/mook_ability + ///are we a mook? + var/is_mook = FALSE + +/datum/action/cooldown/mob_cooldown/mook_ability/Grant(mob/grant_to) + . = ..() + if(isnull(owner)) + return + is_mook = istype(owner, /mob/living/basic/mining/mook) + +/datum/action/cooldown/mob_cooldown/mook_ability/IsAvailable(feedback) + . = ..() + + if(!.) + return FALSE + + if(!is_mook) + return TRUE + + var/mob/living/basic/mining/mook/mook_owner = owner + if(mook_owner.attack_state != MOOK_ATTACK_NEUTRAL) + if(feedback) + mook_owner.balloon_alert(mook_owner, "still recovering!") + return FALSE + return TRUE + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap + name = "Mook leap" + desc = "Leap towards the enemy!" + cooldown_time = 7 SECONDS + shared_cooldown = NONE + melee_cooldown_time = 0 SECONDS + ///telegraph time before jumping + var/wind_up_time = 2 SECONDS + ///intervals between each of our attacks + var/attack_interval = 0.4 SECONDS + ///how many times do we attack if we reach the target? + var/times_to_attack = 4 + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/Activate(atom/target) + if(owner.CanReach(target)) + attack_combo(target) + StartCooldown() + return TRUE + + if(is_mook) + var/mob/living/basic/mining/mook/mook_owner = owner + mook_owner.change_combatant_state(state = MOOK_ATTACK_WARMUP) + + addtimer(CALLBACK(src, PROC_REF(launch_towards_target), target), wind_up_time) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/proc/launch_towards_target(atom/target) + new /obj/effect/temp_visual/mook_dust(get_turf(owner)) + playsound(get_turf(owner), 'sound/weapons/thudswoosh.ogg', 25, TRUE) + playsound(owner, 'sound/voice/mook_leap_yell.ogg', 100, TRUE) + var/turf/target_turf = get_turf(target) + + if(!target_turf.is_blocked_turf()) + owner.throw_at(target = target_turf, range = 7, speed = 1, spin = FALSE, callback = CALLBACK(src, PROC_REF(attack_combo), target)) + return + + var/list/open_turfs = list() + + for(var/turf/possible_turf in get_adjacent_open_turfs(target)) + if(possible_turf.is_blocked_turf()) + continue + open_turfs += possible_turf + + if(!length(open_turfs)) + return + + var/turf/final_turf = get_closest_atom(/turf, open_turfs, owner) + owner.throw_at(target = final_turf, range = 7, speed = 1, spin = FALSE, callback = CALLBACK(src, PROC_REF(attack_combo), target)) + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/proc/attack_combo(atom/target) + if(!owner.CanReach(target)) + return FALSE + + for(var/i in 0 to (times_to_attack - 1)) + addtimer(CALLBACK(src, PROC_REF(attack_target), target), i * attack_interval) + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/proc/attack_target(atom/target) + if(!owner.CanReach(target) || owner.stat == DEAD) + return + var/mob/living/basic/basic_owner = owner + basic_owner.melee_attack(target, ignore_cooldown = TRUE) + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump + name = "Mook Jump" + desc = "Soar high in the air!" + cooldown_time = 14 SECONDS + shared_cooldown = NONE + melee_cooldown_time = 0 SECONDS + click_to_activate = FALSE + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump/Activate(atom/target) + var/obj/effect/landmark/drop_zone = locate(/obj/effect/landmark/mook_village) in GLOB.landmarks_list + if(drop_zone?.z == owner.z) + var/turf/jump_destination = get_turf(drop_zone) + jump_to_turf(jump_destination) + StartCooldown() + return TRUE + var/list/potential_turfs = list() + for(var/turf/open_turf in oview(9, owner)) + if(!open_turf.is_blocked_turf()) + potential_turfs += open_turf + if(!length(potential_turfs)) + return FALSE + jump_to_turf(pick(potential_turfs)) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump/proc/jump_to_turf(turf/target) + if(is_mook) + var/mob/living/basic/mining/mook/mook_owner = owner + mook_owner.change_combatant_state(state = MOOK_ATTACK_ACTIVE) + new /obj/effect/temp_visual/mook_dust(get_turf(owner)) + playsound(get_turf(owner), 'sound/weapons/thudswoosh.ogg', 50, TRUE) + animate(owner, pixel_y = owner.base_pixel_y + 146, time = 0.5 SECONDS) + addtimer(CALLBACK(src, PROC_REF(land_on_turf), target), 0.5 SECONDS) + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump/proc/land_on_turf(turf/target) + do_teleport(owner, target, precision = 3, no_effects = TRUE) + animate(owner, pixel_y = owner.base_pixel_y, time = 0.5 SECONDS) + new /obj/effect/temp_visual/mook_dust(get_turf(owner)) + if(is_mook) + addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob/living/basic/mining/mook, change_combatant_state), MOOK_ATTACK_NEUTRAL), 0.5 SECONDS) + +/obj/effect/temp_visual/mook_dust + name = "dust" + desc = "It's just a dust cloud!" + icon = 'icons/mob/simple/jungle/mook.dmi' + icon_state = "mook_leap_cloud" + layer = BELOW_MOB_LAYER + plane = GAME_PLANE + base_pixel_y = -16 + base_pixel_x = -16 + duration = 1 SECONDS diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm new file mode 100644 index 0000000000000..6a04742d47152 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm @@ -0,0 +1,426 @@ +///commands the chief can pick from +GLOBAL_LIST_INIT(mook_commands, list( + new /datum/pet_command/point_targetting/attack, + new /datum/pet_command/point_targetting/fetch, +)) + +/datum/ai_controller/basic_controller/mook + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/mook, + BB_BLACKLIST_MINERAL_TURFS = list(/turf/closed/mineral/gibtonite, /turf/closed/mineral/strong), + BB_MAXIMUM_DISTANCE_TO_VILLAGE = 7, + BB_STORM_APPROACHING = FALSE, + ) + + 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/look_for_village, + /datum/ai_planning_subtree/targeted_mob_ability/leap, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/find_and_hunt_target/material_stand, + /datum/ai_planning_subtree/use_mob_ability/mook_jump, + /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/mook, + /datum/ai_planning_subtree/mine_walls/mook, + /datum/ai_planning_subtree/wander_away_from_village, + ) + +///check for faction if not a ash walker, otherwise just attack +/datum/targetting_datum/basic/mook/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) + if(FACTION_ASHWALKER in living_mob.faction) + return FALSE + + return ..() + +/datum/ai_planning_subtree/targeted_mob_ability/leap + ability_key = BB_MOOK_LEAP_ABILITY + +/datum/ai_planning_subtree/use_mob_ability/mook_jump + ability_key = BB_MOOK_JUMP_ABILITY + +///jump towards the village when we have found ore or there is a storm coming +/datum/ai_planning_subtree/use_mob_ability/mook_jump/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] + var/mob/living/living_pawn = controller.pawn + var/obj/effect/home = controller.blackboard[BB_HOME_VILLAGE] + if(QDELETED(home)) + return + if(get_dist(living_pawn, home) < controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE]) + return + if(home.z != living_pawn.z) + return + if(!storm_approaching && !(locate(/obj/item/stack/ore) in living_pawn)) + return + + controller.clear_blackboard_key(BB_TARGET_MINERAL_WALL) + return ..() + +///hunt ores that we will haul off back to the village +/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/mook + +/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/mook/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(locate(/obj/item/stack/ore) in living_pawn) + return + return ..() + +///deposit ores into the stand! +/datum/ai_planning_subtree/find_and_hunt_target/material_stand + target_key = BB_MATERIAL_STAND_TARGET + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/material_stand + finding_behavior = /datum/ai_behavior/find_hunt_target + hunt_targets = list(/obj/structure/material_stand) + hunt_range = 9 + +/datum/ai_planning_subtree/find_and_hunt_target/material_stand/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(!locate(/obj/item/stack/ore) in living_pawn) + return + return ..() + +/datum/ai_behavior/hunt_target/unarmed_attack_target/material_stand + required_distance = 0 + always_reset_target = TRUE + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT + +///try to face the counter when depositing ores +/datum/ai_behavior/hunt_target/unarmed_attack_target/material_stand/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) + . = ..() + var/atom/hunt_target = controller.blackboard[hunting_target_key] + if (QDELETED(hunt_target)) + return FALSE + var/list/possible_turfs = list() + var/list/directions = list(SOUTH, SOUTHEAST) + + for(var/direction in directions) + var/turf/bottom_turf = get_step(hunt_target, direction) + if(!bottom_turf.is_blocked_turf()) + possible_turfs += bottom_turf + + if(!length(possible_turfs)) + return FALSE + set_movement_target(controller, pick(possible_turfs)) + +///look for our village +/datum/ai_planning_subtree/look_for_village + +/datum/ai_planning_subtree/look_for_village/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_HOME_VILLAGE)) + return + + controller.queue_behavior(/datum/ai_behavior/find_village, BB_HOME_VILLAGE) + +/datum/ai_behavior/find_village + +/datum/ai_behavior/find_village/perform(seconds_per_tick, datum/ai_controller/controller, village_key) + . = ..() + + var/obj/effect/landmark/home_marker = locate(/obj/effect/landmark/mook_village) in GLOB.landmarks_list + if(isnull(home_marker)) + finish_action(controller, FALSE) + return + + controller.set_blackboard_key(village_key, home_marker) + finish_action(controller, TRUE) + +///explore the lands away from the village to look for ore +/datum/ai_planning_subtree/wander_away_from_village + +/datum/ai_planning_subtree/wander_away_from_village/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] + ///if we have ores to deposit or a storm is approaching, dont wander away + if(storm_approaching || (locate(/obj/item/stack/ore) in living_pawn)) + return + + if(controller.blackboard_key_exists(BB_HOME_VILLAGE)) + controller.queue_behavior(/datum/ai_behavior/wander, BB_HOME_VILLAGE) + +/datum/ai_behavior/wander + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + required_distance = 0 + /// distance we will wander away from the village + var/wander_distance = 9 + +/datum/ai_behavior/wander/setup(datum/ai_controller/controller, village_key) + . = ..() + var/mob/living/living_pawn = controller.pawn + var/obj/effect/target = controller.blackboard[village_key] + if(QDELETED(target)) + return FALSE + + if(target.z != living_pawn.z) + return FALSE + + var/list/angle_directions = list() + for(var/direction in GLOB.alldirs) + angle_directions += dir2angle(direction) + + var/angle_to_home = get_angle(living_pawn, target) + angle_directions -= angle_to_home + angle_directions -= (angle_to_home + 45) + angle_directions -= (angle_to_home - 45) + shuffle_inplace(angle_directions) + + var/turf/wander_destination = get_turf(living_pawn) + for(var/angle in angle_directions) + var/turf/test_turf = get_furthest_turf(living_pawn, angle, target) + if(isnull(test_turf)) + continue + var/distance_from_target = get_dist(target, test_turf) + if(distance_from_target <= get_dist(target, wander_destination)) + continue + wander_destination = test_turf + if(distance_from_target == wander_distance) + break + + set_movement_target(controller, wander_destination) + +/datum/ai_behavior/wander/proc/get_furthest_turf(atom/source, angle, atom/target) + var/turf/return_turf + for(var/i in 1 to wander_distance) + var/turf/test_destination = get_ranged_target_turf_direct(source, target, range = i, offset = angle) + if(test_destination.is_blocked_turf(source_atom = source)) + break + return_turf = test_destination + return return_turf + +/datum/ai_behavior/wander/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) + . = ..() + finish_action(controller, TRUE) + +/datum/ai_planning_subtree/mine_walls/mook + find_wall_behavior = /datum/ai_behavior/find_mineral_wall/mook + +/datum/ai_planning_subtree/mine_walls/mook/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] + if(storm_approaching || locate(/obj/item/stack/ore) in living_pawn) + return + return ..() + +/datum/ai_behavior/find_mineral_wall/mook + +/datum/ai_behavior/find_mineral_wall/mook/check_if_mineable(datum/ai_controller/controller, turf/target_wall) + var/list/forbidden_turfs = controller.blackboard[BB_BLACKLIST_MINERAL_TURFS] + if(is_type_in_list(target_wall, forbidden_turfs)) + return FALSE + return ..() + +///bard mook plays nice music for the village +/datum/ai_controller/basic_controller/mook/bard + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/mook, + BB_MAXIMUM_DISTANCE_TO_VILLAGE = 10, + BB_STORM_APPROACHING = FALSE, + BB_SONG_LINES = MOOK_SONG, + ) + idle_behavior = /datum/idle_behavior/walk_near_target/mook_village + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/look_for_village, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/play_music_for_visitor, + /datum/ai_planning_subtree/use_mob_ability/mook_jump, + /datum/ai_planning_subtree/generic_play_instrument, + ) + + +///find an audience to follow and play music for! +/datum/ai_planning_subtree/play_music_for_visitor + +/datum/ai_planning_subtree/play_music_for_visitor/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard_key_exists(BB_MOOK_MUSIC_AUDIENCE)) + controller.queue_behavior(/datum/ai_behavior/find_and_set/music_audience, BB_MOOK_MUSIC_AUDIENCE, /mob/living/carbon/human) + return + var/atom/home = controller.blackboard[BB_HOME_VILLAGE] + if(isnull(home)) + return + + var/atom/human_target = controller.blackboard[BB_MOOK_MUSIC_AUDIENCE] + if(get_dist(human_target, home) > controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE] || controller.blackboard[BB_STORM_APPROACHING]) + controller.clear_blackboard_key(BB_MOOK_MUSIC_AUDIENCE) + return + + controller.queue_behavior(/datum/ai_behavior/travel_towards, BB_MOOK_MUSIC_AUDIENCE) + +/datum/ai_behavior/find_and_set/music_audience + +/datum/ai_behavior/find_and_set/music_audience/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/atom/home = controller.blackboard[BB_HOME_VILLAGE] + for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) + if(target.stat > UNCONSCIOUS || !target.mind) + continue + if(isnull(home) || get_dist(target, home) > controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE]) + continue + return target + +/datum/idle_behavior/walk_near_target/mook_village + target_key = BB_HOME_VILLAGE + +///healer mooks guard the village from intruders and heal the miner mooks when they come home +/datum/ai_controller/basic_controller/mook/support + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/mook, + BB_MAXIMUM_DISTANCE_TO_VILLAGE = 10, + BB_STORM_APPROACHING = FALSE, + BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends, + ) + idle_behavior = /datum/idle_behavior/walk_near_target/mook_village + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/look_for_village, + /datum/ai_planning_subtree/acknowledge_chief, + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/use_mob_ability/mook_jump, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/find_and_hunt_target/injured_mooks, + ) + +///tree to find and register our leader +/datum/ai_planning_subtree/acknowledge_chief/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_MOOK_TRIBAL_CHIEF)) + return + controller.queue_behavior(/datum/ai_behavior/find_and_set/find_chief, BB_MOOK_TRIBAL_CHIEF, /mob/living/basic/mining/mook/worker/tribal_chief) + +/datum/ai_behavior/find_and_set/find_chief/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/mob/living/chief = locate(locate_path) in oview(search_range, controller.pawn) + if(isnull(chief)) + return null + var/mob/living/living_pawn = controller.pawn + living_pawn.befriend(chief) + return chief + +///find injured miner mooks after they come home from a long day of work +/datum/ai_planning_subtree/find_and_hunt_target/injured_mooks + target_key = BB_INJURED_MOOK + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/injured_mooks + finding_behavior = /datum/ai_behavior/find_hunt_target/injured_mooks + hunt_targets = list(/mob/living/basic/mining/mook/worker) + hunt_range = 9 + +///we only heal when the mooks are home during a storm +/datum/ai_planning_subtree/find_and_hunt_target/injured_mooks/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard[BB_STORM_APPROACHING]) + return ..() + + +/datum/ai_behavior/find_hunt_target/injured_mooks + +/datum/ai_behavior/find_hunt_target/injured_mooks/valid_dinner(mob/living/source, mob/living/injured_mook) + return (injured_mook.health < injured_mook.maxHealth) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/injured_mooks + +/datum/ai_behavior/hunt_target/unarmed_attack_target/injured_mooks + always_reset_target = TRUE + hunt_cooldown = 10 SECONDS + + +///the chief would rather command his mooks to attack people than attack them himself +/datum/ai_controller/basic_controller/mook/tribal_chief + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/mook, + BB_STORM_APPROACHING = FALSE, + ) + idle_behavior = /datum/idle_behavior/walk_near_target/mook_village + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/look_for_village, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability/leap, + /datum/ai_planning_subtree/issue_commands, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/find_and_hunt_target/material_stand, + /datum/ai_planning_subtree/use_mob_ability/mook_jump, + /datum/ai_planning_subtree/find_and_hunt_target/bonfire, + /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/tribal_chief, + ) + +/datum/ai_planning_subtree/issue_commands + ///how far we look for a mook to command + var/command_distance = 5 + +/datum/ai_planning_subtree/issue_commands/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!locate(/mob/living/basic/mining/mook) in oview(command_distance, controller.pawn)) + return + if(controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_BASIC_MOB_CURRENT_TARGET, /datum/pet_command/point_targetting/attack) + return + + var/atom/ore_target = controller.blackboard[BB_ORE_TARGET] + var/mob/living/living_pawn = controller.pawn + if(isnull(ore_target)) + return + if(get_dist(ore_target, living_pawn) <= 1) + return + + controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_ORE_TARGET, /datum/pet_command/point_targetting/fetch) + +/datum/ai_behavior/issue_commands + action_cooldown = 5 SECONDS + +/datum/ai_behavior/issue_commands/perform(seconds_per_tick, datum/ai_controller/controller, target_key, command_path) + . = ..() + var/mob/living/basic/living_pawn = controller.pawn + var/atom/target = controller.blackboard[target_key] + + if(isnull(target)) + finish_action(controller, FALSE) + return + + var/datum/pet_command/to_command = locate(command_path) in GLOB.mook_commands + if(isnull(to_command)) + finish_action(controller, FALSE) + return + + var/issue_command = pick(to_command.speech_commands) + living_pawn.say(issue_command, forced = "controller") + living_pawn._pointed(target) + finish_action(controller, TRUE) + + +///find an ore, only pick it up when a mook brings it close to us +/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/tribal_chief + +/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/tribal_chief/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(locate(/obj/item/stack/ore) in living_pawn) + return + + var/atom/target_ore = controller.blackboard[BB_ORE_TARGET] + + if(isnull(target_ore)) + return ..() + + if(!isturf(target_ore.loc)) //picked up by someone else + controller.clear_blackboard_key(BB_ORE_TARGET) + return + + if(get_dist(target_ore, living_pawn) > 1) + return + + return ..() + +/datum/ai_planning_subtree/find_and_hunt_target/bonfire + target_key = BB_MOOK_BONFIRE_TARGET + finding_behavior = /datum/ai_behavior/find_hunt_target/bonfire + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/bonfire + hunt_targets = list(/obj/structure/bonfire) + hunt_range = 9 + + +/datum/ai_behavior/find_hunt_target/bonfire + +/datum/ai_behavior/find_hunt_target/bonfire/valid_dinner(mob/living/source, obj/structure/bonfire/fire, radius) + if(fire.burning) + return FALSE + + return can_see(source, fire, radius) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/bonfire + always_reset_target = TRUE diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_village.dm b/code/modules/mob/living/basic/lavaland/mook/mook_village.dm new file mode 100644 index 0000000000000..e3a091f6f0e49 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook_village.dm @@ -0,0 +1,85 @@ +///unique items that spawn at the mook village +/obj/structure/material_stand + name = "material stand" + desc = "Is everyone free to use this thing?" + icon = 'icons/mob/simple/jungle/mook.dmi' + icon_state = "material_stand" + density = TRUE + anchored = TRUE + resistance_flags = INDESTRUCTIBLE + bound_width = 64 + bound_height = 64 + +/obj/structure/material_stand/attackby(obj/item/ore, mob/living/carbon/human/user, list/modifiers) + if(istype(ore, /obj/item/stack/ore)) + ore.forceMove(src) + return + return ..() + +/obj/structure/material_stand/Entered(atom/movable/mover) + . = ..() + update_appearance(UPDATE_OVERLAYS) + +/obj/structure/material_stand/Exited(atom/movable/mover) + . = ..() + update_appearance(UPDATE_OVERLAYS) + +///put ore icons on the counter! +/obj/structure/material_stand/update_overlays() + . = ..() + for(var/obj/item/stack/ore/ore_item in contents) + var/image/ore_icon = image(icon = initial(ore_item.icon), icon_state = initial(ore_item.icon_state), layer = LOW_ITEM_LAYER) + ore_icon.transform = ore_icon.transform.Scale(0.6, 0.6) + ore_icon.pixel_x = rand(9, 17) + ore_icon.pixel_y = rand(2, 4) + . += ore_icon + +/obj/structure/material_stand/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "MaterialStand") + ui.open() + +/obj/structure/material_stand/ui_data(mob/user) + var/list/data = list() + data["ores"] = list() + for(var/obj/item/stack/ore/ore_item in contents) + data["ores"] += list(list( + "id" = REF(ore_item), + "name" = ore_item.name, + "amount" = ore_item.amount, + )) + return data + +/obj/structure/material_stand/ui_static_data(mob/user) + var/list/data = list() + data["ore_images"] = list() + for(var/obj/item/stack/ore_item as anything in subtypesof(/obj/item/stack/ore)) + data["ore_images"] += list(list( + "name" = initial(ore_item.name), + "icon" = icon2base64(getFlatIcon(image(icon = initial(ore_item.icon), icon_state = initial(ore_item.icon_state)), no_anim=TRUE)) + )) + return data + +/obj/structure/material_stand/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + + if(. || !isliving(usr)) + return TRUE + + var/mob/living/customer = usr + var/obj/item/stack_to_move + switch(action) + if("withdraw") + if(isnull(params["reference"])) + return TRUE + stack_to_move = locate(params["reference"]) in contents + if(isnull(stack_to_move)) + return TRUE + stack_to_move.forceMove(get_turf(customer)) + return TRUE + +/obj/effect/landmark/mook_village + name = "mook village landmark" + icon_state = "x" diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm index a25234817f31e..348bbcfcaa7d0 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm @@ -27,13 +27,10 @@ return ..() /datum/ai_planning_subtree/ranged_skirmish/watcher - attack_behavior = /datum/ai_behavior/ranged_skirmish/watcher + min_range = 0 /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/minebots/minebot_ai.dm b/code/modules/mob/living/basic/minebots/minebot_ai.dm index a4b082f5dd1bb..33e9821dbc4bb 100644 --- a/code/modules/mob/living/basic/minebots/minebot_ai.dm +++ b/code/modules/mob/living/basic/minebots/minebot_ai.dm @@ -12,7 +12,7 @@ /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/find_and_hunt_target/hunt_ores/minebot, /datum/ai_planning_subtree/minebot_mining, /datum/ai_planning_subtree/locate_dead_humans, ) @@ -133,11 +133,11 @@ controller.clear_blackboard_key(target_key) ///store ores in our body -/datum/ai_planning_subtree/find_and_hunt_target/consume_ores/minebot +/datum/ai_planning_subtree/find_and_hunt_target/hunt_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) +/datum/ai_planning_subtree/find_and_hunt_target/hunt_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 diff --git a/code/modules/mob/living/basic/pets/fox.dm b/code/modules/mob/living/basic/pets/fox.dm index 578a64ba08dd8..150abdc676bc7 100644 --- a/code/modules/mob/living/basic/pets/fox.dm +++ b/code/modules/mob/living/basic/pets/fox.dm @@ -38,9 +38,9 @@ /datum/ai_controller/basic_controller/fox blackboard = list( - BB_BASIC_MOB_FLEEING = TRUE, - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/of_size/ours_or_smaller/ignore_faction, - BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction + BB_ALWAYS_IGNORE_FACTION = TRUE, + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/of_size/ours_or_smaller, + BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic, ) ai_movement = /datum/ai_movement/basic_avoidance @@ -61,12 +61,6 @@ /datum/ai_planning_subtree/random_speech/fox, ) -// Foxes will attack other station pets regardless of faction. -/datum/targetting_datum/basic/of_size/ours_or_smaller/ignore_faction - -/datum/targetting_datum/basic/of_size/ours_or_smaller/ignore_faction/faction_check(mob/living/living_mob, mob/living/the_target) - return FALSE - // The captain's fox, Renault /mob/living/basic/pet/fox/renault name = "Renault" diff --git a/code/modules/mob/living/simple_animal/friendly/sloth.dm b/code/modules/mob/living/basic/pets/sloth.dm similarity index 50% rename from code/modules/mob/living/simple_animal/friendly/sloth.dm rename to code/modules/mob/living/basic/pets/sloth.dm index 8e308c58d12d2..aaeeb218b2c0c 100644 --- a/code/modules/mob/living/simple_animal/friendly/sloth.dm +++ b/code/modules/mob/living/basic/pets/sloth.dm @@ -1,63 +1,68 @@ -GLOBAL_DATUM(cargo_sloth, /mob/living/simple_animal/sloth) +GLOBAL_DATUM(cargo_sloth, /mob/living/basic/sloth) -/mob/living/simple_animal/sloth +/mob/living/basic/sloth name = "sloth" desc = "An adorable, sleepy creature." icon = 'icons/mob/simple/pets.dmi' icon_state = "sloth" icon_living = "sloth" icon_dead = "sloth_dead" + speak_emote = list("yawns") - emote_hear = list("snores.","yawns.") - emote_see = list("dozes off.", "looks around sleepily.") - speak_chance = 1 - turns_per_move = 5 + can_be_held = TRUE - butcher_results = list(/obj/item/food/meat/slab = 3) + held_state = "sloth" + response_help_continuous = "pets" response_help_simple = "pet" response_disarm_continuous = "gently pushes aside" response_disarm_simple = "gently push aside" response_harm_continuous = "kicks" response_harm_simple = "kick" + + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + mob_biotypes = MOB_ORGANIC|MOB_BEAST gold_core_spawnable = FRIENDLY_SPAWN + melee_damage_lower = 18 melee_damage_upper = 18 health = 50 maxHealth = 50 - speed = 10 - held_state = "sloth" - ///In the case 'melee_damage_upper' is somehow raised above 0 - attack_verb_continuous = "bites" - attack_verb_simple = "bite" - attack_sound = 'sound/weapons/bite.ogg' - attack_vis_effect = ATTACK_EFFECT_BITE + speed = 10 // speed is fucking weird man. they aren't fast though don't worry + butcher_results = list(/obj/item/food/meat/slab = 3) - footstep_type = FOOTSTEP_MOB_CLAW + ai_controller = /datum/ai_controller/basic_controller/sloth -/mob/living/simple_animal/sloth/Initialize(mapload) +/mob/living/basic/sloth/Initialize(mapload) . = ..() AddElement(/datum/element/pet_bonus, "slowly smiles!") + AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_CLAW) + AddElement(/datum/element/ai_retaliate) + AddComponent(/datum/component/tree_climber) + + if(!mapload || isnull(GLOB.cargo_sloth) || !is_station_level(z)) + return + // If someone adds non-cargo sloths to maps we'll have a problem but we're fine for now - if(!GLOB.cargo_sloth && mapload && is_station_level(z)) - GLOB.cargo_sloth = src + GLOB.cargo_sloth = src -/mob/living/simple_animal/sloth/Destroy() +/mob/living/basic/sloth/Destroy() if(GLOB.cargo_sloth == src) GLOB.cargo_sloth = null return ..() -//Cargo Sloth -/mob/living/simple_animal/sloth/paperwork +/mob/living/basic/sloth/paperwork name = "Paperwork" desc = "Cargo's pet sloth. About as useful as the rest of the techs." + gender = MALE gold_core_spawnable = NO_SPAWN -//Cargo Sloth 2 - -/mob/living/simple_animal/sloth/citrus +/mob/living/basic/sloth/citrus name = "Citrus" desc = "Cargo's pet sloth. She's dressed in a horrible sweater." icon_state = "cool_sloth" @@ -66,3 +71,26 @@ GLOBAL_DATUM(cargo_sloth, /mob/living/simple_animal/sloth) gender = FEMALE butcher_results = list(/obj/item/toy/spinningtoy = 1) gold_core_spawnable = NO_SPAWN + +/// They're really passive in game, so they just wanna get away if you start smacking them. No trees in space from them to use for clawing your eyes out, but they will try if desperate. +/datum/ai_controller/basic_controller/sloth + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic, + ) + + 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/to_flee, + /datum/ai_planning_subtree/flee_target/from_flee_key, + /datum/ai_planning_subtree/climb_trees, + /datum/ai_planning_subtree/random_speech/sloth, + ) + +/datum/ai_planning_subtree/random_speech/sloth + speech_chance = 1 + emote_hear = list("snores.", "yawns.") + emote_see = list("dozes off.", "looks around sleepily.") diff --git a/code/modules/mob/living/basic/space_fauna/bear/_bear.dm b/code/modules/mob/living/basic/space_fauna/bear/_bear.dm index 414f28a1e9af5..924cf854276d1 100644 --- a/code/modules/mob/living/basic/space_fauna/bear/_bear.dm +++ b/code/modules/mob/living/basic/space_fauna/bear/_bear.dm @@ -42,7 +42,7 @@ /mob/living/basic/bear/Initialize(mapload) . = ..() - ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) + add_traits(list(TRAIT_SPACEWALK, TRAIT_FENCE_CLIMBER), INNATE_TRAIT) AddElement(/datum/element/ai_retaliate) AddComponent(/datum/component/tree_climber, climbing_distance = 15) AddElement(/datum/element/swabable, CELL_LINE_TABLE_BEAR, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm index d220325ca15f5..fc6997896b0d2 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm @@ -31,7 +31,7 @@ finish_planning = TRUE /datum/ai_planning_subtree/make_carp_rift/panic_teleport/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (!controller.blackboard[BB_BASIC_MOB_FLEEING]) + if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) return return ..() diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm index b30970145352b..9d967c5a8b009 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm @@ -9,8 +9,8 @@ */ /datum/ai_controller/basic_controller/carp blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(), - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends() + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items, + BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends, ) ai_movement = /datum/ai_movement/basic_avoidance @@ -35,8 +35,9 @@ */ /datum/ai_controller/basic_controller/carp/pet blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends() + BB_ALWAYS_IGNORE_FACTION = TRUE, + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends, ) ai_traits = STOP_MOVING_WHEN_PULLED planning_subtrees = list( @@ -78,8 +79,8 @@ */ /datum/ai_controller/basic_controller/carp/passive blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends() + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends, ) ai_traits = STOP_MOVING_WHEN_PULLED planning_subtrees = list( diff --git a/code/modules/mob/living/basic/space_fauna/changeling/flesh_spider.dm b/code/modules/mob/living/basic/space_fauna/changeling/flesh_spider.dm new file mode 100644 index 0000000000000..c73b008d6b48b --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/changeling/flesh_spider.dm @@ -0,0 +1,75 @@ +/** + * Spider-esque mob summoned by changelings. Exclusively player-controlled. + * An independent hit-and-run antagonist which can make webs and heals itself if left undamaged for a few seconds. + * Not a spider subtype because it keeps getting hit by unrelated balance changes intended for the Giant Spiders gamemode. + */ +/mob/living/basic/flesh_spider + 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" + mob_biotypes = MOB_ORGANIC|MOB_BUG + speak_emote = list("chitters") + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently pushes aside" + response_disarm_simple = "gently push aside" + damage_coeff = list(BRUTE = 1, BURN = 1.25, TOX = 1, CLONE = 1, STAMINA = 1, OXY = 1) + basic_mob_flags = FLAMMABLE_MOB + status_flags = NONE + speed = -0.1 + maxHealth = 90 + health = 90 + melee_damage_lower = 15 + melee_damage_upper = 20 + obj_damage = 30 + melee_attack_cooldown = CLICK_CD_MELEE + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + unsuitable_cold_damage = 4 + unsuitable_heat_damage = 4 + combat_mode = TRUE + faction = list() // No allies but yourself + pass_flags = PASSTABLE + unique_name = TRUE + lighting_cutoff_red = 22 + lighting_cutoff_green = 5 + lighting_cutoff_blue = 5 + butcher_results = list(/obj/item/food/meat/slab/spider = 2, /obj/item/food/spiderleg = 8) + ai_controller = /datum/ai_controller/basic_controller/giant_spider + +/mob/living/basic/flesh_spider/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_WEB_SURFER, INNATE_TRAIT) + AddElement(/datum/element/cliff_walking) + AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) + AddElement(/datum/element/venomous, /datum/reagent/toxin/hunterspider, 5) + AddElement(/datum/element/web_walker, /datum/movespeed_modifier/fast_web) + 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!") + AddComponent(\ + /datum/component/blood_walk,\ + blood_type = /obj/effect/decal/cleanable/blood/bubblegum,\ + blood_spawn_chance = 5,\ + ) + AddComponent(\ + /datum/component/regenerator,\ + regeneration_delay = 4 SECONDS,\ + health_per_second = maxHealth / 6,\ + outline_colour = COLOR_PINK,\ + ) + + var/datum/action/cooldown/mob_cooldown/lay_web/webbing = new(src) + webbing.webbing_time *= 0.7 + webbing.Grant(src) + ai_controller?.set_blackboard_key(BB_SPIDER_WEB_ACTION, webbing) + + 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) diff --git a/code/modules/mob/living/basic/space_fauna/headslug.dm b/code/modules/mob/living/basic/space_fauna/changeling/headslug.dm similarity index 100% rename from code/modules/mob/living/basic/space_fauna/headslug.dm rename to code/modules/mob/living/basic/space_fauna/changeling/headslug.dm diff --git a/code/modules/mob/living/basic/space_fauna/demon/demon.dm b/code/modules/mob/living/basic/space_fauna/demon/demon.dm index c2d8c751cde90..741ac27712f8f 100644 --- a/code/modules/mob/living/basic/space_fauna/demon/demon.dm +++ b/code/modules/mob/living/basic/space_fauna/demon/demon.dm @@ -32,6 +32,7 @@ obj_damage = 40 melee_damage_lower = 10 melee_damage_upper = 15 + melee_attack_cooldown = CLICK_CD_MELEE death_message = "screams in agony as it sublimates into a sulfurous smoke." death_sound = 'sound/magic/demon_dies.ogg' 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 index a3c8e22071d5d..b3033b27a4bd3 100644 --- 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 @@ -22,7 +22,7 @@ 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) +/datum/targetting_datum/basic/eyeball/can_attack(mob/living/owner, atom/target, vision_range) . = ..() if(!.) return FALSE diff --git a/code/modules/mob/living/basic/space_fauna/ghost.dm b/code/modules/mob/living/basic/space_fauna/ghost.dm index 058c2d2cd3f40..35afaade990c8 100644 --- a/code/modules/mob/living/basic/space_fauna/ghost.dm +++ b/code/modules/mob/living/basic/space_fauna/ghost.dm @@ -93,7 +93,7 @@ /datum/ai_controller/basic_controller/ghost blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, ) ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/space_fauna/lightgeist.dm b/code/modules/mob/living/basic/space_fauna/lightgeist.dm index c70588f4502b4..9ab6ffe677855 100644 --- a/code/modules/mob/living/basic/space_fauna/lightgeist.dm +++ b/code/modules/mob/living/basic/space_fauna/lightgeist.dm @@ -97,7 +97,7 @@ /// Type of limb we can heal var/required_bodytype = BODYTYPE_ORGANIC -/datum/targetting_datum/lightgeist/can_attack(mob/living/living_mob, mob/living/target) +/datum/targetting_datum/lightgeist/can_attack(mob/living/living_mob, mob/living/target, vision_range) if (!isliving(target) || target.stat == DEAD) return FALSE if (!(heal_biotypes & target.mob_biotypes)) diff --git a/code/modules/mob/living/basic/space_fauna/mushroom.dm b/code/modules/mob/living/basic/space_fauna/mushroom.dm index e6d47e2db5cc9..96280db29235b 100644 --- a/code/modules/mob/living/basic/space_fauna/mushroom.dm +++ b/code/modules/mob/living/basic/space_fauna/mushroom.dm @@ -73,7 +73,7 @@ stat_attack = DEAD ///we only attacked another mushrooms -/datum/targetting_datum/basic/mushroom/faction_check(mob/living/living_mob, mob/living/the_target) +/datum/targetting_datum/basic/mushroom/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) return !living_mob.faction_check_mob(the_target, exact_match = check_factions_exactly) /datum/ai_planning_subtree/find_and_hunt_target/mushroom_food 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 index ef92c7b3b76b1..8a7013b962373 100644 --- 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 @@ -1,8 +1,7 @@ /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, + BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic, ) ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm new file mode 100644 index 0000000000000..b3c6935c92efe --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm @@ -0,0 +1,444 @@ +/// Source for a trait we get when we're stunned +#define REVENANT_STUNNED_TRAIT "revenant_got_stunned" + +/// Revenants: "Ghosts" that are invisible and move like ghosts, cannot take damage while invisible +/// Can hear deadchat, but are NOT normal ghosts and do NOT have x-ray vision +/// Admin-spawn or random event +/mob/living/basic/revenant + name = "revenant" + desc = "A malevolent spirit." + icon = 'icons/mob/simple/mob.dmi' + icon_state = "revenant_idle" + mob_biotypes = MOB_SPIRIT + incorporeal_move = INCORPOREAL_MOVE_JAUNT + invisibility = INVISIBILITY_REVENANT + health = INFINITY //Revenants don't use health, they use essence instead + maxHealth = INFINITY + plane = GHOST_PLANE + sight = SEE_SELF + throwforce = 0 + + // Going for faint purple spoopy ghost + lighting_cutoff_red = 20 + lighting_cutoff_green = 15 + lighting_cutoff_blue = 35 + + friendly_verb_continuous = "touches" + friendly_verb_simple = "touch" + response_help_continuous = "passes through" + response_help_simple = "pass through" + response_disarm_continuous = "swings through" + response_disarm_simple = "swing through" + response_harm_continuous = "punches through" + response_harm_simple = "punch through" + unsuitable_atmos_damage = 0 + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) //I don't know how you'd apply those, but revenants no-sell them anyway. + 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 + + status_flags = NONE + density = FALSE + move_resist = MOVE_FORCE_OVERPOWERING + mob_size = MOB_SIZE_TINY + pass_flags = PASSTABLE | PASSGRILLE | PASSMOB + speed = 1 + unique_name = TRUE + hud_possible = list(ANTAG_HUD) + hud_type = /datum/hud/revenant + + /// The icon we use while just floating around. + var/icon_idle = "revenant_idle" + /// The icon we use while in a revealed state. + var/icon_reveal = "revenant_revealed" + /// The icon we use when stunned (temporarily frozen) + var/icon_stun = "revenant_stun" + /// The icon we use while draining someone. + var/icon_drain = "revenant_draining" + + /// Are we currently dormant (ectoplasm'd)? + var/dormant = FALSE + /// Are we currently draining someone? + var/draining = FALSE + /// Have we already given this revenant abilities? + var/generated_objectives_and_spells = FALSE + + /// Lazylist of drained mobs to ensure that we don't steal a soul from someone twice + var/list/drained_mobs = null + /// List of action ability datums to grant on Initialize. Keep in mind that anything with the `/aoe/revenant` subtype starts locked by default. + var/static/list/datum/action/abilities = list( + /datum/action/cooldown/spell/aoe/revenant/blight, + /datum/action/cooldown/spell/aoe/revenant/defile, + /datum/action/cooldown/spell/aoe/revenant/haunt_object, + /datum/action/cooldown/spell/aoe/revenant/malfunction, + /datum/action/cooldown/spell/aoe/revenant/overload, + /datum/action/cooldown/spell/list_target/telepathy/revenant, + ) + + /// The resource, and health, of revenants. + var/essence = 75 + /// The regeneration cap of essence (go figure); regenerates every Life() tick up to this amount. + var/max_essence = 75 + /// If the revenant regenerates essence or not + var/essence_regenerating = TRUE + /// How much essence regenerates per second + var/essence_regen_amount = 2.5 + /// How much essence the revenant has stolen + var/essence_accumulated = 0 + /// How much stolen essence is available for unlocks + var/essence_excess = 0 + /// How long the revenant is revealed for, is about 2 seconds times this var. + var/unreveal_time = 0 + /// How many perfect, regen-cap increasing souls the revenant has. //TODO, add objective for getting a perfect soul(s?) + var/perfectsouls = 0 + +/mob/living/basic/revenant/Initialize(mapload) + . = ..() + AddElement(/datum/element/simple_flying) + add_traits(list(TRAIT_SPACEWALK, TRAIT_SIXTHSENSE, TRAIT_FREE_HYPERSPACE_MOVEMENT), INNATE_TRAIT) + + for(var/ability in abilities) + var/datum/action/spell = new ability(src) + spell.Grant(src) + + RegisterSignal(src, COMSIG_LIVING_BANED, PROC_REF(on_baned)) + RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_move)) + RegisterSignal(src, COMSIG_LIVING_LIFE, PROC_REF(on_life)) + set_random_revenant_name() + + GLOB.revenant_relay_mobs |= src + +/mob/living/basic/revenant/Destroy() + GLOB.revenant_relay_mobs -= src + return ..() + +/mob/living/basic/revenant/Login() + . = ..() + if(!. || isnull(client)) + return FALSE + + var/static/cached_string = null + if(isnull(cached_string)) + cached_string = examine_block(jointext(create_login_string(), "\n")) + + to_chat(src, cached_string, type = MESSAGE_TYPE_INFO) + + if(generated_objectives_and_spells) + return TRUE + + generated_objectives_and_spells = TRUE + mind.set_assigned_role(SSjob.GetJobType(/datum/job/revenant)) + mind.special_role = ROLE_REVENANT + SEND_SOUND(src, sound('sound/effects/ghost.ogg')) + mind.add_antag_datum(/datum/antagonist/revenant) + return TRUE + +/// Signal Handler Injection to handle Life() stuff for revenants +/mob/living/basic/revenant/proc/on_life(seconds_per_tick = SSMOBS_DT, times_fired) + SIGNAL_HANDLER + + if(dormant) + return COMPONENT_LIVING_CANCEL_LIFE_PROCESSING + + if(HAS_TRAIT(src, TRAIT_REVENANT_REVEALED) && essence <= 0) + death() + return COMPONENT_LIVING_CANCEL_LIFE_PROCESSING + + if(essence_regenerating && !HAS_TRAIT(src, TRAIT_REVENANT_INHIBITED) && essence < max_essence) //While inhibited, essence will not regenerate + var/change_in_time = DELTA_WORLD_TIME(SSmobs) + essence = min(essence + (essence_regen_amount * change_in_time), max_essence) + update_mob_action_buttons() //because we update something required by our spells in life, we need to update our buttons + + update_appearance(UPDATE_ICON) + update_health_hud() + +/mob/living/basic/revenant/get_status_tab_items() + . = ..() + . += "Current Essence: [essence >= max_essence ? essence : "[essence] / [max_essence]"] E" + . += "Total Essence Stolen: [essence_accumulated] SE" + . += "Unused Stolen Essence: [essence_excess] SE" + . += "Perfect Souls Stolen: [perfectsouls]" + +/mob/living/basic/revenant/update_health_hud() + if(isnull(hud_used)) + return + + var/essencecolor = "#8F48C6" + if(essence > max_essence) + essencecolor = "#9A5ACB" //oh boy you've got a lot of essence + else if(essence <= 0) + essencecolor = "#1D2953" //oh jeez you're dying + hud_used.healths.maptext = MAPTEXT("
    [essence]E
    ") + +/mob/living/basic/revenant/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) + if(!message) + return + + if(client) + if(client.prefs.muted & MUTE_IC) + to_chat(src, span_boldwarning("You cannot send IC messages (muted).")) + return + if (!(ignore_spam || forced) && client.handle_spam_prevention(message, MUTE_IC)) + return + + if(sanitize) + message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)) + + log_talk(message, LOG_SAY) + var/rendered = span_deadsay("UNDEAD: [src] says, \"[message]\"") + relay_to_list_and_observers(rendered, GLOB.revenant_relay_mobs, src) + +/mob/living/basic/revenant/ClickOn(atom/A, params) //revenants can't interact with the world directly, so we gotta do some wacky override stuff + var/list/modifiers = params2list(params) + if(LAZYACCESS(modifiers, SHIFT_CLICK)) + ShiftClickOn(A) + return + if(LAZYACCESS(modifiers, ALT_CLICK)) + AltClickNoInteract(src, A) + return + if(LAZYACCESS(modifiers, RIGHT_CLICK)) + ranged_secondary_attack(A, modifiers) + return + + if(ishuman(A) && in_range(src, A)) + attempt_harvest(A) + +/mob/living/basic/revenant/ranged_secondary_attack(atom/target, modifiers) + if(HAS_TRAIT(src, TRAIT_REVENANT_INHIBITED) || HAS_TRAIT(src, TRAIT_REVENANT_REVEALED) || HAS_TRAIT(src, TRAIT_NO_TRANSFORM) || !Adjacent(target) || !incorporeal_move_check(target)) + return + + var/list/icon_dimensions = get_icon_dimensions(target.icon) + var/orbitsize = (icon_dimensions["width"] + icon_dimensions["height"]) * 0.5 + orbitsize -= (orbitsize / world.icon_size) * (world.icon_size * 0.25) + orbit(target, orbitsize) + +/mob/living/basic/revenant/adjust_health(amount, updating_health = TRUE, forced = FALSE) + if(!forced && !HAS_TRAIT(src, TRAIT_REVENANT_REVEALED)) + return 0 + + . = amount + + essence = max(0, essence - amount) + if(updating_health) + update_health_hud() + if(essence == 0) + death() + + return . + +/mob/living/basic/revenant/orbit(atom/target) + setDir(SOUTH) // reset dir so the right directional sprites show up + return ..() + +/mob/living/basic/revenant/update_icon_state() + . = ..() + + if(HAS_TRAIT(src, TRAIT_REVENANT_REVEALED)) + icon_state = icon_reveal + return + + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) + if(draining) + icon_state = icon_drain + else + icon_state = icon_stun + + return + + icon_state = icon_idle + +/mob/living/basic/revenant/med_hud_set_health() + return //we use no hud + +/mob/living/basic/revenant/med_hud_set_status() + return //we use no hud + +/mob/living/basic/revenant/dust(just_ash, drop_items, force) + death() + +/mob/living/basic/revenant/gib() + death() + +/mob/living/basic/revenant/can_perform_action(atom/movable/target, action_bitflags) + return FALSE + +/mob/living/basic/revenant/ex_act(severity, target) + return FALSE //Immune to the effects of explosions. + +/mob/living/basic/revenant/blob_act(obj/structure/blob/attacking_blob) + return //blah blah blobs aren't in tune with the spirit world, or something. + +/mob/living/basic/revenant/singularity_act() + return //don't walk into the singularity expecting to find corpses, okay? + +/mob/living/basic/revenant/narsie_act() + return //most humans will now be either bones or harvesters, but we're still un-alive. + +/mob/living/basic/revenant/bullet_act() + if(!HAS_TRAIT(src, TRAIT_REVENANT_REVEALED) || dormant) + return BULLET_ACT_FORCE_PIERCE + return ..() + +/mob/living/basic/revenant/death() + if(!HAS_TRAIT(src, TRAIT_REVENANT_REVEALED) || dormant) //Revenants cannot die if they aren't revealed //or are already dead + return + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, REVENANT_STUNNED_TRAIT) + dormant = TRUE + + visible_message( + span_warning("[src] lets out a waning screech as violet mist swirls around its dissolving body!"), + span_revendanger("NO! No... it's too late, you can feel your essence [pick("breaking apart", "drifting away")]..."), + ) + + invisibility = 0 + icon_state = "revenant_draining" + playsound(src, 'sound/effects/screech.ogg', 100, TRUE) + + animate(src, alpha = 0, time = 3 SECONDS) + addtimer(CALLBACK(src, PROC_REF(move_to_ectoplasm)), 3 SECONDS) + +/// Forces the mob, once dormant, to move inside ectoplasm until it can regenerate. +/mob/living/basic/revenant/proc/move_to_ectoplasm() + if(QDELETED(src) || !dormant) // something fucky happened, abort. we MUST be dormant to go inside the ectoplasm. + return + + visible_message(span_danger("[src]'s body breaks apart into a fine pile of blue dust.")) + + var/obj/item/ectoplasm/revenant/goop = new(get_turf(src)) // the ectoplasm will handle moving us out of dormancy + goop.old_ckey = client.ckey + goop.revenant = src + forceMove(goop) + +/mob/living/basic/revenant/proc/on_move(datum/source, atom/entering_loc) + SIGNAL_HANDLER + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) // just in case it occurs, need to provide some feedback + balloon_alert(src, "can't move!") + return + + if(isnull(orbiting) || incorporeal_move_check(entering_loc)) + return + + // we're about to go somewhere we aren't meant to, end the orbit and block the move. feedback will be given in `incorporeal_move_check()` + orbiting.end_orbit(src) + return COMPONENT_MOVABLE_BLOCK_PRE_MOVE + +/// Generates the information the player needs to know how to play their role, and returns it as a list. +/mob/living/basic/revenant/proc/create_login_string() + RETURN_TYPE(/list) + var/list/returnable_list = list() + returnable_list += span_deadsay(span_boldbig("You are a revenant.")) + returnable_list += span_bold("Your formerly mundane spirit has been infused with alien energies and empowered into a revenant.") + returnable_list += span_bold("You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds.") + returnable_list += span_bold("You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable.") + returnable_list += span_bold("To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities.") + returnable_list += span_bold("You do not remember anything of your past lives, nor will you remember anything about this one after your death.") + returnable_list += span_bold("Be sure to read the wiki page to learn more.") + return returnable_list + +/mob/living/basic/revenant/proc/set_random_revenant_name() + var/list/built_name_strings = list() + built_name_strings += pick(strings(REVENANT_NAME_FILE, "spirit_type")) + built_name_strings += " of " + built_name_strings += pick(strings(REVENANT_NAME_FILE, "adverb")) + built_name_strings += pick(strings(REVENANT_NAME_FILE, "theme")) + name = built_name_strings.Join("") + +/mob/living/basic/revenant/proc/on_baned(obj/item/weapon, mob/living/user) + SIGNAL_HANDLER + visible_message( + span_warning("[src] violently flinches!"), + span_revendanger("As [weapon] passes through you, you feel your essence draining away!"), + ) + apply_status_effect(/datum/status_effect/revenant/inhibited, 3 SECONDS) + +/// Incorporeal move check: blocked by holy-watered tiles and salt piles. +/mob/living/basic/revenant/proc/incorporeal_move_check(atom/destination) + var/turf/open/floor/step_turf = get_turf(destination) + if(isnull(step_turf)) + return TRUE // what? whatever let it happen + + if(step_turf.turf_flags & NOJAUNT) + to_chat(src, span_warning("Some strange aura is blocking the way.")) + return FALSE + + if(locate(/obj/effect/decal/cleanable/food/salt) in step_turf) + balloon_alert(src, "blocked by salt!") + apply_status_effect(/datum/status_effect/revenant/revealed, 2 SECONDS) + apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, 2 SECONDS) + return FALSE + + if(locate(/obj/effect/blessing) in step_turf) + to_chat(src, span_warning("Holy energies block your path!")) + return FALSE + + return TRUE + +/mob/living/basic/revenant/proc/cast_check(essence_cost) + if(QDELETED(src)) + return + + var/turf/current = get_turf(src) + + if(isclosedturf(current)) + to_chat(src, span_revenwarning("You cannot use abilities from inside of a wall.")) + return FALSE + + for(var/obj/thing in current) + if(!thing.density || thing.CanPass(src, get_dir(current, src))) + continue + to_chat(src, span_revenwarning("You cannot use abilities inside of a dense object.")) + return FALSE + + if(HAS_TRAIT(src, TRAIT_REVENANT_INHIBITED)) + to_chat(src, span_revenwarning("Your powers have been suppressed by a nullifying energy!")) + return FALSE + + if(!change_essence_amount(essence_cost, TRUE)) + to_chat(src, span_revenwarning("You lack the essence to use that ability.")) + return FALSE + + return TRUE + +/mob/living/basic/revenant/proc/unlock(essence_cost) + if(essence_excess < essence_cost) + return FALSE + essence_excess -= essence_cost + update_mob_action_buttons() + return TRUE + +/mob/living/basic/revenant/proc/death_reset() + REMOVE_TRAIT(src, TRAIT_NO_TRANSFORM, REVENANT_STUNNED_TRAIT) + forceMove(get_turf(src)) + // clean slate, so no more debilitating effects + remove_status_effect(/datum/status_effect/revenant/revealed) + remove_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant) + remove_status_effect(/datum/status_effect/revenant/inhibited) + draining = FALSE + dormant = FALSE + incorporeal_move = INCORPOREAL_MOVE_JAUNT + invisibility = INVISIBILITY_REVENANT + alpha = 255 + +/mob/living/basic/revenant/proc/change_essence_amount(essence_to_change_by, silent = FALSE, source = null) + if(QDELETED(src)) + return FALSE + + if((essence + essence_to_change_by) < 0) + return FALSE + + essence = max(0, essence + essence_to_change_by) + update_health_hud() + + if(essence_to_change_by > 0) + essence_accumulated = max(0, essence_accumulated + essence_to_change_by) + essence_excess = max(0, essence_excess + essence_to_change_by) + + update_mob_action_buttons() + if(!silent) + if(essence_to_change_by > 0) + to_chat(src, span_revennotice("Gained [essence_to_change_by]E [source ? "from [source]":""].")) + else + to_chat(src, span_revenminor("Lost [essence_to_change_by]E [source ? "from [source]":""].")) + return TRUE + +#undef REVENANT_STUNNED_TRAIT diff --git a/code/modules/antagonists/revenant/revenant_abilities.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm similarity index 63% rename from code/modules/antagonists/revenant/revenant_abilities.dm rename to code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm index 0de2be1d17c80..3ea62afd9f80d 100644 --- a/code/modules/antagonists/revenant/revenant_abilities.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm @@ -1,123 +1,6 @@ #define REVENANT_DEFILE_MIN_DAMAGE 30 #define REVENANT_DEFILE_MAX_DAMAGE 50 - -/mob/living/simple_animal/revenant/ClickOn(atom/A, params) //revenants can't interact with the world directly. - var/list/modifiers = params2list(params) - if(LAZYACCESS(modifiers, SHIFT_CLICK)) - ShiftClickOn(A) - return - if(LAZYACCESS(modifiers, ALT_CLICK)) - AltClickNoInteract(src, A) - return - if(LAZYACCESS(modifiers, RIGHT_CLICK)) - ranged_secondary_attack(A, modifiers) - return - - if(ishuman(A)) - //Humans are tagged, so this is fine - if(REF(A) in drained_mobs) - to_chat(src, span_revenwarning("[A]'s soul is dead and empty.") ) - else if(in_range(src, A)) - Harvest(A) - -/mob/living/simple_animal/revenant/ranged_secondary_attack(atom/target, modifiers) - if(revealed || inhibited || HAS_TRAIT(src, TRAIT_NO_TRANSFORM) || !Adjacent(target) || !incorporeal_move_check(target)) - return - - var/list/icon_dimensions = get_icon_dimensions(target.icon) - var/orbitsize = (icon_dimensions["width"] + icon_dimensions["height"]) * 0.5 - orbitsize -= (orbitsize / world.icon_size) * (world.icon_size * 0.25) - orbit(target, orbitsize) - -//Harvest; activated by clicking the target, will try to drain their essence. -/mob/living/simple_animal/revenant/proc/Harvest(mob/living/carbon/human/target) - if(!castcheck(0)) - return - if(draining) - to_chat(src, span_revenwarning("You are already siphoning the essence of a soul!")) - return - if(!target.stat) - to_chat(src, span_revennotice("[target.p_Their()] soul is too strong to harvest.")) - if(prob(10)) - to_chat(target, span_revennotice("You feel as if you are being watched.")) - return - log_combat(src, target, "started to harvest") - face_atom(target) - draining = TRUE - essence_drained += rand(15, 20) - to_chat(src, span_revennotice("You search for the soul of [target].")) - if(do_after(src, rand(10, 20), target, timed_action_flags = IGNORE_HELD_ITEM)) //did they get deleted in that second? - if(target.ckey) - to_chat(src, span_revennotice("[target.p_Their()] soul burns with intelligence.")) - essence_drained += rand(20, 30) - if(target.stat != DEAD && !HAS_TRAIT(target, TRAIT_WEAK_SOUL)) - to_chat(src, span_revennotice("[target.p_Their()] soul blazes with life!")) - essence_drained += rand(40, 50) - if(HAS_TRAIT(target, TRAIT_WEAK_SOUL) && !target.ckey) - to_chat(src, span_revennotice("[target.p_Their()] soul is weak and underdeveloped. They won't be worth very much.")) - essence_drained = 5 - else - to_chat(src, span_revennotice("[target.p_Their()] soul is weak and faltering.")) - if(do_after(src, rand(15, 20), target, timed_action_flags = IGNORE_HELD_ITEM)) //did they get deleted NOW? - switch(essence_drained) - if(1 to 30) - to_chat(src, span_revennotice("[target] will not yield much essence. Still, every bit counts.")) - if(30 to 70) - to_chat(src, span_revennotice("[target] will yield an average amount of essence.")) - if(70 to 90) - to_chat(src, span_revenboldnotice("Such a feast! [target] will yield much essence to you.")) - if(90 to INFINITY) - to_chat(src, span_revenbignotice("Ah, the perfect soul. [target] will yield massive amounts of essence to you.")) - if(do_after(src, rand(15, 25), target, timed_action_flags = IGNORE_HELD_ITEM)) //how about now - if(!target.stat) - to_chat(src, span_revenwarning("[target.p_Theyre()] now powerful enough to fight off your draining.")) - to_chat(target, span_boldannounce("You feel something tugging across your body before subsiding.")) - draining = 0 - essence_drained = 0 - return //hey, wait a minute... - to_chat(src, span_revenminor("You begin siphoning essence from [target]'s soul.")) - if(target.stat != DEAD) - to_chat(target, span_warning("You feel a horribly unpleasant draining sensation as your grip on life weakens...")) - if(target.stat == SOFT_CRIT) - target.Stun(46) - reveal(46) - stun(46) - target.visible_message(span_warning("[target] suddenly rises slightly into the air, [target.p_their()] skin turning an ashy gray.")) - if(target.can_block_magic(MAGIC_RESISTANCE_HOLY)) - to_chat(src, span_revenminor("Something's wrong! [target] seems to be resisting the siphoning, leaving you vulnerable!")) - target.visible_message(span_warning("[target] slumps onto the ground."), \ - span_revenwarning("Violet lights, dancing in your vision, receding--")) - draining = FALSE - return - var/datum/beam/B = Beam(target,icon_state="drain_life") - if(do_after(src, 46, target, timed_action_flags = IGNORE_HELD_ITEM)) //As one cannot prove the existance of ghosts, ghosts cannot prove the existance of the target they were draining. - change_essence_amount(essence_drained, FALSE, target) - if(essence_drained <= 90 && target.stat != DEAD && !HAS_TRAIT(target, TRAIT_WEAK_SOUL)) - essence_regen_cap += 5 - to_chat(src, span_revenboldnotice("The absorption of [target]'s living soul has increased your maximum essence level. Your new maximum essence is [essence_regen_cap].")) - if(essence_drained > 90) - essence_regen_cap += 15 - perfectsouls++ - to_chat(src, span_revenboldnotice("The perfection of [target]'s soul has increased your maximum essence level. Your new maximum essence is [essence_regen_cap].")) - to_chat(src, span_revennotice("[target]'s soul has been considerably weakened and will yield no more essence for the time being.")) - target.visible_message(span_warning("[target] slumps onto the ground."), \ - span_revenwarning("Violets lights, dancing in your vision, getting clo--")) - drained_mobs += REF(target) - if(target.stat != DEAD) - target.investigate_log("has died from revenant harvest.", INVESTIGATE_DEATHS) - target.death(FALSE) - else - to_chat(src, span_revenwarning("[target ? "[target] has":"[target.p_Theyve()]"] been drawn out of your grasp. The link has been broken.")) - if(target) //Wait, target is WHERE NOW? - target.visible_message(span_warning("[target] slumps onto the ground."), \ - span_revenwarning("Violets lights, dancing in your vision, receding--")) - qdel(B) - else - to_chat(src, span_revenwarning("You are not close enough to siphon [target ? "[target]'s":"[target.p_their()]"] soul. The link has been broken.")) - draining = FALSE - essence_drained = 0 - //Transmit: the revemant's only direct way to communicate. Sends a single message silently to a single mob /datum/action/cooldown/spell/list_target/telepathy/revenant name = "Revenant Transmit" @@ -171,8 +54,8 @@ stack_trace("[type] was owned by a non-revenant mob, please don't.") return FALSE - var/mob/living/simple_animal/revenant/ghost = owner - if(ghost.inhibited) + var/mob/living/basic/revenant/ghost = owner + if(ghost.dormant || HAS_TRAIT(ghost, TRAIT_REVENANT_INHIBITED)) return FALSE if(locked && ghost.essence_excess <= unlock_amount) return FALSE @@ -184,7 +67,7 @@ /datum/action/cooldown/spell/aoe/revenant/get_things_to_cast_on(atom/center) return RANGE_TURFS(aoe_radius, center) -/datum/action/cooldown/spell/aoe/revenant/before_cast(mob/living/simple_animal/revenant/cast_on) +/datum/action/cooldown/spell/aoe/revenant/before_cast(mob/living/basic/revenant/cast_on) . = ..() if(. & SPELL_CANCEL_CAST) return @@ -202,16 +85,16 @@ reset_spell_cooldown() return . | SPELL_CANCEL_CAST - if(!cast_on.castcheck(-cast_amount)) + if(!cast_on.cast_check(-cast_amount)) reset_spell_cooldown() return . | SPELL_CANCEL_CAST -/datum/action/cooldown/spell/aoe/revenant/after_cast(mob/living/simple_animal/revenant/cast_on) +/datum/action/cooldown/spell/aoe/revenant/after_cast(mob/living/basic/revenant/cast_on) . = ..() if(reveal_duration > 0 SECONDS) - cast_on.reveal(reveal_duration) + cast_on.apply_status_effect(/datum/status_effect/revenant/revealed, reveal_duration) if(stun_duration > 0 SECONDS) - cast_on.stun(stun_duration) + cast_on.apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, stun_duration) //Overload Light: Breaks a light that's online and sends out lightning bolts to all nearby people. /datum/action/cooldown/spell/aoe/revenant/overload @@ -227,10 +110,10 @@ /// The range the shocks from the lights go var/shock_range = 2 - /// The damage the shcoskf rom the lgihts do + /// The damage the shocks from the lights do var/shock_damage = 15 -/datum/action/cooldown/spell/aoe/revenant/overload/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster) +/datum/action/cooldown/spell/aoe/revenant/overload/cast_on_thing_in_aoe(turf/victim, mob/living/basic/revenant/caster) for(var/obj/machinery/light/light in victim) if(!light.on) continue @@ -242,7 +125,7 @@ new /obj/effect/temp_visual/revenant(get_turf(light)) addtimer(CALLBACK(src, PROC_REF(overload_shock), light, caster), 20) -/datum/action/cooldown/spell/aoe/revenant/overload/proc/overload_shock(obj/machinery/light/to_shock, mob/living/simple_animal/revenant/caster) +/datum/action/cooldown/spell/aoe/revenant/overload/proc/overload_shock(obj/machinery/light/to_shock, mob/living/basic/revenant/caster) flick("[to_shock.base_state]2", to_shock) for(var/mob/living/carbon/human/human_mob in view(shock_range, to_shock)) if(human_mob == caster) @@ -267,7 +150,7 @@ reveal_duration = 4 SECONDS stun_duration = 2 SECONDS -/datum/action/cooldown/spell/aoe/revenant/defile/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster) +/datum/action/cooldown/spell/aoe/revenant/defile/cast_on_thing_in_aoe(turf/victim, mob/living/basic/revenant/caster) for(var/obj/effect/blessing/blessing in victim) qdel(blessing) new /obj/effect/temp_visual/revenant(victim) @@ -316,7 +199,7 @@ unlock_amount = 125 // A note to future coders: do not replace this with an EMP because it will wreck malf AIs and everyone will hate you. -/datum/action/cooldown/spell/aoe/revenant/malfunction/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster) +/datum/action/cooldown/spell/aoe/revenant/malfunction/cast_on_thing_in_aoe(turf/victim, mob/living/basic/revenant/caster) for(var/mob/living/simple_animal/bot/bot in victim) if(!(bot.bot_cover_flags & BOT_COVER_EMAGGED)) new /obj/effect/temp_visual/revenant(bot.loc) @@ -357,7 +240,7 @@ cast_amount = 50 unlock_amount = 75 -/datum/action/cooldown/spell/aoe/revenant/blight/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster) +/datum/action/cooldown/spell/aoe/revenant/blight/cast_on_thing_in_aoe(turf/victim, mob/living/basic/revenant/caster) for(var/mob/living/mob in victim) if(mob == caster) continue @@ -430,7 +313,7 @@ return things -/datum/action/cooldown/spell/aoe/revenant/haunt_object/cast_on_thing_in_aoe(obj/item/victim, mob/living/simple_animal/revenant/caster) +/datum/action/cooldown/spell/aoe/revenant/haunt_object/cast_on_thing_in_aoe(obj/item/victim, mob/living/basic/revenant/caster) var/distance_from_caster = get_dist(get_turf(victim), get_turf(caster)) var/chance_of_haunting = 150 * (1 / distance_from_caster) if(!prob(chance_of_haunting)) diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm new file mode 100644 index 0000000000000..0eeec231973ee --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm @@ -0,0 +1,73 @@ +/// Parent type for all unique revenant status effects +/datum/status_effect/revenant + +/datum/status_effect/revenant/on_creation(mob/living/new_owner, duration) + if(isnum(duration)) + src.duration = duration + return ..() + +/datum/status_effect/revenant/revealed + id = "revenant_revealed" + +/datum/status_effect/revenant/revealed/on_apply() + . = ..() + if(!.) + return FALSE + owner.orbiting?.end_orbit(src) + + ADD_TRAIT(owner, TRAIT_REVENANT_REVEALED, TRAIT_STATUS_EFFECT(id)) + owner.invisibility = 0 + owner.incorporeal_move = FALSE + owner.update_appearance(UPDATE_ICON) + owner.update_mob_action_buttons() + +/datum/status_effect/revenant/revealed/on_remove() + REMOVE_TRAIT(owner, TRAIT_REVENANT_REVEALED, TRAIT_STATUS_EFFECT(id)) + + owner.incorporeal_move = INCORPOREAL_MOVE_JAUNT + owner.invisibility = INVISIBILITY_REVENANT + owner.update_appearance(UPDATE_ICON) + owner.update_mob_action_buttons() + return ..() + +/datum/status_effect/revenant/inhibited + id = "revenant_inhibited" + +/datum/status_effect/revenant/inhibited/on_apply() + . = ..() + if(!.) + return FALSE + owner.orbiting?.end_orbit(src) + + ADD_TRAIT(owner, TRAIT_REVENANT_INHIBITED, TRAIT_STATUS_EFFECT(id)) + owner.update_appearance(UPDATE_ICON) + + owner.balloon_alert(owner, "inhibited!") + +/datum/status_effect/revenant/inhibited/on_remove() + REMOVE_TRAIT(owner, TRAIT_REVENANT_INHIBITED, TRAIT_STATUS_EFFECT(id)) + owner.update_appearance(UPDATE_ICON) + + owner.balloon_alert(owner, "uninhibited") + return ..() + +/datum/status_effect/incapacitating/paralyzed/revenant + id = "revenant_paralyzed" + +/datum/status_effect/incapacitating/paralyzed/revenant/on_apply() + . = ..() + if(!.) + return FALSE + owner.orbiting?.end_orbit(src) + + ADD_TRAIT(owner, TRAIT_NO_TRANSFORM, TRAIT_STATUS_EFFECT(id)) + owner.balloon_alert(owner, "can't move!") + owner.update_mob_action_buttons() + owner.update_appearance(UPDATE_ICON) + +/datum/status_effect/incapacitating/paralyzed/revenant/on_remove() + REMOVE_TRAIT(owner, TRAIT_NO_TRANSFORM, TRAIT_STATUS_EFFECT(id)) + owner.update_mob_action_buttons() + owner.balloon_alert(owner, "can move again") + + return ..() diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_harvest.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_harvest.dm new file mode 100644 index 0000000000000..b8bb05db48414 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_harvest.dm @@ -0,0 +1,143 @@ +// This file contains the proc we use for revenant harvesting because it is a very long and bulky proc that takes up a lot of space elsewhere + +/// Container proc for `harvest()`, handles the pre-checks as well as potential early-exits for any reason. +/// Will return FALSE if we can't execute `harvest()`, or will otherwise the result of `harvest()`: a boolean value. +/mob/living/basic/revenant/proc/attempt_harvest(mob/living/carbon/human/target) + if(LAZYFIND(drained_mobs, REF(target))) + to_chat(src, span_revenwarning("[target]'s soul is dead and empty.")) + return FALSE + + if(!cast_check(0)) + return FALSE + + if(draining) + to_chat(src, span_revenwarning("You are already siphoning the essence of a soul!")) + return FALSE + + draining = TRUE + var/value_to_return = harvest_soul(target) + if(!value_to_return) + log_combat(src, target, "stopped the harvest of") + draining = FALSE + + return value_to_return + +/// Harvest; activated by clicking a target, will try to drain their essence. Handles all messages and handling of the target. +/// Returns FALSE if we exit out of the harvest, TRUE if it is fully done. +/mob/living/basic/revenant/proc/harvest_soul(mob/living/carbon/human/target) // this isn't in the main revenant code file because holyyyy shit it's long + if(QDELETED(target)) // what + return FALSE + + // cache pronouns in case they get deleted as well as be a nice micro-opt due to the multiple times we use them + var/target_their = target.p_their() + var/target_Their = target.p_Their() + var/target_Theyre = target.p_Theyre() + var/target_They_have = "[target.p_They()] [target.p_have()]" + + if(target.stat == CONSCIOUS) + to_chat(src, span_revennotice("[target_Their] soul is too strong to harvest.")) + if(prob(10)) + to_chat(target, span_revennotice("You feel as if you are being watched.")) + return FALSE + + log_combat(src, target, "started to harvest") + face_atom(target) + var/essence_drained = rand(15, 20) + + to_chat(src, span_revennotice("You search for the soul of [target].")) + + if(!do_after(src, (rand(10, 20) DECISECONDS), target, timed_action_flags = IGNORE_HELD_ITEM)) //did they get deleted in that second? + return FALSE + + var/target_has_client = !isnull(target.client) + if(target_has_client || target.ckey) // any target that has been occupied with a ckey is considered "intelligent" + to_chat(src, span_revennotice("[target_Their] soul burns with intelligence.")) + essence_drained += rand(20, 30) + + if(target.stat != DEAD && !HAS_TRAIT(target, TRAIT_WEAK_SOUL)) + to_chat(src, span_revennotice("[target_Their] soul blazes with life!")) + essence_drained += rand(40, 50) + + if(!target_has_client && HAS_TRAIT(target, TRAIT_WEAK_SOUL)) + to_chat(src, span_revennotice("[target_Their] soul is weak and underdeveloped. They won't be worth very much.")) + essence_drained = 5 + + to_chat(src, span_revennotice("[target_Their] soul is weak and faltering. It's time to harvest.")) + + if(!do_after(src, (rand(15, 20) DECISECONDS), target, timed_action_flags = IGNORE_HELD_ITEM)) + to_chat(src, span_revennotice("The harvest is abandoned.")) + return FALSE + + switch(essence_drained) + if(1 to 30) + to_chat(src, span_revennotice("[target] will not yield much essence. Still, every bit counts.")) + if(30 to 70) + to_chat(src, span_revennotice("[target] will yield an average amount of essence.")) + if(70 to 90) + to_chat(src, span_revenboldnotice("Such a feast! [target] will yield much essence to you.")) + if(90 to INFINITY) + to_chat(src, span_revenbignotice("Ah, the perfect soul. [target] will yield massive amounts of essence to you.")) + + if(!do_after(src, (rand(15, 25) DECISECONDS), target, timed_action_flags = IGNORE_HELD_ITEM)) //how about now + to_chat(src, span_revenwarning("You are not close enough to siphon [target ? "[target]'s" : "[target_their]"] soul. The link has been broken.")) + return FALSE + + if(target.stat == CONSCIOUS) + to_chat(src, span_revenwarning("[target_Theyre] now powerful enough to fight off your draining!")) + to_chat(target, span_boldannounce("You feel something tugging across your body before subsiding.")) //hey, wait a minute... + return FALSE + + to_chat(src, span_revenminor("You begin siphoning essence from [target]'s soul.")) + if(target.stat != DEAD) + to_chat(target, span_warning("You feel a horribly unpleasant draining sensation as your grip on life weakens...")) + if(target.stat == SOFT_CRIT) + target.Stun(46) + + apply_status_effect(/datum/status_effect/revenant/revealed, 5 SECONDS) + apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, 5 SECONDS) + + target.visible_message(span_warning("[target] suddenly rises slightly into the air, [target_their] skin turning an ashy gray.")) + + if(target.can_block_magic(MAGIC_RESISTANCE_HOLY)) + to_chat(src, span_revenminor("Something's wrong! [target] seems to be resisting the siphoning, leaving you vulnerable!")) + target.visible_message( + span_warning("[target] slumps onto the ground."), + span_revenwarning("Violet lights, dancing in your vision, receding--"), + ) + return FALSE + + var/datum/beam/draining_beam = Beam(target, icon_state = "drain_life") + if(!do_after(src, 4.6 SECONDS, target, timed_action_flags = (IGNORE_HELD_ITEM | IGNORE_INCAPACITATED))) //As one cannot prove the existance of ghosts, ghosts cannot prove the existance of the target they were draining. + to_chat(src, span_revenwarning("[target ? "[target]'s soul has" : "[target_They_have]"] been drawn out of your grasp. The link has been broken.")) + if(target) + target.visible_message( + span_warning("[target] slumps onto the ground."), + span_revenwarning("Violet lights, dancing in your vision, receding--"), + ) + qdel(draining_beam) + return FALSE + + change_essence_amount(essence_drained, FALSE, target) + + if(essence_drained <= 90 && target.stat != DEAD && !HAS_TRAIT(target, TRAIT_WEAK_SOUL)) + max_essence += 5 + to_chat(src, span_revenboldnotice("The absorption of [target]'s living soul has increased your maximum essence level. Your new maximum essence is [max_essence].")) + + if(essence_drained > 90) + max_essence += 15 + perfectsouls++ + to_chat(src, span_revenboldnotice("The perfection of [target]'s soul has increased your maximum essence level. Your new maximum essence is [max_essence].")) + + to_chat(src, span_revennotice("[target]'s soul has been considerably weakened and will yield no more essence for the time being.")) + target.visible_message( + span_warning("[target] slumps onto the ground."), + span_revenwarning("Violet lights, dancing in your vision, getting clo--"), + ) + + LAZYADD(drained_mobs, REF(target)) + if(target.stat != DEAD) + target.investigate_log("has died from revenant harvest.", INVESTIGATE_DEATHS) + target.death(FALSE) + + qdel(draining_beam) + return TRUE diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm new file mode 100644 index 0000000000000..a9e17a9b305ff --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm @@ -0,0 +1,106 @@ +//reforming +/obj/item/ectoplasm/revenant + name = "glimmering residue" + desc = "A pile of fine blue dust. Small tendrils of violet mist swirl around it." + icon = 'icons/effects/effects.dmi' + icon_state = "revenantEctoplasm" + w_class = WEIGHT_CLASS_SMALL + /// Are we currently reforming? + var/reforming = TRUE + /// Are we inert (aka distorted such that we can't reform)? + var/inert = FALSE + /// The key of the revenant that we started the reform as + var/old_ckey + /// The revenant we're currently storing + var/mob/living/basic/revenant/revenant + +/obj/item/ectoplasm/revenant/Initialize(mapload) + . = ..() + addtimer(CALLBACK(src, PROC_REF(try_reform)), 1 MINUTES) + +/obj/item/ectoplasm/revenant/Destroy() + if(!QDELETED(revenant)) + qdel(revenant) + return ..() + +/obj/item/ectoplasm/revenant/attack_self(mob/user) + if(!reforming || inert) + return ..() + user.visible_message( + span_notice("[user] scatters [src] in all directions."), + span_notice("You scatter [src] across the area. The particles slowly fade away."), + ) + user.dropItemToGround(src) + qdel(src) + +/obj/item/ectoplasm/revenant/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(inert) + return + visible_message(span_notice("[src] breaks into particles upon impact, which fade away to nothingness.")) + qdel(src) + +/obj/item/ectoplasm/revenant/examine(mob/user) + . = ..() + if(inert) + . += span_revennotice("It seems inert.") + else if(reforming) + . += span_revenwarning("It is shifting and distorted. It would be wise to destroy this.") + +/obj/item/ectoplasm/revenant/suicide_act(mob/living/user) + user.visible_message(span_suicide("[user] is inhaling [src]! It looks like [user.p_theyre()] trying to visit the shadow realm!")) + qdel(src) + return OXYLOSS + +/obj/item/ectoplasm/revenant/proc/try_reform() + if(reforming) + reforming = FALSE + reform() + else + inert = TRUE + visible_message(span_warning("[src] settles down and seems lifeless.")) + +/// Actually moves the revenant out of ourself +/obj/item/ectoplasm/revenant/proc/reform() + if(QDELETED(src) || QDELETED(revenant) || inert) + return + + message_admins("Revenant ectoplasm was left undestroyed for 1 minute and is reforming into a new revenant.") + forceMove(drop_location()) //In case it's in a backpack or someone's hand + + var/user_name = old_ckey + if(isnull(revenant.client)) + var/mob/potential_user = get_new_user() + revenant.key = potential_user.key + user_name = potential_user.ckey + qdel(potential_user) + + message_admins("[user_name] has been [old_ckey == user_name ? "re":""]made into a revenant by reforming ectoplasm.") + revenant.log_message("was [old_ckey == user_name ? "re":""]made as a revenant by reforming ectoplasm.", LOG_GAME) + visible_message(span_revenboldnotice("[src] suddenly rises into the air before fading away.")) + + revenant.death_reset() + revenant = null + qdel(src) + +/// Handles giving the revenant a new client to control it +/obj/item/ectoplasm/revenant/proc/get_new_user() + message_admins("The new revenant's old client either could not be found or is in a new, living mob - grabbing a random candidate instead...") + var/list/candidates = poll_candidates_for_mob("Do you want to be [revenant.name] (reforming)?", ROLE_REVENANT, ROLE_REVENANT, 5 SECONDS, revenant) + + if(!LAZYLEN(candidates)) + message_admins("No candidates were found for the new revenant.") + inert = TRUE + visible_message(span_revenwarning("[src] settles down and seems lifeless.")) + qdel(revenant) + return null + + var/mob/dead/observer/potential_client = pick(candidates) + if(isnull(potential_client)) + qdel(revenant) + message_admins("No candidate was found for the new revenant. Oh well!") + inert = TRUE + visible_message(span_revenwarning("[src] settles down and seems lifeless.")) + return null + + return potential_client diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_objectives.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_objectives.dm new file mode 100644 index 0000000000000..7dd391c17e477 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_objectives.dm @@ -0,0 +1,37 @@ +/datum/objective/revenant + +/datum/objective/revenant/New() + target_amount = rand(350, 600) + explanation_text = "Absorb [target_amount] points of essence from humans." + return ..() + +/datum/objective/revenant/check_completion() + if(!isrevenant(owner.current)) + return FALSE + var/mob/living/basic/revenant/owner_mob = owner.current + if(QDELETED(owner_mob) || owner_mob.stat == DEAD) + return FALSE + var/essence_stolen = owner_mob.essence_accumulated + return essence_stolen >= target_amount + +/datum/objective/revenant_fluff + +/datum/objective/revenant_fluff/New() + var/list/explanation_texts = list( + "Assist and exacerbate existing threats at critical moments.", + "Cause as much chaos and anger as you can without being killed.", + "Damage and render as much of the station rusted and unusable as possible.", + "Disable and cause malfunctions in as many machines as possible.", + "Ensure that any holy weapons are rendered unusable.", + "Heed and obey the requests of the dead, provided that carrying them out wouldn't be too inconvenient or self-destructive.", + "Impersonate or be worshipped as a God.", + "Make the captain as miserable as possible.", + "Make the clown as miserable as possible.", + "Make the crew as miserable as possible.", + "Prevent the use of energy weapons where possible.", + ) + explanation_text = pick(explanation_texts) + return ..() + +/datum/objective/revenant_fluff/check_completion() + return TRUE diff --git a/code/modules/mob/living/basic/space_fauna/spaceman.dm b/code/modules/mob/living/basic/space_fauna/spaceman.dm index 0ad2c4cfb8ce4..5851bfa531b37 100644 --- a/code/modules/mob/living/basic/space_fauna/spaceman.dm +++ b/code/modules/mob/living/basic/space_fauna/spaceman.dm @@ -32,7 +32,7 @@ /datum/ai_controller/basic_controller/spaceman blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction, + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, ) ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm index f998b7b53cd90..7dd61b72070df 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm @@ -30,7 +30,7 @@ /// Used by Araneus, who only attacks those who attack first. He is house-trained and will not web up the HoS office. /datum/ai_controller/basic_controller/giant_spider/retaliate blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, ) planning_subtrees = list( @@ -45,7 +45,6 @@ blackboard = list( BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/of_size/ours_or_smaller(), // Hunt mobs our size BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic/of_size/larger(), // Run away from mobs bigger than we are - BB_BASIC_MOB_FLEEING = TRUE, ) idle_behavior = /datum/idle_behavior/idle_random_walk 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 index 6df2eb427f411..8cb7d8398bf36 100644 --- 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 @@ -478,55 +478,6 @@ 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) * diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider.dm b/code/modules/mob/living/basic/space_fauna/spider/spider.dm index 4bd773f6d0abb..53b48129e2ed4 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spider.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spider.dm @@ -49,7 +49,7 @@ /mob/living/basic/spider/Initialize(mapload) . = ..() - ADD_TRAIT(src, TRAIT_WEB_SURFER, INNATE_TRAIT) + add_traits(list(TRAIT_WEB_SURFER, TRAIT_FENCE_CLIMBER), 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!") diff --git a/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm index 7108983c31051..c949b438683cb 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm @@ -61,7 +61,6 @@ /datum/ai_controller/basic_controller/spiderling blackboard = list( BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic/of_size/larger, // Run away from mobs bigger than we are - BB_BASIC_MOB_FLEEING = TRUE, BB_VENTCRAWL_COOLDOWN = 20 SECONDS, // enough time to get splatted while we're out in the open. BB_TIME_TO_GIVE_UP_ON_VENT_PATHING = 30 SECONDS, ) diff --git a/code/modules/mob/living/basic/space_fauna/statue/statue.dm b/code/modules/mob/living/basic/space_fauna/statue/statue.dm index bce35146ecfc6..d2ea5e8a831d0 100644 --- a/code/modules/mob/living/basic/space_fauna/statue/statue.dm +++ b/code/modules/mob/living/basic/space_fauna/statue/statue.dm @@ -162,7 +162,7 @@ maxHealth = 5000 melee_damage_lower = 65 melee_damage_upper = 65 - faction = list("statue","mining") + faction = list(FACTION_STATUE,FACTION_MINING) /mob/living/basic/statue/frosty/Initialize(mapload) . = ..() diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm index d459648892b01..a9e2b538bdd74 100644 --- a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm +++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm @@ -65,7 +65,7 @@ fugu.melee_damage_upper = 20 fugu.status_flags |= GODMODE fugu.obj_damage = 60 - fugu.ai_controller.set_blackboard_key(BB_BASIC_MOB_FLEEING, FALSE) + fugu.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) fugu.ai_controller.CancelActions() /datum/status_effect/inflated/on_remove() @@ -84,7 +84,7 @@ if (fugu.stat != DEAD) fugu.icon_state = "Fugu0" fugu.obj_damage = 0 - fugu.ai_controller.set_blackboard_key(BB_BASIC_MOB_FLEEING, TRUE) + fugu.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) fugu.ai_controller.CancelActions() /// Remove status effect if we die diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm index 9d3a09c5348f2..e405ee3755abf 100644 --- a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm @@ -2,7 +2,6 @@ /datum/ai_controller/basic_controller/wumborian_fugu blackboard = list( BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), - BB_BASIC_MOB_FLEEING = TRUE, ) ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/vermin/crab.dm b/code/modules/mob/living/basic/vermin/crab.dm index abe5c25117b6d..bb81fd29c4d50 100644 --- a/code/modules/mob/living/basic/vermin/crab.dm +++ b/code/modules/mob/living/basic/vermin/crab.dm @@ -77,9 +77,9 @@ ///The basic ai controller for crabs /datum/ai_controller/basic_controller/crab blackboard = list( - BB_BASIC_MOB_FLEEING = FALSE, + BB_ALWAYS_IGNORE_FACTION = TRUE, BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/of_size/ours_or_smaller, - BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction, + BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED diff --git a/code/modules/mob/living/basic/vermin/mouse.dm b/code/modules/mob/living/basic/vermin/mouse.dm index 46e175c5323bb..87d549ac5234e 100644 --- a/code/modules/mob/living/basic/vermin/mouse.dm +++ b/code/modules/mob/living/basic/vermin/mouse.dm @@ -376,8 +376,7 @@ /// The mouse AI controller /datum/ai_controller/basic_controller/mouse - blackboard = list( - BB_BASIC_MOB_FLEEING = TRUE, // Always cowardly + blackboard = list( // Always cowardly BB_CURRENT_HUNTING_TARGET = null, // cheese BB_LOW_PRIORITY_HUNTING_TARGET = null, // cable BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), // Use this to find people to run away from diff --git a/code/modules/mob/living/basic/vermin/space_bat.dm b/code/modules/mob/living/basic/vermin/space_bat.dm index 3ce1b41173dd5..232febf0f97d2 100644 --- a/code/modules/mob/living/basic/vermin/space_bat.dm +++ b/code/modules/mob/living/basic/vermin/space_bat.dm @@ -42,7 +42,7 @@ ///Controller for space bats, has nothing unique, just retaliation. /datum/ai_controller/basic_controller/space_bat blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index cbec374449c0d..bc0d2e3d8e81a 100644 --- a/code/modules/mob/living/blood.dm +++ b/code/modules/mob/living/blood.dm @@ -41,11 +41,14 @@ //Effects of bloodloss var/word = pick("dizzy","woozy","faint") switch(blood_volume) - if(BLOOD_VOLUME_EXCESS to BLOOD_VOLUME_MAX_LETHAL) + if(BLOOD_VOLUME_MAX_LETHAL to INFINITY) if(SPT_PROB(7.5, seconds_per_tick)) to_chat(src, span_userdanger("Blood starts to tear your skin apart. You're going to burst!")) investigate_log("has been gibbed by having too much blood.", INVESTIGATE_DEATHS) inflate_gib() + if(BLOOD_VOLUME_EXCESS to BLOOD_VOLUME_MAX_LETHAL) + if(SPT_PROB(5, seconds_per_tick)) + to_chat(src, span_warning("You feel your skin swelling.")) if(BLOOD_VOLUME_MAXIMUM to BLOOD_VOLUME_EXCESS) if(SPT_PROB(5, seconds_per_tick)) to_chat(src, span_warning("You feel terribly bloated.")) diff --git a/code/modules/mob/living/carbon/alien/adult/adult_update_icons.dm b/code/modules/mob/living/carbon/alien/adult/adult_update_icons.dm index 8c2111511236b..bbfd68f8186b8 100644 --- a/code/modules/mob/living/carbon/alien/adult/adult_update_icons.dm +++ b/code/modules/mob/living/carbon/alien/adult/adult_update_icons.dm @@ -76,7 +76,7 @@ //Royals have bigger sprites, so inhand things must be handled differently. /mob/living/carbon/alien/adult/royal/update_held_items() - ..() + . = ..() remove_overlay(HANDS_LAYER) var/list/hands = list() diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 199590e3dc637..eb72b273453a4 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -10,6 +10,7 @@ COMSIG_CARBON_DISARM_COLLIDE = PROC_REF(disarm_collision), ) AddElement(/datum/element/connect_loc, loc_connections) + ADD_TRAIT(src, TRAIT_CAN_HOLD_ITEMS, INNATE_TRAIT) // Carbons are assumed to be innately capable of having arms, we check their arms count instead /mob/living/carbon/Destroy() //This must be done first, so the mob ghosts correctly before DNA etc is nulled @@ -27,45 +28,6 @@ QDEL_NULL(dna) GLOB.carbon_list -= src -/mob/living/carbon/perform_hand_swap(held_index) - . = ..() - if(!.) - return - - if(!held_index) - held_index = (active_hand_index % held_items.len)+1 - - if(!isnum(held_index)) - CRASH("You passed [held_index] into swap_hand instead of a number. WTF man") - - var/oindex = active_hand_index - active_hand_index = held_index - if(hud_used) - var/atom/movable/screen/inventory/hand/H - H = hud_used.hand_slots["[oindex]"] - if(H) - H.update_appearance() - H = hud_used.hand_slots["[held_index]"] - if(H) - H.update_appearance() - - -/mob/living/carbon/activate_hand(selhand) //l/r OR 1-held_items.len - if(!selhand) - selhand = (active_hand_index % held_items.len)+1 - - if(istext(selhand)) - selhand = lowertext(selhand) - if(selhand == "right" || selhand == "r") - selhand = 2 - if(selhand == "left" || selhand == "l") - selhand = 1 - - if(selhand != active_hand_index) - swap_hand(selhand) - else - mode() // Activate held item - /mob/living/carbon/attackby(obj/item/item, mob/living/user, params) if(!all_wounds || !(!user.combat_mode || user == src)) return ..() diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm index cdd6900c22b6e..a8c008bc41d84 100644 --- a/code/modules/mob/living/carbon/carbon_update_icons.dm +++ b/code/modules/mob/living/carbon/carbon_update_icons.dm @@ -282,11 +282,17 @@ update_body() /mob/living/carbon/update_held_items() + . = ..() remove_overlay(HANDS_LAYER) if (handcuffed) drop_all_held_items() return + overlays_standing[HANDS_LAYER] = get_held_overlays() + apply_overlay(HANDS_LAYER) + +/// Generate held item overlays +/mob/living/carbon/proc/get_held_overlays() var/list/hands = list() for(var/obj/item/I in held_items) if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) @@ -307,9 +313,7 @@ icon_file = I.righthand_file hands += I.build_worn_icon(default_layer = HANDS_LAYER, default_icon_file = icon_file, isinhands = TRUE) - - overlays_standing[HANDS_LAYER] = hands - apply_overlay(HANDS_LAYER) + return hands /mob/living/carbon/update_fire_overlay(stacks, on_fire, last_icon_state, suffix = "") var/fire_icon = "[dna?.species.fire_overlay || "human"]_[stacks > MOB_BIG_FIRE_STACK_THRESHOLD ? "big_fire" : "small_fire"][suffix]" diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm index d297296a4ec74..040bf76a3db02 100644 --- a/code/modules/mob/living/carbon/damage_procs.dm +++ b/code/modules/mob/living/carbon/damage_procs.dm @@ -56,9 +56,7 @@ return amount /mob/living/carbon/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) - if(!forced && (status_flags & GODMODE)) - return 0 - if(on_damage_adjustment(BRUTE, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_brute_loss(amount, forced, required_bodytype)) return 0 if(amount > 0) . = take_overall_damage(brute = amount, updating_health = updating_health, forced = forced, required_bodytype = required_bodytype) @@ -75,9 +73,7 @@ return adjustBruteLoss(diff, updating_health, forced, required_bodytype) /mob/living/carbon/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) - if(!forced && (status_flags & GODMODE)) - return 0 - if(on_damage_adjustment(BURN, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_fire_loss(amount, forced, required_bodytype)) return 0 if(amount > 0) . = take_overall_damage(burn = amount, updating_health = updating_health, forced = forced, required_bodytype = required_bodytype) @@ -94,11 +90,7 @@ return adjustFireLoss(diff, updating_health, forced, required_bodytype) /mob/living/carbon/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL) - if(!forced && (status_flags & GODMODE)) - return 0 - if(!forced && !(mob_biotypes & required_biotype)) - return 0 - if(on_damage_adjustment(TOX, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_tox_loss(amount, forced, required_biotype)) return 0 if(!forced && HAS_TRAIT(src, TRAIT_TOXINLOVER)) //damage becomes healing and healing becomes damage amount = -amount diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 9d8f7fb5f63fd..5cf1a02a9aabb 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -487,7 +487,7 @@ GLOBAL_LIST_EMPTY(features_by_species) else if(old_species.exotic_bloodtype && !exotic_bloodtype) human_who_gained_species.dna.blood_type = random_blood_type() - //Resets blood if it is excessively high for some reason + //Resets blood if it is excessively high so they don't gib normalize_blood(human_who_gained_species) if(ishuman(human_who_gained_species)) @@ -1117,9 +1117,6 @@ GLOBAL_LIST_EMPTY(features_by_species) // ATTACK PROCS // ////////////////// -/datum/species/proc/spec_updatehealth(mob/living/carbon/human/H) - return - /datum/species/proc/help(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style) if(SEND_SIGNAL(target, COMSIG_CARBON_PRE_HELP, user, attacker_style) & COMPONENT_BLOCK_HELP_ACT) return TRUE @@ -1874,12 +1871,21 @@ GLOBAL_LIST_EMPTY(features_by_species) /datum/species/proc/on_owner_login(mob/living/carbon/human/owner) return +/** + * Gets a description of the species' *physical* attributes. What makes playing as one different. Used in magic mirrors. + * + * Returns a string. + */ + +/datum/species/proc/get_physical_attributes() + return "An unremarkable species." /** * Gets a short description for the specices. Should be relatively succinct. * Used in the preference menu. * * Returns a string. */ + /datum/species/proc/get_species_description() SHOULD_CALL_PARENT(FALSE) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index ba110f26e01c6..a4cab428111e3 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -923,7 +923,6 @@ /mob/living/carbon/human/updatehealth() . = ..() - dna?.species.spec_updatehealth(src) if(HAS_TRAIT(src, TRAIT_IGNOREDAMAGESLOWDOWN)) remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown) remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown_flying) diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm index d49da7e84d658..ccb36715e852e 100644 --- a/code/modules/mob/living/carbon/human/human_update_icons.dm +++ b/code/modules/mob/living/carbon/human/human_update_icons.dm @@ -481,12 +481,7 @@ There are several things that need to be remembered: apply_overlay(LEGCUFF_LAYER) throw_alert("legcuffed", /atom/movable/screen/alert/restrained/legcuffed, new_master = src.legcuffed) -/mob/living/carbon/human/update_held_items() - remove_overlay(HANDS_LAYER) - if (handcuffed) - drop_all_held_items() - return - +/mob/living/carbon/human/get_held_overlays() var/list/hands = list() for(var/obj/item/worn_item in held_items) var/held_index = get_held_index_of_item(worn_item) @@ -515,8 +510,7 @@ There are several things that need to be remembered: held_in_hand?.held_hand_offset?.apply_offset(hand_overlay) hands += hand_overlay - overlays_standing[HANDS_LAYER] = hands - apply_overlay(HANDS_LAYER) + return hands /proc/wear_female_version(t_color, icon, layer, type, greyscale_colors) var/index = "[t_color]-[greyscale_colors]" diff --git a/code/modules/mob/living/carbon/human/species_types/abductors.dm b/code/modules/mob/living/carbon/human/species_types/abductors.dm index 58a46da81a488..349742a15e536 100644 --- a/code/modules/mob/living/carbon/human/species_types/abductors.dm +++ b/code/modules/mob/living/carbon/human/species_types/abductors.dm @@ -28,6 +28,11 @@ BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/abductor, ) + +/datum/species/abductor/get_physical_attributes() + return "Abductors do not need to breathe, eat, do not have blood, a heart, stomach, or lungs and cannot be infected by human viruses. \ + Their hardy physique prevents their skin from being wounded or dismembered, but their chunky tridactyl hands make it hard to operate human equipment." + /datum/species/abductor/on_species_gain(mob/living/carbon/C, datum/species/old_species) . = ..() var/datum/atom_hud/abductor_hud = GLOB.huds[DATA_HUD_ABDUCTOR] diff --git a/code/modules/mob/living/carbon/human/species_types/android.dm b/code/modules/mob/living/carbon/human/species_types/android.dm index f17abdfc868ff..570aa91dc342c 100644 --- a/code/modules/mob/living/carbon/human/species_types/android.dm +++ b/code/modules/mob/living/carbon/human/species_types/android.dm @@ -50,3 +50,8 @@ . = ..() // Androids don't eat, hunger or metabolise foods. Let's do some cleanup. C.set_safe_hunger_level() + +/datum/species/android/get_physical_attributes() + return "Androids are almost, but not quite, identical to fully augmented humans. \ + Unlike those, though, they're completely immune to toxin damage, don't have blood or organs (besides their head), don't get hungry, and can reattach their limbs! \ + That said, an EMP will devastate them and they cannot process any chemicals." diff --git a/code/modules/mob/living/carbon/human/species_types/dullahan.dm b/code/modules/mob/living/carbon/human/species_types/dullahan.dm index e59aa11375ff8..1599b0a23c015 100644 --- a/code/modules/mob/living/carbon/human/species_types/dullahan.dm +++ b/code/modules/mob/living/carbon/human/species_types/dullahan.dm @@ -6,6 +6,17 @@ TRAIT_NOBREATH, TRAIT_NOHUNGER, TRAIT_USES_SKINTONES, + TRAIT_ADVANCEDTOOLUSER, // Normally applied by brain but we don't have one + TRAIT_LITERATE, + TRAIT_CAN_STRIP, + ) + bodypart_overrides = list( + BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left, + BODY_ZONE_R_ARM = /obj/item/bodypart/arm/right, + BODY_ZONE_HEAD = /obj/item/bodypart/head/dullahan, + BODY_ZONE_L_LEG = /obj/item/bodypart/leg/left, + BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right, + BODY_ZONE_CHEST = /obj/item/bodypart/chest, ) inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID mutant_bodyparts = list("wings" = "None") @@ -20,11 +31,9 @@ /// The dullahan relay that's associated with the owner, used to handle many things such as talking and hearing. var/obj/item/dullahan_relay/my_head - /// Did our owner's first client connection get handled yet? Useful for when some proc needs to be called once we're sure that a client has moved into our owner, like for Dullahans. var/owner_first_client_connection_handled = FALSE - /datum/species/dullahan/check_roundstart_eligible() if(check_holidays(HALLOWEEN)) return TRUE @@ -33,62 +42,59 @@ /datum/species/dullahan/on_species_gain(mob/living/carbon/human/human, datum/species/old_species) . = ..() human.lose_hearing_sensitivity(TRAIT_GENERIC) - var/obj/item/bodypart/head/head = human.get_bodypart(BODY_ZONE_HEAD) - - if(head) - head.drop_limb() + RegisterSignal(human, COMSIG_CARBON_ATTACH_LIMB, PROC_REF(on_gained_part)) - if(!QDELETED(head)) //drop_limb() deletes the limb if no drop location exists and character setup dummies are located in nullspace. - head.throwforce = 25 - my_head = new /obj/item/dullahan_relay(head, human) - human.put_in_hands(head) - head.show_organs_on_examine = FALSE - head.speech_span = null // so we don't look roboty when talking through it + var/obj/item/bodypart/head/head = human.get_bodypart(BODY_ZONE_HEAD) + head?.drop_limb() + if(QDELETED(head)) //drop_limb() deletes the limb if no drop location exists and character setup dummies are located in nullspace. + return + my_head = new /obj/item/dullahan_relay(head, human) + human.put_in_hands(head) + + // We want to give the head some boring old eyes just so it doesn't look too jank on the head sprite. + head.eyes = new /obj/item/organ/internal/eyes(head) + head.eyes.eye_color_left = human.eye_color_left + head.eyes.eye_color_right = human.eye_color_right + human.update_body() + head.update_icon_dropped() + human.set_safe_hunger_level() + RegisterSignal(head, COMSIG_QDELETING, PROC_REF(on_head_destroyed)) - // We want to give the head some boring old eyes just so it doesn't look too jank on the head sprite. - head.eyes = new /obj/item/organ/internal/eyes(head) - head.eyes.eye_color_left = human.eye_color_left - head.eyes.eye_color_right = human.eye_color_right - human.update_body() - head.update_icon_dropped() +/// If we gained a new body part, it had better not be a head +/datum/species/dullahan/proc/on_gained_part(mob/living/carbon/human/dullahan, obj/item/bodypart/part) + SIGNAL_HANDLER + if (part.body_zone != BODY_ZONE_HEAD) + return + my_head = null + dullahan.investigate_log("has been gibbed by having an illegal head put on [dullahan.p_their()] shoulders.", INVESTIGATE_DEATHS) + dullahan.gib(DROP_ALL_REMAINS) // Yeah so giving them a head on their body is really not a good idea, so their original head will remain but uh, good luck fixing it after that. - human.set_safe_hunger_level() +/// If our head is destroyed, so are we +/datum/species/dullahan/proc/on_head_destroyed() + SIGNAL_HANDLER + var/mob/living/human = my_head?.owner + if (QDELETED(human)) + return // guess we already died + my_head = null + human.investigate_log("has been gibbed by the loss of [human.p_their()] head.", INVESTIGATE_DEATHS) + human.gib(DROP_ALL_REMAINS) /datum/species/dullahan/on_species_loss(mob/living/carbon/human/human) . = ..() - if(my_head) var/obj/item/bodypart/head/detached_head = my_head.loc + UnregisterSignal(detached_head, COMSIG_QDELETING) my_head.owner = null QDEL_NULL(my_head) if(detached_head) qdel(detached_head) + UnregisterSignal(human, COMSIG_CARBON_ATTACH_LIMB) human.regenerate_limb(BODY_ZONE_HEAD, FALSE) human.become_hearing_sensitive() prevent_perspective_change = FALSE human.reset_perspective(human) -/datum/species/dullahan/spec_life(mob/living/carbon/human/human, seconds_per_tick, times_fired) - . = ..() - if(QDELETED(my_head)) - my_head = null - human.investigate_log("has been gibbed by the loss of [human.p_their()] head.", INVESTIGATE_DEATHS) - human.gib(DROP_ALL_REMAINS) - return - - if(my_head.loc.name != human.real_name && istype(my_head.loc, /obj/item/bodypart/head)) - var/obj/item/bodypart/head/detached_head = my_head.loc - detached_head.real_name = human.real_name - detached_head.name = human.real_name - detached_head.brain.name = "[human.name]'s brain" - - var/obj/item/bodypart/head/illegal_head = human.get_bodypart(BODY_ZONE_HEAD) - if(illegal_head) - my_head = null - human.investigate_log("has been gibbed by having an illegal head put on [human.p_their()] shoulders.", INVESTIGATE_DEATHS) - human.gib(DROP_ALL_REMAINS) // Yeah so giving them a head on their body is really not a good idea, so their original head will remain but uh, good luck fixing it after that. - /datum/species/dullahan/proc/update_vision_perspective(mob/living/carbon/human/human) var/obj/item/organ/internal/eyes/eyes = human.get_organ_slot(ORGAN_SLOT_EYES) if(eyes) @@ -114,6 +120,8 @@ eyes_toggle_perspective_action?.Trigger() owner_first_client_connection_handled = TRUE +/datum/species/dullahan/get_physical_attributes() + return "A dullahan is much like a human, but their head is detached from their body and must be carried around." /datum/species/dullahan/get_species_description() return "An angry spirit, hanging onto the land of the living for \ @@ -210,11 +218,15 @@ . = ..() if(!new_owner) return INITIALIZE_HINT_QDEL + var/obj/item/bodypart/head/detached_head = loc + if (!istype(detached_head)) + return INITIALIZE_HINT_QDEL owner = new_owner START_PROCESSING(SSobj, src) RegisterSignal(owner, COMSIG_CLICK_SHIFT, PROC_REF(examinate_check)) RegisterSignal(owner, COMSIG_CARBON_REGENERATE_LIMBS, PROC_REF(unlist_head)) RegisterSignal(owner, COMSIG_LIVING_REVIVE, PROC_REF(retrieve_head)) + RegisterSignal(owner, COMSIG_HUMAN_PREFS_APPLIED, PROC_REF(update_prefs_name)) become_hearing_sensitive(ROUNDSTART_TRAIT) /obj/item/dullahan_relay/Destroy() @@ -223,9 +235,20 @@ return ..() /obj/item/dullahan_relay/process() - if(!istype(loc, /obj/item/bodypart/head) || QDELETED(owner)) - . = PROCESS_KILL - qdel(src) + if(istype(loc, /obj/item/bodypart/head) && !QDELETED(owner)) + return + qdel(src) + return PROCESS_KILL + +/// Updates our names after applying name prefs +/obj/item/dullahan_relay/proc/update_prefs_name(mob/living/carbon/human/wearer) + SIGNAL_HANDLER + var/obj/item/bodypart/head/detached_head = loc + if (!istype(detached_head)) + return // It's so over + detached_head.real_name = wearer.real_name + detached_head.name = wearer.real_name + detached_head.brain.name = "[wearer.name]'s brain" /obj/item/dullahan_relay/proc/examinate_check(mob/user, atom/source) SIGNAL_HANDLER diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm index c4e7eaa5889e0..232067041012d 100644 --- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm +++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm @@ -55,24 +55,24 @@ QDEL_NULL(ethereal_light) return ..() -/datum/species/ethereal/on_species_gain(mob/living/carbon/new_ethereal, datum/species/old_species, pref_load) +/datum/species/ethereal/on_species_gain(mob/living/carbon/human/new_ethereal, datum/species/old_species, pref_load) . = ..() if(!ishuman(new_ethereal)) return - var/mob/living/carbon/human/ethereal = new_ethereal - default_color = ethereal.dna.features["ethcolor"] + default_color = new_ethereal.dna.features["ethcolor"] fixed_hair_color = default_color r1 = GETREDPART(default_color) g1 = GETGREENPART(default_color) b1 = GETBLUEPART(default_color) - RegisterSignal(ethereal, COMSIG_ATOM_EMAG_ACT, PROC_REF(on_emag_act)) - RegisterSignal(ethereal, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp_act)) - RegisterSignal(ethereal, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater)) - RegisterSignal(ethereal, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur)) - ethereal_light = ethereal.mob_light(light_type = /obj/effect/dummy/lighting_obj/moblight/species) - spec_updatehealth(ethereal) + RegisterSignal(new_ethereal, COMSIG_ATOM_EMAG_ACT, PROC_REF(on_emag_act)) + RegisterSignal(new_ethereal, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp_act)) + RegisterSignal(new_ethereal, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur)) + RegisterSignal(new_ethereal, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater)) + RegisterSignal(new_ethereal, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(refresh_light_color)) + ethereal_light = new_ethereal.mob_light(light_type = /obj/effect/dummy/lighting_obj/moblight/species) + refresh_light_color(new_ethereal) new_ethereal.set_safe_hunger_level() - update_mail_goodies(ethereal) + update_mail_goodies(new_ethereal) var/obj/item/organ/internal/heart/ethereal/ethereal_heart = new_ethereal.get_organ_slot(ORGAN_SLOT_HEART) ethereal_heart.ethereal_color = default_color @@ -82,10 +82,13 @@ limb.update_limb(is_creating = TRUE) /datum/species/ethereal/on_species_loss(mob/living/carbon/human/former_ethereal, datum/species/new_species, pref_load) - UnregisterSignal(former_ethereal, COMSIG_ATOM_EMAG_ACT) - UnregisterSignal(former_ethereal, COMSIG_ATOM_EMP_ACT) - UnregisterSignal(former_ethereal, COMSIG_LIGHT_EATER_ACT) - UnregisterSignal(former_ethereal, COMSIG_HIT_BY_SABOTEUR) + UnregisterSignal(former_ethereal, list( + COMSIG_ATOM_EMAG_ACT, + COMSIG_ATOM_EMP_ACT, + COMSIG_HIT_BY_SABOTEUR, + COMSIG_LIGHT_EATER_ACT, + COMSIG_LIVING_HEALTH_UPDATE, + )) QDEL_NULL(ethereal_light) return ..() @@ -109,9 +112,9 @@ features["ethcolor"] = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)] return features -/datum/species/ethereal/spec_updatehealth(mob/living/carbon/human/ethereal) - . = ..() - if(!ethereal_light) +/datum/species/ethereal/proc/refresh_light_color(mob/living/carbon/human/ethereal) + SIGNAL_HANDLER + if(isnull(ethereal_light)) return if(default_color != ethereal.dna.features["ethcolor"]) var/new_color = ethereal.dna.features["ethcolor"] @@ -138,39 +141,38 @@ ethereal.set_facial_haircolor(dead_color, override = TRUE, update = FALSE) ethereal.set_haircolor(dead_color, override = TRUE, update = TRUE) -/datum/species/ethereal/proc/on_emp_act(mob/living/carbon/human/H, severity, protection) +/datum/species/ethereal/proc/on_emp_act(mob/living/carbon/human/source, severity, protection) SIGNAL_HANDLER if(protection & EMP_PROTECT_SELF) return EMPeffect = TRUE - spec_updatehealth(H) - to_chat(H, span_notice("You feel the light of your body leave you.")) + refresh_light_color(source) + to_chat(source, span_notice("You feel the light of your body leave you.")) switch(severity) if(EMP_LIGHT) - addtimer(CALLBACK(src, PROC_REF(stop_emp), H), 10 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) //We're out for 10 seconds + addtimer(CALLBACK(src, PROC_REF(stop_emp), source), 10 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) //We're out for 10 seconds if(EMP_HEAVY) - addtimer(CALLBACK(src, PROC_REF(stop_emp), H), 20 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) //We're out for 20 seconds + addtimer(CALLBACK(src, PROC_REF(stop_emp), source), 20 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) //We're out for 20 seconds -/datum/species/ethereal/proc/on_saboteur(datum/source, disrupt_duration) +/datum/species/ethereal/proc/on_saboteur(mob/living/carbon/human/source, disrupt_duration) SIGNAL_HANDLER - var/mob/living/carbon/human/our_target = source EMPeffect = TRUE - spec_updatehealth(our_target) - to_chat(our_target, span_warning("Something inside of you crackles in a bad way.")) - our_target.take_bodypart_damage(burn = 3, wound_bonus = CANT_WOUND) - addtimer(CALLBACK(src, PROC_REF(stop_emp), our_target), disrupt_duration, TIMER_UNIQUE|TIMER_OVERRIDE) + refresh_light_color(source) + to_chat(source, span_warning("Something inside of you crackles in a bad way.")) + source.take_bodypart_damage(burn = 3, wound_bonus = CANT_WOUND) + addtimer(CALLBACK(src, PROC_REF(stop_emp), source), disrupt_duration, TIMER_UNIQUE|TIMER_OVERRIDE) return COMSIG_SABOTEUR_SUCCESS -/datum/species/ethereal/proc/on_emag_act(mob/living/carbon/human/H, mob/user) +/datum/species/ethereal/proc/on_emag_act(mob/living/carbon/human/source, mob/user) SIGNAL_HANDLER if(emageffect) return FALSE emageffect = TRUE if(user) - to_chat(user, span_notice("You tap [H] on the back with your card.")) - H.visible_message(span_danger("[H] starts flickering in an array of colors!")) - handle_emag(H) - addtimer(CALLBACK(src, PROC_REF(stop_emag), H), 2 MINUTES) //Disco mode for 2 minutes! This doesn't affect the ethereal at all besides either annoying some players, or making someone look badass. + to_chat(user, span_notice("You tap [source] on the back with your card.")) + source.visible_message(span_danger("[source] starts flickering in an array of colors!")) + handle_emag(source) + addtimer(CALLBACK(src, PROC_REF(stop_emag), source), 2 MINUTES) //Disco mode for 2 minutes! This doesn't affect the ethereal at all besides either annoying some players, or making someone look badass. return TRUE /// Special handling for getting hit with a light eater @@ -179,23 +181,22 @@ source.emp_act(EMP_LIGHT) return COMPONENT_BLOCK_LIGHT_EATER -/datum/species/ethereal/proc/stop_emp(mob/living/carbon/human/H) +/datum/species/ethereal/proc/stop_emp(mob/living/carbon/human/ethereal) EMPeffect = FALSE - spec_updatehealth(H) - to_chat(H, span_notice("You feel more energized as your shine comes back.")) - + refresh_light_color(ethereal) + to_chat(ethereal, span_notice("You feel more energized as your shine comes back.")) -/datum/species/ethereal/proc/handle_emag(mob/living/carbon/human/H) +/datum/species/ethereal/proc/handle_emag(mob/living/carbon/human/ethereal) if(!emageffect) return current_color = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)] - spec_updatehealth(H) - addtimer(CALLBACK(src, PROC_REF(handle_emag), H), 5) //Call ourselves every 0.5 seconds to change color + refresh_light_color(ethereal) + addtimer(CALLBACK(src, PROC_REF(handle_emag), ethereal), 0.5 SECONDS) -/datum/species/ethereal/proc/stop_emag(mob/living/carbon/human/H) +/datum/species/ethereal/proc/stop_emag(mob/living/carbon/human/ethereal) emageffect = FALSE - spec_updatehealth(H) - H.visible_message(span_danger("[H] stops flickering and goes back to their normal state!")) + refresh_light_color(ethereal) + ethereal.visible_message(span_danger("[ethereal] stops flickering and goes back to their normal state!")) /datum/species/ethereal/get_features() var/list/features = ..() @@ -211,6 +212,11 @@ 'sound/voice/ethereal/ethereal_scream_3.ogg', ) +/datum/species/ethereal/get_physical_attributes() + return "Ethereals process electricity as their power supply, not food, and are somewhat resistant to it.\ + They do so via their crystal core, their equivalent of a human heart, which will also encase them in a reviving crystal if they die.\ + However, their skin is very thin and easy to pierce with brute weaponry." + /datum/species/ethereal/get_species_description() return "Coming from the planet of Sprout, the theocratic ethereals are \ separated socially by caste, and espouse a dogma of aiding the weak and \ @@ -274,7 +280,7 @@ TRAIT_FIXED_MUTANT_COLORS, TRAIT_FIXED_HAIRCOLOR, TRAIT_AGENDER, - TRAIT_TENACIOUS, + TRAIT_TENACIOUS, // this doesn't work. tenacity is an element TRAIT_NOBREATH, TRAIT_RESISTHIGHPRESSURE, TRAIT_RESISTLOWPRESSURE, @@ -289,6 +295,10 @@ BODY_ZONE_CHEST = /obj/item/bodypart/chest/ethereal, ) +/datum/species/ethereal/lustrous/get_physical_attributes() + return "Lustrous are what remains of an Ethereal after freebasing esoteric drugs. \ + They are pressure immune, virus immune, can see bluespace tears in reality, and have a really weird scream. They remain vulnerable to physical damage." + /datum/species/ethereal/lustrous/get_scream_sound(mob/living/carbon/human/ethereal) return pick( 'sound/voice/ethereal/lustrous_scream_1.ogg', diff --git a/code/modules/mob/living/carbon/human/species_types/felinid.dm b/code/modules/mob/living/carbon/human/species_types/felinid.dm index 4114d6810dd9b..a6a229d0b2c46 100644 --- a/code/modules/mob/living/carbon/human/species_types/felinid.dm +++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm @@ -138,6 +138,10 @@ cat_ears.color = human_for_preview.hair_color human_for_preview.update_body() +/datum/species/human/felinid/get_physical_attributes() + return "Felinids are very similar to humans in almost all respects, with their biggest differences being the ability to lick their wounds, \ + and an increased sensitivity to noise, which is often detrimental. They are also rather fond of eating oranges." + /datum/species/human/felinid/get_species_description() return "Felinids are one of the many types of bespoke genetic \ modifications to come of humanity's mastery of genetic science, and are \ diff --git a/code/modules/mob/living/carbon/human/species_types/flypeople.dm b/code/modules/mob/living/carbon/human/species_types/flypeople.dm index 7f1d111211569..c36252bbcb2dc 100644 --- a/code/modules/mob/living/carbon/human/species_types/flypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/flypeople.dm @@ -37,6 +37,9 @@ return 30 //Flyswatters deal 30x damage to flypeople. return 1 +/datum/species/fly/get_physical_attributes() + return "These hideous creatures suffer from pesticide immensely, eat waste, and are incredibly vulnerable to bright lights. They do have wings though." + /datum/species/fly/get_species_description() return "With no official documentation or knowledge of the origin of \ this species, they remain a mystery to most. Any and all rumours among \ diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm index 9df07a8e49635..19427def4d2ed 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -56,6 +56,10 @@ name += " [pick(GLOB.last_names)]" return name +/datum/species/golem/get_physical_attributes() + return "Golems are hardy creatures made out of stone, which are thus naturally resistant to many dangers, including asphyxiation, fire, radiation, electricity, and viruses.\ + They gain special abilities depending on the type of material consumed, but they need to consume material to keep their body animated." + /datum/species/golem/create_pref_unique_perks() var/list/to_add = list() diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index 30c374033fca4..bd662d63ad142 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -192,6 +192,11 @@ BODY_ZONE_CHEST = /obj/item/bodypart/chest/slime, ) +/datum/species/jelly/slime/get_physical_attributes() + return "Slimepeople have jelly for blood and their vacuoles can extremely quickly convert plasma to it if they're breathing it in.\ + They can then use the excess blood to split off an excess body, which their consciousness can transfer to at will or on death.\ + Most things that are toxic heal them, but most things that prevent toxicity damage them!" + /datum/species/jelly/slime/on_species_loss(mob/living/carbon/C) if(slime_split) slime_split.Remove(C) @@ -492,6 +497,10 @@ /// The cooldown of us using exteracts COOLDOWN_DECLARE(extract_cooldown) +/datum/species/jelly/luminescent/get_physical_attributes() + return "Luminescent are able to integrate slime extracts into themselves for wondrous effects. \ + Most things that are toxic heal them, but most things that prevent toxicity damage them!" + //Species datums don't normally implement destroy, but JELLIES SUCK ASS OUT OF A STEEL STRAW and have to i guess /datum/species/jelly/luminescent/Destroy(force) current_extract = null @@ -661,6 +670,10 @@ /// Special "project thought" telepathy action for stargazers. var/datum/action/innate/project_thought/project_action +/datum/species/jelly/stargazer/get_physical_attributes() + return "Stargazers can link others' minds with their own, creating a private communication channel. \ + Most things that are toxic heal them, but most things that prevent toxicity damage them!" + /datum/species/jelly/stargazer/on_species_gain(mob/living/carbon/grant_to, datum/species/old_species) . = ..() project_action = new(src) diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index d94d3390b866e..e02813826b13a 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -87,6 +87,10 @@ 'sound/voice/lizard/lizard_scream_3.ogg', ) +/datum/species/lizard/get_physical_attributes() + return "Lizardpeople can withstand slightly higher temperatures than most species, but they are very vulnerable to the cold \ + and can't regulate their body-temperature internally, making the vacuum of space extremely deadly to them." + /datum/species/lizard/get_species_description() return "The militaristic Lizardpeople hail originally from Tizira, but have grown \ throughout their centuries in the stars to possess a large spacefaring \ @@ -153,6 +157,10 @@ Lizard subspecies: ASHWALKERS BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/lizard, ) +/datum/species/lizard/get_physical_attributes() + return "Ash Walkers are identical to lizardpeople in almost all aspects. \ + Unlike them, they're always digitigrade, they can breathe Lavaland's often noxious atmosphere and resist viruses. They are usually illiterate." + /* Lizard subspecies: SILVER SCALED */ @@ -182,6 +190,11 @@ Lizard subspecies: SILVER SCALED ///See above var/old_eye_color_right +/datum/species/lizard/silverscale/get_physical_attributes() + return "Silver Scales are to lizardpeople what angels are to humans. \ + Mostly identical, they are holy, don't breathe, don't get viruses, their hide cannot be pierced, love the taste of wine, \ + and their tongue allows them to turn into a statue, for some reason." + /datum/species/lizard/silverscale/on_species_gain(mob/living/carbon/human/new_silverscale, datum/species/old_species, pref_load) old_mutcolor = new_silverscale.dna.features["mcolor"] old_eye_color_left = new_silverscale.eye_color_left diff --git a/code/modules/mob/living/carbon/human/species_types/monkeys.dm b/code/modules/mob/living/carbon/human/species_types/monkeys.dm index 4d39ece432971..f8aee4d83b17c 100644 --- a/code/modules/mob/living/carbon/human/species_types/monkeys.dm +++ b/code/modules/mob/living/carbon/human/species_types/monkeys.dm @@ -133,6 +133,10 @@ 'sound/creatures/monkey/monkey_screech_7.ogg', ) +/datum/species/monkey/get_physical_attributes() + return "Monkeys are slippery, can crawl into vents, and are more dextrous than humans.. but only when stealing things. \ + Natural monkeys cannot operate machinery or most tools with their paws, but unusually clever monkeys or those that were once something else can." + /datum/species/monkey/get_species_description() return "Monkeys are a type of primate that exist between humans and animals on the evolutionary chain. \ Every year, on Monkey Day, Nanotrasen shows their respect for the little guys by allowing them to roam the station freely." diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm index 0447b1f18695d..a337b8cee0578 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -60,6 +60,10 @@ /datum/species/moth/get_scream_sound(mob/living/carbon/human/human) return 'sound/voice/moth/scream_moth.ogg' +/datum/species/moth/get_physical_attributes() + return "Moths have large and fluffy wings, which help them navigate the station if gravity is offline by pushing the air around them. \ + Due to that, it isn't of much use out in space. Their eyes are very sensitive." + /datum/species/moth/get_species_description() return "Hailing from a planet that was lost long ago, the moths travel \ the galaxy as a nomadic people aboard a colossal fleet of ships, seeking a new homeland." diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm index 5263101680b88..9facc517c1c79 100644 --- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm +++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm @@ -144,6 +144,10 @@ 'sound/voice/plasmaman/plasmeme_scream_3.ogg', ) +/datum/species/plasmaman/get_physical_attributes() + return "Plasmamen literally breathe and live plasma. They spontaneously combust on contact with oxygen, and besides all the quirks that go with that, \ + they're very vulnerable to all kinds of physical damage due to their brittle structure." + /datum/species/plasmaman/get_species_description() return "Found on the Icemoon of Freyja, plasmamen consist of colonial \ fungal organisms which together form a sentient being. In human space, \ diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm index cd0a0c99449f6..0190996567d13 100644 --- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm @@ -74,6 +74,10 @@ if(chem.type == /datum/reagent/toxin/plantbgone) affected.adjustToxLoss(3 * REM * seconds_per_tick) +/datum/species/pod/get_physical_attributes() + return "Podpeople are in many ways the inverse of shadows, healing in light and starving with the dark. \ + Their bodies are like tinder and easy to char." + /datum/species/pod/create_pref_unique_perks() var/list/to_add = list() diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm index 687e304938135..c628d5ffef688 100644 --- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm @@ -37,6 +37,9 @@ return TRUE return ..() +/datum/species/shadow/get_physical_attributes() + return "These cursed creatures heal in the dark, but suffer in the light much more heavily. Their eyes let them see in the dark as though it were day." + /datum/species/shadow/get_species_description() return "Victims of a long extinct space alien. Their flesh is a sickly \ seethrough filament, their tangled insides in clear view. Their form \ diff --git a/code/modules/mob/living/carbon/human/species_types/skeletons.dm b/code/modules/mob/living/carbon/human/species_types/skeletons.dm index db753732fdcd6..9e2d59d03007b 100644 --- a/code/modules/mob/living/carbon/human/species_types/skeletons.dm +++ b/code/modules/mob/living/carbon/human/species_types/skeletons.dm @@ -55,6 +55,11 @@ return TRUE return ..() +/datum/species/skeleton/get_physical_attributes() + return "These humerus folk lack any fleshy biology, which allows them to resist pressure, temperature, radiation, asphyxiation and even toxins. \ + However, due to that same fact, it is quite hard to heal them as well. The calcium found in common space milk is highly effective at treating their wounds. \ + Their limbs are easy to pop off their joints, but they can somehow just slot them back in." + /datum/species/skeleton/get_species_description() return "A rattling skeleton! They descend upon Space Station 13 \ Every year to spook the crew! \"I've got a BONE to pick with you!\"" diff --git a/code/modules/mob/living/carbon/human/species_types/snail.dm b/code/modules/mob/living/carbon/human/species_types/snail.dm index 41a6166812865..32fc0161ea21f 100644 --- a/code/modules/mob/living/carbon/human/species_types/snail.dm +++ b/code/modules/mob/living/carbon/human/species_types/snail.dm @@ -25,6 +25,11 @@ BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/snail ) + +/datum/species/snail/get_physical_attributes() + return "Snailpeople emit a viscous, slippery ooze when crawling along the ground, which they are somewhat faster at than other species. \ + They are almost purely made of water, making them extremely susceptible to shocks, and salt will scour them heavily." + /datum/species/snail/handle_chemical(datum/reagent/chem, mob/living/carbon/human/affected, seconds_per_tick, times_fired) . = ..() if(. & COMSIG_MOB_STOP_REAGENT_CHECK) diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm index 46d507e4999de..c07f0478c03ea 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -69,6 +69,10 @@ return 2 //Whips deal 2x damage to vampires. Vampire killer. return 1 +/datum/species/vampire/get_physical_attributes() + return "Vampires are afflicted with the Thirst, needing to sate it by draining the blood out of another living creature. However, they do not need to breathe or eat normally. \ + They will instantly turn into dust if they run out of blood or enter a holy area. However, coffins stabilize and heal them, and they can transform into bats!" + /datum/species/vampire/get_species_description() return "A classy Vampire! They descend upon Space Station Thirteen Every year to spook the crew! \"Bleeg!!\"" diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm index 28ebe93bd7b08..d07badff51ef2 100644 --- a/code/modules/mob/living/carbon/human/species_types/zombies.dm +++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm @@ -66,6 +66,10 @@ return TRUE return ..() +/datum/species/zombie/get_physical_attributes() + return "Zombies are undead, and thus completely immune to any enviromental hazard, or any physical threat besides blunt force trauma and burns. \ + Their limbs are easy to pop off their joints, but they can somehow just slot them back in." + /datum/species/zombie/get_species_description() return "A rotting zombie! They descend upon Space Station Thirteen Every year to spook the crew! \"Sincerely, the Zombies!\"" @@ -197,7 +201,7 @@ // Your skin falls off /datum/species/human/krokodil_addict - name = "\improper Human" + name = "\improper Krokodil Human" id = SPECIES_ZOMBIE_KROKODIL examine_limb_id = SPECIES_HUMAN changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 355f5bf61ca1c..f3b03cdab35e0 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -458,7 +458,8 @@ return for(var/obj/item/organ/internal/organ in organs) // On-death is where organ decay is handled - organ?.on_death(seconds_per_tick, times_fired) // organ can be null due to reagent metabolization causing organ shuffling + if(organ?.owner) // organ + owner can be null due to reagent metabolization causing organ shuffling + organ.on_death(seconds_per_tick, times_fired) // We need to re-check the stat every organ, as one of our others may have revived us if(stat != DEAD) break @@ -688,7 +689,7 @@ var/datum/reagent/bits = bile if(istype(bits, /datum/reagent/consumable)) var/datum/reagent/consumable/goodbit = bile - fullness += goodbit.get_nutriment_factor() * goodbit.volume / goodbit.metabolization_rate + fullness += goodbit.get_nutriment_factor(src) * goodbit.volume / goodbit.metabolization_rate continue fullness += 0.6 * bits.volume / bits.metabolization_rate //not food takes up space diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index 82425b27a5ba9..45a35bb6e158b 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -159,23 +159,24 @@ return TRUE -/// Should be called by any adjustXLoss proc to send signalling information, returns a bit flag which may indicate that we don't want to make any adjustment -/mob/living/proc/on_damage_adjustment(damage_type, amount, forced) - return SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_DAMAGE, damage_type, amount, forced) - /mob/living/proc/getBruteLoss() return bruteloss -/mob/living/proc/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype = ALL) +/mob/living/proc/can_adjust_brute_loss(amount, forced, required_bodytype) if(!forced && (status_flags & GODMODE)) - return 0 - if(on_damage_adjustment(BRUTE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return FALSE + if(SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_BRUTE_DAMAGE, BRUTE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return FALSE + return TRUE + +/mob/living/proc/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype = ALL) + if (!can_adjust_brute_loss(amount, forced, required_bodytype)) return 0 . = bruteloss bruteloss = clamp((bruteloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) . -= bruteloss if(!.) // no change, no need to update - return FALSE + return 0 if(updating_health) updatehealth() @@ -195,19 +196,24 @@ /mob/living/proc/getOxyLoss() return oxyloss -/mob/living/proc/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL, required_respiration_type = ALL) +/mob/living/proc/can_adjust_oxy_loss(amount, forced, required_biotype, required_respiration_type) if(!forced) if(status_flags & GODMODE) - return 0 + return FALSE + if (required_respiration_type) + var/obj/item/organ/internal/lungs/affected_lungs = get_organ_slot(ORGAN_SLOT_LUNGS) + if(isnull(affected_lungs)) + if(!(mob_respiration_type & required_respiration_type)) // if the mob has no lungs, use mob_respiration_type + return FALSE + else + if(!(affected_lungs.respiration_type & required_respiration_type)) // otherwise use the lungs' respiration_type + return FALSE + if(SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_OXY_DAMAGE, OXY, amount, forced) & COMPONENT_IGNORE_CHANGE) + return FALSE + return TRUE - var/obj/item/organ/internal/lungs/affected_lungs = get_organ_slot(ORGAN_SLOT_LUNGS) - if(isnull(affected_lungs)) - if(!(mob_respiration_type & required_respiration_type)) // if the mob has no lungs, use mob_respiration_type - return 0 - else - if(!(affected_lungs.respiration_type & required_respiration_type)) // otherwise use the lungs' respiration_type - return 0 - if(on_damage_adjustment(OXY, amount, forced) & COMPONENT_IGNORE_CHANGE) +/mob/living/proc/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL, required_respiration_type = ALL) + if(!can_adjust_oxy_loss(amount, forced, required_biotype, required_respiration_type)) return 0 . = oxyloss oxyloss = clamp((oxyloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) @@ -240,13 +246,16 @@ /mob/living/proc/getToxLoss() return toxloss -/mob/living/proc/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL) - if(!forced && (status_flags & GODMODE)) +/mob/living/proc/can_adjust_tox_loss(amount, forced, required_biotype) + if(!forced && ((status_flags & GODMODE) || !(mob_biotypes & required_biotype))) return FALSE - if(on_damage_adjustment(TOX, amount, forced) & COMPONENT_IGNORE_CHANGE) - return 0 - if(!forced && !(mob_biotypes & required_biotype)) + if(SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_TOX_DAMAGE, TOX, amount, forced) & COMPONENT_IGNORE_CHANGE) return FALSE + return TRUE + +/mob/living/proc/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL) + if(!can_adjust_tox_loss(amount, forced, required_biotype)) + return 0 . = toxloss toxloss = clamp((toxloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) . -= toxloss @@ -271,10 +280,15 @@ /mob/living/proc/getFireLoss() return fireloss -/mob/living/proc/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype = ALL) +/mob/living/proc/can_adjust_fire_loss(amount, forced, required_bodytype) if(!forced && (status_flags & GODMODE)) - return 0 - if(on_damage_adjustment(BURN, amount, forced) & COMPONENT_IGNORE_CHANGE) + return FALSE + if(SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_BURN_DAMAGE, BURN, amount, forced) & COMPONENT_IGNORE_CHANGE) + return FALSE + return TRUE + +/mob/living/proc/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype = ALL) + if(!can_adjust_fire_loss(amount, forced, required_bodytype)) return 0 . = fireloss fireloss = clamp((fireloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) @@ -298,10 +312,15 @@ /mob/living/proc/getCloneLoss() return cloneloss -/mob/living/proc/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL) +/mob/living/proc/can_adjust_clone_loss(amount, forced, required_biotype) if(!forced && (!(mob_biotypes & required_biotype) || status_flags & GODMODE || HAS_TRAIT(src, TRAIT_NOCLONELOSS))) - return 0 - if(on_damage_adjustment(CLONE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return FALSE + if(SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_CLONE_DAMAGE, CLONE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return FALSE + return TRUE + +/mob/living/proc/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL) + if(!can_adjust_clone_loss(amount, forced, required_biotype)) return 0 . = cloneloss cloneloss = clamp((cloneloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) @@ -336,10 +355,15 @@ /mob/living/proc/getStaminaLoss() return staminaloss +/mob/living/proc/can_adjust_stamina_loss(amount, forced, required_biotype) + if(!forced && (!(mob_biotypes & required_biotype) || status_flags & GODMODE)) + return FALSE + if(SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_STAMINA_DAMAGE, STAMINA, amount, forced) & COMPONENT_IGNORE_CHANGE) + return FALSE + return TRUE + /mob/living/proc/adjustStaminaLoss(amount, updating_stamina = TRUE, forced = FALSE, required_biotype = ALL) - if(!forced && ((status_flags & GODMODE) || required_biotype && !(mob_biotypes & required_biotype))) - return 0 - if(on_damage_adjustment(STAMINA, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_stamina_loss(amount, forced, required_biotype)) return 0 . = staminaloss staminaloss = clamp((staminaloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, max_stamina) diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 9b0a078c1acc8..356653745e80b 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -12,7 +12,10 @@ /mob/living/proc/Life(seconds_per_tick = SSMOBS_DT, times_fired) set waitfor = FALSE - SEND_SIGNAL(src, COMSIG_LIVING_LIFE, seconds_per_tick, times_fired) + var/signal_result = SEND_SIGNAL(src, COMSIG_LIVING_LIFE, seconds_per_tick, times_fired) + + if(signal_result & COMPONENT_LIVING_CANCEL_LIFE_PROCESSING) // mmm less work + return if (client) var/turf/T = get_turf(src) @@ -114,7 +117,7 @@ for(var/bile in reagents.reagent_list) var/datum/reagent/consumable/bits = bile if(bits) - fullness += bits.get_nutriment_factor() * bits.volume / bits.metabolization_rate + fullness += bits.get_nutriment_factor(src) * bits.volume / bits.metabolization_rate return fullness /** diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index ba5c04ec4602a..d2ccf6e8f87d0 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1233,7 +1233,7 @@ return /mob/living/can_hold_items(obj/item/I) - return usable_hands && ..() + return ..() && HAS_TRAIT(src, TRAIT_CAN_HOLD_ITEMS) && usable_hands /mob/living/can_perform_action(atom/movable/target, action_bitflags) if(!istype(target)) @@ -1414,6 +1414,7 @@ /mob/living/basic/cow, /mob/living/basic/crab, /mob/living/basic/goat, + /mob/living/basic/gorilla, /mob/living/basic/headslug, /mob/living/basic/killer_tomato, /mob/living/basic/lizard, @@ -1431,7 +1432,6 @@ /mob/living/basic/statue, /mob/living/basic/stickman, /mob/living/basic/stickman/dog, - /mob/living/simple_animal/hostile/gorilla, /mob/living/simple_animal/hostile/megafauna/dragon/lesser, /mob/living/simple_animal/parrot, /mob/living/simple_animal/pet/cat, diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index aa3743a44b411..f31fd60b131fc 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -452,7 +452,7 @@ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(cult_ending_helper), CULT_VICTORY_MASS_CONVERSION), 120) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(ending_helper)), 270) if(client) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, src, cultoverride = TRUE) + makeNewConstruct(/mob/living/basic/construct/harvester, src, cultoverride = TRUE) else switch(rand(1, 4)) if(1) diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm index 50aacd2fdf0e1..26d0dd01a4db4 100644 --- a/code/modules/mob/living/living_say.dm +++ b/code/modules/mob/living/living_say.dm @@ -382,8 +382,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( if(!M.client.prefs.read_preference(/datum/preference/toggle/enable_runechat) || (SSlag_switch.measures[DISABLE_RUNECHAT] && !HAS_TRAIT(src, TRAIT_BYPASS_MEASURES))) speech_bubble_recipients.Add(M.client) found_client = TRUE - - if(voice && found_client && !message_mods[MODE_CUSTOM_SAY_ERASE_INPUT] && !HAS_TRAIT(src, TRAIT_SIGN_LANG) && !HAS_TRAIT(src, TRAIT_UNKNOWN)) + if(SStts.tts_enabled && voice && found_client && !message_mods[MODE_CUSTOM_SAY_ERASE_INPUT] && !HAS_TRAIT(src, TRAIT_SIGN_LANG) && !HAS_TRAIT(src, TRAIT_UNKNOWN)) var/tts_message_to_use = tts_message if(!tts_message_to_use) tts_message_to_use = message_raw @@ -399,7 +398,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( filter += tts_filter.Join(",") if(ishuman(src)) var/mob/living/carbon/human/human_speaker = src - if(human_speaker.wear_mask) + if(istype(human_speaker.wear_mask, /obj/item/clothing/mask)) var/obj/item/clothing/mask/worn_mask = human_speaker.wear_mask if(worn_mask.voice_override) voice_to_use = worn_mask.voice_override diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm index 7df2e7d339065..be713b429a6b5 100644 --- a/code/modules/mob/living/silicon/robot/inventory.dm +++ b/code/modules/mob/living/silicon/robot/inventory.dm @@ -401,6 +401,7 @@ /mob/living/silicon/robot/perform_hand_swap() cycle_modules() + return TRUE /mob/living/silicon/robot/can_hold_items(obj/item/I) return (I && (I in model.modules)) //Only if it's part of our model. diff --git a/code/modules/mob/living/simple_animal/damage_procs.dm b/code/modules/mob/living/simple_animal/damage_procs.dm index 0c06049288d09..9640dbb9de440 100644 --- a/code/modules/mob/living/simple_animal/damage_procs.dm +++ b/code/modules/mob/living/simple_animal/damage_procs.dm @@ -19,7 +19,7 @@ toggle_ai(AI_ON) /mob/living/simple_animal/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) - if(on_damage_adjustment(BRUTE, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_brute_loss(amount, forced, required_bodytype)) return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) @@ -27,7 +27,7 @@ . = adjustHealth(amount * damage_coeff[BRUTE] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/simple_animal/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) - if(on_damage_adjustment(BURN, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_fire_loss(amount, forced, required_bodytype)) return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) @@ -35,7 +35,7 @@ . = adjustHealth(amount * damage_coeff[BURN] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/simple_animal/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype, required_respiration_type) - if(on_damage_adjustment(OXY, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_oxy_loss(amount, forced, required_biotype, required_respiration_type)) return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) @@ -43,7 +43,7 @@ . = adjustHealth(amount * damage_coeff[OXY] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/simple_animal/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) - if(on_damage_adjustment(TOX, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_tox_loss(amount, forced, required_biotype)) return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) @@ -51,7 +51,7 @@ . = adjustHealth(amount * damage_coeff[TOX] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/simple_animal/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) - if(on_damage_adjustment(CLONE, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_clone_loss(amount, forced, required_biotype)) return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) @@ -59,7 +59,7 @@ . = adjustHealth(amount * damage_coeff[CLONE] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/simple_animal/adjustStaminaLoss(amount, updating_stamina = TRUE, forced = FALSE, required_biotype) - if(on_damage_adjustment(STAMINA, amount, forced) & COMPONENT_IGNORE_CHANGE) + if(!can_adjust_stamina_loss(amount, forced, required_biotype)) return 0 if(forced) staminaloss = max(0, min(max_staminaloss, staminaloss + amount)) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm index 598695588bc57..7f6a398b9dafc 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm @@ -173,6 +173,7 @@ . = ..() GLOB.drones_list += src access_card = new /obj/item/card/id/advanced/simple_bot(src) + AddComponent(/datum/component/basic_inhands, y_offset = getItemPixelShiftY()) // Doing this hurts my soul, but simple_animal access reworks are for another day. var/datum/id_trim/job/cap_trim = SSid_access.trim_singletons_by_path[/datum/id_trim/job/captain] diff --git a/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm b/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm index 69c2ac3e8cf73..90391dff58546 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm @@ -27,45 +27,6 @@ if(slot_flags & (ITEM_SLOT_HANDS|ITEM_SLOT_BACKPACK|ITEM_SLOT_DEX_STORAGE)) update_inv_internal_storage() -/mob/living/simple_animal/drone/update_held_items() - remove_overlay(DRONE_HANDS_LAYER) - var/list/hands_overlays = list() - - var/obj/item/l_hand = get_item_for_held_index(1) - var/obj/item/r_hand = get_item_for_held_index(2) - - var/y_shift = getItemPixelShiftY() - - if(r_hand) - var/mutable_appearance/r_hand_overlay = r_hand.build_worn_icon(default_layer = DRONE_HANDS_LAYER, default_icon_file = r_hand.righthand_file, isinhands = TRUE) - if(y_shift) - r_hand_overlay.pixel_y += y_shift - - hands_overlays += r_hand_overlay - - if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) - SET_PLANE_EXPLICIT(r_hand, ABOVE_HUD_PLANE, src) - r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand)) - client.screen |= r_hand - - if(l_hand) - var/mutable_appearance/l_hand_overlay = l_hand.build_worn_icon(default_layer = DRONE_HANDS_LAYER, default_icon_file = l_hand.lefthand_file, isinhands = TRUE) - if(y_shift) - l_hand_overlay.pixel_y += y_shift - - hands_overlays += l_hand_overlay - - if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) - SET_PLANE_EXPLICIT(l_hand, ABOVE_HUD_PLANE, src) - l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand)) - client.screen |= l_hand - - - if(hands_overlays.len) - drone_overlays[DRONE_HANDS_LAYER] = hands_overlays - apply_overlay(DRONE_HANDS_LAYER) - - /mob/living/simple_animal/drone/proc/update_inv_internal_storage() if(internal_storage && client && hud_used?.hud_shown) internal_storage.screen_loc = ui_drone_storage diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm index ea5e0685b53fe..4ca35a673577e 100644 --- a/code/modules/mob/living/simple_animal/guardian/guardian.dm +++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm @@ -6,7 +6,8 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians desc = "A mysterious being that stands by its charge, ever vigilant." speak_emote = list("hisses") gender = NEUTER - mob_biotypes = NONE + mob_biotypes = MOB_SPECIAL + sentience_type = SENTIENCE_HUMANOID bubble_icon = "guardian" response_help_continuous = "passes through" response_help_simple = "pass through" @@ -89,6 +90,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians GLOB.parasites += src update_theme(theme) AddElement(/datum/element/simple_flying) + AddComponent(/datum/component/basic_inhands) manifest_effects() /mob/living/simple_animal/hostile/guardian/Destroy() //if deleted by admins or something random, cut from the summoner @@ -461,32 +463,6 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians cut_overlay(overlay) guardian_overlays[cache_index] = null -/mob/living/simple_animal/hostile/guardian/update_held_items() - remove_overlay(GUARDIAN_HANDS_LAYER) - var/list/hands_overlays = list() - var/obj/item/l_hand = get_item_for_held_index(1) - var/obj/item/r_hand = get_item_for_held_index(2) - - if(r_hand) - hands_overlays += r_hand.build_worn_icon(default_layer = GUARDIAN_HANDS_LAYER, default_icon_file = r_hand.righthand_file, isinhands = TRUE) - - if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) - SET_PLANE_EXPLICIT(r_hand, ABOVE_HUD_PLANE, src) - r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand)) - client.screen |= r_hand - - if(l_hand) - hands_overlays += l_hand.build_worn_icon(default_layer = GUARDIAN_HANDS_LAYER, default_icon_file = l_hand.lefthand_file, isinhands = TRUE) - - if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) - SET_PLANE_EXPLICIT(l_hand, ABOVE_HUD_PLANE, src) - l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand)) - client.screen |= l_hand - - if(length(hands_overlays)) - guardian_overlays[GUARDIAN_HANDS_LAYER] = hands_overlays - apply_overlay(GUARDIAN_HANDS_LAYER) - /mob/living/simple_animal/hostile/guardian/regenerate_icons() update_held_items() diff --git a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm index dc4d8a004c6e7..d2fbfc33c877d 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm @@ -22,17 +22,9 @@ dropItemToGround(internal_storage) /mob/living/simple_animal/hostile/guardian/dextrous/examine(mob/user) - if(dextrous) - . = list("This is [icon2html(src)] \a [src]!\n[desc]") - for(var/obj/item/held_item in held_items) - if(held_item.item_flags & (ABSTRACT|EXAMINE_SKIP|HAND_ITEM)) - continue - . += "It has [held_item.get_examine_string(user)] in its [get_held_index_name(get_held_index_of_item(held_item))]." - if(internal_storage && !(internal_storage.item_flags & ABSTRACT)) - . += "It is holding [internal_storage.get_examine_string(user)] in its internal storage." - . += "" - else - return ..() + . = ..() + if(internal_storage && !(internal_storage.item_flags & ABSTRACT)) + . += span_info("It is holding [internal_storage.get_examine_string(user)] in its internal storage.") /mob/living/simple_animal/hostile/guardian/dextrous/recall_effects() drop_all_held_items() diff --git a/code/modules/mob/living/simple_animal/hostile/constructs/constructs.dm b/code/modules/mob/living/simple_animal/hostile/constructs/constructs.dm index 23f7590dc8e24..31150a4dc89c1 100644 --- a/code/modules/mob/living/simple_animal/hostile/constructs/constructs.dm +++ b/code/modules/mob/living/simple_animal/hostile/constructs/constructs.dm @@ -3,7 +3,7 @@ real_name = "Construct" desc = "" gender = NEUTER - mob_biotypes = NONE + mob_biotypes = MOB_MINERAL | MOB_SPECIAL speak_emote = list("hisses") response_help_continuous = "thinks better of touching" response_help_simple = "think better of touching" diff --git a/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm b/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm deleted file mode 100644 index 6f5952382c67d..0000000000000 --- a/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm +++ /dev/null @@ -1,174 +0,0 @@ -#define GORILLA_TOTAL_LAYERS 1 - -/mob/living/simple_animal/hostile/gorilla - name = "Gorilla" - desc = "A ground-dwelling, predominantly herbivorous ape that inhabits the forests of central Africa." - icon = 'icons/mob/simple/gorilla.dmi' - icon_state = "crawling" - icon_living = "crawling" - icon_dead = "dead" - health_doll_icon = "crawling" - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - speak_chance = 80 - maxHealth = 220 - health = 220 - loot = list(/obj/effect/gibspawner/generic/animal) - butcher_results = list(/obj/item/food/meat/slab/gorilla = 4) - response_help_continuous = "prods" - response_help_simple = "prod" - response_disarm_continuous = "challenges" - response_disarm_simple = "challenge" - response_harm_continuous = "thumps" - response_harm_simple = "thump" - speed = 0.5 - melee_damage_lower = 15 - melee_damage_upper = 18 - damage_coeff = list(BRUTE = 1, BURN = 1.5, TOX = 1.5, CLONE = 0, STAMINA = 0, OXY = 1.5) - obj_damage = 20 - environment_smash = ENVIRONMENT_SMASH_WALLS - attack_verb_continuous = "pummels" - attack_verb_simple = "pummel" - attack_sound = 'sound/weapons/punch1.ogg' - dextrous = TRUE - hud_type = /datum/hud/dextrous - held_items = list(null, null) - faction = list(FACTION_MONKEY, FACTION_JUNGLE) - robust_searching = TRUE - stat_attack = HARD_CRIT - minbodytemp = 270 - maxbodytemp = 350 - unique_name = TRUE - footstep_type = FOOTSTEP_MOB_BAREFOOT - - var/list/gorilla_overlays[GORILLA_TOTAL_LAYERS] - var/oogas = 0 - -// Gorillas like to dismember limbs from unconscious mobs. -// Returns null when the target is not an unconscious carbon mob; a list of limbs (possibly empty) otherwise. -/mob/living/simple_animal/hostile/gorilla/proc/get_target_bodyparts(atom/hit_target) - if(!iscarbon(hit_target)) - return - - var/mob/living/carbon/carbon_target = hit_target - if(carbon_target.stat < UNCONSCIOUS) - return - - var/list/parts = list() - for(var/obj/item/bodypart/part as anything in carbon_target.bodyparts) - if(part.body_part == HEAD || part.body_part == CHEST) - continue - if(part.bodypart_flags & BODYPART_UNREMOVABLE) - continue - parts += part - return parts - -/mob/living/simple_animal/hostile/gorilla/AttackingTarget(atom/attacked_target) - . = ..() - if(!.) - return - - if(client) - oogaooga() - - var/list/parts = get_target_bodyparts(target) - if(length(parts)) - var/obj/item/bodypart/to_dismember = pick(parts) - to_dismember.dismember() - return - - if(isliving(target)) - var/mob/living/living_target = target - if(prob(80)) - living_target.throw_at(get_edge_target_turf(living_target, dir), rand(1, 2), 7, src) - - else - living_target.Paralyze(2 SECONDS) - visible_message(span_danger("[src] knocks [living_target] down!")) - -/mob/living/simple_animal/hostile/gorilla/CanAttack(atom/the_target) - var/list/parts = get_target_bodyparts(target) - return ..() && !ismonkey(the_target) && (!parts || length(parts) > 3) - -/mob/living/simple_animal/hostile/gorilla/CanSmashTurfs(turf/T) - return iswallturf(T) - -/mob/living/simple_animal/hostile/gorilla/gib(drop_bitflags=DROP_BRAIN) - if(drop_bitflags & DROP_BRAIN) - var/mob/living/brain/gorilla_brain = new(drop_location()) - gorilla_brain.name = real_name - gorilla_brain.real_name = real_name - mind?.transfer_to(gorilla_brain) - return ..() - -/mob/living/simple_animal/hostile/gorilla/handle_automated_speech(override) - if(speak_chance && (override || prob(speak_chance))) - playsound(src, 'sound/creatures/gorilla.ogg', 50) - return ..() - -/mob/living/simple_animal/hostile/gorilla/can_use_guns(obj/item/G) - to_chat(src, span_warning("Your meaty finger is much too large for the trigger guard!")) - return FALSE - -/mob/living/simple_animal/hostile/gorilla/proc/oogaooga() - oogas -= 1 - if(oogas <= 0) - oogas = rand(2,6) - playsound(src, 'sound/creatures/gorilla.ogg', 50) - -/mob/living/simple_animal/hostile/gorilla/lesser - name = "lesser Gorilla" - desc = "An adolescent Gorilla. It may not be fully grown but, much like a banana, that just means it's sturdier and harder to chew!" - speak_chance = 100 // compensating for something - maxHealth = 120 - health = 120 - butcher_results = list(/obj/item/food/meat/slab/gorilla = 2) - speed = 0.35 - melee_damage_lower = 10 - melee_damage_upper = 15 - obj_damage = 15 - stat_attack = SOFT_CRIT - unique_name = TRUE - -/mob/living/simple_animal/hostile/gorilla/lesser/Initialize(mapload) - . = ..() - transform *= 0.75 // smolrilla - -/mob/living/simple_animal/hostile/gorilla/cargo_domestic - name = "Cargorilla" // Overriden, normally - icon = 'icons/mob/simple/cargorillia.dmi' - desc = "Cargo's pet gorilla. They seem to have an 'I love Mom' tattoo." - maxHealth = 200 - health = 200 - faction = list(FACTION_NEUTRAL, FACTION_MONKEY, FACTION_JUNGLE) - gold_core_spawnable = NO_SPAWN - unique_name = FALSE - -/mob/living/simple_animal/hostile/gorilla/cargo_domestic/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_PACIFISM, INNATE_TRAIT) - AddComponent(/datum/component/crate_carrier) - -/// Poll ghosts for control of the gorilla. -/mob/living/simple_animal/hostile/gorilla/cargo_domestic/proc/poll_for_gorilla() - AddComponent(\ - /datum/component/ghost_direct_control,\ - poll_candidates = TRUE,\ - poll_length = 30 SECONDS,\ - role_name = "Cargorilla",\ - assumed_control_message = "You are Cargorilla, a pacifistic friend of the station and carrier of freight.",\ - poll_ignore_key = POLL_IGNORE_CARGORILLA,\ - after_assumed_control = CALLBACK(src, PROC_REF(became_player_controlled)),\ - ) - -/// Called once a ghost assumes control -/mob/living/simple_animal/hostile/gorilla/cargo_domestic/proc/became_player_controlled() - mind.set_assigned_role(SSjob.GetJobType(/datum/job/cargo_technician)) - mind.special_role = "Cargorilla" - to_chat(src, span_notice("You can pick up crates by clicking on them, and drop them by clicking on the ground.")) - -/obj/item/card/id/advanced/cargo_gorilla - name = "cargorilla ID" - desc = "A card used to provide ID and determine access across the station. A gorilla-sized ID for a gorilla-sized cargo technician." - trim = /datum/id_trim/job/cargo_technician - -#undef GORILLA_TOTAL_LAYERS diff --git a/code/modules/mob/living/simple_animal/hostile/gorilla/visuals_icons.dm b/code/modules/mob/living/simple_animal/hostile/gorilla/visuals_icons.dm deleted file mode 100644 index 39dfe8f7d899e..0000000000000 --- a/code/modules/mob/living/simple_animal/hostile/gorilla/visuals_icons.dm +++ /dev/null @@ -1,56 +0,0 @@ -#define GORILLA_HANDS_LAYER 1 - -/mob/living/simple_animal/hostile/gorilla/proc/apply_overlay(cache_index) - . = gorilla_overlays[cache_index] - if(.) - add_overlay(.) - -/mob/living/simple_animal/hostile/gorilla/proc/remove_overlay(cache_index) - var/I = gorilla_overlays[cache_index] - if(I) - cut_overlay(I) - gorilla_overlays[cache_index] = null - -/mob/living/simple_animal/hostile/gorilla/update_held_items() - cut_overlays("standing_overlay") - remove_overlay(GORILLA_HANDS_LAYER) - - var/standing = FALSE - for(var/I in held_items) - if(I) - standing = TRUE - break - if(!standing) - if(stat != DEAD) - icon_state = "crawling" - set_varspeed(0.5) - return ..() - if(stat != DEAD) - icon_state = "standing" - set_varspeed(1) // Gorillas are slow when standing up. - - var/list/hands_overlays = list() - - var/obj/item/l_hand = get_item_for_held_index(1) - var/obj/item/r_hand = get_item_for_held_index(2) - - if(r_hand) - var/mutable_appearance/r_hand_overlay = r_hand.build_worn_icon(default_layer = GORILLA_HANDS_LAYER, default_icon_file = r_hand.righthand_file, isinhands = TRUE) - r_hand_overlay.pixel_y -= 1 - hands_overlays += r_hand_overlay - - if(l_hand) - var/mutable_appearance/l_hand_overlay = l_hand.build_worn_icon(default_layer = GORILLA_HANDS_LAYER, default_icon_file = l_hand.lefthand_file, isinhands = TRUE) - l_hand_overlay.pixel_y -= 1 - hands_overlays += l_hand_overlay - - if(hands_overlays.len) - gorilla_overlays[GORILLA_HANDS_LAYER] = hands_overlays - apply_overlay(GORILLA_HANDS_LAYER) - add_overlay("standing_overlay") - return ..() - -/mob/living/simple_animal/hostile/gorilla/regenerate_icons() - update_held_items() - -#undef GORILLA_HANDS_LAYER diff --git a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm b/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm deleted file mode 100644 index 090709d052744..0000000000000 --- a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm +++ /dev/null @@ -1,120 +0,0 @@ -/mob/living/simple_animal/hostile/heretic_summon - name = "Eldritch Demon" - real_name = "Eldritch Demon" - desc = "A horror from beyond this realm." - icon = 'icons/mob/nonhuman-player/eldritch_mobs.dmi' - gender = NEUTER - mob_biotypes = NONE - 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" - speak_emote = list("screams") - speak_chance = 1 - speed = 0 - combat_mode = TRUE - stop_automated_movement = TRUE - AIStatus = AI_OFF - // Sort of greenish brown, to match the vibeTM - lighting_cutoff_red = 20 - lighting_cutoff_green = 25 - lighting_cutoff_blue = 5 - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) - atmos_requirements = 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) - minbodytemp = 0 - maxbodytemp = INFINITY - movement_type = GROUND - pressure_resistance = 100 - del_on_death = TRUE - death_message = "implodes into itself." - loot = list(/obj/effect/gibspawner/human) - faction = list(FACTION_HERETIC) - simple_mob_flags = SILENCE_RANGED_MESSAGE - - /// Innate spells that are added when a beast is created. - var/list/actions_to_add - -/mob/living/simple_animal/hostile/heretic_summon/Initialize(mapload) - . = ..() - for(var/spell in actions_to_add) - var/datum/action/cooldown/spell/new_spell = new spell(src) - new_spell.Grant(src) - -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit - name = "Rust Walker" - real_name = "Rusty" - desc = "An incomprehensible abomination. Everywhere it steps, it appears to be actively seeping life out of its surroundings." - icon_state = "rust_walker_s" - icon_living = "rust_walker_s" - status_flags = CANPUSH - maxHealth = 75 - health = 75 - melee_damage_lower = 15 - melee_damage_upper = 20 - sight = SEE_TURFS - actions_to_add = list( - /datum/action/cooldown/spell/aoe/rust_conversion/small, - /datum/action/cooldown/spell/basic_projectile/rust_wave/short, - ) - -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit/setDir(newdir) - . = ..() - if(newdir == NORTH) - icon_state = "rust_walker_n" - else if(newdir == SOUTH) - icon_state = "rust_walker_s" - update_appearance(UPDATE_ICON_STATE) - -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - . = ..() - playsound(src, 'sound/effects/footstep/rustystep1.ogg', 100, TRUE) - -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if(stat == DEAD) - return ..() - - var/turf/our_turf = get_turf(src) - if(HAS_TRAIT(our_turf, TRAIT_RUSTY)) - adjustBruteLoss(-1.5 * seconds_per_tick, FALSE) - adjustFireLoss(-1.5 * seconds_per_tick, FALSE) - - return ..() - -/mob/living/simple_animal/hostile/heretic_summon/ash_spirit - name = "Ash Man" - real_name = "Ashy" - desc = "An incomprehensible abomination. As it moves, a thin trail of ash follows, appearing from seemingly nowhere." - icon_state = "ash_walker" - icon_living = "ash_walker" - status_flags = CANPUSH - maxHealth = 75 - health = 75 - melee_damage_lower = 15 - melee_damage_upper = 20 - sight = SEE_TURFS - actions_to_add = list( - /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash, - /datum/action/cooldown/spell/pointed/cleave, - /datum/action/cooldown/spell/fire_sworn, - ) - -/mob/living/simple_animal/hostile/heretic_summon/stalker - name = "Flesh Stalker" - real_name = "Flesh Stalker" - desc = "An abomination made from several limbs and organs. Every moment you stare at it, it appears to shift and change unnaturally." - icon_state = "stalker" - icon_living = "stalker" - status_flags = CANPUSH - maxHealth = 150 - health = 150 - melee_damage_lower = 15 - melee_damage_upper = 20 - sight = SEE_MOBS - actions_to_add = list( - /datum/action/cooldown/spell/shapeshift/eldritch, - /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash, - /datum/action/cooldown/spell/emp/eldritch, - ) diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm b/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm deleted file mode 100644 index 444635f2dc344..0000000000000 --- a/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm +++ /dev/null @@ -1,229 +0,0 @@ -#define MOOK_ATTACK_NEUTRAL 0 -#define MOOK_ATTACK_WARMUP 1 -#define MOOK_ATTACK_ACTIVE 2 -#define MOOK_ATTACK_RECOVERY 3 -#define ATTACK_INTERMISSION_TIME 5 - -//Fragile but highly aggressive wanderers that pose a large threat in numbers. -//They'll attempt to leap at their target from afar using their hatchets. -/mob/living/simple_animal/hostile/jungle/mook - name = "wanderer" - desc = "This unhealthy looking primitive is wielding a rudimentary hatchet, swinging it with wild abandon. One isn't much of a threat, but in numbers they can quickly overwhelm a superior opponent." - icon = 'icons/mob/simple/jungle/mook.dmi' - icon_state = "mook" - icon_living = "mook" - icon_dead = "mook_dead" - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - SET_BASE_PIXEL(-16, -8) - - maxHealth = 45 - health = 45 - melee_damage_lower = 30 - melee_damage_upper = 30 - ranged = TRUE - ranged_cooldown_time = 10 - pass_flags_self = LETPASSTHROW - robust_searching = TRUE - stat_attack = HARD_CRIT - attack_sound = 'sound/weapons/rapierhit.ogg' - attack_vis_effect = ATTACK_EFFECT_SLASH - death_sound = 'sound/voice/mook_death.ogg' - aggro_vision_range = 15 //A little more aggressive once in combat to balance out their really low HP - var/attack_state = MOOK_ATTACK_NEUTRAL - var/struck_target_leap = FALSE - - footstep_type = FOOTSTEP_MOB_BAREFOOT - -/mob/living/simple_animal/hostile/jungle/mook/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - if(istype(mover, /mob/living/simple_animal/hostile/jungle/mook)) - var/mob/living/simple_animal/hostile/jungle/mook/mook_moover = mover - if(mook_moover.attack_state == MOOK_ATTACK_ACTIVE && mook_moover.throwing) - return TRUE - -/mob/living/simple_animal/hostile/jungle/mook/death() - desc = "A deceased primitive. Upon closer inspection, it was suffering from severe cellular degeneration and its garments are machine made..."//Can you guess the twist - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/AttackingTarget() - if(isliving(target)) - if(ranged_cooldown <= world.time && attack_state == MOOK_ATTACK_NEUTRAL) - var/mob/living/L = target - if(L.incapacitated()) - WarmupAttack(forced_slash_combo = TRUE) - return - WarmupAttack() - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/Goto() - if(attack_state != MOOK_ATTACK_NEUTRAL) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/Move() - if(attack_state == MOOK_ATTACK_WARMUP || attack_state == MOOK_ATTACK_RECOVERY) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/proc/WarmupAttack(forced_slash_combo = FALSE) - if(attack_state == MOOK_ATTACK_NEUTRAL && target) - attack_state = MOOK_ATTACK_WARMUP - SSmove_manager.stop_looping(src) - update_icons() - if(prob(50) && get_dist(src,target) <= 3 || forced_slash_combo) - addtimer(CALLBACK(src, PROC_REF(SlashCombo)), ATTACK_INTERMISSION_TIME) - return - addtimer(CALLBACK(src, PROC_REF(LeapAttack)), ATTACK_INTERMISSION_TIME + rand(0,3)) - return - attack_state = MOOK_ATTACK_RECOVERY - ResetNeutral() - -/mob/living/simple_animal/hostile/jungle/mook/proc/SlashCombo() - if(attack_state == MOOK_ATTACK_WARMUP && !stat) - attack_state = MOOK_ATTACK_ACTIVE - update_icons() - SlashAttack() - addtimer(CALLBACK(src, PROC_REF(SlashAttack)), 3) - addtimer(CALLBACK(src, PROC_REF(SlashAttack)), 6) - addtimer(CALLBACK(src, PROC_REF(AttackRecovery)), 9) - -/mob/living/simple_animal/hostile/jungle/mook/proc/SlashAttack() - if(target && !stat && attack_state == MOOK_ATTACK_ACTIVE) - melee_damage_lower = 15 - melee_damage_upper = 15 - var/mob_direction = get_dir(src,target) - var/atom/target_from = GET_TARGETS_FROM(src) - if(get_dist(src,target) > 1) - step(src,mob_direction) - if(isturf(target_from.loc) && target.Adjacent(target_from) && isliving(target)) - var/mob/living/L = target - L.attack_animal(src) - return - var/swing_turf = get_step(src,mob_direction) - new /obj/effect/temp_visual/kinetic_blast(swing_turf) - playsound(src, 'sound/weapons/slashmiss.ogg', 50, TRUE) - -/mob/living/simple_animal/hostile/jungle/mook/proc/LeapAttack() - if(target && !stat && attack_state == MOOK_ATTACK_WARMUP) - attack_state = MOOK_ATTACK_ACTIVE - ADD_TRAIT(src, TRAIT_UNDENSE, LEAPING_TRAIT) - melee_damage_lower = 30 - melee_damage_upper = 30 - update_icons() - new /obj/effect/temp_visual/mook_dust(get_turf(src)) - playsound(src, 'sound/weapons/thudswoosh.ogg', 25, TRUE) - playsound(src, 'sound/voice/mook_leap_yell.ogg', 100, TRUE) - var/target_turf = get_turf(target) - throw_at(target_turf, 7, 1, src, FALSE, callback = CALLBACK(src, PROC_REF(AttackRecovery))) - return - attack_state = MOOK_ATTACK_RECOVERY - ResetNeutral() - -/mob/living/simple_animal/hostile/jungle/mook/proc/AttackRecovery() - if(attack_state == MOOK_ATTACK_ACTIVE && !stat) - attack_state = MOOK_ATTACK_RECOVERY - REMOVE_TRAIT(src, TRAIT_UNDENSE, LEAPING_TRAIT) - face_atom(target) - if(!struck_target_leap) - update_icons() - struck_target_leap = FALSE - if(prob(40)) - attack_state = MOOK_ATTACK_NEUTRAL - if(target) - if(isliving(target)) - var/mob/living/L = target - if(L.incapacitated() && L.stat != DEAD) - addtimer(CALLBACK(src, PROC_REF(WarmupAttack), TRUE), ATTACK_INTERMISSION_TIME) - return - addtimer(CALLBACK(src, PROC_REF(WarmupAttack)), ATTACK_INTERMISSION_TIME) - return - addtimer(CALLBACK(src, PROC_REF(ResetNeutral)), ATTACK_INTERMISSION_TIME) - -/mob/living/simple_animal/hostile/jungle/mook/proc/ResetNeutral() - if(attack_state == MOOK_ATTACK_RECOVERY) - attack_state = MOOK_ATTACK_NEUTRAL - ranged_cooldown = world.time + ranged_cooldown_time - update_icons() - if(target && !stat) - update_icons() - Goto(target, move_to_delay, minimum_distance) - -/mob/living/simple_animal/hostile/jungle/mook/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - if(isliving(hit_atom) && attack_state == MOOK_ATTACK_ACTIVE) - var/mob/living/L = hit_atom - if(CanAttack(L)) - L.attack_animal(src) - struck_target_leap = TRUE - REMOVE_TRAIT(src, TRAIT_UNDENSE, LEAPING_TRAIT) - update_icons() - var/mook_under_us = FALSE - for(var/A in get_turf(src)) - if(struck_target_leap && mook_under_us) - break - if(A == src) - continue - if(isliving(A)) - var/mob/living/ML = A - if(!struck_target_leap && CanAttack(ML))//Check if some joker is attempting to use rest to evade us - struck_target_leap = TRUE - ML.attack_animal(src) - REMOVE_TRAIT(src, TRAIT_UNDENSE, LEAPING_TRAIT) - struck_target_leap = TRUE - update_icons() - continue - if(istype(ML, /mob/living/simple_animal/hostile/jungle/mook) && !mook_under_us)//If we land on the same tile as another mook, spread out so we don't stack our sprite on the same tile - var/mob/living/simple_animal/hostile/jungle/mook/M = ML - if(!M.stat) - mook_under_us = TRUE - var/anydir = pick(GLOB.cardinals) - Move(get_step(src, anydir), anydir) - continue - -/mob/living/simple_animal/hostile/jungle/mook/handle_automated_action() - if(attack_state) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/OpenFire() - if(isliving(target)) - var/mob/living/L = target - if(L.incapacitated()) - return - WarmupAttack() - -/mob/living/simple_animal/hostile/jungle/mook/update_icons() - . = ..() - if(!stat) - switch(attack_state) - if(MOOK_ATTACK_NEUTRAL) - icon_state = "mook" - if(MOOK_ATTACK_WARMUP) - icon_state = "mook_warmup" - if(MOOK_ATTACK_ACTIVE) - if(!density) - icon_state = "mook_leap" - return - if(struck_target_leap) - icon_state = "mook_strike" - return - icon_state = "mook_slash_combo" - if(MOOK_ATTACK_RECOVERY) - icon_state = "mook" - -/obj/effect/temp_visual/mook_dust - name = "dust" - desc = "It's just a dust cloud!" - icon = 'icons/mob/simple/jungle/mook.dmi' - icon_state = "mook_leap_cloud" - layer = BELOW_MOB_LAYER - plane = GAME_PLANE - SET_BASE_PIXEL(-16, -16) - duration = 10 - -#undef MOOK_ATTACK_NEUTRAL -#undef MOOK_ATTACK_WARMUP -#undef MOOK_ATTACK_ACTIVE -#undef MOOK_ATTACK_RECOVERY -#undef ATTACK_INTERMISSION_TIME diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm deleted file mode 100644 index 9d84fe2e1fe82..0000000000000 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm +++ /dev/null @@ -1,85 +0,0 @@ -/mob/living/simple_animal/hostile/asteroid/ice_demon - name = "demonic watcher" - desc = "A creature formed entirely out of ice, bluespace energy emanates from inside of it." - icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' - icon_state = "ice_demon" - icon_living = "ice_demon" - icon_dead = "ice_demon_dead" - icon_gib = "syndicate_gib" - mob_biotypes = MOB_ORGANIC|MOB_BEAST - mouse_opacity = MOUSE_OPACITY_ICON - speak_emote = list("telepathically cries") - speed = 2 - move_to_delay = 2 - projectiletype = /obj/projectile/temp/ice_demon - projectilesound = 'sound/weapons/pierce.ogg' - ranged = TRUE - ranged_message = "manifests ice" - ranged_cooldown_time = 1.5 SECONDS - minimum_distance = 3 - retreat_distance = 3 - maxHealth = 150 - health = 150 - obj_damage = 40 - melee_damage_lower = 15 - melee_damage_upper = 15 - attack_verb_continuous = "slices" - attack_verb_simple = "slice" - attack_sound = 'sound/weapons/bladeslice.ogg' - attack_vis_effect = ATTACK_EFFECT_SLASH - vision_range = 9 - aggro_vision_range = 9 - move_force = MOVE_FORCE_VERY_STRONG - move_resist = MOVE_FORCE_VERY_STRONG - pull_force = MOVE_FORCE_VERY_STRONG - del_on_death = TRUE - loot = list() - crusher_loot = /obj/item/crusher_trophy/watcher_wing/ice_wing - death_message = "fades as the energies that tied it to this world dissipate." - death_sound = 'sound/magic/demon_dies.ogg' - stat_attack = HARD_CRIT - robust_searching = TRUE - footstep_type = FOOTSTEP_MOB_CLAW - /// Distance the demon will teleport from the target - var/teleport_distance = 3 - -/mob/living/simple_animal/hostile/asteroid/ice_demon/Initialize(mapload) - . = ..() - AddElement(/datum/element/simple_flying) - -/obj/projectile/temp/ice_demon - name = "ice blast" - icon_state = "ice_2" - damage = 5 - damage_type = BURN - armor_flag = ENERGY - speed = 1 - pixel_speed_multiplier = 0.25 - range = 200 - temperature = -75 - -/mob/living/simple_animal/hostile/asteroid/ice_demon/OpenFire() - ranged_cooldown = world.time + ranged_cooldown_time - // Sentient ice demons teleporting has been linked to server crashes - if(client) - return ..() - if(teleport_distance <= 0) - return ..() - var/list/possible_ends = view(teleport_distance, target.loc) - view(teleport_distance - 1, target.loc) - for(var/turf/closed/turf_to_remove in possible_ends) - possible_ends -= turf_to_remove - if(!possible_ends.len) - return ..() - var/turf/end = pick(possible_ends) - do_teleport(src, end, 0, channel=TELEPORT_CHANNEL_BLUESPACE, forced = TRUE) - SLEEP_CHECK_DEATH(8, src) - return ..() - -/mob/living/simple_animal/hostile/asteroid/ice_demon/death(gibbed) - move_force = MOVE_FORCE_DEFAULT - move_resist = MOVE_RESIST_DEFAULT - pull_force = PULL_FORCE_DEFAULT - new /obj/item/stack/ore/bluespace_crystal(loc, 3) - if(prob(5)) - new /obj/item/raw_anomaly_core/bluespace(loc) - return ..() diff --git a/code/modules/mob/living/simple_animal/hostile/ooze.dm b/code/modules/mob/living/simple_animal/hostile/ooze.dm index 6059e7efcc22b..b1c8c55cacc66 100644 --- a/code/modules/mob/living/simple_animal/hostile/ooze.dm +++ b/code/modules/mob/living/simple_animal/hostile/ooze.dm @@ -64,7 +64,7 @@ var/consumption_amount = min(reagents.get_reagent_amount(reagent.type), ooze_metabolism_modifier * REAGENTS_METABOLISM * seconds_per_tick) if(istype(reagent, /datum/reagent/consumable)) var/datum/reagent/consumable/consumable = reagent - nutrition_change += consumption_amount * consumable.get_nutriment_factor() + nutrition_change += consumption_amount * consumable.get_nutriment_factor(src) reagents.remove_reagent(reagent.type, consumption_amount) adjust_ooze_nutrition(nutrition_change) diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm index f87e78009a972..c536da22cd146 100644 --- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm +++ b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm @@ -1,5 +1,7 @@ /// The darkness threshold for space dragon when choosing a color #define DARKNESS_THRESHOLD 50 +/// Any interactions executed by the space dragon +#define DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION "space dragon interaction" /** * # Space Dragon @@ -71,8 +73,6 @@ var/gust_tiredness = 30 /// Determines whether or not Space Dragon is in the middle of using wing gust. If set to true, prevents him from moving and doing certain actions. var/using_special = FALSE - /// Determines whether or not Space Dragon is currently tearing through a wall. - var/tearing_wall = FALSE /// The color of the space dragon. var/chosen_color /// Minimum devastation damage dealt coefficient based on max health @@ -86,6 +86,7 @@ AddElement(/datum/element/simple_flying) add_traits(list(TRAIT_SPACEWALK, TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_NO_FLOATING_ANIM, TRAIT_HEALS_FROM_CARP_RIFTS), INNATE_TRAIT) AddElement(/datum/element/content_barfer) + RegisterSignal(src, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(before_attack)) /mob/living/simple_animal/hostile/space_dragon/Login() . = ..() @@ -121,41 +122,65 @@ eaten.forceMove(loc) eaten.Paralyze(5 SECONDS) -/mob/living/simple_animal/hostile/space_dragon/AttackingTarget() +/mob/living/simple_animal/hostile/space_dragon/proc/before_attack(datum/source, atom/target) + SIGNAL_HANDLER if(using_special) - return + return COMPONENT_CANCEL_ATTACK_CHAIN + if(target == src) to_chat(src, span_warning("You almost bite yourself, but then decide against it.")) - return + return COMPONENT_CANCEL_ATTACK_CHAIN + + if(DOING_INTERACTION(src, DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION)) // patience grasshopper + target.balloon_alert(src, "finish current action first!") + return COMPONENT_CANCEL_ATTACK_CHAIN + + if(ismecha(target)) + target.take_damage(50, BRUTE, MELEE, 1) + return // don't block the rest of the attack chain + if(iswallturf(target)) - if(tearing_wall) - return - tearing_wall = TRUE - var/turf/closed/wall/thewall = target - to_chat(src, span_warning("You begin tearing through the wall...")) - playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) - var/timetotear = 40 - if(istype(target, /turf/closed/wall/r_wall)) - timetotear = 120 - if(do_after(src, timetotear, target = thewall)) - if(isopenturf(thewall)) - return - thewall.dismantle_wall(1) - playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) - tearing_wall = FALSE - return + INVOKE_ASYNC(src, PROC_REF(tear_down_wall), target) + return COMPONENT_CANCEL_ATTACK_CHAIN + if(isliving(target)) //Swallows corpses like a snake to regain health. - var/mob/living/L = target - if(L.stat == DEAD) - to_chat(src, span_warning("You begin to swallow [L] whole...")) - if(do_after(src, 30, target = L)) - if(eat(L)) - adjustHealth(-L.maxHealth * 0.25) - return - . = ..() - if(ismecha(target)) - var/obj/vehicle/sealed/mecha/M = target - M.take_damage(50, BRUTE, MELEE, 1) + var/mob/living/living_target = target + if(living_target.stat != DEAD) + return // go ham on slapping the shit out of them buddy + + INVOKE_ASYNC(src, PROC_REF(eat_this_corpse), living_target) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/// Handles tearing down a wall. Returns TRUE if successful, FALSE otherwise. +/mob/living/simple_animal/hostile/space_dragon/proc/tear_down_wall(turf/closed/wall/wall_target) + to_chat(src, span_warning("You begin tearing through the wall...")) + playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) + + var/time_to_tear = 4 SECONDS + if(istype(wall_target, /turf/closed/wall/r_wall)) + time_to_tear = 12 SECONDS + + if(!do_after(src, time_to_tear, target = wall_target, interaction_key = DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION)) + return FALSE + + if(isopenturf(wall_target)) + return FALSE // well the thing was destroyed while we were sleeping so that's nice, but we didn't successfully tear it down. whatever + + wall_target.dismantle_wall(devastated = TRUE) + playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) + return TRUE + +/// Handles eating a corpse, giving us a bit of health back. Returns TRUE if we were sucessful in eating, FALSE otherwise. +/mob/living/simple_animal/hostile/space_dragon/proc/eat_this_corpse(mob/living/corpse) + to_chat(src, span_warning("You begin to swallow the body of [corpse] whole...")) + + if(!do_after(src, 3 SECONDS, target = corpse, interaction_key = DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION)) + return FALSE + if(!eat(corpse)) + return FALSE + + adjustHealth(-(corpse.maxHealth * 0.25)) + return TRUE /mob/living/simple_animal/hostile/space_dragon/ranged_secondary_attack(atom/target, modifiers) if(using_special) @@ -416,3 +441,4 @@ mind.add_antag_datum(/datum/antagonist/space_dragon) #undef DARKNESS_THRESHOLD +#undef DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION diff --git a/code/modules/mob/living/simple_animal/revenant.dm b/code/modules/mob/living/simple_animal/revenant.dm deleted file mode 100644 index 6e2ec11afeac8..0000000000000 --- a/code/modules/mob/living/simple_animal/revenant.dm +++ /dev/null @@ -1,548 +0,0 @@ -//Revenants: based off of wraiths from Goon -//"Ghosts" that are invisible and move like ghosts, cannot take damage while invisible -//Can hear deadchat, but are NOT normal ghosts and do NOT have x-ray vision -//Admin-spawn or random event - -/// Source for a trait we get when we're stunned -#define REVENANT_STUNNED_TRAIT "revenant_got_stunned" - -/mob/living/simple_animal/revenant - name = "revenant" - desc = "A malevolent spirit." - icon = 'icons/mob/simple/mob.dmi' - icon_state = "revenant_idle" - var/icon_idle = "revenant_idle" - var/icon_reveal = "revenant_revealed" - var/icon_stun = "revenant_stun" - var/icon_drain = "revenant_draining" - var/stasis = FALSE - mob_biotypes = MOB_SPIRIT - incorporeal_move = INCORPOREAL_MOVE_JAUNT - invisibility = INVISIBILITY_REVENANT - health = INFINITY //Revenants don't use health, they use essence instead - maxHealth = INFINITY - plane = GHOST_PLANE - sight = SEE_SELF - throwforce = 0 - - // Going for faint purple spoopy ghost - lighting_cutoff_red = 20 - lighting_cutoff_green = 15 - lighting_cutoff_blue = 35 - response_help_continuous = "passes through" - response_help_simple = "pass through" - response_disarm_continuous = "swings through" - response_disarm_simple = "swing through" - response_harm_continuous = "punches through" - response_harm_simple = "punch through" - unsuitable_atmos_damage = 0 - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) //I don't know how you'd apply those, but revenants no-sell them anyway. - atmos_requirements = 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) - minbodytemp = 0 - maxbodytemp = INFINITY - harm_intent_damage = 0 - friendly_verb_continuous = "touches" - friendly_verb_simple = "touch" - status_flags = 0 - wander = FALSE - density = FALSE - move_resist = MOVE_FORCE_OVERPOWERING - mob_size = MOB_SIZE_TINY - pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - speed = 1 - unique_name = TRUE - hud_possible = list(ANTAG_HUD) - hud_type = /datum/hud/revenant - - var/essence = 75 //The resource, and health, of revenants. - var/essence_regen_cap = 75 //The regeneration cap of essence (go figure); regenerates every Life() tick up to this amount. - var/essence_regenerating = TRUE //If the revenant regenerates essence or not - var/essence_regen_amount = 2.5 //How much essence regenerates per second - var/essence_accumulated = 0 //How much essence the revenant has stolen - var/essence_excess = 0 //How much stolen essence avilable for unlocks - var/revealed = FALSE //If the revenant can take damage from normal sources. - var/unreveal_time = 0 //How long the revenant is revealed for, is about 2 seconds times this var. - var/unstun_time = 0 //How long the revenant is stunned for, is about 2 seconds times this var. - var/inhibited = FALSE //If the revenant's abilities are blocked by a chaplain's power. - var/essence_drained = 0 //How much essence the revenant will drain from the corpse it's feasting on. - var/draining = FALSE //If the revenant is draining someone. - var/list/drained_mobs = list() //Cannot harvest the same mob twice - var/perfectsouls = 0 //How many perfect, regen-cap increasing souls the revenant has. //TODO, add objective for getting a perfect soul(s?) - var/generated_objectives_and_spells = FALSE - -/mob/living/simple_animal/revenant/Initialize(mapload) - . = ..() - AddElement(/datum/element/simple_flying) - add_traits(list(TRAIT_SPACEWALK, TRAIT_SIXTHSENSE, TRAIT_FREE_HYPERSPACE_MOVEMENT), INNATE_TRAIT) - - // Starting spells - - var/datum/action/cooldown/spell/list_target/telepathy/revenant/telepathy = new(src) - telepathy.Grant(src) - - // Starting spells that start locked - var/datum/action/cooldown/spell/aoe/revenant/overload/lights_go_zap = new(src) - lights_go_zap.Grant(src) - - var/datum/action/cooldown/spell/aoe/revenant/defile/windows_go_smash = new(src) - windows_go_smash.Grant(src) - - var/datum/action/cooldown/spell/aoe/revenant/blight/botany_go_mad = new(src) - botany_go_mad.Grant(src) - - var/datum/action/cooldown/spell/aoe/revenant/malfunction/shuttle_go_emag = new(src) - shuttle_go_emag.Grant(src) - - var/datum/action/cooldown/spell/aoe/revenant/haunt_object/toolbox_go_bonk = new(src) - toolbox_go_bonk.Grant(src) - - RegisterSignal(src, COMSIG_LIVING_BANED, PROC_REF(on_baned)) - random_revenant_name() - -/mob/living/simple_animal/revenant/can_perform_action(atom/movable/target, action_bitflags) - return FALSE - -/mob/living/simple_animal/revenant/proc/random_revenant_name() - var/built_name = "" - built_name += pick(strings(REVENANT_NAME_FILE, "spirit_type")) - built_name += " of " - built_name += pick(strings(REVENANT_NAME_FILE, "adverb")) - built_name += pick(strings(REVENANT_NAME_FILE, "theme")) - name = built_name - -/mob/living/simple_animal/revenant/Login() - . = ..() - if(!. || !client) - return FALSE - to_chat(src, span_deadsay("You are a revenant.")) - to_chat(src, "Your formerly mundane spirit has been infused with alien energies and empowered into a revenant.") - to_chat(src, "You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds.") - to_chat(src, "You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable.") - to_chat(src, "To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities.") - to_chat(src, "You do not remember anything of your past lives, nor will you remember anything about this one after your death.") - to_chat(src, "Be sure to read the wiki page to learn more.") - if(!generated_objectives_and_spells) - generated_objectives_and_spells = TRUE - mind.set_assigned_role(SSjob.GetJobType(/datum/job/revenant)) - mind.special_role = ROLE_REVENANT - SEND_SOUND(src, sound('sound/effects/ghost.ogg')) - mind.add_antag_datum(/datum/antagonist/revenant) - -//Life, Stat, Hud Updates, and Say -/mob/living/simple_animal/revenant/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if(stasis) - return - var/delta_time = DELTA_WORLD_TIME(SSmobs) - if(revealed && essence <= 0) - death() - if(unreveal_time && world.time >= unreveal_time) - unreveal_time = 0 - revealed = FALSE - incorporeal_move = INCORPOREAL_MOVE_JAUNT - invisibility = INVISIBILITY_REVENANT - to_chat(src, span_revenboldnotice("You are once more concealed.")) - if(unstun_time && world.time >= unstun_time) - unstun_time = 0 - REMOVE_TRAIT(src, TRAIT_NO_TRANSFORM, REVENANT_STUNNED_TRAIT) - to_chat(src, span_revenboldnotice("You can move again!")) - if(essence_regenerating && !inhibited && essence < essence_regen_cap) //While inhibited, essence will not regenerate - essence = min(essence + (essence_regen_amount * delta_time), essence_regen_cap) - update_mob_action_buttons() //because we update something required by our spells in life, we need to update our buttons - update_spooky_icon() - update_health_hud() - ..() - -/mob/living/simple_animal/revenant/get_status_tab_items() - . = ..() - . += "Current Essence: [essence >= essence_regen_cap ? essence : "[essence] / [essence_regen_cap]"]E" - . += "Total Essence Stolen: [essence_accumulated]SE" - . += "Unused Stolen Essence: [essence_excess]SE" - . += "Perfect Souls Stolen: [perfectsouls]" - -/mob/living/simple_animal/revenant/update_health_hud() - if(hud_used) - var/essencecolor = "#8F48C6" - if(essence > essence_regen_cap) - essencecolor = "#9A5ACB" //oh boy you've got a lot of essence - else if(!essence) - essencecolor = "#1D2953" //oh jeez you're dying - hud_used.healths.maptext = MAPTEXT("
    [essence]E
    ") - -/mob/living/simple_animal/revenant/med_hud_set_health() - return //we use no hud - -/mob/living/simple_animal/revenant/med_hud_set_status() - return //we use no hud - -/mob/living/simple_animal/revenant/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) - if(!message) - return - if(sanitize) - message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)) - src.log_talk(message, LOG_SAY) - var/rendered = span_deadsay("UNDEAD: [src] says, \"[message]\"") - for(var/mob/M in GLOB.mob_list) - if(isrevenant(M)) - to_chat(M, rendered) - else if(isobserver(M)) - var/link = FOLLOW_LINK(M, src) - to_chat(M, "[link] [rendered]") - return - - -//Immunities - -/mob/living/simple_animal/revenant/ex_act(severity, target) - return FALSE //Immune to the effects of explosions. - -/mob/living/simple_animal/revenant/blob_act(obj/structure/blob/B) - return //blah blah blobs aren't in tune with the spirit world, or something. - -/mob/living/simple_animal/revenant/singularity_act() - return //don't walk into the singularity expecting to find corpses, okay? - -/mob/living/simple_animal/revenant/narsie_act() - return //most humans will now be either bones or harvesters, but we're still un-alive. - -/mob/living/simple_animal/revenant/bullet_act() - if(!revealed || stasis) - return BULLET_ACT_FORCE_PIERCE - return ..() - -//damage, gibbing, and dying -/mob/living/simple_animal/revenant/proc/on_baned(obj/item/weapon, mob/living/user) - SIGNAL_HANDLER - visible_message(span_warning("[src] violently flinches!"), \ - span_revendanger("As [weapon] passes through you, you feel your essence draining away!")) - inhibited = TRUE - update_mob_action_buttons() - addtimer(CALLBACK(src, PROC_REF(reset_inhibit)), 3 SECONDS) - -/mob/living/simple_animal/revenant/proc/reset_inhibit() - inhibited = FALSE - update_mob_action_buttons() - -/mob/living/simple_animal/revenant/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - if(!forced && !revealed) - return FALSE - . = amount - essence = max(0, essence-amount) - if(updating_health) - update_health_hud() - if(!essence) - death() - -/mob/living/simple_animal/revenant/dust(just_ash, drop_items, force) - death() - -/mob/living/simple_animal/revenant/gib() - death() - -/mob/living/simple_animal/revenant/death() - if(!revealed || stasis) //Revenants cannot die if they aren't revealed //or are already dead - return - stasis = TRUE - to_chat(src, span_revendanger("NO! No... it's too late, you can feel your essence [pick("breaking apart", "drifting away")]...")) - ADD_TRAIT(src, TRAIT_NO_TRANSFORM, REVENANT_STUNNED_TRAIT) - revealed = TRUE - invisibility = 0 - playsound(src, 'sound/effects/screech.ogg', 100, TRUE) - visible_message(span_warning("[src] lets out a waning screech as violet mist swirls around its dissolving body!")) - icon_state = "revenant_draining" - for(var/i = alpha, i > 0, i -= 10) - stoplag() - alpha = i - visible_message(span_danger("[src]'s body breaks apart into a fine pile of blue dust.")) - var/reforming_essence = essence_regen_cap //retain the gained essence capacity - var/obj/item/ectoplasm/revenant/R = new(get_turf(src)) - R.essence = max(reforming_essence - 15 * perfectsouls, 75) //minus any perfect souls - R.old_key = client.key //If the essence reforms, the old revenant is put back in the body - R.revenant = src - invisibility = INVISIBILITY_ABSTRACT - revealed = FALSE - ghostize(0)//Don't re-enter invisible corpse - - -//reveal, stun, icon updates, cast checks, and essence changing -/mob/living/simple_animal/revenant/proc/reveal(time) - if(!src) - return - if(time <= 0) - return - revealed = TRUE - invisibility = 0 - incorporeal_move = FALSE - if(!unreveal_time) - to_chat(src, span_revendanger("You have been revealed!")) - unreveal_time = world.time + time - else - to_chat(src, span_revenwarning("You have been revealed!")) - unreveal_time = unreveal_time + time - update_spooky_icon() - orbiting?.end_orbit(src) - -/mob/living/simple_animal/revenant/proc/stun(time) - if(!src) - return - if(time <= 0) - return - ADD_TRAIT(src, TRAIT_NO_TRANSFORM, REVENANT_STUNNED_TRAIT) - if(!unstun_time) - to_chat(src, span_revendanger("You cannot move!")) - unstun_time = world.time + time - else - to_chat(src, span_revenwarning("You cannot move!")) - unstun_time = unstun_time + time - update_spooky_icon() - orbiting?.end_orbit(src) - -/mob/living/simple_animal/revenant/proc/update_spooky_icon() - if(revealed) - if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) - if(draining) - icon_state = icon_drain - else - icon_state = icon_stun - else - icon_state = icon_reveal - else - icon_state = icon_idle - -/mob/living/simple_animal/revenant/proc/castcheck(essence_cost) - if(!src) - return - var/turf/T = get_turf(src) - if(isclosedturf(T)) - to_chat(src, span_revenwarning("You cannot use abilities from inside of a wall.")) - return FALSE - for(var/obj/O in T) - if(O.density && !O.CanPass(src, get_dir(T, src))) - to_chat(src, span_revenwarning("You cannot use abilities inside of a dense object.")) - return FALSE - if(inhibited) - to_chat(src, span_revenwarning("Your powers have been suppressed by nulling energy!")) - return FALSE - if(!change_essence_amount(essence_cost, TRUE)) - to_chat(src, span_revenwarning("You lack the essence to use that ability.")) - return FALSE - return TRUE - -/mob/living/simple_animal/revenant/proc/unlock(essence_cost) - if(essence_excess < essence_cost) - return FALSE - essence_excess -= essence_cost - update_mob_action_buttons() - return TRUE - -/mob/living/simple_animal/revenant/proc/change_essence_amount(essence_amt, silent = FALSE, source = null) - if(!src) - return - if(essence + essence_amt < 0) - return - essence = max(0, essence+essence_amt) - update_health_hud() - if(essence_amt > 0) - essence_accumulated = max(0, essence_accumulated+essence_amt) - essence_excess = max(0, essence_excess+essence_amt) - update_mob_action_buttons() - if(!silent) - if(essence_amt > 0) - to_chat(src, span_revennotice("Gained [essence_amt]E[source ? " from [source]":""].")) - else - to_chat(src, span_revenminor("Lost [essence_amt]E[source ? " from [source]":""].")) - return 1 - -/mob/living/simple_animal/revenant/proc/death_reset() - revealed = FALSE - unreveal_time = 0 - REMOVE_TRAIT(src, TRAIT_NO_TRANSFORM, REVENANT_STUNNED_TRAIT) - unstun_time = 0 - inhibited = FALSE - draining = FALSE - incorporeal_move = INCORPOREAL_MOVE_JAUNT - invisibility = INVISIBILITY_REVENANT - alpha=255 - stasis = FALSE - -/mob/living/simple_animal/revenant/orbit(atom/target) - setDir(SOUTH) // reset dir so the right directional sprites show up - return ..() - -/mob/living/simple_animal/revenant/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - if(!orbiting) // only needed when orbiting - return ..() - if(incorporeal_move_check(src)) - return ..() - - // back back back it up, the orbitee went somewhere revenant cannot - orbiting?.end_orbit(src) - abstract_move(old_loc) // gross but maybe orbit component will be able to check pre move in the future - -/mob/living/simple_animal/revenant/stop_orbit(datum/component/orbiter/orbits) - // reset the simple_flying animation - animate(src, pixel_y = 2, time = 1 SECONDS, loop = -1, flags = ANIMATION_RELATIVE) - animate(pixel_y = -2, time = 1 SECONDS, flags = ANIMATION_RELATIVE) - return ..() - -/// Incorporeal move check: blocked by holy-watered tiles and salt piles. -/mob/living/simple_animal/revenant/proc/incorporeal_move_check(atom/destination) - var/turf/open/floor/stepTurf = get_turf(destination) - if(stepTurf) - var/obj/effect/decal/cleanable/food/salt/salt = locate() in stepTurf - if(salt) - to_chat(src, span_warning("[salt] bars your passage!")) - reveal(20) - stun(20) - return - if(stepTurf.turf_flags & NOJAUNT) - to_chat(src, span_warning("Some strange aura is blocking the way.")) - return - if(locate(/obj/effect/blessing) in stepTurf) - to_chat(src, span_warning("Holy energies block your path!")) - return - return TRUE - -//reforming -/obj/item/ectoplasm/revenant - name = "glimmering residue" - desc = "A pile of fine blue dust. Small tendrils of violet mist swirl around it." - icon = 'icons/effects/effects.dmi' - icon_state = "revenantEctoplasm" - w_class = WEIGHT_CLASS_SMALL - var/essence = 75 //the maximum essence of the reforming revenant - var/reforming = TRUE - var/inert = FALSE - var/old_key //key of the previous revenant, will have first pick on reform. - var/mob/living/simple_animal/revenant/revenant - -/obj/item/ectoplasm/revenant/Initialize(mapload) - . = ..() - addtimer(CALLBACK(src, PROC_REF(try_reform)), 600) - -/obj/item/ectoplasm/revenant/proc/scatter() - qdel(src) - -/obj/item/ectoplasm/revenant/proc/try_reform() - if(reforming) - reforming = FALSE - reform() - else - inert = TRUE - visible_message(span_warning("[src] settles down and seems lifeless.")) - -/obj/item/ectoplasm/revenant/attack_self(mob/user) - if(!reforming || inert) - return ..() - user.visible_message(span_notice("[user] scatters [src] in all directions."), \ - span_notice("You scatter [src] across the area. The particles slowly fade away.")) - user.dropItemToGround(src) - scatter() - -/obj/item/ectoplasm/revenant/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - ..() - if(inert) - return - visible_message(span_notice("[src] breaks into particles upon impact, which fade away to nothingness.")) - scatter() - -/obj/item/ectoplasm/revenant/examine(mob/user) - . = ..() - if(inert) - . += span_revennotice("It seems inert.") - else if(reforming) - . += span_revenwarning("It is shifting and distorted. It would be wise to destroy this.") - -/obj/item/ectoplasm/revenant/proc/reform() - if(QDELETED(src) || QDELETED(revenant) || inert) - return - var/key_of_revenant - message_admins("Revenant ectoplasm was left undestroyed for 1 minute and is reforming into a new revenant.") - forceMove(drop_location()) //In case it's in a backpack or someone's hand - revenant.forceMove(loc) - if(old_key) - for(var/mob/M in GLOB.dead_mob_list) - if(M.client && M.client.key == old_key) //Only recreates the mob if the mob the client is in is dead - key_of_revenant = old_key - break - if(!key_of_revenant) - message_admins("The new revenant's old client either could not be found or is in a new, living mob - grabbing a random candidate instead...") - var/list/candidates = poll_candidates_for_mob("Do you want to be [revenant.name] (reforming)?", ROLE_REVENANT, ROLE_REVENANT, 5 SECONDS, revenant) - if(!LAZYLEN(candidates)) - qdel(revenant) - message_admins("No candidates were found for the new revenant. Oh well!") - inert = TRUE - visible_message(span_revenwarning("[src] settles down and seems lifeless.")) - return - var/mob/dead/observer/C = pick(candidates) - key_of_revenant = C.key - if(!key_of_revenant) - qdel(revenant) - message_admins("No ckey was found for the new revenant. Oh well!") - inert = TRUE - visible_message(span_revenwarning("[src] settles down and seems lifeless.")) - return - - message_admins("[key_of_revenant] has been [old_key == key_of_revenant ? "re":""]made into a revenant by reforming ectoplasm.") - revenant.log_message("was [old_key == key_of_revenant ? "re":""]made as a revenant by reforming ectoplasm.", LOG_GAME) - visible_message(span_revenboldnotice("[src] suddenly rises into the air before fading away.")) - - revenant.essence = essence - revenant.essence_regen_cap = essence - revenant.death_reset() - revenant.key = key_of_revenant - revenant = null - qdel(src) - -/obj/item/ectoplasm/revenant/suicide_act(mob/living/user) - user.visible_message(span_suicide("[user] is inhaling [src]! It looks like [user.p_theyre()] trying to visit the shadow realm!")) - scatter() - return OXYLOSS - -/obj/item/ectoplasm/revenant/Destroy() - if(!QDELETED(revenant)) - qdel(revenant) - return ..() - -//objectives -/datum/objective/revenant - var/targetAmount = 100 - -/datum/objective/revenant/New() - targetAmount = rand(350,600) - explanation_text = "Absorb [targetAmount] points of essence from humans." - ..() - -/datum/objective/revenant/check_completion() - if(!isrevenant(owner.current)) - return FALSE - var/mob/living/simple_animal/revenant/R = owner.current - if(!R || R.stat == DEAD) - return FALSE - var/essence_stolen = R.essence_accumulated - if(essence_stolen < targetAmount) - return FALSE - return TRUE - -/datum/objective/revenant_fluff - -/datum/objective/revenant_fluff/New() - var/list/explanation_texts = list( - "Assist and exacerbate existing threats at critical moments.", \ - "Impersonate or be worshipped as a god.", \ - "Cause as much chaos and anger as you can without being killed.", \ - "Damage and render as much of the station rusted and unusable as possible.", \ - "Disable and cause malfunctions in as many machines as possible.", \ - "Ensure that any holy weapons are rendered unusable.", \ - "Heed and obey the requests of the dead, provided that carrying them out wouldn't be too inconvenient or self-destructive.", \ - "Make the crew as miserable as possible.", \ - "Make the clown as miserable as possible.", \ - "Make the captain as miserable as possible.", \ - "Prevent the use of energy weapons where possible.", - ) - explanation_text = pick(explanation_texts) - ..() - -/datum/objective/revenant_fluff/check_completion() - return TRUE - -#undef REVENANT_STUNNED_TRAIT diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 3b16ab685eb27..85c146b29c827 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -177,6 +177,7 @@ stack_trace("Simple animal being instantiated in nullspace") update_simplemob_varspeed() if(dextrous) + AddElement(/datum/element/dextrous, hud_type = hud_type) AddComponent(/datum/component/personal_crafting) add_traits(list(TRAIT_ADVANCEDTOOLUSER, TRAIT_CAN_STRIP), ROUNDSTART_TRAIT) ADD_TRAIT(src, TRAIT_NOFIRE_SPREAD, ROUNDSTART_TRAIT) @@ -447,9 +448,6 @@ /mob/living/simple_animal/death(gibbed) drop_loot() - if(dextrous) - drop_all_held_items() - if(del_on_death) ..() //Prevent infinite loops if the mob Destroy() is overridden in such @@ -560,49 +558,12 @@ /mob/living/simple_animal/get_idcard(hand_first) return (..() || access_card) -/mob/living/simple_animal/can_hold_items(obj/item/I) - return dextrous && ..() - -/mob/living/simple_animal/activate_hand(selhand) - if(!dextrous) - return ..() - if(!selhand) - selhand = (active_hand_index % held_items.len)+1 - if(istext(selhand)) - selhand = lowertext(selhand) - if(selhand == "right" || selhand == "r") - selhand = 2 - if(selhand == "left" || selhand == "l") - selhand = 1 - if(selhand != active_hand_index) - swap_hand(selhand) - else - mode() - -/mob/living/simple_animal/perform_hand_swap(hand_index) - . = ..() - if(!.) - return - if(!dextrous) - return - if(!hand_index) - hand_index = (active_hand_index % held_items.len)+1 - var/oindex = active_hand_index - active_hand_index = hand_index - if(hud_used) - var/atom/movable/screen/inventory/hand/H - H = hud_used.hand_slots["[hand_index]"] - if(H) - H.update_appearance() - H = hud_used.hand_slots["[oindex]"] - if(H) - H.update_appearance() - /mob/living/simple_animal/put_in_hands(obj/item/I, del_on_fail = FALSE, merge_stacks = TRUE, ignore_animation = TRUE) . = ..() update_held_items() /mob/living/simple_animal/update_held_items() + . = ..() if(!client || !hud_used || hud_used.hud_version == HUD_STYLE_NOHUD) return var/turf/our_turf = get_turf(src) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index c5a6fd74e7251..cb6469d16a836 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -937,10 +937,45 @@ /// Performs the actual ritual of swapping hands, such as setting the held index variables /mob/proc/perform_hand_swap(held_index) PROTECTED_PROC(TRUE) + if (!HAS_TRAIT(src, TRAIT_CAN_HOLD_ITEMS)) + return FALSE + + if(!held_index) + held_index = (active_hand_index % held_items.len) + 1 + + if(!isnum(held_index)) + CRASH("You passed [held_index] into swap_hand instead of a number. WTF man") + + var/previous_index = active_hand_index + active_hand_index = held_index + if(hud_used) + var/atom/movable/screen/inventory/hand/held_location + held_location = hud_used.hand_slots["[previous_index]"] + if(!isnull(held_location)) + held_location.update_appearance() + held_location = hud_used.hand_slots["[held_index]"] + if(!isnull(held_location)) + held_location.update_appearance() return TRUE -/mob/proc/activate_hand(selhand) - return +/mob/proc/activate_hand(selected_hand) + if (!HAS_TRAIT(src, TRAIT_CAN_HOLD_ITEMS)) + return + + if(!selected_hand) + selected_hand = (active_hand_index % held_items.len)+1 + + if(istext(selected_hand)) + selected_hand = lowertext(selected_hand) + if(selected_hand == "right" || selected_hand == "r") + selected_hand = 2 + if(selected_hand == "left" || selected_hand == "l") + selected_hand = 1 + + if(selected_hand != active_hand_index) + swap_hand(selected_hand) + else + mode() /mob/proc/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) //For sec bot threat assessment return 0 diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index b0b3014f5b1f9..6d94df367802b 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -253,9 +253,9 @@ if(salt) to_chat(L, span_warning("[salt] bars your passage!")) if(isrevenant(L)) - var/mob/living/simple_animal/revenant/R = L - R.reveal(20) - R.stun(20) + var/mob/living/basic/revenant/ghostie = L + ghostie.apply_status_effect(/datum/status_effect/revenant/revealed, 2 SECONDS) + ghostie.apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, 2 SECONDS) return if(stepTurf.turf_flags & NOJAUNT) to_chat(L, span_warning("Some strange aura is blocking the way.")) diff --git a/code/modules/mob/mob_update_icons.dm b/code/modules/mob/mob_update_icons.dm index 8a6464ee1825a..b8b84f8782afe 100644 --- a/code/modules/mob/mob_update_icons.dm +++ b/code/modules/mob/mob_update_icons.dm @@ -26,7 +26,8 @@ ///Updates the held items overlay(s) & HUD element. /mob/proc/update_held_items() - return + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(src, COMSIG_MOB_UPDATE_HELD_ITEMS) ///Updates the mask overlay & HUD element. /mob/proc/update_worn_mask() diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index 09ce3b3c65c8c..290177f5baf50 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -298,7 +298,7 @@ regenerate_icons() icon = null invisibility = INVISIBILITY_MAXIMUM - var/mob/living/simple_animal/hostile/gorilla/new_gorilla = new (get_turf(src)) + var/mob/living/basic/gorilla/new_gorilla = new (get_turf(src)) new_gorilla.set_combat_mode(TRUE) if(mind) mind.transfer_to(new_gorilla) @@ -372,7 +372,7 @@ if(!MP) return FALSE //Sanity, this should never happen. - if(ispath(MP, /mob/living/simple_animal/hostile/construct)) + if(ispath(MP, /mob/living/simple_animal/hostile/construct) || ispath(MP, /mob/living/basic/construct)) return FALSE //Verbs do not appear for players. //Good mobs! diff --git a/code/modules/mob_spawn/corpses/mob_corpses.dm b/code/modules/mob_spawn/corpses/mob_corpses.dm index 5dd709bde66aa..476c3f70a8491 100644 --- a/code/modules/mob_spawn/corpses/mob_corpses.dm +++ b/code/modules/mob_spawn/corpses/mob_corpses.dm @@ -350,3 +350,7 @@ /datum/outfit/prey_pod_victim name = "Prey Pod Victim" uniform = /obj/item/clothing/under/rank/rnd/roboticist + +/obj/effect/mob_spawn/corpse/human/cyber_police + name = "Dead Cyber Police" + outfit = /datum/outfit/cyber_police diff --git a/code/modules/mob_spawn/corpses/nonhuman_corpses.dm b/code/modules/mob_spawn/corpses/nonhuman_corpses.dm index 060f7e178be2f..ce02c6894aee8 100644 --- a/code/modules/mob_spawn/corpses/nonhuman_corpses.dm +++ b/code/modules/mob_spawn/corpses/nonhuman_corpses.dm @@ -46,6 +46,13 @@ pixel_x = -12 base_pixel_x = -12 +/obj/effect/mob_spawn/corpse/watcher + mob_type = /mob/living/basic/mining/watcher + icon = 'icons/mob/simple/lavaland/lavaland_monsters_wide.dmi' + icon_state = "watcher_dead_helper" + pixel_x = -12 + base_pixel_x = -12 + /// Dead headcrab for changeling-themed ruins /obj/effect/mob_spawn/corpse/headcrab mob_type = /mob/living/basic/headslug/beakless diff --git a/code/modules/mob_spawn/ghost_roles/spider_roles.dm b/code/modules/mob_spawn/ghost_roles/spider_roles.dm index e3dbea6b1ba7b..fb3d470f5aa80 100644 --- a/code/modules/mob_spawn/ghost_roles/spider_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/spider_roles.dm @@ -41,8 +41,9 @@ color = rgb(148, 0, 211) /obj/structure/spider/eggcluster/bloody + icon = 'icons/mob/simple/meteor_heart.dmi' + icon_state = "eggs" name = "bloody egg cluster" - color = rgb(255, 0, 0) /obj/structure/spider/eggcluster/midwife name = "midwife egg cluster" @@ -72,6 +73,8 @@ var/cluster_type = /obj/structure/spider/eggcluster /// Physical structure housing the spawner var/obj/structure/spider/eggcluster/egg + /// Which antag datum do we grant? + var/granted_datum = /datum/antagonist/spider /// The types of spiders that the spawner can produce var/list/potentialspawns = list( /mob/living/basic/spider/growing/spiderling/nurse, @@ -124,10 +127,11 @@ /obj/effect/mob_spawn/ghost_role/spider/special(mob/living/basic/spider/spawned_mob, mob/mob_possessor) . = ..() - spawned_mob.directive = directive + if (isspider(spawned_mob)) + spawned_mob.directive = directive egg.spawner = null QDEL_NULL(egg) - var/datum/antagonist/spider/spider_antag = new(directive) + var/datum/antagonist/spider/spider_antag = new granted_datum(directive) spawned_mob.mind.add_antag_datum(spider_antag) /obj/effect/mob_spawn/ghost_role/spider/enriched @@ -144,15 +148,18 @@ /obj/effect/mob_spawn/ghost_role/spider/bloody name = "bloody egg cluster" - color = rgb(255, 0, 0) - you_are_text = "You are a bloody spider." + icon = 'icons/mob/simple/meteor_heart.dmi' + icon_state = "eggs" + you_are_text = "You are a flesh spider." flavour_text = "An abomination of nature set upon the station by changelings. Your only goal is to kill, terrorize, and survive." - directive = "You are the spawn of a vicious changeling. You have no ambitions except to wreak havoc and ensure your own survival. You are aggressive to all living beings outside of your species, including changelings." + faction = list() + directive = null cluster_type = /obj/structure/spider/eggcluster/bloody potentialspawns = list( - /mob/living/basic/spider/giant/hunter/flesh, + /mob/living/basic/flesh_spider, ) flash_window = TRUE + granted_datum = /datum/antagonist/spider/flesh /obj/effect/mob_spawn/ghost_role/spider/midwife name = "midwife egg cluster" @@ -175,6 +182,14 @@ * * newname - If set, renames the mob to this name */ /obj/effect/mob_spawn/ghost_role/spider/create(mob/user, newname) + var/chosen_spider = length(potentialspawns) > 1 ? get_radial_choice(user) : potentialspawns[1] + if(QDELETED(src) || QDELETED(user) || isnull(chosen_spider)) + return FALSE + mob_type = chosen_spider + return ..() + +/// Pick a spider type from a radial menu +/obj/effect/mob_spawn/ghost_role/spider/proc/get_radial_choice(mob/user) var/list/spider_list = list() var/list/display_spiders = list() for(var/choice in potentialspawns) @@ -196,9 +211,6 @@ display_spiders[initial(spider.name)] = option sort_list(display_spiders) + var/chosen_spider = show_radial_menu(user, egg, display_spiders, radius = 38) - chosen_spider = spider_list[chosen_spider] - if(QDELETED(src) || QDELETED(user) || !chosen_spider) - return FALSE - mob_type = chosen_spider - return ..() + return spider_list[chosen_spider] diff --git a/code/modules/mob_spawn/ghost_roles/venus_human_trap.dm b/code/modules/mob_spawn/ghost_roles/venus_human_trap.dm index 96a75842b1b61..8ab475dce015d 100644 --- a/code/modules/mob_spawn/ghost_roles/venus_human_trap.dm +++ b/code/modules/mob_spawn/ghost_roles/venus_human_trap.dm @@ -4,7 +4,7 @@ desc = "A large pulsating plant..." icon = 'icons/mob/spacevines.dmi' icon_state = "bud0" - mob_type = /mob/living/simple_animal/hostile/venus_human_trap + mob_type = /mob/living/basic/venus_human_trap density = FALSE prompt_name = "venus human trap" you_are_text = "You are a venus human trap." @@ -23,7 +23,7 @@ flower_bud = null return ..() -/obj/effect/mob_spawn/ghost_role/venus_human_trap/equip(mob/living/simple_animal/hostile/venus_human_trap/spawned_human_trap) +/obj/effect/mob_spawn/ghost_role/venus_human_trap/equip(mob/living/basic/venus_human_trap/spawned_human_trap) if(spawned_human_trap && flower_bud) if(flower_bud.trait_flags & SPACEVINE_HEAT_RESISTANT) spawned_human_trap.unsuitable_heat_damage = 0 diff --git a/code/modules/mob_spawn/mob_spawn.dm b/code/modules/mob_spawn/mob_spawn.dm index b282ea7d8b2c3..3bfff7855f03c 100644 --- a/code/modules/mob_spawn/mob_spawn.dm +++ b/code/modules/mob_spawn/mob_spawn.dm @@ -281,6 +281,11 @@ ///burn damage this corpse will spawn with var/burn_damage = 0 + ///what environmental storytelling script should this corpse have + var/corpse_description = "" + ///optionally different text to display if the target is a clown + var/naive_corpse_description = "" + /obj/effect/mob_spawn/corpse/Initialize(mapload, no_spawn) . = ..() if(no_spawn) @@ -298,6 +303,8 @@ spawned_mob.adjustOxyLoss(oxy_damage) spawned_mob.adjustBruteLoss(brute_damage) spawned_mob.adjustFireLoss(burn_damage) + if (corpse_description) + spawned_mob.AddComponent(/datum/component/temporary_description, corpse_description, naive_corpse_description) /obj/effect/mob_spawn/corpse/create(mob/mob_possessor, newname) . = ..() diff --git a/code/modules/modular_computers/file_system/programs/frontier.dm b/code/modules/modular_computers/file_system/programs/frontier.dm index cf6cc4b2bc273..b724892da7e1c 100644 --- a/code/modules/modular_computers/file_system/programs/frontier.dm +++ b/code/modules/modular_computers/file_system/programs/frontier.dm @@ -25,7 +25,7 @@ /datum/computer_file/program/scipaper_program/on_start(mob/living/user) . = ..() if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb) - CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src) + CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, computer) /datum/computer_file/program/scipaper_program/application_attackby(obj/item/attacking_item, mob/living/user) if(!istype(attacking_item, /obj/item/multitool)) diff --git a/code/modules/modular_computers/file_system/programs/secureye.dm b/code/modules/modular_computers/file_system/programs/secureye.dm index bba55b4474efb..6e3e69cdccfc5 100644 --- a/code/modules/modular_computers/file_system/programs/secureye.dm +++ b/code/modules/modular_computers/file_system/programs/secureye.dm @@ -100,18 +100,19 @@ /datum/computer_file/program/secureye/ui_data() var/list/data = list() - data["network"] = network data["activeCamera"] = null var/obj/machinery/camera/active_camera = camera_ref?.resolve() if(active_camera) data["activeCamera"] = list( name = active_camera.c_tag, + ref = REF(active_camera), status = active_camera.status, ) return data /datum/computer_file/program/secureye/ui_static_data(mob/user) var/list/data = list() + data["network"] = network data["mapRef"] = cam_screen.assigned_map data["can_spy"] = !!spying var/list/cameras = get_camera_list(network) @@ -120,6 +121,7 @@ var/obj/machinery/camera/C = cameras[i] data["cameras"] += list(list( name = C.c_tag, + ref = REF(C), )) return data @@ -130,13 +132,14 @@ return switch(action) if("switch_camera") - var/c_tag = format_text(params["name"]) - var/list/cameras = get_camera_list(network) - var/obj/machinery/camera/selected_camera = cameras[c_tag] - camera_ref = WEAKREF(selected_camera) + var/obj/machinery/camera/selected_camera = locate(params["camera"]) in GLOB.cameranet.cameras + if(selected_camera) + camera_ref = WEAKREF(selected_camera) + else + camera_ref = null if(!spying) playsound(computer, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE) - if(!selected_camera) + if(isnull(camera_ref)) return TRUE if(internal_tracker && internal_tracker.tracking) internal_tracker.set_tracking(FALSE) diff --git a/code/modules/modular_computers/file_system/programs/techweb.dm b/code/modules/modular_computers/file_system/programs/techweb.dm index 9c097b2fb9b02..dc9538cf3580d 100644 --- a/code/modules/modular_computers/file_system/programs/techweb.dm +++ b/code/modules/modular_computers/file_system/programs/techweb.dm @@ -24,7 +24,7 @@ /datum/computer_file/program/science/on_start(mob/living/user) . = ..() if(!CONFIG_GET(flag/no_default_techweb_link) && !stored_research) - CONNECT_TO_RND_SERVER_ROUNDSTART(stored_research, src) + CONNECT_TO_RND_SERVER_ROUNDSTART(stored_research, computer) /datum/computer_file/program/science/application_attackby(obj/item/attacking_item, mob/living/user) if(!istype(attacking_item, /obj/item/multitool)) diff --git a/code/modules/pai/defense.dm b/code/modules/pai/defense.dm index b5eb177fddcf4..75c437c2546e0 100644 --- a/code/modules/pai/defense.dm +++ b/code/modules/pai/defense.dm @@ -65,7 +65,7 @@ /mob/living/silicon/pai/ignite_mob(silent) return FALSE -/mob/living/silicon/pai/proc/take_holo_damage(type, amount) +/mob/living/silicon/pai/proc/take_holo_damage(amount) holochassis_health = clamp((holochassis_health - amount), -50, HOLOCHASSIS_MAX_HEALTH) if(holochassis_health < 0) fold_in(force = TRUE) @@ -73,23 +73,17 @@ to_chat(src, span_userdanger("The impact degrades your holochassis!")) return amount -/mob/living/silicon/pai/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) - if(on_damage_adjustment(BRUTE, amount, forced) & COMPONENT_IGNORE_CHANGE) - return 0 - return take_holo_damage(amount) +/// Called when we take burn or brute damage, pass it to the shell instead +/mob/living/silicon/pai/proc/on_shell_damaged(datum/hurt, type, amount, forced) + SIGNAL_HANDLER + take_holo_damage(amount) + return COMPONENT_IGNORE_CHANGE -/mob/living/silicon/pai/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) - if(on_damage_adjustment(BURN, amount, forced) & COMPONENT_IGNORE_CHANGE) - return 0 - return take_holo_damage(amount) - -/mob/living/silicon/pai/adjustStaminaLoss(amount, updating_stamina, forced = FALSE, required_biotype) - if(on_damage_adjustment(STAMINA, amount, forced) & COMPONENT_IGNORE_CHANGE) - return 0 - if(forced) - take_holo_damage(amount) - else - take_holo_damage(amount * 0.25) +/// Called when we take stamina damage, pass it to the shell instead +/mob/living/silicon/pai/proc/on_shell_weakened(datum/hurt, type, amount, forced) + SIGNAL_HANDLER + take_holo_damage(amount * ((forced) ? 1 : 0.25)) + return COMPONENT_IGNORE_CHANGE /mob/living/silicon/pai/getBruteLoss() return HOLOCHASSIS_MAX_HEALTH - holochassis_health diff --git a/code/modules/pai/pai.dm b/code/modules/pai/pai.dm index f38017dedf64d..3998470f74878 100644 --- a/code/modules/pai/pai.dm +++ b/code/modules/pai/pai.dm @@ -235,6 +235,8 @@ update_appearance(UPDATE_DESC) RegisterSignal(src, COMSIG_LIVING_CULT_SACRIFICED, PROC_REF(on_cult_sacrificed)) + RegisterSignals(src, list(COMSIG_LIVING_ADJUST_BRUTE_DAMAGE, COMSIG_LIVING_ADJUST_BURN_DAMAGE), PROC_REF(on_shell_damaged)) + RegisterSignal(src, COMSIG_LIVING_ADJUST_STAMINA_DAMAGE, PROC_REF(on_shell_weakened)) /mob/living/silicon/pai/make_laws() laws = new /datum/ai_laws/pai() diff --git a/code/modules/paperwork/fax.dm b/code/modules/paperwork/fax.dm index 055ac8bba4e6a..a03c79f44d066 100644 --- a/code/modules/paperwork/fax.dm +++ b/code/modules/paperwork/fax.dm @@ -6,6 +6,7 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department icon = 'icons/obj/machines/fax.dmi' icon_state = "fax" density = TRUE + anchored_tabletop_offset = 6 power_channel = AREA_USAGE_EQUIP max_integrity = 100 pass_flags = PASSTABLE diff --git a/code/modules/photography/camera/other.dm b/code/modules/photography/camera/other.dm index e9aa5d94e597a..166517d055fba 100644 --- a/code/modules/photography/camera/other.dm +++ b/code/modules/photography/camera/other.dm @@ -9,13 +9,15 @@ continue // time to steal your soul - if(istype(target, /mob/living/simple_animal/revenant)) - var/mob/living/simple_animal/revenant/peek_a_boo = target - peek_a_boo.reveal(2 SECONDS) // no hiding - if(!peek_a_boo.unstun_time) - peek_a_boo.stun(2 SECONDS) - target.visible_message(span_warning("[target] violently flinches!"), \ - span_revendanger("You feel your essence draining away from having your picture taken!")) + if(istype(target, /mob/living/basic/revenant)) + var/mob/living/basic/revenant/peek_a_boo = target + peek_a_boo.apply_status_effect(/datum/status_effect/revenant/revealed, 2 SECONDS) // no hiding + peek_a_boo.apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, 2 SECONDS) + + target.visible_message( + span_warning("[target] violently flinches!"), + span_revendanger("You feel your essence draining away from having your picture taken!"), + ) target.apply_damage(rand(10, 15)) /obj/item/camera/spooky/badmin diff --git a/code/modules/plumbing/plumbers/acclimator.dm b/code/modules/plumbing/plumbers/acclimator.dm index da5c4529a4230..6b7a8caba4ac8 100644 --- a/code/modules/plumbing/plumbers/acclimator.dm +++ b/code/modules/plumbing/plumbers/acclimator.dm @@ -23,9 +23,7 @@ var/enabled = TRUE ///COOLING, HEATING or NEUTRAL. We track this for change, so we dont needlessly update our icon var/acclimate_state - /**We can't take anything in, at least till we're emptied. Down side of the round robin chem transfer, otherwise while emptying 5u of an unreacted chem gets added, - and you get nasty leftovers - */ + ///When conditions are met we send out the stored reagents var/emptying = FALSE /obj/machinery/plumbing/acclimator/Initialize(mapload, bolt, layer) diff --git a/code/modules/plumbing/plumbers/teleporter.dm b/code/modules/plumbing/plumbers/teleporter.dm index a8e6e7ae3ac55..7bb098eae4e06 100644 --- a/code/modules/plumbing/plumbers/teleporter.dm +++ b/code/modules/plumbing/plumbers/teleporter.dm @@ -45,7 +45,7 @@ ///Transfer reagents and display a flashing icon /obj/machinery/plumbing/sender/proc/teleport_chemicals(obj/machinery/plumbing/receiver/R, amount) flick(initial(icon_state) + "_flash", src) - reagents.trans_to(R, amount, round_robin = TRUE) + reagents.trans_to(R, amount) ///A bluespace output pipe for plumbing. Supports multiple recipients. Must be constructed with a circuit board /obj/machinery/plumbing/receiver diff --git a/code/modules/power/apc/apc_tool_act.dm b/code/modules/power/apc/apc_tool_act.dm index 8884786a4eb0f..55c9b34a35086 100644 --- a/code/modules/power/apc/apc_tool_act.dm +++ b/code/modules/power/apc/apc_tool_act.dm @@ -161,26 +161,25 @@ if(machine_stat & BROKEN) balloon_alert(user, "frame is too damaged!") return FALSE - return list("mode" = RCD_WALLFRAME, "delay" = 2 SECONDS, "cost" = 1) + return list("delay" = 2 SECONDS, "cost" = 1) if(!cell) if(machine_stat & MAINT) balloon_alert(user, "no board for a cell!") return FALSE - return list("mode" = RCD_WALLFRAME, "delay" = 5 SECONDS, "cost" = 10) + return list("delay" = 5 SECONDS, "cost" = 10) balloon_alert(user, "has both board and cell!") return FALSE -/obj/machinery/power/apc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - if(!(the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS) || passed_mode != RCD_WALLFRAME) +/obj/machinery/power/apc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(!(the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS) || rcd_data["[RCD_DESIGN_MODE]"] != RCD_WALLFRAME) return FALSE if(!has_electronics) if(machine_stat & BROKEN) balloon_alert(user, "frame is too damaged!") return - user.visible_message(span_notice("[user] fabricates a circuit and places it into [src].")) balloon_alert(user, "control board placed") has_electronics = TRUE locked = TRUE @@ -194,8 +193,7 @@ C.forceMove(src) cell = C chargecount = 0 - user.visible_message(span_notice("[user] fabricates a weak power cell and places it into [src]."), \ - span_warning("Your [the_rcd.name] whirrs with strain as you create a weak power cell and place it into [src]!")) + balloon_alert(user, "power cell installed") update_appearance() return TRUE diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index aa7b216d39182..0d0bb09c9975d 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -23,7 +23,7 @@ ///Current charge in cell units var/charge = 0 ///Maximum charge in cell units - var/maxcharge = 1000 + var/maxcharge = STANDARD_CELL_CHARGE custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*7, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.5) grind_results = list(/datum/reagent/lithium = 15, /datum/reagent/iron = 5, /datum/reagent/silicon = 5) ///If the cell has been booby-trapped by injecting it with plasma. Chance on use() to explode. @@ -31,7 +31,7 @@ ///If the power cell was damaged by an explosion, chance for it to become corrupted and function the same as rigged. var/corrupted = FALSE ///how much power is given every tick in a recharger - var/chargerate = 100 + var/chargerate = STANDARD_CELL_CHARGE * 0.1 ///If true, the cell will state it's maximum charge in it's description var/ratingdesc = TRUE ///If it's a grown that acts as a battery, add a wire overlay to it. @@ -51,7 +51,7 @@ create_reagents(5, INJECTABLE | DRAINABLE) if (override_maxcharge) maxcharge = override_maxcharge - rating = max(round(maxcharge / 10000, 1), 1) + rating = max(round(maxcharge / (STANDARD_CELL_CHARGE * 10), 1), 1) if(!charge) charge = maxcharge if(empty) @@ -82,7 +82,7 @@ . = COMPONENT_ITEM_CHARGED if(prob(80)) - maxcharge -= 200 + maxcharge -= STANDARD_CELL_CHARGE * 0.2 if(maxcharge <= 1) // Div by 0 protection maxcharge = 1 @@ -281,7 +281,7 @@ /obj/item/stock_parts/cell/crap name = "\improper Nanotrasen brand rechargeable AA battery" desc = "You can't top the plasma top." //TOTALLY TRADEMARK INFRINGEMENT - maxcharge = 500 + maxcharge = STANDARD_CELL_CHARGE * 0.5 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.4) /obj/item/stock_parts/cell/crap/empty @@ -290,18 +290,18 @@ /obj/item/stock_parts/cell/upgraded name = "upgraded power cell" desc = "A power cell with a slightly higher capacity than normal!" - maxcharge = 2500 + maxcharge = STANDARD_CELL_CHARGE * 2.5 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.5) - chargerate = 1000 + chargerate = STANDARD_CELL_CHARGE /obj/item/stock_parts/cell/upgraded/plus name = "upgraded power cell+" desc = "A power cell with an even higher capacity than the base model!" - maxcharge = 5000 + maxcharge = STANDARD_CELL_CHARGE * 5 /obj/item/stock_parts/cell/secborg name = "security borg rechargeable D battery" - maxcharge = 600 //600 max charge / 100 charge per shot = six shots + maxcharge = STANDARD_CELL_CHARGE * 0.6 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.4) /obj/item/stock_parts/cell/secborg/empty @@ -309,38 +309,38 @@ /obj/item/stock_parts/cell/mini_egun name = "miniature energy gun power cell" - maxcharge = 600 + maxcharge = STANDARD_CELL_CHARGE * 0.6 /obj/item/stock_parts/cell/hos_gun name = "X-01 multiphase energy gun power cell" - maxcharge = 1200 + maxcharge = STANDARD_CELL_CHARGE * 1.2 /obj/item/stock_parts/cell/pulse //200 pulse shots name = "pulse rifle power cell" - maxcharge = 40000 - chargerate = 1500 + maxcharge = STANDARD_CELL_CHARGE * 40 + chargerate = STANDARD_CELL_CHARGE * 1.5 /obj/item/stock_parts/cell/pulse/carbine //25 pulse shots name = "pulse carbine power cell" - maxcharge = 5000 + maxcharge = STANDARD_CELL_CHARGE * 5 /obj/item/stock_parts/cell/pulse/pistol //10 pulse shots name = "pulse pistol power cell" - maxcharge = 2000 + maxcharge = STANDARD_CELL_CHARGE * 2 /obj/item/stock_parts/cell/ninja name = "black power cell" icon_state = "bscell" - maxcharge = 10000 + maxcharge = STANDARD_CELL_CHARGE * 10 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.6) - chargerate = 2000 + chargerate = STANDARD_CELL_CHARGE * 2 /obj/item/stock_parts/cell/high name = "high-capacity power cell" icon_state = "hcell" - maxcharge = 10000 + maxcharge = STANDARD_CELL_CHARGE * 10 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.6) - chargerate = 1500 + chargerate = STANDARD_CELL_CHARGE * 1.5 /obj/item/stock_parts/cell/high/empty empty = TRUE @@ -348,9 +348,9 @@ /obj/item/stock_parts/cell/super name = "super-capacity power cell" icon_state = "scell" - maxcharge = 20000 + maxcharge = STANDARD_CELL_CHARGE * 20 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT * 3) - chargerate = 2000 + chargerate = STANDARD_CELL_CHARGE * 2 /obj/item/stock_parts/cell/super/empty empty = TRUE @@ -358,9 +358,9 @@ /obj/item/stock_parts/cell/hyper name = "hyper-capacity power cell" icon_state = "hpcell" - maxcharge = 30000 + maxcharge = STANDARD_CELL_CHARGE * 30 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT * 4) - chargerate = 3000 + chargerate = STANDARD_CELL_CHARGE * 3 /obj/item/stock_parts/cell/hyper/empty empty = TRUE @@ -369,9 +369,9 @@ name = "bluespace power cell" desc = "A rechargeable transdimensional power cell." icon_state = "bscell" - maxcharge = 40000 + maxcharge = STANDARD_CELL_CHARGE * 40 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*6) - chargerate = 4000 + chargerate = STANDARD_CELL_CHARGE * 4 /obj/item/stock_parts/cell/bluespace/empty empty = TRUE @@ -392,7 +392,7 @@ desc = "An alien power cell that produces energy seemingly out of nowhere." icon = 'icons/obj/antags/abductor.dmi' icon_state = "cell" - maxcharge = 50000 + maxcharge = STANDARD_CELL_CHARGE * 50 ratingdesc = FALSE /obj/item/stock_parts/cell/infinite/abductor/Initialize(mapload) @@ -405,7 +405,7 @@ icon = 'icons/obj/service/hydroponics/harvest.dmi' icon_state = "potato" charge = 100 - maxcharge = 300 + maxcharge = STANDARD_CELL_CHARGE * 0.3 charge_light_type = null connector_type = null custom_materials = null @@ -415,7 +415,7 @@ /obj/item/stock_parts/cell/emproof name = "\improper EMP-proof cell" desc = "An EMP-proof cell." - maxcharge = 500 + maxcharge = STANDARD_CELL_CHARGE * 0.5 /obj/item/stock_parts/cell/emproof/Initialize(mapload) AddElement(/datum/element/empprotection, EMP_PROTECT_SELF) @@ -433,15 +433,15 @@ icon = 'icons/mob/simple/slimes.dmi' icon_state = "yellow slime extract" custom_materials = null - maxcharge = 5000 + maxcharge = STANDARD_CELL_CHARGE * 5 charge_light_type = null connector_type = "slimecore" /obj/item/stock_parts/cell/beam_rifle name = "beam rifle capacitor" desc = "A high powered capacitor that can provide huge amounts of energy in an instant." - maxcharge = 50000 - chargerate = 5000 //Extremely energy intensive + maxcharge = STANDARD_CELL_CHARGE * 50 + chargerate = STANDARD_CELL_CHARGE * 5 //Extremely energy intensive /obj/item/stock_parts/cell/beam_rifle/corrupt() return @@ -455,7 +455,7 @@ /obj/item/stock_parts/cell/emergency_light name = "miniature power cell" desc = "A tiny power cell with a very low power capacity. Used in light fixtures to power them in the event of an outage." - maxcharge = 120 //Emergency lights use 0.2 W per tick, meaning ~10 minutes of emergency power from a cell + maxcharge = STANDARD_CELL_CHARGE * 0.12 //Emergency lights use 0.2 W per tick, meaning ~10 minutes of emergency power from a cell custom_materials = list(/datum/material/glass = SMALL_MATERIAL_AMOUNT*0.2) w_class = WEIGHT_CLASS_TINY @@ -470,7 +470,7 @@ name = "crystal power cell" desc = "A very high power cell made from crystallized plasma" icon_state = "crystal_cell" - maxcharge = 50000 + maxcharge = STANDARD_CELL_CHARGE * 50 chargerate = 0 charge_light_type = null connector_type = "crystal" @@ -478,7 +478,7 @@ grind_results = null /obj/item/stock_parts/cell/inducer_supply - maxcharge = 5000 + maxcharge = STANDARD_CELL_CHARGE * 5 #undef CELL_DRAIN_TIME #undef CELL_POWER_GAIN diff --git a/code/modules/power/floodlight.dm b/code/modules/power/floodlight.dm index f36c9b1303866..7155ce6aa2383 100644 --- a/code/modules/power/floodlight.dm +++ b/code/modules/power/floodlight.dm @@ -120,6 +120,12 @@ return ..() +/obj/structure/floodlight_frame/completed + name = "floodlight frame" + desc = "A bare metal frame that looks like a floodlight. Requires a light tube to complete." + icon_state = "floodlight_c3" + state = FLOODLIGHT_NEEDS_LIGHTS + /obj/machinery/power/floodlight name = "floodlight" desc = "A pole with powerful mounted lights on it. Due to its high power draw, it must be powered by a direct connection to a wire node." diff --git a/code/modules/power/singularity/narsie.dm b/code/modules/power/singularity/narsie.dm index 99dd421452cf3..be2cccbcde43c 100644 --- a/code/modules/power/singularity/narsie.dm +++ b/code/modules/power/singularity/narsie.dm @@ -112,7 +112,7 @@ return ..() /obj/narsie/attack_ghost(mob/user) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, user, cultoverride = TRUE, loc_override = loc) + makeNewConstruct(/mob/living/basic/construct/harvester, user, cultoverride = TRUE, loc_override = loc) /obj/narsie/process() var/datum/component/singularity/singularity_component = singularity.resolve() diff --git a/code/modules/projectiles/ammunition/ballistic/shotgun.dm b/code/modules/projectiles/ammunition/ballistic/shotgun.dm index ac3cc859b63b8..da5d4161286e0 100644 --- a/code/modules/projectiles/ammunition/ballistic/shotgun.dm +++ b/code/modules/projectiles/ammunition/ballistic/shotgun.dm @@ -135,6 +135,16 @@ icon_state = "cshell" projectile_type = null +/obj/item/ammo_casing/shotgun/techshell/Initialize(mapload) + . = ..() + + var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/meteorslug, /datum/crafting_recipe/pulseslug, /datum/crafting_recipe/dragonsbreath, /datum/crafting_recipe/ionslug, /datum/crafting_recipe/laserslug) + + AddComponent( + /datum/component/slapcrafting,\ + slapcraft_recipes = slapcraft_recipe_list,\ + ) + /obj/item/ammo_casing/shotgun/dart name = "shotgun dart" desc = "A dart for use in shotguns. Can be injected with up to 15 units of any chemical." diff --git a/code/modules/projectiles/ammunition/energy/_energy.dm b/code/modules/projectiles/ammunition/energy/_energy.dm index 8ff8f6510caf3..d90da88db9457 100644 --- a/code/modules/projectiles/ammunition/energy/_energy.dm +++ b/code/modules/projectiles/ammunition/energy/_energy.dm @@ -4,7 +4,7 @@ caliber = ENERGY projectile_type = /obj/projectile/energy slot_flags = null - var/e_cost = 100 //The amount of energy a cell needs to expend to create this shot. + var/e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE) //The amount of energy a cell needs to expend to create this shot. var/select_name = CALIBER_ENERGY fire_sound = 'sound/weapons/laser.ogg' firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy diff --git a/code/modules/projectiles/ammunition/energy/ebow.dm b/code/modules/projectiles/ammunition/energy/ebow.dm index 0eee10a58e532..535ea126576cb 100644 --- a/code/modules/projectiles/ammunition/energy/ebow.dm +++ b/code/modules/projectiles/ammunition/energy/ebow.dm @@ -1,7 +1,7 @@ /obj/item/ammo_casing/energy/bolt projectile_type = /obj/projectile/energy/bolt select_name = "bolt" - e_cost = 500 + e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE * 0.5) fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' // Even for non-suppressed crossbows, this is the most appropriate sound /obj/item/ammo_casing/energy/bolt/halloween diff --git a/code/modules/projectiles/ammunition/energy/gravity.dm b/code/modules/projectiles/ammunition/energy/gravity.dm index 5b781189c5282..fe73ad26883ed 100644 --- a/code/modules/projectiles/ammunition/energy/gravity.dm +++ b/code/modules/projectiles/ammunition/energy/gravity.dm @@ -1,5 +1,5 @@ /obj/item/ammo_casing/energy/gravity - e_cost = 0 + e_cost = 0 // Not possible to use the macro fire_sound = 'sound/weapons/wave.ogg' select_name = "gravity" delay = 50 diff --git a/code/modules/projectiles/ammunition/energy/laser.dm b/code/modules/projectiles/ammunition/energy/laser.dm index fbf9b289aa079..c47181688a482 100644 --- a/code/modules/projectiles/ammunition/energy/laser.dm +++ b/code/modules/projectiles/ammunition/energy/laser.dm @@ -1,32 +1,40 @@ /obj/item/ammo_casing/energy/laser projectile_type = /obj/projectile/beam/laser - e_cost = 83 + e_cost = LASER_SHOTS(12, STANDARD_CELL_CHARGE) select_name = "kill" /obj/item/ammo_casing/energy/laser/hellfire projectile_type = /obj/projectile/beam/laser/hellfire - e_cost = 100 + e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE) select_name = "maim" -/obj/item/ammo_casing/energy/laser/hellfire/antique - e_cost = 100 - /obj/item/ammo_casing/energy/lasergun projectile_type = /obj/projectile/beam/laser - e_cost = 62.5 + e_cost = LASER_SHOTS(16, STANDARD_CELL_CHARGE) + select_name = "kill" + +/obj/item/ammo_casing/energy/lasergun/carbine + projectile_type = /obj/projectile/beam/laser/carbine + e_cost = LASER_SHOTS(40, STANDARD_CELL_CHARGE) select_name = "kill" + fire_sound = 'sound/weapons/laser2.ogg' + +/obj/item/ammo_casing/energy/lasergun/carbine/practice + projectile_type = /obj/projectile/beam/laser/carbine/practice + select_name = "practice" + harmful = FALSE /obj/item/ammo_casing/energy/lasergun/old projectile_type = /obj/projectile/beam/laser - e_cost = 200 + e_cost = LASER_SHOTS(5, STANDARD_CELL_CHARGE) select_name = "kill" /obj/item/ammo_casing/energy/laser/hos - e_cost = 120 + e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE * 1.2) /obj/item/ammo_casing/energy/laser/musket projectile_type = /obj/projectile/beam/laser/musket - e_cost = 1000 + e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/laser/musket/prime projectile_type = /obj/projectile/beam/laser/musket/prime @@ -38,7 +46,7 @@ /obj/item/ammo_casing/energy/chameleon projectile_type = /obj/projectile/energy/chameleon - e_cost = 0 + e_cost = 0 // Can't really use the macro here, unfortunately var/projectile_vars = list() /obj/item/ammo_casing/energy/chameleon/ready_proj() @@ -78,7 +86,7 @@ /obj/item/ammo_casing/energy/laser/pulse projectile_type = /obj/projectile/beam/pulse - e_cost = 200 + e_cost = LASER_SHOTS(200, STANDARD_CELL_CHARGE * 40) select_name = "DESTROY" fire_sound = 'sound/weapons/pulse.ogg' @@ -100,7 +108,7 @@ /obj/item/ammo_casing/energy/xray projectile_type = /obj/projectile/beam/xray - e_cost = 50 + e_cost = LASER_SHOTS(20, STANDARD_CELL_CHARGE) fire_sound = 'sound/weapons/laser3.ogg' /obj/item/ammo_casing/energy/mindflayer @@ -116,7 +124,7 @@ /obj/item/ammo_casing/energy/nanite projectile_type = /obj/projectile/bullet/c10mm //henk select_name = "bullet" - e_cost = 120 + e_cost = LASER_SHOTS(8, STANDARD_CELL_CHARGE) fire_sound = 'sound/weapons/thermalpistol.ogg' /obj/item/ammo_casing/energy/nanite/inferno diff --git a/code/modules/projectiles/ammunition/energy/lmg.dm b/code/modules/projectiles/ammunition/energy/lmg.dm index fbd5916613e39..632044f065203 100644 --- a/code/modules/projectiles/ammunition/energy/lmg.dm +++ b/code/modules/projectiles/ammunition/energy/lmg.dm @@ -2,5 +2,5 @@ projectile_type = /obj/projectile/bullet/c3d select_name = "spraydown" fire_sound = 'sound/weapons/gun/smg/shot.ogg' - e_cost = 20 + e_cost = LASER_SHOTS(30, STANDARD_CELL_CHARGE * 0.6) firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect diff --git a/code/modules/projectiles/ammunition/energy/plasma.dm b/code/modules/projectiles/ammunition/energy/plasma.dm index 00de4a90ffee7..e660903bdc95d 100644 --- a/code/modules/projectiles/ammunition/energy/plasma.dm +++ b/code/modules/projectiles/ammunition/energy/plasma.dm @@ -3,9 +3,9 @@ select_name = "plasma burst" fire_sound = 'sound/weapons/plasma_cutter.ogg' delay = 15 - e_cost = 25 + e_cost = LASER_SHOTS(40, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/plasma/adv projectile_type = /obj/projectile/plasma/adv delay = 10 - e_cost = 10 + e_cost = LASER_SHOTS(100, STANDARD_CELL_CHARGE) diff --git a/code/modules/projectiles/ammunition/energy/portal.dm b/code/modules/projectiles/ammunition/energy/portal.dm index 8bdd697f1bfc6..0ef63491f11d4 100644 --- a/code/modules/projectiles/ammunition/energy/portal.dm +++ b/code/modules/projectiles/ammunition/energy/portal.dm @@ -1,6 +1,6 @@ /obj/item/ammo_casing/energy/wormhole projectile_type = /obj/projectile/beam/wormhole - e_cost = 0 + e_cost = 0 // Can't use the macro harmful = FALSE fire_sound = 'sound/weapons/pulse3.ogg' select_name = "blue" diff --git a/code/modules/projectiles/ammunition/energy/special.dm b/code/modules/projectiles/ammunition/energy/special.dm index 24fba4b9ba492..f2fc274ee8a10 100644 --- a/code/modules/projectiles/ammunition/energy/special.dm +++ b/code/modules/projectiles/ammunition/energy/special.dm @@ -5,7 +5,7 @@ /obj/item/ammo_casing/energy/ion/hos projectile_type = /obj/projectile/ion/weak - e_cost = 300 + e_cost = LASER_SHOTS(4, STANDARD_CELL_CHARGE * 1.2) /obj/item/ammo_casing/energy/declone projectile_type = /obj/projectile/energy/declone @@ -30,12 +30,12 @@ /obj/item/ammo_casing/energy/flora/revolution projectile_type = /obj/projectile/energy/florarevolution select_name = "revolution" - e_cost = 250 + e_cost = LASER_SHOTS(4, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/temp projectile_type = /obj/projectile/temp select_name = "freeze" - e_cost = 250 + e_cost = LASER_SHOTS(40, STANDARD_CELL_CHARGE * 10) fire_sound = 'sound/weapons/pulse3.ogg' /obj/item/ammo_casing/energy/temp/hot @@ -60,23 +60,23 @@ /obj/item/ammo_casing/energy/tesla_cannon fire_sound = 'sound/magic/lightningshock.ogg' - e_cost = 30 + e_cost = LASER_SHOTS(33, STANDARD_CELL_CHARGE) select_name = "shock" projectile_type = /obj/projectile/energy/tesla_cannon /obj/item/ammo_casing/energy/shrink projectile_type = /obj/projectile/beam/shrink select_name = "shrink ray" - e_cost = 200 + e_cost = LASER_SHOTS(5, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/marksman projectile_type = /obj/projectile/bullet/marksman select_name = "marksman nanoshot" - e_cost = 0 + e_cost = 0 // Can't use the macro fire_sound = 'sound/weapons/gun/revolver/shot_alt.ogg' /obj/item/ammo_casing/energy/fisher projectile_type = /obj/projectile/energy/fisher select_name = "light-buster" - e_cost = 250 + e_cost = LASER_SHOTS(2, STANDARD_CELL_CHARGE * 0.5) fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' // fwip fwip fwip fwip diff --git a/code/modules/projectiles/ammunition/energy/stun.dm b/code/modules/projectiles/ammunition/energy/stun.dm index 0a34ab1782c6b..ee2f9fa17eef5 100644 --- a/code/modules/projectiles/ammunition/energy/stun.dm +++ b/code/modules/projectiles/ammunition/energy/stun.dm @@ -2,33 +2,33 @@ projectile_type = /obj/projectile/energy/electrode select_name = "stun" fire_sound = 'sound/weapons/taser.ogg' - e_cost = 200 + e_cost = LASER_SHOTS(5, STANDARD_CELL_CHARGE) harmful = FALSE /obj/item/ammo_casing/energy/electrode/spec - e_cost = 100 + e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/electrode/gun fire_sound = 'sound/weapons/gun/pistol/shot.ogg' - e_cost = 100 + e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/electrode/old - e_cost = 1000 + e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/disabler projectile_type = /obj/projectile/beam/disabler select_name = "disable" - e_cost = 50 + e_cost = LASER_SHOTS(20, STANDARD_CELL_CHARGE) fire_sound = 'sound/weapons/taser2.ogg' harmful = FALSE /obj/item/ammo_casing/energy/disabler/hos - e_cost = 60 + e_cost = LASER_SHOTS(20, STANDARD_CELL_CHARGE * 1.2) /obj/item/ammo_casing/energy/disabler/smoothbore projectile_type = /obj/projectile/beam/disabler/smoothbore - e_cost = 1000 + e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/disabler/smoothbore/prime projectile_type = /obj/projectile/beam/disabler/smoothbore/prime - e_cost = 500 + e_cost = LASER_SHOTS(2, STANDARD_CELL_CHARGE) diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm index 5d33b3fce5170..9f7ab7e354c94 100644 --- a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm +++ b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm @@ -73,7 +73,7 @@ /obj/projectile/bullet/arrow/holy/Initialize(mapload) . = ..() //50 damage to revenants - AddElement(/datum/element/bane, target_type = /mob/living/simple_animal/revenant, damage_multiplier = 0, added_damage = 30) + AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 30) /// special pyre sect arrow /// in the future, this needs a special sprite, but bows don't support non-hardcoded arrow sprites diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_types.dm b/code/modules/projectiles/guns/ballistic/bows/bow_types.dm index 355ed3575a814..b9ac1af0cca12 100644 --- a/code/modules/projectiles/guns/ballistic/bows/bow_types.dm +++ b/code/modules/projectiles/guns/ballistic/bows/bow_types.dm @@ -30,7 +30,7 @@ on_clear_callback = CALLBACK(src, PROC_REF(on_cult_rune_removed)), \ effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune) \ ) - AddElement(/datum/element/bane, target_type = /mob/living/simple_animal/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE) + AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE) /obj/item/gun/ballistic/bow/divine/proc/on_cult_rune_removed(obj/effect/target, mob/living/user) SIGNAL_HANDLER diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm index 2b62416fe7f42..f87f473ae459b 100644 --- a/code/modules/projectiles/guns/ballistic/revolver.dm +++ b/code/modules/projectiles/guns/ballistic/revolver.dm @@ -139,6 +139,11 @@ desc = "A modernized 7 round revolver manufactured by Waffle Co. Uses .357 ammo." icon_state = "revolversyndie" +/obj/item/gun/ballistic/revolver/syndicate/cowboy + desc = "A classic revolver, refurbished for modern use. Uses .357 ammo." + //There's already a cowboy sprite in there! + icon_state = "lucky" + /obj/item/gun/ballistic/revolver/mateba name = "\improper Unica 6 auto-revolver" desc = "A retro high-powered autorevolver typically used by officers of the New Russia military. Uses .357 ammo." diff --git a/code/modules/projectiles/guns/energy/beam_rifle.dm b/code/modules/projectiles/guns/energy/beam_rifle.dm index 8869da14a59e3..a451b14de9aeb 100644 --- a/code/modules/projectiles/guns/energy/beam_rifle.dm +++ b/code/modules/projectiles/guns/energy/beam_rifle.dm @@ -433,7 +433,7 @@ /obj/item/ammo_casing/energy/beam_rifle/hitscan projectile_type = /obj/projectile/beam/beam_rifle/hitscan select_name = "beam" - e_cost = 10000 + e_cost = LASER_SHOTS(5, 50000) // Beam rifle has a custom cell fire_sound = 'sound/weapons/beam_sniper.ogg' /obj/projectile/beam/beam_rifle diff --git a/code/modules/projectiles/guns/energy/dueling.dm b/code/modules/projectiles/guns/energy/dueling.dm index a766ba5667fdf..7b3929d7574cf 100644 --- a/code/modules/projectiles/guns/energy/dueling.dm +++ b/code/modules/projectiles/guns/energy/dueling.dm @@ -292,7 +292,7 @@ //Casing /obj/item/ammo_casing/energy/duel - e_cost = 0 + e_cost = 0 // Can't use the macro projectile_type = /obj/projectile/energy/duel var/setting diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm index 8652fd0248065..a5dd70b17cb13 100644 --- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm +++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm @@ -165,7 +165,7 @@ /obj/item/ammo_casing/energy/kinetic projectile_type = /obj/projectile/kinetic select_name = "kinetic" - e_cost = 500 + e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE * 0.5) fire_sound = 'sound/weapons/kenetic_accel.ogg' // fine spelling there chap /obj/item/ammo_casing/energy/kinetic/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm index 2d5249ef50c65..4c716f679efe3 100644 --- a/code/modules/projectiles/guns/energy/laser.dm +++ b/code/modules/projectiles/guns/energy/laser.dm @@ -35,6 +35,26 @@ desc = "An older model of the basic lasergun, no longer used by Nanotrasen's private security or military forces. Nevertheless, it is still quite deadly and easy to maintain, making it a favorite amongst pirates and other outlaws." ammo_x_offset = 3 +/obj/item/gun/energy/laser/carbine + name = "laser carbine" + desc = "A modified laser gun which can shoot far faster, but each shot is far less damaging." + icon_state = "laser_carbine" + ammo_type = list(/obj/item/ammo_casing/energy/lasergun/carbine) + var/allow_akimbo = FALSE + +/obj/item/gun/energy/laser/carbine/Initialize(mapload) + . = ..() + AddComponent(/datum/component/automatic_fire, 0.15 SECONDS, allow_akimbo = allow_akimbo) + +/obj/item/gun/energy/laser/carbine/practice + name = "practice laser carbine" + desc = "A modified version of the laser carbine, this one fires even less concentrated energy bolts designed for target practice." + ammo_type = list(/obj/item/ammo_casing/energy/lasergun/carbine/practice) + clumsy_check = FALSE + item_flags = NONE + gun_flags = NOT_A_REAL_GUN + allow_akimbo = TRUE + /obj/item/gun/energy/laser/retro/old name ="laser gun" icon_state = "retro" @@ -59,7 +79,7 @@ selfcharge = 1 resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF flags_1 = PREVENT_CONTENTS_EXPLOSION_1 - ammo_type = list(/obj/item/ammo_casing/energy/laser/hellfire/antique) + ammo_type = list(/obj/item/ammo_casing/energy/laser/hellfire) /obj/item/gun/energy/laser/captain/scattershot name = "scatter shot laser rifle" diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 6bce420a02bcb..37d36b13f6ece 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -1143,13 +1143,17 @@ #undef MUZZLE_EFFECT_PIXEL_INCREMENT /// Fire a projectile from this atom at another atom -/atom/proc/fire_projectile(projectile_type, atom/target, sound, firer) +/atom/proc/fire_projectile(projectile_type, atom/target, sound, firer, list/ignore_targets = list()) if (!isnull(sound)) playsound(src, sound, vol = 100, vary = TRUE) var/turf/startloc = get_turf(src) var/obj/projectile/bullet = new projectile_type(startloc) bullet.starting = startloc + var/list/ignore = list() + for (var/atom/thing as anything in ignore_targets) + ignore[thing] = TRUE + bullet.impacted += ignore bullet.firer = firer || src bullet.fired_from = src bullet.yo = target.y - startloc.y @@ -1157,3 +1161,4 @@ bullet.original = target bullet.preparePixelProjectile(target, src) bullet.fire() + return bullet diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index 454684e1bee32..08a71bff1b86a 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -28,6 +28,16 @@ damage = 25 bare_wound_bonus = 40 +/obj/projectile/beam/laser/carbine + icon_state = "carbine_laser" + impact_effect_type = /obj/effect/temp_visual/impact_effect/yellow_laser + damage = 10 + +/obj/projectile/beam/laser/carbine/practice + name = "practice laser" + impact_effect_type = /obj/effect/temp_visual/impact_effect/yellow_laser + damage = 0 + //overclocked laser, does a bit more damage but has much higher wound power (-0 vs -20) /obj/projectile/beam/laser/hellfire name = "hellfire laser" diff --git a/code/modules/reagents/chemistry/equilibrium.dm b/code/modules/reagents/chemistry/equilibrium.dm index 7d7aff20fae3e..f46c636c50f59 100644 --- a/code/modules/reagents/chemistry/equilibrium.dm +++ b/code/modules/reagents/chemistry/equilibrium.dm @@ -324,8 +324,8 @@ //keep limited if(delta_chem_factor > step_target_vol) delta_chem_factor = step_target_vol - else if (delta_chem_factor < CHEMICAL_VOLUME_MINIMUM) - delta_chem_factor = CHEMICAL_VOLUME_MINIMUM + else if (delta_chem_factor < CHEMICAL_QUANTISATION_LEVEL) + delta_chem_factor = CHEMICAL_QUANTISATION_LEVEL //Normalise to multiproducts delta_chem_factor /= product_ratio //delta_chem_factor = round(delta_chem_factor, CHEMICAL_QUANTISATION_LEVEL) // Might not be needed - left here incase testmerge shows that it does. Remove before full commit. diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index 6388bc4221246..298b0077cb2e9 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -1,136 +1,6 @@ -#define REAGENTS_UI_MODE_LOOKUP 0 -#define REAGENTS_UI_MODE_REAGENT 1 -#define REAGENTS_UI_MODE_RECIPE 2 - #define REAGENT_TRANSFER_AMOUNT "amount" #define REAGENT_PURITY "purity" -/// Initialises all /datum/reagent into a list indexed by reagent id -/proc/init_chemical_reagent_list() - var/list/reagent_list = list() - - var/paths = subtypesof(/datum/reagent) - - for(var/path in paths) - if(path in GLOB.fake_reagent_blacklist) - continue - var/datum/reagent/D = new path() - D.mass = rand(10, 800) //This is terrible and should be removed ASAP! - reagent_list[path] = D - - return reagent_list - -/// Creates an list which is indexed by reagent name . used by plumbing reaction chamber and chemical filter UI -/proc/init_chemical_name_list() - var/list/name_list = list() - for(var/X in GLOB.chemical_reagents_list) - var/datum/reagent/Reagent = GLOB.chemical_reagents_list[X] - name_list += Reagent.name - return sort_list(name_list) - - -/proc/build_chemical_reactions_lists() - //Chemical Reactions - Initialises all /datum/chemical_reaction into a list - // It is filtered into multiple lists within a list. - // For example: - // chemical_reactions_list_reactant_index[/datum/reagent/toxin/plasma] is a list of all reactions relating to plasma - //For chemical reaction list product index - indexes reactions based off the product reagent type - see get_recipe_from_reagent_product() in helpers - //For chemical reactions list lookup list - creates a bit list of info passed to the UI. This is saved to reduce lag from new windows opening, since it's a lot of data. - - //Prevent these reactions from appearing in lookup tables (UI code) - var/list/blacklist = typecacheof(/datum/chemical_reaction/randomized) - - if(GLOB.chemical_reactions_list_reactant_index) - return - - //Randomized need to go last since they need to check against conflicts with normal recipes - var/paths = subtypesof(/datum/chemical_reaction) - typesof(/datum/chemical_reaction/randomized) + subtypesof(/datum/chemical_reaction/randomized) - GLOB.chemical_reactions_list = list() //typepath to reaction list - GLOB.chemical_reactions_list_reactant_index = list() //reagents to reaction list - GLOB.chemical_reactions_results_lookup_list = list() //UI glob - GLOB.chemical_reactions_list_product_index = list() //product to reaction list - - var/list/datum/chemical_reaction/reactions = list() - for(var/path in paths) - var/datum/chemical_reaction/reaction = new path() - reactions += reaction - - // Ok so we're gonna do a thingTM here - // I want to distribute all our reactions such that each reagent id links to as few as possible - // I get the feeling there's a canonical way of doing this, but I don't know it - // So instead, we're gonna wing it - var/list/reagent_to_react_count = list() - for(var/datum/chemical_reaction/reaction as anything in reactions) - for(var/reagent_id as anything in reaction.required_reagents) - reagent_to_react_count[reagent_id] += 1 - - var/list/reaction_lookup = GLOB.chemical_reactions_list_reactant_index - // Create filters based on a random reagent id in the required reagents list - this is used to speed up handle_reactions() - // Basically, we only really need to care about ONE reagent, at least when initially filtering, since any others are ignorable - // Doing this separately because it relies on the loop above, and this is easier to parse - for(var/datum/chemical_reaction/reaction as anything in reactions) - var/preferred_id = null - for(var/reagent_id as anything in reaction.required_reagents) - if(!preferred_id) - preferred_id = reagent_id - continue - // If we would have less then they would, take it - if(length(reaction_lookup[reagent_id]) < length(reaction_lookup[preferred_id])) - preferred_id = reagent_id - continue - // If they potentially have more then us, we take it - if(reagent_to_react_count[reagent_id] < reagent_to_react_count[preferred_id]) - preferred_id = reagent_id - continue - if (preferred_id != null) - if(!reaction_lookup[preferred_id]) - reaction_lookup[preferred_id] = list() - reaction_lookup[preferred_id] += reaction - - for(var/datum/chemical_reaction/reaction as anything in reactions) - var/list/product_ids = list() - var/list/reagents = list() - var/list/product_names = list() - var/bitflags = reaction.reaction_tags - - if(!reaction.required_reagents || !reaction.required_reagents.len) //Skip impossible reactions - continue - - GLOB.chemical_reactions_list[reaction.type] = reaction - - for(var/reagent_path in reaction.required_reagents) - var/datum/reagent/reagent = find_reagent_object_from_type(reagent_path) - if(!istype(reagent)) - stack_trace("Invalid reagent found in [reaction] required_reagents: [reagent_path]") - continue - reagents += list(list("name" = reagent.name, "id" = reagent.type)) - - for(var/product in reaction.results) - var/datum/reagent/reagent = find_reagent_object_from_type(product) - if(!istype(reagent)) - stack_trace("Invalid reagent found in [reaction] results: [product]") - continue - product_names += reagent.name - product_ids += product - - var/product_name - if(!length(product_names)) - var/list/names = splittext("[reaction.type]", "/") - product_name = names[names.len] - else - product_name = product_names[1] - - if(!is_type_in_typecache(reaction.type, blacklist)) - //Master list of ALL reactions that is used in the UI lookup table. This is expensive to make, and we don't want to lag the server by creating it on UI request, so it's cached to send to UIs instantly. - GLOB.chemical_reactions_results_lookup_list += list(list("name" = product_name, "id" = reaction.type, "bitflags" = bitflags, "reactants" = reagents)) - - // Create filters based on each reagent id in the required reagents list - this is specifically for finding reactions from product(reagent) ids/typepaths. - for(var/id in product_ids) - if(!GLOB.chemical_reactions_list_product_index[id]) - GLOB.chemical_reactions_list_product_index[id] = list() - GLOB.chemical_reactions_list_product_index[id] += reaction - - ///////////////////////////////Main reagents code///////////////////////////////////////////// /// Holder for a bunch of [/datum/reagent] @@ -147,8 +17,6 @@ var/chem_temp = 150 ///pH of the whole system var/ph = CHEMICAL_NORMAL_PH - /// unused - var/last_tick = 1 /// various flags, see code\__DEFINES\reagents.dm var/flags ///list of reactions currently on going, this is a lazylist for optimisation @@ -171,7 +39,7 @@ ///If we're syncing with the beaker - so return reactions that are actively happening var/ui_beaker_sync = FALSE -/datum/reagents/New(maximum=100, new_flags=0) +/datum/reagents/New(maximum = 100, new_flags = 0) maximum_volume = maximum flags = new_flags @@ -203,21 +71,36 @@ * * override_base_ph - ingore the present pH of the reagent, and instead use the default (i.e. if buffers/reactions alter it) * * ignore splitting - Don't call the process that handles reagent spliting in a mob (impure/inverse) - generally leave this false unless you care about REAGENTS_DONOTSPLIT flags (see reagent defines) */ -/datum/reagents/proc/add_reagent(reagent, amount, list/data=null, reagtemp = DEFAULT_REAGENT_TEMPERATURE, added_purity = null, added_ph, no_react = FALSE, override_base_ph = FALSE, ignore_splitting = FALSE) - // Prevents small amount problems, as well as zero and below zero amounts. - if(amount <= CHEMICAL_QUANTISATION_LEVEL) +/datum/reagents/proc/add_reagent( + datum/reagent/reagent_type, + amount, + list/data = null, + reagtemp = DEFAULT_REAGENT_TEMPERATURE, + added_purity = null, + added_ph, + no_react = FALSE, + override_base_ph = FALSE, + ignore_splitting = FALSE +) + if(!ispath(reagent_type)) + stack_trace("invalid reagent passed to add reagent [reagent_type]") return FALSE if(!IS_FINITE(amount)) - stack_trace("non finite amount passed to add reagent [amount] [reagent]") + stack_trace("non finite amount passed to add reagent [amount] [reagent_type]") return FALSE - if(SEND_SIGNAL(src, COMSIG_REAGENTS_PRE_ADD_REAGENT, reagent, amount, reagtemp, data, no_react) & COMPONENT_CANCEL_REAGENT_ADD) + if(SEND_SIGNAL(src, COMSIG_REAGENTS_PRE_ADD_REAGENT, reagent_type, amount, reagtemp, data, no_react) & COMPONENT_CANCEL_REAGENT_ADD) return FALSE - var/datum/reagent/glob_reagent = GLOB.chemical_reagents_list[reagent] + // Prevents small amount problems, as well as zero and below zero amounts. + amount = FLOOR(amount, CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE + + var/datum/reagent/glob_reagent = GLOB.chemical_reagents_list[reagent_type] if(!glob_reagent) - stack_trace("[my_atom] attempted to add a reagent called '[reagent]' which doesn't exist. ([usr])") + stack_trace("[my_atom] attempted to add a reagent called '[reagent_type]' which doesn't exist. ([usr])") return FALSE if(isnull(added_purity)) //Because purity additions can be 0 added_purity = glob_reagent.creation_purity //Usually 1 @@ -227,8 +110,8 @@ //Split up the reagent if it's in a mob var/has_split = FALSE if(!ignore_splitting && (flags & REAGENT_HOLDER_ALIVE)) //Stomachs are a pain - they will constantly call on_mob_add unless we split on addition to stomachs, but we also want to make sure we don't double split - var/adjusted_vol = process_mob_reagent_purity(glob_reagent, amount, added_purity) - if(!adjusted_vol) //If we're inverse or FALSE cancel addition + var/adjusted_vol = FLOOR(process_mob_reagent_purity(glob_reagent, amount, added_purity), CHEMICAL_QUANTISATION_LEVEL) + if(adjusted_vol <= 0) //If we're inverse or FALSE cancel addition return TRUE /* We return true here because of #63301 The only cases where this will be false or 0 if its an inverse chem, an impure chem of 0 purity (highly unlikely if even possible), or if glob_reagent is null (which shouldn't happen at all as there's a check for that a few lines up), @@ -240,7 +123,7 @@ update_total() var/cached_total = total_volume if(cached_total + amount > maximum_volume) - amount = (maximum_volume - cached_total) //Doesnt fit in. Make it disappear. shouldn't happen. Will happen. + amount = FLOOR(maximum_volume - cached_total, CHEMICAL_QUANTISATION_LEVEL) //Doesnt fit in. Make it disappear. shouldn't happen. Will happen. if(amount <= 0) return FALSE @@ -255,13 +138,13 @@ //add the reagent to the existing if it exists for(var/datum/reagent/iter_reagent as anything in cached_reagents) - if(iter_reagent.type == reagent) + if(iter_reagent.type == reagent_type) if(override_base_ph) added_ph = iter_reagent.ph iter_reagent.purity = ((iter_reagent.creation_purity * iter_reagent.volume) + (added_purity * amount)) /(iter_reagent.volume + amount) //This should add the purity to the product iter_reagent.creation_purity = iter_reagent.purity iter_reagent.ph = ((iter_reagent.ph*(iter_reagent.volume))+(added_ph*amount))/(iter_reagent.volume+amount) - iter_reagent.volume += round(amount, CHEMICAL_QUANTISATION_LEVEL) + iter_reagent.volume = FLOOR(iter_reagent.volume + amount, CHEMICAL_QUANTISATION_LEVEL) update_total() iter_reagent.on_merge(data, amount) @@ -278,7 +161,7 @@ return TRUE //otherwise make a new one - var/datum/reagent/new_reagent = new reagent(data) + var/datum/reagent/new_reagent = new reagent_type(data) cached_reagents += new_reagent new_reagent.holder = src new_reagent.volume = amount @@ -306,40 +189,68 @@ handle_reactions() return TRUE -/// Like add_reagent but you can enter a list. Format it like this: list(/datum/reagent/toxin = 10, "beer" = 15) -/datum/reagents/proc/add_reagent_list(list/list_reagents, list/data=null) +/** + * Like add_reagent but you can enter a list. + * Arguments + * + * * [list_reagents][list] - list to add. Format it like this: list(/datum/reagent/toxin = 10, "beer" = 15) + * * [data][list] - additional data to add + */ +/datum/reagents/proc/add_reagent_list(list/list_reagents, list/data = null) for(var/r_id in list_reagents) var/amt = list_reagents[r_id] add_reagent(r_id, amt, data) -/// Remove a specific reagent -/datum/reagents/proc/remove_reagent(reagent, amount, safety = TRUE)//Added a safety check for the trans_id_to - if(isnull(amount)) - stack_trace("null amount passed to reagent code") +/** + * Removes a specific reagent. can supress reactions if needed + * Arguments + * + * * [reagent_type][datum/reagent] - the type of reagent + * * amount - the volume to remove + * * safety - if FALSE will initiate reactions upon removing. used for trans_id_to + */ +/datum/reagents/proc/remove_reagent(datum/reagent/reagent_type, amount, safety = TRUE) + if(!ispath(reagent_type)) + stack_trace("invalid reagent passed to remove reagent [reagent_type]") + return FALSE + + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to remove reagent [amount] [reagent_type]") return FALSE - if(amount < 0 || !IS_FINITE(amount)) - stack_trace("invalid number passed to remove_reagent [amount]") + // Prevents small amount problems, as well as zero and below zero amounts. + amount = FLOOR(amount, CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) return FALSE var/list/cached_reagents = reagent_list for(var/datum/reagent/cached_reagent as anything in cached_reagents) - if(cached_reagent.type == reagent) - //clamp the removal amount to be between current reagent amount - //and zero, to prevent removing more than the holder has stored - amount = clamp(amount, 0, cached_reagent.volume) - cached_reagent.volume -= amount + if(cached_reagent.type == reagent_type) + cached_reagent.volume = FLOOR(max(cached_reagent.volume - amount, 0), CHEMICAL_QUANTISATION_LEVEL) update_total() if(!safety)//So it does not handle reactions when it need not to handle_reactions() - SEND_SIGNAL(src, COMSIG_REAGENTS_REM_REAGENT, QDELING(cached_reagent) ? reagent : cached_reagent, amount) + SEND_SIGNAL(src, COMSIG_REAGENTS_REM_REAGENT, QDELING(cached_reagent) ? reagent_type : cached_reagent, amount) return TRUE return FALSE -/// Remove an amount of reagents without caring about what they are +/** + * Removes a reagent at random by the specified amount + * Arguments + * + * * amount- the volume to remove + */ /datum/reagents/proc/remove_any(amount = 1) + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to remove any reagent [amount]") + return FALSE + + amount = FLOOR(amount, CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE + var/list/cached_reagents = reagent_list var/total_removed = 0 var/current_list_element = 1 @@ -356,10 +267,10 @@ if(current_list_element > cached_reagents.len) current_list_element = 1 - var/datum/reagent/R = cached_reagents[current_list_element] - var/remove_amt = min(amount-total_removed,round(amount/rand(2,initial_list_length),round(amount/10,0.01))) //double round to keep it at a somewhat even spread relative to amount without getting funky numbers. + var/datum/reagent/target_holder = cached_reagents[current_list_element] + var/remove_amt = min(amount - total_removed, round(amount / rand(2, initial_list_length), round(amount / 10, 0.01))) //double round to keep it at a somewhat even spread relative to amount without getting funky numbers. //min ensures we don't go over amount. - remove_reagent(R.type, remove_amt) + remove_reagent(target_holder.type, remove_amt) current_list_element++ total_removed += remove_amt @@ -368,22 +279,62 @@ handle_reactions() return total_removed //this should be amount unless the loop is prematurely broken, in which case it'll be lower. It shouldn't ever go OVER amount. -/// Removes all reagents from this holder +/** + * Removes all reagents by an amount equal to + * [amount specified] / total volume present in this holder + * Arguments + * + * * amount - the volume of each reagent + */ + /datum/reagents/proc/remove_all(amount = 1) + if(!total_volume) + return FALSE + + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to remove all reagents [amount]") + return FALSE + + // Prevents small amount problems, as well as zero and below zero amounts. + amount = FLOOR(amount, CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE + var/list/cached_reagents = reagent_list - if(total_volume > 0) - var/part = amount / total_volume - for(var/datum/reagent/reagent as anything in cached_reagents) - remove_reagent(reagent.type, reagent.volume * part) + var/part = amount / total_volume + var/remove_amount + var/removed_amount = 0 - //finish_reacting() //A just in case - update total is in here - should be unneeded, make sure to test this - handle_reactions() - return amount + for(var/datum/reagent/reagent as anything in cached_reagents) + remove_amount = FLOOR(reagent.volume * part, CHEMICAL_QUANTISATION_LEVEL) + remove_reagent(reagent.type, remove_amount) + removed_amount += remove_amount + + handle_reactions() + return removed_amount + +/** + * Removes all reagent of X type + * Arguments + * + * * [reagent_type][datum/reagent] - the reagent typepath we are trying to remove + * * amount - the volume of reagent to remove + * * strict - If TRUE will also remove childs of this reagent type + */ +/datum/reagents/proc/remove_all_type(datum/reagent/reagent_type, amount, strict = 0, safety = 1) + if(!ispath(reagent_type)) + stack_trace("invalid reagent path passed to remove all type [reagent_type]") + return FALSE + + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to remove all type reagent [amount] [reagent_type]") + return FALSE + + // Prevents small amount problems, as well as zero and below zero amounts. + amount = FLOOR(amount, CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE -/// Removes all reagent of X type. @strict set to 1 determines whether the childs of the type are included. -/datum/reagents/proc/remove_all_type(reagent_type, amount, strict = 0, safety = 1) - if(!isnum(amount)) - return 1 var/list/cached_reagents = reagent_list var/has_removed_reagent = 0 @@ -403,37 +354,58 @@ return has_removed_reagent -/// Fuck this one reagent -/datum/reagents/proc/del_reagent(target_reagent_typepath) +/** + * Removes an specific reagent from this holder + * Arguments + * + * * [target_reagent_typepath][datum/reagent] - type typepath of the reagent to remove + */ +/datum/reagents/proc/del_reagent(datum/reagent/target_reagent_typepath) + if(!ispath(target_reagent_typepath)) + stack_trace("invalid reagent path passed to del reagent [target_reagent_typepath]") + return FALSE + + //setting the volume to 0 will allow update_total() to clear it up for us var/list/cached_reagents = reagent_list for(var/datum/reagent/reagent as anything in cached_reagents) if(reagent.type == target_reagent_typepath) - if(isliving(my_atom)) - if(reagent.metabolizing) - reagent.metabolizing = FALSE - reagent.on_mob_end_metabolize(my_atom) - reagent.on_mob_delete(my_atom) - - reagent_list -= reagent - LAZYREMOVE(previous_reagent_list, reagent.type) - qdel(reagent) + reagent.volume = 0 update_total() - SEND_SIGNAL(src, COMSIG_REAGENTS_DEL_REAGENT, reagent) - return TRUE + return TRUE + + return FALSE + +/** + * Turn one reagent into another, preserving volume, temp, purity, ph + * Arguments + * + * * [source_reagent_typepath][/datum/reagent] - the typepath of the reagent you are trying to convert + * * [target_reagent_typepath][/datum/reagent] - the final typepath the source_reagent_typepath will be converted into + * * multiplier - the multiplier applied on the source_reagent_typepath volume before converting + * * include_source_subtypes- if TRUE will convert all subtypes of source_reagent_typepath into target_reagent_typepath as well + */ +/datum/reagents/proc/convert_reagent( + datum/reagent/source_reagent_typepath, + datum/reagent/target_reagent_typepath, + multiplier = 1, + include_source_subtypes = FALSE +) + if(!ispath(source_reagent_typepath)) + stack_trace("invalid reagent path passed to convert reagent [source_reagent_typepath]") + return FALSE -/// Turn one reagent into another, preserving volume, temp, purity, ph -/datum/reagents/proc/convert_reagent(source_reagent_typepath, target_reagent_typepath, multiplier = 1, include_source_subtypes = FALSE) var/reagent_amount var/reagent_purity var/reagent_ph if(include_source_subtypes) reagent_ph = ph var/weighted_purity + var/list/reagent_type_list = typecacheof(source_reagent_typepath) for(var/datum/reagent/reagent as anything in reagent_list) - if(reagent.type in typecacheof(source_reagent_typepath)) + if(reagent.type in reagent_type_list) weighted_purity += reagent.volume * reagent.purity reagent_amount += reagent.volume - remove_reagent(reagent.type, reagent.volume) + remove_reagent(reagent.type, reagent.volume * multiplier) reagent_purity = weighted_purity / reagent_amount else var/datum/reagent/source_reagent = get_reagent(source_reagent_typepath) @@ -443,40 +415,40 @@ remove_reagent(source_reagent_typepath, reagent_amount) add_reagent(target_reagent_typepath, reagent_amount * multiplier, reagtemp = chem_temp, added_purity = reagent_purity, added_ph = reagent_ph) -//Converts the creation_purity to purity -/datum/reagents/proc/uncache_creation_purity(id) - var/datum/reagent/R = has_reagent(id) - if(!R) - return - R.purity = R.creation_purity - -/// Remove every reagent except this one -/datum/reagents/proc/isolate_reagent(reagent) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/cached_reagent as anything in cached_reagents) - if(cached_reagent.type != reagent) - del_reagent(cached_reagent.type) - update_total() - /// Removes all reagents /datum/reagents/proc/clear_reagents() var/list/cached_reagents = reagent_list + + //setting volume to 0 will allow update_total() to clean it up for(var/datum/reagent/reagent as anything in cached_reagents) - del_reagent(reagent.type) - SEND_SIGNAL(src, COMSIG_REAGENTS_CLEAR_REAGENTS) + reagent.volume = 0 + update_total() + SEND_SIGNAL(src, COMSIG_REAGENTS_CLEAR_REAGENTS) /** - * Check if this holder contains this reagent. - * Reagent takes a PATH to a reagent. - * Amount checks for having a specific amount of that chemical. + * Check if this holder contains this reagent. Reagent takes a PATH to a reagent * Needs matabolizing takes into consideration if the chemical is metabolizing when it's checked. - * Check subtypes controls whether it should it should also include subtypes: ispath(type, reagent) versus type == reagent. + * Arguments + * + * * [target_reagent][datum/reagent] - the reagent typepath to check for + * * amount - checks for having a specific amount of that chemical + * * needs_metabolizing - takes into consideration if the chemical is matabolizing when it's checked. + * * check_subtypes - controls whether it should it should also include subtypes: ispath(type, reagent) versus type == reagent. */ -/datum/reagents/proc/has_reagent(reagent, amount = -1, needs_metabolizing = FALSE, check_subtypes = FALSE) +/datum/reagents/proc/has_reagent( + datum/reagent/target_reagent, + amount = -1, + needs_metabolizing = FALSE, + check_subtypes = FALSE +) + if(!ispath(target_reagent)) + stack_trace("invalid reagent path passed to has reagent [target_reagent]") + return FALSE + var/list/cached_reagents = reagent_list for(var/datum/reagent/holder_reagent as anything in cached_reagents) - if (check_subtypes ? ispath(holder_reagent.type, reagent) : holder_reagent.type == reagent) + if (check_subtypes ? ispath(holder_reagent.type, target_reagent) : holder_reagent.type == target_reagent) if(!amount) if(needs_metabolizing && !holder_reagent.metabolizing) if(check_subtypes) @@ -484,7 +456,7 @@ return FALSE return holder_reagent else - if(round(holder_reagent.volume, CHEMICAL_QUANTISATION_LEVEL) >= amount) + if(holder_reagent.volume >= amount) if(needs_metabolizing && !holder_reagent.metabolizing) if(check_subtypes) continue @@ -497,7 +469,10 @@ /** * Check if this holder contains a reagent with a chemical_flags containing this flag * Reagent takes the bitflag to search for - * Amount checks for having a specific amount of reagents matching that chemical + * + * Arguments + * * chemical_flag - the flag to check for + * * amount - checks for having a specific amount of reagents matching that chemical */ /datum/reagents/proc/has_chemical_flag(chemical_flag, amount = 0) var/found_amount = 0 @@ -521,140 +496,156 @@ * * no_react - passed through to [/datum/reagents/proc/add_reagent] * * mob/transferred_by - used for logging * * remove_blacklisted - skips transferring of reagents without REAGENT_CAN_BE_SYNTHESIZED in chemical_flags - * * methods - passed through to [/datum/reagents/proc/expose_single] and [/datum/reagent/proc/on_transfer] - * * show_message - passed through to [/datum/reagents/proc/expose_single] - * * round_robin - if round_robin=TRUE, so transfer 5 from 15 water, 15 sugar and 15 plasma becomes 10, 15, 15 instead of 13.3333, 13.3333 13.3333. Good if you hate floating point errors + * * methods - passed through to [/datum/reagents/proc/expose_multiple] and [/datum/reagent/proc/on_transfer] + * * show_message - passed through to [/datum/reagents/proc/expose_multiple] * * ignore_stomach - when using methods INGEST will not use the stomach as the target */ -/datum/reagents/proc/trans_to(obj/target, amount = 1, multiplier = 1, preserve_data = TRUE, no_react = FALSE, mob/transferred_by, remove_blacklisted = FALSE, methods = NONE, show_message = TRUE, round_robin = FALSE, ignore_stomach = FALSE) - var/list/cached_reagents = reagent_list - if(!target || !total_volume) - return - if(amount < 0) +/datum/reagents/proc/trans_to( + obj/target, + amount = 1, + multiplier = 1, + preserve_data = TRUE, + no_react = FALSE, + mob/transferred_by, + remove_blacklisted = FALSE, + methods = NONE, + show_message = TRUE, + ignore_stomach = FALSE +) + if(QDELETED(target) || !total_volume) return - var/cached_amount = amount + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to trans_to [amount] amount of reagents") + return FALSE + + var/list/cached_reagents = reagent_list + var/atom/target_atom - var/datum/reagents/R + var/datum/reagents/target_holder if(istype(target, /datum/reagents)) - R = target - target_atom = R.my_atom + target_holder = target + target_atom = target_holder.my_atom else if(!ignore_stomach && (methods & INGEST) && iscarbon(target)) var/mob/living/carbon/eater = target var/obj/item/organ/internal/stomach/belly = eater.get_organ_slot(ORGAN_SLOT_STOMACH) if(!belly) - eater.expel_ingested(my_atom, amount) + var/expel_amount = FLOOR(amount, CHEMICAL_QUANTISATION_LEVEL) + if(expel_amount > 0 ) + eater.expel_ingested(my_atom, expel_amount) return - R = belly.reagents + target_holder = belly.reagents target_atom = belly else if(!target.reagents) return else - R = target.reagents + target_holder = target.reagents target_atom = target + var/cached_amount = amount + + // Prevents small amount problems, as well as zero and below zero amounts. + amount = FLOOR(min(amount * multiplier, total_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE + //Set up new reagents to inherit the old ongoing reactions if(!no_react) - transfer_reactions(R) + transfer_reactions(target_holder) - amount = min(min(amount, src.total_volume), R.maximum_volume-R.total_volume) var/trans_data = null - var/transfer_log = list() - var/r_to_send = list() // Validated list of reagents to be exposed - var/reagents_to_remove = list() - if(!round_robin) - var/part = amount / src.total_volume - for(var/datum/reagent/reagent as anything in cached_reagents) - if(remove_blacklisted && !(reagent.chemical_flags & REAGENT_CAN_BE_SYNTHESIZED)) - continue - var/transfer_amount = reagent.volume * part - if(preserve_data) - trans_data = copy_data(reagent) - if(reagent.intercept_reagents_transfer(R, cached_amount))//Use input amount instead. - continue - if(!R.add_reagent(reagent.type, transfer_amount * multiplier, trans_data, chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT)) //we only handle reaction after every reagent has been transferred. - continue - if(methods) - r_to_send += reagent + var/list/transfer_log = list() + var/list/r_to_send = list() // Validated list of reagents to be exposed + var/list/reagents_to_remove = list() - reagents_to_remove += reagent - - if(isorgan(target_atom)) - R.expose_multiple(r_to_send, target, methods, part, show_message) - else - R.expose_multiple(r_to_send, target_atom, methods, part, show_message) - - for(var/datum/reagent/reagent as anything in reagents_to_remove) - var/transfer_amount = reagent.volume * part - if(methods) - reagent.on_transfer(target_atom, methods, transfer_amount * multiplier) - remove_reagent(reagent.type, transfer_amount) - var/list/reagent_qualities = list(REAGENT_TRANSFER_AMOUNT = transfer_amount, REAGENT_PURITY = reagent.purity) - transfer_log[reagent.type] = reagent_qualities + var/part = amount / total_volume + var/transfer_amount + var/transfered_amount = 0 - else - var/to_transfer = amount - for(var/datum/reagent/reagent as anything in cached_reagents) - if(!to_transfer) - break - if(remove_blacklisted && !(reagent.chemical_flags & REAGENT_CAN_BE_SYNTHESIZED)) - continue - if(preserve_data) - trans_data = copy_data(reagent) - var/transfer_amount = amount - if(amount > reagent.volume) - transfer_amount = reagent.volume - if(reagent.intercept_reagents_transfer(R, cached_amount))//Use input amount instead. - continue - if(!R.add_reagent(reagent.type, transfer_amount * multiplier, trans_data, chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT)) //we only handle reaction after every reagent has been transferred. - continue - to_transfer = max(to_transfer - transfer_amount , 0) - if(methods) - if(isorgan(target_atom)) - R.expose_single(reagent, target, methods, transfer_amount, show_message) - else - R.expose_single(reagent, target_atom, methods, transfer_amount, show_message) - reagent.on_transfer(target_atom, methods, transfer_amount * multiplier) - remove_reagent(reagent.type, transfer_amount) - var/list/reagent_qualities = list(REAGENT_TRANSFER_AMOUNT = transfer_amount, REAGENT_PURITY = reagent.purity) - transfer_log[reagent.type] = reagent_qualities + //first add reagents to target + for(var/datum/reagent/reagent as anything in cached_reagents) + if(remove_blacklisted && !(reagent.chemical_flags & REAGENT_CAN_BE_SYNTHESIZED)) + continue + if(preserve_data) + trans_data = copy_data(reagent) + if(reagent.intercept_reagents_transfer(target_holder, cached_amount)) + continue + transfer_amount = FLOOR(reagent.volume * part, CHEMICAL_QUANTISATION_LEVEL) + if(!target_holder.add_reagent(reagent.type, transfer_amount, trans_data, chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT)) //we only handle reaction after every reagent has been transferred. + continue + if(methods) + r_to_send += reagent + reagents_to_remove += list(list("R" = reagent, "T" = transfer_amount)) + transfered_amount += transfer_amount + + //expose target to reagent changes + target_holder.expose_multiple(r_to_send, isorgan(target_atom) ? target : target_atom, methods, part, show_message) + + //remove chemicals that were added above + for(var/list/data as anything in reagents_to_remove) + var/datum/reagent/reagent = data["R"] + transfer_amount = data["T"] + if(methods) + reagent.on_transfer(target_atom, methods, transfer_amount) + remove_reagent(reagent.type, transfer_amount) + transfer_log[reagent.type] = list(REAGENT_TRANSFER_AMOUNT = transfer_amount, REAGENT_PURITY = reagent.purity) if(transferred_by && target_atom) target_atom.add_hiddenprint(transferred_by) //log prints so admins can figure out who touched it last. log_combat(transferred_by, target_atom, "transferred reagents ([get_external_reagent_log_string(transfer_log)]) from [my_atom] to") update_total() - R.update_total() + target_holder.update_total() if(!no_react) - R.handle_reactions() + target_holder.handle_reactions() src.handle_reactions() - return amount + return transfered_amount -/// Transfer a specific reagent id to the target object -/datum/reagents/proc/trans_id_to(obj/target, reagent, amount=1, preserve_data=1)//Not sure why this proc didn't exist before. It does now! /N - var/list/cached_reagents = reagent_list - if (!target) +/** + * Transfer a specific reagent id to the target object + * Arguments + * + * * [target][obj] - the target to transfer reagents to + * * [reagent_type][datum/reagent] - the type of reagent to transfer to the target + * * amount - volume to transfer + * * preserve_data- if TRUE reagent user data will remain preserved + */ +/datum/reagents/proc/trans_id_to( + obj/target, + datum/reagent/reagent_type, + amount = 1, + preserve_data = 1 +) + if (QDELETED(target) || !total_volume) return + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to trans_id_to [amount] [reagent_type]") + return FALSE + + var/cached_amount = amount + + var/available_volume = get_reagent_amount(reagent_type) var/datum/reagents/holder if(istype(target, /datum/reagents)) holder = target - else if(target.reagents && total_volume > 0 && get_reagent_amount(reagent)) + else if(target.reagents && available_volume) holder = target.reagents else return - if(amount < 0) + + // Prevents small amount problems, as well as zero and below zero amounts. + amount = FLOOR(min(amount, available_volume, holder.maximum_volume - holder.total_volume), CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) return - var/cached_amount = amount - if(get_reagent_amount(reagent) < amount) - amount = get_reagent_amount(reagent) - amount = min(round(amount, CHEMICAL_VOLUME_ROUNDING), holder.maximum_volume - holder.total_volume) + var/list/cached_reagents = reagent_list + var/trans_data = null for (var/looping_through_reagents in cached_reagents) var/datum/reagent/current_reagent = looping_through_reagents - if(current_reagent.type == reagent) + if(current_reagent.type == reagent_type) if(preserve_data) trans_data = current_reagent.data if(current_reagent.intercept_reagents_transfer(holder, cached_amount))//Use input amount instead. @@ -669,12 +660,29 @@ holder.handle_reactions() return amount -/// Copies the reagents to the target object -/datum/reagents/proc/copy_to(obj/target, amount = 1, multiplier = 1, preserve_data = TRUE, no_react = FALSE) - var/list/cached_reagents = reagent_list - if(!target || !total_volume) +/** + * Copies the reagents to the target object + * Arguments + * + * * [target][obj] - the target to transfer reagents to + * * multiplier - the multiplier applied on all reagent volumes before transfering + * * preserve_data - preserve user data of all reagents after transfering + * * no_react - if TRUE will not handle reactions + */ +/datum/reagents/proc/copy_to( + atom/target, + amount = 1, + multiplier = 1, + preserve_data = TRUE, + no_react = FALSE +) + if(QDELETED(target) || !total_volume) return + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to copy_to [amount] amount of reagents") + return FALSE + var/datum/reagents/target_holder if(istype(target, /datum/reagents)) target_holder = target @@ -683,17 +691,24 @@ return target_holder = target.reagents - if(amount < 0) + // Prevents small amount problems, as well as zero and below zero amounts. + amount = FLOOR(min(amount * multiplier, total_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) return - amount = min(min(amount, total_volume), target_holder.maximum_volume - target_holder.total_volume) + var/list/cached_reagents = reagent_list var/part = amount / total_volume + var/transfer_amount + var/transfered_amount = 0 var/trans_data = null + for(var/datum/reagent/reagent as anything in cached_reagents) - var/copy_amount = reagent.volume * part + transfer_amount = FLOOR(reagent.volume * part, CHEMICAL_QUANTISATION_LEVEL) if(preserve_data) trans_data = reagent.data - target_holder.add_reagent(reagent.type, copy_amount * multiplier, trans_data, chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) + if(!target_holder.add_reagent(reagent.type, transfer_amount, trans_data, chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT)) + continue + transfered_amount += transfer_amount if(!no_react) // pass over previous ongoing reactions before handle_reactions is called @@ -702,10 +717,14 @@ target_holder.update_total() target_holder.handle_reactions() - return amount + return transfered_amount -///Multiplies the reagents inside this holder by a specific amount -/datum/reagents/proc/multiply_reagents(multiplier=1) +/** + * Multiplies the reagents inside this holder by a specific amount + * Arguments + * * multiplier - the amount to multiply each reagent by + */ +/datum/reagents/proc/multiply_reagents(multiplier = 1) var/list/cached_reagents = reagent_list if(!total_volume) return @@ -784,9 +803,9 @@ // skip metabolizing effects for small units of toxins if(istype(reagent, /datum/reagent/toxin) && liver && !dead) var/datum/reagent/toxin/toxin = reagent - var/amount = round(toxin.volume, CHEMICAL_QUANTISATION_LEVEL) + var/amount = toxin.volume if(belly) - amount += belly.reagents.get_reagent_amount(toxin.type) + amount = FLOOR(amount + belly.reagents.get_reagent_amount(toxin.type), CHEMICAL_QUANTISATION_LEVEL) if(amount <= liver_tolerance) owner.reagents.remove_reagent(toxin.type, toxin.metabolization_rate * owner.metabolism_efficiency * seconds_per_tick) @@ -841,7 +860,13 @@ need_mob_update += reagent.on_mob_dead(owner, seconds_per_tick) return need_mob_update -/// Signals that metabolization has stopped, triggering the end of trait-based effects +/** + * Signals that metabolization has stopped, triggering the end of trait-based effects + * Arguments + * + * * [C][mob/living/carbon] - the mob to end metabolization on + * * keep_liverless - if true will work without a liver + */ /datum/reagents/proc/end_metabolization(mob/living/carbon/C, keep_liverless = TRUE) var/list/cached_reagents = reagent_list for(var/datum/reagent/reagent as anything in cached_reagents) @@ -883,7 +908,14 @@ return FALSE //prevent addition return added_volume -///Processes any chems that have the REAGENT_IGNORE_STASIS bitflag ONLY +/** + * Processes any chems that have the REAGENT_IGNORE_STASIS bitflag ONLY + * Arguments + * + * * [owner][mob/living/carbon] - the mob we are doing stasis handlng on + * * seconds_per_tick - passed from process + * * times_fired - number of times to metabolize this reagent + */ /datum/reagents/proc/handle_stasis_chems(mob/living/carbon/owner, seconds_per_tick, times_fired) var/need_mob_update = FALSE for(var/datum/reagent/reagent as anything in reagent_list) @@ -894,19 +926,6 @@ owner.updatehealth() update_total() -/** - * Calls [/datum/reagent/proc/on_move] on every reagent in this holder - * - * Arguments: - * * atom/A - passed to on_move - * * Running - passed to on_move - */ -/datum/reagents/proc/conditional_update_move(atom/A, Running = 0) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - reagent.on_move(A, Running) - update_total() - /** * Calls [/datum/reagent/proc/on_update] on every reagent in this holder * @@ -968,16 +987,16 @@ var/granularity = 1 if(!(reaction.reaction_flags & REACTION_INSTANT)) - granularity = CHEMICAL_VOLUME_MINIMUM + granularity = CHEMICAL_QUANTISATION_LEVEL var/list/cached_required_reagents = reaction.required_reagents for(var/req_reagent in cached_required_reagents) - if(!has_reagent(req_reagent, (cached_required_reagents[req_reagent]*granularity))) + if(!has_reagent(req_reagent, (cached_required_reagents[req_reagent] * granularity))) continue reaction_loop var/list/cached_required_catalysts = reaction.required_catalysts for(var/_catalyst in cached_required_catalysts) - if(!has_reagent(_catalyst, (cached_required_catalysts[_catalyst]*granularity))) + if(!has_reagent(_catalyst, (cached_required_catalysts[_catalyst] * granularity))) continue reaction_loop if(cached_my_atom) @@ -1090,7 +1109,7 @@ * This ends a single instance of an ongoing reaction * * Arguments: -* * E - the equilibrium that will be ended +* * [equilibrium][datum/equilibrium] - the equilibrium that will be ended * Returns: * * mix_message - the associated mix message of a reaction */ @@ -1115,7 +1134,6 @@ /* * This stops the holder from processing at the end of a series of reactions (i.e. when all the equilibriums are completed) -* * Also resets reaction variables to be null/empty/FALSE so that it can restart correctly in the future */ /datum/reagents/proc/finish_reacting() @@ -1123,7 +1141,7 @@ is_reacting = FALSE //Cap off values for(var/datum/reagent/reagent as anything in reagent_list) - reagent.volume = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING)//To prevent runaways. + reagent.volume = FLOOR(reagent.volume, CHEMICAL_QUANTISATION_LEVEL)//To prevent runaways. LAZYNULL(previous_reagent_list) //reset it to 0 - because any change will be different now. update_total() if(!QDELING(src)) @@ -1223,7 +1241,7 @@ var/datum/cached_my_atom = my_atom var/multiplier = INFINITY for(var/reagent in cached_required_reagents) - multiplier = min(multiplier, round(get_reagent_amount(reagent) / cached_required_reagents[reagent])) + multiplier = FLOOR(min(multiplier, get_reagent_amount(reagent) / cached_required_reagents[reagent]), CHEMICAL_QUANTISATION_LEVEL) if(multiplier == 0)//Incase we're missing reagents - usually from on_reaction being called in an equlibrium when the results.len == 0 handlier catches a misflagged reaction return FALSE @@ -1261,43 +1279,55 @@ selected_reaction.on_reaction(src, null, multiplier) -///Possibly remove - see if multiple instant reactions is okay (Though, this "sorts" reactions by temp decending) -///Presently unused -/datum/reagents/proc/get_priority_instant_reaction(list/possible_reactions) - if(!length(possible_reactions)) - return FALSE - var/datum/chemical_reaction/selected_reaction = possible_reactions[1] - //select the reaction with the most extreme temperature requirements - for(var/datum/chemical_reaction/competitor as anything in possible_reactions) - if(selected_reaction.is_cold_recipe) - if(competitor.required_temp <= selected_reaction.required_temp) - selected_reaction = competitor - else - if(competitor.required_temp >= selected_reaction.required_temp) - selected_reaction = competitor - return selected_reaction - /// Updates [/datum/reagents/var/total_volume] /datum/reagents/proc/update_total() var/list/cached_reagents = reagent_list - . = 0 // This is a relatively hot proc. - var/total_ph = 0 // I know I know, I'm sorry - for(var/datum/reagent/reagent as anything in cached_reagents) - if((reagent.volume < 0.05) && !is_reacting) - del_reagent(reagent.type) - else if(reagent.volume <= CHEMICAL_VOLUME_MINIMUM)//For clarity - del_reagent(reagent.type) - else - . += reagent.volume - total_ph += (reagent.ph * reagent.volume) - total_volume = . + var/list/deleted_reagents = list() + var/chem_index = 1 + var/num_reagents = length(cached_reagents) + var/total_ph = 0 + . = 0 + + //responsible for removing reagents and computing total ph & volume + //all it's code was taken out of del_reagent() initially for efficiency purposes + while(chem_index <= num_reagents) + var/datum/reagent/reagent = cached_reagents[chem_index] + chem_index += 1 - if(!.) // No volume, default to the base + //remove very small amounts of reagents + if((reagent.volume <= 0.05 && !is_reacting) || reagent.volume <= CHEMICAL_QUANTISATION_LEVEL) + //end metabolization + if(isliving(my_atom)) + if(reagent.metabolizing) + reagent.metabolizing = FALSE + reagent.on_mob_end_metabolize(my_atom) + reagent.on_mob_delete(my_atom) + + //removing it and store in a seperate list for processing later + cached_reagents -= reagent + LAZYREMOVE(previous_reagent_list, reagent.type) + deleted_reagents += reagent + + //move pointer back so we don't overflow & decrease length + chem_index -= 1 + num_reagents -= 1 + continue + + //compute volume & ph like we would normally + . += reagent.volume + total_ph += (reagent.ph * reagent.volume) + + //assign the final values + total_volume = . + if(!.) ph = CHEMICAL_NORMAL_PH - return . - //Keep limited // should really be defines - ph = clamp(total_ph/total_volume, 0, 14) + else + ph = clamp(total_ph / total_volume, 0, 14) + //now send the signals after the volume & ph has been computed + for(var/datum/reagent/deleted_reagent as anything in deleted_reagents) + SEND_SIGNAL(src, COMSIG_REAGENTS_DEL_REAGENT, deleted_reagent) + qdel(deleted_reagent) /** * Applies the relevant expose_ proc for every reagent in this holder @@ -1339,56 +1369,74 @@ return A.expose_reagents(reagents, src, methods, volume_modifier, show_message) -/// Same as [/datum/reagents/proc/expose] but only for one reagent -/datum/reagents/proc/expose_single(datum/reagent/R, atom/A, methods = TOUCH, volume_modifier = 1, show_message = TRUE) - if(isnull(A)) - return null - - if(ispath(R)) - R = get_reagent(R) - if(isnull(R)) - return null - - // Yes, we need the parentheses. - return A.expose_reagents(list((R) = R.volume * volume_modifier), src, methods, volume_modifier, show_message) - /// Is this holder full or not /datum/reagents/proc/holder_full() return total_volume >= maximum_volume -/// Get the amount of this reagent -/datum/reagents/proc/get_reagent_amount(reagent, include_subtypes = FALSE) +/** + * Get the amount of this reagent or the sum of all its subtypes if specified + * Arguments + * * [reagent][datum/reagent] - the typepath of the reagent to look for + * * include_subtypes - if TRUE returns the sum of volumes of all subtypes of the above param reagent + */ +/datum/reagents/proc/get_reagent_amount(datum/reagent/reagent, include_subtypes = FALSE) + if(!ispath(reagent)) + stack_trace("invalid path passed to get_reagent_amount [reagent]") + return 0 + var/list/cached_reagents = reagent_list var/total_amount = 0 for(var/datum/reagent/cached_reagent as anything in cached_reagents) if((!include_subtypes && cached_reagent.type == reagent) || (include_subtypes && ispath(cached_reagent.type, reagent))) - total_amount += round(cached_reagent.volume, CHEMICAL_QUANTISATION_LEVEL) - return total_amount + total_amount += cached_reagent.volume + + return FLOOR(total_amount, CHEMICAL_QUANTISATION_LEVEL) +/** + * Gets the sum of volumes of all reagent type paths present in the list + * Arguments + * * [reagents][list] - list of reagent typepaths + */ /datum/reagents/proc/get_multiple_reagent_amounts(list/reagents) var/list/cached_reagents = reagent_list var/total_amount = 0 for(var/datum/reagent/cached_reagent as anything in cached_reagents) if(cached_reagent.type in reagents) - total_amount += round(cached_reagent.volume, CHEMICAL_QUANTISATION_LEVEL) + total_amount += FLOOR(cached_reagent.volume, CHEMICAL_QUANTISATION_LEVEL) return total_amount -/// Get the purity of this reagent -/datum/reagents/proc/get_reagent_purity(reagent) +/** + * Get the purity of this reagent + * Arguments + * * [reagent][datum/reagent] - the typepath of the specific reagent to get purity of + */ +/datum/reagents/proc/get_reagent_purity(datum/reagent/reagent) + if(!ispath(reagent)) + stack_trace("invalid reagent typepath passed to get_reagent_purity [reagent]") + return 0 + var/list/cached_reagents = reagent_list for(var/datum/reagent/cached_reagent as anything in cached_reagents) if(cached_reagent.type == reagent) return round(cached_reagent.purity, 0.01) return 0 -/// Directly set the purity of all contained reagents to a new value +/** + * Directly set the purity of all contained reagents to a new value + * Arguments + * * new_purity - the new purity value + */ /datum/reagents/proc/set_all_reagents_purity(new_purity = 0) var/list/cached_reagents = reagent_list for(var/datum/reagent/cached_reagent as anything in cached_reagents) cached_reagent.purity = max(0, new_purity) -/// Get the average purity of all reagents (or all subtypes of provided typepath) -/datum/reagents/proc/get_average_purity(parent_type = null) +/** + * Get the average purity of all reagents (or all subtypes of provided typepath) + * Arguments + * * [parent_type][datum/reagent] - the typepath of specific reagents to look for + */ +/datum/reagents/proc/get_average_purity(datum/reagent/parent_type = null) var/total_amount var/weighted_purity var/list/cached_reagents = reagent_list @@ -1399,42 +1447,11 @@ weighted_purity += reagent.volume * reagent.purity return weighted_purity / total_amount -/// Get the average nutriment_factor of all consumable reagents -/datum/reagents/proc/get_average_nutriment_factor() - var/consumable_volume - var/weighted_nutriment_factor - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - if(istype(reagent, /datum/reagent/consumable)) - var/datum/reagent/consumable/consumable_reagent = reagent - consumable_volume += consumable_reagent.volume - weighted_nutriment_factor += consumable_reagent.volume * consumable_reagent.nutriment_factor - return weighted_nutriment_factor / consumable_volume - -/// Get a comma separated string of every reagent name in this holder. UNUSED -/datum/reagents/proc/get_reagent_names() - var/list/names = list() - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - names += reagent.name - - return jointext(names, ",") - -/// helper function to preserve data across reactions (needed for xenoarch) -/datum/reagents/proc/get_data(reagent_id) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - if(reagent.type == reagent_id) - return reagent.data - -/// helper function to preserve data across reactions (needed for xenoarch) -/datum/reagents/proc/set_data(reagent_id, new_data) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - if(reagent.type == reagent_id) - reagent.data = new_data - -/// Shallow copies (deep copy of viruses) data from the provided reagent into our copy of that reagent +/** + * Shallow copies (deep copy of viruses) data from the provided reagent into our copy of that reagent + * Arguments + * [current_reagent][datum/reagent] - the reagent(not typepath) to copy data from + */ /datum/reagents/proc/copy_data(datum/reagent/current_reagent) if(!current_reagent || !current_reagent.data) return null @@ -1456,8 +1473,12 @@ return trans_data -/// Get a reference to the reagent if it exists -/datum/reagents/proc/get_reagent(type) +/** + * Get a reference to the reagent if it exists + * Arguments + * * [type][datum/reagent] - the typepath of the reagent to look up + */ +/datum/reagents/proc/get_reagent(datum/reagent/type) var/list/cached_reagents = reagent_list . = locate(type) in cached_reagents @@ -1523,8 +1544,14 @@ return // no div/0 please set_temperature(clamp(chem_temp + (delta_energy / heat_capacity), min_temp, max_temp)) -/// Applies heat to this holder -/datum/reagents/proc/expose_temperature(temperature, coeff=0.02) +/** + * Applies heat to this holder + * Arguments + * + * * temperature - the temperature we to heat/cool by + * * coeff - multiplier to be applied on temp diff between param temp and current temp + */ +/datum/reagents/proc/expose_temperature(temperature, coeff = 0.02) if(istype(my_atom,/obj/item/reagent_containers)) var/obj/item/reagent_containers/RCs = my_atom if(RCs.reagent_flags & NO_REACT) //stasis holders IE cryobeaker @@ -1564,21 +1591,6 @@ for(var/datum/reagent/reagent as anything in reagent_list) reagent.ph = clamp(reagent.ph + value, lower_limit, upper_limit) -/* -* Adjusts the base pH of all of the listed types -* -* - moves it towards acidic -* + moves it towards basic -* Arguments: -* * input_reagents_list - list of reagent objects to adjust -* * value - How much to adjust the base pH by -*/ -/datum/reagents/proc/adjust_specific_reagent_list_ph(list/input_reagents_list, value, lower_limit = 0, upper_limit = 14) - for(var/datum/reagent/reagent as anything in input_reagents_list) - if(!reagent) //We can call this with missing reagents. - continue - reagent.ph = clamp(reagent.ph + value, lower_limit, upper_limit) - /* * Adjusts the base pH of a specific type * @@ -1610,16 +1622,11 @@ for(var/reagent_type in external_list) var/list/qualities = external_list[reagent_type] - data += "[reagent_type] ([round(qualities[REAGENT_TRANSFER_AMOUNT], 0.1)]u, [qualities[REAGENT_PURITY]] purity)" + data += "[reagent_type] ([FLOOR(qualities[REAGENT_TRANSFER_AMOUNT], CHEMICAL_QUANTISATION_LEVEL)]u, [qualities[REAGENT_PURITY]] purity)" return english_list(data) -/** - * Outputs a log-friendly list of reagents based on the internal reagent_list. - * - * Arguments: - * * external_list - Assoc list of (reagent_type) = list(REAGENT_TRANSFER_AMOUNT = amounts, REAGENT_PURITY = purity) - */ +/// Outputs a log-friendly list of reagents based on the internal reagent_list. /datum/reagents/proc/get_reagent_log_string() if(!length(reagent_list)) return "no reagents" @@ -1627,7 +1634,7 @@ var/list/data = list() for(var/datum/reagent/reagent as anything in reagent_list) - data += "[reagent.type] ([round(reagent.volume, 0.1)]u, [reagent.purity] purity)" + data += "[reagent.type] ([FLOOR(reagent.volume, CHEMICAL_QUANTISATION_LEVEL)]u, [reagent.purity] purity)" return english_list(data) @@ -1978,7 +1985,7 @@ ui_reaction_id = text2path(params["id"]) return TRUE if("search_reagents") - var/input_reagent = tgui_input_list(usr, "Select reagent", "Reagent", GLOB.chemical_name_list) + var/input_reagent = tgui_input_list(usr, "Select reagent", "Reagent", GLOB.name2reagent) input_reagent = get_reagent_type_from_product_string(input_reagent) //from string to type var/datum/reagent/reagent = find_reagent_object_from_type(input_reagent) if(!reagent) @@ -2096,13 +2103,5 @@ reagents = new /datum/reagents(max_vol, flags) reagents.my_atom = src -/atom/movable/chem_holder - name = "This atom exists to hold chems. If you can see this, make an issue report" - desc = "God this is stupid" - #undef REAGENT_TRANSFER_AMOUNT #undef REAGENT_PURITY - -#undef REAGENTS_UI_MODE_LOOKUP -#undef REAGENTS_UI_MODE_REAGENT -#undef REAGENTS_UI_MODE_RECIPE diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm index 46ac1eda36074..9b388c660857d 100644 --- a/code/modules/reagents/chemistry/reagents.dm +++ b/code/modules/reagents/chemistry/reagents.dm @@ -1,21 +1,3 @@ -GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) - -/proc/build_name2reagent() - . = list() - for (var/t in subtypesof(/datum/reagent)) - var/datum/reagent/R = t - if (length(initial(R.name))) - .[ckey(initial(R.name))] = t - -GLOBAL_LIST_INIT(blacklisted_metalgen_types, typecacheof(list( - /turf/closed/indestructible, //indestructible turfs should be indestructible, metalgen transmutation to plasma allows them to be destroyed - /turf/open/indestructible -))) - -//Various reagents -//Toxin & acid reagents -//Hydroponics stuff - /// A single reagent /datum/reagent /// datums don't have names by default @@ -60,8 +42,6 @@ GLOBAL_LIST_INIT(blacklisted_metalgen_types, typecacheof(list( var/reagent_weight = 1 ///is it currently metabolizing var/metabolizing = FALSE - /// is it bad for you? Currently only used for borghypo. C2s and Toxins have it TRUE by default. - var/harmful = FALSE /// Are we from a material? We might wanna know that for special stuff. Like metalgen. Is replaced with a ref of the material on New() var/datum/material/material ///A list of causes why this chem should skip being removed, if the length is 0 it will be removed from holder naturally, if this is >0 it will not be removed from the holder. @@ -119,7 +99,8 @@ GLOBAL_LIST_INIT(blacklisted_metalgen_types, typecacheof(list( if(!mass) mass = rand(10, 800) -/datum/reagent/Destroy() // This should only be called by the holder, so it's already handled clearing its references +/// This should only be called by the holder, so it's already handled clearing its references +/datum/reagent/Destroy() . = ..() holder = null @@ -173,11 +154,21 @@ GLOBAL_LIST_INIT(blacklisted_metalgen_types, typecacheof(list( * */ /datum/reagent/proc/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + SHOULD_CALL_PARENT(TRUE) current_cycle++ if(length(reagent_removal_skip_list)) return - if(holder) - holder.remove_reagent(type, metabolization_rate * affected_mob.metabolism_efficiency * seconds_per_tick) //By default it slowly disappears. + if(isnull(holder)) + return + + var/metabolizing_out = metabolization_rate * seconds_per_tick + if(!(chemical_flags & REAGENT_UNAFFECTED_BY_METABOLISM)) + if(chemical_flags & REAGENT_REVERSE_METABOLISM) + metabolizing_out /= affected_mob.metabolism_efficiency + else + metabolizing_out *= affected_mob.metabolism_efficiency + + holder.remove_reagent(type, metabolizing_out) /// Called in burns.dm *if* the reagent has the REAGENT_AFFECTS_WOUNDS process flag /datum/reagent/proc/on_burn_wound_processing(datum/wound/burn/flesh/burn_wound) @@ -187,7 +178,7 @@ GLOBAL_LIST_INIT(blacklisted_metalgen_types, typecacheof(list( Used to run functions before a reagent is transferred. Returning TRUE will block the transfer attempt. Primarily used in reagents/reaction_agents */ -/datum/reagent/proc/intercept_reagents_transfer(datum/reagents/target) +/datum/reagent/proc/intercept_reagents_transfer(datum/reagents/target, amount) return FALSE ///Called after a reagent is transferred @@ -222,10 +213,6 @@ Primarily used in reagents/reaction_agents if(holder) holder.remove_reagent(type, metabolization_rate * affected_mob.metabolism_efficiency * seconds_per_tick) -/// Called by [/datum/reagents/proc/conditional_update_move] -/datum/reagent/proc/on_move(mob/affected_mob) - return - /// Called after add_reagents creates a new reagent. /datum/reagent/proc/on_new(data) if(data) diff --git a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm index 262058b334d71..0c1dd26e17399 100644 --- a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm @@ -1,6 +1,5 @@ // Category 2 medicines are medicines that have an ill effect regardless of volume/OD to dissuade doping. Mostly used as emergency chemicals OR to convert damage (and heal a bit in the process). The type is used to prompt borgs that the medicine is harmful. /datum/reagent/medicine/c2 - harmful = TRUE metabolization_rate = 0.5 * REAGENTS_METABOLISM inverse_chem = null //Some of these use inverse chems - we're just defining them all to null here to avoid repetition, eventually this will be moved up to parent creation_purity = REAGENT_STANDARD_PURITY//All sources by default are 0.75 - reactions are primed to resolve to roughly the same with no intervention for these. diff --git a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm index 98ddd72db2329..be3069e7d4683 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm @@ -2212,6 +2212,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/mauna_loa/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() // Heats the user up while the reagent is in the body. Occasionally makes you burst into flames. drinker.adjust_bodytemperature(25 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick) if (SPT_PROB(2.5, seconds_per_tick)) diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 00f5018447844..fb6e6ea2c0066 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -19,16 +19,18 @@ /// affects mood, typically higher for mixed drinks with more complex recipes' var/quality = 0 +/datum/reagent/consumable/New() + . = ..() + // All food reagents function at a fixed rate + chemical_flags |= REAGENT_UNAFFECTED_BY_METABOLISM + /datum/reagent/consumable/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - current_cycle++ - if(ishuman(affected_mob)) - var/mob/living/carbon/human/affected_human = affected_mob - if(!HAS_TRAIT(affected_human, TRAIT_NOHUNGER)) - affected_human.adjust_nutrition(get_nutriment_factor() * REM * seconds_per_tick) - if(length(reagent_removal_skip_list)) + . = ..() + if(!ishuman(affected_mob) || HAS_TRAIT(affected_mob, TRAIT_NOHUNGER)) return - if(holder) - holder.remove_reagent(type, metabolization_rate * seconds_per_tick) + + var/mob/living/carbon/human/affected_human = affected_mob + affected_human.adjust_nutrition(get_nutriment_factor(affected_mob) * REM * seconds_per_tick) /datum/reagent/consumable/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() @@ -53,8 +55,9 @@ if(isitem(the_real_food) && !is_reagent_container(the_real_food)) exposed_mob.add_mob_memory(/datum/memory/good_food, food = the_real_food) -/datum/reagent/consumable/proc/get_nutriment_factor() - return nutriment_factor * REAGENTS_METABOLISM * (purity * 2) +/// Gets just how much nutrition this reagent is worth for the passed mob +/datum/reagent/consumable/proc/get_nutriment_factor(mob/living/carbon/eater) + return nutriment_factor * REAGENTS_METABOLISM * purity * 2 /datum/reagent/consumable/nutriment name = "Nutriment" @@ -257,15 +260,12 @@ brute_heal = 0 burn_heal = 0 -/datum/reagent/consumable/nutriment/mineral/on_mob_life(mob/living/carbon/eater, seconds_per_tick, times_fired) - if(HAS_TRAIT(eater, TRAIT_ROCK_EATER)) // allow mobs who can eat rocks to do so +/datum/reagent/consumable/nutriment/mineral/get_nutriment_factor(mob/living/carbon/eater) + if(HAS_TRAIT(eater, TRAIT_ROCK_EATER)) return ..() - else // otherwise just let them pass through the system - current_cycle++ - if(length(reagent_removal_skip_list)) - return - if(holder) - holder.remove_reagent(type, metabolization_rate * seconds_per_tick) + + // You cannot eat rocks, it gives no nutrition + return 0 /datum/reagent/consumable/sugar name = "Sugar" @@ -476,6 +476,8 @@ /datum/reagent/consumable/salt/expose_mob(mob/living/exposed_mob, methods, reac_volume) . = ..() + if(!iscarbon(exposed_mob)) + return var/mob/living/carbon/carbies = exposed_mob if(!(methods & (PATCH|TOUCH|VAPOR))) return @@ -650,6 +652,8 @@ /datum/reagent/consumable/flour/expose_mob(mob/living/exposed_mob, methods, reac_volume) . = ..() + if(!iscarbon(exposed_mob)) + return var/mob/living/carbon/carbies = exposed_mob if(!(methods & (PATCH|TOUCH|VAPOR))) return @@ -754,6 +758,8 @@ // Starch has similar absorbing properties to flour (Stronger here because it's rarer) /datum/reagent/consumable/corn_starch/expose_mob(mob/living/exposed_mob, methods, reac_volume) . = ..() + if(!iscarbon(exposed_mob)) + return var/mob/living/carbon/carbies = exposed_mob if(!(methods & (PATCH|TOUCH|VAPOR))) return @@ -866,7 +872,7 @@ /datum/reagent/consumable/nutriment/stabilized/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() if(affected_mob.nutrition > NUTRITION_LEVEL_FULL - 25) - affected_mob.adjust_nutrition(-3 * REM * get_nutriment_factor() * seconds_per_tick) + affected_mob.adjust_nutrition(-3 * REM * get_nutriment_factor(affected_mob) * seconds_per_tick) ////Lavaland Flora Reagents//// diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm index e2b703a9c1386..b317bfafa1ac7 100644 --- a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm @@ -496,6 +496,7 @@ Basically, we fill the time between now and 2s from now with hands based off the //Heals toxins if it's the only thing present - kinda the oposite of multiver! Maybe that's why it's inverse! /datum/reagent/inverse/healing/monover/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/need_mob_update if(length(affected_mob.reagents.reagent_list) > 1) need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.5 * seconds_per_tick, required_organ_flag = affected_organ_flags) //Hey! It's everyone's favourite drawback from multiver! diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index ff1671852c1e0..68eefcdf0cd44 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -9,12 +9,10 @@ /datum/reagent/medicine taste_description = "bitterness" -/datum/reagent/medicine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - current_cycle++ - if(length(reagent_removal_skip_list)) - return - if(holder) - holder.remove_reagent(type, metabolization_rate * seconds_per_tick / affected_mob.metabolism_efficiency) //medicine reagents stay longer if you have a better metabolism +/datum/reagent/medicine/New() + . = ..() + // All medicine metabolizes out slower / stay longer if you have a better metabolism + chemical_flags |= REAGENT_REVERSE_METABOLISM /datum/reagent/medicine/leporazine name = "Leporazine" @@ -928,7 +926,6 @@ color = "#A0E85E" metabolization_rate = 1.25 * REAGENTS_METABOLISM taste_description = "magnets" - harmful = TRUE ph = 0.5 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /// The amount of damage a single unit of this will heal @@ -1371,7 +1368,6 @@ metabolization_rate = 0.4 * REAGENTS_METABOLISM ph = 4.3 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - harmful = TRUE /datum/reagent/medicine/haloperidol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() @@ -1622,6 +1618,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/silibinin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, -2 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) // Add a chance to cure liver trauma once implemented. return UPDATE_MOB_HEALTH @@ -1666,6 +1663,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/granibitaluri/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/healamount = max(0.5 - round(0.01 * (affected_mob.getBruteLoss() + affected_mob.getFireLoss()), 0.1), 0) //base of 0.5 healing per cycle and loses 0.1 healing for every 10 combined brute/burn damage you have var/need_mob_update need_mob_update = affected_mob.adjustBruteLoss(-healamount * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 3a699da266d23..e42cfc55107d7 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -303,6 +303,8 @@ /datum/reagent/water/salt/expose_mob(mob/living/exposed_mob, methods, reac_volume) . = ..() + if(!iscarbon(exposed_mob)) + return var/mob/living/carbon/carbies = exposed_mob if(!(methods & (PATCH|TOUCH|VAPOR))) return @@ -335,7 +337,7 @@ color = "#E0E8EF" // rgb: 224, 232, 239 self_consuming = TRUE //divine intervention won't be limited by the lack of a liver ph = 7.5 //God is alkaline - chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_CLEANS + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_CLEANS|REAGENT_UNAFFECTED_BY_METABOLISM // Operates at fixed metabolism for balancing memes. default_container = /obj/item/reagent_containers/cup/glass/bottle/holywater /datum/glass_style/drinking_glass/holywater @@ -344,6 +346,15 @@ desc = "A glass of holy water." icon_state = "glass_clear" +/datum/reagent/water/holywater/on_new(list/data) + // Tracks the total amount of deciseconds that the reagent has been metab'd for, for the purpose of deconversion + if(isnull(data)) + data = list("deciseconds_metabolized" = 0) + else if(isnull(data["deciseconds_metabolized"])) + data["deciseconds_metabolized"] = 0 + + return ..() + // Holy water. Unlike water, which is nuked, stays in and heals the plant a little with the power of the spirits. Also ALSO increases instability. /datum/reagent/water/holywater/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user) mytray.adjust_waterlevel(round(volume)) @@ -354,53 +365,49 @@ . = ..() ADD_TRAIT(affected_mob, TRAIT_HOLY, type) -/datum/reagent/water/holywater/on_mob_add(mob/living/affected_mob, amount) - . = ..() - if(data) - data["misc"] = 0 - /datum/reagent/water/holywater/on_mob_end_metabolize(mob/living/affected_mob) . = ..() REMOVE_TRAIT(affected_mob, TRAIT_HOLY, type) -/datum/reagent/water/holywater/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) +/datum/reagent/water/holywater/on_mob_add(mob/living/affected_mob, amount) . = ..() - if(IS_CULTIST(exposed_mob)) - to_chat(exposed_mob, span_userdanger("A vile holiness begins to spread its shining tendrils through your mind, purging the Geometer of Blood's influence!")) + if(IS_CULTIST(affected_mob)) + to_chat(affected_mob, span_userdanger("A vile holiness begins to spread its shining tendrils through your mind, purging the Geometer of Blood's influence!")) /datum/reagent/water/holywater/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - if(!data) - data = list("misc" = 0) - data["misc"] += seconds_per_tick SECONDS * REM - affected_mob.adjust_jitter_up_to(4 SECONDS * seconds_per_tick, 20 SECONDS) + data["deciseconds_metabolized"] += (seconds_per_tick * 1 SECONDS * REM) + + affected_mob.adjust_jitter_up_to(4 SECONDS * REM * seconds_per_tick, 20 SECONDS) + if(IS_CULTIST(affected_mob)) for(var/datum/action/innate/cult/blood_magic/BM in affected_mob.actions) - to_chat(affected_mob, span_cultlarge("Your blood rites falter as holy water scours your body!")) + var/removed_any = FALSE for(var/datum/action/innate/cult/blood_spell/BS in BM.spells) + removed_any = TRUE qdel(BS) - if(data["misc"] >= (25 SECONDS)) // 10 units - affected_mob.adjust_stutter_up_to(4 SECONDS * seconds_per_tick, 20 SECONDS) + if(removed_any) + to_chat(affected_mob, span_cultlarge("Your blood rites falter as holy water scours your body!")) + + if(data["deciseconds_metabolized"] >= (25 SECONDS)) // 10 units + affected_mob.adjust_stutter_up_to(4 SECONDS * REM * seconds_per_tick, 20 SECONDS) affected_mob.set_dizzy_if_lower(10 SECONDS) if(IS_CULTIST(affected_mob) && SPT_PROB(10, seconds_per_tick)) affected_mob.say(pick("Av'te Nar'Sie","Pa'lid Mors","INO INO ORA ANA","SAT ANA!","Daim'niodeis Arc'iai Le'eones","R'ge Na'sie","Diabo us Vo'iscum","Eld' Mon Nobis"), forced = "holy water") if(prob(10)) affected_mob.visible_message(span_danger("[affected_mob] starts having a seizure!"), span_userdanger("You have a seizure!")) affected_mob.Unconscious(12 SECONDS) - to_chat(affected_mob, "[pick("Your blood is your bond - you are nothing without it", "Do not forget your place", \ - "All that power, and you still fail?", "If you cannot scour this poison, I shall scour your meager life!")].") - if(data["misc"] >= (1 MINUTES)) // 24 units + to_chat(affected_mob, span_cultlarge("[pick("Your blood is your bond - you are nothing without it", "Do not forget your place", \ + "All that power, and you still fail?", "If you cannot scour this poison, I shall scour your meager life!")].")) + + if(data["deciseconds_metabolized"] >= (1 MINUTES)) // 24 units if(IS_CULTIST(affected_mob)) affected_mob.mind.remove_antag_datum(/datum/antagonist/cult) - affected_mob.Unconscious(100) + affected_mob.Unconscious(10 SECONDS) affected_mob.remove_status_effect(/datum/status_effect/jitter) affected_mob.remove_status_effect(/datum/status_effect/speech/stutter) - if(holder) - holder.remove_reagent(type, volume) // maybe this is a little too perfect and a max() cap on the statuses would be better?? - return - if(holder) - holder.remove_reagent(type, 1 * REAGENTS_METABOLISM * seconds_per_tick) //fixed consumption to prevent balancing going out of whack + holder?.remove_reagent(type, volume) // maybe this is a little too perfect and a max() cap on the statuses would be better?? /datum/reagent/water/holywater/expose_turf(turf/exposed_turf, reac_volume) . = ..() @@ -466,6 +473,11 @@ ph = 6.5 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED +/datum/reagent/fuel/unholywater/on_mob_metabolize(mob/living/affected_mob) + . = ..() + if(IS_CULTIST(affected_mob)) + ADD_TRAIT(affected_mob, TRAIT_COAGULATING, type) + /datum/reagent/fuel/unholywater/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() var/need_mob_update = FALSE @@ -480,6 +492,16 @@ need_mob_update = TRUE if(ishuman(affected_mob) && affected_mob.blood_volume < BLOOD_VOLUME_NORMAL) affected_mob.blood_volume += 3 * REM * seconds_per_tick + + var/datum/wound/bloodiest_wound + + for(var/datum/wound/iter_wound as anything in affected_mob.all_wounds) + if(iter_wound.blood_flow && iter_wound.blood_flow > bloodiest_wound?.blood_flow) + bloodiest_wound = iter_wound + + if(bloodiest_wound) + bloodiest_wound.adjust_blood_flow(-2 * REM * seconds_per_tick) + else // Will deal about 90 damage when 50 units are thrown need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * REM * seconds_per_tick, 150) need_mob_update += affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE) @@ -489,6 +511,10 @@ if(need_mob_update) return UPDATE_MOB_HEALTH +/datum/reagent/fuel/unholywater/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() + REMOVE_TRAIT(affected_mob, TRAIT_COAGULATING, type) //We don't cult check here because potentially our imbiber may no longer be a cultist for whatever reason! It doesn't purge holy water, after all! + /datum/reagent/hellwater //if someone has this in their system they've really pissed off an eldrich god name = "Hell Water" description = "YOUR FLESH! IT BURNS!" @@ -2180,15 +2206,17 @@ /datum/reagent/barbers_aid/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message=TRUE, touch_protection=FALSE) . = ..() - if(!(methods & (TOUCH|VAPOR)) || !ishuman(exposed_mob) || HAS_TRAIT(exposed_mob, TRAIT_BALD) || HAS_TRAIT(exposed_mob, TRAIT_SHAVED)) + if(!(methods & (TOUCH|VAPOR)) || !ishuman(exposed_mob) || (HAS_TRAIT(exposed_mob, TRAIT_BALD) && HAS_TRAIT(exposed_mob, TRAIT_SHAVED))) return var/mob/living/carbon/human/exposed_human = exposed_mob - var/datum/sprite_accessory/hair/picked_hair = pick(GLOB.hairstyles_list) - var/datum/sprite_accessory/facial_hair/picked_beard = pick(GLOB.facial_hairstyles_list) - to_chat(exposed_human, span_notice("Hair starts sprouting from your scalp.")) - exposed_human.set_facial_hairstyle(picked_beard, update = FALSE) - exposed_human.set_hairstyle(picked_hair, update = TRUE) + if(!HAS_TRAIT(exposed_human, TRAIT_SHAVED)) + var/datum/sprite_accessory/facial_hair/picked_beard = pick(GLOB.facial_hairstyles_list) + exposed_human.set_facial_hairstyle(picked_beard, update = FALSE) + if(!HAS_TRAIT(exposed_human, TRAIT_BALD)) + var/datum/sprite_accessory/hair/picked_hair = pick(GLOB.hairstyles_list) + exposed_human.set_hairstyle(picked_hair, update = TRUE) + to_chat(exposed_human, span_notice("Hair starts sprouting from your [HAS_TRAIT(exposed_human, TRAIT_BALD) ? "face" : "scalp"].")) /datum/reagent/concentrated_barbers_aid name = "Concentrated Barber's Aid" @@ -2201,13 +2229,15 @@ /datum/reagent/concentrated_barbers_aid/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message=TRUE, touch_protection=FALSE) . = ..() - if(!(methods & (TOUCH|VAPOR)) || !ishuman(exposed_mob) || HAS_TRAIT(exposed_mob, TRAIT_BALD) || HAS_TRAIT(exposed_mob, TRAIT_SHAVED)) + if(!(methods & (TOUCH|VAPOR)) || !ishuman(exposed_mob) || (HAS_TRAIT(exposed_mob, TRAIT_BALD) && HAS_TRAIT(exposed_mob, TRAIT_SHAVED))) return var/mob/living/carbon/human/exposed_human = exposed_mob - to_chat(exposed_human, span_notice("Your hair starts growing at an incredible speed!")) - exposed_human.set_facial_hairstyle("Beard (Very Long)", update = FALSE) - exposed_human.set_hairstyle("Very Long Hair", update = TRUE) + if(!HAS_TRAIT(exposed_human, TRAIT_SHAVED)) + exposed_human.set_facial_hairstyle("Beard (Very Long)", update = FALSE) + if(!HAS_TRAIT(exposed_human, TRAIT_BALD)) + exposed_human.set_hairstyle("Very Long Hair", update = TRUE) + to_chat(exposed_human, span_notice("Your[HAS_TRAIT(exposed_human, TRAIT_BALD) ? " facial" : ""] hair starts growing at an incredible speed!")) /datum/reagent/concentrated_barbers_aid/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() @@ -2562,11 +2592,12 @@ color = "#9A6750" //RGB: 154, 103, 80 taste_description = "inner peace" penetrates_skin = NONE + var/datum/disease/transformation/gondola_disease = /datum/disease/transformation/gondola /datum/reagent/gondola_mutation_toxin/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(), FALSE, TRUE) + exposed_mob.ForceContractDisease(new gondola_disease, FALSE, TRUE) /datum/reagent/spider_extract diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index 7f4acabef6eb9..390c3d7bfd4dd 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -7,7 +7,6 @@ color = "#CF3600" // rgb: 207, 54, 0 taste_description = "bitterness" taste_mult = 1.2 - harmful = TRUE chemical_flags = REAGENT_CAN_BE_SYNTHESIZED ///The amount of toxin damage this will cause when metabolized (also used to calculate liver damage) var/toxpwr = 1.5 diff --git a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm index 9083de70902e7..d49976ac10eed 100644 --- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm +++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm @@ -125,16 +125,16 @@ ///special size for anti cult effect var/effective_size = round(created_volume/48) playsound(T, 'sound/effects/pray.ogg', 80, FALSE, effective_size) - for(var/mob/living/simple_animal/revenant/R in get_hearers_in_view(7,T)) + for(var/mob/living/basic/revenant/ghostie in get_hearers_in_view(7,T)) var/deity if(GLOB.deity) deity = GLOB.deity else deity = "Christ" - to_chat(R, span_userdanger("The power of [deity] compels you!")) - R.stun(20) - R.reveal(100) - R.adjustHealth(50) + to_chat(ghostie, span_userdanger("The power of [deity] compels you!")) + ghostie.apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, 2 SECONDS) + ghostie.apply_status_effect(/datum/status_effect/revenant/revealed, 10 SECONDS) + ghostie.adjust_health(50) for(var/mob/living/carbon/C in get_hearers_in_view(effective_size,T)) if(IS_CULTIST(C)) to_chat(C, span_userdanger("The divine explosion sears you!")) diff --git a/code/modules/reagents/reagent_containers/cups/_cup.dm b/code/modules/reagents/reagent_containers/cups/_cup.dm index 7ccc3209ab0d7..c15ba1a017f65 100644 --- a/code/modules/reagents/reagent_containers/cups/_cup.dm +++ b/code/modules/reagents/reagent_containers/cups/_cup.dm @@ -124,7 +124,7 @@ return var/trans = reagents.trans_to(target, amount_per_transfer_from_this, transferred_by = user) - to_chat(user, span_notice("You transfer [trans] unit\s of the solution to [target].")) + to_chat(user, span_notice("You transfer [round(trans, 0.01)] unit\s of the solution to [target].")) else if(target.is_drainable()) //A dispenser. Transfer FROM it TO us. if(!target.reagents.total_volume) @@ -136,7 +136,7 @@ return var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this, transferred_by = user) - to_chat(user, span_notice("You fill [src] with [trans] unit\s of the contents of [target].")) + to_chat(user, span_notice("You fill [src] with [round(trans, 0.01)] unit\s of the contents of [target].")) target.update_appearance() @@ -157,7 +157,7 @@ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this, transferred_by = user) - to_chat(user, span_notice("You fill [src] with [trans] unit\s of the contents of [target].")) + to_chat(user, span_notice("You fill [src] with [round(trans, 0.01)] unit\s of the contents of [target].")) target.update_appearance() return SECONDARY_ATTACK_CONTINUE_CHAIN diff --git a/code/modules/reagents/reagent_containers/dropper.dm b/code/modules/reagents/reagent_containers/dropper.dm index cf01c2cd4c2a0..ac8d0af0d1c6b 100644 --- a/code/modules/reagents/reagent_containers/dropper.dm +++ b/code/modules/reagents/reagent_containers/dropper.dm @@ -20,7 +20,7 @@ return if(reagents.total_volume > 0) - if(target.reagents.total_volume >= target.reagents.maximum_volume) + if(target.reagents.holder_full()) to_chat(user, span_notice("[target] is full.")) return @@ -29,7 +29,7 @@ return var/trans = 0 - var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1) + var/fraction = min(amount_per_transfer_from_this / reagents.total_volume, 1) if(ismob(target)) if(ishuman(target)) @@ -46,7 +46,7 @@ target.visible_message(span_danger("[user] tries to squirt something into [target]'s eyes, but fails!"), \ span_userdanger("[user] tries to squirt something into your eyes, but fails!")) - to_chat(user, span_notice("You transfer [trans] unit\s of the solution.")) + to_chat(user, span_notice("You transfer [round(trans, 0.01)] unit\s of the solution.")) update_appearance() return else if(isalien(target)) //hiss-hiss has no eyes! @@ -66,7 +66,7 @@ log_combat(user, M, "squirted", R) trans = src.reagents.trans_to(target, amount_per_transfer_from_this, transferred_by = user) - to_chat(user, span_notice("You transfer [trans] unit\s of the solution.")) + to_chat(user, span_notice("You transfer [round(trans, 0.01)] unit\s of the solution.")) update_appearance() target.update_appearance() @@ -82,7 +82,7 @@ var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this, transferred_by = user) - to_chat(user, span_notice("You fill [src] with [trans] unit\s of the solution.")) + to_chat(user, span_notice("You fill [src] with [round(trans, 0.01)] unit\s of the solution.")) update_appearance() target.update_appearance() diff --git a/code/modules/religion/burdened/psyker.dm b/code/modules/religion/burdened/psyker.dm index 4499030642d93..d4c752751b8bc 100644 --- a/code/modules/religion/burdened/psyker.dm +++ b/code/modules/religion/burdened/psyker.dm @@ -183,7 +183,7 @@ on_clear_callback = CALLBACK(src, PROC_REF(on_cult_rune_removed)), \ effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune, /obj/effect/cosmic_rune), \ ) - AddElement(/datum/element/bane, target_type = /mob/living/simple_animal/revenant, damage_multiplier = 0, added_damage = 25) + AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25) name = pick(possible_names) desc = possible_names[name] diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index 0a2c96dddd598..3b0bf62c26781 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -532,6 +532,16 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SERVICE +/datum/design/board/microwave_engineering + name = "Wireless Microwave Board" + desc = "The circuit board for a cell-powered microwave." + id = "microwave_engineering" + build_path = /obj/item/circuitboard/machine/microwave/engineering + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_KITCHEN + ) + departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_ENGINEERING + /datum/design/board/gibber name = "Gibber Board" desc = "The circuit board for a gibber." diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 7852bdb367e56..441e6f0038b5e 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -476,6 +476,7 @@ "gibber", "griddle", "microwave", + "microwave_engineering", "monkey_recycler", "oven", "processor", diff --git a/code/modules/security_levels/keycard_authentication.dm b/code/modules/security_levels/keycard_authentication.dm index 66bf8b3bc93f3..6d3d9326d6d41 100644 --- a/code/modules/security_levels/keycard_authentication.dm +++ b/code/modules/security_levels/keycard_authentication.dm @@ -55,11 +55,10 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/keycard_auth, 26) /obj/machinery/keycard_auth/ui_status(mob/user) if(isdrone(user)) return UI_CLOSE - if(!isanimal(user)) + if(!isanimal_or_basicmob(user)) return ..() - var/mob/living/simple_animal/A = user - if(!A.dextrous) - to_chat(user, span_warning("You are too primitive to use this device!")) + if(!HAS_TRAIT(user, TRAIT_CAN_HOLD_ITEMS)) + balloon_alert(user, "no hands!") return UI_CLOSE return ..() diff --git a/code/modules/spells/spell_types/conjure/simian.dm b/code/modules/spells/spell_types/conjure/simian.dm index 556a78e50127c..aa9aabc681009 100644 --- a/code/modules/spells/spell_types/conjure/simian.dm +++ b/code/modules/spells/spell_types/conjure/simian.dm @@ -14,14 +14,18 @@ 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_type = list( + /mob/living/basic/gorilla/lesser, + /mob/living/carbon/human/species/monkey/angry, + /mob/living/carbon/human/species/monkey/angry, // Listed twice so it's twice as likely, this class doesn't use pick weight + ) 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) + summon_type = list(/mob/living/carbon/human/species/monkey/angry, /mob/living/basic/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.")) diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index f929d2a779860..2e140bdbf26e7 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -1280,9 +1280,10 @@ else update_icon_dropped() +// Note: Does NOT return EMP protection value from parent call or pass it on to subtypes /obj/item/bodypart/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_WIRES || !IS_ROBOTIC_LIMB(src)) + var/protection = ..() + if((protection & EMP_PROTECT_WIRES) || !IS_ROBOTIC_LIMB(src)) return FALSE // with defines at the time of writing, this is 2 brute and 1.5 burn @@ -1299,16 +1300,14 @@ burn_damage *= 2 receive_damage(brute_damage, burn_damage) - do_sparks(number = 1, cardinal_only = FALSE, source = owner) - var/damage_percent_to_max = (get_damage() / max_damage) - if (time_needed && (damage_percent_to_max >= robotic_emp_paralyze_damage_percent_threshold)) - owner.visible_message(span_danger("[owner]'s [src] seems to malfunction!")) + do_sparks(number = 1, cardinal_only = FALSE, source = owner || src) + + if(can_be_disabled && (get_damage() / max_damage) >= robotic_emp_paralyze_damage_percent_threshold) ADD_TRAIT(src, TRAIT_PARALYSIS, EMP_TRAIT) - addtimer(CALLBACK(src, PROC_REF(un_paralyze)), time_needed) - return TRUE + addtimer(TRAIT_CALLBACK_REMOVE(src, TRAIT_PARALYSIS, EMP_TRAIT), time_needed) + owner?.visible_message(span_danger("[owner]'s [plaintext_zone] seems to malfunction!")) -/obj/item/bodypart/proc/un_paralyze() - REMOVE_TRAITS_IN(src, EMP_TRAIT) + return TRUE /// Returns the generic description of our BIO_EXTERNAL feature(s), prioritizing certain ones over others. Returns error on failure. /obj/item/bodypart/proc/get_external_description() diff --git a/code/modules/surgery/bodyparts/robot_bodyparts.dm b/code/modules/surgery/bodyparts/robot_bodyparts.dm index 37b6cef989750..99591daaa4b2b 100644 --- a/code/modules/surgery/bodyparts/robot_bodyparts.dm +++ b/code/modules/surgery/bodyparts/robot_bodyparts.dm @@ -111,15 +111,16 @@ /obj/item/bodypart/leg/left/robot/emp_act(severity) . = ..() - if(!.) + if(!. || isnull(owner)) return + var/knockdown_time = AUGGED_LEG_EMP_KNOCKDOWN_TIME if (severity == EMP_HEAVY) knockdown_time *= 2 owner.Knockdown(knockdown_time) if(owner.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB)) // So the message isn't duplicated. If they were stunned beforehand by something else, then the message not showing makes more sense anyways. return - to_chat(owner, span_danger("As your [src] unexpectedly malfunctions, it causes you to fall to the ground!")) + to_chat(owner, span_danger("As your [plaintext_zone] unexpectedly malfunctions, it causes you to fall to the ground!")) /obj/item/bodypart/leg/right/robot name = "cyborg right leg" @@ -156,15 +157,16 @@ /obj/item/bodypart/leg/right/robot/emp_act(severity) . = ..() - if(!.) + if(!. || isnull(owner)) return + var/knockdown_time = AUGGED_LEG_EMP_KNOCKDOWN_TIME if (severity == EMP_HEAVY) knockdown_time *= 2 owner.Knockdown(knockdown_time) if(owner.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB)) // So the message isn't duplicated. If they were stunned beforehand by something else, then the message not showing makes more sense anyways. return - to_chat(owner, span_danger("As your [src] unexpectedly malfunctions, it causes you to fall to the ground!")) + to_chat(owner, span_danger("As your [plaintext_zone] unexpectedly malfunctions, it causes you to fall to the ground!")) /obj/item/bodypart/chest/robot name = "cyborg torso" @@ -203,7 +205,7 @@ /obj/item/bodypart/chest/robot/emp_act(severity) . = ..() - if(!.) + if(!. || isnull(owner)) return var/stun_time = 0 @@ -219,7 +221,7 @@ var/damage_percent_to_max = (get_damage() / max_damage) if (stun_time && (damage_percent_to_max >= robotic_emp_paralyze_damage_percent_threshold)) - to_chat(owner, span_danger("Your [src]'s logic boards temporarily become unresponsive!")) + to_chat(owner, span_danger("Your [plaintext_zone]'s logic boards temporarily become unresponsive!")) owner.Stun(stun_time) owner.Shake(pixelshiftx = shift_x, pixelshifty = shift_y, duration = shake_duration) @@ -338,9 +340,10 @@ /obj/item/bodypart/head/robot/emp_act(severity) . = ..() - if(!.) + if(!. || isnull(owner)) return - to_chat(owner, span_danger("Your [src]'s optical transponders glitch out and malfunction!")) + + to_chat(owner, span_danger("Your [plaintext_zone]'s optical transponders glitch out and malfunction!")) var/glitch_duration = AUGGED_HEAD_EMP_GLITCH_DURATION if (severity == EMP_HEAVY) diff --git a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm index 408afea6679d8..b8b7d427b67b2 100644 --- a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm @@ -387,6 +387,25 @@ burn_modifier = 1.25 speed_modifier = 0.75 //big fungus big fungus +/// Dullahan head preserves organs inside it +/obj/item/bodypart/head/dullahan + throwforce = 25 // It's also a potent weapon + show_organs_on_examine = FALSE + speech_span = null + +/obj/item/bodypart/head/dullahan/Entered(obj/item/organ/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + if (!isorgan(arrived)) + return + arrived.organ_flags |= ORGAN_FROZEN + +/obj/item/bodypart/head/dullahan/Exited(obj/item/organ/gone, direction) + . = ..() + if (!isorgan(gone)) + return + gone.organ_flags &= ~ORGAN_FROZEN + + //GOLEM /obj/item/bodypart/head/golem icon = 'icons/mob/human/species/golems.dmi' diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm index 86dacdd98ffee..1ea3a1bf9c4ab 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm @@ -52,31 +52,50 @@ slot = ORGAN_SLOT_HEART_AID var/revive_cost = 0 var/reviving = FALSE + /// revival/defibrillation possibility flag that gathered from owner's .can_defib() proc + var/can_defib_owner COOLDOWN_DECLARE(reviver_cooldown) +/obj/item/organ/internal/cyberimp/chest/reviver/on_death(seconds_per_tick, times_fired) + if(isnull(owner)) // owner can be null, on_death() gets called by /obj/item/organ/internal/process() for decay + return + try_heal() // Allows implant to work even on dead people /obj/item/organ/internal/cyberimp/chest/reviver/on_life(seconds_per_tick, times_fired) + try_heal() + +/obj/item/organ/internal/cyberimp/chest/reviver/proc/try_heal() if(reviving) - switch(owner.stat) - if(UNCONSCIOUS, HARD_CRIT, SOFT_CRIT) - addtimer(CALLBACK(src, PROC_REF(heal)), 3 SECONDS) - else - COOLDOWN_START(src, reviver_cooldown, revive_cost) - reviving = FALSE - to_chat(owner, span_notice("Your reviver implant shuts down and starts recharging. It will be ready again in [DisplayTimeText(revive_cost)].")) + if(owner.stat == CONSCIOUS) + COOLDOWN_START(src, reviver_cooldown, revive_cost) + reviving = FALSE + to_chat(owner, span_notice("Your reviver implant shuts down and starts recharging. It will be ready again in [DisplayTimeText(revive_cost)].")) + else + addtimer(CALLBACK(src, PROC_REF(heal)), 3 SECONDS) return if(!COOLDOWN_FINISHED(src, reviver_cooldown) || HAS_TRAIT(owner, TRAIT_SUICIDED)) return - switch(owner.stat) - if(UNCONSCIOUS, HARD_CRIT) - revive_cost = 0 - reviving = TRUE - to_chat(owner, span_notice("You feel a faint buzzing as your reviver implant starts patching your wounds...")) + if(owner.stat != CONSCIOUS) + revive_cost = 0 + reviving = TRUE + to_chat(owner, span_notice("You feel a faint buzzing as your reviver implant starts patching your wounds...")) /obj/item/organ/internal/cyberimp/chest/reviver/proc/heal() + if(can_defib_owner == DEFIB_POSSIBLE) + revive_dead() + can_defib_owner = null + revive_cost += 10 MINUTES // Additional 10 minutes cooldown after revival. + // this check goes after revive_dead() to delay revival a bit + if(owner.stat == DEAD) + can_defib_owner = owner.can_defib() + if(can_defib_owner == DEFIB_POSSIBLE) + owner.notify_ghost_cloning("You are being revived by [src]!") + owner.grab_ghost() + /// boolean that stands for if PHYSICAL damage being patched + var/body_damage_patched = FALSE var/need_mob_update = FALSE if(owner.getOxyLoss()) need_mob_update += owner.adjustOxyLoss(-5, updating_health = FALSE) @@ -84,15 +103,34 @@ if(owner.getBruteLoss()) need_mob_update += owner.adjustBruteLoss(-2, updating_health = FALSE) revive_cost += 40 + body_damage_patched = TRUE if(owner.getFireLoss()) need_mob_update += owner.adjustFireLoss(-2, updating_health = FALSE) revive_cost += 40 + body_damage_patched = TRUE if(owner.getToxLoss()) need_mob_update += owner.adjustToxLoss(-1, updating_health = FALSE) revive_cost += 40 if(need_mob_update) owner.updatehealth() + if(body_damage_patched && prob(35)) // healing is called every few seconds, not every tick + owner.visible_message(span_warning("[owner]'s body twitches a bit."), span_notice("You feel like something is patching your injured body.")) + + +/obj/item/organ/internal/cyberimp/chest/reviver/proc/revive_dead() + owner.grab_ghost() + + owner.visible_message(span_warning("[owner]'s body convulses a bit.")) + playsound(owner, SFX_BODYFALL, 50, TRUE) + playsound(owner, 'sound/machines/defib_zap.ogg', 75, TRUE, -1) + owner.revive() + owner.emote("gasp") + owner.set_jitter_if_lower(200 SECONDS) + SEND_SIGNAL(owner, COMSIG_LIVING_MINOR_SHOCK) + log_game("[owner] been revived by [src]") + + /obj/item/organ/internal/cyberimp/chest/reviver/emp_act(severity) . = ..() if(!owner || . & EMP_PROTECT_SELF) diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm index 71dc305ac5f82..faf67a4596f2c 100644 --- a/code/modules/surgery/organs/internal/lungs/_lungs.dm +++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm @@ -514,6 +514,7 @@ if(prob(20)) n2o_euphoria = EUPHORIA_ACTIVE breather.emote(pick("giggle", "laugh")) + breather.set_drugginess(30 SECONDS) else n2o_euphoria = EUPHORIA_INACTIVE return diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index d4839542122d3..c6fdecbd29bc0 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -100,6 +100,7 @@ #include "baseturfs.dm" #include "bespoke_id.dm" #include "binary_insert.dm" +#include "bitrunning.dm" #include "blindness.dm" #include "bloody_footprints.dm" #include "breath.dm" diff --git a/code/modules/unit_tests/bitrunning.dm b/code/modules/unit_tests/bitrunning.dm new file mode 100644 index 0000000000000..568eeeed8c133 --- /dev/null +++ b/code/modules/unit_tests/bitrunning.dm @@ -0,0 +1,15 @@ +/// Ensures settings on vdoms are correct +/datum/unit_test/bitrunner_vdom_settings + +/datum/unit_test/bitrunner_vdom_settings/Run() + var/obj/structure/closet/crate/secure/bitrunning/decrypted/cache = allocate(/obj/structure/closet/crate/secure/bitrunning/decrypted) + + for(var/path in subtypesof(/datum/lazy_template/virtual_domain)) + var/datum/lazy_template/virtual_domain/vdom = new path + TEST_ASSERT_NOTNULL(vdom.key, "[path] should have a key") + TEST_ASSERT_NOTNULL(vdom.map_name, "[path] should have a map name") + + if(!length(vdom.extra_loot)) + continue + + TEST_ASSERT_EQUAL(cache.spawn_loot(vdom.extra_loot), TRUE, "[path] didn't spawn loot. Extra loot should be an associative list") diff --git a/code/modules/unit_tests/changeling.dm b/code/modules/unit_tests/changeling.dm index 644a7247ad1d9..c01105ce9c44c 100644 --- a/code/modules/unit_tests/changeling.dm +++ b/code/modules/unit_tests/changeling.dm @@ -48,6 +48,10 @@ if(isnull(final_icon)) final_icon = icon('icons/effects/effects.dmi', "nothing") + // If we have a lot of dna features with a lot of parts (icons) + // This'll eventually runtime into a bad icon operation + // So we're recaching the icons here to prevent it from failing + final_icon = icon(final_icon) final_icon.Insert(getFlatIcon(ling, no_anim = TRUE), dir = SOUTH, frame = last_frame) final_icon.Insert(getFlatIcon(victim, no_anim = TRUE), dir = NORTH, frame = last_frame) diff --git a/code/modules/unit_tests/rcd.dm b/code/modules/unit_tests/rcd.dm index 27ab66fa0be3b..49e9f8461fd0a 100644 --- a/code/modules/unit_tests/rcd.dm +++ b/code/modules/unit_tests/rcd.dm @@ -15,7 +15,7 @@ engineer.put_in_hands(rcd, forced = TRUE) - rcd.mode = RCD_MACHINE + rcd.mode = RCD_STRUCTURE var/list/adjacent_turfs = get_adjacent_open_turfs(engineer) @@ -24,7 +24,7 @@ var/turf/adjacent_turf = adjacent_turfs[1] for(var/i in 1 to 10) - adjacent_turf.rcd_act(engineer, rcd, rcd.mode) + adjacent_turf.rcd_act(engineer, rcd, list("[RCD_DESIGN_MODE]" = rcd.mode, "[RCD_DESIGN_PATH]" = /obj/structure/frame/machine/secured)) var/frame_count = 0 for(var/obj/structure/frame/machine_frame in adjacent_turf.contents) diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_changelingmidround.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_changelingmidround.png index ac412207c236d..d8e9c548db997 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_changelingmidround.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_changelingmidround.png differ diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 4e9b10a80ee7b..a4ef1a1a28255 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -64,7 +64,6 @@ /mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch, /mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck, /mob/living/simple_animal/hostile/asteroid/gutlunch/guthen, - /mob/living/simple_animal/hostile/asteroid/ice_demon, /mob/living/simple_animal/hostile/asteroid/polarbear, /mob/living/simple_animal/hostile/asteroid/polarbear/lesser, /mob/living/simple_animal/hostile/asteroid/wolf, @@ -74,7 +73,6 @@ /mob/living/simple_animal/hostile/construct/artificer/hostile, /mob/living/simple_animal/hostile/construct/artificer/mystic, /mob/living/simple_animal/hostile/construct/artificer/noncult, - /mob/living/simple_animal/hostile/construct/harvester, /mob/living/simple_animal/hostile/construct/juggernaut, /mob/living/simple_animal/hostile/construct/juggernaut/angelic, /mob/living/simple_animal/hostile/construct/juggernaut/hostile, @@ -88,9 +86,6 @@ /mob/living/simple_animal/hostile/construct/wraith/mystic, /mob/living/simple_animal/hostile/construct/wraith/noncult, /mob/living/simple_animal/hostile/dark_wizard, - /mob/living/simple_animal/hostile/gorilla, - /mob/living/simple_animal/hostile/gorilla/lesser, - /mob/living/simple_animal/hostile/gorilla/cargo_domestic, /mob/living/simple_animal/hostile/guardian, /mob/living/simple_animal/hostile/guardian/assassin, /mob/living/simple_animal/hostile/guardian/charger, @@ -103,17 +98,11 @@ /mob/living/simple_animal/hostile/guardian/ranged, /mob/living/simple_animal/hostile/guardian/standard, /mob/living/simple_animal/hostile/guardian/support, - /mob/living/simple_animal/hostile/heretic_summon, - /mob/living/simple_animal/hostile/heretic_summon/ash_spirit, - /mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror, - /mob/living/simple_animal/hostile/heretic_summon/rust_spirit, - /mob/living/simple_animal/hostile/heretic_summon/stalker, /mob/living/simple_animal/hostile/illusion, /mob/living/simple_animal/hostile/illusion/escape, /mob/living/simple_animal/hostile/illusion/mirage, /mob/living/simple_animal/hostile/jungle, /mob/living/simple_animal/hostile/jungle/leaper, - /mob/living/simple_animal/hostile/jungle/mook, /mob/living/simple_animal/hostile/megafauna, /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner, /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/doom, @@ -177,7 +166,6 @@ /mob/living/simple_animal/hostile/space_dragon, /mob/living/simple_animal/hostile/space_dragon/spawn_with_antag, /mob/living/simple_animal/hostile/vatbeast, - /mob/living/simple_animal/hostile/venus_human_trap, /mob/living/simple_animal/hostile/wizard, /mob/living/simple_animal/hostile/zombie, /mob/living/simple_animal/parrot, @@ -197,15 +185,11 @@ /mob/living/simple_animal/pet/gondola, /mob/living/simple_animal/pet/gondola/gondolapod, /mob/living/simple_animal/pet/gondola/virtual_domain, - /mob/living/simple_animal/revenant, /mob/living/simple_animal/shade, /mob/living/simple_animal/slime, /mob/living/simple_animal/slime/pet, /mob/living/simple_animal/slime/random, /mob/living/simple_animal/slime/transformed_slime, - /mob/living/simple_animal/sloth, - /mob/living/simple_animal/sloth/citrus, - /mob/living/simple_animal/sloth/paperwork, /mob/living/simple_animal/soulscythe, // DO NOT ADD NEW ENTRIES TO THIS LIST // READ THE COMMENT ABOVE diff --git a/code/modules/uplink/uplink_items/ammunition.dm b/code/modules/uplink/uplink_items/ammunition.dm index 292f87ffe13b3..e88727812528d 100644 --- a/code/modules/uplink/uplink_items/ammunition.dm +++ b/code/modules/uplink/uplink_items/ammunition.dm @@ -18,7 +18,6 @@ /datum/uplink_item/ammo/pistol name = "9mm Handgun Magazine" desc = "An additional 8-round 9mm magazine, compatible with the Makarov pistol." - progression_minimum = 10 MINUTES item = /obj/item/ammo_box/magazine/m9mm cost = 1 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) @@ -28,7 +27,6 @@ name = "9mm Armour Piercing Magazine" desc = "An additional 8-round 9mm magazine, compatible with the Makarov pistol. \ These rounds are less effective at injuring the target but penetrate protective gear." - progression_minimum = 30 MINUTES item = /obj/item/ammo_box/magazine/m9mm/ap cost = 2 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) @@ -37,7 +35,6 @@ name = "9mm Hollow Point Magazine" desc = "An additional 8-round 9mm magazine, compatible with the Makarov pistol. \ These rounds are more damaging but ineffective against armour." - progression_minimum = 30 MINUTES item = /obj/item/ammo_box/magazine/m9mm/hp cost = 3 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) @@ -46,7 +43,6 @@ name = "9mm Incendiary Magazine" desc = "An additional 8-round 9mm magazine, compatible with the Makarov pistol. \ Loaded with incendiary rounds which inflict little damage, but ignite the target." - progression_minimum = 30 MINUTES item = /obj/item/ammo_box/magazine/m9mm/fire cost = 2 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) @@ -55,7 +51,6 @@ name = ".357 Speed Loader" desc = "A speed loader that contains seven additional .357 Magnum rounds; usable with the Syndicate revolver. \ For when you really need a lot of things dead." - progression_minimum = 30 MINUTES item = /obj/item/ammo_box/a357 cost = 4 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) //nukies get their own version diff --git a/code/modules/uplink/uplink_items/badass.dm b/code/modules/uplink/uplink_items/badass.dm index 93087be9dd00e..67676edcd7da9 100644 --- a/code/modules/uplink/uplink_items/badass.dm +++ b/code/modules/uplink/uplink_items/badass.dm @@ -48,7 +48,6 @@ manufactured to pack a little bit more of a punch if your client needs some convincing." item = /obj/item/storage/secure/briefcase/syndie cost = 3 - progression_minimum = 5 MINUTES restricted = TRUE illegal_tech = FALSE diff --git a/code/modules/uplink/uplink_items/bundle.dm b/code/modules/uplink/uplink_items/bundle.dm index 912c28e2bcb2b..a930784fe1462 100644 --- a/code/modules/uplink/uplink_items/bundle.dm +++ b/code/modules/uplink/uplink_items/bundle.dm @@ -58,7 +58,6 @@ These items are collectively worth more than 25 telecrystals, but you do not know which specialization \ you will receive. May contain discontinued and/or exotic items. \ The Syndicate will only provide one Syndi-Kit per agent." - progression_minimum = 30 MINUTES item = /obj/item/storage/box/syndicate/bundle_a cost = 20 stock_key = UPLINK_SHARED_STOCK_KITS @@ -70,7 +69,6 @@ In Syndi-kit Special, you will receive items used by famous syndicate agents of the past. \ Collectively worth more than 25 telecrystals, the syndicate loves a good throwback. \ The Syndicate will only provide one Syndi-Kit per agent." - progression_minimum = 30 MINUTES item = /obj/item/storage/box/syndicate/bundle_b cost = 20 stock_key = UPLINK_SHARED_STOCK_KITS @@ -149,7 +147,6 @@ The Syndicate will only provide one surplus item per agent." cost = 20 item = /obj/structure/closet/crate/syndicrate - progression_minimum = 30 MINUTES stock_key = UPLINK_SHARED_STOCK_SURPLUS crate_tc_value = 80 crate_type = /obj/structure/closet/crate/syndicrate @@ -173,6 +170,5 @@ The Syndicate will only provide one surplus item per agent." cost = 20 item = /obj/item/syndicrate_key - progression_minimum = 30 MINUTES purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) stock_key = UPLINK_SHARED_STOCK_SURPLUS diff --git a/code/modules/uplink/uplink_items/dangerous.dm b/code/modules/uplink/uplink_items/dangerous.dm index 0cdcf3a7bb7f6..f1788c6e1dec3 100644 --- a/code/modules/uplink/uplink_items/dangerous.dm +++ b/code/modules/uplink/uplink_items/dangerous.dm @@ -19,7 +19,6 @@ name = "Makarov Pistol" desc = "A small, easily concealable handgun that uses 9mm auto rounds in 8-round magazines and is compatible \ with suppressors." - progression_minimum = 10 MINUTES item = /obj/item/gun/ballistic/automatic/pistol cost = 7 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) @@ -28,7 +27,6 @@ name = "Box of Throwing Weapons" desc = "A box of shurikens and reinforced bolas from ancient Earth martial arts. They are highly effective \ throwing weapons. The bolas can knock a target down and the shurikens will embed into limbs." - progression_minimum = 10 MINUTES item = /obj/item/storage/box/syndie_kit/throwing_weapons cost = 3 illegal_tech = FALSE @@ -95,7 +93,6 @@ name = "Syndicate Revolver" desc = "Waffle Co.'s modernized Syndicate revolver. Fires 7 brutal rounds of .357 Magnum." item = /obj/item/gun/ballistic/revolver/syndicate - progression_minimum = 30 MINUTES cost = 13 surplus = 50 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) //nukies get their own version diff --git a/code/modules/uplink/uplink_items/explosive.dm b/code/modules/uplink/uplink_items/explosive.dm index 27145ccf004df..72f40b00dfca3 100644 --- a/code/modules/uplink/uplink_items/explosive.dm +++ b/code/modules/uplink/uplink_items/explosive.dm @@ -16,7 +16,6 @@ desc = "C-4 is plastic explosive of the common variety Composition C. You can use it to breach walls, sabotage equipment, or connect \ an assembly to it in order to alter the way it detonates. It can be attached to almost all objects and has a modifiable timer with a \ minimum setting of 10 seconds." - progression_minimum = 5 MINUTES item = /obj/item/grenade/c4 cost = 1 @@ -24,7 +23,6 @@ name = "Bag of C-4 explosives" desc = "Because sometimes quantity is quality. Contains 10 C-4 plastic explosives." item = /obj/item/storage/backpack/duffelbag/syndie/c4 - progression_minimum = 10 MINUTES cost = 8 //20% discount! cant_discount = TRUE @@ -43,7 +41,6 @@ desc = "When inserted into a tablet, this cartridge gives you four opportunities to \ detonate tablets of crewmembers who have their message feature enabled. \ The concussive effect from the explosion will knock the recipient out for a short period, and deafen them for longer." - progression_minimum = 20 MINUTES item = /obj/item/computer_disk/virus/detomatix cost = 6 restricted = TRUE @@ -64,7 +61,7 @@ name = "Pizza Bomb" desc = "A pizza box with a bomb cunningly attached to the lid. The timer needs to be set by opening the box; afterwards, \ opening the box again will trigger the detonation after the timer has elapsed. Comes with free pizza, for you or your target!" - progression_minimum = 30 MINUTES + progression_minimum = 15 MINUTES item = /obj/item/pizzabox/bomb cost = 6 surplus = 8 @@ -82,7 +79,6 @@ /datum/uplink_item/explosives/syndicate_bomb/emp name = "Syndicate EMP Bomb" desc = "A variation of the syndicate bomb designed to produce a large EMP effect." - progression_minimum = 30 MINUTES item = /obj/item/sbeacondrop/emp cost = 7 diff --git a/code/modules/uplink/uplink_items/job.dm b/code/modules/uplink/uplink_items/job.dm index 6dcd48dee839c..2dc7fdf153cc5 100644 --- a/code/modules/uplink/uplink_items/job.dm +++ b/code/modules/uplink/uplink_items/job.dm @@ -150,7 +150,6 @@ name = "Magillitis Serum Autoinjector" desc = "A single-use autoinjector which contains an experimental serum that causes rapid muscular growth in Hominidae. \ Side-affects may include hypertrichosis, violent outbursts, and an unending affinity for bananas." - progression_minimum = 10 MINUTES item = /obj/item/reagent_containers/hypospray/medipen/magillitis cost = 15 restricted_roles = list(JOB_GENETICIST, JOB_RESEARCH_DIRECTOR) @@ -159,7 +158,6 @@ name = "Box of Gorilla Cubes" desc = "A box with three Waffle Co. brand gorilla cubes. Eat big to get big. \ Caution: Product may rehydrate when exposed to water." - progression_minimum = 15 MINUTES item = /obj/item/storage/box/gorillacubes cost = 6 restricted_roles = list(JOB_GENETICIST, JOB_RESEARCH_DIRECTOR) @@ -209,7 +207,8 @@ name = "Kinetic Accelerator Pressure Mod" desc = "A modification kit which allows Kinetic Accelerators to do greatly increased damage while indoors. \ Occupies 35% mod capacity." - progression_minimum = 30 MINUTES + // While less deadly than a revolver it does have infinite ammo + progression_minimum = 15 MINUTES item = /obj/item/borg/upgrade/modkit/indoors cost = 5 //you need two for full damage, so total of 10 for maximum damage limited_stock = 2 //you can't use more than two! @@ -220,7 +219,6 @@ name = "Guide to Advanced Mimery Series" desc = "The classical two part series on how to further hone your mime skills. Upon studying the series, the user should be able to make 3x1 invisible walls, and shoot bullets out of their fingers. \ Obviously only works for Mimes." - progression_minimum = 20 MINUTES cost = 12 item = /obj/item/storage/box/syndie_kit/mimery restricted_roles = list(JOB_MIME) @@ -238,7 +236,7 @@ /datum/uplink_item/role_restricted/chemical_gun name = "Reagent Dartgun" desc = "A heavily modified syringe gun which is capable of synthesizing its own chemical darts using input reagents. Can hold 90u of reagents." - progression_minimum = 20 MINUTES + progression_minimum = 15 MINUTES item = /obj/item/gun/chem cost = 12 restricted_roles = list(JOB_CHEMIST, JOB_CHIEF_MEDICAL_OFFICER, JOB_BOTANIST) @@ -246,7 +244,6 @@ /datum/uplink_item/role_restricted/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!" - progression_minimum = 10 MINUTES cost = 10 item = /obj/item/pneumatic_cannon/pie/selfcharge restricted_roles = list(JOB_CLOWN) @@ -276,9 +273,6 @@ someone saves them or they manage to crawl out. Be sure not to ram into any walls or vending machines, as the springloaded seats \ are very sensitive. Now with our included lube defense mechanism which will protect you against any angry shitcurity! \ Premium features can be unlocked with a cryptographic sequencer!" - // It has a low progression cost because it's the sort of item that only works well early in the round - // Plus, it costs all your TC, and it's not an instant kill tool. - progression_minimum = 5 MINUTES item = /obj/vehicle/sealed/car/clowncar cost = 20 restricted_roles = list(JOB_CLOWN) @@ -290,9 +284,6 @@ His Grace grants gradual regeneration and complete stun immunity to His wielder, but be wary: if He gets too hungry, He will become impossible to drop and eventually kill you if not fed. \ However, if left alone for long enough, He will fall back to slumber. \ To activate His Grace, simply unlatch Him." - // It has a low progression cost because it's the sort of item that only works well early in the round - // Plus, it costs all your TC and will lock your uplink. - progression_minimum = 5 MINUTES lock_other_purchases = TRUE cant_discount = TRUE item = /obj/item/his_grace diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm index be7c163bd861d..8a1c301830a09 100644 --- a/code/modules/uplink/uplink_items/nukeops.dm +++ b/code/modules/uplink/uplink_items/nukeops.dm @@ -498,6 +498,16 @@ cost = 10 purchasable_from = UPLINK_NUKE_OPS +/datum/uplink_item/bundles_tc/cowboy + name = "Syndicate Outlaw Kit" + desc = "There've been high tales of an outlaw 'round these parts. A fella so ruthless and efficient no ranger could ever capture 'em. \ + Now you can be just like 'em! \ + This kit contains armor-lined cowboy equipment, a custom revolver and holster, and a horse with a complimentary apple to tame. \ + A lighter is also included, though you must supply your own smokes." + item = /obj/item/storage/box/syndie_kit/cowboy + cost = 18 + purchasable_from = UPLINK_NUKE_OPS + // Mech related gear /datum/uplink_category/mech diff --git a/code/modules/uplink/uplink_items/stealthy.dm b/code/modules/uplink/uplink_items/stealthy.dm index 491f8e8e99d6d..2f205a9d0bd69 100644 --- a/code/modules/uplink/uplink_items/stealthy.dm +++ b/code/modules/uplink/uplink_items/stealthy.dm @@ -65,7 +65,6 @@ desc = "This box contains a guide on how to craft masterful works of origami, allowing you to transform normal pieces of paper into \ perfectly aerodynamic (and potentially lethal) paper airplanes." item = /obj/item/storage/box/syndie_kit/origami_bundle - progression_minimum = 10 MINUTES cost = 4 surplus = 0 purchasable_from = ~UPLINK_NUKE_OPS //clown ops intentionally left in, because that seems like some s-tier shenanigans. diff --git a/config/blanks.json b/config/blanks.json index 299fa67a594b3..f3b38d67bdb4b 100644 --- a/config/blanks.json +++ b/config/blanks.json @@ -545,7 +545,7 @@ "
    ", "
    By writing and signing this form, you consent to the processing of your personal data by Nanotrasen Corporation.

    ", "
    ", - "

    Name of offical to take action:

    ", + "

    Name of official to take action:

    ", "

    [___________________________________]

    ", "

    Official Decision:

    ", "

    [___________________________________]

    ", diff --git a/config/lavaruinblacklist.txt b/config/lavaruinblacklist.txt index 6f90b1f254e1b..d40c4386d31de 100644 --- a/config/lavaruinblacklist.txt +++ b/config/lavaruinblacklist.txt @@ -23,20 +23,21 @@ #_maps/RandomRuins/LavaRuins/lavaland_surface_sloth.dmm ##MISC +#_maps/RandomRuins/AnywhereRuins/fountain_hall.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_automated_trade_outpost.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_ufo_crash.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_ww_vault.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_automated_trade_outpost.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_xeno_nest.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_wwiioutpost.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_tomb.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_gaia.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_elephant_graveyard.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_gaia.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_library.dmm -#_maps/RandomRuins/AnywhereRuins/fountain_hall.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_phonebooth.dmm \ No newline at end of file +#_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_phonebooth.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_tomb.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_ufo_crash.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_watcher_grave.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_ww_vault.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_wwiioutpost.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_xeno_nest.dmm diff --git a/html/changelogs/AutoChangeLog-pr-78583.yml b/html/changelogs/AutoChangeLog-pr-78583.yml new file mode 100644 index 0000000000000..d5723ae152162 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-78583.yml @@ -0,0 +1,6 @@ +author: "ZephyrTFA" +delete-after: True +changes: + - rscadd: "Vent Pumps can now be overclocked, do some light reading in the air alarm to figure out what this means." + - balance: "Vent Pumps now have fan integrity, the damaging of which reduces their ability to move air." + - sound: "Overclocking sounds, including spool, stop, and loop" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78669.yml b/html/changelogs/AutoChangeLog-pr-78669.yml deleted file mode 100644 index 519ec9c826a0a..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78669.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "unit0016" -delete-after: True -changes: - - bugfix: "It is no longer possible to chasm yourself on the geode. Again." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78674.yml b/html/changelogs/AutoChangeLog-pr-78674.yml deleted file mode 100644 index 2fd8072593f9c..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78674.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "necromanceranne" -delete-after: True -changes: - - balance: "Despite earlier reports suggesting that the famous lethality of the Regal Condor was largely a myth, there has been rumors that the gun has once again started to display its true killing potential on any station that it 'manifests'." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78742.yml b/html/changelogs/AutoChangeLog-pr-78742.yml deleted file mode 100644 index c9f9775b94cff..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78742.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - bugfix: "Fixed silent catwalks." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78745.yml b/html/changelogs/AutoChangeLog-pr-78745.yml deleted file mode 100644 index ba964a0876856..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78745.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - rscadd: "Fish analyzers can now be used to perform fish scanning experiments." - - balance: "They can now be singularly bought as a goodie pack for 125 cr each, instead of a crate of three for 500 cr." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78747.yml b/html/changelogs/AutoChangeLog-pr-78747.yml deleted file mode 100644 index 6549830c58da7..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78747.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Sealed101" -delete-after: True -changes: - - bugfix: "fixed bad food not having bad food reagents" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78754.yml b/html/changelogs/AutoChangeLog-pr-78754.yml deleted file mode 100644 index c19e3e967f2fc..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78754.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "timothymtorres" -delete-after: True -changes: - - refactor: "Refactor gib code to be more robust." - - qol: "Gibbing a mob will result in all items being dropped instead of getting deleted. There are a few exceptions (like admin gib self) where this will not take place." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78759.yml b/html/changelogs/AutoChangeLog-pr-78759.yml deleted file mode 100644 index ddd3a3ba173e9..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78759.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "san7890" -delete-after: True -changes: - - refactor: "Refactored goats into basic mobs! Not much should have changed beyond their endless desire to retaliate should you attack them, they're still just as good as chomping away plant life as ever." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78766.yml b/html/changelogs/AutoChangeLog-pr-78766.yml deleted file mode 100644 index 92544abeaec67..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78766.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "GPeckman" -delete-after: True -changes: - - bugfix: "The AI can no longer turn you off if you shapeshift into a robot." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78768.yml b/html/changelogs/AutoChangeLog-pr-78768.yml deleted file mode 100644 index 1c1fea677fb14..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78768.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "GPeckman" -delete-after: True -changes: - - bugfix: "Adminheal will now properly clear negative mutations as intended." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78771.yml b/html/changelogs/AutoChangeLog-pr-78771.yml deleted file mode 100644 index e55b8dc436782..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78771.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "GPeckman" -delete-after: True -changes: - - bugfix: "Borgs will no longer become permanently upside-down if tipped over by multiple people at the same time." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78773.yml b/html/changelogs/AutoChangeLog-pr-78773.yml deleted file mode 100644 index 6b661f241e31e..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78773.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "GPeckman" -delete-after: True -changes: - - bugfix: "Engineering borgs can no longer grab and drop their own iron/glass sheet module." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78774.yml b/html/changelogs/AutoChangeLog-pr-78774.yml deleted file mode 100644 index 960314de4262a..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78774.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - qol: "AI, cyborg, and PAI camera (photo taking) behavior now uses balloon alerts and has sound effects associated" - - refactor: "Refactored AI, cyborg, and PAI camera (photo taking) code" - - bugfix: "fixed being unable to print photos as a cyborg when below 50% toner, even though photos only take 5%" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78775.yml b/html/changelogs/AutoChangeLog-pr-78775.yml deleted file mode 100644 index cc4a35246834d..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78775.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "GPeckman" -delete-after: True -changes: - - bugfix: "Ice whelps can now use spells given to them by admins, and people who have polymorphed into ice whelps can now polymorph back to normal." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78776.yml b/html/changelogs/AutoChangeLog-pr-78776.yml deleted file mode 100644 index bcc4dd4a02b16..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78776.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Likteer" -delete-after: True -changes: - - rscadd: "Fake moustaches are now poorly slapped on top of what you're wearing" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78783.yml b/html/changelogs/AutoChangeLog-pr-78783.yml deleted file mode 100644 index 57d3955572cb3..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78783.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Kapu1178" -delete-after: True -changes: - - bugfix: "Blood once again appears as small drops instead of splatters during minor bleeding." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78809.yml b/html/changelogs/AutoChangeLog-pr-78809.yml new file mode 100644 index 0000000000000..e2c06429f4d0f --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-78809.yml @@ -0,0 +1,4 @@ +author: "Iamgoofball" +delete-after: True +changes: + - balance: "Allows spacemen to use age-appropriate drugs by making it so you can now huff N2O to get high." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78914.yml b/html/changelogs/AutoChangeLog-pr-78914.yml new file mode 100644 index 0000000000000..0d9e5a3217f21 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-78914.yml @@ -0,0 +1,4 @@ +author: "starrm4nn" +delete-after: True +changes: + - bugfix: "makes the riot helmet hide hair like other sec helmets" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78972.yml b/html/changelogs/AutoChangeLog-pr-78972.yml new file mode 100644 index 0000000000000..ee28a7c42d525 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-78972.yml @@ -0,0 +1,4 @@ +author: "carlarctg" +delete-after: True +changes: + - bugfix: "Fixes Monkey's Delight recipe" \ No newline at end of file diff --git a/html/changelogs/archive/2023-10.yml b/html/changelogs/archive/2023-10.yml index f7035bd489938..ff449e2e69689 100644 --- a/html/changelogs/archive/2023-10.yml +++ b/html/changelogs/archive/2023-10.yml @@ -171,3 +171,395 @@ neocloudy: - bugfix: MetaStation disposal pipes from Cargo to Disposals/the rest of the station are working again. +2023-10-07: + CoiledLamb: + - qol: allows janitor keys to be stored in janitor wintercoats and janibets + - qol: gives empty fireaxe and mech removal crowbars cabinets directional helpers + GPeckman: + - bugfix: Borgs will no longer become permanently upside-down if tipped over by + multiple people at the same time. + - bugfix: Adminheal will now properly clear negative mutations as intended. + - bugfix: The AI can no longer turn you off if you shapeshift into a robot. + - rscadd: The laser carbine, a weak but fully automatic sidegrade to the normal + laser gun, can now be ordered from cargo. + - bugfix: Engineering borgs can no longer grab and drop their own iron/glass sheet + module. + - bugfix: Ice whelps can now use spells given to them by admins, and people who + have polymorphed into ice whelps can now polymorph back to normal. + Ghommie: + - bugfix: Fixed silent catwalks. + - rscadd: Fish analyzers can now be used to perform fish scanning experiments. + - balance: They can now be singularly bought as a goodie pack for 125 cr each, instead + of a crate of three for 500 cr. + Isratosh: + - spellcheck: '"offical" has been officially corrected to "official" in several + official locations.' + Jacquerel: + - refactor: Rust Walkers, Ash Spirits, Flesh Stalkers, and The Maid in the Mirror + now use the basic mob framework. Please report any unusual behaviour. + Kapu1178: + - bugfix: Blood once again appears as small drops instead of splatters during minor + bleeding. + Likteer: + - rscadd: Fake moustaches are now poorly slapped on top of what you're wearing + Melbert: + - refactor: Refactors how ethereals update their color when damaged. + - qol: AI, cyborg, and PAI camera (photo taking) behavior now uses balloon alerts + and has sound effects associated + - refactor: Refactored AI, cyborg, and PAI camera (photo taking) code + - bugfix: fixed being unable to print photos as a cyborg when below 50% toner, even + though photos only take 5% + ReezeBL: + - bugfix: fixed a PDA's messenger TGUI issue with handling of destroyed recipients. + Sealed101: + - bugfix: fixed bad food not having bad food reagents + necromanceranne: + - balance: Despite earlier reports suggesting that the famous lethality of the Regal + Condor was largely a myth, there has been rumors that the gun has once again + started to display its true killing potential on any station that it 'manifests'. + oranges: + - rscadd: Dogs now react to centrist grillers more realistically + san7890: + - refactor: Refactored goats into basic mobs! Not much should have changed beyond + their endless desire to retaliate should you attack them, they're still just + as good as chomping away plant life as ever. + timothymtorres: + - refactor: Refactor gib code to be more robust. + - qol: Gibbing a mob will result in all items being dropped instead of getting deleted. + There are a few exceptions (like admin gib self) where this will not take place. + unit0016: + - bugfix: It is no longer possible to chasm yourself on the geode. Again. +2023-10-08: + Comxy: + - bugfix: Spider types get properly checked again. + Ghommie: + - bugfix: People who are irremediably bald can still grow a beard with barber aid. + Hatterhat: + - qol: Miners can now tag monster spawners (necropolis tendrils, animal dens, demonic + portals, and netherworld links) by using their mining scanner on it, which updates + their GPS tag (and/or gives them one) to give it a numerical designation and + a short identifier for what it's spawning. + Jacquerel: + - bugfix: Flesh Worms will move smoothly more consistently. + LT3: + - image: Text alignment on ID cards slightly adjusted + Melbert: + - bugfix: Fixed an error from reading an ID card closely when you can't read + YehnBeep: + - qol: '"prison" intercoms have been renamed to "receive-only" intercoms to make + it clearer they cannot transmit.' + carlarctg: + - qol: Added slapcrafting to unloaded tech shells, click on them with ingredients + to quickly craft your shell. + san7890: + - refactor: Sloths are now basic mobs, however their overall sluggish behavior shouldn't + have changed much- let us know if anything is broken. + timothymtorres: + - bugfix: Fix bodies now lose fire stacks while husked. +2023-10-09: + Ben10Omintrix: + - refactor: ice demons have been refactored into basic mbos. please report any bugs + - rscadd: ice demons now have a unique trophy + IndieanaJones: + - bugfix: Slaughter/Laughter demon melee cooldowns have been fixed and now attack + at the regular player character attack speed + Jacquerel: + - balance: The chemical gun and PKA pressure mod traitor items are now purchasable + within 15 minutes of the round starting rather than 20/30. + - balance: All preset bundle kits, the cash briefcase, the makarov, the revolver, + the throwing weapon kit, c4, the detomatix cartridge, the large EMP bomb, gorillas, + advanced mimery tome, pie cannon, clown car, His Grace, and the origami kit + are now all purchasable at the start of a round. + distributivgesetz: + - qol: Supermatter shards can now be fastened with right click too. Now, just don't + forget to use a wrench. +2023-10-10: + BlueMemesauce: + - bugfix: fixed gibbing from having too much blood not working in some cases + Fazzie: + - qol: NT's logo on Centcom's landing pad looks better + - qol: Centcom's Cargo and other rooms had their items rearanged to look marginally + better. Like you're every gonna see them! + - bugfix: The Thunderdome on Centcom now has up-to-date cooking machinery + FlufflesTheDog: + - bugfix: Virtual domain gondola meat will no longer have a small chance to turn + you into a weaker gondola variant + GPeckman: + - bugfix: Warm donk-pockets should now have omnizine in them again. + HWSensum: + - balance: Reviver Implant now able to revive dead people. + Hatterhat: + - bugfix: Necropolis tendrils and other mining mob spawners can be hit in melee + again. + Jacquerel: + - bugfix: Cowardly mobs will consistently run away from you instead of getting tired + and just sort of standing there after an initial burst of movement. + Melber: + - bugfix: Wearing bread (or roses, or other non-mask things) no longer prevents + you from TTS speaking. + Melbert: + - bugfix: Robotic bodyparts not attached to people are now properly affected by + EMPs. + - bugfix: Virtual Drink Glasses now look correct. + SyncIt21: + - code_imp: moved some global procs and vars related to reagents to its own dedicated + file. removed some unused procs and macros + - code_imp: heavy auto docs for a lot of procs + - refactor: adds reagent sanity and bound check code + - refactor: multiple reagents are more uniformly distributed when transferring them + between beakers or dropper & in every other reagent dependent operation + Wallem: + - rscadd: Nuclear Operatives now have ready access to ancient cowboy technology + in the form of the Outlaw Bundle. Now you too can roll into town on your horse. + YehnBeep: + - qol: The autopsy tray (and surgery trays) can now hold the autopsy scanner + admeeer: + - code_imp: made an eensy teensie weensie change to some supermatter boilerplate + jlsnow301: + - bugfix: Added extra checks to bitrunning domain cleanup so avatars are deleted + properly. + - rscadd: Quantum servers now look for a new machine called a byteforge to spawn + loot on- no longer on an invisible landmark. This should make the rooms rebuildable + after disasters. + - rscadd: '*Most* bitrunning machinery is now researchable and buildable via circuits + in the engineering protolathe.' + lizardqueenlexi: + - bugfix: Metastation disposals will no longer infinitely loop garbage around the + station. + mc-oofert: + - code_imp: COMSIG_GLOB_LIGHT_MECHANISM_COMPLETED is now COMSIG_GLOB_PUZZLE_COMPLETED + ninjanomnom: + - bugfix: Images are once more displayed as images in vv instead of as an appearance + - rscadd: Pipes now have a colored visual display that shows their contents at a + glance. + san7890: + - refactor: Revenants, the mob that's split between planes of Life and Death, have + been refactored into a basic mob. While this alone shouldn't touch behavior, + a lot of the backend code has been gutted and refactored to try and furnish + a better antagonist experience. This might mean that some weird stuff can come + up and around, report something if it's utterly broken. + - code_imp: In order to better facilitate some code, you do not ghost outside of + a revenant on death, you simply get transferred into the ectoplasm. You should + still be able to speak with your ghost friends on how hard you got wrecked or + if you'll be able to resurrect though. + - code_imp: The timing on revenant stuff such as being revealed, stunned, and inhibited + (by holy weapons) should be tweaked a bit to allow better management. This should + mean that getting unstunned and such should be a bit more precise now. + - qol: Revenant instructions are now relayed in a neat little examine block. +2023-10-11: + GPeckman: + - bugfix: Borg modules can no longer be sold by pirates. + Iamgoofball: + - bugfix: Fixes a few runtimes with TTS and skips some code if TTS isn't enabled. + Jacquerel: + - balance: The Changeling Space Suit has been replaced by a new ability which makes + you passively spaceproof without replacing your clothing. + - admin: Editing the atmos sensitivity variables on a basic mob during the game + will now actually do something. + - qol: You can now see what drones and gorillas are holding by examining them. + - admin: It's now easier to give handless mobs hands by applying the "dextrous" + element. + - balance: Spiders and Bears can now climb railings (you know if... they'd rather + do that than destroy them). + - bugfix: Heretic mobs will not be summoned with AI enabled, and won't turn into + small animals instead of summoning a flesh stalker. + JohnFulpWillard: + - bugfix: Antiglow now probably has negative glow power. + Melbert: + - bugfix: Miner's Salve, Sterilizine, and Space Cleaner now all properly affect + burn wounds + Sealed101: + - bugfix: you can no longer polymorph belt into a holoparasite + ViktorKoL: + - bugfix: fixed some faulty research connections in between heretic's blade and + rust paths. + carlarctg: + - rscadd: Adds practice carbines to all firing ranges. They don't deal damage. + - qol: Adds a base physical description proc to gameplay species, displays it on + magic mirrors. It will give a description of not the lore of the species but + in what way they differ from base species. + - bugfix: Fixes a bad subtype on magical mirrors. + - bugfix: Magical mirrors now give the user ADVANCEDTOOLUSER and LITERACY if they + lack either of them, so monkey wizards aren't softlocked. + jlsnow301: + - bugfix: Fixed some issues in the security camera UI - pressing next or back will + now loop through the cameras + - bugfix: Fixed some style issues in the camera console where selected cams weren't + showing as selected + - bugfix: Camera console search works again + lizardqueenlexi: + - refactor: Harvester constructs have been updated to the basic mob framework. This + should have very little impact on their behavior, but please report any issues. + mc-oofert: + - bugfix: count station food verb now counts food only onstation + necromanceranne: + - balance: Unholy water acts as a coagulant for cultists. + nikothedude: + - bugfix: Wound promotion and demotion no longer removes gauze from the limb + timothymtorres: + - sound: Add burning sound loop to bonfires and fireplaces + - code_imp: Improved fireplaces to only process when lit + vinylspiders: + - bugfix: fixes a runtime in organ on_death() + - bugfix: using a magic mirror to change gender or skintone will now update your + icon properly to match your selection + - bugfix: bitrunners will no longer be lumped in with assistants on the crew monitor + console's display + - bugfix: moved a garbage spawner on Tramstation that was causing random runtimes + due to sometimes spawning in space depending on which module got loaded +2023-10-12: + Ghommie: + - bugfix: Examining twice experiment handlers with an active fish-related experiment + now gives a comprehensible, correctly spaced list of scanned species rather + than something like "pufferfishguppyslimefishchasmchrab". + - bugfix: No more "line snapped" balloon messages everytime the fishing minigame + is over + - bugfix: Getting to the Master level of the fishing skill now correctly gives you + that slight helping hand to identify yet-to-be-caught fishes. + Isratosh: + - admin: Gondola supplypods are functional again. + Jacquerel: + - refactor: Gorillas now use the basic mob framework. Please report any unusual + side effects. + - rscadd: Adds a new lavaland ruin where you can find a unique egg. + - balance: Flesh Spiders heal automatically over time if they go a short time without + taking damage, instead of healing large chunks by clicking themselves and waiting + two seconds. + - qol: Spider egg clusters which only hatch into one kind of spider don't ask you + to select that one type from a radial menu with one option on it. + - qol: As a Flesh Spider, the game now tells you how you can heal yourself. + LT3: + - rscadd: Introducing Nanotrasen Wave! A Nanotrasen exclusive, Waveallows your PDA + to be charged wirelessly through microwave frequencies. You can Wave-charge + your device by placing it inside a compatible microwave and selecting the charge + mode. + - rscadd: Microwaves can be upgraded to add wireless charging + - rscadd: Cell-swappable microwave for the engineer on-the-go + - rscadd: Microwave now has a wire to swap charge/cook modes + - rscadd: Furnishings RCD upgrade now includes wireless microwave + - rscadd: Tramstation and Birdshot engineering break rooms now have microwave and + donk pockets. Some microwaves come pre-equipped with wireless charging and an + upgraded cell. + - bugfix: The microwave in the snowdin ruin is now real, not a fluff prop + - bugfix: After the untimely loss of too many novice HoPs, the Icebox "New IDs and + You" instructions have been moved from the icemoon wastes to the HoP's office, + ending this rite of passage + - bugfix: Added some missing firelocks in the pharmacy area. Icebox pharmacy now + has a shower + SyncIt21: + - code_imp: removed round robin method of transferring reagents which would result + in some missing reagents after transferring. + - code_imp: added some more rounding for reagent operations. + - code_imp: cleaned up some plumbing & reaction chamber code + - code_imp: improves the performance of `update_total()` , `clear_reagents()` & + `del_reagent()` procs + - bugfix: plumbing setups will no longer output 0 or more than maximum available + volume of reagents. + - bugfix: removing, copying, transfering reagents is now done proportionally and + not equally again. + - refactor: examining individual reagents up close will display their volumes up + to 4 decimal places for accuracy. + - qol: droppers & beakers round the amount of reagents transferred before displaying + them to chat for easy readibility + - bugfix: You cannot order with cargo budget if you don't have cargo access in the + Galactic Market + - bugfix: Private & Cargo orders no longer get mixed together in the same crate + if you order them interchangeably so no more embezzlement in the Galactic Market + - bugfix: Orders made with cargo budget come in a regular cargo crate thus allowing + you to open them without QM cargo budget card in the Galactic Market + - qol: Orders made in the Galactic Market will deduct money from your account/cargo + budget only after the order has been confirmed in the cargo request console + & after the shuttle arrives with your order. This way you drain the budget only + after your orders were successfully delivered and not before hand itself + ViktorKoL: + - sound: the blood cult's rise to power is now accompanied by several new sound + effects + mc-oofert: + - refactor: venus human traps are basicmobs now + - balance: venus human traps have 100 health + - balance: venus human traps take damage out of range of kudzu, heal near kudzu, + are slightly slower, attack slower, and their damage output is slightly more + random + - balance: also venus human trap tangle ability now needs you to actually move backwards + to pull victims + vinylspiders: + - image: you can now change the style of lipstick to be higher or lower on the face + by alt-clicking the lipstick tube +2023-10-13: + EuSouAFazer: + - qol: The rollerdome is now better - the dance floor works now, and the bar is + groovier. + Jacquerel: + - bugfix: Dullahans can read, strip people, and utilise tools. + - bugfix: Dullahan brains and eyes will not decay while inside their living severed + head. + LT3: + - qol: Canisters can now be built in one step, no upgrading required + Paxilmaniac: + - image: Inhands for the Sakhno and related rifles will no longer be way too high + or big + Rhials: + - rscadd: Two new psyker-oriented virtual domains -- Crate Chaos and Infected Domain. + - rscadd: Map helper for cyber-police corpse spawn. + - rscadd: Map helper for swapping the encrypted crate in an area with a random crate + from that same area. + jlsnow301: + - bugfix: Fixed the errant bluescreen in the camera console. + mc-oofert: + - code_imp: basicmobs that delete on death, ghost before dying + san7890: + - bugfix: The Holy Hand Grenade's effect on revealing a revenant had its duration + accidentally nerfed, it is now back to 10 seconds. + - bugfix: Revenant midrounds should now properly run. + - bugfix: Revenant harvesting should now let you actually pass the final do_after + so you can harvest that sweet essence. +2023-10-14: + BlueMemesauce: + - bugfix: Modsuits can no longer be deepfried + DrDiasyl: + - sound: laser2.ogg sound has been changed. Now laser carbine uses it. + - image: Laser carbine and orange laser sprite have been improved. + IndieanaJones: + - bugfix: Space Dragon can break walls, eat corpses and destroy mechs more efficiently + again + - bugfix: Player-controlled lavaland elites can once again return to their tumor + after winning their fight + Jacquerel: + - bugfix: '"Mirror Walk" is once more the domain of the Maid in the Mirror rather + than "every heretic summon"' + - bugfix: Heretic mobs can once again survive space + - bugfix: Pete's anger management training has worn off, and he will once again + sometimes pick a fight with you for absolutely no reason. + - qol: Attacking a goat will not spam messages so frequently. + LT3: + - image: Nitrogen canisters are now yellow, antinob are grey/yellow, empty are grey, + hydrogen are red/white + MTandi: + - bugfix: The crew is instructed to place fax machines properly in the center of + a table without hanging. + Melbert: + - bugfix: Fixes Mauna Loa, Monover, Silibinin, Granibitaluri not exiting your system + on metabolism + - bugfix: Fixes holy water exiting your system at double the rate on metabolism + - bugfix: Holy Water no longer spams cultists with big text every time, it's much + more tame now + Rhials: + - qol: You can now return to your old body after being summoned by a manifest rune. + - qol: You can now return to your old body after dying in CTF. + - qol: You can now return to your old body after dying in the Medisim Shuttle battle + area. + - qol: You can no longer suicide in CTF areas, for integrity purposes. + bun235: + - rscadd: targetting someone's arm with *slap now has a unique message + dragomagol: + - qol: apples can now be sliced + mc-oofert: + - bugfix: sqdl2 query readout displays location of turfs properly + ninjanomnom: + - admin: VV can now display the contents of special byond lists like filters, or + client.images + - admin: VV on images now displays the image in the header + - admin: VV can now display filters and includes their type + san7890: + - bugfix: Space Dragons can now, once again, tear down walls and eat corpses. They + also have regained their special damage modifier when attacking mechs. diff --git a/icons/hud/radial.dmi b/icons/hud/radial.dmi index f4c4ab7693e98..42d5c451018ae 100644 Binary files a/icons/hud/radial.dmi and b/icons/hud/radial.dmi differ diff --git a/icons/mob/human/human_face.dmi b/icons/mob/human/human_face.dmi index 6985cf07eee49..6530b300aa676 100644 Binary files a/icons/mob/human/human_face.dmi and b/icons/mob/human/human_face.dmi differ diff --git a/icons/mob/inhands/weapons/guns_lefthand.dmi b/icons/mob/inhands/weapons/guns_lefthand.dmi index 013e6e4745854..d0caa04a33a03 100644 Binary files a/icons/mob/inhands/weapons/guns_lefthand.dmi and b/icons/mob/inhands/weapons/guns_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/guns_righthand.dmi b/icons/mob/inhands/weapons/guns_righthand.dmi index 14bf9e762c753..e4842aca23aa6 100644 Binary files a/icons/mob/inhands/weapons/guns_righthand.dmi and b/icons/mob/inhands/weapons/guns_righthand.dmi differ diff --git a/icons/mob/simple/animal.dmi b/icons/mob/simple/animal.dmi index 8e3affff4a90a..01670b07d9389 100644 Binary files a/icons/mob/simple/animal.dmi and b/icons/mob/simple/animal.dmi differ diff --git a/icons/mob/simple/jungle/mook.dmi b/icons/mob/simple/jungle/mook.dmi index c9265b22a0ad2..fbc38d29d99de 100644 Binary files a/icons/mob/simple/jungle/mook.dmi and b/icons/mob/simple/jungle/mook.dmi differ diff --git a/icons/mob/simple/lavaland/lavaland_monsters.dmi b/icons/mob/simple/lavaland/lavaland_monsters.dmi index 38b78cf468f1f..f68e3db4a6cb9 100644 Binary files a/icons/mob/simple/lavaland/lavaland_monsters.dmi and b/icons/mob/simple/lavaland/lavaland_monsters.dmi differ diff --git a/icons/mob/simple/lavaland/lavaland_monsters_wide.dmi b/icons/mob/simple/lavaland/lavaland_monsters_wide.dmi index 2be68ef4c6696..808fdc59d9bae 100644 Binary files a/icons/mob/simple/lavaland/lavaland_monsters_wide.dmi and b/icons/mob/simple/lavaland/lavaland_monsters_wide.dmi differ diff --git a/icons/obj/card.dmi b/icons/obj/card.dmi index 4172a0a3641a7..a5e34e9cc27cb 100644 Binary files a/icons/obj/card.dmi and b/icons/obj/card.dmi differ diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi index d89ee6e5d6408..fe74b6c11c5c7 100644 Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ diff --git a/icons/obj/fluff/general.dmi b/icons/obj/fluff/general.dmi index 3f990111c3c74..2628eea874694 100644 Binary files a/icons/obj/fluff/general.dmi and b/icons/obj/fluff/general.dmi differ diff --git a/icons/obj/food/egg.dmi b/icons/obj/food/egg.dmi index c7661fca918f0..58908d8247913 100644 Binary files a/icons/obj/food/egg.dmi and b/icons/obj/food/egg.dmi differ diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi index 97ded07df214b..46d7459e0b0b7 100644 Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ diff --git a/icons/obj/machines/atmospherics/unary_devices.dmi b/icons/obj/machines/atmospherics/unary_devices.dmi index 3350500fde950..6a929f211b8dc 100644 Binary files a/icons/obj/machines/atmospherics/unary_devices.dmi and b/icons/obj/machines/atmospherics/unary_devices.dmi differ diff --git a/icons/obj/machines/bitrunning.dmi b/icons/obj/machines/bitrunning.dmi index a910a16b35cf8..b3f8ad63a6c99 100644 Binary files a/icons/obj/machines/bitrunning.dmi and b/icons/obj/machines/bitrunning.dmi differ diff --git a/icons/obj/machines/microwave.dmi b/icons/obj/machines/microwave.dmi index 427a5d9daaee6..7a72ba5d3dca3 100644 Binary files a/icons/obj/machines/microwave.dmi and b/icons/obj/machines/microwave.dmi differ diff --git a/icons/obj/mining_zones/artefacts.dmi b/icons/obj/mining_zones/artefacts.dmi index f3f7d00e4eef8..d4c603834d21b 100644 Binary files a/icons/obj/mining_zones/artefacts.dmi and b/icons/obj/mining_zones/artefacts.dmi differ diff --git a/icons/obj/pipes_n_cables/!pipe_gas_overlays.dmi b/icons/obj/pipes_n_cables/!pipe_gas_overlays.dmi new file mode 100644 index 0000000000000..0262adcaeb241 Binary files /dev/null and b/icons/obj/pipes_n_cables/!pipe_gas_overlays.dmi differ diff --git a/icons/obj/pipes_n_cables/!pipes_bitmask.dmi b/icons/obj/pipes_n_cables/!pipes_bitmask.dmi new file mode 100644 index 0000000000000..97643036fbe3b Binary files /dev/null and b/icons/obj/pipes_n_cables/!pipes_bitmask.dmi differ diff --git a/icons/obj/pipes_n_cables/atmos.dmi b/icons/obj/pipes_n_cables/atmos.dmi index 65cbdc672db74..91badbf3ccf9b 100644 Binary files a/icons/obj/pipes_n_cables/atmos.dmi and b/icons/obj/pipes_n_cables/atmos.dmi differ diff --git a/icons/obj/pipes_n_cables/pipe_template_pieces.dmi b/icons/obj/pipes_n_cables/pipe_template_pieces.dmi new file mode 100644 index 0000000000000..d0d2f7ff7bb80 Binary files /dev/null and b/icons/obj/pipes_n_cables/pipe_template_pieces.dmi differ diff --git a/icons/obj/pipes_n_cables/pipes_bitmask.dmi b/icons/obj/pipes_n_cables/pipes_bitmask.dmi deleted file mode 100644 index 7a382fb55c5e4..0000000000000 Binary files a/icons/obj/pipes_n_cables/pipes_bitmask.dmi and /dev/null differ diff --git a/icons/obj/service/hydroponics/harvest.dmi b/icons/obj/service/hydroponics/harvest.dmi index 28a43776a50a0..57a127cd9975e 100644 Binary files a/icons/obj/service/hydroponics/harvest.dmi and b/icons/obj/service/hydroponics/harvest.dmi differ diff --git a/icons/obj/weapons/guns/energy.dmi b/icons/obj/weapons/guns/energy.dmi index 97b75335b91a0..9a86e65329fb4 100644 Binary files a/icons/obj/weapons/guns/energy.dmi and b/icons/obj/weapons/guns/energy.dmi differ diff --git a/icons/obj/weapons/guns/projectiles.dmi b/icons/obj/weapons/guns/projectiles.dmi index e1c70c4f5ade4..05ad142ff58d3 100644 Binary files a/icons/obj/weapons/guns/projectiles.dmi and b/icons/obj/weapons/guns/projectiles.dmi differ diff --git a/sound/ambience/antag/bloodcult/bloodcult_eyes.ogg b/sound/ambience/antag/bloodcult/bloodcult_eyes.ogg new file mode 100644 index 0000000000000..38c223b1ad858 Binary files /dev/null and b/sound/ambience/antag/bloodcult/bloodcult_eyes.ogg differ diff --git a/sound/ambience/antag/bloodcult.ogg b/sound/ambience/antag/bloodcult/bloodcult_gain.ogg similarity index 100% rename from sound/ambience/antag/bloodcult.ogg rename to sound/ambience/antag/bloodcult/bloodcult_gain.ogg diff --git a/sound/ambience/antag/bloodcult/bloodcult_halos.ogg b/sound/ambience/antag/bloodcult/bloodcult_halos.ogg new file mode 100644 index 0000000000000..bd22934fd301b Binary files /dev/null and b/sound/ambience/antag/bloodcult/bloodcult_halos.ogg differ diff --git a/sound/ambience/antag/bloodcult/bloodcult_scribe.ogg b/sound/ambience/antag/bloodcult/bloodcult_scribe.ogg new file mode 100644 index 0000000000000..a01ef30a1d487 Binary files /dev/null and b/sound/ambience/antag/bloodcult/bloodcult_scribe.ogg differ diff --git a/sound/attributions.txt b/sound/attributions.txt index 3ae6c797dd33b..4f000ad82f95c 100644 --- a/sound/attributions.txt +++ b/sound/attributions.txt @@ -105,3 +105,8 @@ https://freesound.org/people/reelworldstudio/sounds/161122/ arcade_jump.ogg is adapted from se2001's "8-Bit Jump 3", which is public domain (CC 0): hhttps://freesound.org/people/se2001/sounds/528568/ + +laser2.ogg is adapted with 3 SFX made by junggle (CC 4), inferno (CC Sampling+), humanoide9000 (CC 0): +https://freesound.org/people/junggle/sounds/28917/ +https://freesound.org/people/inferno/sounds/18397/ +https://freesound.org/people/humanoide9000/sounds/330293/ diff --git a/sound/effects/footstep/spurs1.ogg b/sound/effects/footstep/spurs1.ogg new file mode 100644 index 0000000000000..d2754587ca15e Binary files /dev/null and b/sound/effects/footstep/spurs1.ogg differ diff --git a/sound/effects/footstep/spurs2.ogg b/sound/effects/footstep/spurs2.ogg new file mode 100644 index 0000000000000..e02725e9079bb Binary files /dev/null and b/sound/effects/footstep/spurs2.ogg differ diff --git a/sound/effects/footstep/spurs3.ogg b/sound/effects/footstep/spurs3.ogg new file mode 100644 index 0000000000000..e79b90dc78d9f Binary files /dev/null and b/sound/effects/footstep/spurs3.ogg differ diff --git a/sound/machines/fan_break.ogg b/sound/machines/fan_break.ogg new file mode 100644 index 0000000000000..ca0549333ad66 Binary files /dev/null and b/sound/machines/fan_break.ogg differ diff --git a/sound/machines/fan_loop.ogg b/sound/machines/fan_loop.ogg new file mode 100644 index 0000000000000..9c7820548f670 Binary files /dev/null and b/sound/machines/fan_loop.ogg differ diff --git a/sound/machines/fan_start.ogg b/sound/machines/fan_start.ogg new file mode 100644 index 0000000000000..a0d11c3e969aa Binary files /dev/null and b/sound/machines/fan_start.ogg differ diff --git a/sound/machines/fan_stop.ogg b/sound/machines/fan_stop.ogg new file mode 100644 index 0000000000000..84d39c3ee5a85 Binary files /dev/null and b/sound/machines/fan_stop.ogg differ diff --git a/sound/weapons/laser2.ogg b/sound/weapons/laser2.ogg index 5e22e30d4a0d8..7fd3969b2adf3 100644 Binary files a/sound/weapons/laser2.ogg and b/sound/weapons/laser2.ogg differ diff --git a/strings/sillytips.txt b/strings/sillytips.txt index 752a09b25cb58..5aa7af7ba0064 100644 --- a/strings/sillytips.txt +++ b/strings/sillytips.txt @@ -37,3 +37,4 @@ To defeat the slaughter demon, shoot at it until it dies. When a round ends nearly everything about it is lost forever, leave your salt behind with it. You can win a pulse rifle from the arcade machine. Honest. Your sprite represents your hitbox, so that afro makes you easier to kill. The sacrifices we make for style. +Gorillas can be killed by land mines placed along forest paths. diff --git a/strings/tips.txt b/strings/tips.txt index c96394adb2657..51dd6a635d160 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -266,5 +266,6 @@ You can screwdriver any non-chemical grenade to shorten fuses from 5 seconds, to You can spray a fire extinguisher, throw items or fire a gun while floating through space to change your direction. Simply fire opposite to where you want to go. You can swap floor tiles by holding a crowbar in one hand and a stack of tiles in the other. You can use a machine in the vault to deposit cash or rob Cargo's department funds. +You can use an upgraded microwave to charge your PDA! You'll quickly lose your interest in the game if you play to win and kill. If you find yourself doing this, take a step back and talk to people - it's a much better experience! -Some areas of the station use simple nautical directions to indicate their respective locations, like Fore (Front of the ship), Aft (Back), Port (Left side), Starboard (Right), Quarter and Bow (Either sides of Aft and Fore, respectively). You can review these terms on the Notepad App of your PDA. \ No newline at end of file +Some areas of the station use simple nautical directions to indicate their respective locations, like Fore (Front of the ship), Aft (Back), Port (Left side), Starboard (Right), Quarter and Bow (Either sides of Aft and Fore, respectively). You can review these terms on the Notepad App of your PDA. diff --git a/tgstation.dme b/tgstation.dme index 96b0f791a98c1..bb2da82fed3ee 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -66,7 +66,6 @@ #include "code\__DEFINES\communications.dm" #include "code\__DEFINES\computers.dm" #include "code\__DEFINES\configuration.dm" -#include "code\__DEFINES\construction.dm" #include "code\__DEFINES\cooldowns.dm" #include "code\__DEFINES\crafting.dm" #include "code\__DEFINES\crushing.dm" @@ -134,7 +133,6 @@ #include "code\__DEFINES\map_switch.dm" #include "code\__DEFINES\mapping.dm" #include "code\__DEFINES\maps.dm" -#include "code\__DEFINES\materials.dm" #include "code\__DEFINES\maths.dm" #include "code\__DEFINES\matrices.dm" #include "code\__DEFINES\MC.dm" @@ -267,6 +265,10 @@ #include "code\__DEFINES\atmospherics\atmos_mapping_helpers.dm" #include "code\__DEFINES\atmospherics\atmos_mob_interaction.dm" #include "code\__DEFINES\atmospherics\atmos_piping.dm" +#include "code\__DEFINES\construction\actions.dm" +#include "code\__DEFINES\construction\material.dm" +#include "code\__DEFINES\construction\rcd.dm" +#include "code\__DEFINES\construction\structures.dm" #include "code\__DEFINES\dcs\flags.dm" #include "code\__DEFINES\dcs\helpers.dm" #include "code\__DEFINES\dcs\signals\mapping.dm" @@ -486,6 +488,7 @@ #include "code\_globalvars\lighting.dm" #include "code\_globalvars\logging.dm" #include "code\_globalvars\phobias.dm" +#include "code\_globalvars\rcd.dm" #include "code\_globalvars\religion.dm" #include "code\_globalvars\tgui.dm" #include "code\_globalvars\time_vars.dm" @@ -494,6 +497,7 @@ #include "code\_globalvars\lists\ambience.dm" #include "code\_globalvars\lists\client.dm" #include "code\_globalvars\lists\color.dm" +#include "code\_globalvars\lists\crafting.dm" #include "code\_globalvars\lists\flavor_misc.dm" #include "code\_globalvars\lists\icons.dm" #include "code\_globalvars\lists\keybindings.dm" @@ -504,6 +508,8 @@ #include "code\_globalvars\lists\objects.dm" #include "code\_globalvars\lists\poll_ignore.dm" #include "code\_globalvars\lists\quirks.dm" +#include "code\_globalvars\lists\rcd.dm" +#include "code\_globalvars\lists\reagents.dm" #include "code\_globalvars\lists\rtd.dm" #include "code\_globalvars\lists\typecache.dm" #include "code\_globalvars\lists\wiremod.dm" @@ -803,7 +809,6 @@ #include "code\datums\ai\basic_mobs\generic_controllers.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\basic_attacking.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\climb_tree.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\find_mineable_wall.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\find_parent.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\nearest_targetting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\pick_up_item.dm" @@ -818,15 +823,19 @@ #include "code\datums\ai\basic_mobs\basic_ai_behaviors\write_on_paper.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\attack_adjacent_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\attack_obstacle_in_path.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\capricious_retaliate.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\climb_tree.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_food.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_paper_and_write.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_parent.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\flee_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\maintain_distance.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\mine_walls.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\move_to_cardinal.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\opportunistic_ventcrawler.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\ranged_skirmish.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\run_emote.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\shapechange_ambush.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_nearest_target_to_flee.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm" @@ -835,6 +844,7 @@ #include "code\datums\ai\basic_mobs\basic_subtrees\stare_at_thing.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\targeted_mob_ability.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\teleport_away_from_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\tipped_subtree.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\travel_to_point.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\use_mob_ability.dm" @@ -845,6 +855,7 @@ #include "code\datums\ai\basic_mobs\pet_commands\play_dead.dm" #include "code\datums\ai\basic_mobs\targetting_datums\basic_targetting_datum.dm" #include "code\datums\ai\basic_mobs\targetting_datums\dont_target_friends.dm" +#include "code\datums\ai\basic_mobs\targetting_datums\with_object.dm" #include "code\datums\ai\cursed\cursed_behaviors.dm" #include "code\datums\ai\cursed\cursed_controller.dm" #include "code\datums\ai\cursed\cursed_subtrees.dm" @@ -932,6 +943,7 @@ #include "code\datums\components\attached_sticker.dm" #include "code\datums\components\aura_healing.dm" #include "code\datums\components\bakeable.dm" +#include "code\datums\components\basic_inhands.dm" #include "code\datums\components\basic_mob_attack_telegraph.dm" #include "code\datums\components\basic_ranged_ready_overlay.dm" #include "code\datums\components\beetlejuice.dm" @@ -1012,6 +1024,7 @@ #include "code\datums\components\itembound.dm" #include "code\datums\components\itempicky.dm" #include "code\datums\components\jetpack.dm" +#include "code\datums\components\joint_damage.dm" #include "code\datums\components\jousting.dm" #include "code\datums\components\keep_me_secure.dm" #include "code\datums\components\knockoff.dm" @@ -1108,6 +1121,7 @@ #include "code\datums\components\technoshy.dm" #include "code\datums\components\telegraph_ability.dm" #include "code\datums\components\temporary_body.dm" +#include "code\datums\components\temporary_description.dm" #include "code\datums\components\tether.dm" #include "code\datums\components\thermite.dm" #include "code\datums\components\tippable.dm" @@ -1286,6 +1300,7 @@ #include "code\datums\elements\death_gases.dm" #include "code\datums\elements\delete_on_drop.dm" #include "code\datums\elements\deliver_first.dm" +#include "code\datums\elements\dextrous.dm" #include "code\datums\elements\diggable.dm" #include "code\datums\elements\digitalcamo.dm" #include "code\datums\elements\drag_pickup.dm" @@ -1357,6 +1372,7 @@ #include "code\datums\elements\squish.dm" #include "code\datums\elements\sticker.dm" #include "code\datums\elements\strippable.dm" +#include "code\datums\elements\structure_repair.dm" #include "code\datums\elements\swabbable.dm" #include "code\datums\elements\tear_wall.dm" #include "code\datums\elements\temporary_atom.dm" @@ -1374,6 +1390,7 @@ #include "code\datums\elements\waddling.dm" #include "code\datums\elements\wall_engraver.dm" #include "code\datums\elements\wall_smasher.dm" +#include "code\datums\elements\wall_walker.dm" #include "code\datums\elements\weapon_description.dm" #include "code\datums\elements\weather_listener.dm" #include "code\datums\elements\web_walker.dm" @@ -1436,11 +1453,13 @@ #include "code\datums\keybinding\robot.dm" #include "code\datums\looping_sounds\_looping_sound.dm" #include "code\datums\looping_sounds\acid.dm" +#include "code\datums\looping_sounds\burning.dm" #include "code\datums\looping_sounds\choking.dm" #include "code\datums\looping_sounds\cyborg.dm" #include "code\datums\looping_sounds\item_sounds.dm" #include "code\datums\looping_sounds\machinery_sounds.dm" #include "code\datums\looping_sounds\music.dm" +#include "code\datums\looping_sounds\vents.dm" #include "code\datums\looping_sounds\weather.dm" #include "code\datums\mapgen\_MapGenerator.dm" #include "code\datums\mapgen\CaveGenerator.dm" @@ -1796,7 +1815,6 @@ #include "code\game\machinery\barsigns.dm" #include "code\game\machinery\botlaunchpad.dm" #include "code\game\machinery\buttons.dm" -#include "code\game\machinery\canister_frame.dm" #include "code\game\machinery\cell_charger.dm" #include "code\game\machinery\civilian_bounties.dm" #include "code\game\machinery\constructable_frame.dm" @@ -1989,6 +2007,7 @@ #include "code\game\objects\effects\poster_demotivational.dm" #include "code\game\objects\effects\poster_motivational.dm" #include "code\game\objects\effects\powerup.dm" +#include "code\game\objects\effects\rcd.dm" #include "code\game\objects\effects\spiderwebs.dm" #include "code\game\objects\effects\step_triggers.dm" #include "code\game\objects\effects\wanted_poster.dm" @@ -2830,6 +2849,7 @@ #include "code\modules\antagonists\changeling\powers\strained_muscles.dm" #include "code\modules\antagonists\changeling\powers\tiny_prick.dm" #include "code\modules\antagonists\changeling\powers\transform.dm" +#include "code\modules\antagonists\changeling\powers\void_adaption.dm" #include "code\modules\antagonists\clown_ops\bananium_bomb.dm" #include "code\modules\antagonists\clown_ops\clown_weapons.dm" #include "code\modules\antagonists\clown_ops\clownop.dm" @@ -2938,7 +2958,6 @@ #include "code\modules\antagonists\heretic\magic\void_phase.dm" #include "code\modules\antagonists\heretic\magic\void_pull.dm" #include "code\modules\antagonists\heretic\magic\wave_of_desperation.dm" -#include "code\modules\antagonists\heretic\mobs\maid_in_mirror.dm" #include "code\modules\antagonists\heretic\status_effects\buffs.dm" #include "code\modules\antagonists\heretic\status_effects\debuffs.dm" #include "code\modules\antagonists\heretic\status_effects\ghoul.dm" @@ -2984,7 +3003,6 @@ #include "code\modules\antagonists\pirate\pirate_shuttle_equipment.dm" #include "code\modules\antagonists\pyro_slime\pyro_slime.dm" #include "code\modules\antagonists\revenant\haunted_item.dm" -#include "code\modules\antagonists\revenant\revenant_abilities.dm" #include "code\modules\antagonists\revenant\revenant_antag.dm" #include "code\modules\antagonists\revenant\revenant_blight.dm" #include "code\modules\antagonists\revolution\enemy_of_the_state.dm" @@ -3196,6 +3214,7 @@ #include "code\modules\atmospherics\machinery\pipes\layermanifold.dm" #include "code\modules\atmospherics\machinery\pipes\mapping.dm" #include "code\modules\atmospherics\machinery\pipes\multiz.dm" +#include "code\modules\atmospherics\machinery\pipes\pipe_spritesheet_helper.dm" #include "code\modules\atmospherics\machinery\pipes\pipes.dm" #include "code\modules\atmospherics\machinery\pipes\smart.dm" #include "code\modules\atmospherics\machinery\pipes\heat_exchange\he_pipes.dm" @@ -3240,15 +3259,16 @@ #include "code\modules\bitrunning\abilities.dm" #include "code\modules\bitrunning\alerts.dm" #include "code\modules\bitrunning\areas.dm" +#include "code\modules\bitrunning\designs.dm" #include "code\modules\bitrunning\event.dm" #include "code\modules\bitrunning\job.dm" +#include "code\modules\bitrunning\outfits.dm" #include "code\modules\bitrunning\turfs.dm" #include "code\modules\bitrunning\antagonists\cyber_police.dm" -#include "code\modules\bitrunning\antagonists\outfit.dm" #include "code\modules\bitrunning\components\avatar_connection.dm" #include "code\modules\bitrunning\components\bitrunning_points.dm" #include "code\modules\bitrunning\components\netpod_healing.dm" -#include "code\modules\bitrunning\objects\bit_vendor.dm" +#include "code\modules\bitrunning\objects\byteforge.dm" #include "code\modules\bitrunning\objects\clothing.dm" #include "code\modules\bitrunning\objects\disks.dm" #include "code\modules\bitrunning\objects\hololadder.dm" @@ -3257,6 +3277,7 @@ #include "code\modules\bitrunning\objects\loot_crate.dm" #include "code\modules\bitrunning\objects\netpod.dm" #include "code\modules\bitrunning\objects\quantum_console.dm" +#include "code\modules\bitrunning\objects\vendor.dm" #include "code\modules\bitrunning\orders\disks.dm" #include "code\modules\bitrunning\orders\flair.dm" #include "code\modules\bitrunning\orders\tech.dm" @@ -3279,6 +3300,8 @@ #include "code\modules\bitrunning\virtual_domain\domains\legion.dm" #include "code\modules\bitrunning\virtual_domain\domains\pipedream.dm" #include "code\modules\bitrunning\virtual_domain\domains\pirates.dm" +#include "code\modules\bitrunning\virtual_domain\domains\psyker_shuffle.dm" +#include "code\modules\bitrunning\virtual_domain\domains\psyker_zombies.dm" #include "code\modules\bitrunning\virtual_domain\domains\stairs_and_cliffs.dm" #include "code\modules\bitrunning\virtual_domain\domains\syndicate_assault.dm" #include "code\modules\bitrunning\virtual_domain\domains\test_only.dm" @@ -4195,6 +4218,7 @@ #include "code\modules\mapfluff\ruins\lavalandruin_code\sloth.dm" #include "code\modules\mapfluff\ruins\lavalandruin_code\surface.dm" #include "code\modules\mapfluff\ruins\lavalandruin_code\syndicate_base.dm" +#include "code\modules\mapfluff\ruins\lavalandruin_code\watcher_grave.dm" #include "code\modules\mapfluff\ruins\objects_and_mobs\ash_walker_den.dm" #include "code\modules\mapfluff\ruins\objects_and_mobs\cursed_slot_machine.dm" #include "code\modules\mapfluff\ruins\objects_and_mobs\necropolis_gate.dm" @@ -4351,6 +4375,8 @@ #include "code\modules\mob\living\basic\blob_minions\blobbernaut.dm" #include "code\modules\mob\living\basic\clown\clown.dm" #include "code\modules\mob\living\basic\clown\clown_ai.dm" +#include "code\modules\mob\living\basic\constructs\_construct.dm" +#include "code\modules\mob\living\basic\constructs\harvester.dm" #include "code\modules\mob\living\basic\farm_animals\deer.dm" #include "code\modules\mob\living\basic\farm_animals\pig.dm" #include "code\modules\mob\living\basic\farm_animals\pony.dm" @@ -4368,14 +4394,26 @@ #include "code\modules\mob\living\basic\farm_animals\goat\_goat.dm" #include "code\modules\mob\living\basic\farm_animals\goat\goat_ai.dm" #include "code\modules\mob\living\basic\farm_animals\goat\goat_subtypes.dm" +#include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla.dm" +#include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla_accessories.dm" +#include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla_ai.dm" +#include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla_emotes.dm" +#include "code\modules\mob\living\basic\heretic\ash_spirit.dm" #include "code\modules\mob\living\basic\heretic\fire_shark.dm" +#include "code\modules\mob\living\basic\heretic\flesh_stalker.dm" #include "code\modules\mob\living\basic\heretic\flesh_worm.dm" #include "code\modules\mob\living\basic\heretic\heretic_summon.dm" +#include "code\modules\mob\living\basic\heretic\maid_in_the_mirror.dm" #include "code\modules\mob\living\basic\heretic\raw_prophet.dm" +#include "code\modules\mob\living\basic\heretic\rust_walker.dm" #include "code\modules\mob\living\basic\heretic\star_gazer.dm" +#include "code\modules\mob\living\basic\icemoon\ice_demon\ice_demon.dm" +#include "code\modules\mob\living\basic\icemoon\ice_demon\ice_demon_abilities.dm" +#include "code\modules\mob\living\basic\icemoon\ice_demon\ice_demon_ai.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp_abilities.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp_ai.dm" +#include "code\modules\mob\living\basic\jungle\venus_human_trap.dm" #include "code\modules\mob\living\basic\jungle\mega_arachnid\mega_arachnid.dm" #include "code\modules\mob\living\basic\jungle\mega_arachnid\mega_arachnid_abilities.dm" #include "code\modules\mob\living\basic\jungle\mega_arachnid\mega_arachnid_ai.dm" @@ -4414,6 +4452,10 @@ #include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity.dm" #include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity_ai.dm" #include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity_trophy.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook_abilities.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook_ai.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook_village.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher_ai.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher_gaze.dm" @@ -4426,6 +4468,7 @@ #include "code\modules\mob\living\basic\pets\fox.dm" #include "code\modules\mob\living\basic\pets\penguin.dm" #include "code\modules\mob\living\basic\pets\pet.dm" +#include "code\modules\mob\living\basic\pets\sloth.dm" #include "code\modules\mob\living\basic\pets\dog\_dog.dm" #include "code\modules\mob\living\basic\pets\dog\corgi.dm" #include "code\modules\mob\living\basic\pets\dog\dog_subtypes.dm" @@ -4436,7 +4479,6 @@ #include "code\modules\mob\living\basic\space_fauna\faithless.dm" #include "code\modules\mob\living\basic\space_fauna\garden_gnome.dm" #include "code\modules\mob\living\basic\space_fauna\ghost.dm" -#include "code\modules\mob\living\basic\space_fauna\headslug.dm" #include "code\modules\mob\living\basic\space_fauna\killer_tomato.dm" #include "code\modules\mob\living\basic\space_fauna\lightgeist.dm" #include "code\modules\mob\living\basic\space_fauna\morph.dm" @@ -4455,6 +4497,8 @@ #include "code\modules\mob\living\basic\space_fauna\carp\carp_controllers.dm" #include "code\modules\mob\living\basic\space_fauna\carp\magicarp.dm" #include "code\modules\mob\living\basic\space_fauna\carp\megacarp.dm" +#include "code\modules\mob\living\basic\space_fauna\changeling\flesh_spider.dm" +#include "code\modules\mob\living\basic\space_fauna\changeling\headslug.dm" #include "code\modules\mob\living\basic\space_fauna\demon\demon.dm" #include "code\modules\mob\living\basic\space_fauna\demon\demon_items.dm" #include "code\modules\mob\living\basic\space_fauna\demon\demon_subtypes.dm" @@ -4478,6 +4522,12 @@ #include "code\modules\mob\living\basic\space_fauna\regal_rat\regal_rat.dm" #include "code\modules\mob\living\basic\space_fauna\regal_rat\regal_rat_actions.dm" #include "code\modules\mob\living\basic\space_fauna\regal_rat\regal_rat_ai.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\_revenant.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_abilities.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_effects.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_harvest.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_items.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_objectives.dm" #include "code\modules\mob\living\basic\space_fauna\snake\snake.dm" #include "code\modules\mob\living\basic\space_fauna\snake\snake_ai.dm" #include "code\modules\mob\living\basic\space_fauna\spider\spider.dm" @@ -4656,7 +4706,6 @@ #include "code\modules\mob\living\simple_animal\animal_defense.dm" #include "code\modules\mob\living\simple_animal\damage_procs.dm" #include "code\modules\mob\living\simple_animal\parrot.dm" -#include "code\modules\mob\living\simple_animal\revenant.dm" #include "code\modules\mob\living\simple_animal\shade.dm" #include "code\modules\mob\living\simple_animal\simple_animal.dm" #include "code\modules\mob\living\simple_animal\bot\bot.dm" @@ -4676,7 +4725,6 @@ #include "code\modules\mob\living\simple_animal\friendly\cat.dm" #include "code\modules\mob\living\simple_animal\friendly\gondola.dm" #include "code\modules\mob\living\simple_animal\friendly\pet.dm" -#include "code\modules\mob\living\simple_animal\friendly\sloth.dm" #include "code\modules\mob\living\simple_animal\friendly\drone\_drone.dm" #include "code\modules\mob\living\simple_animal\friendly\drone\drone_say.dm" #include "code\modules\mob\living\simple_animal\friendly\drone\drone_tools.dm" @@ -4701,7 +4749,6 @@ #include "code\modules\mob\living\simple_animal\guardian\types\support.dm" #include "code\modules\mob\living\simple_animal\hostile\alien.dm" #include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm" -#include "code\modules\mob\living\simple_animal\hostile\heretic_monsters.dm" #include "code\modules\mob\living\simple_animal\hostile\hostile.dm" #include "code\modules\mob\living\simple_animal\hostile\illusion.dm" #include "code\modules\mob\living\simple_animal\hostile\mimic.dm" @@ -4711,20 +4758,14 @@ #include "code\modules\mob\living\simple_animal\hostile\skeleton.dm" #include "code\modules\mob\living\simple_animal\hostile\space_dragon.dm" #include "code\modules\mob\living\simple_animal\hostile\vatbeast.dm" -#include "code\modules\mob\living\simple_animal\hostile\venus_human_trap.dm" #include "code\modules\mob\living\simple_animal\hostile\wizard.dm" #include "code\modules\mob\living\simple_animal\hostile\zombie.dm" #include "code\modules\mob\living\simple_animal\hostile\constructs\artificer.dm" #include "code\modules\mob\living\simple_animal\hostile\constructs\constructs.dm" -#include "code\modules\mob\living\simple_animal\hostile\constructs\harvester.dm" #include "code\modules\mob\living\simple_animal\hostile\constructs\juggernaut.dm" #include "code\modules\mob\living\simple_animal\hostile\constructs\wraith.dm" -#include "code\modules\mob\living\simple_animal\hostile\gorilla\emotes.dm" -#include "code\modules\mob\living\simple_animal\hostile\gorilla\gorilla.dm" -#include "code\modules\mob\living\simple_animal\hostile\gorilla\visuals_icons.dm" #include "code\modules\mob\living\simple_animal\hostile\jungle\_jungle_mobs.dm" #include "code\modules\mob\living\simple_animal\hostile\jungle\leaper.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\mook.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\_megafauna.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\blood_drunk_miner.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\bubblegum.dm" @@ -4737,7 +4778,6 @@ #include "code\modules\mob\living\simple_animal\hostile\megafauna\wendigo.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\curse_blob.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\gutlunch.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\ice_demon.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\mining_mobs.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\polarbear.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\wolf.dm" diff --git a/tgui/packages/tgui/constants.ts b/tgui/packages/tgui/constants.ts index 270ce9873bd6e..db6b33ca7d20d 100644 --- a/tgui/packages/tgui/constants.ts +++ b/tgui/packages/tgui/constants.ts @@ -159,7 +159,7 @@ const GASES = [ path: '/datum/gas/nitrogen', name: 'Nitrogen', label: 'N₂', - color: 'red', + color: 'yellow', }, { id: 'co2', diff --git a/tgui/packages/tgui/interfaces/AdminFax.js b/tgui/packages/tgui/interfaces/AdminFax.js index e91130baf394f..46e1261592214 100644 --- a/tgui/packages/tgui/interfaces/AdminFax.js +++ b/tgui/packages/tgui/interfaces/AdminFax.js @@ -91,7 +91,7 @@ export const FaxMainPanel = (props, context) => { icon="n" mr="7px" width="49%" - onClick={() => setPaperName('Nanotrasen Offical Report')}> + onClick={() => setPaperName('Nanotrasen Official Report')}> Nanotrasen