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 += "" - - for(var/path in subtypesof(/datum/horror_chem)) - var/datum/horror_chem/chem = path - if(path in horror_chems) - content += "" - - content += "
[initial(chem.chemname)] ([initial(chem.chemuse)])

[initial(chem.chem_desc)]

" - - 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 += "" + + for(var/path in subtypesof(/datum/horror_chem)) + var/datum/horror_chem/chem = path + if(path in horror_chems) + content += "" + + content += "
[initial(chem.chemname)] ([initial(chem.chemuse)])

[initial(chem.chem_desc)]

" + + 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 - - - - - - -
-

Horror Chemicals

-
- [content] -
"} +/mob/living/simple_animal/horror/proc/get_html_template(content) + var/html = {" + + + Horror Chemicals + + + + + + +
+

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"