diff --git a/_maps/map_files/YogsBox/YogsBox.dmm b/_maps/map_files/YogsBox/YogsBox.dmm
index 0c472e77353a..ff197c641c17 100644
--- a/_maps/map_files/YogsBox/YogsBox.dmm
+++ b/_maps/map_files/YogsBox/YogsBox.dmm
@@ -231,16 +231,6 @@
/mob/living/simple_animal/spiffles,
/turf/open/floor/plasteel,
/area/clerk)
-"aaE" = (
-/obj/structure/window/reinforced,
-/obj/structure/window/reinforced{
- dir = 8
- },
-/obj/machinery/atmospherics/components/unary/vent_pump/on/layer2{
- dir = 1
- },
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"aaG" = (
/obj/structure/window/reinforced,
/obj/structure/chair{
@@ -5904,29 +5894,6 @@
},
/turf/open/floor/plating,
/area/maintenance/fore)
-"apf" = (
-/obj/machinery/airalarm{
- dir = 4;
- pixel_x = -24
- },
-/obj/structure/disposalpipe/segment,
-/obj/effect/turf_decal/tile/neutral{
- dir = 1
- },
-/obj/effect/turf_decal/tile/neutral{
- dir = 8
- },
-/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden/layer4{
- dir = 8
- },
-/obj/machinery/atmospherics/pipe/manifold/supply/hidden/layer2{
- dir = 8
- },
-/obj/structure/cable{
- icon_state = "1-2"
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"apg" = (
/obj/machinery/door/airlock/maintenance{
req_access_txt = "12"
@@ -6530,16 +6497,6 @@
/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2,
/turf/open/floor/plasteel,
/area/crew_quarters/fitness)
-"arn" = (
-/obj/structure/closet/athletic_mixed,
-/obj/effect/turf_decal/tile/neutral{
- dir = 1
- },
-/obj/effect/turf_decal/tile/neutral{
- dir = 4
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"aro" = (
/turf/open/floor/engine{
name = "Holodeck Projector Floor"
@@ -6793,20 +6750,6 @@
},
/turf/open/floor/plasteel,
/area/crew_quarters/fitness)
-"asr" = (
-/obj/structure/closet/boxinggloves,
-/obj/machinery/light{
- dir = 1
- },
-/obj/item/clothing/shoes/jackboots,
-/obj/effect/turf_decal/tile/neutral{
- dir = 1
- },
-/obj/effect/turf_decal/tile/neutral{
- dir = 4
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"ast" = (
/obj/structure/closet/lasertag/blue,
/obj/effect/turf_decal/tile/neutral{
@@ -7000,15 +6943,6 @@
},
/turf/open/floor/plasteel/grimy,
/area/security/detectives_office)
-"ath" = (
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
- dir = 4
- },
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
- dir = 8
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"ati" = (
/obj/effect/turf_decal/tile/neutral,
/obj/effect/turf_decal/tile/neutral{
@@ -7057,24 +6991,6 @@
},
/turf/open/floor/plasteel/dark,
/area/security/courtroom)
-"atn" = (
-/obj/machinery/door/window/eastright{
- base_state = "left";
- dir = 8;
- icon_state = "left";
- name = "Fitness Ring"
- },
-/obj/structure/window/reinforced{
- dir = 1
- },
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
- dir = 4
- },
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
- dir = 10
- },
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"ato" = (
/obj/effect/decal/cleanable/dirt,
/obj/structure/chair{
@@ -7220,15 +7136,6 @@
"atN" = (
/turf/closed/wall/r_wall,
/area/science/nanite)
-"atP" = (
-/obj/structure/window/reinforced{
- dir = 1
- },
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
- dir = 4
- },
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"atQ" = (
/obj/machinery/door/airlock{
id_tag = "Dorm5";
@@ -7375,15 +7282,6 @@
/obj/machinery/light/small,
/turf/open/floor/plasteel/dark,
/area/security/courtroom)
-"aur" = (
-/obj/structure/window/reinforced{
- dir = 1
- },
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
- dir = 8
- },
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"auu" = (
/obj/structure/chair{
dir = 8
@@ -7393,18 +7291,6 @@
},
/turf/open/floor/plasteel,
/area/crew_quarters/fitness)
-"auv" = (
-/obj/structure/window/reinforced{
- dir = 4
- },
-/obj/structure/window/reinforced{
- dir = 1
- },
-/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{
- dir = 8
- },
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"auw" = (
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer2{
dir = 8
@@ -7774,21 +7660,6 @@
},
/turf/open/floor/plating,
/area/maintenance/fore)
-"avw" = (
-/obj/structure/disposalpipe/segment,
-/obj/effect/turf_decal/tile/neutral{
- dir = 1
- },
-/obj/effect/turf_decal/tile/neutral{
- dir = 8
- },
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4,
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2,
-/obj/structure/cable{
- icon_state = "1-2"
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"avx" = (
/obj/effect/spawner/structure/window,
/turf/open/floor/plating,
@@ -7972,12 +7843,6 @@
},
/turf/open/floor/wood,
/area/crew_quarters/dorms)
-"awb" = (
-/obj/machinery/atmospherics/components/unary/vent_pump/on/layer2{
- dir = 8
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"awc" = (
/obj/structure/rack,
/obj/effect/spawner/lootdrop/maintenance,
@@ -8426,19 +8291,6 @@
},
/turf/open/floor/plasteel,
/area/hydroponics/garden)
-"axU" = (
-/obj/structure/window/reinforced,
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
-"axW" = (
-/obj/machinery/door/window/eastright{
- base_state = "left";
- icon_state = "left";
- name = "Fitness Ring"
- },
-/obj/structure/window/reinforced,
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"axX" = (
/obj/machinery/light/small{
dir = 8
@@ -8800,35 +8652,6 @@
},
/turf/open/floor/plasteel,
/area/crew_quarters/fitness)
-"azl" = (
-/obj/effect/turf_decal/tile/neutral{
- dir = 8
- },
-/obj/structure/disposalpipe/segment{
- dir = 10
- },
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
- dir = 10
- },
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
- dir = 10
- },
-/obj/structure/cable{
- icon_state = "2-8"
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
-"azm" = (
-/obj/structure/chair/stool{
- pixel_y = 8
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
-"azn" = (
-/obj/structure/table,
-/obj/item/clothing/mask/balaclava,
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"azq" = (
/obj/structure/cable{
icon_state = "1-2"
@@ -9060,25 +8883,6 @@
},
/turf/open/floor/plasteel,
/area/ai_monitored/storage/eva)
-"aAk" = (
-/obj/structure/cable{
- icon_state = "4-8"
- },
-/obj/effect/turf_decal/tile/neutral,
-/obj/effect/turf_decal/tile/neutral{
- dir = 8
- },
-/obj/structure/disposalpipe/segment{
- dir = 8
- },
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
- dir = 4
- },
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
- dir = 8
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"aAl" = (
/obj/structure/cable{
icon_state = "1-4"
@@ -9119,28 +8923,6 @@
},
/turf/open/floor/plasteel,
/area/vacant_room/commissary)
-"aAn" = (
-/obj/structure/cable{
- icon_state = "4-8"
- },
-/obj/structure/disposalpipe/segment{
- dir = 4
- },
-/obj/machinery/light_switch{
- pixel_y = -25
- },
-/obj/effect/turf_decal/tile/neutral,
-/obj/effect/turf_decal/tile/neutral{
- dir = 8
- },
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
- dir = 8
- },
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
- dir = 4
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"aAo" = (
/obj/structure/reagent_dispensers/water_cooler,
/obj/effect/turf_decal/tile/neutral,
@@ -26270,6 +26052,12 @@
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plasteel,
/area/science/storage)
+"bHg" = (
+/obj/effect/turf_decal/pool{
+ dir = 1
+ },
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"bHi" = (
/obj/machinery/atmospherics/pipe/simple/cyan/visible,
/obj/effect/spawner/structure/window/reinforced,
@@ -27145,6 +26933,14 @@
},
/turf/open/floor/plasteel,
/area/engine/atmos_distro)
+"bMp" = (
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4,
+/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2,
+/obj/effect/turf_decal/pool/corner{
+ dir = 8
+ },
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"bMr" = (
/obj/effect/spawner/structure/window,
/turf/open/floor/plating,
@@ -28790,10 +28586,6 @@
/obj/effect/turf_decal/trimline/red/filled/line,
/turf/open/floor/plasteel,
/area/security/checkpoint/medical)
-"bZi" = (
-/obj/machinery/holopad,
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"bZC" = (
/obj/effect/spawner/structure/window/reinforced,
/obj/machinery/door/poddoor/preopen{
@@ -32518,20 +32310,6 @@
/obj/structure/chair/office/dark,
/turf/open/floor/plasteel/dark,
/area/engine/engineering)
-"cUT" = (
-/obj/effect/landmark/event_spawn,
-/obj/effect/turf_decal/tile/neutral,
-/obj/effect/turf_decal/tile/neutral{
- dir = 8
- },
-/obj/effect/turf_decal/tile/green{
- dir = 1
- },
-/obj/effect/turf_decal/tile/green{
- dir = 4
- },
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"cVb" = (
/turf/closed/wall,
/area/hallway/secondary/service)
@@ -33062,6 +32840,25 @@
},
/turf/open/floor/plating/airless,
/area/space/nearstation)
+"drH" = (
+/obj/machinery/airalarm{
+ dir = 4;
+ pixel_x = -24
+ },
+/obj/structure/disposalpipe/segment,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/neutral{
+ dir = 8
+ },
+/obj/structure/cable{
+ icon_state = "1-2"
+ },
+/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4,
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"dsh" = (
/obj/structure/disposalpipe/segment{
dir = 4
@@ -33738,6 +33535,22 @@
/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden/layer4,
/turf/open/floor/plating,
/area/maintenance/fore)
+"dOK" = (
+/obj/structure/disposalpipe/segment,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/neutral{
+ dir = 8
+ },
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4,
+/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2,
+/obj/structure/cable{
+ icon_state = "1-2"
+ },
+/obj/effect/landmark/start/assistant,
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"dOS" = (
/obj/machinery/door/firedoor/border_only{
dir = 1
@@ -34445,6 +34258,13 @@
},
/turf/open/floor/plasteel/dark/telecomms,
/area/ai_monitored/turret_protected/ai)
+"eqQ" = (
+/obj/machinery/holopad,
+/obj/effect/turf_decal/pool{
+ dir = 4
+ },
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"eqW" = (
/obj/item/stack/cable_coil,
/obj/effect/decal/cleanable/dirt/dust,
@@ -34759,6 +34579,18 @@
},
/turf/open/floor/plasteel/white,
/area/science/misc_lab)
+"eAl" = (
+/obj/structure/closet/athletic_mixed,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/neutral{
+ dir = 4
+ },
+/obj/item/twohanded/required/pool/pool_noodle,
+/obj/item/twohanded/required/pool/pool_noodle,
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"eAn" = (
/obj/structure/cable{
icon_state = "1-2"
@@ -35299,15 +35131,6 @@
},
/turf/open/floor/plasteel,
/area/hallway/primary/central)
-"eQU" = (
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
- dir = 1
- },
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
- dir = 1
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"eQZ" = (
/obj/structure/chair/office/dark{
dir = 8
@@ -35333,6 +35156,18 @@
/obj/structure/grille,
/turf/open/space/basic,
/area/space/nearstation)
+"eRX" = (
+/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
+ dir = 1
+ },
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
+ dir = 1
+ },
+/obj/effect/turf_decal/pool{
+ dir = 8
+ },
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"eSR" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
dir = 4
@@ -35365,17 +35200,6 @@
/obj/machinery/light,
/turf/open/floor/plasteel,
/area/construction/mining/aux_base)
-"eTs" = (
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2,
-/obj/structure/window/reinforced{
- dir = 8
- },
-/obj/effect/turf_decal/tile/red{
- dir = 8
- },
-/obj/effect/turf_decal/tile/red,
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"eUi" = (
/obj/effect/turf_decal/stripes/line,
/obj/structure/closet/firecloset/full,
@@ -35883,16 +35707,6 @@
},
/turf/open/floor/plating,
/area/hallway/secondary/entry)
-"fkH" = (
-/obj/structure/window/reinforced{
- dir = 4
- },
-/obj/effect/turf_decal/tile/red,
-/obj/effect/turf_decal/tile/red{
- dir = 8
- },
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"flZ" = (
/obj/machinery/atmospherics/pipe/simple/cyan/visible,
/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden/layer4{
@@ -38158,6 +37972,18 @@
},
/turf/open/floor/wood,
/area/library)
+"gNJ" = (
+/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
+ dir = 6
+ },
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
+ dir = 6
+ },
+/obj/effect/turf_decal/pool/corner{
+ dir = 4
+ },
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"gNS" = (
/obj/structure/disposalpipe/segment{
dir = 4
@@ -38194,19 +38020,6 @@
},
/turf/open/floor/plasteel/dark/telecomms,
/area/tcommsat/server)
-"gOF" = (
-/obj/effect/turf_decal/tile/neutral,
-/obj/effect/turf_decal/tile/neutral{
- dir = 8
- },
-/obj/effect/turf_decal/tile/green{
- dir = 1
- },
-/obj/effect/turf_decal/tile/green{
- dir = 4
- },
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"gPr" = (
/obj/machinery/door/window/northleft{
base_state = "right";
@@ -38396,6 +38209,9 @@
},
/turf/open/floor/plating,
/area/maintenance/solars/port/fore)
+"gVx" = (
+/turf/open/indestructible/sound/pool,
+/area/crew_quarters/fitness)
"gWe" = (
/obj/structure/cable{
icon_state = "4-8"
@@ -40393,6 +40209,13 @@
},
/turf/open/floor/wood,
/area/crew_quarters/heads/captain)
+"iiz" = (
+/obj/machinery/atmospherics/components/unary/vent_pump/on/layer2{
+ dir = 8
+ },
+/obj/effect/turf_decal/pool/corner,
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"iiE" = (
/obj/machinery/light/small,
/obj/structure/closet/crate/hydroponics,
@@ -40467,19 +40290,6 @@
},
/turf/open/floor/plasteel,
/area/engine/engineering)
-"ild" = (
-/obj/effect/turf_decal/tile/neutral{
- dir = 1
- },
-/obj/effect/turf_decal/tile/neutral{
- dir = 4
- },
-/obj/effect/turf_decal/tile/red{
- dir = 8
- },
-/obj/effect/turf_decal/tile/red,
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"ile" = (
/obj/structure/closet/secure_closet/paramedic,
/obj/effect/turf_decal/delivery,
@@ -40963,6 +40773,18 @@
},
/turf/open/floor/plasteel/white,
/area/medical/chemistry)
+"izA" = (
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
+ dir = 1
+ },
+/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
+ dir = 1
+ },
+/obj/effect/turf_decal/pool{
+ dir = 8
+ },
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"izV" = (
/obj/item/radio/intercom{
name = "Station Intercom (General)";
@@ -42899,18 +42721,6 @@
/obj/effect/turf_decal/siding/wood,
/turf/open/floor/wood,
/area/library)
-"jIM" = (
-/obj/structure/window/reinforced{
- dir = 4
- },
-/obj/effect/turf_decal/tile/green{
- dir = 1
- },
-/obj/effect/turf_decal/tile/green{
- dir = 4
- },
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"jJK" = (
/obj/machinery/telecomms/processor/preset_three,
/turf/open/floor/circuit/green/telecomms/mainframe,
@@ -43220,6 +43030,10 @@
},
/turf/open/floor/plasteel/dark,
/area/ai_monitored/security/armory)
+"jUD" = (
+/obj/structure/pool_ladder,
+/turf/open/indestructible/sound/pool/end,
+/area/crew_quarters/fitness)
"jUX" = (
/obj/machinery/power/apc{
areastring = "/area/medical/morgue";
@@ -43797,6 +43611,24 @@
},
/turf/open/floor/plasteel/grimy,
/area/tcommsat/computer)
+"kmt" = (
+/obj/effect/turf_decal/tile/neutral,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 8
+ },
+/obj/machinery/camera{
+ c_tag = "Fitness Room South";
+ dir = 1
+ },
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
+ dir = 9
+ },
+/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
+ dir = 9
+ },
+/obj/effect/landmark/event_spawn,
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"kmy" = (
/obj/effect/turf_decal/tile/yellow{
dir = 1
@@ -44593,6 +44425,13 @@
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4,
/turf/open/floor/plating,
/area/maintenance/solars/starboard/aft)
+"kRj" = (
+/obj/machinery/pool_filter{
+ pixel_x = 14;
+ pixel_y = -22
+ },
+/turf/open/indestructible/sound/pool,
+/area/crew_quarters/fitness)
"kRp" = (
/obj/effect/mapping_helpers/airlock/cyclelink_helper{
dir = 4
@@ -46065,6 +45904,12 @@
},
/turf/open/floor/plasteel/dark,
/area/bridge)
+"lLk" = (
+/obj/effect/turf_decal/pool{
+ dir = 4
+ },
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"lLm" = (
/obj/item/twohanded/required/kirbyplants/random,
/obj/machinery/atmospherics/pipe/simple/purple/visible{
@@ -46797,19 +46642,6 @@
"mof" = (
/turf/closed/wall/r_wall,
/area/quartermaster/sorting)
-"moI" = (
-/obj/structure/window/reinforced{
- dir = 8
- },
-/obj/effect/turf_decal/tile/green{
- dir = 1
- },
-/obj/effect/turf_decal/tile/green{
- dir = 4
- },
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2,
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"mqp" = (
/obj/machinery/space_heater,
/turf/open/floor/plating,
@@ -47021,6 +46853,31 @@
},
/turf/open/floor/plating,
/area/maintenance/fore/secondary)
+"mwN" = (
+/obj/structure/cable{
+ icon_state = "4-8"
+ },
+/obj/structure/disposalpipe/segment{
+ dir = 4
+ },
+/obj/machinery/light_switch{
+ pixel_y = -25
+ },
+/obj/effect/turf_decal/tile/neutral,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 8
+ },
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
+ dir = 8
+ },
+/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
+ dir = 4
+ },
+/obj/structure/chair/stool{
+ pixel_y = 8
+ },
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"mwV" = (
/obj/machinery/atmospherics/pipe/simple/yellow/hidden{
dir = 1
@@ -49362,11 +49219,6 @@
},
/turf/open/floor/plating,
/area/maintenance/port/aft)
-"nMX" = (
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4,
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2,
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"nOA" = (
/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
dir = 8
@@ -49638,23 +49490,6 @@
},
/turf/open/floor/plating,
/area/maintenance/fore)
-"nWA" = (
-/obj/effect/turf_decal/tile/neutral,
-/obj/effect/turf_decal/tile/neutral{
- dir = 8
- },
-/obj/machinery/camera{
- c_tag = "Fitness Room South";
- dir = 1
- },
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
- dir = 9
- },
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
- dir = 9
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"nWK" = (
/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden/layer4{
dir = 4
@@ -51011,6 +50846,10 @@
},
/turf/open/floor/plasteel/dark,
/area/ai_monitored/security/armory)
+"oKp" = (
+/obj/item/twohanded/required/pool/pool_noodle,
+/turf/open/indestructible/sound/pool,
+/area/crew_quarters/fitness)
"oKv" = (
/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden/layer4{
dir = 1
@@ -51988,6 +51827,10 @@
},
/turf/open/floor/plasteel/freezer,
/area/crew_quarters/toilet)
+"pyd" = (
+/obj/effect/turf_decal/pool,
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"pym" = (
/obj/machinery/power/apc{
areastring = "/area/crew_quarters/dorms";
@@ -52377,20 +52220,6 @@
},
/turf/open/floor/plasteel/dark,
/area/ai_monitored/storage/satellite)
-"pGM" = (
-/obj/effect/turf_decal/tile/neutral{
- dir = 1
- },
-/obj/effect/turf_decal/tile/neutral{
- dir = 4
- },
-/obj/effect/landmark/start/assistant,
-/obj/effect/turf_decal/tile/red{
- dir = 8
- },
-/obj/effect/turf_decal/tile/red,
-/turf/open/floor/plasteel/dark,
-/area/crew_quarters/fitness)
"pHz" = (
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{
dir = 1
@@ -53926,6 +53755,10 @@
/obj/item/reagent_containers/food/drinks/bottle/vodka/badminka,
/turf/open/floor/plating,
/area/maintenance/starboard/fore)
+"qAA" = (
+/obj/item/twohanded/required/pool/rubber_ring,
+/turf/open/indestructible/sound/pool,
+/area/crew_quarters/fitness)
"qAF" = (
/obj/machinery/computer/crew,
/turf/open/floor/plasteel/showroomfloor,
@@ -55032,15 +54865,6 @@
/obj/machinery/atmospherics/pipe/simple/scrubbers/visible,
/turf/open/floor/plasteel/dark,
/area/engine/atmos_distro)
-"ris" = (
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
- dir = 1
- },
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
- dir = 1
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"riv" = (
/obj/machinery/atmospherics/pipe/manifold/supply/hidden/layer2{
dir = 1
@@ -56682,15 +56506,6 @@
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plasteel,
/area/maintenance/fore)
-"sjh" = (
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
- dir = 6
- },
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
- dir = 6
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"sjo" = (
/obj/structure/cable{
icon_state = "1-2"
@@ -59253,15 +59068,6 @@
},
/turf/open/floor/plasteel,
/area/construction/mining/aux_base)
-"tTv" = (
-/obj/machinery/atmospherics/pipe/manifold/supply/hidden/layer2{
- dir = 8
- },
-/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden/layer4{
- dir = 8
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/fitness)
"tTI" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
dir = 9
@@ -60797,6 +60603,22 @@
},
/turf/open/floor/plasteel,
/area/security/main)
+"uQV" = (
+/obj/structure/closet/boxinggloves,
+/obj/machinery/light{
+ dir = 1
+ },
+/obj/item/clothing/shoes/jackboots,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/neutral{
+ dir = 4
+ },
+/obj/item/twohanded/required/pool/rubber_ring,
+/obj/item/twohanded/required/pool/rubber_ring,
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"uQX" = (
/obj/machinery/door/airlock/maintenance{
name = "Cargo Bay Warehouse Maintenance";
@@ -61642,6 +61464,27 @@
},
/turf/open/floor/plating,
/area/maintenance/starboard/fore)
+"vtA" = (
+/obj/structure/cable{
+ icon_state = "4-8"
+ },
+/obj/effect/turf_decal/tile/neutral,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 8
+ },
+/obj/structure/disposalpipe/segment{
+ dir = 8
+ },
+/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
+ dir = 4
+ },
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
+ dir = 8
+ },
+/obj/structure/table,
+/obj/item/clothing/mask/balaclava,
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"vtR" = (
/obj/machinery/light/small{
dir = 8
@@ -63091,6 +62934,9 @@
/obj/effect/mapping_helpers/teleport_anchor,
/turf/open/floor/plasteel,
/area/engine/engineering)
+"wpl" = (
+/turf/open/indestructible/sound/pool/end,
+/area/crew_quarters/fitness)
"wpY" = (
/obj/effect/landmark/event_spawn,
/turf/open/floor/engine/vacuum,
@@ -64822,6 +64668,18 @@
},
/turf/open/floor/plasteel/dark,
/area/science/xenobiology)
+"xAx" = (
+/obj/machinery/atmospherics/pipe/manifold/supply/hidden/layer2{
+ dir = 8
+ },
+/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden/layer4{
+ dir = 8
+ },
+/obj/effect/turf_decal/pool{
+ dir = 8
+ },
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"xAP" = (
/obj/structure/rack,
/obj/item/electronics/airlock,
@@ -65671,6 +65529,27 @@
},
/turf/open/floor/plating,
/area/maintenance/port/fore)
+"ycU" = (
+/obj/effect/turf_decal/tile/neutral{
+ dir = 8
+ },
+/obj/structure/disposalpipe/segment{
+ dir = 10
+ },
+/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
+ dir = 10
+ },
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
+ dir = 10
+ },
+/obj/structure/cable{
+ icon_state = "2-8"
+ },
+/obj/effect/turf_decal/pool/corner{
+ dir = 1
+ },
+/turf/open/floor/plasteel,
+/area/crew_quarters/fitness)
"ydd" = (
/obj/structure/closet/crate,
/obj/effect/spawner/lootdrop/maintenance,
@@ -101878,8 +101757,8 @@ aqc
apv
arl
aqT
-apf
-avw
+drH
+dOK
rea
rVP
azj
@@ -102134,12 +102013,12 @@ sIp
kJl
arj
asb
-awb
-ath
-asN
-bZi
-asN
-azl
+iiz
+lLk
+lLk
+eqQ
+lLk
+ycU
aAl
aCr
iSq
@@ -102390,14 +102269,14 @@ qdv
xls
bDx
arj
-arn
-asN
-atn
-eTs
-moI
-aaE
-asN
-aAk
+eAl
+pyd
+jUD
+gVx
+gVx
+qAA
+bHg
+vtA
aCr
cIO
aAN
@@ -102647,14 +102526,14 @@ arj
arj
arj
arj
-asr
-asN
-atP
-pGM
-cUT
-axU
-azn
-aAn
+uQV
+pyd
+wpl
+gVx
+gVx
+kRj
+bHg
+mwN
aCr
aCh
aFd
@@ -102905,12 +102784,12 @@ iwj
uyw
arj
asq
-asN
-aur
-ild
-gOF
-axU
-azm
+pyd
+wpl
+oKp
+gVx
+gVx
+bHg
auU
aCr
aCr
@@ -103162,12 +103041,12 @@ cYm
eeI
arj
ast
-asN
-auv
-fkH
-jIM
-axW
-asN
+pyd
+wpl
+gVx
+gVx
+gVx
+bHg
jXO
iVG
aCt
@@ -103419,13 +103298,13 @@ nGE
eOZ
arj
vBs
-sjh
-ris
-ris
-eQU
-tTv
-nMX
-nWA
+gNJ
+eRX
+eRX
+izA
+xAx
+bMp
+kmt
arj
alP
alP
diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index 1c275dffcbc7..9d7eb469cad7 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -191,6 +191,7 @@
#define COMPONENT_BLOCK_MAGIC 1
#define COMSIG_MOB_HUD_CREATED "mob_hud_created" //from base of mob/create_mob_hud(): ()
#define COMSIG_MOB_ATTACK_HAND "mob_attack_hand" //from base of
+#define COMSIG_MOB_ATTACK_HAND_TURF "mob_attack_hand_turf" //from the base of turf/attack_hand
#define COMSIG_MOB_ITEM_ATTACK "mob_item_attack" //from base of /obj/item/attack(): (mob/M, mob/user)
#define COMSIG_MOB_APPLY_DAMAGE "mob_apply_damage" //from base of /mob/living/proc/apply_damage(): (damage, damagetype, def_zone)
#define COMSIG_MOB_ITEM_AFTERATTACK "mob_item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, proximity_flag, click_parameters)
@@ -240,7 +241,7 @@
#define COMSIG_CARBON_STATUS_STAMCRIT "living_stamcrit" //from base of mob/living/carbon/enter_stamcrit()
#define COMSIG_BODYPART_GAUZED "bodypart_gauzed" // from /obj/item/bodypart/proc/apply_gauze(/obj/item/stack/gauze)
#define COMSIG_BODYPART_GAUZE_DESTROYED "bodypart_degauzed" // from [/obj/item/bodypart/proc/seep_gauze] when it runs out of absorption
-
+#define COMSIG_CARBON_SPECIESCHANGE "mob_carbon_specieschange" //from base of mob/living/carbon/set_species(): (new_race)
// /mob/living/simple_animal/hostile signals
#define COMSIG_HOSTILE_ATTACKINGTARGET "hostile_attackingtarget"
#define COMPONENT_HOSTILE_NO_ATTACK 1
diff --git a/code/__DEFINES/movespeed_modification.dm b/code/__DEFINES/movespeed_modification.dm
index d95b015d3156..d174f57c35bc 100644
--- a/code/__DEFINES/movespeed_modification.dm
+++ b/code/__DEFINES/movespeed_modification.dm
@@ -72,3 +72,5 @@
#define MOVESPEED_ID_CLOCKCHANT "CLOCKCHANT"
#define MOVESPEED_ID_NECRO_VIRUS_SLOWDOWN "NECRO_VIRUS"
+
+#define MOVESPEED_ID_SWIMMING "SWIMMING_SPEED_MOD"
diff --git a/code/_onclick/hud/horror.dm b/code/_onclick/hud/horror.dm
index 876dc00be9ca..a9043b5754a5 100644
--- a/code/_onclick/hud/horror.dm
+++ b/code/_onclick/hud/horror.dm
@@ -1,18 +1,18 @@
-/obj/screen/horror_chemicals
- name = "chemicals"
- icon_state = "horror_counter"
- screen_loc = ui_lingchemdisplay
-
-/datum/hud/chemical_counter
- ui_style = 'icons/mob/screen_midnight.dmi'
- var/obj/screen/horror_chemicals/chemical_counter
-
-/datum/hud/chemical_counter/New(mob/owner)
- . = ..()
- chemical_counter = new /obj/screen/horror_chemicals
- infodisplay += chemical_counter
-
-/datum/hud/chemical_counter/Destroy()
- . = ..()
- QDEL_NULL(chemical_counter)
-
+/obj/screen/horror_chemicals
+ name = "chemicals"
+ icon_state = "horror_counter"
+ screen_loc = ui_lingchemdisplay
+
+/datum/hud/chemical_counter
+ ui_style = 'icons/mob/screen_midnight.dmi'
+ var/obj/screen/horror_chemicals/chemical_counter
+
+/datum/hud/chemical_counter/New(mob/owner)
+ . = ..()
+ chemical_counter = new /obj/screen/horror_chemicals
+ infodisplay += chemical_counter
+
+/datum/hud/chemical_counter/Destroy()
+ . = ..()
+ QDEL_NULL(chemical_counter)
+
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index cabbf46d8054..8aadd86a4e91 100755
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -120,6 +120,8 @@ GLOBAL_LIST_EMPTY(station_turfs)
/turf/attack_hand(mob/user)
. = ..()
+ if(SEND_SIGNAL(user, COMSIG_MOB_ATTACK_HAND_TURF, src) & COMPONENT_NO_ATTACK_HAND)
+ . = TRUE
if(.)
return
user.Move_Pulled(src)
diff --git a/code/modules/antagonists/horror/horror.dm b/code/modules/antagonists/horror/horror.dm
index e0653bcbb8e6..5da28bbddbaf 100644
--- a/code/modules/antagonists/horror/horror.dm
+++ b/code/modules/antagonists/horror/horror.dm
@@ -1,848 +1,848 @@
-/mob/living/simple_animal/horror
- name = "eldritch horror"
- desc = "Your eyes can barely comprehend what they're looking at."
- icon_state = "horror"
- icon_living = "horror"
- icon_dead = "horror_dead"
- icon_gib = "horror_gib"
- health = 50
- maxHealth = 50
- melee_damage_lower = 10
- melee_damage_upper = 10
- see_in_dark = 5
- stop_automated_movement = TRUE
- attacktext = "bites"
- speak_emote = list("gurgles")
- attack_sound = 'sound/weapons/bite.ogg'
- pass_flags = PASSTABLE | PASSMOB
- mob_size = MOB_SIZE_SMALL
- faction = list("neutral","silicon","creature","heretics","abomination")
- ventcrawler = VENTCRAWLER_ALWAYS
- initial_language_holder = /datum/language_holder/universal
- hud_type = /datum/hud/chemical_counter
-
- atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
- minbodytemp = 0
- maxbodytemp = 1500
- unsuitable_atmos_damage = 0.5
-
- var/playstyle_string = span_bold(span_big("You are an eldritch horror,") + " an evermutating parasitic abomination. Seek human souls to consume. \
- Crawl into people's heads and steal their essence. Use it to mutate yourself, giving you access to more power and abilities. \
- You operate on chemicals that get built up while you spend time in someone's head. You are weak when outside, play carefully. \
- You can attack airlocks to squeeze yourself through them. " + span_danger("Alt+Click on people to infest them."))
-
- var/mob/living/carbon/victim
- var/datum/mind/target
- var/mob/living/captive_brain/host_brain
- var/available_points = 4
- var/consumed_souls = 0
-
- //An associative list (associated by ability typepaths) containing the abilities the horror has
- var/list/horrorabilities = list()
- //same (associated by their ID), but for permanent upgrades
- var/list/horrorupgrades = list()
- //list storing what items we have to un-glue when stopping mind control
- var/list/clothing = list()
-
- var/bonding = FALSE
- var/controlling = FALSE
- var/chemicals = 10
- var/chem_regen_rate = 2
- var/used_freeze
- var/used_target
- var/horror_chems = list(/datum/horror_chem/epinephrine,/datum/horror_chem/mannitol,/datum/horror_chem/bicaridine,/datum/horror_chem/kelotane,/datum/horror_chem/charcoal)
-
- var/leaving = FALSE
- var/hiding = FALSE
- var/invisible = FALSE
- var/datum/action/innate/horror/talk_to_horror/talk_to_horror_action = new
-
-/mob/living/simple_animal/horror/Initialize(mapload, gen=1)
- ..()
- real_name = "[pick(GLOB.horror_names)]"
-
- //default abilities
- add_ability(/datum/action/innate/horror/mutate)
- add_ability(/datum/action/innate/horror/seek_soul)
- add_ability(/datum/action/innate/horror/consume_soul)
- add_ability(/datum/action/innate/horror/talk_to_host)
- add_ability(/datum/action/innate/horror/freeze_victim)
- add_ability(/datum/action/innate/horror/toggle_hide)
- add_ability(/datum/action/innate/horror/talk_to_brain)
- add_ability(/datum/action/innate/horror/take_control)
- add_ability(/datum/action/innate/horror/leave_body)
- add_ability(/datum/action/innate/horror/make_chems)
- add_ability(/datum/action/innate/horror/give_back_control)
- RefreshAbilities()
-
- var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
- hud.add_hud_to(src)
- update_horror_hud()
-
-
-/mob/living/simple_animal/horror/Destroy()
- host_brain = null
- victim = null
- return ..()
-
-/mob/living/simple_animal/horror/AltClickOn(atom/A)
- if(iscarbon(A))
- var/mob/living/carbon/C = A
- if(!C || QDELETED(src) || !Adjacent(C) || victim || !can_use_ability())
- return
- if(victim)
- to_chat(src, span_warning("You are already within a host."))
- return
-
- to_chat(src, span_warning("You slither your tentacles up [C] and begin probing at their ear canal..."))
-
- if(!do_mob(src, C, 3 SECONDS))
- to_chat(src, span_warning("As [C] moves away, you are dislodged and fall to the ground."))
- return
-
- if(!C || QDELETED(src))
- return
- if(C.has_horror_inside())
- to_chat(src, span_warning("[C] is already infested!"))
- return
- Infect(C)
- return
- ..()
-
-/mob/living/simple_animal/horror/proc/has_chemicals(amt)
- return chemicals >= amt
-
-/mob/living/simple_animal/horror/proc/use_chemicals(amt)
- if(!has_chemicals(amt))
- return FALSE
- chemicals -= amt
- update_horror_hud()
- return TRUE
-
-/mob/living/simple_animal/horror/proc/regenerate_chemicals(amt)
- chemicals += amt
- chemicals = min(250, chemicals)
- update_horror_hud()
-
-/mob/living/simple_animal/horror/proc/update_horror_hud()
- if(!src || !hud_used)
- return
- var/datum/hud/chemical_counter/H = hud_used
- var/obj/screen/counter = H.chemical_counter
- counter.maptext = "
[chemicals]
"
-
-/mob/living/simple_animal/horror/proc/can_use_ability()
- if(stat != CONSCIOUS)
- to_chat(src, "You cannot do that in your current state.")
- return FALSE
- return TRUE
-
-/mob/living/simple_animal/horror/proc/SearchTarget()
- if(target)
- if(world.time - used_target < 3 MINUTES)
- to_chat(src, span_warning("You cannot use that ability again so soon."))
- return
- if(alert("You already have a target ([target.name]). Would you like to change that target?","Swap targets?","Yes","No") != "Yes")
- return
-
- var/list/possible_targets = list()
- for(var/datum/mind/M in SSticker.minds)
- if(M.current && M.current.stat != DEAD)
- if(ishuman(M.current))
- if(M.hasSoul && (mind.enslaved_to != M.current))
- possible_targets[M] = M
-
- var/list/selected_targets = list()
- var/list/icons = list()
- while(selected_targets.len != 4)
- if(possible_targets.len <= 0)
- break
- var/datum/mind/M = pick(possible_targets)
- selected_targets[M] = M
- possible_targets -= M
-
- var/mob/living/carbon/human/H = M.current
- icons[M] = H
-
- used_target = world.time
-
- var/entry_name = show_radial_menu(src, (victim ? src.loc : src), icons, tooltips = TRUE)
- target = selected_targets[entry_name]
-
- //you didn't select your target? let me do that for you, my friend
- if(selected_targets.len > 0 && !target)
- target = pick(selected_targets)
-
- if(target)
- to_chat(src, span_warning("You caught their scent. Go and consume [target.current.real_name], the [target.assigned_role]'s soul!"))
- apply_status_effect(/datum/status_effect/agent_pinpointer/horror)
- for(var/datum/status_effect/agent_pinpointer/horror/status in status_effects)
- status.scan_target = target.current
- else
- //refund cooldown
- used_target = 0
- to_chat(src, span_warning("Failed to select a target!"))
-
-/mob/living/simple_animal/horror/proc/ConsumeSoul()
- if(!can_use_ability())
- return
-
- if(!victim.mind.hasSoul)
- to_chat(src, "This host doesn't have a soul!")
- return
-
- if(victim == mind.enslaved_to)
- to_chat(src, span_userdanger("No, not yet... We still need them..."))
- return
-
- if(victim.mind != target)
- to_chat(src, "This soul isn't your target, you can't consume it!")
- return
-
- to_chat(src, "You begin consuming [victim.name]'s soul!")
- if(do_after(src, 20 SECONDS, target = victim, stayStill = FALSE))
- consume()
-
-/mob/living/simple_animal/horror/proc/consume()
- if(!can_use_ability() || !victim || !victim.mind.hasSoul || victim.mind != target)
- return
- consumed_souls++
- available_points++
- to_chat(src, span_userdanger("You succeed in consuming [victim.name]'s soul!"))
- to_chat(victim, span_userdanger("You suddenly feel weak and hollow inside..."))
- victim.health -= 20
- victim.maxHealth -= 20
- victim.mind.hasSoul = FALSE
- target = null
- remove_status_effect(/datum/status_effect/agent_pinpointer/horror)
- playsound(src, 'sound/effects/curseattack.ogg', 150)
- playsound(src, 'sound/effects/ghost.ogg', 50)
-
-/mob/living/simple_animal/horror/proc/Communicate()
- if(!can_use_ability())
- return
- if(!victim)
- to_chat(src, "You do not have a host to communicate with!")
- return
-
- var/input = stripped_input(src, "Please enter a message to tell your host.", "Horror", null)
- if(!input)
- return
-
- if(src && !QDELETED(src) && !QDELETED(victim))
- if(victim)
- to_chat(victim, span_changeling("[real_name] slurs: [input]"))
- for(var/M in GLOB.dead_mob_list)
- if(isobserver(M))
- var/rendered = span_changeling("Horror Communication from [real_name] : [input]")
- var/link = FOLLOW_LINK(M, src)
- to_chat(M, "[link] [rendered]")
- to_chat(src, span_changeling("[real_name] slurs: [input]"))
- add_verb(victim, /mob/living/proc/horror_comm)
- talk_to_horror_action.Grant(victim)
-
-/mob/living/proc/horror_comm()
- set name = "Converse with Horror"
- set category = "Horror"
- set desc = "Communicate mentally with the thing in your head."
-
- var/mob/living/simple_animal/horror/B = has_horror_inside()
- if(B)
- var/input = stripped_input(src, "Please enter a message to tell the horror.", "Message", "")
- if(!input)
- return
-
- to_chat(B, span_changeling("[real_name] says: [input]"))
-
- for(var/M in GLOB.dead_mob_list)
- if(isobserver(M))
- var/rendered = span_changeling("Horror Communication from [real_name] : [input]")
- var/link = FOLLOW_LINK(M, src)
- to_chat(M, "[link] [rendered]")
- to_chat(src, span_changeling("[real_name] says: [input]"))
-
-/mob/living/proc/trapped_mind_comm()
- var/mob/living/simple_animal/horror/B = has_horror_inside()
- if(!B || !B.host_brain)
- return
- var/mob/living/captive_brain/CB = B.host_brain
- var/input = stripped_input(src, "Please enter a message to tell the trapped mind.", "Message", null)
- if(!input)
- return
-
- to_chat(CB, span_changeling("[B.real_name] says: [input]"))
-
- for(var/M in GLOB.dead_mob_list)
- if(isobserver(M))
- var/rendered = span_changeling("Horror Communication from [B.real_name] : [input]")
- var/link = FOLLOW_LINK(M, src)
- to_chat(M, "[link] [rendered]")
- to_chat(src, span_changeling("[B.real_name] says: [input]"))
-
-/mob/living/simple_animal/horror/Life()
- ..()
- if(has_upgrade("regen"))
- heal_overall_damage(5)
-
- if(invisible) //don't regenerate chemicals when invisible
- if(use_chemicals(5))
- alpha = max(alpha - 100, 1)
- else
- to_chat(src, span_warning("You ran out of chemicals to support your invisibility."))
- invisible = FALSE
- Update_Invisibility_Button()
- else
- if(has_upgrade("nohost_regen"))
- regenerate_chemicals(chem_regen_rate)
- else if(victim)
- if(victim.stat == DEAD)
- regenerate_chemicals(1)
- else
- regenerate_chemicals(chem_regen_rate)
- alpha = min(255, alpha + 50)
-
- if(victim)
- if(stat != DEAD && victim.stat != DEAD)
- heal_overall_damage(1)
-
-/mob/living/simple_animal/horror/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
- if(victim)
- to_chat(src, span_warning("You cannot speak out loud while inside a host!"))
- return
- return ..()
-
-/mob/living/simple_animal/horror/emote(act, m_type = null, message = null, intentional = FALSE)
- if(victim)
- to_chat(src, span_warning("You cannot emote while inside a host!"))
- return
- return ..()
-
-/mob/living/simple_animal/horror/UnarmedAttack(atom/A)
- if(istype(A, /obj/machinery/door/airlock))
- var/obj/machinery/door/airlock/door = A
- if(door.welded)
- to_chat(src, span_danger("The door is welded shut!"))
- return
- visible_message(span_warning("[src] slips their tentacles into the airlock and starts prying it open!"), span_warning("You start moving onto the airlock."))
- playsound(A, 'sound/misc/splort.ogg', 50, 1)
- if(do_after(src, 5 SECONDS, target = A))
- if(door.welded)
- to_chat(src, span_danger("The door is welded shut!"))
- return
- visible_message(span_warning("[src] forces themselves through the airlock!"), span_warning("You force yourself through the airlock"))
- forceMove(get_turf(A))
- playsound(A, 'sound/machines/airlock_alien_prying.ogg', 50, 1)
- return
-
- if(isliving(A))
- if(victim || A == src.mind.enslaved_to)
- healthscan(usr, A)
- chemscan(usr, A)
- else
- alpha = 255
- if(hiding)
- var/datum/action/innate/horror/H = has_ability(/datum/action/innate/horror/toggle_hide)
- H.Activate()
- if(invisible)
- var/datum/action/innate/horror/H = has_ability(/datum/action/innate/horror/chameleon)
- H.Activate()
- Update_Invisibility_Button()
- ..()
-
-/mob/living/simple_animal/horror/ex_act()
- if(victim)
- return
-
- ..()
-
-/mob/living/simple_animal/horror/proc/Infect(mob/living/carbon/C)
- if(!C)
- return
- var/obj/item/bodypart/head/head = C.get_bodypart(BODY_ZONE_HEAD)
- if(!head)
- to_chat(src, span_warning("[C] doesn't have a head!"))
- return
- var/hasbrain = locate(/obj/item/organ/brain) in C.internal_organs
-
- if(!hasbrain)
- to_chat(src, span_warning("[C] doesn't have a brain!"))
- return
-
- if(C.has_horror_inside())
- to_chat(src, span_warning("[C] is already infested!"))
- return
-
- //can only infect non-ssd alive people / corpses with ghosts attached / current target
- if((C.stat == DEAD || !C.key) && (C.stat != DEAD || !C.get_ghost()) && (!target || C != target.current))
- to_chat(src, span_warning("[C]'s mind seems unresponsive. Try someone else!"))
- return
-
- if(hiding)
- var/datum/action/innate/horror/H = has_ability(/datum/action/innate/horror/toggle_hide)
- H.Activate()
- invisible = FALSE
- Update_Invisibility_Button()
-
- victim = C
- forceMove(victim)
- RefreshAbilities()
- log_game("[src]/([src.ckey]) has infested [victim]/([victim.ckey]")
-
-/mob/living/simple_animal/horror/proc/secrete_chemicals()
- if(!can_use_ability())
- return
- if(!victim)
- to_chat(src, span_warning("You are not inside a host body."))
- return
-
- var/content = "Chemicals: [chemicals]
"
- content += ""
-
- var/html = get_html_template(content)
-
- usr << browse(html, "window=ViewHorror\ref[src]Chems;size=600x800")
-
-/mob/living/simple_animal/horror/proc/hide()
- if(victim)
- to_chat(src, span_warning("You cannot do this while you're inside a host."))
- return
-
- if(stat != CONSCIOUS)
- return
-
- if(!hiding)
- layer = LATTICE_LAYER
- visible_message(span_name("[src] scurries to the ground!"), \
- span_noticealien("You are now hiding."))
- hiding = TRUE
- else
- layer = MOB_LAYER
- visible_message("[src] slowly peaks up from the ground...", \
- span_noticealien("You stop hiding."))
- hiding = FALSE
-
-/mob/living/simple_animal/horror/proc/go_invisible()
- if(victim)
- to_chat(src, span_warning("You cannot do this while you're inside a host."))
- return
-
- if(!can_use_ability())
- return
-
- if(!has_chemicals(10))
- to_chat(src, span_warning("You don't have enough chemicals to do that."))
- return
-
- if(!invisible)
- to_chat(src, span_noticealien("You focus your chameleon skin to blend into the environment."))
- invisible = TRUE
- else
- to_chat(src, span_noticealien("You stop your camouflage."))
- invisible = FALSE
-
-/mob/living/simple_animal/horror/proc/freeze_victim()
- if(world.time - used_freeze < 150)
- to_chat(src, span_warning("You cannot use that ability again so soon."))
- return
-
- if(victim)
- to_chat(src, span_warning("You cannot do that from within a host body."))
- return
-
- if(!can_use_ability())
- return
-
- var/list/choices = list()
- for(var/mob/living/carbon/C in view(1,src))
- if(C.stat == CONSCIOUS)
- choices += C
-
- if(!choices.len)
- return
-
- if(QDELETED(src) || stat != CONSCIOUS || victim || (world.time - used_freeze < 150))
- return
-
- layer = MOB_LAYER
- for (var/mob/living/carbon/M in range(1, src))
- if(!M || !Adjacent(M))
- return
-
- if(has_upgrade("paralysis"))
- playsound(loc, "sound/effects/sparks4.ogg", 30, 1, -1)
- M.Stun(50)
- M.SetSleeping(70) //knocked out cold
- M.electrocute_act(15, src, 1, FALSE, FALSE, FALSE, 1, FALSE)
- else
- to_chat(M, span_userdanger("You feel something wrapping around your leg, pulling you down!"))
- playsound(loc, "sound/weapons/whipgrab.ogg", 30, 1, -1)
- M.Immobilize(50)
- M.Knockdown(70)
- used_freeze = world.time
-
-/mob/living/simple_animal/horror/proc/is_leaving()
- return leaving
-
-/mob/living/simple_animal/horror/proc/release_victim()
- if(!victim)
- to_chat(src, span_danger("You are not inside a host body."))
- return
-
- if(!can_use_ability())
- return
-
- if(leaving)
- leaving = FALSE
- to_chat(src, span_danger("You decide against leaving your host."))
- return
-
- to_chat(src, span_danger("You begin disconnecting from [victim]'s synapses and prodding at their internal ear canal."))
-
- if(victim.stat != DEAD && !has_upgrade("invisible_exit"))
- to_chat(victim, span_userdanger("An odd, uncomfortable pressure begins to build inside your skull, behind your ear..."))
-
- leaving = TRUE
- if(do_after(src, 100, target = victim, extra_checks = CALLBACK(src, .proc/is_leaving), stayStill = FALSE))
- release_host()
-
-/mob/living/simple_animal/horror/proc/release_host()
- if(!victim || QDELETED(victim) || QDELETED(src) || controlling)
- return
-
- if(!can_use_ability())
- return
-
- if(has_upgrade("invisible_exit"))
- alpha = 60
- if(has_ability(/datum/action/innate/horror/chameleon))
- invisible = TRUE
- Update_Invisibility_Button()
- to_chat(src, span_danger("You silently wiggle out of [victim]'s ear and plop to the ground before vanishing via reflective solution that covers you."))
- else
- to_chat(src, span_danger("You wiggle out of [victim]'s ear and plop to the ground."))
- if(victim.mind)
- if(!has_upgrade("invisible_exit"))
- to_chat(victim, span_danger("Something slimy wiggles out of your ear and plops to the ground!"))
-
- leaving = FALSE
-
- leave_victim()
-
-/mob/living/simple_animal/horror/proc/leave_victim()
- if(!victim)
- return
-
- if(controlling)
- detatch()
-
- forceMove(get_turf(victim))
-
- reset_perspective()
- unset_machine()
-
- victim.reset_perspective()
- victim.unset_machine()
-
- var/mob/living/V = victim
- remove_verb(V, /mob/living/proc/horror_comm)
- talk_to_horror_action.Remove(victim)
-
- for(var/obj/item/horrortentacle/T in victim)
- victim.visible_message(span_warning("[victim]'s tentacle transforms back!"), span_notice("Your tentacle disappears!"))
- playsound(victim, 'sound/effects/blobattack.ogg', 30, 1)
- qdel(T)
- victim = null
-
- RefreshAbilities()
-
-
-/mob/living/simple_animal/horror/proc/jumpstart()
- if(!victim)
- to_chat(src, span_warning("You need a host to be able to use this."))
- return
-
- if(!can_use_ability())
- return
-
- if(victim.stat != DEAD)
- to_chat(src, span_warning("Your host is already alive!"))
- return
-
- if(!has_chemicals(250))
- to_chat(src, span_warning("You need 250 chemicals to use this!"))
- return
-
- if(HAS_TRAIT_FROM(target, TRAIT_BADDNA, CHANGELING_DRAIN))
- to_chat(src, span_warning("Their DNA is completely destroyed! You can't revive them"))
- return
-
- if(victim.stat == DEAD)
- playsound(src, 'sound/machines/defib_charge.ogg', 50, 1, -1)
- sleep(1 SECONDS)
- victim.tod = null
- victim.setToxLoss(0)
- victim.setOxyLoss(0)
- victim.setCloneLoss(0)
- victim.SetUnconscious(0)
- victim.SetStun(0)
- victim.SetKnockdown(0)
- victim.radiation = 0
- victim.heal_overall_damage(victim.getBruteLoss(), victim.getFireLoss())
- victim.reagents.clear_reagents()
- if(HAS_TRAIT_FROM(victim, TRAIT_HUSK, BURN))
- victim.cure_husk(BURN)
- for(var/organ in victim.internal_organs)
- var/obj/item/organ/O = organ
- O.setOrganDamage(0)
- victim.restore_blood()
- victim.remove_all_embedded_objects()
- victim.revive()
- log_game("[src]/([src.ckey]) has revived [victim]/([victim.ckey]")
- chemicals -= 250
- to_chat(src, span_notice("You send a jolt of energy to your host, reviving them!"))
- victim.grab_ghost(force = TRUE) //brings the host back, no eggscape
- victim.adjustOxyLoss(30)
- to_chat(victim, span_userdanger("You bolt upright, gasping for breath!"))
- victim.electrocute_act(15, src, 1, FALSE, FALSE, FALSE, 1, FALSE)
- playsound(src, 'sound/machines/defib_zap.ogg', 50, 1, -1)
-
-
-/mob/living/simple_animal/horror/proc/view_memory()
- if(!victim)
- to_chat(src, span_warning("You need a host to be able to use this."))
- return
-
- if(!can_use_ability())
- return
-
- if(victim.stat == DEAD)
- to_chat(src, span_warning("Your host brain is unresponsive. They are dead!"))
- return
-
- if(prob(20))
- to_chat(victim, span_danger("You suddenly feel your memory being tangled with..."))//chance to alert the victim
-
- if(victim.mind)
- var/datum/mind/suckedbrain = victim.mind
- to_chat(src, span_boldnotice("You skim through [victim]'s memories...[suckedbrain.memory]"))
- for(var/A in suckedbrain.antag_datums)
- var/datum/antagonist/antag_types = A
- var/list/all_objectives = antag_types.objectives.Copy()
- if(antag_types.antag_memory)
- to_chat(src, span_notice("[antag_types.antag_memory]"))
- if(LAZYLEN(all_objectives))
- to_chat(src, span_boldnotice("Objectives:"))
- var/obj_count = 1
- for(var/O in all_objectives)
- var/datum/objective/objective = O
- to_chat(src, span_notice("Objective #[obj_count++]: [objective.explanation_text]"))
- var/list/datum/mind/other_owners = objective.get_owners() - suckedbrain
- if(other_owners.len)
- for(var/mind in other_owners)
- var/datum/mind/M = mind
- to_chat(src, span_notice("Conspirator: [M.name]"))
-
- var/list/recent_speech = list()
- var/list/say_log = list()
- var/log_source = victim.logging
- for(var/log_type in log_source)
- var/nlog_type = text2num(log_type)
- if(nlog_type & LOG_SAY)
- var/list/reversed = log_source[log_type]
- if(islist(reversed))
- say_log = reverseRange(reversed.Copy())
- break
- if(LAZYLEN(say_log))
- for(var/spoken_memory in say_log)
- if(recent_speech.len >= 5)//up to 5 random lines of speech, favoring more recent speech
- break
- if(prob(50))
- recent_speech[spoken_memory] = say_log[spoken_memory]
- if(recent_speech.len)
- to_chat(src, span_boldnotice("You catch some drifting memories of their past conversations..."))
- for(var/spoken_memory in recent_speech)
- to_chat(src, span_notice("[recent_speech[spoken_memory]]"))
- var/mob/living/carbon/human/H = victim
- var/datum/dna/the_dna = H.has_dna()
- if(the_dna)
- to_chat(src, span_boldnotice("You uncover that [H.p_their()] true identity is [the_dna.real_name]."))
-
-/mob/living/simple_animal/horror/proc/is_bonding()
- return bonding
-
-/mob/living/simple_animal/horror/proc/bond_brain()
- if(!victim)
- to_chat(src, span_warning("You are not inside a host body."))
- return
-
- if(!can_use_ability())
- return
-
- if(victim.stat == DEAD)
- to_chat(src, span_notice("This host lacks enough brain function to control."))
- return
-
- if(victim.has_trauma_type(/datum/brain_trauma/severe/split_personality))
- to_chat(src, span_notice("This host's brain lobe separation makes it too complex for you to control."))
- return
-
- if(bonding)
- bonding = FALSE
- to_chat(src, span_danger("You stop attempting to take control of your host."))
- return
-
- to_chat(src, span_danger("You begin delicately adjusting your connection to the host brain..."))
-
- if(QDELETED(src) || QDELETED(victim))
- return
-
- bonding = TRUE
-
- var/delay = 20 SECONDS
- if(has_upgrade("fast_control"))
- delay -= 12 SECONDS
- if(do_after(src, delay, target = victim, extra_checks = CALLBACK(src, .proc/is_bonding), stayStill = FALSE))
- assume_control()
-
-/mob/living/simple_animal/horror/proc/assume_control()
- if(!victim || !src || controlling || victim.stat == DEAD)
- return
- if(is_servant_of_ratvar(victim) || iscultist(victim))
- to_chat(src, span_warning("[victim]'s mind seems to be blocked by some unknown force!"))
- bonding = FALSE
- return
- if(HAS_TRAIT(victim, TRAIT_MINDSHIELD))
- to_chat(src, span_warning("[victim]'s mind seems to be shielded from your influence!"))
- bonding = FALSE
- return
- else
- RegisterSignal(victim, COMSIG_MOB_APPLY_DAMAGE, .proc/hit_detatch)
- log_game("[src]/([src.ckey]) assumed control of [victim]/([victim.ckey] with eldritch powers.")
- to_chat(src, span_warning("You plunge your probosci deep into the cortex of the host brain, interfacing directly with their nervous system."))
- to_chat(victim, span_userdanger("You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours."))
-
- clothing = victim.get_equipped_items()
- for(var/obj/item/I in clothing)
- ADD_TRAIT(I, TRAIT_NODROP, HORROR_TRAIT)
-
- qdel(host_brain)
- host_brain = new(src)
- host_brain.H = src
- host_brain.name = "Trapped mind of [victim.real_name]"
- victim.mind.transfer_to(host_brain)
- if(victim.key)
- host_brain.key = victim.key
-
- to_chat(host_brain, "You are trapped in your own mind. You feel that there must be a way to resist!")
-
- mind.transfer_to(victim)
-
- bonding = FALSE
- controlling = TRUE
-
- remove_verb(victim, /mob/living/proc/horror_comm)
- talk_to_horror_action.Remove(victim)
- GrantControlActions()
-
- victim.med_hud_set_status()
- if(target)
- victim.apply_status_effect(/datum/status_effect/agent_pinpointer/horror)
- for(var/datum/status_effect/agent_pinpointer/horror/status in victim.status_effects)
- status.scan_target = target.current
-
-/mob/living/carbon/proc/release_control()
- var/mob/living/simple_animal/horror/B = has_horror_inside()
- if(B && B.host_brain)
- to_chat(src, span_danger("You withdraw your probosci, releasing control of [B.host_brain]"))
- B.detatch()
-
-//Check for brain worms in head.
-/mob/proc/has_horror_inside()
- for(var/I in contents)
- if(ishorror(I))
- return I
-
-
-/mob/living/simple_animal/horror/proc/hit_detatch()
- if(victim.health <= 75)
- detatch()
- to_chat(src, span_warning("It appears that [victim]s brain detected danger, and hastily took over."))
- to_chat(victim, span_danger("Your body is under attack, you unconciously forced your brain to immediately take over!"))
-
-/mob/living/simple_animal/horror/proc/detatch()
- if(!victim || !controlling)
- return
-
- controlling = FALSE
- UnregisterSignal(victim, COMSIG_MOB_APPLY_DAMAGE)
- add_verb(victim, /mob/living/proc/horror_comm)
- RemoveControlActions()
- RefreshAbilities()
- talk_to_horror_action.Grant(victim)
-
- for(var/obj/item/I in clothing)
- REMOVE_TRAIT(I, TRAIT_NODROP, HORROR_TRAIT)
- clothing = list()
-
- victim.med_hud_set_status()
- victim.remove_status_effect(/datum/status_effect/agent_pinpointer/horror)
-
- victim.mind.transfer_to(src)
- if(host_brain)
- host_brain.mind.transfer_to(victim)
- if(host_brain.key)
- victim.key = host_brain.key
-
- log_game("[src]/([src.ckey]) released control of [victim]/([victim.ckey]")
- qdel(host_brain)
-
-/mob/living/simple_animal/horror/proc/Update_Invisibility_Button()
- var/datum/action/innate/horror/action = has_ability(/datum/action/innate/horror/chameleon)
- if(action)
- action.button_icon_state = "horror_sneak_[invisible ? "true" : "false"]"
- action.UpdateButtonIcon()
-
-/mob/living/simple_animal/horror/proc/GrantHorrorActions()
- for(var/datum/action/innate/horror/ability in horrorabilities)
- if("horror" in ability.category)
- ability.Grant(src)
-
-/mob/living/simple_animal/horror/proc/RemoveHorrorActions()
- for(var/datum/action/innate/horror/ability in horrorabilities)
- if("horror" in ability.category)
- ability.Remove(src)
-
-/mob/living/simple_animal/horror/proc/GrantInfestActions()
- for(var/datum/action/innate/horror/ability in horrorabilities)
- if("infest" in ability.category)
- ability.Grant(src)
-
-/mob/living/simple_animal/horror/proc/RemoveInfestActions()
- for(var/datum/action/innate/horror/ability in horrorabilities)
- if("infest" in ability.category)
- ability.Remove(src)
-
-/mob/living/simple_animal/horror/proc/GrantControlActions()
- for(var/datum/action/innate/horror/ability in horrorabilities)
- if("control" in ability.category)
- ability.Grant(victim)
-
-/mob/living/simple_animal/horror/proc/RemoveControlActions()
- for(var/datum/action/innate/horror/ability in horrorabilities)
- if("control" in ability.category)
- ability.Remove(victim)
-
-/mob/living/simple_animal/horror/proc/RefreshAbilities() //control abilities technically don't belong to horror
- if(victim)
- RemoveHorrorActions()
- GrantInfestActions()
- else
- RemoveInfestActions()
+/mob/living/simple_animal/horror
+ name = "eldritch horror"
+ desc = "Your eyes can barely comprehend what they're looking at."
+ icon_state = "horror"
+ icon_living = "horror"
+ icon_dead = "horror_dead"
+ icon_gib = "horror_gib"
+ health = 50
+ maxHealth = 50
+ melee_damage_lower = 10
+ melee_damage_upper = 10
+ see_in_dark = 5
+ stop_automated_movement = TRUE
+ attacktext = "bites"
+ speak_emote = list("gurgles")
+ attack_sound = 'sound/weapons/bite.ogg'
+ pass_flags = PASSTABLE | PASSMOB
+ mob_size = MOB_SIZE_SMALL
+ faction = list("neutral","silicon","creature","heretics","abomination")
+ ventcrawler = VENTCRAWLER_ALWAYS
+ initial_language_holder = /datum/language_holder/universal
+ hud_type = /datum/hud/chemical_counter
+
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ minbodytemp = 0
+ maxbodytemp = 1500
+ unsuitable_atmos_damage = 0.5
+
+ var/playstyle_string = span_bold(span_big("You are an eldritch horror,") + " an evermutating parasitic abomination. Seek human souls to consume. \
+ Crawl into people's heads and steal their essence. Use it to mutate yourself, giving you access to more power and abilities. \
+ You operate on chemicals that get built up while you spend time in someone's head. You are weak when outside, play carefully. \
+ You can attack airlocks to squeeze yourself through them. " + span_danger("Alt+Click on people to infest them."))
+
+ var/mob/living/carbon/victim
+ var/datum/mind/target
+ var/mob/living/captive_brain/host_brain
+ var/available_points = 4
+ var/consumed_souls = 0
+
+ //An associative list (associated by ability typepaths) containing the abilities the horror has
+ var/list/horrorabilities = list()
+ //same (associated by their ID), but for permanent upgrades
+ var/list/horrorupgrades = list()
+ //list storing what items we have to un-glue when stopping mind control
+ var/list/clothing = list()
+
+ var/bonding = FALSE
+ var/controlling = FALSE
+ var/chemicals = 10
+ var/chem_regen_rate = 2
+ var/used_freeze
+ var/used_target
+ var/horror_chems = list(/datum/horror_chem/epinephrine,/datum/horror_chem/mannitol,/datum/horror_chem/bicaridine,/datum/horror_chem/kelotane,/datum/horror_chem/charcoal)
+
+ var/leaving = FALSE
+ var/hiding = FALSE
+ var/invisible = FALSE
+ var/datum/action/innate/horror/talk_to_horror/talk_to_horror_action = new
+
+/mob/living/simple_animal/horror/Initialize(mapload, gen=1)
+ ..()
+ real_name = "[pick(GLOB.horror_names)]"
+
+ //default abilities
+ add_ability(/datum/action/innate/horror/mutate)
+ add_ability(/datum/action/innate/horror/seek_soul)
+ add_ability(/datum/action/innate/horror/consume_soul)
+ add_ability(/datum/action/innate/horror/talk_to_host)
+ add_ability(/datum/action/innate/horror/freeze_victim)
+ add_ability(/datum/action/innate/horror/toggle_hide)
+ add_ability(/datum/action/innate/horror/talk_to_brain)
+ add_ability(/datum/action/innate/horror/take_control)
+ add_ability(/datum/action/innate/horror/leave_body)
+ add_ability(/datum/action/innate/horror/make_chems)
+ add_ability(/datum/action/innate/horror/give_back_control)
+ RefreshAbilities()
+
+ var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
+ hud.add_hud_to(src)
+ update_horror_hud()
+
+
+/mob/living/simple_animal/horror/Destroy()
+ host_brain = null
+ victim = null
+ return ..()
+
+/mob/living/simple_animal/horror/AltClickOn(atom/A)
+ if(iscarbon(A))
+ var/mob/living/carbon/C = A
+ if(!C || QDELETED(src) || !Adjacent(C) || victim || !can_use_ability())
+ return
+ if(victim)
+ to_chat(src, span_warning("You are already within a host."))
+ return
+
+ to_chat(src, span_warning("You slither your tentacles up [C] and begin probing at their ear canal..."))
+
+ if(!do_mob(src, C, 3 SECONDS))
+ to_chat(src, span_warning("As [C] moves away, you are dislodged and fall to the ground."))
+ return
+
+ if(!C || QDELETED(src))
+ return
+ if(C.has_horror_inside())
+ to_chat(src, span_warning("[C] is already infested!"))
+ return
+ Infect(C)
+ return
+ ..()
+
+/mob/living/simple_animal/horror/proc/has_chemicals(amt)
+ return chemicals >= amt
+
+/mob/living/simple_animal/horror/proc/use_chemicals(amt)
+ if(!has_chemicals(amt))
+ return FALSE
+ chemicals -= amt
+ update_horror_hud()
+ return TRUE
+
+/mob/living/simple_animal/horror/proc/regenerate_chemicals(amt)
+ chemicals += amt
+ chemicals = min(250, chemicals)
+ update_horror_hud()
+
+/mob/living/simple_animal/horror/proc/update_horror_hud()
+ if(!src || !hud_used)
+ return
+ var/datum/hud/chemical_counter/H = hud_used
+ var/obj/screen/counter = H.chemical_counter
+ counter.maptext = "[chemicals]
"
+
+/mob/living/simple_animal/horror/proc/can_use_ability()
+ if(stat != CONSCIOUS)
+ to_chat(src, "You cannot do that in your current state.")
+ return FALSE
+ return TRUE
+
+/mob/living/simple_animal/horror/proc/SearchTarget()
+ if(target)
+ if(world.time - used_target < 3 MINUTES)
+ to_chat(src, span_warning("You cannot use that ability again so soon."))
+ return
+ if(alert("You already have a target ([target.name]). Would you like to change that target?","Swap targets?","Yes","No") != "Yes")
+ return
+
+ var/list/possible_targets = list()
+ for(var/datum/mind/M in SSticker.minds)
+ if(M.current && M.current.stat != DEAD)
+ if(ishuman(M.current))
+ if(M.hasSoul && (mind.enslaved_to != M.current))
+ possible_targets[M] = M
+
+ var/list/selected_targets = list()
+ var/list/icons = list()
+ while(selected_targets.len != 4)
+ if(possible_targets.len <= 0)
+ break
+ var/datum/mind/M = pick(possible_targets)
+ selected_targets[M] = M
+ possible_targets -= M
+
+ var/mob/living/carbon/human/H = M.current
+ icons[M] = H
+
+ used_target = world.time
+
+ var/entry_name = show_radial_menu(src, (victim ? src.loc : src), icons, tooltips = TRUE)
+ target = selected_targets[entry_name]
+
+ //you didn't select your target? let me do that for you, my friend
+ if(selected_targets.len > 0 && !target)
+ target = pick(selected_targets)
+
+ if(target)
+ to_chat(src, span_warning("You caught their scent. Go and consume [target.current.real_name], the [target.assigned_role]'s soul!"))
+ apply_status_effect(/datum/status_effect/agent_pinpointer/horror)
+ for(var/datum/status_effect/agent_pinpointer/horror/status in status_effects)
+ status.scan_target = target.current
+ else
+ //refund cooldown
+ used_target = 0
+ to_chat(src, span_warning("Failed to select a target!"))
+
+/mob/living/simple_animal/horror/proc/ConsumeSoul()
+ if(!can_use_ability())
+ return
+
+ if(!victim.mind.hasSoul)
+ to_chat(src, "This host doesn't have a soul!")
+ return
+
+ if(victim == mind.enslaved_to)
+ to_chat(src, span_userdanger("No, not yet... We still need them..."))
+ return
+
+ if(victim.mind != target)
+ to_chat(src, "This soul isn't your target, you can't consume it!")
+ return
+
+ to_chat(src, "You begin consuming [victim.name]'s soul!")
+ if(do_after(src, 20 SECONDS, target = victim, stayStill = FALSE))
+ consume()
+
+/mob/living/simple_animal/horror/proc/consume()
+ if(!can_use_ability() || !victim || !victim.mind.hasSoul || victim.mind != target)
+ return
+ consumed_souls++
+ available_points++
+ to_chat(src, span_userdanger("You succeed in consuming [victim.name]'s soul!"))
+ to_chat(victim, span_userdanger("You suddenly feel weak and hollow inside..."))
+ victim.health -= 20
+ victim.maxHealth -= 20
+ victim.mind.hasSoul = FALSE
+ target = null
+ remove_status_effect(/datum/status_effect/agent_pinpointer/horror)
+ playsound(src, 'sound/effects/curseattack.ogg', 150)
+ playsound(src, 'sound/effects/ghost.ogg', 50)
+
+/mob/living/simple_animal/horror/proc/Communicate()
+ if(!can_use_ability())
+ return
+ if(!victim)
+ to_chat(src, "You do not have a host to communicate with!")
+ return
+
+ var/input = stripped_input(src, "Please enter a message to tell your host.", "Horror", null)
+ if(!input)
+ return
+
+ if(src && !QDELETED(src) && !QDELETED(victim))
+ if(victim)
+ to_chat(victim, span_changeling("[real_name] slurs: [input]"))
+ for(var/M in GLOB.dead_mob_list)
+ if(isobserver(M))
+ var/rendered = span_changeling("Horror Communication from [real_name] : [input]")
+ var/link = FOLLOW_LINK(M, src)
+ to_chat(M, "[link] [rendered]")
+ to_chat(src, span_changeling("[real_name] slurs: [input]"))
+ add_verb(victim, /mob/living/proc/horror_comm)
+ talk_to_horror_action.Grant(victim)
+
+/mob/living/proc/horror_comm()
+ set name = "Converse with Horror"
+ set category = "Horror"
+ set desc = "Communicate mentally with the thing in your head."
+
+ var/mob/living/simple_animal/horror/B = has_horror_inside()
+ if(B)
+ var/input = stripped_input(src, "Please enter a message to tell the horror.", "Message", "")
+ if(!input)
+ return
+
+ to_chat(B, span_changeling("[real_name] says: [input]"))
+
+ for(var/M in GLOB.dead_mob_list)
+ if(isobserver(M))
+ var/rendered = span_changeling("Horror Communication from [real_name] : [input]")
+ var/link = FOLLOW_LINK(M, src)
+ to_chat(M, "[link] [rendered]")
+ to_chat(src, span_changeling("[real_name] says: [input]"))
+
+/mob/living/proc/trapped_mind_comm()
+ var/mob/living/simple_animal/horror/B = has_horror_inside()
+ if(!B || !B.host_brain)
+ return
+ var/mob/living/captive_brain/CB = B.host_brain
+ var/input = stripped_input(src, "Please enter a message to tell the trapped mind.", "Message", null)
+ if(!input)
+ return
+
+ to_chat(CB, span_changeling("[B.real_name] says: [input]"))
+
+ for(var/M in GLOB.dead_mob_list)
+ if(isobserver(M))
+ var/rendered = span_changeling("Horror Communication from [B.real_name] : [input]")
+ var/link = FOLLOW_LINK(M, src)
+ to_chat(M, "[link] [rendered]")
+ to_chat(src, span_changeling("[B.real_name] says: [input]"))
+
+/mob/living/simple_animal/horror/Life()
+ ..()
+ if(has_upgrade("regen"))
+ heal_overall_damage(5)
+
+ if(invisible) //don't regenerate chemicals when invisible
+ if(use_chemicals(5))
+ alpha = max(alpha - 100, 1)
+ else
+ to_chat(src, span_warning("You ran out of chemicals to support your invisibility."))
+ invisible = FALSE
+ Update_Invisibility_Button()
+ else
+ if(has_upgrade("nohost_regen"))
+ regenerate_chemicals(chem_regen_rate)
+ else if(victim)
+ if(victim.stat == DEAD)
+ regenerate_chemicals(1)
+ else
+ regenerate_chemicals(chem_regen_rate)
+ alpha = min(255, alpha + 50)
+
+ if(victim)
+ if(stat != DEAD && victim.stat != DEAD)
+ heal_overall_damage(1)
+
+/mob/living/simple_animal/horror/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+ if(victim)
+ to_chat(src, span_warning("You cannot speak out loud while inside a host!"))
+ return
+ return ..()
+
+/mob/living/simple_animal/horror/emote(act, m_type = null, message = null, intentional = FALSE)
+ if(victim)
+ to_chat(src, span_warning("You cannot emote while inside a host!"))
+ return
+ return ..()
+
+/mob/living/simple_animal/horror/UnarmedAttack(atom/A)
+ if(istype(A, /obj/machinery/door/airlock))
+ var/obj/machinery/door/airlock/door = A
+ if(door.welded)
+ to_chat(src, span_danger("The door is welded shut!"))
+ return
+ visible_message(span_warning("[src] slips their tentacles into the airlock and starts prying it open!"), span_warning("You start moving onto the airlock."))
+ playsound(A, 'sound/misc/splort.ogg', 50, 1)
+ if(do_after(src, 5 SECONDS, target = A))
+ if(door.welded)
+ to_chat(src, span_danger("The door is welded shut!"))
+ return
+ visible_message(span_warning("[src] forces themselves through the airlock!"), span_warning("You force yourself through the airlock"))
+ forceMove(get_turf(A))
+ playsound(A, 'sound/machines/airlock_alien_prying.ogg', 50, 1)
+ return
+
+ if(isliving(A))
+ if(victim || A == src.mind.enslaved_to)
+ healthscan(usr, A)
+ chemscan(usr, A)
+ else
+ alpha = 255
+ if(hiding)
+ var/datum/action/innate/horror/H = has_ability(/datum/action/innate/horror/toggle_hide)
+ H.Activate()
+ if(invisible)
+ var/datum/action/innate/horror/H = has_ability(/datum/action/innate/horror/chameleon)
+ H.Activate()
+ Update_Invisibility_Button()
+ ..()
+
+/mob/living/simple_animal/horror/ex_act()
+ if(victim)
+ return
+
+ ..()
+
+/mob/living/simple_animal/horror/proc/Infect(mob/living/carbon/C)
+ if(!C)
+ return
+ var/obj/item/bodypart/head/head = C.get_bodypart(BODY_ZONE_HEAD)
+ if(!head)
+ to_chat(src, span_warning("[C] doesn't have a head!"))
+ return
+ var/hasbrain = locate(/obj/item/organ/brain) in C.internal_organs
+
+ if(!hasbrain)
+ to_chat(src, span_warning("[C] doesn't have a brain!"))
+ return
+
+ if(C.has_horror_inside())
+ to_chat(src, span_warning("[C] is already infested!"))
+ return
+
+ //can only infect non-ssd alive people / corpses with ghosts attached / current target
+ if((C.stat == DEAD || !C.key) && (C.stat != DEAD || !C.get_ghost()) && (!target || C != target.current))
+ to_chat(src, span_warning("[C]'s mind seems unresponsive. Try someone else!"))
+ return
+
+ if(hiding)
+ var/datum/action/innate/horror/H = has_ability(/datum/action/innate/horror/toggle_hide)
+ H.Activate()
+ invisible = FALSE
+ Update_Invisibility_Button()
+
+ victim = C
+ forceMove(victim)
+ RefreshAbilities()
+ log_game("[src]/([src.ckey]) has infested [victim]/([victim.ckey]")
+
+/mob/living/simple_animal/horror/proc/secrete_chemicals()
+ if(!can_use_ability())
+ return
+ if(!victim)
+ to_chat(src, span_warning("You are not inside a host body."))
+ return
+
+ var/content = "Chemicals: [chemicals]
"
+ content += ""
+
+ var/html = get_html_template(content)
+
+ usr << browse(html, "window=ViewHorror\ref[src]Chems;size=600x800")
+
+/mob/living/simple_animal/horror/proc/hide()
+ if(victim)
+ to_chat(src, span_warning("You cannot do this while you're inside a host."))
+ return
+
+ if(stat != CONSCIOUS)
+ return
+
+ if(!hiding)
+ layer = LATTICE_LAYER
+ visible_message(span_name("[src] scurries to the ground!"), \
+ span_noticealien("You are now hiding."))
+ hiding = TRUE
+ else
+ layer = MOB_LAYER
+ visible_message("[src] slowly peaks up from the ground...", \
+ span_noticealien("You stop hiding."))
+ hiding = FALSE
+
+/mob/living/simple_animal/horror/proc/go_invisible()
+ if(victim)
+ to_chat(src, span_warning("You cannot do this while you're inside a host."))
+ return
+
+ if(!can_use_ability())
+ return
+
+ if(!has_chemicals(10))
+ to_chat(src, span_warning("You don't have enough chemicals to do that."))
+ return
+
+ if(!invisible)
+ to_chat(src, span_noticealien("You focus your chameleon skin to blend into the environment."))
+ invisible = TRUE
+ else
+ to_chat(src, span_noticealien("You stop your camouflage."))
+ invisible = FALSE
+
+/mob/living/simple_animal/horror/proc/freeze_victim()
+ if(world.time - used_freeze < 150)
+ to_chat(src, span_warning("You cannot use that ability again so soon."))
+ return
+
+ if(victim)
+ to_chat(src, span_warning("You cannot do that from within a host body."))
+ return
+
+ if(!can_use_ability())
+ return
+
+ var/list/choices = list()
+ for(var/mob/living/carbon/C in view(1,src))
+ if(C.stat == CONSCIOUS)
+ choices += C
+
+ if(!choices.len)
+ return
+
+ if(QDELETED(src) || stat != CONSCIOUS || victim || (world.time - used_freeze < 150))
+ return
+
+ layer = MOB_LAYER
+ for (var/mob/living/carbon/M in range(1, src))
+ if(!M || !Adjacent(M))
+ return
+
+ if(has_upgrade("paralysis"))
+ playsound(loc, "sound/effects/sparks4.ogg", 30, 1, -1)
+ M.Stun(50)
+ M.SetSleeping(70) //knocked out cold
+ M.electrocute_act(15, src, 1, FALSE, FALSE, FALSE, 1, FALSE)
+ else
+ to_chat(M, span_userdanger("You feel something wrapping around your leg, pulling you down!"))
+ playsound(loc, "sound/weapons/whipgrab.ogg", 30, 1, -1)
+ M.Immobilize(50)
+ M.Knockdown(70)
+ used_freeze = world.time
+
+/mob/living/simple_animal/horror/proc/is_leaving()
+ return leaving
+
+/mob/living/simple_animal/horror/proc/release_victim()
+ if(!victim)
+ to_chat(src, span_danger("You are not inside a host body."))
+ return
+
+ if(!can_use_ability())
+ return
+
+ if(leaving)
+ leaving = FALSE
+ to_chat(src, span_danger("You decide against leaving your host."))
+ return
+
+ to_chat(src, span_danger("You begin disconnecting from [victim]'s synapses and prodding at their internal ear canal."))
+
+ if(victim.stat != DEAD && !has_upgrade("invisible_exit"))
+ to_chat(victim, span_userdanger("An odd, uncomfortable pressure begins to build inside your skull, behind your ear..."))
+
+ leaving = TRUE
+ if(do_after(src, 100, target = victim, extra_checks = CALLBACK(src, .proc/is_leaving), stayStill = FALSE))
+ release_host()
+
+/mob/living/simple_animal/horror/proc/release_host()
+ if(!victim || QDELETED(victim) || QDELETED(src) || controlling)
+ return
+
+ if(!can_use_ability())
+ return
+
+ if(has_upgrade("invisible_exit"))
+ alpha = 60
+ if(has_ability(/datum/action/innate/horror/chameleon))
+ invisible = TRUE
+ Update_Invisibility_Button()
+ to_chat(src, span_danger("You silently wiggle out of [victim]'s ear and plop to the ground before vanishing via reflective solution that covers you."))
+ else
+ to_chat(src, span_danger("You wiggle out of [victim]'s ear and plop to the ground."))
+ if(victim.mind)
+ if(!has_upgrade("invisible_exit"))
+ to_chat(victim, span_danger("Something slimy wiggles out of your ear and plops to the ground!"))
+
+ leaving = FALSE
+
+ leave_victim()
+
+/mob/living/simple_animal/horror/proc/leave_victim()
+ if(!victim)
+ return
+
+ if(controlling)
+ detatch()
+
+ forceMove(get_turf(victim))
+
+ reset_perspective()
+ unset_machine()
+
+ victim.reset_perspective()
+ victim.unset_machine()
+
+ var/mob/living/V = victim
+ remove_verb(V, /mob/living/proc/horror_comm)
+ talk_to_horror_action.Remove(victim)
+
+ for(var/obj/item/horrortentacle/T in victim)
+ victim.visible_message(span_warning("[victim]'s tentacle transforms back!"), span_notice("Your tentacle disappears!"))
+ playsound(victim, 'sound/effects/blobattack.ogg', 30, 1)
+ qdel(T)
+ victim = null
+
+ RefreshAbilities()
+
+
+/mob/living/simple_animal/horror/proc/jumpstart()
+ if(!victim)
+ to_chat(src, span_warning("You need a host to be able to use this."))
+ return
+
+ if(!can_use_ability())
+ return
+
+ if(victim.stat != DEAD)
+ to_chat(src, span_warning("Your host is already alive!"))
+ return
+
+ if(!has_chemicals(250))
+ to_chat(src, span_warning("You need 250 chemicals to use this!"))
+ return
+
+ if(HAS_TRAIT_FROM(target, TRAIT_BADDNA, CHANGELING_DRAIN))
+ to_chat(src, span_warning("Their DNA is completely destroyed! You can't revive them"))
+ return
+
+ if(victim.stat == DEAD)
+ playsound(src, 'sound/machines/defib_charge.ogg', 50, 1, -1)
+ sleep(1 SECONDS)
+ victim.tod = null
+ victim.setToxLoss(0)
+ victim.setOxyLoss(0)
+ victim.setCloneLoss(0)
+ victim.SetUnconscious(0)
+ victim.SetStun(0)
+ victim.SetKnockdown(0)
+ victim.radiation = 0
+ victim.heal_overall_damage(victim.getBruteLoss(), victim.getFireLoss())
+ victim.reagents.clear_reagents()
+ if(HAS_TRAIT_FROM(victim, TRAIT_HUSK, BURN))
+ victim.cure_husk(BURN)
+ for(var/organ in victim.internal_organs)
+ var/obj/item/organ/O = organ
+ O.setOrganDamage(0)
+ victim.restore_blood()
+ victim.remove_all_embedded_objects()
+ victim.revive()
+ log_game("[src]/([src.ckey]) has revived [victim]/([victim.ckey]")
+ chemicals -= 250
+ to_chat(src, span_notice("You send a jolt of energy to your host, reviving them!"))
+ victim.grab_ghost(force = TRUE) //brings the host back, no eggscape
+ victim.adjustOxyLoss(30)
+ to_chat(victim, span_userdanger("You bolt upright, gasping for breath!"))
+ victim.electrocute_act(15, src, 1, FALSE, FALSE, FALSE, 1, FALSE)
+ playsound(src, 'sound/machines/defib_zap.ogg', 50, 1, -1)
+
+
+/mob/living/simple_animal/horror/proc/view_memory()
+ if(!victim)
+ to_chat(src, span_warning("You need a host to be able to use this."))
+ return
+
+ if(!can_use_ability())
+ return
+
+ if(victim.stat == DEAD)
+ to_chat(src, span_warning("Your host brain is unresponsive. They are dead!"))
+ return
+
+ if(prob(20))
+ to_chat(victim, span_danger("You suddenly feel your memory being tangled with..."))//chance to alert the victim
+
+ if(victim.mind)
+ var/datum/mind/suckedbrain = victim.mind
+ to_chat(src, span_boldnotice("You skim through [victim]'s memories...[suckedbrain.memory]"))
+ for(var/A in suckedbrain.antag_datums)
+ var/datum/antagonist/antag_types = A
+ var/list/all_objectives = antag_types.objectives.Copy()
+ if(antag_types.antag_memory)
+ to_chat(src, span_notice("[antag_types.antag_memory]"))
+ if(LAZYLEN(all_objectives))
+ to_chat(src, span_boldnotice("Objectives:"))
+ var/obj_count = 1
+ for(var/O in all_objectives)
+ var/datum/objective/objective = O
+ to_chat(src, span_notice("Objective #[obj_count++]: [objective.explanation_text]"))
+ var/list/datum/mind/other_owners = objective.get_owners() - suckedbrain
+ if(other_owners.len)
+ for(var/mind in other_owners)
+ var/datum/mind/M = mind
+ to_chat(src, span_notice("Conspirator: [M.name]"))
+
+ var/list/recent_speech = list()
+ var/list/say_log = list()
+ var/log_source = victim.logging
+ for(var/log_type in log_source)
+ var/nlog_type = text2num(log_type)
+ if(nlog_type & LOG_SAY)
+ var/list/reversed = log_source[log_type]
+ if(islist(reversed))
+ say_log = reverseRange(reversed.Copy())
+ break
+ if(LAZYLEN(say_log))
+ for(var/spoken_memory in say_log)
+ if(recent_speech.len >= 5)//up to 5 random lines of speech, favoring more recent speech
+ break
+ if(prob(50))
+ recent_speech[spoken_memory] = say_log[spoken_memory]
+ if(recent_speech.len)
+ to_chat(src, span_boldnotice("You catch some drifting memories of their past conversations..."))
+ for(var/spoken_memory in recent_speech)
+ to_chat(src, span_notice("[recent_speech[spoken_memory]]"))
+ var/mob/living/carbon/human/H = victim
+ var/datum/dna/the_dna = H.has_dna()
+ if(the_dna)
+ to_chat(src, span_boldnotice("You uncover that [H.p_their()] true identity is [the_dna.real_name]."))
+
+/mob/living/simple_animal/horror/proc/is_bonding()
+ return bonding
+
+/mob/living/simple_animal/horror/proc/bond_brain()
+ if(!victim)
+ to_chat(src, span_warning("You are not inside a host body."))
+ return
+
+ if(!can_use_ability())
+ return
+
+ if(victim.stat == DEAD)
+ to_chat(src, span_notice("This host lacks enough brain function to control."))
+ return
+
+ if(victim.has_trauma_type(/datum/brain_trauma/severe/split_personality))
+ to_chat(src, span_notice("This host's brain lobe separation makes it too complex for you to control."))
+ return
+
+ if(bonding)
+ bonding = FALSE
+ to_chat(src, span_danger("You stop attempting to take control of your host."))
+ return
+
+ to_chat(src, span_danger("You begin delicately adjusting your connection to the host brain..."))
+
+ if(QDELETED(src) || QDELETED(victim))
+ return
+
+ bonding = TRUE
+
+ var/delay = 20 SECONDS
+ if(has_upgrade("fast_control"))
+ delay -= 12 SECONDS
+ if(do_after(src, delay, target = victim, extra_checks = CALLBACK(src, .proc/is_bonding), stayStill = FALSE))
+ assume_control()
+
+/mob/living/simple_animal/horror/proc/assume_control()
+ if(!victim || !src || controlling || victim.stat == DEAD)
+ return
+ if(is_servant_of_ratvar(victim) || iscultist(victim))
+ to_chat(src, span_warning("[victim]'s mind seems to be blocked by some unknown force!"))
+ bonding = FALSE
+ return
+ if(HAS_TRAIT(victim, TRAIT_MINDSHIELD))
+ to_chat(src, span_warning("[victim]'s mind seems to be shielded from your influence!"))
+ bonding = FALSE
+ return
+ else
+ RegisterSignal(victim, COMSIG_MOB_APPLY_DAMAGE, .proc/hit_detatch)
+ log_game("[src]/([src.ckey]) assumed control of [victim]/([victim.ckey] with eldritch powers.")
+ to_chat(src, span_warning("You plunge your probosci deep into the cortex of the host brain, interfacing directly with their nervous system."))
+ to_chat(victim, span_userdanger("You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours."))
+
+ clothing = victim.get_equipped_items()
+ for(var/obj/item/I in clothing)
+ ADD_TRAIT(I, TRAIT_NODROP, HORROR_TRAIT)
+
+ qdel(host_brain)
+ host_brain = new(src)
+ host_brain.H = src
+ host_brain.name = "Trapped mind of [victim.real_name]"
+ victim.mind.transfer_to(host_brain)
+ if(victim.key)
+ host_brain.key = victim.key
+
+ to_chat(host_brain, "You are trapped in your own mind. You feel that there must be a way to resist!")
+
+ mind.transfer_to(victim)
+
+ bonding = FALSE
+ controlling = TRUE
+
+ remove_verb(victim, /mob/living/proc/horror_comm)
+ talk_to_horror_action.Remove(victim)
+ GrantControlActions()
+
+ victim.med_hud_set_status()
+ if(target)
+ victim.apply_status_effect(/datum/status_effect/agent_pinpointer/horror)
+ for(var/datum/status_effect/agent_pinpointer/horror/status in victim.status_effects)
+ status.scan_target = target.current
+
+/mob/living/carbon/proc/release_control()
+ var/mob/living/simple_animal/horror/B = has_horror_inside()
+ if(B && B.host_brain)
+ to_chat(src, span_danger("You withdraw your probosci, releasing control of [B.host_brain]"))
+ B.detatch()
+
+//Check for brain worms in head.
+/mob/proc/has_horror_inside()
+ for(var/I in contents)
+ if(ishorror(I))
+ return I
+
+
+/mob/living/simple_animal/horror/proc/hit_detatch()
+ if(victim.health <= 75)
+ detatch()
+ to_chat(src, span_warning("It appears that [victim]s brain detected danger, and hastily took over."))
+ to_chat(victim, span_danger("Your body is under attack, you unconciously forced your brain to immediately take over!"))
+
+/mob/living/simple_animal/horror/proc/detatch()
+ if(!victim || !controlling)
+ return
+
+ controlling = FALSE
+ UnregisterSignal(victim, COMSIG_MOB_APPLY_DAMAGE)
+ add_verb(victim, /mob/living/proc/horror_comm)
+ RemoveControlActions()
+ RefreshAbilities()
+ talk_to_horror_action.Grant(victim)
+
+ for(var/obj/item/I in clothing)
+ REMOVE_TRAIT(I, TRAIT_NODROP, HORROR_TRAIT)
+ clothing = list()
+
+ victim.med_hud_set_status()
+ victim.remove_status_effect(/datum/status_effect/agent_pinpointer/horror)
+
+ victim.mind.transfer_to(src)
+ if(host_brain)
+ host_brain.mind.transfer_to(victim)
+ if(host_brain.key)
+ victim.key = host_brain.key
+
+ log_game("[src]/([src.ckey]) released control of [victim]/([victim.ckey]")
+ qdel(host_brain)
+
+/mob/living/simple_animal/horror/proc/Update_Invisibility_Button()
+ var/datum/action/innate/horror/action = has_ability(/datum/action/innate/horror/chameleon)
+ if(action)
+ action.button_icon_state = "horror_sneak_[invisible ? "true" : "false"]"
+ action.UpdateButtonIcon()
+
+/mob/living/simple_animal/horror/proc/GrantHorrorActions()
+ for(var/datum/action/innate/horror/ability in horrorabilities)
+ if("horror" in ability.category)
+ ability.Grant(src)
+
+/mob/living/simple_animal/horror/proc/RemoveHorrorActions()
+ for(var/datum/action/innate/horror/ability in horrorabilities)
+ if("horror" in ability.category)
+ ability.Remove(src)
+
+/mob/living/simple_animal/horror/proc/GrantInfestActions()
+ for(var/datum/action/innate/horror/ability in horrorabilities)
+ if("infest" in ability.category)
+ ability.Grant(src)
+
+/mob/living/simple_animal/horror/proc/RemoveInfestActions()
+ for(var/datum/action/innate/horror/ability in horrorabilities)
+ if("infest" in ability.category)
+ ability.Remove(src)
+
+/mob/living/simple_animal/horror/proc/GrantControlActions()
+ for(var/datum/action/innate/horror/ability in horrorabilities)
+ if("control" in ability.category)
+ ability.Grant(victim)
+
+/mob/living/simple_animal/horror/proc/RemoveControlActions()
+ for(var/datum/action/innate/horror/ability in horrorabilities)
+ if("control" in ability.category)
+ ability.Remove(victim)
+
+/mob/living/simple_animal/horror/proc/RefreshAbilities() //control abilities technically don't belong to horror
+ if(victim)
+ RemoveHorrorActions()
+ GrantInfestActions()
+ else
+ RemoveInfestActions()
GrantHorrorActions()
\ No newline at end of file
diff --git a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
index e5512caf24af..ddc238a8d2e1 100644
--- a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
+++ b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
@@ -1,461 +1,461 @@
-//ABILITIES
-
-/datum/action/innate/horror
- background_icon_state = "bg_ecult"
- icon_icon = 'icons/mob/actions/actions_horror.dmi'
- var/blacklisted = FALSE //If the ability can't be mutated
- var/soul_price = 0 //How much souls the ability costs to buy; if this is 0, it isn't listed on the catalog
- var/chemical_cost = 0 //How much chemicals the ability costs to use
- var/mob/living/simple_animal/horror/B //Horror holding the ability
- var/category //category for when the ability is active, "horror" is for creature, "infest" is during infestation, "controlling" is when a horror is controlling a body
-
-/datum/action/innate/horror/IsAvailable()
- if(!B)
- return
- if(!B.has_chemicals(chemical_cost))
- return
- . = ..()
-
-/datum/action/innate/horror/mutate
- name = "Mutate"
- desc = "Use consumed souls to mutate your abilities."
- button_icon_state = "mutate"
- blacklisted = TRUE
- category = list("horror")
-
-/datum/action/innate/horror/mutate/Activate()
- to_chat(usr, span_velvet(span_bold("You focus on mutating your body...")))
- B.ui_interact(usr)
- return TRUE
-
-/datum/action/innate/horror/seek_soul
- name = "Seek target soul"
- desc = "Search for a soul weak enough for you to consume."
- button_icon_state = "seek_soul"
- blacklisted = TRUE
- category = list("horror","infest")
-
-/datum/action/innate/horror/seek_soul/Activate()
- B.SearchTarget()
-
-/datum/action/innate/horror/consume_soul
- name = "Consume soul"
- desc = "Consume your target's soul."
- button_icon_state = "consume_soul"
- blacklisted = TRUE
- category = list("infest")
-
-/datum/action/innate/horror/consume_soul/Activate()
- B.ConsumeSoul()
-
-/datum/action/innate/horror/talk_to_host
- name = "Converse with Host"
- desc = "Send a silent message to your host."
- button_icon_state = "talk_to_host"
- blacklisted = TRUE
- category = list("infest")
-
-/datum/action/innate/horror/talk_to_host/Activate()
- B.Communicate()
-
-/datum/action/innate/horror/toggle_hide
- name = "Toggle Hide"
- desc = "Become invisible to the common eye. Toggled on or off."
- button_icon_state = "horror_hiding_false"
- blacklisted = TRUE
- category = list("horror")
-
-/datum/action/innate/horror/toggle_hide/Activate()
- B.hide()
- button_icon_state = "horror_hiding_[B.hiding ? "true" : "false"]"
- UpdateButtonIcon()
-
-/datum/action/innate/horror/talk_to_horror
- name = "Converse with Horror"
- desc = "Communicate mentally with your horror."
- button_icon_state = "talk_to_horror"
- blacklisted = TRUE
- var/mob/living/O
-
-/datum/action/innate/horror/talk_to_horror/IsAvailable()
- if(owner.stat == DEAD)
- return
- return TRUE
-
-/datum/action/innate/horror/talk_to_horror/Activate()
- var/mob/living/O = owner
- O.horror_comm()
-
-/datum/action/innate/horror/talk_to_brain
- name = "Converse with Trapped Mind"
- desc = "Communicate mentally with the trapped mind of your host."
- button_icon_state = "talk_to_trapped_mind"
- blacklisted = TRUE
- category = list("control")
-
-/datum/action/innate/horror/talk_to_brain/Activate()
- B.victim.trapped_mind_comm()
-
-/datum/action/innate/horror/take_control
- name = "Assume Control"
- desc = "Fully connect to the brain of your host."
- button_icon_state = "horror_brain"
- blacklisted = TRUE
- category = list("infest")
-
-/datum/action/innate/horror/take_control/Activate()
- B.bond_brain()
-
-/datum/action/innate/horror/give_back_control
- name = "Release Control"
- desc = "Release control of your host's body."
- button_icon_state = "horror_leave"
- blacklisted = TRUE
- category = list("control")
-
-/datum/action/innate/horror/give_back_control/Activate()
- B.victim.release_control()
-
-/datum/action/innate/horror/leave_body
- name = "Release Host"
- desc = "Slither out of your host."
- button_icon_state = "horror_leave"
- blacklisted = TRUE
- category = list("infest")
-
-/datum/action/innate/horror/leave_body/Activate()
- B.release_victim()
-
-/datum/action/innate/horror/make_chems
- name = "Secrete chemicals"
- desc = "Push some chemicals into your host's bloodstream."
- icon_icon = 'icons/obj/chemical.dmi'
- button_icon_state = "minidispenser"
- blacklisted = TRUE
- category = list("infest")
-
-/datum/action/innate/horror/make_chems/Activate()
- B.secrete_chemicals()
-
-/datum/action/innate/horror/freeze_victim
- name = "Knockdown victim"
- desc = "Use your tentacle to trip a victim, stunning for a short duration."
- button_icon_state = "trip"
- blacklisted = TRUE
- category = list("horror")
-
-/datum/action/innate/horror/freeze_victim/Activate()
- B.freeze_victim()
- UpdateButtonIcon()
- addtimer(CALLBACK(src, .proc/UpdateButtonIcon), 150)
-
-/datum/action/innate/horror/freeze_victim/IsAvailable()
- if(world.time - B.used_freeze < 150)
- return FALSE
- else
- return ..()
-
-//non-default abilities, can be mutated
-
-/datum/action/innate/horror/tentacle
- name = "Grow Tentacle"
- desc = "Makes your host grow a tentacle in their arm. Costs 50 chemicals to activate."
- button_icon_state = "tentacle"
- chemical_cost = 50
- category = list("infest", "control")
- soul_price = 2
-
-/datum/action/innate/horror/tentacle/IsAvailable()
- if(!active && !B.has_chemicals(chemical_cost))
- return
- return ..()
-
-/datum/action/innate/horror/tentacle/New()
- ..()
- START_PROCESSING(SSfastprocess, src)
-
-/datum/action/innate/horror/tentacle/Destroy()
- STOP_PROCESSING(SSfastprocess, src)
- return ..()
-
-/datum/action/innate/horror/tentacle/process()
- ..()
- active = locate(/obj/item/horrortentacle) in B.victim
- UpdateButtonIcon()
-
-
-/datum/action/innate/horror/tentacle/Activate()
- B.use_chemicals(50)
- B.victim.visible_message(span_warning("[B.victim]'s arm contorts into tentacles!"), span_notice("Your arm transforms into a giant tentacle. Examine it to see possible uses."))
- playsound(B.victim, 'sound/effects/blobattack.ogg', 30, 1)
- to_chat(B, span_warning("You transform [B.victim]'s arm into a tentacle!"))
- var/obj/item/horrortentacle/T = new
- B.victim.put_in_hands(T)
- return TRUE
-
-/datum/action/innate/horror/tentacle/Deactivate()
- B.victim.visible_message(span_warning("[B.victim]'s tentacle transforms back!"), span_notice("Your tentacle disappears!"))
- playsound(B.victim, 'sound/effects/blobattack.ogg', 30, 1)
- to_chat(B, span_warning("You transform [B.victim]'s arm back."))
- for(var/obj/item/horrortentacle/T in B.victim)
- qdel(T)
- return TRUE
-
-/datum/action/innate/horror/transfer_host
- name = "Transfer to another Host"
- desc = "Move into another host directly. Grabbing makes the process faster."
- button_icon_state = "transfer_host"
- category = list("infest", "control")
- soul_price = 1
- var/transferring = FALSE
-
-/datum/action/innate/horror/transfer_host/proc/is_transferring(var/mob/living/carbon/C)
- return transferring && C.Adjacent(B.victim)
-
-/datum/action/innate/horror/transfer_host/Activate()
- if(transferring)
- transferring = FALSE
- to_chat(src, span_warning("You decide against leaving your host."))
- return
-
- var/list/choices = list()
- for(var/mob/living/carbon/C in range(1,B.victim))
- if(C!=B.victim && C.Adjacent(B.victim))
- choices += C
-
- if(!choices.len)
- return
- var/mob/living/carbon/C = choices.len > 1 ? input(owner,"Who do you wish to infest?") in null|choices : choices[1]
- if(!C || !B)
- return
- if(!C.Adjacent(B.victim))
- return
- var/obj/item/bodypart/head/head = C.get_bodypart(BODY_ZONE_HEAD)
- if(!head)
- to_chat(owner, span_warning("[C] doesn't have a head!"))
- return
- var/hasbrain = FALSE
- for(var/obj/item/organ/brain/X in C.internal_organs)
- hasbrain = TRUE
- break
- if(!hasbrain)
- to_chat(owner, span_warning("[C] doesn't have a brain!"))
- return
- if((!C.key || !C.mind) && C != B.target.current)
- to_chat(owner, span_warning("[C]'s mind seems unresponsive. Try someone else!"))
- return
- if(C.has_horror_inside())
- to_chat(owner, span_warning("[C] is already infested!"))
- return
-
- to_chat(owner, span_warning("You move your tentacles away from [B.victim] and begin to transfer to [C]..."))
- var/delay = 20 SECONDS
- var/silent
- if(B.victim.pulling != C)
- silent = TRUE
- else
- switch(B.victim.grab_state)
- if(GRAB_PASSIVE)
- delay = 10 SECONDS
- if(GRAB_AGGRESSIVE)
- delay = 5 SECONDS
- if(GRAB_NECK)
- delay = 3 SECONDS
- else
- delay = 1 SECONDS
-
- transferring = TRUE
- if(!do_after(B.victim, delay, target = C, extra_checks = CALLBACK(src, .proc/is_transferring, C), stayStill = FALSE))
- to_chat(owner, span_warning("As [C] moves away, your transfer gets interrupted!"))
- transferring = FALSE
- return
- transferring = FALSE
- if(!C || !B || !C.Adjacent(B.victim))
- return
- B.leave_victim()
- B.Infect(C)
- if(!silent)
- to_chat(C, span_warning("Something slimy wiggles into your ear!"))
- playsound(B, 'sound/effects/blobattack.ogg', 30, 1)
-
-/datum/action/innate/horror/jumpstart_host
- name = "Revive Host"
- desc = "Bring your host back to life."
- button_icon_state = "revive"
- category = list("infest")
- soul_price = 2
-
-/datum/action/innate/horror/jumpstart_host/Activate()
- B.jumpstart()
-
-/datum/action/innate/horror/view_memory
- name = "View Memory"
- desc = "Read recent memory of the host you're inside of."
- button_icon_state = "view_memory"
- category = list("infest")
- soul_price = 1
-
-/datum/action/innate/horror/view_memory/Activate()
- B.view_memory()
-
-/datum/action/innate/horror/chameleon
- name = "Chameleon Skin"
- desc = "Adjust your skin color to blend into environment. Costs 5 chemicals per tick, also stopping chemical regeneration while active. Attacking stops the invisibility completely."
- button_icon_state = "horror_sneak_false"
- category = list("horror")
- soul_price = 1
-
-/datum/action/innate/horror/chameleon/Activate()
- B.go_invisible()
- button_icon_state = "horror_sneak_[B.invisible ? "true" : "false"]"
- UpdateButtonIcon()
-
-/datum/action/innate/horror/lube_spill
- name = "Lube spill"
- desc = "Makes you spin around and flail slippery lube around you. Costs 30 chemicals to activate."
- button_icon_state = "lube_spill"
- chemical_cost = 30
- category = list("horror")
- soul_price = 1
- var/cooldown = 0
-
-/datum/action/innate/horror/lube_spill/IsAvailable()
- if(cooldown > world.time || !B.has_chemicals(chemical_cost) || !B.can_use_ability())
- return
- return ..()
-
-/datum/action/innate/horror/lube_spill/Activate()
- B.use_chemicals(30)
- cooldown = world.time + 10 SECONDS
- UpdateButtonIcon()
- addtimer(CALLBACK(src, .proc/UpdateButtonIcon), 10 SECONDS)
- B.visible_message(span_warning("[B] starts spinning and throwing some sort of substance!"), span_notice("Your start to spin and flail oily substance everywhere!"))
- var/spins_remaining = 10
- B.icon_state = "horror_spin"
- while(spins_remaining > 0)
- playsound(B, 'sound/effects/blobattack.ogg', rand(20, 30), rand(0.5, 2))
- for(var/turf/open/t in range(1, B))
- if(prob(60) && B.Adjacent(t))
- t.MakeSlippery(TURF_WET_LUBE, 100)
- sleep(5)
- spins_remaining--
- if(!B.can_use_ability())
- return TRUE
- B.icon_state = "horror"
- return TRUE
-
-//UPGRADES
-/datum/horror_upgrade
- var/name = "horror upgrade"
- var/desc = "This is an upgrade."
- var/id
- var/soul_price = 0 //How much souls an upgrade costs to buy
- var/mob/living/simple_animal/horror/B //Horror holding the upgrades
-
-/datum/horror_upgrade/proc/unlock()
- if(!B)
- return
- apply_effects()
- qdel(src)
- return TRUE
-
-/datum/horror_upgrade/New(owner)
- ..()
- B = owner
-
-/datum/horror_upgrade/proc/apply_effects()
- return
-
-//Upgrades the stun ability
-/datum/horror_upgrade/paralysis
- name = "Electrocharged tentacle"
- id = "paralysis"
- desc = "Empowers your tentacle knockdown ability by giving it extra charge, knocking your victim down unconcious."
- soul_price = 3
-
-/datum/horror_upgrade/paralysis/apply_effects()
- var/datum/action/innate/horror/A = B.has_ability(/datum/action/innate/horror/freeze_victim)
- if(A)
- A.name = "Paralyze Victim"
- A.desc = "Shock a victim with an electrically charged tentacle."
- A.button_icon_state = "paralyze"
- B.update_action_buttons()
-
-//Increases chemical regeneration rate by 2
-/datum/horror_upgrade/chemical_regen
- name = "Efficient chemical glands"
- id = "chem_regen"
- desc = "Your chemical glands work more efficiently. Unlocking this increases your chemical regeneration."
- soul_price = 2
-
-/datum/horror_upgrade/chemical_regen/apply_effects()
- B.chem_regen_rate += 2
-
-//Lets horror regenerate chemicals outside of a host
-/datum/horror_upgrade/nohost_regen
- name = "Independent chemical glands"
- id = "nohost_regen"
- desc = "Your chemical glands become less parasitic and let you regenerate chemicals on their own without need for a host."
- soul_price = 2
-
-//Lets horror regenerate health
-/datum/horror_upgrade/regen
- name = "Regenerative skin"
- id = "regen"
- desc = "Your skin adapts to sustained damage and slowly regenerates itself, healing your wounds over time."
- soul_price = 1
-
-//Doubles horror's health pool
-/datum/horror_upgrade/hp_up
- name = "Rhino skin" //Horror can....roll?
- id = "hp_up"
- desc = "Your skin becomes hard as rock, greatly increasing your maximum health - and odds of survival outside of a host."
- soul_price = 2
-
-/datum/horror_upgrade/hp_up/apply_effects()
- B.health = round(min(B.maxHealth,B.health * 2))
- B.maxHealth = round(B.maxHealth * 2)
-
-//Makes horror almost invisible for a short time after leaving a host
-/datum/horror_upgrade/invisibility
- name = "Reflective fluids"
- id = "invisible_exit"
- desc = "You build up reflective solution inside host's brain. Upon exiting a host, you're briefly covered in it, rendering you near invisible for a few seconds. This mutation also makes the host unable to notice you exiting it directly."
- soul_price = 2
-
-//Increases melee damage to 20
-/datum/horror_upgrade/dmg_up
- name = "Sharpened teeth"
- id = "dmg_up"
- desc = "Your teeth become sharp blades, this mutation increases your melee damage."
- soul_price = 2
-
-/datum/horror_upgrade/dmg_up/apply_effects()
- B.attacktext = "crushes"
- B.attack_sound = 'sound/weapons/pierce_slow.ogg' //chunky
- B.melee_damage_lower += 10
- B.melee_damage_upper += 10
-
-//Expands the reagent selection horror can make
-/datum/horror_upgrade/upgraded_chems
- name = "Advanced reagent synthesis"
- id = "upgraded_chems"
- desc = "Lets you synthetize adrenaline, salicyclic acid, oxandrolone, pentetic acid and rezadone into your host."
- soul_price = 2
-
-/datum/horror_upgrade/upgraded_chems/apply_effects()
- B.horror_chems += list(/datum/horror_chem/adrenaline,/datum/horror_chem/sal_acid,/datum/horror_chem/oxandrolone,/datum/horror_chem/pen_acid,/datum/horror_chem/rezadone)
-
-//faster mind control
-/datum/horror_upgrade/fast_control
- name = "Precise probosci"
- id = "fast_control"
- desc = "Your probosci become more precise, allowing you to take control over your host's brain noticably faster."
- soul_price = 2
-
-//makes it longer for host to snap out of mind control
-/datum/horror_upgrade/deep_control
- name = "Insulated probosci"
- id = "deep_control"
- desc = "Your probosci become insulated, protecting them from neural shocks. This makes it harder for the host to regain control over their body."
+//ABILITIES
+
+/datum/action/innate/horror
+ background_icon_state = "bg_ecult"
+ icon_icon = 'icons/mob/actions/actions_horror.dmi'
+ var/blacklisted = FALSE //If the ability can't be mutated
+ var/soul_price = 0 //How much souls the ability costs to buy; if this is 0, it isn't listed on the catalog
+ var/chemical_cost = 0 //How much chemicals the ability costs to use
+ var/mob/living/simple_animal/horror/B //Horror holding the ability
+ var/category //category for when the ability is active, "horror" is for creature, "infest" is during infestation, "controlling" is when a horror is controlling a body
+
+/datum/action/innate/horror/IsAvailable()
+ if(!B)
+ return
+ if(!B.has_chemicals(chemical_cost))
+ return
+ . = ..()
+
+/datum/action/innate/horror/mutate
+ name = "Mutate"
+ desc = "Use consumed souls to mutate your abilities."
+ button_icon_state = "mutate"
+ blacklisted = TRUE
+ category = list("horror")
+
+/datum/action/innate/horror/mutate/Activate()
+ to_chat(usr, span_velvet(span_bold("You focus on mutating your body...")))
+ B.ui_interact(usr)
+ return TRUE
+
+/datum/action/innate/horror/seek_soul
+ name = "Seek target soul"
+ desc = "Search for a soul weak enough for you to consume."
+ button_icon_state = "seek_soul"
+ blacklisted = TRUE
+ category = list("horror","infest")
+
+/datum/action/innate/horror/seek_soul/Activate()
+ B.SearchTarget()
+
+/datum/action/innate/horror/consume_soul
+ name = "Consume soul"
+ desc = "Consume your target's soul."
+ button_icon_state = "consume_soul"
+ blacklisted = TRUE
+ category = list("infest")
+
+/datum/action/innate/horror/consume_soul/Activate()
+ B.ConsumeSoul()
+
+/datum/action/innate/horror/talk_to_host
+ name = "Converse with Host"
+ desc = "Send a silent message to your host."
+ button_icon_state = "talk_to_host"
+ blacklisted = TRUE
+ category = list("infest")
+
+/datum/action/innate/horror/talk_to_host/Activate()
+ B.Communicate()
+
+/datum/action/innate/horror/toggle_hide
+ name = "Toggle Hide"
+ desc = "Become invisible to the common eye. Toggled on or off."
+ button_icon_state = "horror_hiding_false"
+ blacklisted = TRUE
+ category = list("horror")
+
+/datum/action/innate/horror/toggle_hide/Activate()
+ B.hide()
+ button_icon_state = "horror_hiding_[B.hiding ? "true" : "false"]"
+ UpdateButtonIcon()
+
+/datum/action/innate/horror/talk_to_horror
+ name = "Converse with Horror"
+ desc = "Communicate mentally with your horror."
+ button_icon_state = "talk_to_horror"
+ blacklisted = TRUE
+ var/mob/living/O
+
+/datum/action/innate/horror/talk_to_horror/IsAvailable()
+ if(owner.stat == DEAD)
+ return
+ return TRUE
+
+/datum/action/innate/horror/talk_to_horror/Activate()
+ var/mob/living/O = owner
+ O.horror_comm()
+
+/datum/action/innate/horror/talk_to_brain
+ name = "Converse with Trapped Mind"
+ desc = "Communicate mentally with the trapped mind of your host."
+ button_icon_state = "talk_to_trapped_mind"
+ blacklisted = TRUE
+ category = list("control")
+
+/datum/action/innate/horror/talk_to_brain/Activate()
+ B.victim.trapped_mind_comm()
+
+/datum/action/innate/horror/take_control
+ name = "Assume Control"
+ desc = "Fully connect to the brain of your host."
+ button_icon_state = "horror_brain"
+ blacklisted = TRUE
+ category = list("infest")
+
+/datum/action/innate/horror/take_control/Activate()
+ B.bond_brain()
+
+/datum/action/innate/horror/give_back_control
+ name = "Release Control"
+ desc = "Release control of your host's body."
+ button_icon_state = "horror_leave"
+ blacklisted = TRUE
+ category = list("control")
+
+/datum/action/innate/horror/give_back_control/Activate()
+ B.victim.release_control()
+
+/datum/action/innate/horror/leave_body
+ name = "Release Host"
+ desc = "Slither out of your host."
+ button_icon_state = "horror_leave"
+ blacklisted = TRUE
+ category = list("infest")
+
+/datum/action/innate/horror/leave_body/Activate()
+ B.release_victim()
+
+/datum/action/innate/horror/make_chems
+ name = "Secrete chemicals"
+ desc = "Push some chemicals into your host's bloodstream."
+ icon_icon = 'icons/obj/chemical.dmi'
+ button_icon_state = "minidispenser"
+ blacklisted = TRUE
+ category = list("infest")
+
+/datum/action/innate/horror/make_chems/Activate()
+ B.secrete_chemicals()
+
+/datum/action/innate/horror/freeze_victim
+ name = "Knockdown victim"
+ desc = "Use your tentacle to trip a victim, stunning for a short duration."
+ button_icon_state = "trip"
+ blacklisted = TRUE
+ category = list("horror")
+
+/datum/action/innate/horror/freeze_victim/Activate()
+ B.freeze_victim()
+ UpdateButtonIcon()
+ addtimer(CALLBACK(src, .proc/UpdateButtonIcon), 150)
+
+/datum/action/innate/horror/freeze_victim/IsAvailable()
+ if(world.time - B.used_freeze < 150)
+ return FALSE
+ else
+ return ..()
+
+//non-default abilities, can be mutated
+
+/datum/action/innate/horror/tentacle
+ name = "Grow Tentacle"
+ desc = "Makes your host grow a tentacle in their arm. Costs 50 chemicals to activate."
+ button_icon_state = "tentacle"
+ chemical_cost = 50
+ category = list("infest", "control")
+ soul_price = 2
+
+/datum/action/innate/horror/tentacle/IsAvailable()
+ if(!active && !B.has_chemicals(chemical_cost))
+ return
+ return ..()
+
+/datum/action/innate/horror/tentacle/New()
+ ..()
+ START_PROCESSING(SSfastprocess, src)
+
+/datum/action/innate/horror/tentacle/Destroy()
+ STOP_PROCESSING(SSfastprocess, src)
+ return ..()
+
+/datum/action/innate/horror/tentacle/process()
+ ..()
+ active = locate(/obj/item/horrortentacle) in B.victim
+ UpdateButtonIcon()
+
+
+/datum/action/innate/horror/tentacle/Activate()
+ B.use_chemicals(50)
+ B.victim.visible_message(span_warning("[B.victim]'s arm contorts into tentacles!"), span_notice("Your arm transforms into a giant tentacle. Examine it to see possible uses."))
+ playsound(B.victim, 'sound/effects/blobattack.ogg', 30, 1)
+ to_chat(B, span_warning("You transform [B.victim]'s arm into a tentacle!"))
+ var/obj/item/horrortentacle/T = new
+ B.victim.put_in_hands(T)
+ return TRUE
+
+/datum/action/innate/horror/tentacle/Deactivate()
+ B.victim.visible_message(span_warning("[B.victim]'s tentacle transforms back!"), span_notice("Your tentacle disappears!"))
+ playsound(B.victim, 'sound/effects/blobattack.ogg', 30, 1)
+ to_chat(B, span_warning("You transform [B.victim]'s arm back."))
+ for(var/obj/item/horrortentacle/T in B.victim)
+ qdel(T)
+ return TRUE
+
+/datum/action/innate/horror/transfer_host
+ name = "Transfer to another Host"
+ desc = "Move into another host directly. Grabbing makes the process faster."
+ button_icon_state = "transfer_host"
+ category = list("infest", "control")
+ soul_price = 1
+ var/transferring = FALSE
+
+/datum/action/innate/horror/transfer_host/proc/is_transferring(var/mob/living/carbon/C)
+ return transferring && C.Adjacent(B.victim)
+
+/datum/action/innate/horror/transfer_host/Activate()
+ if(transferring)
+ transferring = FALSE
+ to_chat(src, span_warning("You decide against leaving your host."))
+ return
+
+ var/list/choices = list()
+ for(var/mob/living/carbon/C in range(1,B.victim))
+ if(C!=B.victim && C.Adjacent(B.victim))
+ choices += C
+
+ if(!choices.len)
+ return
+ var/mob/living/carbon/C = choices.len > 1 ? input(owner,"Who do you wish to infest?") in null|choices : choices[1]
+ if(!C || !B)
+ return
+ if(!C.Adjacent(B.victim))
+ return
+ var/obj/item/bodypart/head/head = C.get_bodypart(BODY_ZONE_HEAD)
+ if(!head)
+ to_chat(owner, span_warning("[C] doesn't have a head!"))
+ return
+ var/hasbrain = FALSE
+ for(var/obj/item/organ/brain/X in C.internal_organs)
+ hasbrain = TRUE
+ break
+ if(!hasbrain)
+ to_chat(owner, span_warning("[C] doesn't have a brain!"))
+ return
+ if((!C.key || !C.mind) && C != B.target.current)
+ to_chat(owner, span_warning("[C]'s mind seems unresponsive. Try someone else!"))
+ return
+ if(C.has_horror_inside())
+ to_chat(owner, span_warning("[C] is already infested!"))
+ return
+
+ to_chat(owner, span_warning("You move your tentacles away from [B.victim] and begin to transfer to [C]..."))
+ var/delay = 20 SECONDS
+ var/silent
+ if(B.victim.pulling != C)
+ silent = TRUE
+ else
+ switch(B.victim.grab_state)
+ if(GRAB_PASSIVE)
+ delay = 10 SECONDS
+ if(GRAB_AGGRESSIVE)
+ delay = 5 SECONDS
+ if(GRAB_NECK)
+ delay = 3 SECONDS
+ else
+ delay = 1 SECONDS
+
+ transferring = TRUE
+ if(!do_after(B.victim, delay, target = C, extra_checks = CALLBACK(src, .proc/is_transferring, C), stayStill = FALSE))
+ to_chat(owner, span_warning("As [C] moves away, your transfer gets interrupted!"))
+ transferring = FALSE
+ return
+ transferring = FALSE
+ if(!C || !B || !C.Adjacent(B.victim))
+ return
+ B.leave_victim()
+ B.Infect(C)
+ if(!silent)
+ to_chat(C, span_warning("Something slimy wiggles into your ear!"))
+ playsound(B, 'sound/effects/blobattack.ogg', 30, 1)
+
+/datum/action/innate/horror/jumpstart_host
+ name = "Revive Host"
+ desc = "Bring your host back to life."
+ button_icon_state = "revive"
+ category = list("infest")
+ soul_price = 2
+
+/datum/action/innate/horror/jumpstart_host/Activate()
+ B.jumpstart()
+
+/datum/action/innate/horror/view_memory
+ name = "View Memory"
+ desc = "Read recent memory of the host you're inside of."
+ button_icon_state = "view_memory"
+ category = list("infest")
+ soul_price = 1
+
+/datum/action/innate/horror/view_memory/Activate()
+ B.view_memory()
+
+/datum/action/innate/horror/chameleon
+ name = "Chameleon Skin"
+ desc = "Adjust your skin color to blend into environment. Costs 5 chemicals per tick, also stopping chemical regeneration while active. Attacking stops the invisibility completely."
+ button_icon_state = "horror_sneak_false"
+ category = list("horror")
+ soul_price = 1
+
+/datum/action/innate/horror/chameleon/Activate()
+ B.go_invisible()
+ button_icon_state = "horror_sneak_[B.invisible ? "true" : "false"]"
+ UpdateButtonIcon()
+
+/datum/action/innate/horror/lube_spill
+ name = "Lube spill"
+ desc = "Makes you spin around and flail slippery lube around you. Costs 30 chemicals to activate."
+ button_icon_state = "lube_spill"
+ chemical_cost = 30
+ category = list("horror")
+ soul_price = 1
+ var/cooldown = 0
+
+/datum/action/innate/horror/lube_spill/IsAvailable()
+ if(cooldown > world.time || !B.has_chemicals(chemical_cost) || !B.can_use_ability())
+ return
+ return ..()
+
+/datum/action/innate/horror/lube_spill/Activate()
+ B.use_chemicals(30)
+ cooldown = world.time + 10 SECONDS
+ UpdateButtonIcon()
+ addtimer(CALLBACK(src, .proc/UpdateButtonIcon), 10 SECONDS)
+ B.visible_message(span_warning("[B] starts spinning and throwing some sort of substance!"), span_notice("Your start to spin and flail oily substance everywhere!"))
+ var/spins_remaining = 10
+ B.icon_state = "horror_spin"
+ while(spins_remaining > 0)
+ playsound(B, 'sound/effects/blobattack.ogg', rand(20, 30), rand(0.5, 2))
+ for(var/turf/open/t in range(1, B))
+ if(prob(60) && B.Adjacent(t))
+ t.MakeSlippery(TURF_WET_LUBE, 100)
+ sleep(5)
+ spins_remaining--
+ if(!B.can_use_ability())
+ return TRUE
+ B.icon_state = "horror"
+ return TRUE
+
+//UPGRADES
+/datum/horror_upgrade
+ var/name = "horror upgrade"
+ var/desc = "This is an upgrade."
+ var/id
+ var/soul_price = 0 //How much souls an upgrade costs to buy
+ var/mob/living/simple_animal/horror/B //Horror holding the upgrades
+
+/datum/horror_upgrade/proc/unlock()
+ if(!B)
+ return
+ apply_effects()
+ qdel(src)
+ return TRUE
+
+/datum/horror_upgrade/New(owner)
+ ..()
+ B = owner
+
+/datum/horror_upgrade/proc/apply_effects()
+ return
+
+//Upgrades the stun ability
+/datum/horror_upgrade/paralysis
+ name = "Electrocharged tentacle"
+ id = "paralysis"
+ desc = "Empowers your tentacle knockdown ability by giving it extra charge, knocking your victim down unconcious."
+ soul_price = 3
+
+/datum/horror_upgrade/paralysis/apply_effects()
+ var/datum/action/innate/horror/A = B.has_ability(/datum/action/innate/horror/freeze_victim)
+ if(A)
+ A.name = "Paralyze Victim"
+ A.desc = "Shock a victim with an electrically charged tentacle."
+ A.button_icon_state = "paralyze"
+ B.update_action_buttons()
+
+//Increases chemical regeneration rate by 2
+/datum/horror_upgrade/chemical_regen
+ name = "Efficient chemical glands"
+ id = "chem_regen"
+ desc = "Your chemical glands work more efficiently. Unlocking this increases your chemical regeneration."
+ soul_price = 2
+
+/datum/horror_upgrade/chemical_regen/apply_effects()
+ B.chem_regen_rate += 2
+
+//Lets horror regenerate chemicals outside of a host
+/datum/horror_upgrade/nohost_regen
+ name = "Independent chemical glands"
+ id = "nohost_regen"
+ desc = "Your chemical glands become less parasitic and let you regenerate chemicals on their own without need for a host."
+ soul_price = 2
+
+//Lets horror regenerate health
+/datum/horror_upgrade/regen
+ name = "Regenerative skin"
+ id = "regen"
+ desc = "Your skin adapts to sustained damage and slowly regenerates itself, healing your wounds over time."
+ soul_price = 1
+
+//Doubles horror's health pool
+/datum/horror_upgrade/hp_up
+ name = "Rhino skin" //Horror can....roll?
+ id = "hp_up"
+ desc = "Your skin becomes hard as rock, greatly increasing your maximum health - and odds of survival outside of a host."
+ soul_price = 2
+
+/datum/horror_upgrade/hp_up/apply_effects()
+ B.health = round(min(B.maxHealth,B.health * 2))
+ B.maxHealth = round(B.maxHealth * 2)
+
+//Makes horror almost invisible for a short time after leaving a host
+/datum/horror_upgrade/invisibility
+ name = "Reflective fluids"
+ id = "invisible_exit"
+ desc = "You build up reflective solution inside host's brain. Upon exiting a host, you're briefly covered in it, rendering you near invisible for a few seconds. This mutation also makes the host unable to notice you exiting it directly."
+ soul_price = 2
+
+//Increases melee damage to 20
+/datum/horror_upgrade/dmg_up
+ name = "Sharpened teeth"
+ id = "dmg_up"
+ desc = "Your teeth become sharp blades, this mutation increases your melee damage."
+ soul_price = 2
+
+/datum/horror_upgrade/dmg_up/apply_effects()
+ B.attacktext = "crushes"
+ B.attack_sound = 'sound/weapons/pierce_slow.ogg' //chunky
+ B.melee_damage_lower += 10
+ B.melee_damage_upper += 10
+
+//Expands the reagent selection horror can make
+/datum/horror_upgrade/upgraded_chems
+ name = "Advanced reagent synthesis"
+ id = "upgraded_chems"
+ desc = "Lets you synthetize adrenaline, salicyclic acid, oxandrolone, pentetic acid and rezadone into your host."
+ soul_price = 2
+
+/datum/horror_upgrade/upgraded_chems/apply_effects()
+ B.horror_chems += list(/datum/horror_chem/adrenaline,/datum/horror_chem/sal_acid,/datum/horror_chem/oxandrolone,/datum/horror_chem/pen_acid,/datum/horror_chem/rezadone)
+
+//faster mind control
+/datum/horror_upgrade/fast_control
+ name = "Precise probosci"
+ id = "fast_control"
+ desc = "Your probosci become more precise, allowing you to take control over your host's brain noticably faster."
+ soul_price = 2
+
+//makes it longer for host to snap out of mind control
+/datum/horror_upgrade/deep_control
+ name = "Insulated probosci"
+ id = "deep_control"
+ desc = "Your probosci become insulated, protecting them from neural shocks. This makes it harder for the host to regain control over their body."
soul_price = 2
\ No newline at end of file
diff --git a/code/modules/antagonists/horror/horror_chemicals.dm b/code/modules/antagonists/horror/horror_chemicals.dm
index d0c6bdc4e04b..50cfdafb7f60 100644
--- a/code/modules/antagonists/horror/horror_chemicals.dm
+++ b/code/modules/antagonists/horror/horror_chemicals.dm
@@ -1,96 +1,96 @@
-/mob/living/simple_animal/horror/Topic(href, href_list, hsrc)
- if(href_list["horror_use_chem"])
- locate(href_list["src"])
- if(!istype(src, /mob/living/simple_animal/horror))
- return
-
- var/topic_chem = href_list["horror_use_chem"]
- var/datum/horror_chem/C
-
- for(var/datum in typesof(/datum/horror_chem))
- var/datum/horror_chem/test = new datum()
- if(test.chemname == topic_chem)
- C = test
- break
-
- if(!istype(C, /datum/horror_chem))
- return
-
- if(!C || !victim || controlling || !src || stat)
- return
-
- if(!istype(C, /datum/horror_chem))
- return
-
- if(chemicals < C.chemuse)
- to_chat(src, span_boldnotice("You need [C.chemuse] chemicals stored to use this chemical!"))
- return
-
- to_chat(src, span_danger("You squirt a measure of [C.chemname] from your reservoirs into [victim]'s bloodstream."))
- victim.reagents.add_reagent(C.R, C.quantity)
- chemicals -= C.chemuse
- log_game("[src]/([src.ckey]) has injected [C.chemname] into their host [victim]/([victim.ckey])")
-
- src << output(chemicals, "ViewHorror\ref[src]Chems.browser:update_chemicals")
-
- ..()
-
-/datum/horror_chem
- var/chemname
- var/chem_desc = "This is a chemical"
- var/datum/reagent/R
- var/chemuse = 30
- var/quantity = 10
-
-/datum/horror_chem/epinephrine
- chemname = "epinephrine"
- R = /datum/reagent/medicine/epinephrine
- chem_desc = "Stabilizes critical condition and slowly restores oxygen damage."
-
-/datum/horror_chem/mannitol
- chemname = "mannitol"
- R = /datum/reagent/medicine/mannitol
- chem_desc = "Heals brain damage."
-
-/datum/horror_chem/bicaridine
- chemname = "bicaridine"
- R = /datum/reagent/medicine/bicaridine
- chem_desc = "Heals brute damage."
-
-/datum/horror_chem/kelotane
- chemname = "kelotane"
- R = /datum/reagent/medicine/kelotane
- chem_desc = "Heals burn damage."
-
-/datum/horror_chem/charcoal
- chemname = "charcoal"
- R = /datum/reagent/medicine/charcoal
- chem_desc = "Slowly heals toxin damage, while also slowly removing any other chemicals."
-
-/datum/horror_chem/adrenaline
- chemname = "adrenaline"
- R = /datum/reagent/medicine/changelingadrenaline
- chemuse = 100
- chem_desc = "Stimulates the brain, shrugging off effect of stuns while regenerating stamina."
-
-/datum/horror_chem/rezadone
- chemname = "rezadone"
- R = /datum/reagent/medicine/rezadone
- chemuse = 50
- chem_desc = "Heals cellular damage."
-
-/datum/horror_chem/pen_acid
- chemname = "pentetic acid"
- R = /datum/reagent/medicine/pen_acid
- chemuse = 50
- chem_desc = "Reduces massive amounts of radiation and toxin damage while purging other chemicals from the body."
-
-/datum/horror_chem/sal_acid
- chemname = "salicyclic acid"
- R = /datum/reagent/medicine/sal_acid
- chem_desc = "Stimulates the healing of severe bruises. Rapidly heals severe bruising and slowly heals minor ones."
-
-/datum/horror_chem/oxandrolone
- chemname = "oxandrolone"
- R = /datum/reagent/medicine/oxandrolone
+/mob/living/simple_animal/horror/Topic(href, href_list, hsrc)
+ if(href_list["horror_use_chem"])
+ locate(href_list["src"])
+ if(!istype(src, /mob/living/simple_animal/horror))
+ return
+
+ var/topic_chem = href_list["horror_use_chem"]
+ var/datum/horror_chem/C
+
+ for(var/datum in typesof(/datum/horror_chem))
+ var/datum/horror_chem/test = new datum()
+ if(test.chemname == topic_chem)
+ C = test
+ break
+
+ if(!istype(C, /datum/horror_chem))
+ return
+
+ if(!C || !victim || controlling || !src || stat)
+ return
+
+ if(!istype(C, /datum/horror_chem))
+ return
+
+ if(chemicals < C.chemuse)
+ to_chat(src, span_boldnotice("You need [C.chemuse] chemicals stored to use this chemical!"))
+ return
+
+ to_chat(src, span_danger("You squirt a measure of [C.chemname] from your reservoirs into [victim]'s bloodstream."))
+ victim.reagents.add_reagent(C.R, C.quantity)
+ chemicals -= C.chemuse
+ log_game("[src]/([src.ckey]) has injected [C.chemname] into their host [victim]/([victim.ckey])")
+
+ src << output(chemicals, "ViewHorror\ref[src]Chems.browser:update_chemicals")
+
+ ..()
+
+/datum/horror_chem
+ var/chemname
+ var/chem_desc = "This is a chemical"
+ var/datum/reagent/R
+ var/chemuse = 30
+ var/quantity = 10
+
+/datum/horror_chem/epinephrine
+ chemname = "epinephrine"
+ R = /datum/reagent/medicine/epinephrine
+ chem_desc = "Stabilizes critical condition and slowly restores oxygen damage."
+
+/datum/horror_chem/mannitol
+ chemname = "mannitol"
+ R = /datum/reagent/medicine/mannitol
+ chem_desc = "Heals brain damage."
+
+/datum/horror_chem/bicaridine
+ chemname = "bicaridine"
+ R = /datum/reagent/medicine/bicaridine
+ chem_desc = "Heals brute damage."
+
+/datum/horror_chem/kelotane
+ chemname = "kelotane"
+ R = /datum/reagent/medicine/kelotane
+ chem_desc = "Heals burn damage."
+
+/datum/horror_chem/charcoal
+ chemname = "charcoal"
+ R = /datum/reagent/medicine/charcoal
+ chem_desc = "Slowly heals toxin damage, while also slowly removing any other chemicals."
+
+/datum/horror_chem/adrenaline
+ chemname = "adrenaline"
+ R = /datum/reagent/medicine/changelingadrenaline
+ chemuse = 100
+ chem_desc = "Stimulates the brain, shrugging off effect of stuns while regenerating stamina."
+
+/datum/horror_chem/rezadone
+ chemname = "rezadone"
+ R = /datum/reagent/medicine/rezadone
+ chemuse = 50
+ chem_desc = "Heals cellular damage."
+
+/datum/horror_chem/pen_acid
+ chemname = "pentetic acid"
+ R = /datum/reagent/medicine/pen_acid
+ chemuse = 50
+ chem_desc = "Reduces massive amounts of radiation and toxin damage while purging other chemicals from the body."
+
+/datum/horror_chem/sal_acid
+ chemname = "salicyclic acid"
+ R = /datum/reagent/medicine/sal_acid
+ chem_desc = "Stimulates the healing of severe bruises. Rapidly heals severe bruising and slowly heals minor ones."
+
+/datum/horror_chem/oxandrolone
+ chemname = "oxandrolone"
+ R = /datum/reagent/medicine/oxandrolone
chem_desc = "Stimulates the healing of severe burns. Rapidly heals severe burns and slowly heals minor ones."
\ No newline at end of file
diff --git a/code/modules/antagonists/horror/horror_datums.dm b/code/modules/antagonists/horror/horror_datums.dm
index 3ab9138356a4..6f75827e090a 100644
--- a/code/modules/antagonists/horror/horror_datums.dm
+++ b/code/modules/antagonists/horror/horror_datums.dm
@@ -1,333 +1,333 @@
-//ANTAG DATUMS
-/datum/antagonist/horror
- name = "Horror"
- show_in_antagpanel = TRUE
- prevent_roundtype_conversion = FALSE
- show_name_in_check_antagonists = TRUE
- show_to_ghosts = TRUE
- var/datum/mind/summoner
-
-/datum/antagonist/horror/on_gain()
- . = ..()
- give_objectives()
- if(ishorror(owner.current) && owner.current.mind)
- var/mob/living/simple_animal/horror/H = owner.current
- H.update_horror_hud()
-
-/datum/antagonist/horror/proc/give_objectives()
- if(summoner)
- var/datum/objective/newobjective = new
- newobjective.explanation_text = "Serve your summoner, [summoner.name]."
- newobjective.owner = owner
- newobjective.completed = TRUE
- objectives += newobjective
- else
- //succ some souls
- var/datum/objective/horrorascend/ascend = new
- ascend.owner = owner
- ascend.hor = owner.current
- ascend.target_amount = rand(5, 8)
- objectives += ascend
- ascend.update_explanation_text()
-
- //looking for antagonist we can assist
- var/list/possible_targets = list()
- for(var/datum/mind/M in SSticker.minds)
- if(M.current && M.current.stat != DEAD)
- if(ishuman(M.current))
- if(M.special_role)
- possible_targets += M
-
- if(possible_targets.len)
- var/datum/mind/M = pick(possible_targets)
- var/datum/objective/protect/O = new
- O.owner = owner
- O.target = M
- O.explanation_text = "Protect and assist \the [M.current.real_name], the [M.assigned_role]."
- objectives += O
-
-
- //don't die while you're at is
- var/datum/objective/survive/survive = new
- survive.owner = owner
- objectives += survive
-
-/datum/objective/horrorascend
- name = "consume souls"
- var/mob/living/simple_animal/horror/hor
-
-/datum/objective/horrorascend/update_explanation_text()
- . = ..()
- explanation_text = "Consume [target_amount] souls."
-
-/datum/objective/horrorascend/check_completion()
- if(hor && hor.consumed_souls >= target_amount)
- return TRUE
- return FALSE
-
-
-//SPAWNER
-/obj/item/horrorspawner
- name = "suspicious pet carrier"
- desc = "It contains some sort of creature inside. You can see tentacles sticking out of it."
- icon = 'icons/obj/pet_carrier.dmi'
- lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/items_righthand.dmi'
- item_state = "pet_carrier"
- icon_state = "pet_carrier_occupied"
- var/used = FALSE
- color = rgb(130, 105, 160)
-
-/obj/item/horrorspawner/attack_self(mob/living/user)
- if(used)
- to_chat(user, "The pet carrier appears unresponsive.")
- return
- used = TRUE
- to_chat(user, "You're attempting to wake up the creature inside the box...")
- sleep(5 SECONDS)
- var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you want to play as the eldritch horror in service of [user.real_name]?", ROLE_HORROR, null, FALSE, 100)
- if(LAZYLEN(candidates))
- var/mob/dead/observer/C = pick(candidates)
- var/mob/living/simple_animal/horror/H = new /mob/living/simple_animal/horror(get_turf(src))
- H.key = C.key
- H.mind.enslave_mind_to_creator(user)
- H.mind.add_antag_datum(C)
- H.mind.memory += "You are " + span_purple(span_bold("[H.real_name]")) + ", an eldritch horror. Consume souls to evolve.
"
- var/datum/antagonist/horror/S = new
- S.summoner = user.mind
- S.antag_memory += "[user.mind] woke you from your eternal slumber. Aid them in their objectives as a token of gratitude.
"
- H.mind.add_antag_datum(S)
- log_game("[key_name(user)] has summoned [key_name(H)], an eldritch horror.")
- to_chat(user, span_bold("[H.real_name] has awoken into your service!"))
- used = TRUE
- icon_state = "pet_carrier_open"
- sleep(5)
- var/obj/item/horrorsummonhorn/horn = new /obj/item/horrorsummonhorn(get_turf(src))
- horn.summoner = user.mind
- horn.horror = H
- to_chat(user, span_notice("A strange looking [horn] falls out of [src]!"))
- else
- to_chat(user, "The creatures looks at you with one of it's eyes before going back to slumber.")
- used = FALSE
- return
-
-//Summoning horn
-/obj/item/horrorsummonhorn
- name = "old horn"
- desc = "A very old horn. You feel an incredible urge to blow into it."
- icon = 'icons/obj/items_and_weapons.dmi'
- lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/items_righthand.dmi'
- item_state = "horn"
- icon_state = "horn"
- var/datum/mind/summoner
- var/mob/living/simple_animal/horror/horror
- var/cooldown
-
-/obj/item/horrorsummonhorn/examine(mob/user)
- . = ..()
- if(user.mind == summoner)
- to_chat(user, span_purple("Blowing into this horn will recall the horror back to you. Be wary, the horn is loud, and may attract unwanted attention."))
-
-/obj/item/horrorsummonhorn/attack_self(mob/living/user)
- if(cooldown > world.time)
- to_chat(user, span_notice("Take a breath before you blow [src] again."))
- return
- to_chat(user, span_notice("You take a deep breath and prepare to blow into [src]..."))
- if(do_mob(user, src, 10 SECONDS))
- if(cooldown > world.time)
- return
- cooldown = world.time + 10 SECONDS
- to_chat(src, span_notice("You blow the horn..."))
- playsound(loc, "sound/items/airhorn.ogg", 100, 1, 30)
- var/turf/summonplace = get_turf(src)
- sleep(5 SECONDS)
- if(prob(20)) //yeah you're summoning an eldritch horror allright
- new /obj/effect/temp_visual/summon(summonplace)
- sleep(10)
- var/type = pick(typesof(/mob/living/simple_animal/hostile/abomination))
- var/mob/R = new type(summonplace)
- playsound(summonplace, "sound/effects/phasein.ogg", 30)
- summonplace.visible_message(span_danger("[R] emerges!"))
- else
- if(!horror || horror.stat == DEAD)
- summonplace.visible_message(span_danger("But nothing responds to the call!"))
- else
- new /obj/effect/temp_visual/summon(summonplace)
- sleep(10)
- horror.leave_victim()
- horror.forceMove(summonplace)
- playsound(summonplace, "sound/effects/phasein.ogg", 30)
- summonplace.visible_message(span_notice("[horror] appears out of nowhere!"))
- if(user.mind != summoner)
- sleep(2 SECONDS)
- playsound(summonplace, "sound/effects/glassbr2.ogg", 30, 1)
- to_chat(user, span_danger("[src] breaks!"))
- qdel(src)
-/obj/item/horrorsummonhorn/suicide_act(mob/living/user) //"I am the prettiest unicorn that ever was!" ~Spy 2013
- user.visible_message(span_suicide("[user] stabs [user.p_their()] forehead with [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
- return BRUTELOSS
-//Tentacle arm
-/obj/item/horrortentacle
- name = "tentacle"
- desc = "A long, slimy, arm-like appendage."
- icon = 'icons/obj/items_and_weapons.dmi'
- icon_state = "horrortentacle"
- item_state = "tentacle"
- lefthand_file = 'icons/mob/inhands/antag/horror_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/antag/horror_righthand.dmi'
- resistance_flags = ACID_PROOF
- force = 17
- item_flags = ABSTRACT | DROPDEL
- weapon_stats = list(SWING_SPEED = 1, ENCUMBRANCE = 0, ENCUMBRANCE_TIME = 0, REACH = 2, DAMAGE_LOW = 0, DAMAGE_HIGH = 0)
- range_cooldown_mod = 0 //tentacle is designed to hit from range
- hitsound = 'sound/weapons/whip.ogg'
-/obj/item/horrortentacle/Initialize(mapload)
- . = ..()
- ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
-/obj/item/horrortentacle/examine(mob/user)
- . = ..()
- to_chat(user, span_velvet(span_bold("Functions:")))
- to_chat(user, span_velvet("All attacks work up to 2 tiles away."))
- to_chat(user, span_velvet("Help intent: Usual help function of an arm."))
- to_chat(user, span_velvet("Disarm intent: Whips the tentacle, disarming your opponent."))
- to_chat(user, span_velvet("Grab intent: Instant aggressive grab on an opponent. Can also throw them!"))
- to_chat(user, span_velvet("Harm intent: Whips the tentacle, damaging your opponent."))
- to_chat(user, span_velvet("Also functions to pry open unbolted airlocks."))
-/obj/item/horrortentacle/attack(atom/target, mob/living/user)
- if(isliving(target))
- user.Beam(target,"purpletentacle",time=5)
- var/mob/living/L = target
- switch(user.a_intent)
- if(INTENT_HELP)
- L.attack_hand(user)
- return
- if(INTENT_GRAB)
- if(L != user)
- L.grabbedby(user)
- L.grippedby(user, instant = TRUE)
- L.Knockdown(30)
- return
- if(INTENT_DISARM)
- if(iscarbon(L))
- var/mob/living/carbon/C = L
- var/obj/item/I = C.get_active_held_item()
- if(I)
- if(C.dropItemToGround(I))
- playsound(loc, "sound/weapons/whipgrab.ogg", 30)
- target.visible_message(span_danger("[I] is whipped out of [C]'s hand by [user]!"),span_userdanger("A tentacle whips [I] out of your hand!"))
- return
- else
- to_chat(user, span_danger("You can't seem to pry [I] off [C]'s hands!"))
- return
- else
- C.attack_hand(user)
- return
- . = ..()
-/obj/item/horrortentacle/afterattack(atom/target, mob/user, proximity)
- if(isliving(user.pulling) && user.pulling != target)
- var/mob/living/H = user.pulling
- user.visible_message(span_warning("[user] throws [H] with [user.p_their()] [src]!"), span_warning("You throw [H] with [src]."))
- H.throw_at(target, 8, 2)
- H.Knockdown(30)
- return
- if(!proximity)
- return
- if(istype(target, /obj/machinery/door/airlock))
- var/obj/machinery/door/airlock/A = target
- if((!A.requiresID() || A.allowed(user)) && A.hasPower())
- return
- if(A.locked)
- to_chat(user, span_warning("The airlock's bolts prevent it from being forced!"))
- return
- if(A.hasPower())
- user.visible_message(span_warning("[user] jams [src] into the airlock and starts prying it open!"), span_warning("You start forcing the airlock open."),
- span_italics("You hear a metal screeching sound."))
- playsound(A, 'sound/machines/airlock_alien_prying.ogg', 150, 1)
- if(!do_after(user, 10 SECONDS, target = A))
- return
- user.visible_message(span_warning("[user] forces the airlock to open with [user.p_their()] [src]!"), span_warning("You force the airlock to open."),
- span_italics("You hear a metal screeching sound."))
- A.open(2)
- return
- . = ..()
-/obj/item/horrortentacle/suicide_act(mob/user) //this will never be called, since horror stops suicide, but might as well if they get tentacle through other means
- user.visible_message(span_suicide("[src] coils itself around [user] tightly gripping [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!"))
- return (OXYLOSS)
-//Pinpointer
-/obj/screen/alert/status_effect/agent_pinpointer/horror
- name = "Soul locator"
- desc = "Find your target soul."
-/datum/status_effect/agent_pinpointer/horror
- id = "horror_pinpointer"
- minimum_range = 0
- range_fuzz_factor = 0
- tick_interval = 20
- alert_type = /obj/screen/alert/status_effect/agent_pinpointer/horror
-/datum/status_effect/agent_pinpointer/horror/scan_for_target()
- return
-//TRAPPED MIND - when horror takes control over your body, you become a mute trapped mind
-/mob/living/captive_brain
- name = "host brain"
- real_name = "host brain"
- var/datum/action/innate/resist_control/R
- var/mob/living/simple_animal/horror/H
-
-/mob/living/captive_brain/Initialize(mapload, gen=1)
- ..()
- R = new
- R.Grant(src)
-
-/mob/living/captive_brain/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
- if(client)
- if(client.prefs.muted & MUTE_IC)
- to_chat(src, span_danger("You cannot speak in IC (muted)."))
- return
- if(client.handle_spam_prevention(message,MUTE_IC))
- return
- if(ishorror(loc))
- message = sanitize(message)
- if(!message)
- return
- if(stat == 2)
- return say_dead(message)
- to_chat(src, span_alien(span_italics("You whisper silently, \"[message]\"")))
- to_chat(H.victim, span_alien(span_italics("[src] whispers, \"[message]\"")))
- for(var/M in GLOB.dead_mob_list)
- if(isobserver(M))
- var/rendered = span_changeling("[src] transfers: \"[message]\"")
- var/link = FOLLOW_LINK(M, H.victim)
- to_chat(M, "[link] [rendered]")
-
-/mob/living/captive_brain/emote(act, m_type = null, message = null, intentional = FALSE)
- return
-
-/datum/action/innate/resist_control
- name = "Resist control"
- desc = "Try to take back control over your brain. A strong nerve impulse should do it."
- background_icon_state = "bg_ecult"
- icon_icon = 'icons/mob/actions/actions_horror.dmi'
- button_icon_state = "resist_control"
-
-/datum/action/innate/resist_control/Activate()
- var/mob/living/captive_brain/B = owner
- if(B)
- B.try_resist()
-
-/mob/living/captive_brain/resist()
- try_resist()
-
-/mob/living/captive_brain/proc/try_resist()
- var/delay = rand(20 SECONDS,30 SECONDS)
- if(H.horrorupgrades["deep_control"])
- delay += rand(20 SECONDS,30 SECONDS)
- to_chat(src, span_danger("You begin doggedly resisting the parasite's control."))
- to_chat(H.victim, span_danger("You feel the captive mind of [src] begin to resist your control."))
- addtimer(CALLBACK(src, .proc/return_control), delay)
-
-/mob/living/captive_brain/proc/return_control()
- if(!H || !H.controlling)
- return
- to_chat(src, span_userdanger("With an immense exertion of will, you regain control of your body!"))
- to_chat(H.victim, span_danger("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you."))
- H.detatch()
+//ANTAG DATUMS
+/datum/antagonist/horror
+ name = "Horror"
+ show_in_antagpanel = TRUE
+ prevent_roundtype_conversion = FALSE
+ show_name_in_check_antagonists = TRUE
+ show_to_ghosts = TRUE
+ var/datum/mind/summoner
+
+/datum/antagonist/horror/on_gain()
+ . = ..()
+ give_objectives()
+ if(ishorror(owner.current) && owner.current.mind)
+ var/mob/living/simple_animal/horror/H = owner.current
+ H.update_horror_hud()
+
+/datum/antagonist/horror/proc/give_objectives()
+ if(summoner)
+ var/datum/objective/newobjective = new
+ newobjective.explanation_text = "Serve your summoner, [summoner.name]."
+ newobjective.owner = owner
+ newobjective.completed = TRUE
+ objectives += newobjective
+ else
+ //succ some souls
+ var/datum/objective/horrorascend/ascend = new
+ ascend.owner = owner
+ ascend.hor = owner.current
+ ascend.target_amount = rand(5, 8)
+ objectives += ascend
+ ascend.update_explanation_text()
+
+ //looking for antagonist we can assist
+ var/list/possible_targets = list()
+ for(var/datum/mind/M in SSticker.minds)
+ if(M.current && M.current.stat != DEAD)
+ if(ishuman(M.current))
+ if(M.special_role)
+ possible_targets += M
+
+ if(possible_targets.len)
+ var/datum/mind/M = pick(possible_targets)
+ var/datum/objective/protect/O = new
+ O.owner = owner
+ O.target = M
+ O.explanation_text = "Protect and assist \the [M.current.real_name], the [M.assigned_role]."
+ objectives += O
+
+
+ //don't die while you're at is
+ var/datum/objective/survive/survive = new
+ survive.owner = owner
+ objectives += survive
+
+/datum/objective/horrorascend
+ name = "consume souls"
+ var/mob/living/simple_animal/horror/hor
+
+/datum/objective/horrorascend/update_explanation_text()
+ . = ..()
+ explanation_text = "Consume [target_amount] souls."
+
+/datum/objective/horrorascend/check_completion()
+ if(hor && hor.consumed_souls >= target_amount)
+ return TRUE
+ return FALSE
+
+
+//SPAWNER
+/obj/item/horrorspawner
+ name = "suspicious pet carrier"
+ desc = "It contains some sort of creature inside. You can see tentacles sticking out of it."
+ icon = 'icons/obj/pet_carrier.dmi'
+ lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ item_state = "pet_carrier"
+ icon_state = "pet_carrier_occupied"
+ var/used = FALSE
+ color = rgb(130, 105, 160)
+
+/obj/item/horrorspawner/attack_self(mob/living/user)
+ if(used)
+ to_chat(user, "The pet carrier appears unresponsive.")
+ return
+ used = TRUE
+ to_chat(user, "You're attempting to wake up the creature inside the box...")
+ sleep(5 SECONDS)
+ var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you want to play as the eldritch horror in service of [user.real_name]?", ROLE_HORROR, null, FALSE, 100)
+ if(LAZYLEN(candidates))
+ var/mob/dead/observer/C = pick(candidates)
+ var/mob/living/simple_animal/horror/H = new /mob/living/simple_animal/horror(get_turf(src))
+ H.key = C.key
+ H.mind.enslave_mind_to_creator(user)
+ H.mind.add_antag_datum(C)
+ H.mind.memory += "You are " + span_purple(span_bold("[H.real_name]")) + ", an eldritch horror. Consume souls to evolve.
"
+ var/datum/antagonist/horror/S = new
+ S.summoner = user.mind
+ S.antag_memory += "[user.mind] woke you from your eternal slumber. Aid them in their objectives as a token of gratitude.
"
+ H.mind.add_antag_datum(S)
+ log_game("[key_name(user)] has summoned [key_name(H)], an eldritch horror.")
+ to_chat(user, span_bold("[H.real_name] has awoken into your service!"))
+ used = TRUE
+ icon_state = "pet_carrier_open"
+ sleep(5)
+ var/obj/item/horrorsummonhorn/horn = new /obj/item/horrorsummonhorn(get_turf(src))
+ horn.summoner = user.mind
+ horn.horror = H
+ to_chat(user, span_notice("A strange looking [horn] falls out of [src]!"))
+ else
+ to_chat(user, "The creatures looks at you with one of it's eyes before going back to slumber.")
+ used = FALSE
+ return
+
+//Summoning horn
+/obj/item/horrorsummonhorn
+ name = "old horn"
+ desc = "A very old horn. You feel an incredible urge to blow into it."
+ icon = 'icons/obj/items_and_weapons.dmi'
+ lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ item_state = "horn"
+ icon_state = "horn"
+ var/datum/mind/summoner
+ var/mob/living/simple_animal/horror/horror
+ var/cooldown
+
+/obj/item/horrorsummonhorn/examine(mob/user)
+ . = ..()
+ if(user.mind == summoner)
+ to_chat(user, span_purple("Blowing into this horn will recall the horror back to you. Be wary, the horn is loud, and may attract unwanted attention."))
+
+/obj/item/horrorsummonhorn/attack_self(mob/living/user)
+ if(cooldown > world.time)
+ to_chat(user, span_notice("Take a breath before you blow [src] again."))
+ return
+ to_chat(user, span_notice("You take a deep breath and prepare to blow into [src]..."))
+ if(do_mob(user, src, 10 SECONDS))
+ if(cooldown > world.time)
+ return
+ cooldown = world.time + 10 SECONDS
+ to_chat(src, span_notice("You blow the horn..."))
+ playsound(loc, "sound/items/airhorn.ogg", 100, 1, 30)
+ var/turf/summonplace = get_turf(src)
+ sleep(5 SECONDS)
+ if(prob(20)) //yeah you're summoning an eldritch horror allright
+ new /obj/effect/temp_visual/summon(summonplace)
+ sleep(10)
+ var/type = pick(typesof(/mob/living/simple_animal/hostile/abomination))
+ var/mob/R = new type(summonplace)
+ playsound(summonplace, "sound/effects/phasein.ogg", 30)
+ summonplace.visible_message(span_danger("[R] emerges!"))
+ else
+ if(!horror || horror.stat == DEAD)
+ summonplace.visible_message(span_danger("But nothing responds to the call!"))
+ else
+ new /obj/effect/temp_visual/summon(summonplace)
+ sleep(10)
+ horror.leave_victim()
+ horror.forceMove(summonplace)
+ playsound(summonplace, "sound/effects/phasein.ogg", 30)
+ summonplace.visible_message(span_notice("[horror] appears out of nowhere!"))
+ if(user.mind != summoner)
+ sleep(2 SECONDS)
+ playsound(summonplace, "sound/effects/glassbr2.ogg", 30, 1)
+ to_chat(user, span_danger("[src] breaks!"))
+ qdel(src)
+/obj/item/horrorsummonhorn/suicide_act(mob/living/user) //"I am the prettiest unicorn that ever was!" ~Spy 2013
+ user.visible_message(span_suicide("[user] stabs [user.p_their()] forehead with [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
+ return BRUTELOSS
+//Tentacle arm
+/obj/item/horrortentacle
+ name = "tentacle"
+ desc = "A long, slimy, arm-like appendage."
+ icon = 'icons/obj/items_and_weapons.dmi'
+ icon_state = "horrortentacle"
+ item_state = "tentacle"
+ lefthand_file = 'icons/mob/inhands/antag/horror_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/antag/horror_righthand.dmi'
+ resistance_flags = ACID_PROOF
+ force = 17
+ item_flags = ABSTRACT | DROPDEL
+ weapon_stats = list(SWING_SPEED = 1, ENCUMBRANCE = 0, ENCUMBRANCE_TIME = 0, REACH = 2, DAMAGE_LOW = 0, DAMAGE_HIGH = 0)
+ range_cooldown_mod = 0 //tentacle is designed to hit from range
+ hitsound = 'sound/weapons/whip.ogg'
+/obj/item/horrortentacle/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
+/obj/item/horrortentacle/examine(mob/user)
+ . = ..()
+ to_chat(user, span_velvet(span_bold("Functions:")))
+ to_chat(user, span_velvet("All attacks work up to 2 tiles away."))
+ to_chat(user, span_velvet("Help intent: Usual help function of an arm."))
+ to_chat(user, span_velvet("Disarm intent: Whips the tentacle, disarming your opponent."))
+ to_chat(user, span_velvet("Grab intent: Instant aggressive grab on an opponent. Can also throw them!"))
+ to_chat(user, span_velvet("Harm intent: Whips the tentacle, damaging your opponent."))
+ to_chat(user, span_velvet("Also functions to pry open unbolted airlocks."))
+/obj/item/horrortentacle/attack(atom/target, mob/living/user)
+ if(isliving(target))
+ user.Beam(target,"purpletentacle",time=5)
+ var/mob/living/L = target
+ switch(user.a_intent)
+ if(INTENT_HELP)
+ L.attack_hand(user)
+ return
+ if(INTENT_GRAB)
+ if(L != user)
+ L.grabbedby(user)
+ L.grippedby(user, instant = TRUE)
+ L.Knockdown(30)
+ return
+ if(INTENT_DISARM)
+ if(iscarbon(L))
+ var/mob/living/carbon/C = L
+ var/obj/item/I = C.get_active_held_item()
+ if(I)
+ if(C.dropItemToGround(I))
+ playsound(loc, "sound/weapons/whipgrab.ogg", 30)
+ target.visible_message(span_danger("[I] is whipped out of [C]'s hand by [user]!"),span_userdanger("A tentacle whips [I] out of your hand!"))
+ return
+ else
+ to_chat(user, span_danger("You can't seem to pry [I] off [C]'s hands!"))
+ return
+ else
+ C.attack_hand(user)
+ return
+ . = ..()
+/obj/item/horrortentacle/afterattack(atom/target, mob/user, proximity)
+ if(isliving(user.pulling) && user.pulling != target)
+ var/mob/living/H = user.pulling
+ user.visible_message(span_warning("[user] throws [H] with [user.p_their()] [src]!"), span_warning("You throw [H] with [src]."))
+ H.throw_at(target, 8, 2)
+ H.Knockdown(30)
+ return
+ if(!proximity)
+ return
+ if(istype(target, /obj/machinery/door/airlock))
+ var/obj/machinery/door/airlock/A = target
+ if((!A.requiresID() || A.allowed(user)) && A.hasPower())
+ return
+ if(A.locked)
+ to_chat(user, span_warning("The airlock's bolts prevent it from being forced!"))
+ return
+ if(A.hasPower())
+ user.visible_message(span_warning("[user] jams [src] into the airlock and starts prying it open!"), span_warning("You start forcing the airlock open."),
+ span_italics("You hear a metal screeching sound."))
+ playsound(A, 'sound/machines/airlock_alien_prying.ogg', 150, 1)
+ if(!do_after(user, 10 SECONDS, target = A))
+ return
+ user.visible_message(span_warning("[user] forces the airlock to open with [user.p_their()] [src]!"), span_warning("You force the airlock to open."),
+ span_italics("You hear a metal screeching sound."))
+ A.open(2)
+ return
+ . = ..()
+/obj/item/horrortentacle/suicide_act(mob/user) //this will never be called, since horror stops suicide, but might as well if they get tentacle through other means
+ user.visible_message(span_suicide("[src] coils itself around [user] tightly gripping [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!"))
+ return (OXYLOSS)
+//Pinpointer
+/obj/screen/alert/status_effect/agent_pinpointer/horror
+ name = "Soul locator"
+ desc = "Find your target soul."
+/datum/status_effect/agent_pinpointer/horror
+ id = "horror_pinpointer"
+ minimum_range = 0
+ range_fuzz_factor = 0
+ tick_interval = 20
+ alert_type = /obj/screen/alert/status_effect/agent_pinpointer/horror
+/datum/status_effect/agent_pinpointer/horror/scan_for_target()
+ return
+//TRAPPED MIND - when horror takes control over your body, you become a mute trapped mind
+/mob/living/captive_brain
+ name = "host brain"
+ real_name = "host brain"
+ var/datum/action/innate/resist_control/R
+ var/mob/living/simple_animal/horror/H
+
+/mob/living/captive_brain/Initialize(mapload, gen=1)
+ ..()
+ R = new
+ R.Grant(src)
+
+/mob/living/captive_brain/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+ if(client)
+ if(client.prefs.muted & MUTE_IC)
+ to_chat(src, span_danger("You cannot speak in IC (muted)."))
+ return
+ if(client.handle_spam_prevention(message,MUTE_IC))
+ return
+ if(ishorror(loc))
+ message = sanitize(message)
+ if(!message)
+ return
+ if(stat == 2)
+ return say_dead(message)
+ to_chat(src, span_alien(span_italics("You whisper silently, \"[message]\"")))
+ to_chat(H.victim, span_alien(span_italics("[src] whispers, \"[message]\"")))
+ for(var/M in GLOB.dead_mob_list)
+ if(isobserver(M))
+ var/rendered = span_changeling("[src] transfers: \"[message]\"")
+ var/link = FOLLOW_LINK(M, H.victim)
+ to_chat(M, "[link] [rendered]")
+
+/mob/living/captive_brain/emote(act, m_type = null, message = null, intentional = FALSE)
+ return
+
+/datum/action/innate/resist_control
+ name = "Resist control"
+ desc = "Try to take back control over your brain. A strong nerve impulse should do it."
+ background_icon_state = "bg_ecult"
+ icon_icon = 'icons/mob/actions/actions_horror.dmi'
+ button_icon_state = "resist_control"
+
+/datum/action/innate/resist_control/Activate()
+ var/mob/living/captive_brain/B = owner
+ if(B)
+ B.try_resist()
+
+/mob/living/captive_brain/resist()
+ try_resist()
+
+/mob/living/captive_brain/proc/try_resist()
+ var/delay = rand(20 SECONDS,30 SECONDS)
+ if(H.horrorupgrades["deep_control"])
+ delay += rand(20 SECONDS,30 SECONDS)
+ to_chat(src, span_danger("You begin doggedly resisting the parasite's control."))
+ to_chat(H.victim, span_danger("You feel the captive mind of [src] begin to resist your control."))
+ addtimer(CALLBACK(src, .proc/return_control), delay)
+
+/mob/living/captive_brain/proc/return_control()
+ if(!H || !H.controlling)
+ return
+ to_chat(src, span_userdanger("With an immense exertion of will, you regain control of your body!"))
+ to_chat(H.victim, span_danger("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you."))
+ H.detatch()
diff --git a/code/modules/antagonists/horror/horror_html.dm b/code/modules/antagonists/horror/horror_html.dm
index 4f70c856805d..9cf8882f8015 100644
--- a/code/modules/antagonists/horror/horror_html.dm
+++ b/code/modules/antagonists/horror/horror_html.dm
@@ -1,102 +1,102 @@
-/mob/living/simple_animal/horror/proc/get_html_template(content)
- var/html = {"
-
-
- Horror Chemicals
-
-
-
-
-
-
-
-
-
- [content]
-
"}
+/mob/living/simple_animal/horror/proc/get_html_template(content)
+ var/html = {"
+
+
+ Horror Chemicals
+
+
+
+
+
+
+
+
+
+ [content]
+
"}
return html
\ No newline at end of file
diff --git a/code/modules/antagonists/horror/horror_mutate.dm b/code/modules/antagonists/horror/horror_mutate.dm
index 2eaa8e93af92..97cb19c20572 100644
--- a/code/modules/antagonists/horror/horror_mutate.dm
+++ b/code/modules/antagonists/horror/horror_mutate.dm
@@ -1,97 +1,97 @@
-// Horror mutation menu
-// Totally not a copypaste of darkspawn menu, not a copypaste of cellular emporium, i swear. Edit: now looks like guardianbuilder too
-
-/mob/living/simple_animal/horror/proc/has_ability(typepath)
- for(var/datum/action/innate/horror/ability in horrorabilities)
- if(istype(ability, typepath))
- return ability
- return
-
-/mob/living/simple_animal/horror/proc/add_ability(typepath)
- if(has_ability(typepath))
- return
- var/datum/action/innate/horror/action = new typepath
- action.B = src
- horrorabilities += action
- RefreshAbilities()
- to_chat(src, span_velvet("You have mutated the [action.name]."))
- available_points = max(0, available_points - action.soul_price)
- return TRUE
-
-/mob/living/simple_animal/horror/proc/has_upgrade(id)
- return horrorupgrades[id]
-
-/mob/living/simple_animal/horror/proc/add_upgrade(id)
- if(has_upgrade(id))
- return
- for(var/V in subtypesof(/datum/horror_upgrade))
- var/datum/horror_upgrade/_U = V
- if(initial(_U.id) == id)
- var/datum/horror_upgrade/U = new _U(src)
- horrorupgrades[id] = TRUE
- to_chat(src, "You have adapted the \"[U.name]\" upgrade.")
- available_points = max(0, available_points - U.soul_price)
- U.unlock()
-
-//mutation menu, 100% ripoff of psiweb, pls don't sue
-
-/mob/living/simple_animal/horror/ui_state(mob/user)
- return GLOB.always_state
-
-/mob/living/simple_animal/horror/ui_interact(mob/user, datum/tgui/ui)
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "HorrorMutate", "Horror Mutation")
- ui.open()
-
-/mob/living/simple_animal/horror/ui_data(mob/user)
- var/list/data = list()
-
- data["available_points"] = "[available_points] | [consumed_souls] consumed souls total"
-
- var/list/abilities = list()
- var/list/upgrades = list()
-
- for(var/path in subtypesof(/datum/action/innate/horror))
- var/datum/action/innate/horror/ability = path
-
- if(initial(ability.blacklisted))
- continue
-
- var/list/AL = list()
- AL["name"] = initial(ability.name)
- AL["typepath"] = path
- AL["desc"] = initial(ability.desc)
- AL["soul_cost"] = initial(ability.soul_price)
- AL["owned"] = has_ability(path)
- AL["can_purchase"] = !AL["owned"] && available_points >= initial(ability.soul_price)
-
- abilities += list(AL)
-
- data["abilities"] = abilities
-
- for(var/path in subtypesof(/datum/horror_upgrade))
- var/datum/horror_upgrade/upgrade = path
-
- var/list/DE = list()
- DE["name"] = initial(upgrade.name)
- DE["id"] = initial(upgrade.id)
- DE["desc"] = initial(upgrade.desc)
- DE["soul_cost"] = initial(upgrade.soul_price)
- DE["owned"] = has_upgrade(initial(upgrade.id))
- DE["can_purchase"] = !DE["owned"] && available_points >= initial(upgrade.soul_price)
-
- upgrades += list(DE)
-
- data["upgrades"] = upgrades
-
- return data
-
-/mob/living/simple_animal/horror/ui_act(action, params)
- if(..())
- return
- switch(action)
- if("unlock")
- add_ability(params["typepath"])
- if("upgrade")
+// Horror mutation menu
+// Totally not a copypaste of darkspawn menu, not a copypaste of cellular emporium, i swear. Edit: now looks like guardianbuilder too
+
+/mob/living/simple_animal/horror/proc/has_ability(typepath)
+ for(var/datum/action/innate/horror/ability in horrorabilities)
+ if(istype(ability, typepath))
+ return ability
+ return
+
+/mob/living/simple_animal/horror/proc/add_ability(typepath)
+ if(has_ability(typepath))
+ return
+ var/datum/action/innate/horror/action = new typepath
+ action.B = src
+ horrorabilities += action
+ RefreshAbilities()
+ to_chat(src, span_velvet("You have mutated the [action.name]."))
+ available_points = max(0, available_points - action.soul_price)
+ return TRUE
+
+/mob/living/simple_animal/horror/proc/has_upgrade(id)
+ return horrorupgrades[id]
+
+/mob/living/simple_animal/horror/proc/add_upgrade(id)
+ if(has_upgrade(id))
+ return
+ for(var/V in subtypesof(/datum/horror_upgrade))
+ var/datum/horror_upgrade/_U = V
+ if(initial(_U.id) == id)
+ var/datum/horror_upgrade/U = new _U(src)
+ horrorupgrades[id] = TRUE
+ to_chat(src, "You have adapted the \"[U.name]\" upgrade.")
+ available_points = max(0, available_points - U.soul_price)
+ U.unlock()
+
+//mutation menu, 100% ripoff of psiweb, pls don't sue
+
+/mob/living/simple_animal/horror/ui_state(mob/user)
+ return GLOB.always_state
+
+/mob/living/simple_animal/horror/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "HorrorMutate", "Horror Mutation")
+ ui.open()
+
+/mob/living/simple_animal/horror/ui_data(mob/user)
+ var/list/data = list()
+
+ data["available_points"] = "[available_points] | [consumed_souls] consumed souls total"
+
+ var/list/abilities = list()
+ var/list/upgrades = list()
+
+ for(var/path in subtypesof(/datum/action/innate/horror))
+ var/datum/action/innate/horror/ability = path
+
+ if(initial(ability.blacklisted))
+ continue
+
+ var/list/AL = list()
+ AL["name"] = initial(ability.name)
+ AL["typepath"] = path
+ AL["desc"] = initial(ability.desc)
+ AL["soul_cost"] = initial(ability.soul_price)
+ AL["owned"] = has_ability(path)
+ AL["can_purchase"] = !AL["owned"] && available_points >= initial(ability.soul_price)
+
+ abilities += list(AL)
+
+ data["abilities"] = abilities
+
+ for(var/path in subtypesof(/datum/horror_upgrade))
+ var/datum/horror_upgrade/upgrade = path
+
+ var/list/DE = list()
+ DE["name"] = initial(upgrade.name)
+ DE["id"] = initial(upgrade.id)
+ DE["desc"] = initial(upgrade.desc)
+ DE["soul_cost"] = initial(upgrade.soul_price)
+ DE["owned"] = has_upgrade(initial(upgrade.id))
+ DE["can_purchase"] = !DE["owned"] && available_points >= initial(upgrade.soul_price)
+
+ upgrades += list(DE)
+
+ data["upgrades"] = upgrades
+
+ return data
+
+/mob/living/simple_animal/horror/ui_act(action, params)
+ if(..())
+ return
+ switch(action)
+ if("unlock")
+ add_ability(params["typepath"])
+ if("upgrade")
add_upgrade(params["id"])
\ No newline at end of file
diff --git a/code/modules/events/horror.dm b/code/modules/events/horror.dm
index dd42420f8477..afa1182ff04e 100644
--- a/code/modules/events/horror.dm
+++ b/code/modules/events/horror.dm
@@ -1,34 +1,34 @@
-/datum/round_event_control/horror
- name = "Spawn Eldritch Horror"
- typepath = /datum/round_event/ghost_role/horror
- max_occurrences = 2
- min_players = 15
- earliest_start = 20 MINUTES
-
-/datum/round_event/ghost_role/horror
- minimum_required = 1
- role_name = "horror"
- fakeable = FALSE
-
-/datum/round_event/ghost_role/horror/spawn_role()
- var/list/candidates = get_candidates(ROLE_HORROR, null, ROLE_HORROR)
- if(!candidates.len)
- return NOT_ENOUGH_PLAYERS
-
- var/mob/dead/selected = pick_n_take(candidates)
-
- var/datum/mind/player_mind = new /datum/mind(selected.key)
- player_mind.active = 1
- if(!GLOB.generic_event_spawns)
- return MAP_ERROR
- var/mob/living/simple_animal/horror/S = new /mob/living/simple_animal/horror(get_turf(pick(GLOB.generic_event_spawns)))
- player_mind.transfer_to(S)
- player_mind.assigned_role = "Eldritch Horror"
- player_mind.special_role = "Eldritch Horror"
- player_mind.add_antag_datum(/datum/antagonist/horror)
- to_chat(S, S.playstyle_string)
- SEND_SOUND(S, sound('sound/hallucinations/growl2.ogg'))
- message_admins("[ADMIN_LOOKUPFLW(S)] has been made into an eldritch horror by an event.")
- log_game("[key_name(S)] was spawned as an eldritch horror by an event.")
- spawned_mobs += S
+/datum/round_event_control/horror
+ name = "Spawn Eldritch Horror"
+ typepath = /datum/round_event/ghost_role/horror
+ max_occurrences = 2
+ min_players = 15
+ earliest_start = 20 MINUTES
+
+/datum/round_event/ghost_role/horror
+ minimum_required = 1
+ role_name = "horror"
+ fakeable = FALSE
+
+/datum/round_event/ghost_role/horror/spawn_role()
+ var/list/candidates = get_candidates(ROLE_HORROR, null, ROLE_HORROR)
+ if(!candidates.len)
+ return NOT_ENOUGH_PLAYERS
+
+ var/mob/dead/selected = pick_n_take(candidates)
+
+ var/datum/mind/player_mind = new /datum/mind(selected.key)
+ player_mind.active = 1
+ if(!GLOB.generic_event_spawns)
+ return MAP_ERROR
+ var/mob/living/simple_animal/horror/S = new /mob/living/simple_animal/horror(get_turf(pick(GLOB.generic_event_spawns)))
+ player_mind.transfer_to(S)
+ player_mind.assigned_role = "Eldritch Horror"
+ player_mind.special_role = "Eldritch Horror"
+ player_mind.add_antag_datum(/datum/antagonist/horror)
+ to_chat(S, S.playstyle_string)
+ SEND_SOUND(S, sound('sound/hallucinations/growl2.ogg'))
+ message_admins("[ADMIN_LOOKUPFLW(S)] has been made into an eldritch horror by an event.")
+ log_game("[key_name(S)] was spawned as an eldritch horror by an event.")
+ spawned_mobs += S
return SUCCESSFUL_SPAWN
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 801eabf75825..95b109cc1439 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -156,6 +156,10 @@ GLOBAL_LIST_EMPTY(mentor_races)
///Bitflag that controls what in game ways can select this species as a spawnable source. Think magic mirror and pride mirror, slime extract, ERT etc, see defines in __DEFINES/mobs.dm, defaults to NONE, so people actually have to think about it
var/changesource_flags = NONE
+
+ //The component to add when swimming
+ var/swimming_component = /datum/component/swimming
+
///////////
// PROCS //
///////////
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 090aba2eb7a7..aedbece3e919 100644
--- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
@@ -25,6 +25,7 @@
inert_mutation = SHOCKTOUCH
hair_color = "fixedmutcolor"
hair_alpha = 140
+ swimming_component = /datum/component/swimming/ethereal
var/current_color
var/EMPeffect = FALSE
var/emageffect = FALSE
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 5bf71c26daff..3f26a17452ce 100644
--- a/code/modules/mob/living/carbon/human/species_types/felinid.dm
+++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm
@@ -12,6 +12,7 @@
mutantears = /obj/item/organ/ears/cat
mutanttail = /obj/item/organ/tail/cat
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT
+ swimming_component = /datum/component/swimming/felinid
species_language_holder = /datum/language_holder/felinid
/datum/species/human/felinid/qualifies_for_rank(rank, list/features)
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 53b203f5dfb5..67d85cafbf41 100644
--- a/code/modules/mob/living/carbon/human/species_types/golems.dm
+++ b/code/modules/mob/living/carbon/human/species_types/golems.dm
@@ -22,6 +22,7 @@
// changes, only the Random Golem type can be chosen
limbs_id = "golem"
fixed_mut_color = "aaa"
+ swimming_component = /datum/component/swimming/golem
var/info_text = "As an Iron Golem, you don't have any special traits."
var/random_eligible = TRUE //If false, the golem subtype can't be made through golem mutation toxin
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 a9a1698abf49..fb67c03d956f 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -17,6 +17,7 @@
burnmod = 0.5 // = 1/2x generic burn damage
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT
species_language_holder = /datum/language_holder/jelly
+ swimming_component = /datum/component/swimming/dissolve
/datum/species/jelly/on_species_loss(mob/living/carbon/C)
if(regenerate_limbs)
@@ -738,4 +739,4 @@
to_chat(H, span_notice("You connect [target]'s mind to your slime link!"))
else
to_chat(H, span_warning("You can't seem to link [target]'s mind..."))
- to_chat(target, span_warning("The foreign presence leaves your mind."))
\ No newline at end of file
+ to_chat(target, span_warning("The foreign presence leaves your mind."))
diff --git a/code/modules/pool/components/swimming.dm b/code/modules/pool/components/swimming.dm
new file mode 100644
index 000000000000..d08c57582032
--- /dev/null
+++ b/code/modules/pool/components/swimming.dm
@@ -0,0 +1,131 @@
+//Component used to show that a mob is swimming, and force them to swim a lil' bit slower. Components are actually really based!
+
+/datum/component/swimming
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+ var/lengths = 0 //How far have we swum?
+ var/lengths_for_bonus = 25 //If you swim this much, you'll count as having "excercised" and thus gain a buff.
+ var/list/species = list()
+ var/drowning = FALSE
+ var/ticks_drowned = 0
+ var/slowdown = 4
+ var/bob_height_min = 2
+ var/bob_height_max = 5
+ var/bob_tick = 0
+
+/datum/component/swimming/Initialize()
+ . = ..()
+ if(!isliving(parent))
+ message_admins("Swimming component erroneously added to a non-living mob ([parent]).")
+ return INITIALIZE_HINT_QDEL //Only mobs can swim, like Ian...
+ var/mob/M = parent
+ M.visible_message("[parent] starts splashing around in the water!")
+ M.add_movespeed_modifier(MOVESPEED_ID_SWIMMING, update=TRUE, priority=50, multiplicative_slowdown=slowdown, movetypes=GROUND)
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/onMove)
+ RegisterSignal(parent, COMSIG_CARBON_SPECIESCHANGE, .proc/onChangeSpecies)
+ RegisterSignal(parent, COMSIG_MOB_ATTACK_HAND_TURF, .proc/try_leave_pool)
+ START_PROCESSING(SSprocessing, src)
+ enter_pool()
+
+/datum/component/swimming/proc/onMove()
+ lengths ++
+ if(lengths > lengths_for_bonus)
+ var/mob/living/L = parent
+ SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "exercise", /datum/mood_event/exercise)
+ L.apply_status_effect(STATUS_EFFECT_EXERCISED) //Swimming is really good excercise!
+ lengths = 0
+
+//Damn edge cases
+/datum/component/swimming/proc/onChangeSpecies()
+ var/mob/living/carbon/C = parent
+ var/component_type = /datum/component/swimming
+ if(istype(C) && C?.dna?.species)
+ component_type = C.dna.species.swimming_component
+ var/mob/M = parent
+ RemoveComponent()
+ M.AddComponent(component_type)
+
+/datum/component/swimming/proc/try_leave_pool(datum/source, turf/clicked_turf)
+ var/mob/living/L = parent
+ if(!L.can_interact_with(clicked_turf))
+ return
+ if(is_blocked_turf(clicked_turf))
+ return
+ if(istype(clicked_turf, /turf/open/indestructible/sound/pool))
+ return
+ to_chat(parent, "You start to climb out of the pool...")
+ if(do_after(parent, 1 SECONDS, target=clicked_turf))
+ L.forceMove(clicked_turf)
+ L.visible_message("[parent] climbs out of the pool.")
+ RemoveComponent()
+
+/datum/component/swimming/UnregisterFromParent()
+ exit_pool()
+ var/mob/M = parent
+ if(drowning)
+ stop_drowning(M)
+ if(bob_tick)
+ M.pixel_y = 0
+ M.remove_movespeed_modifier(MOVESPEED_ID_SWIMMING)
+ UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
+ UnregisterSignal(parent, COMSIG_CARBON_SPECIESCHANGE)
+ UnregisterSignal(parent, COMSIG_MOB_ATTACK_HAND_TURF)
+ STOP_PROCESSING(SSprocessing, src)
+ return ..()
+
+/datum/component/swimming/process()
+ var/mob/living/L = parent
+ var/floating = FALSE
+ var/obj/item/twohanded/required/pool/helditem = L.get_active_held_item()
+ if(istype(helditem) && helditem.wielded)
+ bob_tick ++
+ animate(L, time=9.5, pixel_y = (L.pixel_y == bob_height_max) ? bob_height_min : bob_height_max)
+ floating = TRUE
+ else
+ if(bob_tick)
+ animate(L, time=5, pixel_y = 0)
+ bob_tick = 0
+ if(!floating && is_drowning(L))
+ if(!drowning)
+ start_drowning(L)
+ drowning = TRUE
+ drown(L)
+ else if(drowning)
+ stop_drowning(L)
+ drowning = FALSE
+ L.adjust_fire_stacks(-1)
+
+/datum/component/swimming/proc/is_drowning(mob/living/victim)
+ var/obj/item/twohanded/required/pool/helditem = victim.get_active_held_item()
+ if(istype(helditem) && helditem.wielded)
+ return
+ return ((!(victim.mobility_flags & MOBILITY_STAND)) && (!HAS_TRAIT(victim, TRAIT_NOBREATH)))
+
+/datum/component/swimming/proc/drown(mob/living/victim)
+ if(victim.losebreath < 1)
+ victim.losebreath += 1
+ ticks_drowned ++
+ if(prob(20))
+ victim.emote("cough")
+ else if(prob(25))
+ victim.emote("gasp")
+ if(ticks_drowned > 20)
+ if(prob(10))
+ victim.visible_message("[victim] falls unconcious for a moment!")
+ victim.Unconscious(10)
+
+/datum/component/swimming/proc/start_drowning(mob/living/victim)
+ to_chat(victim, "Water fills your lungs and mouth, you can't breathe!")
+ ADD_TRAIT(victim, TRAIT_MUTE, "pool")
+
+/datum/component/swimming/proc/stop_drowning(mob/living/victim)
+ victim.emote("cough")
+ to_chat(victim, "You cough up the last of the water, regaining your ability to speak and breathe clearly!")
+ REMOVE_TRAIT(victim, TRAIT_MUTE, "pool")
+ ticks_drowned = 0
+
+/datum/component/swimming/proc/enter_pool()
+ return
+
+//Essentially the same as remove component, but easier for overiding
+/datum/component/swimming/proc/exit_pool()
+ return
diff --git a/code/modules/pool/components/swimming_dissolve.dm b/code/modules/pool/components/swimming_dissolve.dm
new file mode 100644
index 000000000000..e92f1009afce
--- /dev/null
+++ b/code/modules/pool/components/swimming_dissolve.dm
@@ -0,0 +1,28 @@
+/datum/component/swimming/dissolve
+ var/start_alpha = 0
+
+/datum/component/swimming/dissolve/enter_pool()
+ var/mob/living/L = parent
+ start_alpha = L.alpha
+ to_chat(parent, "You begin disolving into the pool, get out fast!")
+
+/datum/component/swimming/dissolve/process()
+ ..()
+ var/mob/living/L = parent
+ var/mob/living/carbon/human/H = L
+ if(istype(H))
+ if(H.wear_suit && istype(H.wear_suit, /obj/item/clothing))
+ var/obj/item/clothing/CH = H.wear_suit
+ if (CH.clothing_flags & THICKMATERIAL)
+ return
+ L.adjustCloneLoss(1)
+ L.alpha = ((L.health-HEALTH_THRESHOLD_DEAD) / (L.maxHealth - HEALTH_THRESHOLD_DEAD)) * 255
+ if(L.stat == DEAD)
+ L.visible_message("[L] dissolves into the pool!")
+ var/obj/item/organ/brain = L.getorgan(/obj/item/organ/brain)
+ brain.Remove(L) //Maybe making them completely unrecoverable is too far
+ brain.forceMove(get_turf(L))
+ qdel(L)
+
+/datum/component/swimming/dissolve/exit_pool()
+ animate(parent, alpha=start_alpha, time=20)
diff --git a/code/modules/pool/components/swimming_ethereal.dm b/code/modules/pool/components/swimming_ethereal.dm
new file mode 100644
index 000000000000..13f1877e9d3b
--- /dev/null
+++ b/code/modules/pool/components/swimming_ethereal.dm
@@ -0,0 +1,11 @@
+/datum/component/swimming/ethereal/enter_pool()
+ var/mob/living/L = parent
+ L.visible_message("Sparks of energy begin coursing around the pool!")
+
+/datum/component/swimming/ethereal/process()
+ ..()
+ var/mob/living/L = parent
+ if(prob(2) && L.nutrition > NUTRITION_LEVEL_FED)
+ L.adjust_nutrition(-50)
+ tesla_zap(L, 7, 2000, TESLA_MOB_STUN)
+ playsound(L, 'sound/machines/defib_zap.ogg', 50, TRUE)
diff --git a/code/modules/pool/components/swimming_felinid.dm b/code/modules/pool/components/swimming_felinid.dm
new file mode 100644
index 000000000000..b6cde0425452
--- /dev/null
+++ b/code/modules/pool/components/swimming_felinid.dm
@@ -0,0 +1,27 @@
+/datum/component/swimming/felinid/enter_pool()
+ var/mob/living/L = parent
+ L.emote("scream")
+ to_chat(parent, "You get covered in water and start panicking!")
+
+/datum/component/swimming/felinid/process()
+ ..()
+ var/mob/living/L = parent
+ var/obj/item/twohanded/required/pool/helditem = L.get_active_held_item()
+ if(istype(helditem) && helditem.wielded)
+ return
+ switch(rand(1, 100))
+ if(1 to 4)
+ to_chat(parent, "You can't touch the bottom!")
+ L.emote("scream")
+ if(5 to 7)
+ if(L.confused < 5)
+ L.confused += 1
+ if(8 to 12)
+ L.Jitter(10)
+ if(13 to 14)
+ shake_camera(L, 15, 1)
+ L.emote("whimper")
+ L.Paralyze(10)
+ to_chat(parent, "You feel like you are never going to get out...")
+ if(15 to 17)
+ L.emote("cry")
diff --git a/code/modules/pool/components/swimming_golem.dm b/code/modules/pool/components/swimming_golem.dm
new file mode 100644
index 000000000000..669d2aa097ff
--- /dev/null
+++ b/code/modules/pool/components/swimming_golem.dm
@@ -0,0 +1,9 @@
+/datum/component/swimming/golem/enter_pool()
+ var/mob/living/M = parent
+ M.Paralyze(60)
+ M.visible_message("[M] crashed violently into the ground!",
+ "You sink like a rock!")
+ playsound(get_turf(M), 'sound/effects/picaxe1.ogg')
+
+/datum/component/swimming/golem/is_drowning()
+ return FALSE
diff --git a/code/modules/pool/pool.dm b/code/modules/pool/pool.dm
new file mode 100644
index 000000000000..530ee5eac5b2
--- /dev/null
+++ b/code/modules/pool/pool.dm
@@ -0,0 +1,294 @@
+//Simple pool behaviour. Sprites by Cdey!
+
+/**
+How to pool!
+Place pool turfs on the inside of your pool. This is where you swim. Pool end caps to cap it off on the north face
+Place pool border decals around the pool so it doesn't look weird
+Place a pool ladder at the top of the pool so that it leads to a normal tile (or else it'll be hard to get out of the pool.)
+Place a pool filter somewhere in the pool if you want people to be able to modify the pool's settings (Temperature) or dump reagents into the pool (which'll change the pool's colour)
+*/
+
+/obj/effect/overlay/poolwater
+ name = "Pool water"
+ icon = 'icons/obj/pool.dmi'
+ icon_state = "water"
+ anchored = TRUE
+ layer = ABOVE_ALL_MOB_LAYER
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+/turf/open/indestructible/sound/pool
+ name = "Swimming pool"
+ desc = "A fun place where you go to swim! Drag and drop yourself onto it to climb in..."
+ icon = 'icons/obj/pool.dmi'
+ icon_state = "pool"
+ sound = 'sound/effects/splash.ogg'
+ var/id = null //Set me if you don't want the pool and the pump to be in the same area, or you have multiple pools per area.
+ var/obj/effect/water_overlay = null
+
+/turf/open/indestructible/sound/pool/end
+ icon_state = "poolwall"
+
+/turf/open/indestructible/sound/pool/Initialize(mapload)
+ . = ..()
+ water_overlay = new /obj/effect/overlay/poolwater(get_turf(src))
+
+/turf/open/indestructible/sound/pool/proc/set_colour(colour)
+ water_overlay.color = colour
+
+/turf/open/indestructible/sound/pool/end/ChangeTurf(path, list/new_baseturfs, flags)
+ if(water_overlay)
+ qdel(water_overlay)
+ . = ..()
+
+/turf/open/CanPass(atom/movable/mover, turf/target)
+ var/datum/component/swimming/S = mover.GetComponent(/datum/component/swimming) //If you're swimming around, you don't really want to stop swimming just like that do you?
+ if(S)
+ return FALSE //If you're swimming, you can't swim into a regular turf, y'dig?
+ . = ..()
+
+/turf/open/indestructible/sound/pool/CanPass(atom/movable/mover, turf/target)
+ if(mover.throwing)
+ return TRUE
+ var/datum/component/swimming/S = mover.GetComponent(/datum/component/swimming) //You can't get in the pool unless you're swimming.
+ return (isliving(mover)) ? S : ..() //So you can do stuff like throw beach balls around the pool!
+
+/turf/open/indestructible/sound/pool/Entered(atom/movable/AM)
+ . = ..()
+ SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, 2)
+ if(isliving(AM))
+ var/datum/component/swimming/S = AM.GetComponent(/datum/component/swimming) //You can't get in the pool unless you're swimming.
+ if(!S)
+ var/mob/living/carbon/C = AM
+ var/component_type = /datum/component/swimming
+ if(istype(C) && C?.dna?.species)
+ component_type = C.dna.species.swimming_component
+ AM.AddComponent(component_type)
+
+/turf/open/indestructible/sound/pool/Exited(atom/movable/Obj, atom/newloc)
+ . = ..()
+ if(!istype(newloc, /turf/open/indestructible/sound/pool))
+ var/datum/component/swimming/S = Obj.GetComponent(/datum/component/swimming) //Handling admin TPs here.
+ S?.RemoveComponent()
+
+/turf/open/MouseDrop_T(atom/dropping, mob/user)
+ if(!isliving(user) || !isliving(dropping)) //No I don't want ghosts to be able to dunk people into the pool.
+ return
+ var/atom/movable/AM = dropping
+ var/datum/component/swimming/S = dropping.GetComponent(/datum/component/swimming)
+ if(S)
+ if(do_after(user, 1 SECONDS, target=src))
+ S.RemoveComponent()
+ visible_message("[dropping] climbs out of the pool.")
+ AM.forceMove(src)
+ else
+ . = ..()
+
+/turf/open/indestructible/sound/pool/MouseDrop_T(atom/dropping, mob/user)
+ if(!isliving(user) || !isliving(dropping)) //No I don't want ghosts to be able to dunk people into the pool.
+ return
+ var/datum/component/swimming/S = dropping.GetComponent(/datum/component/swimming) //If they're already swimming, don't let them start swimming again.
+ if(S)
+ return FALSE
+ . = ..()
+ if(user != dropping)
+ dropping.visible_message("[user] starts to lower [dropping] down into [src].", \
+ "You start to lower [dropping] down into [src].")
+ else
+ to_chat(user, "You start climbing down into [src]...")
+ if(do_after(user, 4 SECONDS, target=src))
+ splash(dropping)
+
+/datum/mood_event/poolparty
+ description = "I love swimming!.\n"
+ mood_change = 2
+ timeout = 2 MINUTES
+
+/datum/mood_event/robotpool
+ description = "I really wasn't built with water resistance in mind...\n"
+ mood_change = -3
+ timeout = 2 MINUTES
+
+/datum/mood_event/poolwet
+ description = "Eugh! my clothes are soaking wet from that swim.\n"
+ mood_change = -4
+ timeout = 4 MINUTES
+
+/turf/open/indestructible/sound/pool/proc/splash(mob/user)
+ user.forceMove(src)
+ playsound(src, 'sound/effects/splosh.ogg', 100, 1) //Credit to hippiestation for this sound file!
+ user.visible_message("SPLASH!")
+ var/zap = 0
+ if(issilicon(user)) //Do not throw brick in a pool. Brick begs.
+ zap = 1 //Sorry borgs! Swimming will come at a cost.
+ if(ishuman(user))
+ var/mob/living/carbon/human/F = user
+ var/datum/species/SS = F.dna.species
+ if(MOB_ROBOTIC in SS.inherent_biotypes || ispreternis(F)) //ZAP goes preternis
+ zap = 2 //You can protect yourself from water damage with thick clothing.
+ if(F.head && istype(F.head, /obj/item/clothing))
+ var/obj/item/clothing/CH = F.head
+ if (CH.clothing_flags & THICKMATERIAL) //thick suits should suffice! But preternis are robots and probably not water-sealed.
+ zap --
+ if(F.wear_suit && istype(F.wear_suit, /obj/item/clothing))
+ var/obj/item/clothing/CS = F.wear_suit
+ if (CS.clothing_flags & THICKMATERIAL)
+ zap --
+ if(zap > 0)
+ user.emp_act(zap)
+ user.emote("scream") //Chad coders use M.say("*scream")
+ do_sparks(zap, TRUE, user)
+ to_chat(user, "WARNING: WATER DAMAGE DETECTED!")
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "robotpool", /datum/mood_event/robotpool)
+ else
+ if(!check_clothes(user))
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "pool", /datum/mood_event/poolparty)
+ return
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "pool", /datum/mood_event/poolwet)
+
+//Largely a copypaste from shower.dm. Checks if the mob was stupid enough to enter a pool fully clothed. We allow masks as to not discriminate against clown and mime players.
+/turf/open/indestructible/sound/pool/proc/check_clothes(mob/living/carbon/human/H)
+ if(!istype(H) || iscatperson(H)) //Don't care about non humans.
+ return FALSE
+ if(H.wear_suit && (H.wear_suit.clothing_flags & SHOWEROKAY))
+ // Do not check underclothing if the over-suit is suitable.
+ // This stops people feeling dumb if they're showering
+ // with a radiation suit on.
+ return FALSE
+
+ . = FALSE
+ if(!(H.wear_suit?.clothing_flags & SHOWEROKAY))
+ return TRUE
+ if(!(H.w_uniform?.clothing_flags & SHOWEROKAY))
+ return TRUE
+ if(!(H.head?.clothing_flags & SHOWEROKAY))
+ return TRUE
+
+/obj/effect/turf_decal/pool
+ name = "Pool siding"
+ icon = 'icons/obj/pool.dmi'
+ icon_state = "poolborder"
+
+/obj/effect/turf_decal/pool/corner
+ icon_state = "bordercorner"
+
+/obj/effect/turf_decal/pool/innercorner
+ icon_state = "innercorner"
+
+//Pool machinery
+
+/obj/structure/pool_ladder
+ name = "Pool ladder"
+ desc = "Click this to get out of a pool quickly."
+ icon = 'icons/obj/pool.dmi'
+ icon_state = "ladder"
+ pixel_y = 12
+
+GLOBAL_LIST_EMPTY(pool_filters)
+
+/obj/machinery/pool_filter
+ name = "Pool filter"
+ desc = "A device which can help you regulate conditions in a pool. Use a wrench to change its operating temperature, or hit it with a reagent container to load in new liquid to add to the pool."
+ icon = 'icons/obj/pool.dmi'
+ icon_state = "poolfilter"
+ pixel_y = 12 //So it sits above the water
+ idle_power_usage = IDLE_POWER_USE
+ var/id = null //change this if youre an annoying mapper who wants multiple pools per area.
+ var/list/pool = list()
+ var/desired_temperature = 300 //Room temperature
+ var/current_temperature = 300 //current temp
+ var/preset_reagent_type = null //Set this if you want your pump to start filled with a given reagent. SKEWIUM POOL SKEWIUM POOL!
+
+/obj/machinery/pool_filter/examine(mob/user)
+ . = ..()
+ . += "The thermostat on it reads [current_temperature]."
+
+/obj/machinery/pool_filter/Initialize()
+ . = ..()
+ create_reagents(100, OPENCONTAINER) //If you're a terrible terrible clown and want to dump reagents into the pool.
+ if(preset_reagent_type)
+ reagents.add_reagent(preset_reagent_type, 100)
+ var/area/AR = get_area(src)
+ for(var/turf/open/indestructible/sound/pool/water in get_area_turfs(AR))
+ if(id && water.id != id)
+ continue //Not the same id. Fine. Ignore that one then!
+ pool += water
+ GLOB.pool_filters += src
+
+//Brick can set the pool to low temperatures remotely. This will probably be hell on malf!
+
+/obj/machinery/pool_filter/attack_robot(mob/user)
+ . = ..()
+ wrench_act(user, null)
+
+/obj/machinery/pool_filter/attack_ai(mob/user)
+ . = ..()
+ wrench_act(user, null)
+
+/obj/machinery/pool_filter/wrench_act(mob/living/user, obj/item/I)
+ . = ..()
+ var/newTemp = input(user, "Set a new temperature for [src] (Kelvin).", "[src]", null) as num
+ if(!newTemp)
+ return
+ newTemp = clamp(newTemp, T0C, 320)
+ desired_temperature = newTemp
+ return FALSE
+
+/obj/machinery/pool_filter/process()
+ if(!LAZYLEN(pool) || !is_operational())
+ return //No use having one of these processing for no reason is there?
+ use_power(idle_power_usage)
+ var/delta = (current_temperature > desired_temperature) ? -0.5 : 0.5
+ current_temperature += delta
+ current_temperature = clamp(current_temperature, T0C, desired_temperature)
+ var/trans_amount = reagents.total_volume / pool.len //Split up the reagents equally.
+ for(var/turf/open/indestructible/sound/pool/water as() in pool)
+ if(reagents.reagent_list.len)
+ water.set_colour(mix_color_from_reagents(reagents.reagent_list))
+ else
+ water.set_colour("#009999")
+ if(water.contents.len && reagents.total_volume > 0)
+ for(var/mob/living/M in water)
+ if(!istype(M))
+ continue
+ var/datum/reagents/splash_holder = new/datum/reagents(trans_amount) //Take some of our reagents out, react them with the pool denizens.
+ splash_holder.my_atom = water
+ reagents.trans_to(splash_holder, trans_amount, transfered_by = src)
+ splash_holder.chem_temp = current_temperature
+ if(prob(80))
+ splash_holder.reaction(M, TOUCH)
+ else //Sometimes the water penetrates a lil deeper than just a splosh.
+ splash_holder.reaction(M, INGEST)
+ splash_holder.trans_to(M, trans_amount, transfered_by = src) //Actually put reagents in the mob
+ qdel(splash_holder)
+ var/mob/living/carbon/C = M
+ if(current_temperature <= 283.5) //Colder than 10 degrees is going to make you very cold
+ if(iscarbon(M))
+ C.adjust_bodytemperature(-80, 80)
+ to_chat(M, "The water is freezing cold!")
+ else if(current_temperature >= 308.5) //Hotter than 35 celsius is going to make you burn up
+ if(iscarbon(M))
+ C.adjust_bodytemperature(35, 0, 500)
+ M.adjustFireLoss(5)
+ to_chat(M, "The water is searing hot!")
+
+/obj/structure/pool_ladder/attack_hand(mob/user)
+ var/datum/component/swimming/S = user.GetComponent(/datum/component/swimming)
+ if(S)
+ to_chat(user, "You start to climb out of the pool...")
+ if(do_after(user, 1 SECONDS, target=src))
+ S.RemoveComponent()
+ visible_message("[user] climbs out of the pool.")
+ user.forceMove(get_turf(get_step(src, NORTH))) //Ladders shouldn't adjoin another pool section. Ever.
+ else
+ to_chat(user, "You start to climb into the pool...")
+ var/turf/T = get_turf(src)
+ if(do_after(user, 1 SECONDS, target=src))
+ if(!istype(T, /turf/open/indestructible/sound/pool)) //Ugh, fine. Whatever.
+ user.forceMove(get_turf(src))
+ else
+ var/turf/open/indestructible/sound/pool/P = T
+ P.splash(user)
+
+/obj/structure/pool_ladder/attack_robot(mob/user)
+ . = ..()
+ attack_hand(user)
diff --git a/code/modules/pool/pool_items.dm b/code/modules/pool/pool_items.dm
new file mode 100644
index 000000000000..9d4049449892
--- /dev/null
+++ b/code/modules/pool/pool_items.dm
@@ -0,0 +1,78 @@
+/obj/item/twohanded/required/pool
+ icon = 'icons/obj/pool.dmi'
+ lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ force = 0
+ damtype = STAMINA
+ force_wielded = 5
+ wieldsound = 'sound/weapons/tap.ogg'
+ unwieldsound = 'sound/weapons/tap.ogg'
+ w_class = WEIGHT_CLASS_BULKY
+ attack_verb = list("attacked", "bashed", "battered", "bludgeoned", "whacked")
+
+/obj/item/twohanded/required/pool/Initialize()
+ . = ..()
+ //Pick a random color
+ color = pick(COLOR_YELLOW, COLOR_LIME, COLOR_RED, COLOR_BLUE_LIGHT, COLOR_CYAN, COLOR_MAGENTA)
+
+/obj/item/twohanded/required/pool/rubber_ring
+ name = "inflateable ring"
+ desc = "An inflateable ring used for keeping people afloat. Throw at drowning people to save them."
+ icon_state = "rubber_ring"
+
+/obj/item/twohanded/required/pool/rubber_ring/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
+ . = ..()
+ if(ishuman(hit_atom))
+ var/mob/living/carbon/human/H = hit_atom
+ //Make sure they are in a pool
+ if(!istype(get_turf(H), /turf/open/indestructible/sound/pool))
+ return
+ //Make sure they are alive and can pick it up
+ if(H.stat)
+ return
+ //Try shove it in their inventory
+ if(H.put_in_active_hand(src))
+ visible_message("The [src] lands over [H]'s head!")
+
+/obj/item/twohanded/required/pool/pool_noodle
+ icon_state = "pool_noodle"
+ lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
+ name = "pool noodle"
+ desc = "A long noodle made of foam. Helping those with fears of swimming swim since the 1980s."
+ var/suiciding = FALSE
+
+/obj/item/twohanded/required/pool/pool_noodle/attack(mob/target, mob/living/carbon/human/user)
+ . = ..()
+ if(wielded && prob(50))
+ INVOKE_ASYNC(src, .proc/jedi_spin, user)
+
+/obj/item/twohanded/required/pool/pool_noodle/proc/jedi_spin(mob/living/user) //rip complex code, but this fucked up blocking
+ user.emote("flip")
+
+/obj/item/twohanded/required/pool/pool_noodle/suicide_act(mob/user)
+ if(suiciding)
+ return SHAME
+ suiciding = TRUE
+ user.visible_message("[user] begins kicking their legs to stay afloat!")
+ var/mob/living/L = user
+ if(istype(L))
+ L.Immobilize(63)
+ animate(user, time=20, pixel_y=18)
+ sleep(20)
+ animate(user, time=10, pixel_y=12)
+ sleep(10)
+ user.visible_message("[user] keeps swimming higher and higher!")
+ animate(user, time=10, pixel_y=22)
+ sleep(10)
+ animate(user, time=10, pixel_y=16)
+ sleep(10)
+ animate(user, time=15, pixel_y=32)
+ sleep(15)
+ user.visible_message("[user] suddenly realised they aren't in the water and cannot float.")
+ animate(user, time=1, pixel_y=0)
+ sleep(1)
+ user.ghostize()
+ user.gib()
+ suiciding = FALSE
+ return MANUAL_SUICIDE
diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi
index 6add2927d80d..d2ccb674dfb3 100644
Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ
diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi
index 34bae4e97300..7f8dcfa9debd 100644
Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/melee_lefthand.dmi b/icons/mob/inhands/weapons/melee_lefthand.dmi
index 50ae2fb1639d..551e45cd5428 100644
Binary files a/icons/mob/inhands/weapons/melee_lefthand.dmi and b/icons/mob/inhands/weapons/melee_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/melee_righthand.dmi b/icons/mob/inhands/weapons/melee_righthand.dmi
index 24f4e1ab8210..21976c191fde 100644
Binary files a/icons/mob/inhands/weapons/melee_righthand.dmi and b/icons/mob/inhands/weapons/melee_righthand.dmi differ
diff --git a/icons/obj/pool.dmi b/icons/obj/pool.dmi
new file mode 100644
index 000000000000..54135ea36349
Binary files /dev/null and b/icons/obj/pool.dmi differ
diff --git a/sound/effects/splash.ogg b/sound/effects/splash.ogg
new file mode 100644
index 000000000000..39b6e69c5d5b
Binary files /dev/null and b/sound/effects/splash.ogg differ
diff --git a/sound/effects/splosh.ogg b/sound/effects/splosh.ogg
new file mode 100644
index 000000000000..ebb9c090f184
Binary files /dev/null and b/sound/effects/splosh.ogg differ
diff --git a/yogstation.dme b/yogstation.dme
index 7a8595e5b009..bc527422ad36 100644
--- a/yogstation.dme
+++ b/yogstation.dme
@@ -2631,6 +2631,13 @@
#include "code\modules\photography\photos\photo.dm"
#include "code\modules\plumbing\ducts.dm"
#include "code\modules\plumbing\plumbers\pumps.dm"
+#include "code\modules\pool\pool.dm"
+#include "code\modules\pool\pool_items.dm"
+#include "code\modules\pool\components\swimming.dm"
+#include "code\modules\pool\components\swimming_dissolve.dm"
+#include "code\modules\pool\components\swimming_ethereal.dm"
+#include "code\modules\pool\components\swimming_felinid.dm"
+#include "code\modules\pool\components\swimming_golem.dm"
#include "code\modules\power\apc.dm"
#include "code\modules\power\cable.dm"
#include "code\modules\power\cell.dm"