diff --git a/_maps/RandomRuins/IceRuins/icemoon_surface_engioutpost.dmm b/_maps/RandomRuins/IceRuins/icemoon_surface_engioutpost.dmm index 1a1d07b6a999..e6be68cae54f 100644 --- a/_maps/RandomRuins/IceRuins/icemoon_surface_engioutpost.dmm +++ b/_maps/RandomRuins/IceRuins/icemoon_surface_engioutpost.dmm @@ -818,7 +818,7 @@ /turf/open/floor/engine/vacuum, /area/ruin/planetengi) "yF" = ( -/mob/living/simple_animal/hostile/construct/proteon, +/mob/living/basic/construct/proteon, /turf/open/floor/iron, /area/ruin/planetengi) "Ai" = ( @@ -829,7 +829,7 @@ /turf/open/floor/iron/icemoon, /area/ruin/planetengi) "Iy" = ( -/mob/living/simple_animal/hostile/construct/proteon, +/mob/living/basic/construct/proteon, /obj/effect/turf_decal/tile/yellow, /turf/open/floor/iron/icemoon, /area/ruin/planetengi) diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm index 44e5b655c89f..7b99eeba0758 100644 --- a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm +++ b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm @@ -1,11 +1,4 @@ //MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE -"ac" = ( -/obj/structure/window/reinforced/spawner/directional/north, -/obj/structure/window/reinforced/spawner, -/obj/structure/window/reinforced/spawner/directional/east, -/obj/structure/grille, -/turf/open/floor/plating/icemoon, -/area/ruin/plasma_facility/operations) "af" = ( /obj/structure/window/reinforced/spawner/directional/north, /obj/structure/grille, @@ -82,6 +75,37 @@ /obj/effect/mapping_helpers/airlock/abandoned, /turf/open/floor/plating, /area/ruin/plasma_facility/commons) +"aZ" = ( +/obj/structure/railing{ + dir = 5 + }, +/obj/structure/closet/crate{ + icon_state = "crateopen" + }, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/sheet/mineral/silver, +/obj/item/stack/ore/silver, +/obj/item/stack/ore/silver, +/obj/item/stack/ore/silver, +/obj/item/stack/ore/silver, +/obj/item/stack/ore/silver, +/obj/item/stack/ore/silver, +/turf/open/floor/plating/snowed/icemoon, +/area/icemoon/underground/explored) +"bj" = ( +/obj/effect/turf_decal/bot/left, +/obj/structure/closet/crate{ + icon_state = "crateopen" + }, +/obj/item/stack/sheet/mineral/plasma/thirty, +/turf/open/floor/iron/smooth_half{ + initial_gas_mix = "ICEMOON_ATMOS" + }, +/area/ruin/plasma_facility/operations) "br" = ( /obj/structure/table/reinforced, /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ @@ -127,7 +151,7 @@ name = "Plasma Relief Valve" }, /obj/effect/decal/cleanable/dirt, -/mob/living/simple_animal/hostile/skeleton/plasmaminer/jackhammer, +/mob/living/basic/skeleton/plasmaminer/jackhammer, /turf/open/floor/plating/snowed/icemoon, /area/icemoon/underground/explored) "bX" = ( @@ -274,16 +298,15 @@ /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron, /area/ruin/plasma_facility/commons) +"ea" = ( +/obj/effect/decal/cleanable/dirt, +/obj/machinery/light/floor/has_bulb, +/turf/open/floor/plating/snowed/icemoon, +/area/icemoon/underground/explored) "ed" = ( /obj/machinery/atmospherics/components/tank/plasma, /turf/open/floor/plating/snowed/icemoon, /area/ruin/plasma_facility/operations) -"ev" = ( -/obj/structure/window/reinforced/unanchored{ - dir = 1 - }, -/turf/open/misc/asteroid/snow/icemoon, -/area/icemoon/underground/explored) "eC" = ( /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, @@ -298,17 +321,6 @@ /obj/structure/fence/cut/large, /turf/open/floor/plating/snowed/smoothed/icemoon, /area/icemoon/underground/explored) -"eM" = ( -/obj/structure/window/reinforced/spawner, -/obj/effect/decal/cleanable/glass, -/obj/item/shard{ - pixel_x = 11; - pixel_y = 6 - }, -/obj/item/shard, -/obj/effect/decal/cleanable/dirt, -/turf/open/floor/plating/icemoon, -/area/ruin/plasma_facility/operations) "eO" = ( /obj/structure/table, /obj/item/plate, @@ -326,6 +338,12 @@ /obj/structure/bonfire, /turf/open/misc/asteroid/snow/icemoon, /area/icemoon/underground/explored) +"fc" = ( +/obj/structure/window/reinforced/unanchored{ + dir = 1 + }, +/turf/open/misc/asteroid/snow/icemoon, +/area/icemoon/underground/explored) "fx" = ( /obj/machinery/light/small/built/directional/west, /turf/open/lava/plasma/ice_moon, @@ -359,9 +377,9 @@ }, /turf/open/lava/plasma/ice_moon, /area/icemoon/underground/explored) -"gd" = ( -/mob/living/simple_animal/hostile/asteroid/wolf, -/turf/open/misc/asteroid/snow/icemoon, +"gi" = ( +/mob/living/basic/mining/wolf, +/turf/open/floor/plating/snowed/smoothed/icemoon, /area/icemoon/underground/explored) "gj" = ( /obj/structure/closet/emcloset, @@ -475,12 +493,6 @@ /obj/effect/decal/cleanable/dirt, /turf/open/floor/wood/large, /area/ruin/plasma_facility/commons) -"hN" = ( -/obj/structure/grille, -/obj/structure/window/reinforced/spawner, -/obj/structure/window/reinforced/spawner/directional/east, -/turf/open/floor/plating/icemoon, -/area/ruin/plasma_facility/commons) "hR" = ( /turf/closed/wall/ice, /area/ruin/plasma_facility/operations) @@ -563,6 +575,13 @@ /obj/effect/decal/cleanable/dirt, /turf/open/floor/stone, /area/ruin/plasma_facility/commons) +"im" = ( +/obj/structure/window/reinforced/spawner, +/obj/effect/decal/cleanable/glass, +/obj/item/shard, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/plating/icemoon, +/area/ruin/plasma_facility/operations) "iw" = ( /obj/structure/railing, /obj/item/grown/log{ @@ -608,6 +627,17 @@ initial_gas_mix = "ICEMOON_ATMOS" }, /area/ruin/plasma_facility/operations) +"jI" = ( +/obj/structure/window/reinforced/spawner, +/obj/effect/decal/cleanable/glass, +/obj/item/shard{ + pixel_x = 11; + pixel_y = 6 + }, +/obj/item/shard, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/plating/icemoon, +/area/ruin/plasma_facility/operations) "jN" = ( /obj/item/grown/log{ pixel_x = -7; @@ -630,24 +660,6 @@ }, /turf/open/misc/asteroid/snow/icemoon, /area/icemoon/underground/explored) -"jT" = ( -/obj/structure/window/reinforced/spawner, -/obj/structure/grille, -/turf/open/floor/plating/icemoon, -/area/ruin/plasma_facility/operations) -"kh" = ( -/obj/structure/window/reinforced/spawner, -/obj/structure/frame/computer{ - anchored = 1; - dir = 4 - }, -/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/obj/effect/decal/cleanable/dirt, -/turf/open/floor/iron/smooth{ - initial_gas_mix = "ICEMOON_ATMOS" - }, -/area/ruin/plasma_facility/operations) "ku" = ( /obj/effect/decal/cleanable/dirt, /obj/effect/turf_decal/tile/brown/anticorner{ @@ -660,30 +672,6 @@ dir = 4 }, /area/ruin/plasma_facility/commons) -"kA" = ( -/obj/structure/closet/crate{ - icon_state = "crateopen" - }, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/obj/item/stack/ore/plasma, -/turf/open/floor/plating/snowed/icemoon, -/area/icemoon/underground/explored) "kG" = ( /obj/structure/flora/grass/both/style_random, /turf/open/misc/asteroid/snow/icemoon, @@ -813,6 +801,12 @@ /obj/effect/turf_decal/tile/purple/half, /turf/open/floor/iron/textured_half, /area/ruin/plasma_facility/operations) +"ng" = ( +/obj/structure/grille/broken, +/obj/structure/window/reinforced/spawner, +/obj/effect/decal/cleanable/glass, +/turf/open/floor/plating/icemoon, +/area/ruin/plasma_facility/operations) "nk" = ( /obj/machinery/atmospherics/pipe/smart/simple/general/hidden{ dir = 5 @@ -856,6 +850,14 @@ dir = 1 }, /area/ruin/plasma_facility/commons) +"ns" = ( +/obj/item/kirbyplants{ + icon_state = "applebush" + }, +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/tile/brown/full, +/turf/open/floor/iron/large, +/area/ruin/plasma_facility/commons) "nu" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, @@ -990,14 +992,6 @@ }, /turf/open/floor/iron/edge, /area/ruin/plasma_facility/commons) -"pn" = ( -/obj/item/kirbyplants{ - icon_state = "applebush" - }, -/obj/effect/decal/cleanable/dirt, -/obj/effect/turf_decal/tile/brown/full, -/turf/open/floor/iron/large, -/area/ruin/plasma_facility/commons) "pt" = ( /obj/structure/table, /obj/item/paper/paperslip{ @@ -1106,14 +1100,6 @@ "rs" = ( /turf/open/lava/plasma/ice_moon, /area/icemoon/underground/explored) -"rE" = ( -/obj/effect/spawner/structure/window/hollow/reinforced/directional{ - dir = 8 - }, -/obj/effect/decal/cleanable/glass, -/obj/structure/window/reinforced/spawner, -/turf/open/floor/plating/icemoon, -/area/ruin/plasma_facility/operations) "rF" = ( /obj/effect/decal/cleanable/dirt, /obj/item/wallframe/airalarm{ @@ -1127,16 +1113,18 @@ dir = 1 }, /area/ruin/plasma_facility/commons) -"rS" = ( -/obj/effect/turf_decal/bot/left, +"rW" = ( +/obj/structure/lattice/catwalk, +/obj/structure/railing/corner, /obj/structure/closet/crate{ icon_state = "crateopen" }, -/obj/item/stack/sheet/mineral/plasma/thirty, -/turf/open/floor/iron/smooth_half{ - initial_gas_mix = "ICEMOON_ATMOS" - }, -/area/ruin/plasma_facility/operations) +/obj/item/stack/sheet/mineral/uranium/five, +/obj/item/stack/sheet/mineral/uranium/five, +/obj/item/stack/sheet/mineral/uranium/five, +/obj/item/stack/sheet/mineral/uranium/five, +/turf/open/lava/plasma/ice_moon, +/area/icemoon/underground/explored) "sq" = ( /obj/structure/table/reinforced/rglass, /obj/effect/decal/cleanable/dirt, @@ -1144,27 +1132,6 @@ /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron/dark/textured, /area/ruin/plasma_facility/commons) -"sr" = ( -/obj/structure/railing{ - dir = 5 - }, -/obj/structure/closet/crate{ - icon_state = "crateopen" - }, -/obj/item/stack/sheet/mineral/silver, -/obj/item/stack/sheet/mineral/silver, -/obj/item/stack/sheet/mineral/silver, -/obj/item/stack/sheet/mineral/silver, -/obj/item/stack/sheet/mineral/silver, -/obj/item/stack/sheet/mineral/silver, -/obj/item/stack/ore/silver, -/obj/item/stack/ore/silver, -/obj/item/stack/ore/silver, -/obj/item/stack/ore/silver, -/obj/item/stack/ore/silver, -/obj/item/stack/ore/silver, -/turf/open/floor/plating/snowed/icemoon, -/area/icemoon/underground/explored) "su" = ( /obj/structure/chair/stool/directional/south, /obj/effect/decal/cleanable/dirt, @@ -1249,6 +1216,16 @@ }, /turf/open/lava/plasma/ice_moon, /area/icemoon/underground/explored) +"ub" = ( +/obj/effect/decal/cleanable/glass, +/obj/effect/turf_decal/box/corners{ + dir = 4 + }, +/mob/living/basic/mining/wolf, +/turf/open/floor/iron/smooth{ + initial_gas_mix = "ICEMOON_ATMOS" + }, +/area/ruin/plasma_facility/operations) "ui" = ( /obj/structure/lattice/catwalk, /obj/structure/railing{ @@ -1268,16 +1245,6 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /turf/open/floor/plating/icemoon, /area/ruin/plasma_facility/operations) -"up" = ( -/obj/effect/decal/cleanable/glass, -/obj/effect/turf_decal/box/corners{ - dir = 4 - }, -/mob/living/simple_animal/hostile/asteroid/wolf, -/turf/open/floor/iron/smooth{ - initial_gas_mix = "ICEMOON_ATMOS" - }, -/area/ruin/plasma_facility/operations) "uq" = ( /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, @@ -1323,11 +1290,6 @@ "vt" = ( /turf/closed/wall, /area/ruin/plasma_facility/operations) -"vz" = ( -/obj/effect/decal/cleanable/dirt, -/obj/machinery/light/floor/has_bulb, -/turf/open/floor/plating/snowed/icemoon, -/area/icemoon/underground/explored) "vI" = ( /obj/structure/fence{ dir = 4 @@ -1439,6 +1401,14 @@ }, /turf/open/floor/iron/edge, /area/ruin/plasma_facility/commons) +"yG" = ( +/obj/effect/spawner/structure/window/hollow/reinforced/directional{ + dir = 8 + }, +/obj/effect/decal/cleanable/glass, +/obj/structure/window/reinforced/spawner, +/turf/open/floor/plating/icemoon, +/area/ruin/plasma_facility/operations) "yL" = ( /obj/structure/girder, /turf/open/floor/plating/icemoon, @@ -1625,7 +1595,7 @@ name = "mining conveyor" }, /obj/effect/decal/cleanable/dirt, -/mob/living/simple_animal/hostile/skeleton/plasmaminer, +/mob/living/basic/skeleton/plasmaminer, /obj/effect/turf_decal/tile/brown/opposingcorners, /turf/open/floor/iron, /area/ruin/plasma_facility/operations) @@ -1689,13 +1659,6 @@ /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron, /area/ruin/plasma_facility/commons) -"DQ" = ( -/obj/item/kirbyplants{ - icon_state = "applebush" - }, -/obj/effect/turf_decal/tile/brown/full, -/turf/open/floor/iron/large, -/area/ruin/plasma_facility/commons) "DR" = ( /obj/machinery/light/small/broken/directional/north, /turf/open/lava/plasma/ice_moon, @@ -1757,18 +1720,6 @@ /obj/machinery/light/small/broken/directional/north, /turf/open/lava/plasma/ice_moon, /area/ruin/plasma_facility/operations) -"FD" = ( -/obj/structure/lattice/catwalk, -/obj/structure/railing/corner, -/obj/structure/closet/crate{ - icon_state = "crateopen" - }, -/obj/item/stack/sheet/mineral/uranium/five, -/obj/item/stack/sheet/mineral/uranium/five, -/obj/item/stack/sheet/mineral/uranium/five, -/obj/item/stack/sheet/mineral/uranium/five, -/turf/open/lava/plasma/ice_moon, -/area/icemoon/underground/explored) "FG" = ( /obj/structure/lattice/catwalk, /obj/structure/railing{ @@ -1815,6 +1766,22 @@ /obj/item/shard, /turf/open/floor/plating/icemoon, /area/ruin/plasma_facility/operations) +"Gu" = ( +/obj/machinery/door/poddoor/shutters/window/preopen{ + id = "fire_facility_car"; + name = "Garage Door" + }, +/mob/living/basic/mining/wolf, +/turf/open/floor/plating/icemoon, +/area/ruin/plasma_facility/operations) +"Hn" = ( +/obj/structure/grille, +/obj/structure/window/reinforced/spawner/directional/east, +/obj/structure/window/reinforced/spawner, +/obj/item/shard, +/obj/effect/decal/cleanable/glass, +/turf/open/floor/plating/icemoon, +/area/ruin/plasma_facility/operations) "Hp" = ( /obj/structure/railing/corner, /turf/open/lava/plasma/ice_moon, @@ -1832,10 +1799,6 @@ }, /turf/open/floor/plating/icemoon, /area/ruin/plasma_facility/commons) -"Hz" = ( -/mob/living/simple_animal/hostile/asteroid/wolf, -/turf/open/floor/plating/snowed/smoothed/icemoon, -/area/icemoon/underground/explored) "HA" = ( /obj/effect/turf_decal/siding/wood{ dir = 8 @@ -1885,14 +1848,6 @@ /obj/machinery/light/small/directional/north, /turf/open/lava/plasma/ice_moon, /area/ruin/plasma_facility/operations) -"Ij" = ( -/obj/structure/grille, -/obj/structure/window/reinforced/spawner/directional/east, -/obj/structure/window/reinforced/spawner, -/obj/item/shard, -/obj/effect/decal/cleanable/glass, -/turf/open/floor/plating/icemoon, -/area/ruin/plasma_facility/operations) "In" = ( /obj/structure/flora/grass/green/style_random, /obj/machinery/light/small/broken/directional/east, @@ -1903,13 +1858,13 @@ /obj/machinery/light/small/broken/directional/south, /turf/open/lava/plasma/ice_moon, /area/ruin/plasma_facility/operations) -"IJ" = ( -/obj/structure/window/reinforced/spawner, -/obj/effect/decal/cleanable/glass, -/obj/item/shard, -/obj/effect/decal/cleanable/dirt, -/turf/open/floor/plating/icemoon, -/area/ruin/plasma_facility/operations) +"IP" = ( +/obj/item/kirbyplants{ + icon_state = "applebush" + }, +/obj/effect/turf_decal/tile/brown/full, +/turf/open/floor/iron/large, +/area/ruin/plasma_facility/commons) "IU" = ( /obj/structure/chair/sofa/right/brown{ dir = 4 @@ -1976,7 +1931,7 @@ }, /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2, /obj/effect/decal/cleanable/dirt, -/mob/living/simple_animal/hostile/skeleton/plasmaminer/jackhammer, +/mob/living/basic/skeleton/plasmaminer/jackhammer, /obj/machinery/light_switch/directional/north, /turf/open/floor/iron/smooth{ initial_gas_mix = "ICEMOON_ATMOS" @@ -2037,6 +1992,13 @@ /obj/effect/turf_decal/box/corners, /turf/open/floor/plating/snowed/smoothed/icemoon, /area/icemoon/underground/explored) +"Ku" = ( +/obj/structure/window/reinforced/spawner/directional/north, +/obj/structure/window/reinforced/spawner, +/obj/structure/window/reinforced/spawner/directional/east, +/obj/structure/grille, +/turf/open/floor/plating/icemoon, +/area/ruin/plasma_facility/operations) "Kw" = ( /obj/effect/turf_decal/siding/wood/corner{ dir = 8 @@ -2175,12 +2137,6 @@ /obj/item/stack/sheet/iron, /turf/open/floor/plating/snowed/icemoon, /area/ruin/plasma_facility/operations) -"MN" = ( -/obj/structure/grille/broken, -/obj/structure/window/reinforced/spawner, -/obj/effect/decal/cleanable/glass, -/turf/open/floor/plating/icemoon, -/area/ruin/plasma_facility/operations) "MO" = ( /obj/structure/lattice/catwalk, /obj/structure/railing/corner{ @@ -2260,6 +2216,10 @@ }, /turf/open/floor/plating/snowed/icemoon, /area/icemoon/underground/explored) +"Np" = ( +/mob/living/basic/mining/wolf, +/turf/open/misc/asteroid/snow/icemoon, +/area/icemoon/underground/explored) "Nw" = ( /obj/machinery/atmospherics/components/binary/valve{ dir = 4; @@ -2302,7 +2262,7 @@ /obj/effect/decal/cleanable/glass, /obj/item/shard, /obj/effect/decal/cleanable/dirt, -/mob/living/simple_animal/hostile/skeleton/plasmaminer/jackhammer, +/mob/living/basic/skeleton/plasmaminer/jackhammer, /turf/open/floor/iron/freezer, /area/ruin/plasma_facility/commons) "Or" = ( @@ -2317,6 +2277,11 @@ /obj/item/crowbar/red, /turf/open/floor/plating/snowed/icemoon, /area/icemoon/underground/explored) +"OB" = ( +/obj/structure/window/reinforced/spawner, +/obj/structure/grille, +/turf/open/floor/plating/icemoon, +/area/ruin/plasma_facility/operations) "OC" = ( /obj/structure/chair/office/light{ dir = 4 @@ -2325,7 +2290,7 @@ dir = 8 }, /obj/effect/decal/cleanable/dirt, -/mob/living/simple_animal/hostile/skeleton/plasmaminer/jackhammer, +/mob/living/basic/skeleton/plasmaminer/jackhammer, /obj/machinery/light_switch/directional/south, /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron/dark/textured, @@ -2413,6 +2378,30 @@ dir = 1 }, /area/ruin/plasma_facility/commons) +"PE" = ( +/obj/structure/closet/crate{ + icon_state = "crateopen" + }, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/obj/item/stack/ore/plasma, +/turf/open/floor/plating/snowed/icemoon, +/area/icemoon/underground/explored) "PP" = ( /obj/structure/dresser, /obj/effect/decal/cleanable/dirt, @@ -2477,6 +2466,12 @@ /obj/item/screwdriver, /turf/open/floor/plating/snowed/smoothed/icemoon, /area/icemoon/underground/explored) +"QH" = ( +/obj/structure/grille, +/obj/structure/window/reinforced/spawner, +/obj/structure/window/reinforced/spawner/directional/east, +/turf/open/floor/plating/icemoon, +/area/ruin/plasma_facility/commons) "QJ" = ( /obj/machinery/computer/order_console/mining, /obj/effect/decal/cleanable/dirt, @@ -2583,14 +2578,6 @@ /obj/effect/decal/cleanable/dirt, /turf/open/floor/plating/icemoon, /area/ruin/plasma_facility/commons) -"SV" = ( -/obj/machinery/atmospherics/components/unary/portables_connector{ - dir = 4 - }, -/obj/effect/decal/cleanable/dirt, -/obj/structure/canister_frame/machine, -/turf/open/floor/plating/snowed/icemoon, -/area/icemoon/underground/explored) "Tr" = ( /obj/effect/turf_decal/tile/brown/half{ dir = 1 @@ -2664,14 +2651,6 @@ /obj/structure/flora/tree/stump, /turf/open/misc/asteroid/snow/icemoon, /area/icemoon/underground/explored) -"Vy" = ( -/obj/machinery/door/poddoor/shutters/window/preopen{ - id = "fire_facility_car"; - name = "Garage Door" - }, -/mob/living/simple_animal/hostile/asteroid/wolf, -/turf/open/floor/plating/icemoon, -/area/ruin/plasma_facility/operations) "VD" = ( /obj/effect/turf_decal/tile/brown/half, /obj/effect/turf_decal/tile/neutral/half/contrasted{ @@ -2701,6 +2680,14 @@ /obj/structure/grille, /turf/open/floor/plating/icemoon, /area/ruin/plasma_facility/commons) +"Wi" = ( +/obj/machinery/atmospherics/components/unary/portables_connector{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt, +/obj/structure/canister_frame/machine, +/turf/open/floor/plating/snowed/icemoon, +/area/icemoon/underground/explored) "Wu" = ( /turf/open/floor/wood/large, /area/ruin/plasma_facility/commons) @@ -2815,6 +2802,19 @@ /obj/effect/turf_decal/tile/brown/full, /turf/open/floor/iron/textured_large, /area/ruin/plasma_facility/commons) +"Yk" = ( +/obj/structure/window/reinforced/spawner, +/obj/structure/frame/computer{ + anchored = 1; + dir = 4 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron/smooth{ + initial_gas_mix = "ICEMOON_ATMOS" + }, +/area/ruin/plasma_facility/operations) "Yl" = ( /obj/structure/flora/rock/pile/icy/style_random, /turf/open/misc/asteroid/snow/icemoon, @@ -3135,7 +3135,7 @@ Yl vI Yw ms -Hz +gi Yw Yw Yw @@ -3208,11 +3208,11 @@ hJ Py Yw hJ -gd +Np Yw XI wb -hN +QH nD Km Yw @@ -3238,7 +3238,7 @@ AG AG PQ vI -Hz +gi Yw Yw Yw @@ -3321,7 +3321,7 @@ lW pl Mc af -rS +bj hd vt hR @@ -3333,7 +3333,7 @@ Ve ms VT Yw -Hz +gi ms Yw vY @@ -3355,7 +3355,7 @@ PB bM VD gj -ac +Ku pX Ib js @@ -3404,7 +3404,7 @@ ms hR Dr Dr -Vy +Gu yL vY "} @@ -3424,7 +3424,7 @@ pw Zk eC bv -DQ +IP hR OJ go @@ -3475,7 +3475,7 @@ vt FO LD FO -MN +ng vY "} (16,1,1) = {" @@ -3490,7 +3490,7 @@ hK ZH wF Cm -pn +ns PB eC yv @@ -3507,7 +3507,7 @@ AZ dO DZ Jb -up +ub FO ia HX @@ -3685,7 +3685,7 @@ ms eD tG Zf -ev +fc AG "} (22,1,1) = {" @@ -3849,7 +3849,7 @@ wm XA Rj Gb -kA +PE rk fO rs @@ -3885,7 +3885,7 @@ rk hR DZ ie -SV +Wi AT rs rs @@ -3919,7 +3919,7 @@ KV ie TY ie -vz +ea HS oc rs @@ -3991,7 +3991,7 @@ XK ak IU Dp -FD +rW Ee rs rs @@ -4022,7 +4022,7 @@ rs tX fN Na -vz +ea rk hR qP @@ -4056,7 +4056,7 @@ Ue rs Qn rs -sr +aZ Mz vt ed @@ -4194,7 +4194,7 @@ NX vt no ly -eM +jI rs rs rs @@ -4227,9 +4227,9 @@ rs rs af lX -kh +Yk mA -jT +OB rs rs rs @@ -4299,7 +4299,7 @@ oi qw Or hc -IJ +im rs rs rs @@ -4330,7 +4330,7 @@ AG AG rs rs -Ij +Hn qw qw vt @@ -4368,7 +4368,7 @@ rs Pz ul Kx -rE +yG Pz rs rs diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_village.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_village.dmm index 8d851421f1cd..939ddf92b5ef 100644 --- a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_village.dmm +++ b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_village.dmm @@ -108,7 +108,7 @@ "la" = ( /obj/effect/decal/cleanable/blood/gibs/up, /obj/effect/mob_spawn/corpse/human/assistant, -/mob/living/simple_animal/hostile/skeleton/eskimo{ +/mob/living/basic/skeleton/settler{ name = "Village Hunter" }, /turf/open/misc/asteroid/snow/icemoon, @@ -135,7 +135,7 @@ /area/icemoon/underground/explored) "mi" = ( /obj/effect/decal/remains/human, -/mob/living/simple_animal/hostile/construct/juggernaut/hostile{ +/mob/living/basic/construct/juggernaut/hostile{ health = 450; maxHealth = 450; name = "Right Hand of the Elder" @@ -360,7 +360,7 @@ "EF" = ( /obj/effect/decal/cleanable/blood/gibs/torso, /obj/effect/decal/remains/human, -/mob/living/simple_animal/hostile/construct/juggernaut/hostile{ +/mob/living/basic/construct/juggernaut/hostile{ health = 450; maxHealth = 450; name = "Left Hand of the Elder" diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_frozen_comms.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_frozen_comms.dmm index 03bd2ad2a5d6..77db984ba4ea 100644 --- a/_maps/RandomRuins/IceRuins/icemoon_underground_frozen_comms.dmm +++ b/_maps/RandomRuins/IceRuins/icemoon_underground_frozen_comms.dmm @@ -1,4 +1,8 @@ //MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"al" = ( +/mob/living/basic/mining/wolf, +/turf/open/floor/plating/icemoon, +/area/ruin/powered/shuttle) "aD" = ( /obj/structure/fence/cut/medium, /turf/open/misc/asteroid/snow/icemoon, @@ -6,10 +10,6 @@ "aU" = ( /turf/open/genturf, /area/icemoon/underground/unexplored/rivers) -"bh" = ( -/mob/living/simple_animal/hostile/asteroid/wolf, -/turf/open/floor/plating/snowed/smoothed/icemoon, -/area/icemoon/underground/explored) "cv" = ( /obj/structure/flora/rock/pile/icy/style_random, /obj/machinery/light/small/directional/north, @@ -43,6 +43,12 @@ initial_gas_mix = "ICEMOON_ATMOS" }, /area/ruin/powered/shuttle) +"fD" = ( +/mob/living/basic/mining/wolf, +/turf/open/floor/iron/showroomfloor{ + initial_gas_mix = "ICEMOON_ATMOS" + }, +/area/ruin/powered/shuttle) "gz" = ( /turf/closed/wall/ice, /area/ruin/powered/shuttle) @@ -68,12 +74,6 @@ initial_gas_mix = "ICEMOON_ATMOS" }, /area/ruin/powered/shuttle) -"oD" = ( -/mob/living/simple_animal/hostile/asteroid/wolf, -/turf/open/floor/iron/showroomfloor{ - initial_gas_mix = "ICEMOON_ATMOS" - }, -/area/ruin/powered/shuttle) "oT" = ( /turf/open/floor/iron/showroomfloor{ initial_gas_mix = "ICEMOON_ATMOS" @@ -98,10 +98,6 @@ initial_gas_mix = "ICEMOON_ATMOS" }, /area/ruin/powered/shuttle) -"rn" = ( -/mob/living/simple_animal/hostile/asteroid/wolf, -/turf/open/floor/plating/icemoon, -/area/ruin/powered/shuttle) "rS" = ( /obj/structure/flora/tree/pine/style_random, /obj/structure/flora/grass/green/style_random, @@ -130,6 +126,13 @@ }, /turf/open/misc/asteroid/snow/icemoon, /area/icemoon/underground/explored) +"tx" = ( +/obj/structure/window/reinforced/spawner, +/obj/structure/grille, +/obj/item/shard, +/obj/effect/decal/cleanable/glass, +/turf/open/floor/plating/icemoon, +/area/ruin/powered/shuttle) "uz" = ( /obj/structure/tank_dispenser, /turf/open/floor/iron/smooth{ @@ -226,7 +229,7 @@ /obj/structure/fence/cut/large, /turf/open/misc/asteroid/snow/icemoon, /area/icemoon/underground/explored) -"CH" = ( +"DV" = ( /obj/structure/window/reinforced/spawner, /obj/effect/spawner/structure/window/hollow/reinforced/directional{ dir = 1 @@ -248,11 +251,6 @@ }, /turf/open/floor/plating/icemoon, /area/ruin/powered/shuttle) -"FK" = ( -/mob/living/simple_animal/hostile/asteroid/wolf, -/obj/machinery/light/small/broken/directional/north, -/turf/open/floor/plating/snowed/smoothed/icemoon, -/area/ruin/powered/shuttle) "FP" = ( /obj/effect/spawner/structure/window/hollow/reinforced/middle{ dir = 4 @@ -276,12 +274,10 @@ /obj/effect/decal/cleanable/glass, /turf/open/floor/plating/icemoon, /area/ruin/powered/shuttle) -"GQ" = ( -/obj/structure/window/reinforced/spawner, -/obj/structure/grille, -/obj/item/shard, -/obj/effect/decal/cleanable/glass, -/turf/open/floor/plating/icemoon, +"GX" = ( +/mob/living/basic/mining/wolf, +/obj/machinery/light/small/broken/directional/north, +/turf/open/floor/plating/snowed/smoothed/icemoon, /area/ruin/powered/shuttle) "HT" = ( /obj/structure/closet/wardrobe/grey, @@ -347,6 +343,10 @@ }, /turf/open/floor/plating/icemoon, /area/ruin/powered/shuttle) +"Ok" = ( +/mob/living/basic/mining/wolf, +/turf/open/floor/plating/snowed/smoothed/icemoon, +/area/icemoon/underground/explored) "OM" = ( /obj/effect/turf_decal/stripes/line{ dir = 8 @@ -530,7 +530,7 @@ aU aU qS TG -CH +DV Gl gz Cr @@ -553,7 +553,7 @@ GH NO MS oT -oD +fD oT oT eC @@ -568,7 +568,7 @@ qS aU qS qS -GQ +tx oj gz wJ @@ -613,7 +613,7 @@ Et gz SR wP -bh +Ok qS th yl @@ -645,7 +645,7 @@ aU aU gz wg -rn +al Qj te RK @@ -668,7 +668,7 @@ OM yW Kh gz -FK +GX wP qS wP diff --git a/_maps/RandomRuins/LavaRuins/lavaland_biodome_clown_planet.dmm b/_maps/RandomRuins/LavaRuins/lavaland_biodome_clown_planet.dmm index 7ab354935c39..ef650913f141 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_biodome_clown_planet.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_biodome_clown_planet.dmm @@ -671,7 +671,7 @@ /area/ruin/powered/clownplanet) "RW" = ( /obj/effect/mapping_helpers/no_lava, -/mob/living/simple_animal/hostile/retaliate/clown, +/mob/living/basic/clown, /turf/open/floor/noslip, /area/ruin/powered/clownplanet) "Tj" = ( @@ -714,7 +714,7 @@ "YI" = ( /obj/machinery/light/small/directional/south, /obj/effect/mapping_helpers/no_lava, -/mob/living/simple_animal/hostile/retaliate/clown, +/mob/living/basic/clown, /turf/open/floor/noslip, /area/ruin/powered/clownplanet) "Zg" = ( diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm index d63d261b813c..47fe3152a8b5 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm @@ -213,7 +213,7 @@ dir = 1 }, /obj/structure/stone_tile, -/mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck, +/mob/living/basic/mining/gutlunch/milk, /turf/open/indestructible/boss, /area/ruin/unpowered/ash_walkers) "aR" = ( @@ -287,7 +287,7 @@ /obj/structure/stone_tile{ dir = 4 }, -/mob/living/simple_animal/hostile/asteroid/gutlunch/guthen, +/mob/living/basic/mining/gutlunch/warrior, /turf/open/indestructible/boss, /area/ruin/unpowered/ash_walkers) "bd" = ( diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_biodome_winter.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_biodome_winter.dmm index f83887d9b427..a49846f2b0a6 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_surface_biodome_winter.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_biodome_winter.dmm @@ -148,7 +148,7 @@ /turf/open/floor/wood, /area/ruin/powered/snow_biodome) "aH" = ( -/mob/living/simple_animal/hostile/skeleton/eskimo, +/mob/living/basic/skeleton/settler, /turf/open/floor/wood, /area/ruin/powered/snow_biodome) "aI" = ( @@ -258,11 +258,11 @@ /area/ruin/powered/snow_biodome) "gz" = ( /obj/structure/chair/stool/directional/south, -/mob/living/simple_animal/hostile/skeleton/eskimo, +/mob/living/basic/skeleton/settler, /turf/open/floor/pod/dark, /area/ruin/powered/snow_biodome) "hr" = ( -/mob/living/simple_animal/hostile/skeleton/eskimo, +/mob/living/basic/skeleton/settler, /obj/effect/turf_decal/siding/wood{ dir = 1 }, diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm new file mode 100644 index 000000000000..01891e8ef3dd --- /dev/null +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm @@ -0,0 +1,620 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"aa" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"ab" = ( +/obj/structure/bonfire/prelit, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"ad" = ( +/obj/structure/railing{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"ae" = ( +/obj/structure/flora/ash/fireblossom, +/mob/living/basic/mining/mook/worker/tribal_chief, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"ah" = ( +/turf/closed/wall/mineral/wood, +/area/lavaland/surface/outdoors) +"al" = ( +/obj/structure/flora/ash/fireblossom, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"an" = ( +/obj/structure/flora/ash/fireblossom, +/mob/living/basic/mining/mook/worker, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"as" = ( +/obj/effect/landmark/mook_village, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"at" = ( +/obj/structure/chair/pew/left, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"au" = ( +/obj/structure/railing, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"aw" = ( +/obj/structure/railing{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"az" = ( +/mob/living/basic/mining/mook/worker/bard, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"aA" = ( +/obj/structure/chair/pew/right, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"aB" = ( +/turf/open/misc/grass/jungle, +/area/lavaland/surface/outdoors) +"aC" = ( +/obj/structure/sign/nanotrasen, +/turf/closed/wall/mineral/wood, +/area/lavaland/surface/outdoors) +"aD" = ( +/obj/item/flashlight/lantern{ + on = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"aF" = ( +/obj/structure/chair/pew, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"aI" = ( +/mob/living/basic/mining/mook, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"aJ" = ( +/obj/structure/railing{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"aK" = ( +/obj/structure/table/wood/shuttle_bar, +/obj/item/hatchet/wooden, +/obj/item/storage/belt/champion/wrestling, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"aL" = ( +/obj/item/flashlight/lantern{ + on = 1 + }, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"aM" = ( +/obj/structure/chair/pew/right{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"aN" = ( +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"aP" = ( +/mob/living/basic/mining/mook/worker, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"aR" = ( +/obj/structure/bed/pod, +/obj/item/bedsheet/nanotrasen, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"aT" = ( +/obj/structure/flora/tree/jungle/style_5, +/turf/open/misc/grass/jungle, +/area/lavaland/surface/outdoors) +"aU" = ( +/obj/structure/table/wood/shuttle_bar, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"aV" = ( +/obj/structure/chair/pew{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"aX" = ( +/obj/structure/flora/grass/jungle/b, +/turf/open/misc/grass/jungle, +/area/lavaland/surface/outdoors) +"aY" = ( +/obj/structure/chair/pew/left{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"eN" = ( +/obj/structure/table/wood/shuttle_bar, +/obj/item/flashlight/flare/candle/infinite, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"hi" = ( +/turf/open/floor/carpet/lone, +/area/lavaland/surface/outdoors) +"sk" = ( +/obj/structure/bed/maint, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"xb" = ( +/obj/structure/chair/wood{ + dir = 1 + }, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"xv" = ( +/obj/item/fishing_rod, +/obj/structure/bed/maint, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"Br" = ( +/obj/structure/ore_container/material_stand, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"Jh" = ( +/obj/structure/closet/cabinet, +/obj/item/food/grown/banana, +/obj/item/food/meat/slab/goliath, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"Mn" = ( +/obj/structure/flora/tree/jungle/style_3, +/turf/open/misc/grass/jungle, +/area/lavaland/surface/outdoors) +"WL" = ( +/obj/structure/chair/wood, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) + +(1,1,1) = {" +aa +aa +aa +aa +aa +aa +aa +al +aa +aa +aa +aa +aa +aa +aa +aa +aa +aa +aa +aa +"} +(2,1,1) = {" +aa +aa +aa +aa +aa +aa +al +aD +aa +aa +aa +aa +aa +aa +aa +at +aa +aa +aa +aa +"} +(3,1,1) = {" +aa +aa +aa +aa +aa +aa +aa +aa +aa +aa +Br +aa +aa +aa +aa +aF +aa +aa +aa +aa +"} +(4,1,1) = {" +ah +ah +ah +ah +ah +ah +aa +as +aa +aa +aa +aa +aa +aa +aa +aF +aa +ab +aa +aa +"} +(5,1,1) = {" +aN +aN +Jh +sk +aN +aN +aa +aD +aa +aa +aa +aa +aP +aa +aD +aF +aa +aa +aa +aa +"} +(6,1,1) = {" +aN +aL +aN +aN +aN +aN +hi +an +aa +aP +aa +aa +aa +aa +aa +aA +aa +aa +aa +aa +"} +(7,1,1) = {" +aN +aN +aN +aN +aN +aN +hi +aa +aI +aa +aa +aa +aa +aa +al +ae +aY +aV +aV +aM +"} +(8,1,1) = {" +aN +WL +eN +xb +aN +aN +aa +aa +aa +aa +aw +aw +aw +aa +aI +aa +aa +aa +aa +aa +"} +(9,1,1) = {" +ah +ah +ah +ah +ah +ah +aa +aa +aa +au +aB +aB +aT +ad +aa +aa +aa +aP +aa +aa +"} +(10,1,1) = {" +aa +aa +aa +aa +aa +aa +aa +aI +aa +au +aB +aB +aX +ad +aa +aa +al +aa +aa +aa +"} +(11,1,1) = {" +aa +aa +al +al +aa +aa +aa +aa +aa +au +aB +Mn +aX +ad +aa +aa +aa +aa +aa +aa +"} +(12,1,1) = {" +aa +aa +aa +aa +aa +aP +aa +aa +aa +au +aB +aB +aT +ad +aa +aa +aa +aa +aa +aa +"} +(13,1,1) = {" +aa +aa +aa +aa +aa +aa +aa +aa +aa +aa +aJ +aJ +aJ +aa +aa +aa +aP +aa +aa +aa +"} +(14,1,1) = {" +ah +ah +ah +ah +ah +ah +ah +az +aD +al +aa +aa +aI +al +al +aa +aI +aa +aa +al +"} +(15,1,1) = {" +sk +sk +sk +sk +xv +sk +sk +al +aa +aa +aa +aa +aa +aa +aa +aa +aa +aa +aa +aa +"} +(16,1,1) = {" +aN +aN +aN +aN +aN +aN +aN +hi +aa +aa +aa +aa +ah +aN +aN +aN +ah +aa +aa +aa +"} +(17,1,1) = {" +aN +aN +aN +aL +aN +aN +aN +hi +aa +aP +aa +aa +ah +aK +aN +aN +ah +aa +aa +aa +"} +(18,1,1) = {" +sk +sk +sk +sk +sk +sk +sk +aa +aa +aa +aa +aa +aC +aU +aN +aN +ah +aa +aa +aa +"} +(19,1,1) = {" +ah +ah +ah +ah +ah +ah +ah +aa +aa +al +aa +aD +ah +aL +aN +aR +ah +aa +aa +aa +"} +(20,1,1) = {" +aa +aa +aa +aa +aa +aa +aa +aa +aa +aa +aa +aa +ah +aN +aN +aN +ah +aa +aa +aa +"} diff --git a/_maps/RandomRuins/SpaceRuins/atmosasteroidruin.dmm b/_maps/RandomRuins/SpaceRuins/atmosasteroidruin.dmm index a68da2a99759..8670ad8113ce 100644 --- a/_maps/RandomRuins/SpaceRuins/atmosasteroidruin.dmm +++ b/_maps/RandomRuins/SpaceRuins/atmosasteroidruin.dmm @@ -604,7 +604,7 @@ /obj/effect/turf_decal/tile/yellow/half{ dir = 4 }, -/mob/living/simple_animal/hostile/asteroid/hivelord, +/mob/living/basic/mining/hivelord, /turf/open/floor/iron/co2_pressurized, /area/ruin/space/has_grav/atmosasteroid) "RK" = ( diff --git a/_maps/RandomRuins/SpaceRuins/caravanambush.dmm b/_maps/RandomRuins/SpaceRuins/caravanambush.dmm index bf0972ae3118..9641778ac2cf 100644 --- a/_maps/RandomRuins/SpaceRuins/caravanambush.dmm +++ b/_maps/RandomRuins/SpaceRuins/caravanambush.dmm @@ -749,7 +749,7 @@ dir = 4 }, /obj/effect/decal/cleanable/dirt, -/mob/living/basic/syndicate/ranged/smg/space, +/mob/living/basic/trooper/syndicate/ranged/smg/space, /turf/open/floor/iron/airless, /area/shuttle/ruin/caravan/freighter3) "Cj" = ( @@ -1009,7 +1009,7 @@ /area/shuttle/ruin/caravan/freighter3) "PF" = ( /obj/effect/decal/cleanable/dirt, -/mob/living/basic/syndicate/ranged/smg/space, +/mob/living/basic/trooper/syndicate/ranged/smg/space, /turf/open/floor/iron/airless, /area/shuttle/ruin/caravan/freighter3) "PY" = ( @@ -1165,7 +1165,7 @@ /obj/effect/turf_decal/box/white/corners{ dir = 4 }, -/mob/living/basic/syndicate/ranged/shotgun/space/stormtrooper, +/mob/living/basic/trooper/syndicate/ranged/shotgun/space/stormtrooper, /turf/open/floor/iron/dark/airless, /area/shuttle/ruin/caravan/freighter3) "Zz" = ( diff --git a/_maps/RandomRuins/SpaceRuins/clericden.dmm b/_maps/RandomRuins/SpaceRuins/clericden.dmm index 0bf5a3a05798..5b1782559aed 100644 --- a/_maps/RandomRuins/SpaceRuins/clericden.dmm +++ b/_maps/RandomRuins/SpaceRuins/clericden.dmm @@ -131,7 +131,7 @@ /area/ruin/space) "E" = ( /obj/effect/decal/cleanable/blood/tracks, -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /turf/open/floor/carpet/airless, /area/ruin/space) "F" = ( @@ -171,7 +171,7 @@ /turf/open/floor/plating/airless, /area/ruin/space) "N" = ( -/mob/living/simple_animal/hostile/construct/proteon, +/mob/living/basic/construct/proteon, /turf/open/misc/asteroid/airless, /area/ruin/space) "O" = ( diff --git a/_maps/RandomRuins/SpaceRuins/clownplanet.dmm b/_maps/RandomRuins/SpaceRuins/clownplanet.dmm index b0cc8cc29e20..fa0b50cc4435 100644 --- a/_maps/RandomRuins/SpaceRuins/clownplanet.dmm +++ b/_maps/RandomRuins/SpaceRuins/clownplanet.dmm @@ -26,7 +26,7 @@ /turf/open/floor/engine, /area/ruin/space/has_grav/powered/clownplanet) "ah" = ( -/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/destroyer, +/mob/living/basic/clown/clownhulk/destroyer, /turf/open/floor/engine, /area/ruin/space/has_grav/powered/clownplanet) "ai" = ( diff --git a/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm b/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm index da21f9ffb4a0..52b7347f1353 100644 --- a/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm +++ b/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm @@ -1334,10 +1334,7 @@ /turf/open/floor/iron/dark, /area/ruin/space/has_grav/dangerous_research/lab) "re" = ( -/mob/living/simple_animal/hostile/heretic_summon/raw_prophet{ - AIStatus = 1; - stop_automated_movement = 0 - }, +/mob/living/basic/heretic_summon/raw_prophet/ruins, /turf/open/floor/plating/rust, /area/ruin/space/has_grav/dangerous_research/medical) "ri" = ( @@ -2017,10 +2014,7 @@ /obj/effect/decal/cleanable/glass, /obj/item/shard, /obj/item/stack/sheet/iron, -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit{ - AIStatus = 1; - stop_automated_movement = 0 - }, +/mob/living/basic/heretic_summon/rust_walker, /turf/open/floor/plating/rust, /area/ruin/space/has_grav/dangerous_research/lab) "zR" = ( @@ -2050,10 +2044,7 @@ dir = 1 }, /obj/effect/decal/cleanable/blood/footprints, -/mob/living/simple_animal/hostile/heretic_summon/raw_prophet{ - AIStatus = 1; - stop_automated_movement = 0 - }, +/mob/living/basic/heretic_summon/raw_prophet/ruins, /obj/effect/turf_decal/tile/blue/fourcorners, /turf/open/floor/iron/white, /area/ruin/space/has_grav/dangerous_research/medical) @@ -3751,10 +3742,7 @@ /turf/open/floor/engine/airless, /area/ruin/space/has_grav/dangerous_research/maint) "Xh" = ( -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit{ - AIStatus = 1; - stop_automated_movement = 0 - }, +/mob/living/basic/heretic_summon/rust_walker, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/structure/cable, @@ -3896,10 +3884,7 @@ /turf/open/floor/iron/white, /area/ruin/space/has_grav/dangerous_research/medical) "YZ" = ( -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit{ - AIStatus = 1; - stop_automated_movement = 0 - }, +/mob/living/basic/heretic_summon/rust_walker, /turf/open/floor/plating/rust, /area/ruin/space/has_grav/dangerous_research/lab) "Zc" = ( diff --git a/_maps/RandomRuins/SpaceRuins/forgottenship.dmm b/_maps/RandomRuins/SpaceRuins/forgottenship.dmm index 27626b3466d4..fb7e04b2d32e 100644 --- a/_maps/RandomRuins/SpaceRuins/forgottenship.dmm +++ b/_maps/RandomRuins/SpaceRuins/forgottenship.dmm @@ -295,7 +295,7 @@ /area/ruin/space/has_grav/syndicate_forgotten_cargopod) "bc" = ( /obj/machinery/light/directional/south, -/mob/living/simple_animal/hostile/nanotrasen/ranged/assault, +/mob/living/basic/trooper/nanotrasen/ranged/assault, /turf/open/floor/mineral/plastitanium/red, /area/ruin/space/has_grav/syndicate_forgotten_ship) "bd" = ( @@ -657,7 +657,7 @@ /area/ruin/space/has_grav/syndicate_forgotten_ship) "cd" = ( /obj/machinery/light/directional/south, -/mob/living/simple_animal/hostile/nanotrasen/ranged/assault, +/mob/living/basic/trooper/nanotrasen/ranged/assault, /turf/open/floor/iron/dark, /area/ruin/space/has_grav/syndicate_forgotten_ship) "ce" = ( @@ -812,7 +812,7 @@ dir = 10 }, /obj/item/wrench, -/mob/living/simple_animal/hostile/nanotrasen/ranged/assault, +/mob/living/basic/trooper/nanotrasen/ranged/assault, /turf/open/floor/mineral/plastitanium/red, /area/ruin/space/has_grav/syndicate_forgotten_ship) "cz" = ( @@ -971,7 +971,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 4 }, -/mob/living/simple_animal/hostile/nanotrasen/elite, +/mob/living/basic/trooper/nanotrasen/ranged/elite, /turf/open/floor/mineral/plastitanium, /area/ruin/space/has_grav/syndicate_forgotten_ship) "cR" = ( diff --git a/_maps/RandomRuins/SpaceRuins/prey_pod.dmm b/_maps/RandomRuins/SpaceRuins/prey_pod.dmm index 9089dea664c0..6f53409b8735 100644 --- a/_maps/RandomRuins/SpaceRuins/prey_pod.dmm +++ b/_maps/RandomRuins/SpaceRuins/prey_pod.dmm @@ -17,7 +17,7 @@ /obj/structure/chair/comfy/shuttle{ dir = 4 }, -/mob/living/simple_animal/hostile/asteroid/hivelord, +/mob/living/basic/mining/hivelord, /turf/open/floor/mineral/titanium/white/airless, /area/ruin/space/has_grav) "p" = ( @@ -32,7 +32,7 @@ /area/ruin/space/has_grav) "D" = ( /obj/structure/chair/comfy/shuttle, -/mob/living/simple_animal/hostile/asteroid/hivelord, +/mob/living/basic/mining/hivelord, /turf/open/floor/mineral/titanium/white/airless, /area/ruin/space/has_grav) "E" = ( @@ -57,7 +57,7 @@ /turf/open/floor/mineral/titanium/white/airless, /area/ruin/space/has_grav) "S" = ( -/mob/living/simple_animal/hostile/asteroid/hivelord, +/mob/living/basic/mining/hivelord, /turf/open/floor/mineral/titanium/white/airless, /area/ruin/space/has_grav) "T" = ( diff --git a/_maps/RandomRuins/SpaceRuins/spacehotel.dmm b/_maps/RandomRuins/SpaceRuins/spacehotel.dmm index cafa74f8d3f5..e1bfb59abaa2 100644 --- a/_maps/RandomRuins/SpaceRuins/spacehotel.dmm +++ b/_maps/RandomRuins/SpaceRuins/spacehotel.dmm @@ -772,7 +772,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/light/directional/east, -/mob/living/basic/syndicate/ranged/smg/space, +/mob/living/basic/trooper/syndicate/ranged/smg/space, /turf/open/floor/iron/dark/textured, /area/ruin/space/has_grav/hotel/security) "gQ" = ( @@ -875,7 +875,7 @@ "hH" = ( /obj/structure/cable, /obj/machinery/airalarm/directional/north, -/mob/living/basic/syndicate/melee/sword/space/stormtrooper, +/mob/living/basic/trooper/syndicate/melee/sword/space/stormtrooper, /turf/open/floor/glass/reinforced, /area/ruin/space/has_grav/hotel/security) "hI" = ( @@ -1645,7 +1645,7 @@ /obj/effect/turf_decal/trimline/yellow/warning{ dir = 10 }, -/mob/living/basic/syndicate/ranged/shotgun/space/stormtrooper, +/mob/living/basic/trooper/syndicate/ranged/shotgun/space/stormtrooper, /obj/effect/decal/cleanable/blood/drip, /obj/effect/decal/cleanable/blood, /turf/open/floor/iron/dark/textured, @@ -1907,7 +1907,7 @@ /area/ruin/space/has_grav/hotel/security) "pr" = ( /obj/effect/decal/cleanable/dirt/dust, -/mob/living/basic/syndicate/melee/space/stormtrooper, +/mob/living/basic/trooper/syndicate/melee/space/stormtrooper, /turf/open/floor/iron/dark/textured_large, /area/ruin/space/has_grav/hotel/dock) "pz" = ( @@ -1993,7 +1993,7 @@ /turf/open/misc/beach/sand, /area/ruin/space/has_grav/hotel/pool) "qx" = ( -/mob/living/basic/syndicate/melee/sword/space/stormtrooper, +/mob/living/basic/trooper/syndicate/melee/sword/space/stormtrooper, /turf/open/floor/glass/reinforced, /area/ruin/space/has_grav/hotel/security) "qz" = ( diff --git a/_maps/RandomRuins/SpaceRuins/the_faceoff.dmm b/_maps/RandomRuins/SpaceRuins/the_faceoff.dmm index dd6656c22cdd..1c727e56d91c 100644 --- a/_maps/RandomRuins/SpaceRuins/the_faceoff.dmm +++ b/_maps/RandomRuins/SpaceRuins/the_faceoff.dmm @@ -174,8 +174,7 @@ /turf/open/floor/iron/dark/airless, /area/ruin/space) "eI" = ( -/obj/item/ammo_casing/a357, -/mob/living/basic/syndicate/melee/space, +/mob/living/basic/trooper/syndicate/melee/space, /turf/open/floor/iron/dark/airless, /area/ruin/space) "eU" = ( @@ -699,7 +698,14 @@ /obj/effect/decal/cleanable/dirt/dust, /obj/effect/turf_decal/stripes/full, /obj/item/grenade/c4, -/mob/living/simple_animal/hostile/mining_drone, +/obj/effect/decal/cleanable/dirt, +/obj/effect/mob_spawn/corpse/human/russian, +/obj/effect/decal/cleanable/blood, +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/cleanable/dirt, +/obj/effect/turf_decal/stripes/full, +/mob/living/basic/mining_drone, +/obj/item/grenade/c4, /turf/open/misc/asteroid/basalt/airless, /area/ruin/space) "Do" = ( diff --git a/_maps/RandomRuins/SpaceRuins/the_outlet.dmm b/_maps/RandomRuins/SpaceRuins/the_outlet.dmm index 44721abb8966..05a9867e8ca7 100644 --- a/_maps/RandomRuins/SpaceRuins/the_outlet.dmm +++ b/_maps/RandomRuins/SpaceRuins/the_outlet.dmm @@ -103,7 +103,7 @@ /turf/open/floor/eighties, /area/ruin/space/has_grav/the_outlet/storefront) "cK" = ( -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /obj/machinery/light/small/directional/west, /obj/effect/decal/cleanable/blood{ icon_state = "floor2-old" @@ -1064,7 +1064,7 @@ /obj/effect/decal/cleanable/dirt{ icon_state = "dirt-4" }, -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /obj/effect/decal/cleanable/crayon{ icon_state = "rune6" }, @@ -1238,7 +1238,7 @@ /area/ruin/space/has_grav/the_outlet/storefront) "Db" = ( /obj/effect/decal/cleanable/crayon, -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /turf/open/floor/cult, /area/ruin/space/has_grav/the_outlet/cultinfluence) "Do" = ( @@ -1493,7 +1493,7 @@ /obj/effect/decal/cleanable/crayon{ icon_state = "rune2" }, -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /turf/open/floor/iron/white, /area/ruin/space/has_grav/the_outlet/researchrooms) "IF" = ( @@ -1585,7 +1585,7 @@ /turf/template_noop, /area/template_noop) "LL" = ( -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /obj/effect/decal/cleanable/crayon{ icon_state = "rune2" }, @@ -1645,7 +1645,7 @@ /obj/effect/decal/cleanable/blood{ icon_state = "gib2-old" }, -/mob/living/simple_animal/hostile/construct/juggernaut/hostile, +/mob/living/basic/construct/juggernaut/hostile, /turf/open/floor/cult, /area/ruin/space/has_grav/the_outlet/cultinfluence) "MR" = ( @@ -1686,7 +1686,7 @@ /obj/effect/decal/cleanable/crayon{ icon_state = "rune4" }, -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /turf/open/floor/cult, /area/ruin/space/has_grav/the_outlet/cultinfluence) "NE" = ( @@ -2013,7 +2013,7 @@ dir = 8 }, /obj/effect/decal/cleanable/crayon, -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /obj/effect/decal/cleanable/dirt{ icon_state = "dirt-127" }, @@ -2142,7 +2142,7 @@ /turf/open/floor/iron/white, /area/ruin/space/has_grav/the_outlet/researchrooms) "Yr" = ( -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /obj/effect/decal/cleanable/crayon, /turf/open/floor/cult, /area/ruin/space/has_grav/the_outlet/cultinfluence) diff --git a/_maps/RandomRuins/SpaceRuins/waystation.dmm b/_maps/RandomRuins/SpaceRuins/waystation.dmm index 6dac443f81ab..9f4c138fec8c 100644 --- a/_maps/RandomRuins/SpaceRuins/waystation.dmm +++ b/_maps/RandomRuins/SpaceRuins/waystation.dmm @@ -73,7 +73,7 @@ /turf/open/floor/plating, /area/ruin/space/has_grav/waystation) "bA" = ( -/mob/living/basic/syndicate/melee/sword/space/stormtrooper, +/mob/living/basic/trooper/syndicate/melee/sword/space/stormtrooper, /turf/open/floor/iron/dark, /area/ruin/space/has_grav/waystation/securestorage) "bG" = ( @@ -1960,7 +1960,7 @@ /turf/open/floor/plating, /area/ruin/space/has_grav/waystation/cargobay) "JM" = ( -/mob/living/basic/syndicate/ranged/shotgun/space, +/mob/living/basic/trooper/syndicate/ranged/shotgun/space, /turf/open/floor/iron, /area/ruin/space/has_grav/waystation/cargobay) "Kc" = ( @@ -2120,7 +2120,7 @@ "LW" = ( /obj/effect/turf_decal/loading_area/red, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/mob/living/basic/syndicate/ranged/smg/space, +/mob/living/basic/trooper/syndicate/ranged/smg/space, /obj/structure/cable, /turf/open/floor/iron, /area/ruin/space/has_grav/waystation/cargobay) @@ -2562,19 +2562,7 @@ /turf/open/floor/iron, /area/ruin/space/has_grav/waystation/cargobay) "SJ" = ( -/mob/living/basic/syndicate/ranged/shotgun/space, -/obj/item/ammo_casing/shotgun/buckshot{ - pixel_x = 6; - pixel_y = 1 - }, -/obj/item/ammo_casing/shotgun/buckshot{ - pixel_x = 3; - pixel_y = 10 - }, -/obj/item/ammo_casing/shotgun/buckshot{ - pixel_x = -3; - pixel_y = 1 - }, +/mob/living/basic/trooper/syndicate/ranged/shotgun/space, /turf/open/floor/iron, /area/ruin/space/has_grav/waystation/dorms) "SL" = ( @@ -2686,7 +2674,7 @@ /area/ruin/space/has_grav/waystation/dorms) "UC" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/mob/living/basic/syndicate/ranged/smg/space, +/mob/living/basic/trooper/syndicate/ranged/smg/space, /obj/structure/cable, /turf/open/floor/iron, /area/ruin/space/has_grav/waystation/cargooffice) diff --git a/_maps/RandomZLevels/SnowCabin.dmm b/_maps/RandomZLevels/SnowCabin.dmm index e9661b18531c..f28f8e70e8d1 100644 --- a/_maps/RandomZLevels/SnowCabin.dmm +++ b/_maps/RandomZLevels/SnowCabin.dmm @@ -3044,12 +3044,11 @@ /obj/effect/decal/cleanable/dirt, /obj/effect/decal/cleanable/blood, /obj/effect/decal/cleanable/blood/gibs, -/mob/living/simple_animal/hostile/skeleton{ +/mob/living/basic/skeleton{ desc = "Oh shit!"; dir = 1; faction = list("sewer"); - name = "sewer skeleton"; - wander = 0 + name = "sewer skeleton" }, /turf/open/floor/carpet, /area/awaymission/cabin/caves/mountain) @@ -3859,14 +3858,13 @@ /turf/open/misc/asteroid/snow/snow_cabin, /area/awaymission/cabin/caves) "yj" = ( -/mob/living/simple_animal/hostile/skeleton/ice{ +/mob/living/basic/skeleton/ice{ desc = "A reanimated skeleton covered in thick sheet of natural ice. It is obvious to tell that they look really slow."; maxHealth = 20; melee_damage_lower = 5; melee_damage_upper = 5; name = "frozen skeleton"; - speed = 7; - wander = 0 + speed = 7 }, /turf/open/misc/ice/smooth, /area/awaymission/cabin/caves) @@ -3899,7 +3897,7 @@ /obj/item/shovel{ desc = "A large tool for digging and moving snow."; force = 10; - name = "eskimo shovel" + name = "settler's shovel" }, /obj/effect/decal/remains/human{ color = "#72e4fa" @@ -4036,14 +4034,13 @@ /turf/open/misc/asteroid/snow/snow_cabin, /area/awaymission/cabin/caves) "BR" = ( -/mob/living/simple_animal/hostile/skeleton/ice{ +/mob/living/basic/skeleton/ice{ desc = "A reanimated skeleton covered in thick sheet of natural ice. It is obvious to tell that they look really slow."; maxHealth = 20; melee_damage_lower = 5; melee_damage_upper = 5; name = "frozen skeleton"; - speed = 7; - wander = 0 + speed = 7 }, /turf/open/misc/asteroid/snow/snow_cabin, /area/awaymission/cabin/caves) diff --git a/_maps/RandomZLevels/TheBeach.dmm b/_maps/RandomZLevels/TheBeach.dmm index 29098c05096d..a34169c53478 100644 --- a/_maps/RandomZLevels/TheBeach.dmm +++ b/_maps/RandomZLevels/TheBeach.dmm @@ -930,7 +930,7 @@ /turf/open/water/beach/biodome, /area/awaymission/beach) "da" = ( -/mob/living/simple_animal/parrot, +/mob/living/basic/parrot, /turf/open/misc/beach/sand, /area/awaymission/beach) "db" = ( diff --git a/_maps/RandomZLevels/caves.dmm b/_maps/RandomZLevels/caves.dmm index 40b88aa6addf..0263f8789372 100644 --- a/_maps/RandomZLevels/caves.dmm +++ b/_maps/RandomZLevels/caves.dmm @@ -283,7 +283,7 @@ }, /area/awaymission/caves/bmp_asteroid/level_three) "bD" = ( -/mob/living/simple_animal/hostile/skeleton, +/mob/living/basic/skeleton, /turf/open/floor/engine/cult{ initial_gas_mix = "n2=23;o2=14;TEMP=2.7" }, @@ -360,7 +360,7 @@ }, /area/awaymission/caves/bmp_asteroid/level_two) "bY" = ( -/mob/living/simple_animal/hostile/skeleton, +/mob/living/basic/skeleton, /turf/open/floor/plating{ initial_gas_mix = "n2=23;o2=14;TEMP=2.7" }, @@ -1210,7 +1210,7 @@ "he" = ( /obj/structure/window/spawner/directional/west, /obj/structure/window/spawner/directional/south, -/mob/living/simple_animal/hostile/mining_drone, +/mob/living/basic/mining_drone, /turf/open/floor/plating, /area/awaymission/caves/listeningpost) "hq" = ( @@ -1734,7 +1734,7 @@ /area/awaymission/caves/bmp_asteroid/level_three) "IH" = ( /obj/structure/window/spawner/directional/west, -/mob/living/simple_animal/hostile/mining_drone, +/mob/living/basic/mining_drone, /turf/open/floor/plating, /area/awaymission/caves/listeningpost) "IN" = ( @@ -2006,7 +2006,7 @@ }, /area/awaymission/caves/bmp_asteroid/level_three) "YZ" = ( -/mob/living/simple_animal/hostile/skeleton, +/mob/living/basic/skeleton, /turf/open/misc/asteroid/basalt{ initial_gas_mix = "n2=23;o2=14;TEMP=2.7" }, diff --git a/_maps/RandomZLevels/moonoutpost19.dmm b/_maps/RandomZLevels/moonoutpost19.dmm index 28b4725a1600..759bd7198736 100644 --- a/_maps/RandomZLevels/moonoutpost19.dmm +++ b/_maps/RandomZLevels/moonoutpost19.dmm @@ -5450,6 +5450,10 @@ heat_capacity = 1e+006 }, /area/awaymission/moonoutpost19/research) +"pO" = ( +/mob/living/basic/construct/proteon/hostile, +/turf/open/space, +/area/space) "qr" = ( /obj/effect/decal/cleanable/blood/tracks{ desc = "Your instincts say you shouldn't be following these."; @@ -13954,7 +13958,7 @@ aa aa aa aa -aa +pO aa aa aa @@ -14731,6 +14735,7 @@ aa aa aa aa +pO aa aa aa @@ -14741,8 +14746,7 @@ aa aa aa aa -aa -aa +pO aa aa aa @@ -17041,7 +17045,7 @@ aa aa aa aa -aa +pO aa aa aa @@ -17310,13 +17314,13 @@ aa aa aa aa +pO aa aa aa aa aa -aa -aa +pO aa aa aa @@ -18312,7 +18316,7 @@ aa aa aa aa -aa +pO aa aa aa @@ -18838,6 +18842,7 @@ aa aa aa aa +pO aa aa aa @@ -18851,8 +18856,7 @@ aa aa aa aa -aa -aa +pO aa aa aa @@ -20107,7 +20111,7 @@ aa aa aa aa -aa +pO aa aa aa @@ -20400,7 +20404,7 @@ aa aa aa aa -aa +pO aa aa aa @@ -21644,7 +21648,7 @@ aa aa aa aa -aa +pO aa aa aa @@ -21900,7 +21904,7 @@ aa aa aa aa -aa +pO aa aa aa diff --git a/_maps/RandomZLevels/research.dmm b/_maps/RandomZLevels/research.dmm index bc6878f09530..cd0fab883407 100644 --- a/_maps/RandomZLevels/research.dmm +++ b/_maps/RandomZLevels/research.dmm @@ -72,7 +72,7 @@ /turf/closed/wall/r_wall, /area/awaymission/research/interior/engineering) "as" = ( -/mob/living/basic/syndicate/ranged/smg, +/mob/living/basic/trooper/syndicate/ranged/smg, /turf/open/floor/mineral/plastitanium/red, /area/awaymission/research/interior/engineering) "at" = ( @@ -113,7 +113,7 @@ /obj/structure/chair{ dir = 8 }, -/mob/living/basic/syndicate, +/mob/living/basic/trooper/syndicate, /turf/open/floor/mineral/plastitanium/red, /area/awaymission/research/interior/engineering) "az" = ( @@ -207,7 +207,7 @@ /turf/open/floor/plating, /area/awaymission/research/interior/engineering) "aT" = ( -/mob/living/basic/syndicate/melee/sword, +/mob/living/basic/trooper/syndicate/melee/sword, /turf/open/floor/plating, /area/awaymission/research/interior/engineering) "aU" = ( @@ -575,7 +575,7 @@ /area/awaymission/research/interior/maint) "cz" = ( /obj/structure/cable, -/mob/living/basic/syndicate/ranged/smg, +/mob/living/basic/trooper/syndicate/ranged/smg, /turf/open/floor/plating, /area/awaymission/research/interior/maint) "cA" = ( @@ -603,7 +603,7 @@ /area/awaymission/research/interior/gateway) "cL" = ( /obj/structure/cable, -/mob/living/basic/syndicate/ranged, +/mob/living/basic/trooper/syndicate/ranged, /turf/open/floor/plating, /area/awaymission/research/interior/maint) "cM" = ( @@ -718,7 +718,7 @@ /area/awaymission/research/interior) "dn" = ( /obj/item/ammo_casing/c45, -/mob/living/basic/syndicate, +/mob/living/basic/trooper/syndicate, /obj/effect/turf_decal/tile/yellow/half/contrasted{ dir = 8 }, @@ -801,7 +801,7 @@ /turf/open/floor/iron/dark, /area/awaymission/research/interior/secure) "dP" = ( -/mob/living/basic/syndicate/ranged/smg, +/mob/living/basic/trooper/syndicate/ranged/smg, /obj/effect/turf_decal/tile/blue/fourcorners, /turf/open/floor/iron/white, /area/awaymission/research/interior/medbay) @@ -848,7 +848,7 @@ /turf/open/floor/iron/stairs, /area/awaymission/research/interior/genetics) "ec" = ( -/mob/living/basic/syndicate/ranged/smg, +/mob/living/basic/trooper/syndicate/ranged/smg, /turf/open/floor/plating, /area/awaymission/research/interior/maint) "ed" = ( @@ -947,7 +947,7 @@ /turf/open/floor/iron/white, /area/awaymission/research/interior/cryo) "eB" = ( -/mob/living/simple_animal/hostile/nanotrasen/ranged, +/mob/living/basic/trooper/nanotrasen/ranged, /obj/effect/turf_decal/tile/purple/half/contrasted{ dir = 1 }, @@ -996,7 +996,7 @@ /turf/open/floor/iron/white, /area/awaymission/research/interior) "eL" = ( -/mob/living/basic/syndicate, +/mob/living/basic/trooper/syndicate, /obj/effect/turf_decal/tile/yellow/anticorner/contrasted{ dir = 4 }, @@ -1048,7 +1048,7 @@ /turf/open/floor/iron/dark, /area/awaymission/research/interior/secure) "eV" = ( -/mob/living/simple_animal/hostile/nanotrasen/ranged/smg, +/mob/living/basic/trooper/nanotrasen/ranged/smg, /turf/open/floor/iron/dark, /area/awaymission/research/interior/secure) "eW" = ( @@ -1134,7 +1134,7 @@ /turf/open/floor/iron/white, /area/awaymission/research/interior) "fn" = ( -/mob/living/basic/syndicate/melee/sword, +/mob/living/basic/trooper/syndicate/melee/sword, /turf/open/floor/iron/white, /area/awaymission/research/interior) "fo" = ( @@ -1201,7 +1201,7 @@ /area/awaymission/research/interior/secure) "fA" = ( /obj/machinery/light/small/directional/west, -/mob/living/simple_animal/hostile/nanotrasen/ranged/smg, +/mob/living/basic/trooper/nanotrasen/ranged/smg, /turf/open/floor/iron/dark, /area/awaymission/research/interior/secure) "fD" = ( @@ -1331,7 +1331,7 @@ /area/awaymission/research/interior/secure) "gj" = ( /obj/structure/cable, -/mob/living/simple_animal/hostile/nanotrasen/ranged/smg, +/mob/living/basic/trooper/nanotrasen/ranged/smg, /turf/open/floor/iron/dark, /area/awaymission/research/interior/secure) "gk" = ( @@ -1511,7 +1511,7 @@ /turf/open/floor/iron/white, /area/awaymission/research/interior/security) "hq" = ( -/mob/living/simple_animal/hostile/nanotrasen/ranged/smg, +/mob/living/basic/trooper/nanotrasen/ranged/smg, /obj/effect/turf_decal/tile/red/half/contrasted{ dir = 1 }, @@ -1858,7 +1858,7 @@ /turf/open/floor/iron/freezer, /area/awaymission/research/interior/bathroom) "iQ" = ( -/mob/living/basic/syndicate, +/mob/living/basic/trooper/syndicate, /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 4 }, @@ -3093,7 +3093,7 @@ /turf/open/floor/iron, /area/awaymission/research/interior/security) "sn" = ( -/mob/living/basic/syndicate/ranged/smg, +/mob/living/basic/trooper/syndicate/ranged/smg, /obj/effect/turf_decal/tile/green/fourcorners, /turf/open/floor/iron/white, /area/awaymission/research/interior) @@ -3265,7 +3265,7 @@ pixel_x = 32; pixel_y = 26 }, -/mob/living/basic/syndicate/ranged/smg, +/mob/living/basic/trooper/syndicate/ranged/smg, /obj/effect/turf_decal/tile/green/fourcorners, /turf/open/floor/iron/white, /area/awaymission/research/interior) @@ -3283,7 +3283,7 @@ /turf/open/floor/iron/white, /area/awaymission/research/interior) "yW" = ( -/mob/living/simple_animal/hostile/nanotrasen, +/mob/living/basic/trooper/nanotrasen, /obj/effect/turf_decal/tile/purple/fourcorners, /turf/open/floor/iron, /area/awaymission/research/interior/cryo) @@ -3343,7 +3343,7 @@ /turf/open/floor/iron, /area/awaymission/research/interior/genetics) "BQ" = ( -/mob/living/simple_animal/hostile/nanotrasen/ranged/smg, +/mob/living/basic/trooper/nanotrasen/ranged/smg, /obj/effect/turf_decal/tile/red/fourcorners, /turf/open/floor/iron/white, /area/awaymission/research/interior/security) @@ -3413,7 +3413,7 @@ /turf/open/floor/iron, /area/awaymission/research/interior/genetics) "DY" = ( -/mob/living/simple_animal/hostile/nanotrasen/ranged, +/mob/living/basic/trooper/nanotrasen/ranged, /obj/effect/turf_decal/tile/purple/fourcorners, /turf/open/floor/iron/white, /area/awaymission/research/interior/cryo) @@ -3823,7 +3823,7 @@ /turf/open/floor/iron, /area/awaymission/research/interior/genetics) "Qq" = ( -/mob/living/simple_animal/hostile/nanotrasen/ranged, +/mob/living/basic/trooper/nanotrasen/ranged, /obj/effect/turf_decal/tile/red/fourcorners, /turf/open/floor/iron, /area/awaymission/research/interior/security) @@ -3892,7 +3892,7 @@ /turf/open/misc/asteroid, /area/awaymission/research/exterior) "Th" = ( -/mob/living/basic/syndicate, +/mob/living/basic/trooper/syndicate, /obj/effect/turf_decal/tile/blue/fourcorners, /turf/open/floor/iron/white, /area/awaymission/research/interior/medbay) @@ -3951,7 +3951,7 @@ /turf/open/floor/plating, /area/awaymission/research/interior/maint) "Uq" = ( -/mob/living/basic/syndicate/ranged/smg/space, +/mob/living/basic/trooper/syndicate/ranged/smg/space, /turf/open/misc/asteroid/airless, /area/awaymission/research/exterior) "Uu" = ( diff --git a/_maps/RandomZLevels/snowdin.dmm b/_maps/RandomZLevels/snowdin.dmm index 5ee092df524a..962a25801369 100644 --- a/_maps/RandomZLevels/snowdin.dmm +++ b/_maps/RandomZLevels/snowdin.dmm @@ -6207,7 +6207,7 @@ /turf/open/floor/engine/cult, /area/awaymission/snowdin/post/cavern2) "st" = ( -/mob/living/simple_animal/hostile/skeleton/ice, +/mob/living/basic/skeleton/ice, /turf/open/misc/asteroid/snow{ floor_variance = 0; icon_state = "snow_dug"; @@ -6430,7 +6430,7 @@ /turf/open/misc/asteroid/snow, /area/awaymission/snowdin/outside) "tt" = ( -/mob/living/simple_animal/hostile/skeleton/plasmaminer/jackhammer, +/mob/living/basic/skeleton/plasmaminer/jackhammer, /turf/open/floor/plating/snowed/cavern, /area/awaymission/snowdin/cave/cavern) "tu" = ( @@ -6443,7 +6443,7 @@ /turf/open/floor/plating/snowed/cavern, /area/awaymission/snowdin/cave/cavern) "tz" = ( -/mob/living/simple_animal/hostile/skeleton/ice, +/mob/living/basic/skeleton/ice, /turf/open/floor/plating/snowed/cavern, /area/awaymission/snowdin/cave/cavern) "tE" = ( @@ -6573,7 +6573,7 @@ /area/awaymission/snowdin/post/cavern1) "uK" = ( /obj/structure/cable, -/mob/living/simple_animal/hostile/skeleton/plasmaminer, +/mob/living/basic/skeleton/plasmaminer, /turf/open/floor/iron, /area/awaymission/snowdin/post/cavern1) "uM" = ( @@ -6595,7 +6595,7 @@ /turf/open/floor/plating, /area/awaymission/snowdin/post/cavern1) "uS" = ( -/mob/living/simple_animal/hostile/skeleton/plasmaminer, +/mob/living/basic/skeleton/plasmaminer, /obj/effect/mapping_helpers/broken_floor, /turf/open/floor/plating, /area/awaymission/snowdin/post/cavern1) @@ -6678,7 +6678,7 @@ }, /area/awaymission/snowdin/outside) "vi" = ( -/mob/living/simple_animal/hostile/skeleton/templar, +/mob/living/basic/skeleton/templar, /turf/open/floor/engine/cult{ initial_gas_mix = "n2=82;plasma=24;TEMP=120"; temperature = 120 @@ -7197,7 +7197,7 @@ /obj/structure/bed{ dir = 4 }, -/mob/living/simple_animal/hostile/skeleton/ice{ +/mob/living/basic/skeleton/ice{ name = "Privateer Jones" }, /turf/open/misc/asteroid/snow{ @@ -7450,7 +7450,7 @@ /obj/effect/turf_decal/weather/snow/corner{ dir = 4 }, -/mob/living/simple_animal/hostile/skeleton/eskimo, +/mob/living/basic/skeleton/settler, /turf/open/misc/asteroid/snow{ floor_variance = 0; icon_state = "snow_dug"; @@ -7856,7 +7856,7 @@ /turf/open/floor/plating/snowed/smoothed, /area/awaymission/snowdin/cave) "AA" = ( -/mob/living/simple_animal/hostile/skeleton/eskimo, +/mob/living/basic/skeleton/settler, /turf/open/floor/wood, /area/awaymission/snowdin/igloo) "AC" = ( @@ -10490,7 +10490,7 @@ /area/awaymission/snowdin/post/mining_dock) "Ma" = ( /obj/effect/turf_decal/weather/snow/corner, -/mob/living/simple_animal/hostile/skeleton/eskimo, +/mob/living/basic/skeleton/settler, /turf/open/misc/asteroid/snow{ floor_variance = 0; icon_state = "snow_dug"; @@ -11145,7 +11145,7 @@ /obj/effect/turf_decal/weather/snow/corner{ dir = 4 }, -/mob/living/simple_animal/hostile/skeleton/eskimo, +/mob/living/basic/skeleton/settler, /turf/open/misc/asteroid/snow{ floor_variance = 0; icon_state = "snow_dug"; @@ -11517,7 +11517,7 @@ /turf/open/misc/asteroid/snow, /area/awaymission/snowdin/cave) "Sb" = ( -/mob/living/simple_animal/hostile/skeleton/plasmaminer/jackhammer, +/mob/living/basic/skeleton/plasmaminer/jackhammer, /turf/open/misc/asteroid/snow/ice, /area/awaymission/snowdin/cave/cavern) "Sd" = ( @@ -11821,7 +11821,7 @@ /turf/open/floor/iron, /area/awaymission/snowdin/post/gateway) "TA" = ( -/mob/living/simple_animal/hostile/skeleton/eskimo, +/mob/living/basic/skeleton/settler, /turf/open/misc/asteroid/snow, /area/awaymission/snowdin/outside) "TD" = ( @@ -11866,7 +11866,7 @@ /turf/open/floor/iron/dark, /area/awaymission/snowdin/cave) "TW" = ( -/mob/living/simple_animal/hostile/skeleton/plasmaminer, +/mob/living/basic/skeleton/plasmaminer, /turf/open/floor/plating/snowed/cavern, /area/awaymission/snowdin/cave/cavern) "TY" = ( @@ -12023,7 +12023,7 @@ /turf/open/misc/asteroid/snow/ice, /area/awaymission/snowdin/cave/cavern) "UM" = ( -/mob/living/simple_animal/hostile/skeleton/eskimo, +/mob/living/basic/skeleton/settler, /turf/open/misc/asteroid/snow{ floor_variance = 0; icon_state = "snow_dug"; @@ -12060,7 +12060,7 @@ /turf/open/floor/iron/white, /area/awaymission/snowdin/post/mining_main/robotics) "Ve" = ( -/mob/living/simple_animal/hostile/skeleton/plasmaminer, +/mob/living/basic/skeleton/plasmaminer, /turf/open/misc/asteroid/snow/ice, /area/awaymission/snowdin/cave/cavern) "Vf" = ( @@ -12362,7 +12362,7 @@ /turf/open/floor/plating, /area/awaymission/snowdin/post/mining_main/mechbay) "Xb" = ( -/mob/living/simple_animal/hostile/skeleton/eskimo, +/mob/living/basic/skeleton/settler, /turf/open/floor/plating, /area/awaymission/snowdin/post/mining_main) "Xd" = ( @@ -12756,7 +12756,7 @@ /turf/open/misc/asteroid/snow, /area/awaymission/snowdin/post/minipost) "Zv" = ( -/mob/living/simple_animal/hostile/skeleton/ice{ +/mob/living/basic/skeleton/ice{ name = "Captain Bones" }, /turf/open/misc/asteroid/snow{ diff --git a/_maps/map_files/Basketball/ash_gladiators.dmm b/_maps/map_files/Basketball/ash_gladiators.dmm new file mode 100644 index 000000000000..75315e028a83 --- /dev/null +++ b/_maps/map_files/Basketball/ash_gladiators.dmm @@ -0,0 +1,1657 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"af" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 1 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"ao" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"at" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"aw" = ( +/obj/structure/fluff/drake_statue, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"bA" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"cc" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/center/cracked, +/obj/structure/stone_tile/surrounding_tile/cracked, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 1 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"cw" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"dg" = ( +/obj/structure/stone_tile/block, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"eD" = ( +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/surrounding_tile{ + dir = 1 + }, +/obj/structure/stone_tile/center/cracked, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 8 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"eM" = ( +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/center/cracked, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 8 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"fj" = ( +/obj/effect/decal/remains/human, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"fu" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 1 + }, +/obj/structure/stone_tile/block, +/obj/item/reagent_containers/cup/glass/trophy/silver_cup, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"fT" = ( +/obj/structure/stone_tile/block{ + dir = 1 + }, +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"gY" = ( +/turf/open/floor/plating, +/area/centcom/basketball) +"hn" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 1 + }, +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/center/cracked, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"hZ" = ( +/obj/item/storage/bag/plants/portaseeder, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 1 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"ie" = ( +/mob/living/basic/mining/gutlunch/warrior, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"ix" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"iB" = ( +/obj/item/spear, +/obj/structure/stone_tile/block/cracked, +/obj/structure/stone_tile/block{ + dir = 1 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"jb" = ( +/obj/structure/stone_tile/surrounding_tile/cracked, +/obj/structure/stone_tile/center, +/obj/structure/stone_tile/surrounding_tile{ + dir = 1 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"jq" = ( +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"ku" = ( +/obj/structure/fence{ + dir = 8 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"kv" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"ld" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"lh" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile/block/cracked, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"lD" = ( +/obj/structure/fence{ + dir = 8 + }, +/obj/structure/stone_tile/block{ + dir = 8 + }, +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"lQ" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile/center, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"mk" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 1 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/obj/structure/stone_tile/center, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"mW" = ( +/obj/structure/stone_tile/surrounding_tile/cracked, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/center, +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"nv" = ( +/obj/machinery/hydroponics/soil, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"oR" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"pV" = ( +/obj/structure/stone_tile/block, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"qF" = ( +/obj/structure/fence/corner{ + dir = 1 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"ro" = ( +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"ru" = ( +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile/cracked, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"rU" = ( +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"sb" = ( +/turf/closed/indestructible/riveted/boss, +/area/centcom/basketball) +"sA" = ( +/obj/effect/decal/remains/xeno, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"un" = ( +/obj/effect/decal/cleanable/blood, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"uH" = ( +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"vo" = ( +/turf/open/floor/fakepit, +/area/centcom/basketball) +"vq" = ( +/obj/structure/fence/corner{ + dir = 8 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"wN" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/block{ + dir = 8 + }, +/obj/effect/landmark/basketball/team_spawn/referee, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"xe" = ( +/obj/item/flashlight/flare/torch, +/obj/structure/stone_tile/block{ + dir = 1 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"xG" = ( +/obj/structure/fence/corner{ + dir = 5 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"xI" = ( +/obj/structure/stone_tile/slab/burnt, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"yk" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/obj/structure/stone_tile/center/cracked, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"yK" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"yO" = ( +/obj/effect/landmark/basketball/team_spawn/home, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"yY" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/table/wood, +/obj/item/spear, +/obj/item/scythe, +/turf/open/indestructible/boss, +/area/centcom/basketball) +"zk" = ( +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"zU" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/center/cracked, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"zZ" = ( +/obj/structure/stone_tile/block/cracked, +/obj/structure/stone_tile/block/cracked{ + dir = 1 + }, +/obj/item/reagent_containers/cup/glass/trophy/bronze_cup, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Aj" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/table/wood, +/obj/item/spear, +/obj/item/storage/belt/utility, +/turf/open/indestructible/boss, +/area/centcom/basketball) +"Aw" = ( +/obj/effect/mob_spawn/corpse/human/miner, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"AA" = ( +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"BD" = ( +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/center, +/obj/structure/stone_tile/surrounding_tile{ + dir = 1 + }, +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"BW" = ( +/obj/structure/stone_tile/block/cracked, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Ct" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Cv" = ( +/obj/structure/hoop/minigame{ + dir = 4 + }, +/obj/effect/landmark/basketball/team_spawn/home_hoop, +/turf/open/floor/fakepit, +/area/centcom/basketball) +"Cw" = ( +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"CI" = ( +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/surrounding_tile{ + dir = 1 + }, +/obj/structure/stone_tile/center, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 8 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Dc" = ( +/obj/structure/stone_tile/block{ + dir = 1 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Di" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/center/cracked, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 4 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"DN" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"DP" = ( +/obj/effect/landmark/basketball/team_spawn/away_hoop, +/obj/structure/hoop/minigame{ + dir = 8 + }, +/turf/open/floor/fakepit, +/area/centcom/basketball) +"DV" = ( +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Eb" = ( +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/center/cracked, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 1 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"EX" = ( +/obj/structure/fence/corner, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Fh" = ( +/obj/item/flashlight/lantern, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Fi" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/center/cracked, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 4 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Fy" = ( +/mob/living/basic/mining/gutlunch/milk, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"FS" = ( +/obj/structure/stone_tile/block/burnt, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Gh" = ( +/obj/structure/stone_tile/block{ + dir = 1 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Hu" = ( +/turf/closed/indestructible/riveted, +/area/centcom/basketball) +"Hx" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Ih" = ( +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile/cracked, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"IM" = ( +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Jb" = ( +/obj/structure/stone_tile/block{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"JX" = ( +/obj/structure/stone_tile/block{ + dir = 4 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Kf" = ( +/obj/structure/stone_tile/slab/cracked, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Kh" = ( +/obj/structure/stone_tile/block{ + dir = 4 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Lu" = ( +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/obj/structure/stone_tile, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Lx" = ( +/obj/effect/landmark/basketball/team_spawn/home, +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"LV" = ( +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/closet/crate/internals, +/obj/item/pickaxe, +/obj/item/pickaxe, +/obj/item/pickaxe, +/obj/item/pickaxe, +/obj/item/pickaxe, +/obj/item/pickaxe, +/obj/effect/mapping_helpers/no_lava, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"MA" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/obj/item/flashlight/flare/torch, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"MN" = ( +/obj/structure/stone_tile/surrounding, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"MR" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Nc" = ( +/obj/structure/stone_tile{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile/block/burnt, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Nt" = ( +/turf/closed/indestructible/rock, +/area/centcom/basketball) +"Nw" = ( +/obj/item/cultivator/rake, +/obj/structure/stone_tile/surrounding/cracked, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"NX" = ( +/obj/structure/stone_tile/block, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"OB" = ( +/obj/effect/landmark/basketball/team_spawn/home, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"OF" = ( +/obj/structure/stone_tile/block{ + dir = 1 + }, +/obj/structure/stone_tile/cracked, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/table/wood, +/obj/item/spear, +/turf/open/indestructible/boss, +/area/centcom/basketball) +"OG" = ( +/obj/effect/mob_spawn/corpse/human/damaged, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"OS" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Ql" = ( +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"QF" = ( +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/center/cracked, +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/surrounding_tile{ + dir = 1 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Rc" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Rl" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile/cracked, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Ry" = ( +/obj/structure/stone_tile/center, +/obj/structure/stone_tile/surrounding_tile, +/obj/structure/stone_tile/surrounding_tile{ + dir = 4 + }, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 1 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Se" = ( +/obj/structure/stone_tile/block{ + dir = 4 + }, +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Tc" = ( +/obj/structure/stone_tile/block/cracked, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Ue" = ( +/obj/structure/fluff/drake_statue/falling, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Uv" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"UL" = ( +/obj/structure/bonfire/dense, +/obj/structure/stone_tile/center, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"UT" = ( +/obj/effect/landmark/basketball/team_spawn/away, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Vc" = ( +/obj/structure/stone_tile/block{ + dir = 8 + }, +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Vf" = ( +/obj/structure/fence, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Vm" = ( +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Vr" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"VI" = ( +/obj/effect/landmark/basketball/team_spawn/away, +/obj/structure/stone_tile, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"VM" = ( +/obj/structure/stone_tile/block{ + dir = 4 + }, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 4 + }, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"VY" = ( +/obj/structure/stone_tile, +/obj/structure/stone_tile{ + dir = 8 + }, +/obj/structure/stone_tile{ + dir = 1 + }, +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/table/wood, +/obj/item/spear, +/obj/item/clothing/head/helmet/roman/legionnaire, +/turf/open/indestructible/boss, +/area/centcom/basketball) +"WE" = ( +/obj/structure/ore_box, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"WH" = ( +/obj/structure/stone_tile/surrounding/cracked, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Xq" = ( +/obj/structure/stone_tile/surrounding_tile{ + dir = 8 + }, +/obj/structure/stone_tile/center, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile/surrounding_tile/cracked{ + dir = 4 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"XQ" = ( +/obj/structure/stone_tile/slab/cracked{ + dir = 4 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"XT" = ( +/obj/structure/stone_tile/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/cracked{ + dir = 8 + }, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/obj/structure/stone_tile/cracked, +/turf/open/lava/smooth/basketball, +/area/centcom/basketball) +"Yl" = ( +/obj/structure/water_source/puddle{ + pixel_x = -3; + pixel_y = 1 + }, +/obj/item/reagent_containers/cup/bucket/wooden, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Yv" = ( +/obj/effect/landmark/basketball/team_spawn/away, +/obj/structure/stone_tile/cracked{ + dir = 1 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"YI" = ( +/obj/structure/stone_tile/block/cracked{ + dir = 4 + }, +/obj/structure/stone_tile/block/cracked{ + dir = 8 + }, +/obj/item/reagent_containers/cup/glass/trophy/gold_cup{ + pixel_x = 0 + }, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"YX" = ( +/obj/structure/lavaland/ash_walker_fake, +/obj/item/toy/basketball, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) +"Zc" = ( +/obj/structure/necropolis_arch, +/obj/structure/stone_tile/slab, +/obj/structure/necropolis_gate/locked, +/turf/open/floor/fakebasalt, +/area/centcom/basketball) + +(1,1,1) = {" +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +"} +(2,1,1) = {" +Hu +gY +gY +gY +gY +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +gY +gY +gY +gY +Hu +"} +(3,1,1) = {" +Hu +gY +gY +gY +gY +Nt +Cw +Ql +Ql +Nt +Nt +WE +LV +Nt +Ql +Ql +Nt +WE +Nt +Nt +gY +gY +gY +gY +Hu +"} +(4,1,1) = {" +Hu +gY +gY +gY +gY +Nt +Cw +Ql +Ql +Ql +Ql +Ql +Ql +Ql +Ql +Ql +Ql +Ql +Ql +Nt +gY +gY +gY +gY +Hu +"} +(5,1,1) = {" +Hu +gY +sb +sb +sb +sb +Cw +Ql +vq +Vf +Vf +Vf +Vf +Vf +Vf +Vf +qF +Ql +Ql +sb +sb +sb +sb +gY +Hu +"} +(6,1,1) = {" +Hu +gY +sb +eD +JX +Eb +Cw +Ql +ku +sA +Ql +vo +Cv +vo +Ql +sA +ku +Ql +Ql +ix +Aj +yY +sb +gY +Hu +"} +(7,1,1) = {" +Hu +gY +sb +dg +aw +Dc +Cw +Ql +ku +Ql +yO +vo +vo +vo +yO +Ql +ku +Fh +Ql +XQ +WH +OF +sb +gY +Hu +"} +(8,1,1) = {" +Hu +sb +sb +Fi +DN +mk +Cw +Ql +ku +Ct +zk +OB +yO +Lx +zk +Ct +ku +Ql +Aw +Vr +MA +VY +sb +sb +Hu +"} +(9,1,1) = {" +Hu +sb +Cw +Cw +Cw +Cw +Cw +Uv +lD +kv +kv +lh +kv +ao +kv +Lu +lD +ao +ao +ao +ao +Nt +Nt +sb +Hu +"} +(10,1,1) = {" +Hu +sb +Cw +MN +ro +AA +rU +MN +ku +Ql +OS +MR +xI +VM +yK +Ql +ku +MN +Vc +iB +Jb +MN +Nt +sb +Hu +"} +(11,1,1) = {" +Hu +sb +Ry +at +dg +zZ +Dc +at +ku +Ql +NX +jb +cw +QF +fT +Ql +ku +XT +eM +Kh +cc +FS +Nt +Nt +Hu +"} +(12,1,1) = {" +Hu +Zc +Se +Uv +BW +YI +Dc +Uv +ku +Ql +xI +Tc +YX +Gh +Kf +Ql +ku +wN +Tc +UL +af +NX +Nt +Nt +Hu +"} +(13,1,1) = {" +Hu +sb +yk +bA +dg +fu +Dc +Rl +ku +Ql +pV +mW +Ct +BD +oR +un +ku +IM +Di +Ct +Xq +Nc +Nt +Nt +Hu +"} +(14,1,1) = {" +Hu +sb +jq +MN +Ih +Vm +DV +MN +ku +Ql +Hx +ld +Kf +ao +uH +Ql +ku +MN +Lu +Vc +lh +MN +Nt +sb +Hu +"} +(15,1,1) = {" +Hu +sb +Cw +Cw +Cw +ru +Cw +Uv +lD +kv +lh +kv +Lu +NX +kv +ao +lD +VM +VM +VM +VM +Nt +Nt +sb +Hu +"} +(16,1,1) = {" +Hu +sb +sb +CI +JX +hn +Cw +Ql +ku +Kh +Rc +VI +UT +Yv +Rc +Kh +ku +Ql +xe +nv +nv +Nt +sb +sb +Hu +"} +(17,1,1) = {" +Hu +gY +sb +dg +Ue +Dc +Cw +Ql +ku +Ql +UT +vo +vo +vo +UT +Ql +ku +Ql +Yl +hZ +Nw +Nt +sb +gY +Hu +"} +(18,1,1) = {" +Hu +gY +sb +zU +DN +lQ +Cw +Ql +ku +fj +Ql +vo +DP +vo +Ql +fj +ku +Ql +Gh +nv +nv +Nt +sb +gY +Hu +"} +(19,1,1) = {" +Hu +gY +sb +sb +sb +sb +Cw +Ql +xG +Vf +Vf +Vf +Vf +Vf +Vf +Vf +EX +Ql +Ql +sb +sb +sb +sb +gY +Hu +"} +(20,1,1) = {" +Hu +gY +gY +gY +gY +Nt +Cw +Ql +Ql +Ql +Ql +Fy +Ql +OG +Ql +Ql +Ql +Ql +Nt +Nt +gY +gY +gY +gY +Hu +"} +(21,1,1) = {" +Hu +gY +gY +gY +gY +Nt +Cw +Ql +OG +Nt +Nt +Ql +Ql +Ql +ie +Ql +Nt +Ql +Nt +Nt +gY +gY +gY +gY +Hu +"} +(22,1,1) = {" +Hu +gY +gY +gY +gY +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +Nt +gY +gY +gY +gY +Hu +"} +(23,1,1) = {" +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +Hu +"} diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index 6d418b15bb42..ed4307e9e9b4 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -14713,10 +14713,11 @@ /turf/open/floor/iron/dark, /area/station/service/theater) "dxl" = ( -/obj/effect/turf_decal/tile/neutral/fourcorners, /obj/machinery/status_display/evac/directional/west, /obj/structure/filingcabinet/chestdrawer, -/mob/living/simple_animal/parrot/poly, +/obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/machinery/newscaster/directional/north, +/mob/living/basic/parrot/poly, /turf/open/floor/iron/dark, /area/station/command/heads_quarters/ce) "dxo" = ( @@ -38713,7 +38714,8 @@ /obj/structure/disposalpipe/segment{ dir = 10 }, -/mob/living/simple_animal/sloth/citrus, +/mob/living/basic/sloth/citrus, +/obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron, /area/station/command/heads_quarters/qm) "jrA" = ( @@ -82277,9 +82279,7 @@ /obj/structure/disposalpipe/segment, /obj/effect/decal/cleanable/dirt, /obj/machinery/duct, -/mob/living/simple_animal/hostile/retaliate/goat{ - name = "Pete" - }, +/mob/living/basic/goat/pete, /turf/open/floor/iron/freezer, /area/station/service/kitchen/coldroom) "uaz" = ( diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index 2270243a2afc..3c425eb7696b 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -4256,7 +4256,7 @@ "bup" = ( /obj/structure/filingcabinet/chestdrawer, /obj/effect/turf_decal/tile/neutral/fourcorners, -/mob/living/simple_animal/parrot/poly, +/mob/living/basic/parrot/poly, /turf/open/floor/iron/dark, /area/station/command/heads_quarters/ce) "buv" = ( @@ -17404,7 +17404,7 @@ /area/station/maintenance/starboard/aft) "fya" = ( /obj/structure/cable, -/mob/living/simple_animal/sloth/paperwork, +/mob/living/basic/sloth/paperwork, /turf/open/floor/iron, /area/station/cargo/storage) "fyc" = ( @@ -32284,7 +32284,7 @@ /turf/open/floor/plating/snowed/smoothed/icemoon, /area/icemoon/underground/explored) "ksu" = ( -/mob/living/simple_animal/hostile/asteroid/gutlunch, +/mob/living/basic/mining/gutlunch/warrior, /turf/open/misc/asteroid/snow/icemoon, /area/icemoon/underground/explored) "ksC" = ( @@ -43925,10 +43925,10 @@ /turf/open/floor/iron/white, /area/station/science/xenobiology) "ocF" = ( -/mob/living/simple_animal/hostile/retaliate/goat{ - atmos_requirements = list("min_oxy"=1,"max_oxy"=0,"min_plas"=0,"max_plas"=1,"min_co2"=0,"max_co2"=5,"min_n2"=0,"max_n2"=0); +/mob/living/basic/goat/pete{ desc = "Not known for their pleasant disposition. This one seems a bit more hardy to the cold."; - minbodytemp = 150; + habitable_atmos = list("min_oxy"=1,"max_oxy"=0,"min_plas"=0,"max_plas"=1,"min_co2"=0,"max_co2"=5,"min_n2"=0,"max_n2"=0); + minimum_survivable_temperature = 150; name = "Snowy Pete" }, /turf/open/misc/asteroid/snow/coldroom, diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm index 1d51253a6d62..9aa4fe401324 100644 --- a/_maps/map_files/KiloStation/KiloStation.dmm +++ b/_maps/map_files/KiloStation/KiloStation.dmm @@ -6746,7 +6746,7 @@ /obj/effect/turf_decal/tile/yellow{ dir = 1 }, -/mob/living/simple_animal/sloth/citrus, +/mob/living/basic/sloth/citrus, /turf/open/floor/iron, /area/station/cargo/sorting) "coy" = ( @@ -7040,7 +7040,7 @@ /turf/open/misc/asteroid/airless, /area/space/nearstation) "cuj" = ( -/mob/living/simple_animal/hostile/asteroid/hivelord, +/mob/living/basic/mining/legion, /turf/open/misc/asteroid/airless, /area/space/nearstation) "cup" = ( @@ -13219,9 +13219,6 @@ dir = 8 }, /obj/effect/decal/cleanable/blood/old, -/mob/living/simple_animal/hostile/retaliate/goat{ - name = "Pete" - }, /turf/open/floor/iron/freezer, /area/station/service/kitchen/coldroom) "eBx" = ( @@ -27198,6 +27195,17 @@ /obj/effect/mapping_helpers/broken_floor, /turf/open/floor/plating, /area/station/maintenance/port/fore) +"iQO" = ( +/obj/structure/chair/office/light{ + dir = 4 + }, +/mob/living/basic/parrot/poly, +/obj/effect/decal/cleanable/dirt, +/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ + dir = 4 + }, +/turf/open/space/basic, +/area/space) "iRq" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4, /obj/structure/cable, @@ -31818,10 +31826,6 @@ /obj/effect/decal/cleanable/blood/gibs/old, /obj/effect/decal/cleanable/blood/old, /obj/effect/decal/cleanable/dirt, -/mob/living/simple_animal/hostile/jungle/mook{ - environment_smash = 0; - name = "deformed creature" - }, /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron/dark, /area/station/maintenance/port/fore) @@ -38280,7 +38284,7 @@ /obj/structure/spider/stickyweb, /obj/effect/mapping_helpers/broken_floor, /obj/effect/turf_decal/siding/wood, -/mob/living/basic/syndicate/russian/ranged/lootless, +/mob/living/basic/trooper/russian/ranged/lootless, /turf/open/floor/plating, /area/station/maintenance/starboard) "mCO" = ( @@ -41625,10 +41629,6 @@ /obj/effect/decal/remains/human, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/mob/living/simple_animal/hostile/jungle/mook{ - environment_smash = 0; - name = "deformed creature" - }, /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron/dark, /area/station/maintenance/port/fore) @@ -51478,7 +51478,7 @@ /obj/structure/cable, /obj/effect/mapping_helpers/broken_floor, /obj/structure/chair/stool/directional/west, -/mob/living/basic/syndicate/russian/ranged/lootless, +/mob/living/basic/trooper/russian/ranged/lootless, /turf/open/floor/plating, /area/station/maintenance/starboard) "rbO" = ( @@ -53023,6 +53023,12 @@ /obj/effect/turf_decal/delivery, /turf/open/floor/iron/freezer, /area/station/service/kitchen/coldroom) +"rAn" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/machinery/holopad, +/mob/living/basic/sloth/citrus, +/turf/open/space/basic, +/area/station/solars/port/fore) "rAr" = ( /obj/effect/turf_decal/delivery, /obj/machinery/light/directional/west, @@ -63425,11 +63431,11 @@ pixel_x = -4; pixel_y = 4 }, -/obj/item/pen, -/obj/effect/turf_decal/stripes/corner{ +/obj/structure/disposalpipe/segment{ dir = 4 }, -/obj/effect/turf_decal/tile/neutral/anticorner/contrasted, +/mob/living/basic/goat/pete, +/obj/machinery/duct, /turf/open/floor/iron/dark, /area/station/hallway/primary/central/fore) "uWQ" = ( @@ -68121,7 +68127,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ dir = 8 }, -/mob/living/simple_animal/hostile/asteroid/hivelord, +/mob/living/basic/mining/legion, /turf/open/floor/plating, /area/station/cargo/warehouse) "wpw" = ( @@ -68791,7 +68797,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 1 }, -/mob/living/simple_animal/hostile/asteroid/hivelord, +/mob/living/basic/mining/legion, /turf/open/floor/plating, /area/station/cargo/warehouse) "wAz" = ( @@ -70742,7 +70748,7 @@ pixel_x = 12 }, /obj/item/radio/intercom/directional/east, -/mob/living/simple_animal/parrot/poly, +/mob/living/basic/parrot/poly, /turf/open/floor/iron/dark, /area/station/command/heads_quarters/ce) "xjr" = ( @@ -73513,7 +73519,7 @@ /turf/open/floor/iron/dark, /area/station/ai_monitored/turret_protected/ai_upload) "ycp" = ( -/mob/living/simple_animal/hostile/asteroid/hivelord, +/mob/living/basic/mining/legion, /turf/open/misc/asteroid/lowpressure, /area/space/nearstation) "ycr" = ( @@ -87601,7 +87607,7 @@ aof aUz cmU grV -grV +rAn grV grV grV @@ -94515,7 +94521,7 @@ aaa aaa aaa aaa -aaa +iQO aaa aaa aaa diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 7182dcf69a6f..fd3027598486 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -25229,7 +25229,7 @@ dir = 4 }, /obj/structure/disposalpipe/segment, -/mob/living/simple_animal/sloth/citrus, +/mob/living/basic/sloth/citrus, /turf/open/floor/iron, /area/station/cargo/storage) "jhD" = ( @@ -47254,7 +47254,7 @@ "qXw" = ( /obj/structure/filingcabinet/chestdrawer, /obj/effect/turf_decal/tile/neutral/fourcorners, -/mob/living/simple_animal/parrot/poly, +/mob/living/basic/parrot/poly, /turf/open/floor/iron/dark, /area/station/command/heads_quarters/ce) "qXB" = ( @@ -58876,10 +58876,7 @@ "uYg" = ( /obj/structure/cable, /obj/structure/sink/kitchen/directional/west, -/obj/machinery/power/apc/auto_name/directional/north, -/mob/living/simple_animal/hostile/retaliate/goat{ - name = "Pete" - }, +/mob/living/basic/goat/pete, /turf/open/floor/iron/kitchen_coldroom/freezerfloor, /area/station/service/kitchen/coldroom) "uYi" = ( diff --git a/_maps/map_files/NorthStar/north_star.dmm b/_maps/map_files/NorthStar/north_star.dmm index 0da7b68198dd..f46e67ab5d9e 100644 --- a/_maps/map_files/NorthStar/north_star.dmm +++ b/_maps/map_files/NorthStar/north_star.dmm @@ -7993,10 +7993,10 @@ /obj/structure/table/reinforced/plastitaniumglass, /obj/structure/window/reinforced/spawner/directional/west, /obj/structure/cable, -/mob/living/simple_animal/parrot/poly, /obj/machinery/computer/security/telescreen/engine{ name = "Engineering and atmospherics monitor" }, +/mob/living/basic/parrot/poly, /turf/open/floor/catwalk_floor/iron_dark, /area/station/command/heads_quarters/ce) "bVT" = ( @@ -30014,7 +30014,7 @@ /turf/open/floor/plating, /area/station/maintenance/floor3/starboard/fore) "idf" = ( -/mob/living/simple_animal/sloth/paperwork, +/mob/living/basic/sloth/paperwork, /obj/effect/turf_decal/tile/yellow, /obj/effect/turf_decal/tile/brown{ dir = 4 @@ -57672,9 +57672,7 @@ "pmG" = ( /obj/machinery/power/apc/auto_name/directional/west, /obj/structure/cable, -/mob/living/simple_animal/hostile/retaliate/goat{ - name = "Pete" - }, +/mob/living/basic/goat/pete, /turf/open/floor/iron/kitchen_coldroom/freezerfloor, /area/station/service/kitchen/coldroom) "pmO" = ( diff --git a/_maps/map_files/Oshan/oshan.dmm b/_maps/map_files/Oshan/oshan.dmm index a8787a400312..3a5b43de35f0 100644 --- a/_maps/map_files/Oshan/oshan.dmm +++ b/_maps/map_files/Oshan/oshan.dmm @@ -18608,7 +18608,7 @@ /turf/open/floor/wood, /area/station/commons/dorms) "jCm" = ( -/mob/living/simple_animal/hostile/retaliate/clown{ +/mob/living/basic/clown{ limb_destroyer = 1; health = 200; desc = "This clown was hired to entertain visitors."; @@ -24103,7 +24103,7 @@ dir = 1; icon_state = "pink2_1" }, -/mob/living/simple_animal/sloth/citrus, +/mob/living/basic/sloth/citrus, /turf/open/floor/wood, /area/station/command/heads_quarters/qm) "mlu" = ( diff --git a/_maps/map_files/tramstation/maintenance_modules/medsciupper_attachment_b_1.dmm b/_maps/map_files/tramstation/maintenance_modules/medsciupper_attachment_b_1.dmm index 8bb9fa1c7463..ec76b1e334cc 100644 --- a/_maps/map_files/tramstation/maintenance_modules/medsciupper_attachment_b_1.dmm +++ b/_maps/map_files/tramstation/maintenance_modules/medsciupper_attachment_b_1.dmm @@ -59,7 +59,7 @@ }, /obj/effect/landmark/generic_maintenance_landmark, /obj/structure/closet/body_bag, -/mob/living/simple_animal/hostile/skeleton{ +/mob/living/basic/skeleton{ name = "Jim"; desc = "Left to rot in maintenance, a poor soul whose green light was never noticed by the doctor..." }, diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index 5c73bd9058f8..31d6d443310c 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -1496,13 +1496,17 @@ /turf/open/misc/asteroid/snow/coldroom, /area/station/service/kitchen/coldroom) "aep" = ( +/obj/effect/turf_decal/weather/snow/corner{ + dir = 10 + }, /obj/effect/turf_decal/weather/snow/corner{ dir = 10 }, /obj/effect/turf_decal/weather/snow, -/mob/living/simple_animal/hostile/retaliate/goat{ - name = "Pete" +/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ + dir = 4 }, +/mob/living/basic/goat/pete, /turf/open/floor/iron/kitchen_coldroom, /area/station/service/kitchen/coldroom) "aeq" = ( @@ -3291,6 +3295,10 @@ /area/station/tcommsat/computer) "apZ" = ( /obj/structure/filingcabinet/chestdrawer, +/obj/effect/turf_decal/trimline/yellow/filled/line{ + dir = 9 + }, +/obj/structure/filingcabinet/chestdrawer, /obj/effect/turf_decal/trimline/yellow/filled/line{ dir = 9 }, @@ -3311,8 +3319,7 @@ pixel_y = -8; req_access = list("engineering") }, -/obj/structure/sign/clock/directional/north, -/mob/living/simple_animal/parrot/poly, +/mob/living/basic/parrot/poly, /turf/open/floor/iron, /area/station/command/heads_quarters/ce) "aqf" = ( @@ -48527,7 +48534,7 @@ /turf/open/floor/iron/dark/textured, /area/station/engineering/main) "oDF" = ( -/mob/living/simple_animal/hostile/retaliate/clown{ +/mob/living/basic/clown{ name = "The Forbidden One"; limb_destroyer = 1; health = 1000; @@ -59229,7 +59236,7 @@ }, /obj/effect/landmark/generic_maintenance_landmark, /obj/structure/closet/body_bag, -/mob/living/simple_animal/hostile/skeleton{ +/mob/living/basic/skeleton{ name = "Jim"; desc = "Left to rot in maintenance, a poor soul whose green light was never noticed by the doctor..." }, @@ -59717,7 +59724,7 @@ /turf/open/floor/iron/white, /area/station/science/lower) "sha" = ( -/mob/living/simple_animal/sloth/paperwork, +/mob/living/basic/sloth/paperwork, /turf/open/floor/glass, /area/station/cargo/storage) "shg" = ( diff --git a/_maps/shuttles/emergency_bar.dmm b/_maps/shuttles/emergency_bar.dmm index 5f248c384609..01023ed050e3 100644 --- a/_maps/shuttles/emergency_bar.dmm +++ b/_maps/shuttles/emergency_bar.dmm @@ -252,7 +252,7 @@ /turf/open/floor/iron/grimy, /area/shuttle/escape) "aX" = ( -/mob/living/simple_animal/drone/snowflake/bardrone, +/mob/living/basic/drone/snowflake/bardrone, /obj/effect/turf_decal/tile/bar/opposingcorners, /turf/open/floor/iron, /area/shuttle/escape) diff --git a/_maps/shuttles/emergency_cruise.dmm b/_maps/shuttles/emergency_cruise.dmm index a6d02332e064..6d1ca3b9e5a4 100644 --- a/_maps/shuttles/emergency_cruise.dmm +++ b/_maps/shuttles/emergency_cruise.dmm @@ -133,7 +133,7 @@ /obj/structure/disposalpipe/segment{ dir = 8 }, -/mob/living/simple_animal/hostile/retaliate/clown, +/mob/living/basic/clown, /turf/open/floor/eighties, /area/shuttle/escape/luxury) "cZ" = ( @@ -659,7 +659,7 @@ "ld" = ( /obj/effect/decal/cleanable/confetti, /obj/structure/window/reinforced/spawner/directional/west, -/mob/living/simple_animal/hostile/retaliate/clown{ +/mob/living/basic/clown{ health = 300; name = "Honk-E-Clown"; desc = "This clown looks pretty tough, better stay on his good side..."; @@ -1823,7 +1823,7 @@ /turf/open/floor/iron/dark/textured, /area/shuttle/escape/brig) "Ht" = ( -/mob/living/simple_animal/hostile/retaliate/clown, +/mob/living/basic/clown, /turf/open/floor/eighties, /area/shuttle/escape/luxury) "HA" = ( @@ -2618,6 +2618,13 @@ }, /turf/open/floor/iron/dark/textured, /area/shuttle/escape/brig) +"Wa" = ( +/obj/structure/chair/comfy/teal{ + dir = 4 + }, +/mob/living/basic/drone/snowflake/bardrone, +/turf/open/floor/wood/tile, +/area/shuttle/escape) "Wc" = ( /obj/effect/turf_decal/trimline/red/corner{ dir = 4 @@ -3541,7 +3548,7 @@ xf xf mw mw -mw +Wa xf Ov uV diff --git a/_maps/shuttles/emergency_hugcage.dmm b/_maps/shuttles/emergency_hugcage.dmm index b1162a76a137..cd8d4e7ff00d 100644 --- a/_maps/shuttles/emergency_hugcage.dmm +++ b/_maps/shuttles/emergency_hugcage.dmm @@ -30,7 +30,7 @@ /turf/open/floor/mineral/titanium/blue, /area/shuttle/escape) "dz" = ( -/mob/living/simple_animal/parrot/natural{ +/mob/living/basic/parrot{ melee_damage_upper = 5 }, /turf/open/floor/mineral/titanium/blue, diff --git a/_maps/shuttles/emergency_narnar.dmm b/_maps/shuttles/emergency_narnar.dmm index 835a6054a874..d6c48664e08e 100644 --- a/_maps/shuttles/emergency_narnar.dmm +++ b/_maps/shuttles/emergency_narnar.dmm @@ -111,7 +111,7 @@ /turf/open/floor/cult, /area/shuttle/escape) "w" = ( -/mob/living/simple_animal/hostile/construct/artificer, +/mob/living/basic/construct/artificer, /turf/open/floor/cult, /area/shuttle/escape) "x" = ( @@ -139,7 +139,7 @@ /turf/open/floor/cult, /area/shuttle/escape) "C" = ( -/mob/living/simple_animal/hostile/construct/juggernaut, +/mob/living/basic/construct/juggernaut, /turf/open/floor/cult, /area/shuttle/escape) "D" = ( @@ -166,7 +166,7 @@ /turf/open/floor/cult, /area/shuttle/escape) "H" = ( -/mob/living/simple_animal/hostile/construct/wraith, +/mob/living/basic/construct/wraith, /turf/open/floor/cult, /area/shuttle/escape) "I" = ( diff --git a/_maps/shuttles/emergency_rollerdome.dmm b/_maps/shuttles/emergency_rollerdome.dmm index f6cec0091aa9..0518c951e1ba 100644 --- a/_maps/shuttles/emergency_rollerdome.dmm +++ b/_maps/shuttles/emergency_rollerdome.dmm @@ -238,6 +238,10 @@ /obj/structure/window/reinforced/spawner/directional/north, /turf/open/floor/plating/airless, /area/shuttle/escape) +"KO" = ( +/mob/living/basic/drone/snowflake/bardrone, +/turf/open/floor/wood, +/area/shuttle/escape) "Ow" = ( /obj/structure/table, /obj/item/storage/box/drinkingglasses, @@ -270,10 +274,6 @@ }, /turf/open/floor/wood, /area/shuttle/escape) -"Qv" = ( -/mob/living/simple_animal/drone/snowflake/bardrone, -/turf/open/floor/wood, -/area/shuttle/escape) "QO" = ( /obj/machinery/door/airlock/gold/glass, /turf/open/floor/wood, @@ -469,7 +469,7 @@ Cg Cg ce ns -Qv +KO JR KJ od @@ -517,7 +517,7 @@ Cg Cg ce uN -Ky +KO Ow KJ od diff --git a/_maps/shuttles/ruin_caravan_victim.dmm b/_maps/shuttles/ruin_caravan_victim.dmm index 614ae713c7b1..6b95b857f153 100644 --- a/_maps/shuttles/ruin_caravan_victim.dmm +++ b/_maps/shuttles/ruin_caravan_victim.dmm @@ -13,7 +13,7 @@ /turf/closed/wall/mineral/titanium/nodiagonal, /area/shuttle/ruin/caravan/freighter1) "cu" = ( -/mob/living/basic/syndicate/ranged/smg/space, +/mob/living/basic/trooper/syndicate/ranged/smg/space, /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron/dark/airless, /area/shuttle/ruin/caravan/freighter1) @@ -458,7 +458,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 8 }, -/mob/living/basic/syndicate/ranged/smg/space, +/mob/living/basic/trooper/syndicate/ranged/smg/space, /obj/effect/turf_decal/tile/yellow{ dir = 4 }, @@ -468,7 +468,7 @@ /area/shuttle/ruin/caravan/freighter1) "Fu" = ( /obj/effect/decal/cleanable/blood, -/mob/living/basic/syndicate/melee/sword/space/stormtrooper, +/mob/living/basic/trooper/syndicate/melee/sword/space/stormtrooper, /obj/effect/turf_decal/tile/blue{ dir = 4 }, @@ -606,7 +606,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 4 }, -/mob/living/basic/syndicate/ranged/smg/space, +/mob/living/basic/trooper/syndicate/ranged/smg/space, /turf/open/floor/iron/airless, /area/shuttle/ruin/caravan/freighter1) "Ld" = ( diff --git a/_maps/shuttles/ruin_pirate_cutter.dmm b/_maps/shuttles/ruin_pirate_cutter.dmm index 615cb8b255c3..db75b1eff6c2 100644 --- a/_maps/shuttles/ruin_pirate_cutter.dmm +++ b/_maps/shuttles/ruin_pirate_cutter.dmm @@ -713,7 +713,7 @@ "Ry" = ( /obj/structure/rack, /obj/item/storage/bag/money/vault, -/mob/living/simple_animal/parrot{ +/mob/living/basic/parrot{ faction = list("pirate"); name = "Pegwing" }, diff --git a/_maps/shuttles/ruin_syndicate_dropship.dmm b/_maps/shuttles/ruin_syndicate_dropship.dmm index e3987b39ed54..b1f3f00897b7 100644 --- a/_maps/shuttles/ruin_syndicate_dropship.dmm +++ b/_maps/shuttles/ruin_syndicate_dropship.dmm @@ -215,7 +215,7 @@ /obj/structure/chair/comfy/shuttle{ dir = 4 }, -/mob/living/basic/syndicate/ranged/smg/pilot{ +/mob/living/basic/trooper/syndicate/ranged/smg/pilot{ environment_smash = 0 }, /turf/open/floor/iron/dark, diff --git a/_maps/shuttles/ruin_syndicate_fighter_shiv.dmm b/_maps/shuttles/ruin_syndicate_fighter_shiv.dmm index be8b4c2bad9f..7c2903773389 100644 --- a/_maps/shuttles/ruin_syndicate_fighter_shiv.dmm +++ b/_maps/shuttles/ruin_syndicate_fighter_shiv.dmm @@ -20,7 +20,7 @@ req_access = list("syndicate") }, /obj/structure/cable, -/mob/living/basic/syndicate/ranged/smg/pilot{ +/mob/living/basic/trooper/syndicate/ranged/smg/pilot{ environment_smash = 0 }, /turf/open/floor/mineral/plastitanium/red, diff --git a/_maps/shuttles/whiteship_meta.dmm b/_maps/shuttles/whiteship_meta.dmm index d13fccbf7016..0e3c921935f6 100644 --- a/_maps/shuttles/whiteship_meta.dmm +++ b/_maps/shuttles/whiteship_meta.dmm @@ -286,7 +286,12 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 4 }, -/mob/living/basic/syndicate/ranged{ +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/cleanable/dirt, +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ + dir = 4 + }, +/mob/living/basic/trooper/syndicate/ranged{ environment_smash = 0; name = "Syndicate Salvage Worker" }, @@ -1649,7 +1654,12 @@ dir = 8 }, /obj/effect/decal/cleanable/blood/gibs/old, -/mob/living/basic/syndicate/melee{ +/obj/effect/decal/cleanable/dirt, +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 8 + }, +/obj/effect/decal/cleanable/blood/gibs/old, +/mob/living/basic/trooper/syndicate/melee{ environment_smash = 0 }, /obj/effect/turf_decal/tile/neutral/fourcorners, @@ -1709,8 +1719,9 @@ /area/shuttle/abandoned/crew) "qc" = ( /obj/effect/decal/cleanable/dirt/dust, +/obj/effect/decal/cleanable/dirt, /obj/machinery/atmospherics/components/unary/vent_pump/on, -/mob/living/basic/syndicate/melee{ +/mob/living/basic/trooper/syndicate/melee{ environment_smash = 0 }, /obj/effect/turf_decal/tile/neutral/fourcorners, @@ -1730,7 +1741,11 @@ dir = 4 }, /obj/effect/decal/cleanable/dirt/dust, -/mob/living/basic/syndicate/melee{ +/obj/effect/turf_decal/box/white/corners{ + dir = 4 + }, +/obj/effect/decal/cleanable/dirt, +/mob/living/basic/trooper/syndicate/melee{ environment_smash = 0 }, /turf/open/floor/iron/dark, diff --git a/_maps/shuttles/whiteship_obelisk.dmm b/_maps/shuttles/whiteship_obelisk.dmm index 303b0912c9f2..05b17d19b71e 100644 --- a/_maps/shuttles/whiteship_obelisk.dmm +++ b/_maps/shuttles/whiteship_obelisk.dmm @@ -112,7 +112,7 @@ /area/shuttle/abandoned/engine) "gw" = ( /obj/structure/cable, -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /obj/effect/decal/cleanable/crayon{ icon_state = "rune4"; paint_colour = "#DC143C" @@ -124,7 +124,7 @@ /turf/open/floor/catwalk_floor/iron_smooth, /area/shuttle/abandoned/engine) "hg" = ( -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /obj/effect/decal/cleanable/blood/old, /obj/structure/cable, /turf/open/floor/mineral/titanium/white, @@ -156,7 +156,7 @@ /area/shuttle/abandoned/engine) "ji" = ( /obj/structure/cable, -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 4 }, @@ -398,7 +398,7 @@ /area/shuttle/abandoned/crew) "yo" = ( /obj/structure/cable, -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /obj/effect/decal/cleanable/crayon{ icon_state = "rune4"; paint_colour = "#DC143C" @@ -570,7 +570,7 @@ /area/shuttle/abandoned) "HU" = ( /obj/structure/cable, -/mob/living/simple_animal/hostile/construct/juggernaut/hostile, +/mob/living/basic/construct/juggernaut/hostile, /obj/effect/decal/cleanable/crayon{ icon_state = "rune4"; paint_colour = "#DC143C" @@ -590,7 +590,7 @@ /area/shuttle/abandoned) "IH" = ( /obj/structure/cable, -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /obj/effect/decal/cleanable/blood/gibs/core, /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 1 @@ -789,7 +789,7 @@ /area/shuttle/abandoned/medbay) "SE" = ( /obj/structure/cable, -/mob/living/simple_animal/hostile/construct/proteon/hostile, +/mob/living/basic/construct/proteon/hostile, /obj/effect/decal/cleanable/crayon{ icon_state = "rune4"; paint_colour = "#DC143C" diff --git a/_maps/virtual_domains/pirates.dmm b/_maps/virtual_domains/pirates.dmm index 5356f53d1932..805c7bc061cc 100644 --- a/_maps/virtual_domains/pirates.dmm +++ b/_maps/virtual_domains/pirates.dmm @@ -752,7 +752,7 @@ /area/virtual_domain/powered) "Pz" = ( /obj/structure/table/wood, -/mob/living/simple_animal/parrot{ +/mob/living/basic/parrot{ name = "pepper" }, /turf/open/floor/carpet/blue, diff --git a/_maps/virtual_domains/syndicate_assault.dmm b/_maps/virtual_domains/syndicate_assault.dmm index ea3cebaedd1e..a41275e20187 100644 --- a/_maps/virtual_domains/syndicate_assault.dmm +++ b/_maps/virtual_domains/syndicate_assault.dmm @@ -41,7 +41,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 4 }, -/mob/living/basic/syndicate/ranged/shotgun/space/stormtrooper, +/mob/living/basic/trooper/syndicate/ranged/shotgun/space/stormtrooper, /turf/open/floor/mineral/plastitanium, /area/ruin/space/has_grav/powered/virtual_domain) "bh" = ( @@ -241,7 +241,7 @@ /turf/open/floor/mineral/plastitanium, /area/ruin/space/has_grav/powered/virtual_domain) "ip" = ( -/mob/living/basic/syndicate/melee/sword/space/stormtrooper, +/mob/living/basic/trooper/syndicate/melee/sword/space/stormtrooper, /turf/open/floor/plastic, /area/ruin/space/has_grav/powered/virtual_domain) "iB" = ( @@ -279,7 +279,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 4 }, -/mob/living/basic/syndicate/ranged/smg/space/stormtrooper, +/mob/living/basic/trooper/syndicate/ranged/smg/space/stormtrooper, /turf/open/floor/mineral/plastitanium, /area/ruin/space/has_grav/powered/virtual_domain) "ja" = ( @@ -298,7 +298,7 @@ /area/ruin/space/has_grav/powered/virtual_domain) "jA" = ( /obj/structure/cable, -/mob/living/basic/syndicate/melee/space/stormtrooper, +/mob/living/basic/trooper/syndicate/melee/space/stormtrooper, /turf/open/floor/mineral/plastitanium, /area/ruin/space/has_grav/powered/virtual_domain) "jJ" = ( @@ -417,7 +417,7 @@ /turf/open/floor/mineral/plastitanium, /area/ruin/space/has_grav/powered/virtual_domain) "oZ" = ( -/mob/living/basic/syndicate/melee/sword/space/stormtrooper, +/mob/living/basic/trooper/syndicate/melee/sword/space/stormtrooper, /turf/open/floor/carpet/royalblack, /area/ruin/space/has_grav/powered/virtual_domain) "pl" = ( @@ -648,7 +648,7 @@ /turf/open/floor/mineral/plastitanium/red, /area/ruin/space/has_grav/powered/virtual_domain) "yD" = ( -/mob/living/basic/syndicate/ranged/smg/space/stormtrooper, +/mob/living/basic/trooper/syndicate/ranged/smg/space/stormtrooper, /turf/open/floor/mineral/plastitanium, /area/ruin/space/has_grav/powered/virtual_domain) "yJ" = ( @@ -699,7 +699,7 @@ /obj/structure/chair/comfy/shuttle{ dir = 4 }, -/mob/living/basic/syndicate/ranged/smg/space/stormtrooper, +/mob/living/basic/trooper/syndicate/ranged/smg/space/stormtrooper, /turf/open/floor/mineral/plastitanium, /area/ruin/space/has_grav/powered/virtual_domain) "Bm" = ( @@ -745,7 +745,7 @@ /obj/structure/chair/comfy/shuttle{ dir = 4 }, -/mob/living/basic/syndicate/ranged/smg/pilot, +/mob/living/basic/trooper/syndicate/ranged/smg/pilot, /turf/open/floor/mineral/plastitanium/red, /area/ruin/space/has_grav/powered/virtual_domain) "CR" = ( @@ -795,7 +795,7 @@ /turf/open/floor/mineral/plastitanium/red, /area/ruin/space/has_grav/powered/virtual_domain) "EX" = ( -/mob/living/basic/syndicate/ranged/shotgun/space/stormtrooper, +/mob/living/basic/trooper/syndicate/ranged/shotgun/space/stormtrooper, /turf/open/floor/mineral/plastitanium, /area/ruin/space/has_grav/powered/virtual_domain) "Fp" = ( @@ -1084,7 +1084,7 @@ /turf/closed/mineral/random, /area/space) "QX" = ( -/mob/living/basic/syndicate/ranged/space/stormtrooper, +/mob/living/basic/trooper/syndicate/ranged/space/stormtrooper, /turf/open/floor/mineral/plastitanium, /area/ruin/space/has_grav/powered/virtual_domain) "Ra" = ( diff --git a/code/__DEFINES/ai/ai.dm b/code/__DEFINES/ai/ai.dm index 1a6829946a14..eee874a041c9 100644 --- a/code/__DEFINES/ai/ai.dm +++ b/code/__DEFINES/ai/ai.dm @@ -1,4 +1,5 @@ #define GET_AI_BEHAVIOR(behavior_type) SSai_behaviors.ai_behaviors[behavior_type] +#define GET_TARGETING_STRATEGY(targeting_type) SSai_behaviors.targeting_strategies[targeting_type] #define HAS_AI_CONTROLLER_TYPE(thing, type) istype(thing?.ai_controller, type) #define AI_STATUS_ON 1 @@ -29,7 +30,11 @@ /// Don't move if being pulled #define STOP_MOVING_WHEN_PULLED (1<<0) /// Continue processing even if dead -#define CAN_ACT_WHILE_DEAD (1<<1) +#define CAN_ACT_WHILE_DEAD (1<<1) +/// Stop processing while in a progress bar +#define PAUSE_DURING_DO_AFTER (1<<2) +/// Continue processing while in stasis +#define CAN_ACT_IN_STASIS (1<<3) //Base Subtree defines diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index fe8e9541bf33..1e7c0e892e8c 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -8,6 +8,27 @@ #define BB_FOOD_TARGET "bb_food_target" ///Path we should use next time we use the JPS movement datum #define BB_PATH_TO_USE "BB_path_to_use" +///How close a mob must be for us to select it as a target, if that is less than how far we can maintain it as a target +#define BB_AGGRO_RANGE "BB_aggro_range" +///are we hungry? determined by the udder compnent +#define BB_CHECK_HUNGRY "BB_check_hungry" +///are we ready to breed? +#define BB_BREED_READY "BB_breed_ready" +///maximum kids we can have +#define BB_MAX_CHILDREN "BB_max_children" + +/// Store a single or list of emotes at this key +#define BB_EMOTE_KEY "BB_emotes" +/// Chance to perform an emote per second +#define BB_EMOTE_CHANCE "BB_EMOTE_CHANCE" + +/// Something the mob will say when calling reinforcements +#define BB_REINFORCEMENTS_SAY "BB_reinforcements_say" +/// Something the mob will remote when calling reinforcements +#define BB_REINFORCEMENTS_EMOTE "BB_reinforcements_emote" + +///Turf we want a mob to move to +#define BB_TRAVEL_DESTINATION "BB_travel_destination" ///song instrument blackboard, set by instrument subtrees #define BB_SONG_INSTRUMENT "BB_SONG_INSTRUMENT" @@ -25,25 +46,39 @@ ///Basic Mob Keys -///Targetting subtrees +///Targeting subtrees #define BB_BASIC_MOB_CURRENT_TARGET "BB_basic_current_target" #define BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION "BB_basic_current_target_hiding_location" -#define BB_TARGETTING_DATUM "targetting_datum" +#define BB_TARGETING_STRATEGY "targeting_strategy" ///some behaviors that check current_target also set this on deep crit mobs #define BB_BASIC_MOB_EXECUTION_TARGET "BB_basic_execution_target" ///Blackboard key for a whitelist typecache of "things we can target while trying to move" -#define BB_OBSTACLE_TARGETTING_WHITELIST "BB_targetting_whitelist" - -/// Blackboard key storing how long your targetting datum has held a particular target +#define BB_OBSTACLE_TARGETING_WHITELIST "BB_targeting_whitelist" +/// Key for the minimum status at which we want to target mobs (does not need to be specified if CONSCIOUS) +#define BB_TARGET_MINIMUM_STAT "BB_target_minimum_stat" +/// Flag for whether to target only wounded mobs +#define BB_TARGET_WOUNDED_ONLY "BB_target_wounded_only" +/// What typepath the holding object targeting strategy should look for +#define BB_TARGET_HELD_ITEM "BB_target_held_item" + +/// Blackboard key storing how long your targeting strategy has held a particular target #define BB_BASIC_MOB_HAS_TARGET_TIME "BB_basic_mob_has_target_time" -///Targetting keys for something to run away from, if you need to store this separately from current target +///Targeting keys for something to run away from, if you need to store this separately from current target #define BB_BASIC_MOB_FLEE_TARGET "BB_basic_flee_target" #define BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION "BB_basic_flee_target_hiding_location" -#define BB_FLEE_TARGETTING_DATUM "flee_targetting_datum" +#define BB_FLEE_TARGETING_STRATEGY "flee_targeting_strategy" +#define BB_BASIC_MOB_FLEE_DISTANCE "BB_basic_flee_distance" +#define DEFAULT_BASIC_FLEE_DISTANCE 9 + +/// Generic key for a non-specific targeted action +#define BB_TARGETED_ACTION "BB_targeted_action" -/// Generic key for a non-specific targetted action -#define BB_TARGETTED_ACTION "BB_targetted_action" +/// Generic key for a non-specific action +#define BB_GENERIC_ACTION "BB_generic_action" + +/// Generic key for a shapeshifting action +#define BB_SHAPESHIFT_ACTION "BB_shapeshift_action" ///How long have we spent with no target? #define BB_TARGETLESS_TIME "BB_targetless_time" @@ -68,8 +103,13 @@ ///List of mobs who have damaged us #define BB_BASIC_MOB_RETALIATE_LIST "BB_basic_mob_shitlist" -/// Flag to set on or off if you want your mob to prioritise running away -#define BB_BASIC_MOB_FLEEING "BB_basic_fleeing" +/// Chance to randomly acquire a new target +#define BB_RANDOM_AGGRO_CHANCE "BB_random_aggro_chance" +/// Chance to randomly drop all of our targets +#define BB_RANDOM_DEAGGRO_CHANCE "BB_random_deaggro_chance" + +/// Flag to set on if you want your mob to STOP running away +#define BB_BASIC_MOB_STOP_FLEEING "BB_basic_stop_fleeing" ///list of foods this mob likes #define BB_BASIC_FOODS "BB_basic_foods" @@ -85,3 +125,20 @@ #define MOD_AI_RANGE 200 #define BB_GROUP_DATUM "BB_group_datum" +///should we skip the faction check for the targeting strategy? +#define BB_ALWAYS_IGNORE_FACTION "BB_always_ignore_factions" +///are we in some kind of temporary state of ignoring factions when targeting? can result in volatile results if multiple behaviours touch this +#define BB_TEMPORARILY_IGNORE_FACTION "BB_temporarily_ignore_factions" + +///currently only used by clowns, a list of what can the mob speak randomly +#define BB_BASIC_MOB_SPEAK_LINES "BB_speech_lines" +#define BB_EMOTE_SAY "emote_say" +#define BB_EMOTE_HEAR "emote_hear" +#define BB_EMOTE_SEE "emote_see" +#define BB_EMOTE_SOUND "emote_sound" +#define BB_SPEAK_CHANCE "emote_chance" + +/// A target that has called this mob for reinforcements +#define BB_BASIC_MOB_REINFORCEMENT_TARGET "BB_basic_mob_reinforcement_target" +/// The next time at which this mob can call for reinforcements +#define BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN "BB_basic_mob_reinforcements_cooldown" diff --git a/code/__DEFINES/ai/carp.dm b/code/__DEFINES/ai/carp.dm index 8286f3262077..459b98ffb020 100644 --- a/code/__DEFINES/ai/carp.dm +++ b/code/__DEFINES/ai/carp.dm @@ -7,9 +7,9 @@ /// Current target turf in your migration #define BB_CARP_MIGRATION_TARGET "BB_carp_migration_target" -/// Targetting keys for magicarp spells +/// Targeting keys for magicarp spells #define BB_MAGICARP_SPELL_TARGET "BB_magicarp_spell_target" -#define BB_MAGICARP_SPELL_SPECIAL_TARGETTING "BB_magicarp_spell_special_targetting" +#define BB_MAGICARP_SPELL_SPECIAL_TARGETING "BB_magicarp_spell_special_targeting" #define MAGICARP_SPELL_CORPSES "magicarp_spell_corpses" #define MAGICARP_SPELL_WALLS "magicarp_spell_walls" #define MAGICARP_SPELL_OBJECTS "magicarp_spell_objects" diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index 4915ff9cbc86..2b2a55ebe0f8 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -119,3 +119,106 @@ #define BB_ORE_TARGET "BB_ore_target" /// which ore types we will not eat #define BB_ORE_IGNORE_TYPES "BB_ore_ignore_types" + +// minebot keys +/// key that stores our toggle light ability +#define BB_MINEBOT_LIGHT_ABILITY "minebot_light_ability" +/// key that stores our dump ore ability +#define BB_MINEBOT_DUMP_ABILITY "minebot_dump_ability" +/// key that stores our target turf +#define BB_TARGET_MINERAL_TURF "target_mineral_turf" +/// key that stores list of the turfs we ignore +#define BB_BLACKLIST_MINERAL_TURFS "blacklist_mineral_turfs" +/// key that stores the previous blocked wall +#define BB_PREVIOUS_UNREACHABLE_WALL "previous_unreachable_wall" +/// key that stores our mining mode +#define BB_AUTOMATED_MINING "automated_mining" +/// key that stores the nearest dead human +#define BB_NEARBY_DEAD_MINER "nearby_dead_miner" + +//seedling keys +/// the water can we will pick up +#define BB_WATERCAN_TARGET "watercan_target" +/// the hydrotray we will heal +#define BB_HYDROPLANT_TARGET "hydroplant_target" +/// minimum weed levels for us to cure +#define BB_WEEDLEVEL_THRESHOLD "weedlevel_threshold" +/// minimum water levels for us to refill +#define BB_WATERLEVEL_THRESHOLD "waterlevel_threshold" +/// key holds our solarbeam ability +#define BB_SOLARBEAM_ABILITY "solarbeam_ability" +/// key holds our rapid seeds ability +#define BB_RAPIDSEEDS_ABILITY "rapidseeds_ability" +/// key holds the tray we will beam +#define BB_BEAMABLE_HYDROPLANT_TARGET "beamable_hydroplant_target" + +//ice demons +///the list of items we are afraid of +#define BB_LIST_SCARY_ITEMS "list_scary_items" +///our teleportation ability +#define BB_DEMON_TELEPORT_ABILITY "demon_teleport_ability" +///the destination of our teleport ability +#define BB_TELEPORT_DESTINATION "teleport_destination" +///the ability to clone ourself +#define BB_DEMON_CLONE_ABILITY "demon_clone_ability" +///our slippery ice ability +#define BB_DEMON_SLIP_ABILITY "demon_slip_ability" +///the turf we are escaping to +#define BB_ESCAPE_DESTINATION "escape_destination" + +/// Corpse we have consumed +#define BB_LEGION_CORPSE "legion_corpse" +/// Things our target recently said +#define BB_LEGION_RECENT_LINES "legion_recent_lines" +/// The creator of our legion skull +#define BB_LEGION_BROOD_CREATOR "legion_brood_creator" + +//mook keys +/// our home landmark +#define BB_HOME_VILLAGE "home_village" +/// maximum distance we can be from home during a storm +#define BB_MAXIMUM_DISTANCE_TO_VILLAGE "maximum_distance_to_village" +/// stand where we deposit our ores +#define BB_MATERIAL_STAND_TARGET "material_stand_target" +/// our jump ability +#define BB_MOOK_JUMP_ABILITY "mook_jump_ability" +/// our leap ability +#define BB_MOOK_LEAP_ABILITY "mook_leap_ability" +/// the chief we must obey +#define BB_MOOK_TRIBAL_CHIEF "mook_tribal_chief" +/// the injured mook we must heal +#define BB_INJURED_MOOK "injured_mook" +/// the player we will follow and play music for +#define BB_MOOK_MUSIC_AUDIENCE "music_audience" +/// the bonfire we will light up +#define BB_MOOK_BONFIRE_TARGET "bonfire_target" + +//gutlunch keys +///the trough we will eat from +#define BB_TROUGH_TARGET "trough_target" +//leaper keys +///key holds our volley ability +#define BB_LEAPER_VOLLEY "leaper_volley" +///key holds our flop ability +#define BB_LEAPER_FLOP "leaper_flop" +///key holds our bubble ability +#define BB_LEAPER_BUBBLE "leaper_bubble" +///key holds our summon ability +#define BB_LEAPER_SUMMON "leaper_summon" +///key holds the world timer for swimming +#define BB_KEY_SWIM_TIME "key_swim_time" +///key holds the water or land target turf +#define BB_SWIM_ALTERNATE_TURF "swim_alternate_turf" +///key holds our state of swimming +#define BB_CURRENTLY_SWIMMING "currently_swimming" +///key holds how long we will be swimming for +#define BB_KEY_SWIMMER_COOLDOWN "key_swimmer_cooldown" +//Wizard AI keys +/// Key where we store our main targeted spell +#define BB_WIZARD_TARGETED_SPELL "BB_wizard_targeted_spell" +/// Key where we store our secondary, untargeted spell +#define BB_WIZARD_SECONDARY_SPELL "BB_wizard_secondary_spell" +/// Key where we store our blink spell +#define BB_WIZARD_BLINK_SPELL "BB_wizard_blink_spell" +/// Key for the next time we can cast a spell +#define BB_WIZARD_SPELL_COOLDOWN "BB_wizard_spell_cooldown" diff --git a/code/__DEFINES/ai/pet_commands.dm b/code/__DEFINES/ai/pet_commands.dm index 5894aedff14d..1e692b9f805a 100644 --- a/code/__DEFINES/ai/pet_commands.dm +++ b/code/__DEFINES/ai/pet_commands.dm @@ -4,6 +4,6 @@ /// Blackboard field for what we actually want the pet to target #define BB_CURRENT_PET_TARGET "BB_current_pet_target" /// Blackboard field for how we target things, as usually we want to be more permissive than normal -#define BB_PET_TARGETTING_DATUM "BB_pet_targetting" +#define BB_PET_TARGETING_STRATEGY "BB_pet_targeting" /// Typecache of weakrefs to mobs this mob is friends with, will follow their instructions and won't attack them #define BB_FRIENDS_LIST "BB_friends_list" diff --git a/code/__DEFINES/ai/pets.dm b/code/__DEFINES/ai/pets.dm index b3ad67ecc068..e41c9ac0c3ff 100644 --- a/code/__DEFINES/ai/pets.dm +++ b/code/__DEFINES/ai/pets.dm @@ -27,3 +27,27 @@ #define BB_FIND_MOM_TYPES "BB_find_mom_types" ///list of types of mobs we must ignore #define BB_IGNORE_MOM_TYPES "BB_ignore_mom_types" + +/// The current string that this parrot will repeat back to someone +#define BB_PARROT_REPEAT_STRING "BB_parrot_repeat_string" +/// The odds that this parrot will repeat back a string +#define BB_PARROT_REPEAT_PROBABILITY "BB_parrot_repeat_probability" +/// The odds that this parrot will choose another string to repeat +#define BB_PARROT_PHRASE_CHANGE_PROBABILITY "BB_parrot_phrase_change_probability" +/// A copy of the string buffer that we end the shift with. DO NOT ACCESS THIS DIRECTLY - YOU SHOULD USE THE COMPONENT IN MOST CASES +#define BB_EXPORTABLE_STRING_BUFFER_LIST "BB_parrot_repeat_string_buffer" +/// The types of perches we desire to use +#define BB_PARROT_PERCH_TYPES "BB_parrot_perch_types" +/// key that holds our perch target +#define BB_PERCH_TARGET "perch_target" +/// key that holds our theft item target +#define BB_HOARD_ITEM_TARGET "hoard_item_target" +/// key that holds the mob we will steal from +#define BB_THEFT_VICTIM "theft_victim" +/// key that holds the turf we will be hauling stolen items to +#define BB_HOARD_LOCATION "hoard_location" +/// key that holds the minimum range we must be from the hoard spot +#define BB_HOARD_LOCATION_RANGE "hoard_location_range" +/// key that holds items we arent interested in hoarding +#define BB_IGNORE_ITEMS "ignore_items" + diff --git a/code/__DEFINES/ai/trader.dm b/code/__DEFINES/ai/trader.dm new file mode 100644 index 000000000000..853dd8736b64 --- /dev/null +++ b/code/__DEFINES/ai/trader.dm @@ -0,0 +1,6 @@ +///The ability to setup our "shop" +#define BB_SETUP_SHOP "BB_setup_shop" +///Reference to the plastic chair that is considered as our shop +#define BB_SHOP_SPOT "BB_shop_spot" +///Reference to our first customer to harass with deals +#define BB_FIRST_CUSTOMER "BB_first_customer" diff --git a/code/__DEFINES/ai/ventcrawling.dm b/code/__DEFINES/ai/ventcrawling.dm index f981ef3bba5d..a60b7fd59401 100644 --- a/code/__DEFINES/ai/ventcrawling.dm +++ b/code/__DEFINES/ai/ventcrawling.dm @@ -3,7 +3,7 @@ /// Key that holds a vent that we want to exit out of (when we're already in a pipenet) #define BB_EXIT_VENT_TARGET "BB_exit_vent_target" /// Do we plan on going inside a vent? Boolean. -#define BB_CURRENTLY_TARGETTING_VENT "BB_currently_targetting_vent" +#define BB_CURRENTLY_TARGETING_VENT "BB_currently_targeting_vent" /// How long should we wait before we try and enter a vent again? #define BB_VENTCRAWL_COOLDOWN "BB_ventcrawl_cooldown" /// The least amount of time (in seconds) we take to go through the vents. diff --git a/code/__DEFINES/basic_mobs.dm b/code/__DEFINES/basic_mobs.dm index 5a4aebaee23e..6c8a3022e8fa 100644 --- a/code/__DEFINES/basic_mobs.dm +++ b/code/__DEFINES/basic_mobs.dm @@ -19,3 +19,10 @@ /// Above this speed we stop gliding because it looks silly #define END_GLIDE_SPEED 10 + +///mook attack status flags +#define MOOK_ATTACK_NEUTRAL 0 +#define MOOK_ATTACK_WARMUP 1 +#define MOOK_ATTACK_ACTIVE 2 +#define MOOK_ATTACK_STRIKE 3 + diff --git a/code/__DEFINES/blob_defines.dm b/code/__DEFINES/blob_defines.dm index 6a6e9d5c75c8..81edf9a02085 100644 --- a/code/__DEFINES/blob_defines.dm +++ b/code/__DEFINES/blob_defines.dm @@ -32,7 +32,6 @@ #define BLOB_CORE_EXPAND_RANGE 3 // Radius of automatic expansion #define BLOB_CORE_STRONG_REINFORCE_RANGE 1 // The radius of tiles surrounding the core that get upgraded #define BLOB_CORE_REFLECTOR_REINFORCE_RANGE 0 -#define BLOB_CORE_MAX_SPORES 0 // Spores that the core can produce for free #define BLOB_NODE_MAX_HP 200 #define BLOB_NODE_HP_REGEN 3 @@ -42,7 +41,6 @@ #define BLOB_NODE_EXPAND_RANGE 2 // Radius of automatic expansion #define BLOB_NODE_STRONG_REINFORCE_RANGE 0 // The radius of tiles surrounding the node that get upgraded #define BLOB_NODE_REFLECTOR_REINFORCE_RANGE 0 -#define BLOB_NODE_MAX_SPORES 0 // Spores that nodes can maintain #define BLOB_FACTORY_MAX_HP 200 #define BLOB_FACTORY_HP_REGEN 1 diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index 11828510f341..deb84d0f0b0f 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -45,6 +45,7 @@ #define COLOR_SOFT_RED "#FA8282" #define COLOR_CULT_RED "#960000" #define COLOR_BUBBLEGUM_RED "#950A0A" +#define COLOR_CARP_RIFT_RED "#ff330030" #define COLOR_YELLOW "#FFFF00" #define COLOR_VIVID_YELLOW "#FBFF23" diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 03b8514e0cf1..f28dc8ec7dc6 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -270,6 +270,8 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( #define BODY_ZONE_L_LEG "l_leg" #define BODY_ZONE_R_LEG "r_leg" +GLOBAL_LIST_INIT(arm_zones, list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + #define BODY_ZONE_PRECISE_EYES "eyes" #define BODY_ZONE_PRECISE_MOUTH "mouth" #define BODY_ZONE_PRECISE_GROIN "groin" @@ -335,6 +337,8 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( #define ARMOR_WEAKENED_MULTIPLIER 2 /// Armor can't block more than this as a percentage #define ARMOR_MAX_BLOCK 90 +/// Calculates the new armour value after armour penetration. Can return negative values, and those must be caught. +#define PENETRATE_ARMOUR(armour, penetration) (penetration == 100 ? 0 : 100 * (armour - penetration) / (100 - penetration)) /// Return values used in item/melee/baton/baton_attack. /// Does a normal item attack. diff --git a/code/__DEFINES/crushing.dm b/code/__DEFINES/crushing.dm new file mode 100644 index 000000000000..1261b98e730e --- /dev/null +++ b/code/__DEFINES/crushing.dm @@ -0,0 +1,15 @@ +// from _vending.dm + +/// Set if the tipped object successfully crushed a subtype of /mob/living. +#define SUCCESSFULLY_CRUSHED_MOB (1<<0) +/// Set if the tipped object successfully crushed a subtype of /atom. +#define SUCCESSFULLY_CRUSHED_ATOM (1<<1) +/// Set if the tipped object successfully actually fell over, which can fail if it couldn't enter the turf it tried to fall into. +#define SUCCESSFULLY_FELL_OVER (1<<2) + +#define CRUSH_CRIT_SHATTER_LEGS "crush_crit_shatter_legs" +#define CRUSH_CRIT_PARAPALEGIC "crush_crit_parapalegic" +#define CRUSH_CRIT_SQUISH_LIMB "crush_crit_pin" +#define CRUSH_CRIT_HEADGIB "crush_crit_headgib" +#define VENDOR_CRUSH_CRIT_PIN "vendor_crush_crit_pin" +#define VENDOR_CRUSH_CRIT_GLASSCANDY "vendor_crush_crit_glasscandy" diff --git a/code/__DEFINES/dcs/flags.dm b/code/__DEFINES/dcs/flags.dm index 3f3db0212b6d..fb93e3a337a0 100644 --- a/code/__DEFINES/dcs/flags.dm +++ b/code/__DEFINES/dcs/flags.dm @@ -1,4 +1,4 @@ -/// Return this from `/datum/component/Initialize` or `datum/component/OnTransfer` to have the component be deleted if it's applied to an incorrect type. +/// Return this from `/datum/component/Initialize` or `/datum/component/OnTransfer` or `/datum/component/on_source_add` to have the component be deleted if it's applied to an incorrect type. /// `parent` must not be modified if this is to be returned. /// This will be noted in the runtime logs #define COMPONENT_INCOMPATIBLE 1 @@ -33,8 +33,8 @@ /** * Component uses source tracking to manage adding and removal logic. * Add a source/spawn to/the component by using AddComponentFrom(source, component_type, args...) - * Only the first args will be respected, and you should instead handle most of your logic in the on_source_added proc. * Removing the last source will automatically remove the component from the parent. + * Arguments will be passed to on_source_add(source, args...); ensure that Initialize and on_source_add have the same signature. */ #define COMPONENT_DUPE_SOURCES 3 /// old component is given the initialization args of the new diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_attack.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_attack.dm index 5191d7e8c68d..55a19a4347ad 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_attack.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_attack.dm @@ -52,7 +52,15 @@ #define COMSIG_ATOM_ATTACK_ROBOT_SECONDARY "atom_attack_robot_secondary" ///from relay_attackers element: (atom/attacker, attack_flags) #define COMSIG_ATOM_WAS_ATTACKED "atom_was_attacked" +///Called before a atom gets something tilted on them. If [COMPONENT_IMMUNE_TO_TILT_AND_CRUSH] is returned in a signal, the atom will be unaffected. +#define COMSIG_PRE_TILT_AND_CRUSH "atom_pre_tilt_and_crush" + #define COMPONENT_IMMUNE_TO_TILT_AND_CRUSH (1<<0) +///Called when a atom gets something tilted on them +#define COMSIG_POST_TILT_AND_CRUSH "atom_post_tilt_and_crush" + ///The damage type of the weapon projectile is non-lethal stamina #define ATTACKER_STAMINA_ATTACK (1<<0) ///the attacker is shoving the source #define ATTACKER_SHOVING (1<<1) + /// The attack is a damaging-type attack + #define ATTACKER_DAMAGING_ATTACK (1<<2) diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm index dc4d5a25ea0c..5e6f50a1d654 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm @@ -47,13 +47,17 @@ #define COMSIG_MOVABLE_THROW_LANDED "movable_throw_landed" ///from base of atom/movable/on_changed_z_level(): (turf/old_turf, turf/new_turf, same_z_layer) #define COMSIG_MOVABLE_Z_CHANGED "movable_ztransit" +///called before hearing a message from atom/movable/Hear(): +#define COMSIG_MOVABLE_PRE_HEAR "movable_pre_hear" + ///cancel hearing the message because we're doing something else presumably + #define COMSIG_MOVABLE_CANCEL_HEARING (1<<0) ///from base of atom/movable/Hear(): (proc args list(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range)) #define COMSIG_MOVABLE_HEAR "movable_hear" - //#define HEARING_MESSAGE 1 - (I'm pretty sure this is never really used and can be gutted) + #define HEARING_MESSAGE 1 #define HEARING_SPEAKER 2 #define HEARING_LANGUAGE 3 #define HEARING_RAW_MESSAGE 4 - //#define HEARING_RADIO_FREQ 5 + #define HEARING_RADIO_FREQ 5 #define HEARING_SPANS 6 #define HEARING_MESSAGE_MODE 7 #define HEARING_RANGE 8 diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm index a4f19e29979b..d21e6b286ec4 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm @@ -15,6 +15,9 @@ #define COMSIG_ATOM_ON_Z_IMPACT "movable_on_z_impact" ///called on a movable (NOT living) when it starts pulling (atom/movable/pulled, state, force) #define COMSIG_ATOM_START_PULL "movable_start_pull" +/// called on /atom when something attempts to pass through it (atom/movable/source, atom/movable/passing, dir) +#define COMSIG_ATOM_TRIED_PASS "atom_tried_pass" + #define COMSIG_COMPONENT_PERMIT_PASSAGE (1 << 0) ///called on /living when someone starts pulling (atom/movable/pulled, state, force) #define COMSIG_LIVING_START_PULL "living_start_pull" ///called on /living when someone is pulled (mob/living/puller) diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm index 23e91ad86b23..a40436e77365 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm @@ -2,6 +2,10 @@ // When the signal is called: (signal arguments) // All signals send the source datum of the signal as the first argument +///from the [EX_ACT] wrapper macro: (severity, target) +#define COMSIG_ATOM_PRE_EX_ACT "atom_pre_ex_act" + /// if returned, don't let the explosion act on this atom + #define COMPONENT_CANCEL_EX_ACT (1<<0) ///from the [EX_ACT] wrapper macro: (severity, target) #define COMSIG_ATOM_EX_ACT "atom_ex_act" ///from base of atom/emp_act(): (severity) @@ -9,6 +13,14 @@ ///from base of atom/fire_act(): (exposed_temperature, exposed_volume) #define COMSIG_ATOM_FIRE_ACT "atom_fire_act" ///from base of atom/bullet_act(): (/obj/projectile, def_zone) +#define COMSIG_ATOM_PRE_BULLET_ACT "pre_atom_bullet_act" + /// All this does is prevent default bullet on_hit from being called, [BULLET_ACT_HIT] being return is implied + #define COMPONENT_BULLET_ACTED (1<<0) + /// Forces bullet act to return [BULLET_ACT_BLOCK], takes priority over above + #define COMPONENT_BULLET_BLOCKED (1<<1) + /// Forces bullet act to return [BULLET_ACT_FORCE_PIERCE], takes priority over above + #define COMPONENT_BULLET_PIERCED (1<<2) +///from base of atom/bullet_act(): (/obj/projectile, def_zone) #define COMSIG_ATOM_BULLET_ACT "atom_bullet_act" ///from base of atom/CheckParts(): (list/parts_list, datum/crafting_recipe/R) #define COMSIG_ATOM_CHECKPARTS "atom_checkparts" diff --git a/code/__DEFINES/dcs/signals/signals_blob.dm b/code/__DEFINES/dcs/signals/signals_blob.dm new file mode 100644 index 000000000000..afd4737bdd96 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_blob.dm @@ -0,0 +1,4 @@ +/// Signal sent when a blob overmind picked a new strain (/mob/camera/blob/overmind, /datum/blobstrain/new_strain) +#define COMSIG_BLOB_SELECTED_STRAIN "blob_selected_strain" +/// Signal sent by a blob spore when it creates a zombie (/mob/living/basic/blob_minion/spore/spore, //mob/living/basic/blob_minion/zombie/zombie) +#define COMSIG_BLOB_ZOMBIFIED "blob_zombified" diff --git a/code/__DEFINES/dcs/signals/signals_global.dm b/code/__DEFINES/dcs/signals/signals_global.dm index 5d78f2b63836..b1cafb083c0a 100644 --- a/code/__DEFINES/dcs/signals/signals_global.dm +++ b/code/__DEFINES/dcs/signals/signals_global.dm @@ -51,6 +51,8 @@ #define LINKED_UP (1<<0) /// an obj/item is created! (obj/item/created_item) #define COMSIG_GLOB_NEW_ITEM "!new_item" +/// called post /obj/item initialize (obj/item/created_item) +#define COMSIG_GLOB_ATOM_AFTER_POST_INIT "!atom_after_post_init" /// an obj/machinery is created! (obj/machinery/created_machine) #define COMSIG_GLOB_NEW_MACHINE "!new_machine" /// a client (re)connected, after all /client/New() checks have passed : (client/connected_client) diff --git a/code/__DEFINES/dcs/signals/signals_leash.dm b/code/__DEFINES/dcs/signals/signals_leash.dm new file mode 100644 index 000000000000..4f83d7909034 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_leash.dm @@ -0,0 +1,11 @@ +/// Called when a /datum/component/leash must forcibly teleport the parent to the owner. +/// Fired on the object with the leash component. +#define COMSIG_LEASH_FORCE_TELEPORT "leash_force_teleport" + +/// Called when a /datum/component/leash plans on pathfinding to the target, if out of range. +/// Fired on the object with the leash component. +#define COMSIG_LEASH_PATH_STARTED "leash_path_started" + +/// Called when a /datum/component/leash finishes its pathfinding to the target. +/// Fired on the object with the leash component. +#define COMSIG_LEASH_PATH_COMPLETE "leash_path_complete" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm index 85630c8e8f04..a04b8e751a0c 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm @@ -3,3 +3,4 @@ /// Signal sent when a blackboard key is cleared #define COMSIG_AI_BLACKBOARD_KEY_CLEARED(blackboard_key) "ai_blackboard_key_clear_[blackboard_key]" + diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm index 18c6c651435b..8420a4864172 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm @@ -5,3 +5,10 @@ ///from the ranged_attacks component for basic mobs: (mob/living/basic/firer, atom/target, modifiers) #define COMSIG_BASICMOB_POST_ATTACK_RANGED "basicmob_post_attack_ranged" + +/// Sent from /datum/ai_planning_subtree/parrot_as_in_repeat() : () +#define COMSIG_NEEDS_NEW_PHRASE "parrot_needs_new_phrase" + #define NO_NEW_PHRASE_AVAILABLE (1<<0) //! Cancel to try again later for when we actually get a new phrase + +/// Called whenever an animal is pet via the /datum/element/pet_bonus element: (mob/living/petter, modifiers) +#define COMSIG_ANIMAL_PET "animal_pet" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index adae4324d59e..42934c3c7fa9 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -22,11 +22,11 @@ ///When a carbon slips. Called on /turf/open/handle_slip() #define COMSIG_ON_CARBON_SLIP "carbon_slip" -///When a carbon gets a vending machine tilted on them -#define COMSIG_ON_VENDOR_CRUSH "carbon_vendor_crush" // /mob/living/carbon physiology signals #define COMSIG_CARBON_GAIN_WOUND "carbon_gain_wound" //from /datum/wound/proc/apply_wound() (/mob/living/carbon/C, /datum/wound/W, /obj/item/bodypart/L) #define COMSIG_CARBON_LOSE_WOUND "carbon_lose_wound" //from /datum/wound/proc/remove_wound() (/mob/living/carbon/C, /datum/wound/W, /obj/item/bodypart/L) +/// Called after limb AND victim has been unset +#define COMSIG_CARBON_POST_LOSE_WOUND "carbon_post_lose_wound" //from /datum/wound/proc/remove_wound() (/datum/wound/lost_wound, /obj/item/bodypart/part, ignore_limb, replaced) ///from base of /obj/item/bodypart/proc/can_attach_limb(): (new_limb, special) allows you to fail limb attachment #define COMSIG_ATTEMPT_CARBON_ATTACH_LIMB "attempt_carbon_attach_limb" #define COMPONENT_NO_ATTACH (1<<0) @@ -36,6 +36,14 @@ #define COMSIG_CARBON_POST_ATTACH_LIMB "carbon_post_attach_limb" #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 +///from /obj/item/bodypart/proc/receive_damage, sent from the limb owner (limb, brute, burn) +#define COMSIG_CARBON_LIMB_DAMAGED "carbon_limb_damaged" + #define COMPONENT_PREVENT_LIMB_DAMAGE (1 << 0) +/// from /obj/item/stack/medical/gauze/Destroy(): (/obj/item/stack/medical/gauze/removed_gauze) +#define COMSIG_BODYPART_UNGAUZED "bodypart_ungauzed" + +/// Called from bodypart changing owner, which could be on attach or detachment. Either argument can be null. (mob/living/carbon/new_owner, mob/living/carbon/old_owner) +#define COMSIG_BODYPART_CHANGED_OWNER "bodypart_changed_owner" /// Called from update_health_hud, whenever a bodypart is being updated on the health doll #define COMSIG_BODYPART_UPDATING_HEALTH_HUD "bodypart_updating_health_hud" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_guardian.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_guardian.dm new file mode 100644 index 000000000000..9f4819de7974 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_guardian.dm @@ -0,0 +1,7 @@ +/// Sent when a guardian is manifested +#define COMSIG_GUARDIAN_MANIFESTED "guardian_manifested" +/// Sent when a guardian is recalled +#define COMSIG_GUARDIAN_RECALLED "guardian_recalled" + +/// Sent when an assassin guardian is forced to exit stealth +#define COMSIG_GUARDIAN_ASSASSIN_REVEALED "guardian_assassin_revealed" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index 63fd640d77f9..2466f770827e 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -40,12 +40,50 @@ #define COMSIG_LIVING_TRY_SYRINGE "living_try_syringe" ///From living/Life(). (deltatime, times_fired) #define COMSIG_LIVING_LIFE "living_life" + /// Block the Life() proc from proceeding... this should really only be done in some really wacky situations. + #define COMPONENT_LIVING_CANCEL_LIFE_PROCESSING (1<<0) ///From living/set_resting(): (new_resting, silent, instant) #define COMSIG_LIVING_RESTING "living_resting" ///from base of element/bane/activate(): (item/weapon, mob/user) #define COMSIG_LIVING_BANED "living_baned" +///from base of element/bane/activate(): (item/weapon, mob/user) +#define COMSIG_OBJECT_PRE_BANING "obj_pre_baning" + #define COMPONENT_CANCEL_BANING (1<<0) + +///from base of element/bane/activate(): (item/weapon, mob/user) +#define COMSIG_OBJECT_ON_BANING "obj_on_baning" + +// adjust_x_loss messages sent from /mob/living/proc/adjust[x]Loss +/// Returned from all the following messages if you actually aren't going to apply any change +#define COMPONENT_IGNORE_CHANGE (1<<0) +// Each of these messages sends the damagetype even though it is inferred by the signal so you can pass all of them to the same proc if required +/// Send when bruteloss is modified (type, amount, forced) +#define COMSIG_LIVING_ADJUST_BRUTE_DAMAGE "living_adjust_brute_damage" +/// Send when fireloss is modified (type, amount, forced) +#define COMSIG_LIVING_ADJUST_BURN_DAMAGE "living_adjust_burn_damage" +/// Send when oxyloss is modified (type, amount, forced) +#define COMSIG_LIVING_ADJUST_OXY_DAMAGE "living_adjust_oxy_damage" +/// Send when toxloss is modified (type, amount, forced) +#define COMSIG_LIVING_ADJUST_TOX_DAMAGE "living_adjust_tox_damage" +/// Send when cloneloss is modified (type, amount, forced) +#define COMSIG_LIVING_ADJUST_CLONE_DAMAGE "living_adjust_clone_damage" +/// Send when staminaloss is modified (type, amount, forced) +#define COMSIG_LIVING_ADJUST_STAMINA_DAMAGE "living_adjust_stamina_damage" + +/// List of signals sent when you receive any damage except stamina +#define COMSIG_LIVING_ADJUST_STANDARD_DAMAGE_TYPES list(\ + COMSIG_LIVING_ADJUST_BRUTE_DAMAGE,\ + COMSIG_LIVING_ADJUST_BURN_DAMAGE,\ + COMSIG_LIVING_ADJUST_CLONE_DAMAGE,\ + COMSIG_LIVING_ADJUST_OXY_DAMAGE,\ + COMSIG_LIVING_ADJUST_TOX_DAMAGE,\ +) +/// List of signals sent when you receive any kind of damage at all +#define COMSIG_LIVING_ADJUST_ALL_DAMAGE_TYPES (COMSIG_LIVING_ADJUST_STANDARD_DAMAGE_TYPES + COMSIG_LIVING_ADJUST_STAMINA_DAMAGE) + + /// from base of mob/living/updatehealth() #define COMSIG_LIVING_HEALTH_UPDATE "living_health_update" ///from base of mob/living/death(): (gibbed) @@ -97,6 +135,10 @@ #define COMSIG_LIVING_UNARMED_ATTACK "living_unarmed_attack" ///From base of mob/living/MobBump() (mob/living) #define COMSIG_LIVING_MOB_BUMP "living_mob_bump" +///From base of mob/living/Bump() (turf/closed) +#define COMSIG_LIVING_WALL_BUMP "living_wall_bump" +///From base of turf/closed/Exited() (turf/closed) +#define COMSIG_LIVING_WALL_EXITED "living_wall_exited" ///From base of mob/living/ZImpactDamage() (mob/living, levels, turf/t) #define COMSIG_LIVING_Z_IMPACT "living_z_impact" #define NO_Z_IMPACT_DAMAGE (1<<0) @@ -168,3 +210,10 @@ /// From /datum/ai/behavior/climb_tree/perform() : (mob/living/basic/living_pawn) #define COMSIG_LIVING_CLIMB_TREE "living_climb_tree" + +/// Sent on a mob from /datum/component/mob_chain when component is attached with it as the "front" : (mob/living/basic/tail) +#define COMSIG_MOB_GAINED_CHAIN_TAIL "living_gained_chain_tail" +/// Sent on a mob from /datum/component/mob_chain when component is detached from it as the "front" : (mob/living/basic/tail) +#define COMSIG_MOB_LOST_CHAIN_TAIL "living_detached_chain_tail" +/// Sent from a 'contract chain' button on a mob chain +#define COMSIG_MOB_CHAIN_CONTRACT "living_chain_contracted" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index fc9fcf22893f..a7a04b7dfdd3 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -7,6 +7,8 @@ #define COMSIG_MOB_LOGIN "mob_login" ///from base of /mob/Logout(): () #define COMSIG_MOB_LOGOUT "mob_logout" +///from base of /mob/mind_initialize +#define COMSIG_MOB_MIND_INITIALIZED "mob_mind_inited" ///from base of mob/set_stat(): (new_stat, old_stat) #define COMSIG_MOB_STATCHANGE "mob_statchange" ///from base of mob/clickon(): (atom/A, params) @@ -25,7 +27,10 @@ /// From base of /mob/living/simple_animal/bot/proc/bot_step() #define COMSIG_MOB_BOT_STEP "mob_bot_step" -/// From base of /client/Move() +/// From base of /mob/proc/update_held_items +#define COMSIG_MOB_UPDATE_HELD_ITEMS "mob_update_held_items" + +/// From base of /client/Move(): (list/move_args) #define COMSIG_MOB_CLIENT_PRE_LIVING_MOVE "mob_client_pre_living_move" /// Should we stop the current living movement attempt #define COMSIG_MOB_CLIENT_BLOCK_PRE_LIVING_MOVE COMPONENT_MOVABLE_BLOCK_PRE_MOVE @@ -83,11 +88,15 @@ ///from base of mob/set_invis_see(): (new_invis, old_invis) #define COMSIG_MOB_SEE_INVIS_CHANGE "mob_see_invis_change" - +/// from /mob/living/proc/apply_damage(): (list/damage_mods, damage, damagetype, def_zone, sharpness, attack_direction, attacking_item) +/// allows you to add multiplicative damage modifiers to the damage mods argument to adjust incoming damage +/// not sent if the apply damage call was forced +#define COMSIG_MOB_APPLY_DAMAGE_MODIFIERS "mob_apply_damage_modifiers" ///from base of /mob/living/proc/apply_damage(): (damage, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction) #define COMSIG_MOB_APPLY_DAMAGE "mob_apply_damage" ///from /mob/living/proc/apply_damage(), works like above but after the damage is actually inflicted: (damage, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction) #define COMSIG_MOB_AFTER_APPLY_DAMAGE "mob_after_apply_damage" + ///from base of /mob/living/attack_alien(): (user) #define COMSIG_MOB_ATTACK_ALIEN "mob_attack_alien" ///from base of /mob/throw_item(): (atom/target) @@ -209,3 +218,6 @@ /// from mob/proc/dropItemToGround() #define COMSIG_MOB_DROPPING_ITEM "mob_dropping_item" + +/// from /mob/proc/change_mob_type_unchecked() : () +#define COMSIG_MOB_CHANGED_TYPE "mob_changed_type" diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 5fd653fd600f..08d80b05ad6e 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -129,6 +129,15 @@ #define COMSIG_ITEM_DROPPED "item_drop" ///from base of obj/item/pickup(): (/mob/taker) #define COMSIG_ITEM_PICKUP "item_pickup" +///from base of obj/item/on_outfit_equip(): (mob/equipper, visuals_only, slot) +#define COMSIG_ITEM_EQUIPPED_AS_OUTFIT "item_equip_as_outfit" +///from base of datum/storage/attempt_insert(): () +#define COMSIG_ITEM_STORED "item_stored" + +///from base of obj/item/apply_fantasy_bonuses(): (bonus) +#define COMSIG_ITEM_APPLY_FANTASY_BONUSES "item_apply_fantasy_bonuses" +///from base of obj/item/remove_fantasy_bonuses(): (bonus) +#define COMSIG_ITEM_REMOVE_FANTASY_BONUSES "item_remove_fantasy_bonuses" /// Sebt from obj/item/ui_action_click(): (mob/user, datum/action) #define COMSIG_ITEM_UI_ACTION_CLICK "item_action_click" @@ -175,7 +184,11 @@ ///from [/obj/structure/closet/supplypod/proc/preOpen]: #define COMSIG_SUPPLYPOD_LANDED "supplypodgoboom" -///from /obj/item/storage/book/bible/afterattack(): (mob/user, proximity) +/// from [/obj/item/stack/proc/can_merge]: (obj/item/stack/merge_with, in_hand) +#define COMSIG_STACK_CAN_MERGE "stack_can_merge" + #define CANCEL_STACK_MERGE (1<<0) + +///from /obj/item/book/bible/afterattack(): (mob/user, proximity) #define COMSIG_BIBLE_SMACKED "bible_smacked" ///stops the bible chain from continuing. When all of the effects of the bible smacking have been moved to a signal we can kill this #define COMSIG_END_BIBLE_CHAIN (1<<0) @@ -221,6 +234,11 @@ ///called when getting the item's exact ratio for cargo's profit, without selling the item. #define COMSIG_ITEM_SPLIT_PROFIT_DRY "item_split_profits_dry" +/// Called on component/uplink/OnAttackBy(..) +#define COMSIG_ITEM_ATTEMPT_TC_REIMBURSE "item_attempt_tc_reimburse" +///Called when a holoparasite/guardiancreator is used. +#define COMSIG_TRAITOR_ITEM_USED(type) "traitor_item_used_[type]" + // /obj/item/clothing signals ///from [/mob/living/carbon/human/Move]: () diff --git a/code/__DEFINES/dcs/signals/signals_reagent.dm b/code/__DEFINES/dcs/signals/signals_reagent.dm index 00fdddc6c9e7..a73d59a234c2 100644 --- a/code/__DEFINES/dcs/signals/signals_reagent.dm +++ b/code/__DEFINES/dcs/signals/signals_reagent.dm @@ -6,6 +6,8 @@ #define COMSIG_ATOM_EXPOSE_REAGENTS "atom_expose_reagents" /// Prevents the atom from being exposed to reagents if returned on [COMSIG_ATOM_EXPOSE_REAGENTS] #define COMPONENT_NO_EXPOSE_REAGENTS (1<<0) +///from base of atom/expose_reagents(): (/list, /datum/reagents, methods, volume_modifier, show_message) +#define COMSIG_ATOM_AFTER_EXPOSE_REAGENTS "atom_after_expose_reagents" ///from base of [/datum/reagent/proc/expose_atom]: (/datum/reagent, reac_volume) #define COMSIG_ATOM_EXPOSE_REAGENT "atom_expose_reagent" ///from base of [/datum/reagent/proc/expose_atom]: (/atom, reac_volume) diff --git a/code/__DEFINES/dcs/signals/signals_turf.dm b/code/__DEFINES/dcs/signals/signals_turf.dm index ea4791f5629b..7ea0a9631096 100644 --- a/code/__DEFINES/dcs/signals/signals_turf.dm +++ b/code/__DEFINES/dcs/signals/signals_turf.dm @@ -39,3 +39,5 @@ #define COMSIG_TURF_PREPARE_STEP_SOUND "turf_prepare_step_sound" ///from base of datum/thrownthing/finalize(): (turf/turf, atom/movable/thrownthing) when something is thrown and lands on us #define COMSIG_TURF_MOVABLE_THROW_LANDED "turf_movable_throw_landed" +///from /obj/item/pushbroom/sweep(): (broom, user, items_to_sweep) +#define COMSIG_TURF_RECEIVE_SWEEPED_ITEMS "turf_receive_sweeped_items" diff --git a/code/__DEFINES/drone.dm b/code/__DEFINES/drone.dm index 46c19fdbaf41..caf8f5015ecd 100644 --- a/code/__DEFINES/drone.dm +++ b/code/__DEFINES/drone.dm @@ -2,9 +2,8 @@ /// If drones are blacklisted from certain sensitive machines GLOBAL_VAR_INIT(drone_machine_blacklist_enabled, TRUE) -#define DRONE_HANDS_LAYER 1 -#define DRONE_HEAD_LAYER 2 -#define DRONE_TOTAL_LAYERS 2 +#define DRONE_HEAD_LAYER 1 +#define DRONE_TOTAL_LAYERS 1 /// Message displayed when new drone spawns in drone network #define DRONE_NET_CONNECT span_notice("DRONE NETWORK: [name] connected.") diff --git a/code/__DEFINES/explosions.dm b/code/__DEFINES/explosions.dm index 0ce9644af1c2..a1645b659d1c 100644 --- a/code/__DEFINES/explosions.dm +++ b/code/__DEFINES/explosions.dm @@ -23,8 +23,10 @@ if(!(target.flags_1 & PREVENT_CONTENTS_EXPLOSION_1)) { \ target.contents_explosion(##args);\ };\ - SEND_SIGNAL(target, COMSIG_ATOM_EX_ACT, ##args);\ - target.ex_act(##args); + if(!(SEND_SIGNAL(target, COMSIG_ATOM_PRE_EX_ACT, ##args) & COMPONENT_CANCEL_EX_ACT)) { \ + SEND_SIGNAL(target, COMSIG_ATOM_EX_ACT, ##args);\ + target.ex_act(##args);\ + } // Internal explosion argument list keys. // Must match the arguments to [/datum/controller/subsystem/explosions/proc/propagate_blastwave] diff --git a/code/__DEFINES/footsteps.dm b/code/__DEFINES/footsteps.dm index 4ec2ce0f90d2..fd88a17ce9f5 100644 --- a/code/__DEFINES/footsteps.dm +++ b/code/__DEFINES/footsteps.dm @@ -18,15 +18,25 @@ //misc footstep sounds #define FOOTSTEP_GENERIC_HEAVY "heavy" + //footstep mob defines -#define FOOTSTEP_MOB_CLAW 1 -#define FOOTSTEP_MOB_BAREFOOT 2 -#define FOOTSTEP_MOB_HEAVY 3 -#define FOOTSTEP_MOB_SHOE 4 -#define FOOTSTEP_MOB_HUMAN 5 //Warning: Only works on /mob/living/carbon/human -#define FOOTSTEP_MOB_SLIME 6 -#define FOOTSTEP_OBJ_MACHINE 7 -#define FOOTSTEP_OBJ_ROBOT 8 +#define FOOTSTEP_MOB_CLAW "footstep_claw" +#define FOOTSTEP_MOB_BAREFOOT "footstep_barefoot" +#define FOOTSTEP_MOB_HEAVY "footstep_heavy" +#define FOOTSTEP_MOB_SHOE "footstep_shoe" +#define FOOTSTEP_MOB_HUMAN "footstep_human" //Warning: Only works on /mob/living/carbon/human +#define FOOTSTEP_MOB_SLIME "footstep_slime" +#define FOOTSTEP_MOB_RUST "footstep_rust" +#define FOOTSTEP_OBJ_MACHINE "footstep_machine" +#define FOOTSTEP_OBJ_ROBOT "footstep_robot" + +//priority defines for the footstep_override element +#define STEP_SOUND_NO_PRIORITY 0 +#define STEP_SOUND_CONVEYOR_PRIORITY 1 +#define STEP_SOUND_TABLE_PRIORITY 2 + +///the name of the index key for priority +#define STEP_SOUND_PRIORITY "step_sound_priority" /* diff --git a/code/__DEFINES/guardian_defines.dm b/code/__DEFINES/guardian_defines.dm index d7aae6965a75..ae2c3175b4a6 100644 --- a/code/__DEFINES/guardian_defines.dm +++ b/code/__DEFINES/guardian_defines.dm @@ -3,6 +3,28 @@ #define GUARDIAN_THEME_CARP "carp" #define GUARDIAN_THEME_MINER "miner" -#define GUARDIAN_COLOR_LAYER 2 -#define GUARDIAN_HANDS_LAYER 1 -#define GUARDIAN_TOTAL_LAYERS 2 +#define GUARDIAN_MAGIC "magic" +#define GUARDIAN_TECH "tech" + +#define GUARDIAN_ASSASSIN "assassin" +#define GUARDIAN_CHARGER "charger" +#define GUARDIAN_DEXTROUS "dextrous" +#define GUARDIAN_EXPLOSIVE "explosive" +#define GUARDIAN_GASEOUS "gaseous" +#define GUARDIAN_GRAVITOKINETIC "gravitokinetic" +#define GUARDIAN_LIGHTNING "lightning" +#define GUARDIAN_PROTECTOR "protector" +#define GUARDIAN_RANGED "ranged" +#define GUARDIAN_STANDARD "standard" +#define GUARDIAN_SUPPORT "support" + +/// List of all guardians currently extant +GLOBAL_LIST_EMPTY(parasites) + +/// Assoc list of guardian theme singletons +GLOBAL_LIST_INIT(guardian_themes, list( + GUARDIAN_THEME_TECH = new /datum/guardian_fluff/tech, + GUARDIAN_THEME_MAGIC = new /datum/guardian_fluff, + GUARDIAN_THEME_CARP = new /datum/guardian_fluff/carp, + GUARDIAN_THEME_MINER = new /datum/guardian_fluff/miner, +)) diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 37611368f8b1..e84d26de32c3 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -80,6 +80,7 @@ GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list( #define isabductor(A) (is_species(A, /datum/species/abductor)) #define isgolem(A) (is_species(A, /datum/species/golem)) #define islizard(A) (is_species(A, /datum/species/lizard)) +#define isashwalker(A) (is_species(A, /datum/species/lizard/ashwalker)) #define isplasmaman(A) (is_species(A, /datum/species/plasmaman)) #define issimian(A) (is_species(A, /datum/species/simian)) //Monkestation Addition #define ispodperson(A) (is_species(A, /datum/species/pod)) @@ -130,8 +131,16 @@ GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list( // basic mobs #define isbasicmob(A) (istype(A, /mob/living/basic)) +#define isconstruct(A) (istype(A, /mob/living/basic/construct)) + #define iscow(A) (istype(A, /mob/living/basic/cow)) +#define isgorilla(A) (istype(A, /mob/living/basic/gorilla)) + +#define isshade(A) (istype(A, /mob/living/basic/shade)) + +#define is_simian(A) (isgorilla(A) || ismonkey(A)) + /// returns whether or not the atom is either a basic mob OR simple animal #define isanimal_or_basicmob(A) (istype(A, /mob/living/simple_animal) || istype(A, /mob/living/basic)) @@ -143,17 +152,15 @@ GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list( //Simple animals #define isanimal(A) (istype(A, /mob/living/simple_animal)) -#define isrevenant(A) (istype(A, /mob/living/simple_animal/revenant)) +#define isrevenant(A) (istype(A, /mob/living/basic/revenant)) #define isbot(A) (istype(A, /mob/living/simple_animal/bot)) -#define isshade(A) (istype(A, /mob/living/simple_animal/shade)) - #define ismouse(A) (istype(A, /mob/living/basic/mouse)) #define isslime(A) (istype(A, /mob/living/simple_animal/slime)) -#define isdrone(A) (istype(A, /mob/living/simple_animal/drone)) +#define isdrone(A) (istype(A, /mob/living/basic/drone)) #define iscat(A) (istype(A, /mob/living/simple_animal/pet/cat)) @@ -165,13 +172,11 @@ GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list( #define isregalrat(A) (istype(A, /mob/living/basic/regal_rat)) -#define isguardian(A) (istype(A, /mob/living/simple_animal/hostile/guardian)) - -#define isconstruct(A) (istype(A, /mob/living/simple_animal/hostile/construct)) +#define isguardian(A) (istype(A, /mob/living/basic/guardian)) #define ismegafauna(A) (istype(A, /mob/living/simple_animal/hostile/megafauna)) -#define isclown(A) (istype(A, /mob/living/simple_animal/hostile/retaliate/clown)) +#define isclown(A) (istype(A, /mob/living/basic/clown)) #define isspider(A) (istype(A, /mob/living/basic/spider/giant)) @@ -216,6 +221,8 @@ GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list( #define ismachinery(A) (istype(A, /obj/machinery)) +#define isvendor(A) (istype(A, /obj/machinery/vending)) + #define isvehicle(A) (istype(A, /obj/vehicle)) #define ismecha(A) (istype(A, /obj/vehicle/sealed/mecha)) @@ -267,8 +274,6 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list( #define isholoeffect(O) (istype(O, /obj/effect/holodeck_effect)) -#define isblobmonster(O) (istype(O, /mob/living/simple_animal/hostile/blob)) - #define isshuttleturf(T) (!isnull(T.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle))) #define isProbablyWallMounted(O) (O.pixel_x > 20 || O.pixel_x < -20 || O.pixel_y > 20 || O.pixel_y < -20) diff --git a/code/__DEFINES/megafauna.dm b/code/__DEFINES/megafauna.dm new file mode 100644 index 000000000000..981b8d49f520 --- /dev/null +++ b/code/__DEFINES/megafauna.dm @@ -0,0 +1,4 @@ +/// Temperature of drake fire hotspots +#define DRAKE_FIRE_TEMP 500 +/// Volume of drake fire hotspots +#define DRAKE_FIRE_EXPOSURE 50 diff --git a/code/__DEFINES/mood.dm b/code/__DEFINES/mood.dm new file mode 100644 index 000000000000..161f253b04c7 --- /dev/null +++ b/code/__DEFINES/mood.dm @@ -0,0 +1 @@ +#define MOOD_CATEGORY_LEGION_CORE "regenerative core" diff --git a/code/__DEFINES/obj_flags.dm b/code/__DEFINES/obj_flags.dm index 51bb3d2e9afa..36ad2ddd1e30 100644 --- a/code/__DEFINES/obj_flags.dm +++ b/code/__DEFINES/obj_flags.dm @@ -42,6 +42,8 @@ #define ITEM_HAS_CONTEXTUAL_SCREENTIPS (1 << 19) /// No blood overlay is allowed to appear on this item, and it cannot gain blood DNA forensics #define NO_BLOOD_ON_ITEM (1 << 20) +/// Whether this item should skip the /datum/component/fantasy applied on spawn on the RPG event. Used on things like stacks +#define SKIP_FANTASY_ON_SPAWN (1<<21) // Flags for the clothing_flags var on /obj/item/clothing diff --git a/code/__DEFINES/pai.dm b/code/__DEFINES/pai.dm index f47524d32e26..4015ee3bc504 100644 --- a/code/__DEFINES/pai.dm +++ b/code/__DEFINES/pai.dm @@ -11,6 +11,13 @@ /// The amount of time between spamming for pAI candidates #define PAI_SPAM_TIME (40 SECONDS) +/// Maximum distance you can set the holoform leash +#define HOLOFORM_MAX_RANGE 9 +/// Minimum distance you can set the holoform leash +#define HOLOFORM_MIN_RANGE 3 +/// Default holoform leash distance +#define HOLOFORM_DEFAULT_RANGE HOLOFORM_MAX_RANGE + /// UI action to toggle huds #define PAI_TOGGLE_MEDICAL_HUD 0 #define PAI_TOGGLE_SECURITY_HUD 1 diff --git a/code/__DEFINES/projectiles.dm b/code/__DEFINES/projectiles.dm index 8283e3840598..712b1503aa17 100644 --- a/code/__DEFINES/projectiles.dm +++ b/code/__DEFINES/projectiles.dm @@ -56,6 +56,7 @@ #define CALIBER_HOOK "hook" /// The caliber used by the changeling tentacle mutation. #define CALIBER_TENTACLE "tentacle" +#define CALIBER_A223 "a223" /// For gunpoints, how many tiles around the target the shooter can roam without losing their shot #define GUNPOINT_SHOOTER_STRAY_RANGE 2 diff --git a/code/__DEFINES/song.dm b/code/__DEFINES/song.dm index 7af269fbe219..782a7923ea14 100644 --- a/code/__DEFINES/song.dm +++ b/code/__DEFINES/song.dm @@ -13,3 +13,5 @@ ///it's what monkeys play! #define MONKEY_SONG "BPM: 200\nC4/0,14,C,A4-F2,F3,A3,F-F2,A-F,F4,G4,F,D4-Bb2-G2\nD3,G3,D-G2,G3-G2,D,D4-G3,D,B4-B2,G,B3,G-B2,B3-B2\nG4,A4,G,E4-C3,E3,G3,E-C,G-C,E,E4-G,E,C5-E-A3,C4\nA-E3,C,E4-C3,A4-C4,B4-A3-A2,C5-C4,D5-F-B3,D4,B-F3\nD,F4-D3,D4,F-B-B2,G4-D,A4-C-F3,F,C/2,B3/2,A3-C3/2\nB/2,C4,E-C3,F4,G-C,F-F3,F-C,C4/2,B/2,A-A2/2,G3/2\nF/I" +///song played by the mook bard +#define MOOK_SONG "BPM: 240\nA5,B5,C#6,D6,E6/0.17,A/0.5,A/0.25,A3/0.25\nA4/0.25,C#5/0.25,E5/0.25,A/0.25,C#/0.25,E/0.12\nC#6/0.25,C#/0.25,E6/0.25,A3/0.25,A4/0.25\nC#5/0.25,E5/0.25,A/0.25,C#/0.25,E/0.25,D/0.25\nG6/0.25,D/0.17,F6/0.17,C#6/0.5,E6/0.5,D4/0.25\nA/0.25,D5/0.25,F5/0.25,A/0.25,D/0.25,F/0.25\nD6/0.08,F6/0.08,D4/0.25,A/0.25,D5/0.25,F5/0.25\nCn4/0.2,B/0.17,D6/0.17,G5/0.5,G/0.25,B3/0.25\nD4/0.25,G4/0.25,B4/0.25,D/0.25,G/0.25,B/0.12\nB5/0.25,B/0.25,D6/0.25,G3/0.25,G4/0.25,B4/0.25\nF/0.25,G/0.25,B/0.25,F/0.25,D/0.25,F6/0.25\nC6/0.17,E/0.17,B5/0.5,D#/0.5,C4/0.25,G/0.25\nC5/0.25,E5/0.25,G/0.25,C/0.25,E/0.25,C6/0.08\nE6/0.08,C4/0.25,Dn4/0.25,E4/0.25,A5/0.17,B/0.5\nC6/0.25,F5/0.08,F4/0.08,C5/0.08,E5/0.12,G5/0.12\nC6/0.25,E6/0.25,E4/0.08,C5/0.08,B/0.17,F6/0.17\nE6/0.5,B/0.25,E4/0.08,G#4/0.08,C6/0.17,D6/0.5\nE6/0.25,A3/0.25,E4/0.25,C5/0.25,Gn3/0.25\nF5/0.12,A5/0.12,A6/0.25,F3/0.25,F4/0.12,A4/0.12\nC/0.12,F6/0.17,A6/0.17,G#6/0.5,A/0.25,F3/0.25\nF4/0.12,A4/0.12,D#5/0.12,B/0.17,G#/0.17,B6/0.5\nB5/0.25,G#/0.25,E3/0.25,E4/0.12,G#4/0.12\nDn/0.12,E6/0.08,E3/0.25,F#3/0.25,G#3/0.25\nE5/0.17,A5/0.17,E/0.5,E/0.25,A3/0.25,C#4/0.25\nE4/0.25,C#/0.25,E/0.12,A5/0.5,B/0.5,C#6/0.5\nD6/0.5,A3/0.25,C#4/0.25,E/0.25,C#/0.25,E/0.25\nE6/0.08,Gn/0.25,E4/0.25,A4/0.25,C#5/0.25,E/0.25\nA/0.25,C#/0.25,E6/0.17,E/0.5,Fn6/0.5,G6/0.5\nG3/0.25,E4/0.25,A/0.25,C#/0.25,E/0.25,A/0.25\nC#/0.25,F/0.08,A6/0.08,F3/0.25,F4/0.25,A4/0.25\nCn/0.25,F/0.25,A/0.25,C/0.25,G6/0.12,A6/0.12\nG A G F6 G3/0.25 D4/0.25 G4/0.25 B4/0.25 D/0.25\nG/0.25 B/0.25 E6/0.12 G6/0.12 F/0.71 G/0.71 F/0.71\nE3/0.25 E4/0.25 G4/0.25 B/0.25 E/0.25 G/0.25 B/0.25\nA5/0.08 E6/0.08 A3/0.25 E4/0.25 A4/0.25 C#/0.25 E/0.25 A/0.25 C#/0.25 D6/0.17 E6/0.5 F/0.25 B3/0.25 D4/0.12 F4/0.12 B4/0.12 F6/0.25 E/0.25 D6/0.25 G#3/0.25 E4/0.12 G#4/0.12 B/0.12 Cn6/0.12 D/0.25 A3/0.25 A4/0.25 C5/0.25 E5/0.25 G#3/0.25 Gn/0.25 C4/0.25 E4/0.25 A/" diff --git a/code/__DEFINES/span.dm b/code/__DEFINES/span.dm index 69190c39b626..7e31cc4a48b6 100644 --- a/code/__DEFINES/span.dm +++ b/code/__DEFINES/span.dm @@ -17,11 +17,13 @@ #define span_bigicon(str) ("" + str + "") #define span_binarysay(str) ("" + str + "") #define span_blob(str) ("" + str + "") +#define span_blobannounce(str) ("" + str + "") #define span_blue(str) ("" + str + "") #define span_blueteamradio(str) ("" + str + "") #define span_bold(str) ("" + str + "") #define span_boldannounce(str) ("" + str + "") #define span_bolddanger(str) ("" + str + "") +#define span_bolditalic(str) ("" + str + "") #define span_boldnicegreen(str) ("" + str + "") #define span_boldnotice(str) ("" + str + "") #define span_boldwarning(str) ("" + str + "") diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 07230479d473..a8455c502404 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -38,6 +38,8 @@ #define STASIS_CHEMICAL_EFFECT "stasis_chemical" #define STASIS_SHAPECHANGE_EFFECT "stasis_shapechange" +#define STASIS_ADMIN "stasis_admin" +#define STASIS_LEGION_EATEN "stasis_eaten" #define STASIS_NETPOD_EFFECT "stasis_netpod" diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 2a7084841746..b92ea50900c7 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -317,6 +317,9 @@ #define SSMOBS_DT (SSmobs.wait/10) #define SSOBJ_DT (SSobj.wait/10) +// The change in the world's time from the subsystem's last fire in seconds. +#define DELTA_WORLD_TIME(ss) ((world.time - ss.last_fire) * 0.1) + /// The timer key used to know how long subsystem initialization takes #define SS_INIT_TIMER_KEY "ss_init" diff --git a/code/__DEFINES/surgery.dm b/code/__DEFINES/surgery.dm index 5f698598d73e..e5b528f96d30 100644 --- a/code/__DEFINES/surgery.dm +++ b/code/__DEFINES/surgery.dm @@ -42,3 +42,8 @@ #define SURGERY_REQUIRE_LIMB (1<<3) ///Will allow the surgery to work only if there's a real (eg. not pseudopart) limb. #define SURGERY_REQUIRES_REAL_LIMB (1<<4) +///Will grant a bonus during surgery steps to users with TRAIT_MORBID while they're using tools with CRUEL_IMPLEMENT +#define SURGERY_MORBID_CURIOSITY (1<<5) + +///Return true if target is not in a valid body position for the surgery +#define IS_IN_INVALID_SURGICAL_POSITION(target, surgery) ((surgery.surgery_flags & SURGERY_REQUIRE_RESTING) && (target.mobility_flags & MOBILITY_LIEDOWN && target.body_position != LYING_DOWN)) diff --git a/code/__DEFINES/trader.dm b/code/__DEFINES/trader.dm new file mode 100644 index 000000000000..aae6da264998 --- /dev/null +++ b/code/__DEFINES/trader.dm @@ -0,0 +1,16 @@ +#define ITEM_REJECTED_PHRASE "ITEM_REJECTED_PHRASE" +#define ITEM_SELLING_CANCELED_PHRASE "ITEM_SELLING_CANCELED_PHRASE" +#define ITEM_SELLING_ACCEPTED_PHRASE "ITEM_SELLING_ACCEPTED_PHRASE" +#define INTERESTED_PHRASE "INTERESTED_PHRASE" +#define BUY_PHRASE "BUY_PHRASE" +#define NO_CASH_PHRASE "NO_CASH_PHRASE" +#define NO_STOCK_PHRASE "NO_STOCK_PHRASE" +#define NOT_WILLING_TO_BUY_PHRASE "NOT_WILLING_TO_BUY_PHRASE" +#define ITEM_IS_WORTHLESS_PHRASE "ITEM_IS_WORTHLESS_PHRASE" +#define TRADER_HAS_ENOUGH_ITEM_PHRASE "TRADER_HAS_ENOUGH_ITEM_PHRASE" +#define TRADER_LORE_PHRASE "TRADER_LORE_PHRASE" +#define TRADER_BATTLE_START_PHRASE "TRADER_AGGRO_PHRASE" +#define TRADER_BATTLE_END_PHRASE "TRADER_DEAGGRO_PHRASE" +#define TRADER_NOT_BUYING_ANYTHING "TRADER_NOT_BUYING_ANYTHING" +#define TRADER_NOT_SELLING_ANYTHING "TRADER_NOT_SELLING_ANYTHING" +#define TRADER_SHOP_OPENING_PHRASE "TRADER_SHOP_OPENING_PHRASE" diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 7ef7a0f10c38..014a5a970fef 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -144,6 +144,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_PULL_BLOCKED "pullblocked" /// Abstract condition that prevents movement if being pulled and might be resisted against. Handcuffs and straight jackets, basically. #define TRAIT_RESTRAINED "restrained" +/// Apply this to make a mob not dense, and remove it when you want it to no longer make them undense, other sorces of undesity will still apply. Always define a unique source when adding a new instance of this! +#define TRAIT_UNDENSE "undense" +/// Expands our FOV by 30 degrees if restricted +#define TRAIT_EXPANDED_FOV "expanded_fov" /// Doesn't miss attacks #define TRAIT_PERFECT_ATTACKER "perfect_attacker" #define TRAIT_INCAPACITATED "incapacitated" @@ -172,6 +176,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_DEFIB_BLACKLISTED "defib_blacklisted" #define TRAIT_BADDNA "baddna" #define TRAIT_CLUMSY "clumsy" +/// Trait that means you are capable of holding items in some form +#define TRAIT_CAN_HOLD_ITEMS "can_hold_items" +/// Trait which lets you clamber over a barrier +#define TRAIT_FENCE_CLIMBER "can_climb_fences" /// means that you can't use weapons with normal trigger guards. #define TRAIT_CHUNKYFINGERS "chunkyfingers" #define TRAIT_DUMB "dumb" @@ -189,8 +197,13 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Makes the owner appear as dead to most forms of medical examination #define TRAIT_FAKEDEATH "fakedeath" #define TRAIT_DISFIGURED "disfigured" +/// "Magic" trait that blocks the mob from moving or interacting with anything. Used for transient stuff like mob transformations or incorporality in special cases. +/// Will block movement, `Life()` (!!!), and other stuff based on the mob. +#define TRAIT_NO_TRANSFORM "block_transformations" /// Tracks whether we're gonna be a baby alien's mummy. #define TRAIT_XENO_HOST "xeno_host" +/// This parrot is currently perched +#define TRAIT_PARROT_PERCHED "parrot_perched" /// This mob is immune to stun causing status effects and stamcrit. /// Prefer to use [/mob/living/proc/check_stun_immunity] over checking for this trait exactly. #define TRAIT_STUNIMMUNE "stun_immunity" @@ -479,6 +492,19 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai // and emit less heat. Present on /mob or /datum/mind #define TRAIT_SUPERMATTER_SOOTHER "supermatter_soother" +/// Trait added when a revenant is visible. +#define TRAIT_REVENANT_REVEALED "revenant_revealed" +/// Trait added when a revenant has been inhibited (typically by the bane of a holy weapon) +#define TRAIT_REVENANT_INHIBITED "revenant_inhibited" + +/// Trait which prevents you from becoming overweight +#define TRAIT_NOFAT "cant_get_fat" + +/// Trait which allows you to eat rocks +#define TRAIT_ROCK_EATER "rock_eater" +/// Trait which allows you to gain bonuses from consuming rocks +#define TRAIT_ROCK_METAMORPHIC "rock_metamorphic" + /// `do_teleport` will not allow this atom to teleport #define TRAIT_NO_TELEPORT "no-teleport" /// This atom is a secluded location, which is counted as out of bounds. @@ -587,7 +613,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_TENTACLE_IMMUNE "tentacle_immune" /// Currently under the effect of overwatch #define TRAIT_OVERWATCHED "watcher_overwatched" -/// Cannot be targetted by watcher overwatch +/// Cannot be targeted by watcher overwatch #define TRAIT_OVERWATCH_IMMUNE "overwatch_immune" //non-mob traits @@ -829,6 +855,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai ///Trait needed for the lubefish evolution #define TRAIT_FISH_FED_LUBE "fish_fed_lube" +/// Trait given to angelic constructs to let them purge cult runes +#define TRAIT_ANGELIC "angelic" + // common trait sources #define TRAIT_GENERIC "generic" #define UNCONSCIOUS_TRAIT "unconscious" @@ -899,6 +928,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define SUIT_TRAIT "suit" /// Trait associated to lying down (having a [lying_angle] of a different value than zero). #define LYING_DOWN_TRAIT "lying-down" +/// A trait gained by leaning against a wall +#define LEANING_TRAIT "leaning" /// Trait associated to lacking electrical power. #define POWER_LACK_TRAIT "power-lack" /// Trait associated to lacking motor movement @@ -968,7 +999,15 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define SPECIES_FLIGHT_TRAIT "species-flight" #define FROSTMINER_ENRAGE_TRAIT "frostminer-enrage" #define NO_GRAVITY_TRAIT "no-gravity" +/// A trait gained from a mob's leap action, like the leaper #define LEAPING_TRAIT "leaping" +/// A trait gained from a mob's vanish action, like the herophant +#define VANISHING_TRAIT "vanishing" +/// A trait gained from a mob's swoop action, like the ash drake +#define SWOOPING_TRAIT "swooping" +/// A trait gained from a mob's mimic ability, like the mimic +#define MIMIC_TRAIT "mimic" +#define SHRUNKEN_TRAIT "shrunken" #define LEAPER_BUBBLE_TRAIT "leaper-bubble" #define DNA_VAULT_TRAIT "dna_vault" /// sticky nodrop sounds like a bad soundcloud rapper's name @@ -1037,7 +1076,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define AUTOPSY_TRAIT "autopsy_trait" /// Trait given by [/datum/status_effect/blessing_of_insanity] #define MAD_WIZARD_TRAIT "mad_wizard_trait" - +/// Isn't attacked harmfully by blob structures +#define TRAIT_BLOB_ALLY "blob_ally" /** * Trait granted by [/mob/living/carbon/Initialize] and @@ -1194,3 +1234,24 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Do IPC's dream of doomsday? The answer is yes #define TRAIT_ROBOT_CAN_BLEED "robots_can_bleed" //monkestation edit end +/// This atom can have spells cast from it if a mob is within it +/// This means the "caster" of the spell is changed to the mob's loc +/// Note this doesn't mean all spells are guaranteed to work or the mob is guaranteed to cast +#define TRAIT_CASTABLE_LOC "castable_loc" + +///Trait given by /datum/element/relay_attacker +#define TRAIT_RELAYING_ATTACKER "relaying_attacker" +// unique trait sources, still defines +#define EMP_TRAIT "emp_trait" + +/// Trait given while using /datum/action/cooldown/mob_cooldown/wing_buffet +#define TRAIT_WING_BUFFET "wing_buffet" +/// Trait given while tired after using /datum/action/cooldown/mob_cooldown/wing_buffet +#define TRAIT_WING_BUFFET_TIRED "wing_buffet_tired" +/// Trait given to a dragon who fails to defend their rifts +#define TRAIT_RIFT_FAILURE "fail_dragon_loser" + +///trait determines if this mob can breed given by /datum/component/breeding +#define TRAIT_MOB_BREEDER "mob_breeder" +/// Trait given to mobs that we do not want to mindswap +#define TRAIT_NO_MINDSWAP "no_mindswap" diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 32f5837c3d24..31e722178279 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -57,7 +57,7 @@ // VV HREF KEYS #define VV_HK_TARGET "target" -#define VV_HK_VARNAME "targetvar" //name or index of var for 1 variable targetting hrefs. +#define VV_HK_VARNAME "targetvar" //name or index of var for 1 variable targeting hrefs. // vv_do_list() keys #define VV_HK_LIST_ADD "listadd" diff --git a/code/__DEFINES/wounds.dm b/code/__DEFINES/wounds.dm index 0aadb71d58be..815e60d0738e 100644 --- a/code/__DEFINES/wounds.dm +++ b/code/__DEFINES/wounds.dm @@ -1,4 +1,3 @@ - // ~wound damage/rolling defines /// the cornerstone of the wound threshold system, your base wound roll for any attack is rand(1, damage^this), after armor reduces said damage. See [/obj/item/bodypart/proc/check_wounding] #define WOUND_DAMAGE_EXPONENT 1.4 @@ -13,6 +12,10 @@ /// set wound_bonus on an item or attack to this to disable checking wounding for the attack #define CANT_WOUND -100 +/// If there are multiple possible and valid wounds for the same type and severity, weight will be used to pick among them. See _wound_pregen_data.dm for more details +/// This is used in pick_weight, so use integers +#define WOUND_DEFAULT_WEIGHT 50 + // ~wound severities /// for jokey/meme wounds like stubbed toe, no standard messages/sounds or second winds #define WOUND_SEVERITY_TRIVIAL 0 @@ -22,16 +25,26 @@ /// outright dismemberment of limb #define WOUND_SEVERITY_LOSS 4 +/// A "chronological" list of wound severities, starting at the least severe. +GLOBAL_LIST_INIT(wound_severities_chronological, list( + "[WOUND_SEVERITY_TRIVIAL]", + "[WOUND_SEVERITY_MODERATE]", + "[WOUND_SEVERITY_SEVERE]", + "[WOUND_SEVERITY_CRITICAL]" +)) -// ~wound categories +// ~wound categories: wounding_types /// any brute weapon/attack that doesn't have sharpness. rolls for blunt bone wounds -#define WOUND_BLUNT 1 +#define WOUND_BLUNT "wound_blunt" /// any brute weapon/attack with sharpness = SHARP_EDGED. rolls for slash wounds -#define WOUND_SLASH 2 +#define WOUND_SLASH "wound_slash" /// any brute weapon/attack with sharpness = SHARP_POINTY. rolls for piercing wounds -#define WOUND_PIERCE 3 +#define WOUND_PIERCE "wound_pierce" /// any concentrated burn attack (lasers really). rolls for burning wounds -#define WOUND_BURN 4 +#define WOUND_BURN "wound_burn" + +/// Mainly a define used for wound_pregen_data, if a pregen data instance expects this, it will accept any and all wound types, even none at all +#define WOUND_ALL "wound_all" // ~determination second wind defines @@ -46,20 +59,226 @@ /// While someone has determination in their system, their bleed rate is slightly reduced #define WOUND_DETERMINATION_BLEED_MOD 0.85 -// ~wound global lists -// list in order of highest severity to lowest -GLOBAL_LIST_INIT(global_wound_types, list(WOUND_BLUNT = list(/datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/moderate), - WOUND_SLASH = list(/datum/wound/slash/critical, /datum/wound/slash/severe, /datum/wound/slash/moderate), - WOUND_PIERCE = list(/datum/wound/pierce/critical, /datum/wound/pierce/severe, /datum/wound/pierce/moderate), - WOUND_BURN = list(/datum/wound/burn/critical, /datum/wound/burn/severe, /datum/wound/burn/moderate) - )) +/// Wounds using this competition mode will remove any wounds of a greater severity than itself in a random wound roll. In most cases, you dont want to use this. +#define WOUND_COMPETITION_OVERPOWER_GREATERS "wound_submit" +/// Wounds using this competition mode will remove any wounds of a lower severity than itself in a random wound roll. Used for ensuring the worse case scenario of a given injury_roll. +#define WOUND_COMPETITION_OVERPOWER_LESSERS "wound_dominate" + +// ~biology defines +// What kind of biology a limb has, and what wounds it can suffer +/// Has absolutely fucking nothing, no wounds +#define BIO_INORGANIC NONE +/// Has bone - allows the victim to suffer T2-T3 bone blunt wounds +#define BIO_BONE (1<<0) +/// Has flesh - allows the victim to suffer fleshy slash pierce and burn wounds +#define BIO_FLESH (1<<1) +/// Has metal - allows the victim to suffer robotic blunt and burn wounds +#define BIO_METAL (1<<2) +/// Is wired internally - allows the victim to suffer electrical wounds (robotic T1-T3 slash/pierce) +#define BIO_WIRED (1<<3) +/// Has bloodflow - can suffer bleeding wounds and can bleed +#define BIO_BLOODED (1<<4) +/// Is connected by a joint - can suffer T1 bone blunt wounds (dislocation) +#define BIO_JOINTED (1<<5) +/// Robotic - can suffer all metal/wired wounds, such as: UNIMPLEMENTED PLEASE UPDATE ONCE SYNTH WOUNDS 9/5/2023 ~Niko +#define BIO_ROBOTIC (BIO_METAL|BIO_WIRED) +/// Has flesh and bone - See BIO_BONE and BIO_FLESH +#define BIO_FLESH_BONE (BIO_BONE|BIO_FLESH) +/// Standard humanoid - can bleed and suffer all flesh/bone wounds, such as: T1-3 slash/pierce/burn/blunt, except dislocations. Think human heads/chests +#define BIO_STANDARD_UNJOINTED (BIO_FLESH_BONE|BIO_BLOODED) +/// Standard humanoid limbs - can bleed and suffer all flesh/bone wounds, such as: T1-3 slash/pierce/burn/blunt. Can also bleed, and be dislocated. Think human arms and legs +#define BIO_STANDARD_JOINTED (BIO_STANDARD_UNJOINTED|BIO_JOINTED) + +// "Where" a specific biostate is within a given limb +// Interior is hard shit, the last line, shit like bones +// Exterior is soft shit, targeted by slashes and pierces (usually), protects exterior +// A limb needs both mangled interior and exterior to be dismembered, but slash/pierce must mangle exterior to attack the interior +// Not having exterior/interior counts as mangled exterior/interior for the purposes of dismemberment +/// The given biostate is on the "interior" of the limb - hard shit, protected by exterior +#define ANATOMY_INTERIOR (1<<0) +/// The given biostate is on the "exterior" of the limb - soft shit, protects interior +#define ANATOMY_EXTERIOR (1<<1) +#define ANATOMY_EXTERIOR_AND_INTERIOR (ANATOMY_EXTERIOR|ANATOMY_INTERIOR) + +/// A assoc list of BIO_ define to EXTERIOR/INTERIOR defines. +/// This is where the interior/exterior state of a given biostate is set. +/// Note that not all biostates are guaranteed to be one of these - and in fact, many are not +/// IMPORTANT NOTE: All keys are stored as text and must be converted via text2num +GLOBAL_LIST_INIT(bio_state_anatomy, list( + "[BIO_WIRED]" = ANATOMY_EXTERIOR, + "[BIO_METAL]" = ANATOMY_INTERIOR, + "[BIO_FLESH]" = ANATOMY_EXTERIOR, + "[BIO_BONE]" = ANATOMY_INTERIOR, +)) + +// Wound series +// A "wound series" is just a family of wounds that logically follow eachother +// Multiple wounds in a single series cannot be on a limb - the highest severity will always be prioritized, and lower ones will be skipped + +/// T1-T3 Bleeding slash wounds. Requires flesh. Can cause bleeding, but doesn't require it. From: slash.dm +#define WOUND_SERIES_FLESH_SLASH_BLEED "wound_series_flesh_slash_bled" +/// T1-T3 Basic blunt wounds. T1 requires jointed, but 2-3 require bone. From: bone.dm +#define WOUND_SERIES_BONE_BLUNT_BASIC "wound_series_bone_blunt_basic" +/// T1-T3 Basic burn wounds. Requires flesh. From: burns.dm +#define WOUND_SERIES_FLESH_BURN_BASIC "wound_series_flesh_burn_basic" +/// T1-T3 Bleeding puncture wounds. Requires flesh. Can cause bleeding, but doesn't require it. From: pierce.dm +#define WOUND_SERIES_FLESH_PUNCTURE_BLEED "wound_series_flesh_puncture_bleed" +/// Generic loss wounds. See loss.dm +#define WOUND_SERIES_LOSS_BASIC "wound_series_loss_basic" + +/// A assoc list of (wound typepath -> wound_pregen_data instance). Every wound should have a pregen data. +GLOBAL_LIST_INIT_TYPED(all_wound_pregen_data, /datum/wound_pregen_data, generate_wound_static_data()) + +/// Constructs [GLOB.all_wound_pregen_data] by iterating through a typecache of pregen data, ignoring abstract types, and instantiating the rest. +/proc/generate_wound_static_data() + RETURN_TYPE(/list/datum/wound_pregen_data) + + var/list/datum/wound_pregen_data/all_pregen_data = list() + + for (var/datum/wound_pregen_data/iterated_path as anything in typecacheof(path = /datum/wound_pregen_data, ignore_root_path = TRUE)) + if (initial(iterated_path.abstract)) + continue + + if (!isnull(all_pregen_data[initial(iterated_path.wound_path_to_generate)])) + stack_trace("pre-existing pregen data for [initial(iterated_path.wound_path_to_generate)] when [iterated_path] was being considered: [all_pregen_data[initial(iterated_path.wound_path_to_generate)]]. \ + this is definitely a bug, and is probably because one of the two pregen data have the wrong wound typepath defined. [iterated_path] will not be instantiated") + + continue + + var/datum/wound_pregen_data/pregen_data = new iterated_path + all_pregen_data[pregen_data.wound_path_to_generate] = pregen_data + + return all_pregen_data + +// A wound series "collection" is merely a way for us to track what is in what series, and what their types are. +// Without this, we have no centralized way to determine what type is in what series outside of iterating over every pregen data. + +/// A branching assoc list of (series -> list(severity -> list(typepath -> weight))). Allows you to say "I want a generic slash wound", +/// then "Of severity 2", and get a wound of that description - via get_corresponding_wound_type() +/// Series: A generic wound_series, such as WOUND_SERIES_BONE_BLUNT_BASIC +/// Severity: Any wounds held within this will be of this severity. +/// Typepath, Weight: Merely a pairing of a given typepath to its weight, held for convenience in pickweight. +GLOBAL_LIST_INIT(wound_series_collections, generate_wound_series_collection()) + +// Series -> severity -> type -> weight +/// Generates [wound_series_collections] by iterating through all pregen_data. Refer to the mentioned list for documentation +/proc/generate_wound_series_collection() + RETURN_TYPE(/list/datum/wound) + + var/list/datum/wound/wound_collection = list() -// every single type of wound that can be rolled naturally, in case you need to pull a random one -GLOBAL_LIST_INIT(global_all_wound_types, list(/datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/moderate, - /datum/wound/slash/critical, /datum/wound/slash/severe, /datum/wound/slash/moderate, - /datum/wound/pierce/critical, /datum/wound/pierce/severe, /datum/wound/pierce/moderate, - /datum/wound/burn/critical, /datum/wound/burn/severe, /datum/wound/burn/moderate)) + for (var/datum/wound/wound_typepath as anything in typecacheof(/datum/wound, FALSE, TRUE)) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound_typepath] + if (!pregen_data) + continue + if (pregen_data.abstract) + stack_trace("somehow, a abstract wound_pregen_data instance ([pregen_data.type]) was instantiated and made it to generate_wound_series_collection()! \ + i literally have no idea how! please fix this!") + continue + + var/series = pregen_data.wound_series + var/list/datum/wound/series_list = wound_collection[series] + if (isnull(series_list)) + wound_collection[series] = list() + series_list = wound_collection[series] + + var/severity = "[(initial(wound_typepath.severity))]" + var/list/datum/wound/severity_list = series_list[severity] + if (isnull(severity_list)) + series_list[severity] = list() + severity_list = series_list[severity] + + severity_list[wound_typepath] = pregen_data.weight + + return wound_collection + +/// A branching assoc list of (wounding_type -> list(wound_series)). +/// Allows for determining of which wound series are caused by what. +GLOBAL_LIST_INIT(wounding_types_to_series, list( + WOUND_BLUNT = list( + WOUND_SERIES_BONE_BLUNT_BASIC + ), + WOUND_SLASH = list( + WOUND_SERIES_FLESH_SLASH_BLEED, + ), + WOUND_BURN = list( + WOUND_SERIES_FLESH_BURN_BASIC, + ), + WOUND_PUNCTURE = list( + WOUND_SERIES_FLESH_PUNCTURE_BLEED + ), +)) + +/// Used in get_corresponding_wound_type(): Will pick the highest severity wound out of severity_min and severity_max +#define WOUND_PICK_HIGHEST_SEVERITY 1 +/// Used in get_corresponding_wound_type(): Will pick the lowest severity wound out of severity_min and severity_max +#define WOUND_PICK_LOWEST_SEVERITY 2 + +/** + * Searches through all wounds for any of proper type, series, and biostate, and then returns a single one via pickweight. + * Is able to discern between, say, a flesh slash wound, and a metallic slash wound, and will return the respective one for the provided limb. + * + * The severity_max and severity_pick_mode args mostly exist in case you want a wound in a series that may not have your ideal severity wound, as it lets you + * essentially set a "fallback", where if your ideal wound doesnt exist, it'll still return something, trying to get closest to your ideal severity. + * + * Generally speaking, if you want a critical/severe/moderate wound, you should set severity_min to WOUND_SEVERITY_MODERATE, severity_max to your ideal wound, + * and severity_pick_mode to WOUND_PICK_HIGHEST_SEVERITY - UNLESS you for some reason want the LOWEST severity, in which case you should set + * severity_max to the highest wound you're willing to tolerate, and severity_pick_mode to WOUND_PICK_LOWEST_SEVERITY. + * + * Args: + * * list/wounding_types: A list of wounding_types. Only wounds that accept these wound types will be considered. + * * obj/item/bodypart/part: The limb we are considering. Extremely important for biostates. + * * severity_min: The minimum wound severity we will search for. + * * severity_max = severity_min: The maximum wound severity we will search for. + * * severity_pick_mode = WOUND_PICK_HIGHEST_SEVERITY: The "pick mode" we will use when considering multiple wounds of acceptable severity. See the above defines. + * * random_roll = TRUE: If this is considered a "random" consideration. If true, only wounds that can be randomly generated will be considered. + * * duplicates_allowed = FALSE: If exact duplicates of a given wound on part are tolerated. Useful for simply getting a path and not instantiating. + * * care_about_existing_wounds = TRUE: If we iterate over wounds to see if any are above or at a given wounds severity, and disregard it if any are. Useful for simply getting a path and not instantiating. + * + * Returns: + * A randomly picked wound typepath meeting all the above criteria and being applicable to the part's biotype - or null if there were none. + */ +/proc/get_corresponding_wound_type(list/wounding_types, obj/item/bodypart/part, severity_min, severity_max = severity_min, severity_pick_mode = WOUND_PICK_HIGHEST_SEVERITY, random_roll = TRUE, duplicates_allowed = FALSE, care_about_existing_wounds = TRUE) + RETURN_TYPE(/datum/wound) // note that just because its set to return this doesnt mean its non-nullable + + var/list/wounding_type_list = list() + for (var/wounding_type as anything in wounding_types) + wounding_type_list += GLOB.wounding_types_to_series[wounding_type] + if (!length(wounding_type_list)) + return null + + var/list/datum/wound/paths_to_pick_from = list() + for (var/series as anything in shuffle(wounding_type_list)) + var/list/severity_list = GLOB.wound_series_collections[series] + if (!length(severity_list)) + continue + + var/picked_severity + for (var/severity_text as anything in shuffle(GLOB.wound_severities_chronological)) + var/severity = text2num(severity_text) + if (severity > severity_min || severity < severity_max) + continue + + if (isnull(picked_severity) || ((severity_pick_mode == WOUND_PICK_HIGHEST_SEVERITY && severity > picked_severity) || (severity_pick_mode == WOUND_PICK_LOWEST_SEVERITY && severity < picked_severity))) + picked_severity = severity + + var/list/wound_typepaths = severity_list["[picked_severity]"] + if (!length(wound_typepaths)) + continue + + for (var/datum/wound/iterated_path as anything in wound_typepaths) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[iterated_path] + if (pregen_data.can_be_applied_to(part, wounding_types, random_roll = random_roll, duplicates_allowed = duplicates_allowed, care_about_existing_wounds = care_about_existing_wounds)) + paths_to_pick_from[iterated_path] = wound_typepaths[iterated_path] + + return pick_weight(paths_to_pick_from) // we found our winners! + +/// Assoc list of biotype -> ideal scar file to be used and grab stuff from. +GLOBAL_LIST_INIT(biotypes_to_scar_file, list( + "[BIO_FLESH]" = FLESH_SCAR_FILE, + "[BIO_BONE]" = BONE_SCAR_FILE +)) // ~burn wound infection defines // Thresholds for infection for burn wounds, once infestation hits each threshold, things get steadily worse @@ -87,40 +306,25 @@ GLOBAL_LIST_INIT(global_all_wound_types, list(/datum/wound/blunt/critical, /datu // ~mangling defines -// With the wounds pt. 2 update, general dismemberment now requires 2 things for a limb to be dismemberable (bone only creatures just need the second): -// 1. Flesh is mangled: A critical slash or pierce wound on that limb -// 2. Bone is mangled: At least a severe bone wound on that limb -// see [/obj/item/bodypart/proc/get_mangled_state] for more information +// With the wounds pt. 2 update, general dismemberment now requires 2 things for a limb to be dismemberable (exterior/bone only creatures just need the second): +// 1. Exterior is mangled: A critical slash or pierce wound on that limb +// 2. Interior is mangled: At least a severe bone wound on that limb +// Lack of exterior or interior count as mangled exterior/interior respectively +// see [/obj/item/bodypart/proc/get_mangled_state] for more information, as well as GLOB.bio_state_anatomy #define BODYPART_MANGLED_NONE NONE -#define BODYPART_MANGLED_BONE (1<<0) -#define BODYPART_MANGLED_FLESH (1<<1) -#define BODYPART_MANGLED_BOTH (BODYPART_MANGLED_BONE | BODYPART_MANGLED_FLESH) - - -// ~biology defines -// What kind of biology a limb has, and what wounds it can suffer -/// golems and androids, cannot suffer any wounds -#define BIO_INORGANIC NONE -/// skeletons and plasmemes, can only suffer bone wounds, only needs mangled bone to be able to dismember -#define BIO_BONE (1<<0) -/// nothing right now, maybe slimepeople in the future, can only suffer slashing, piercing, and burn wounds -#define BIO_FLESH (1<<1) -/// standard humanoids, can suffer all wounds, needs mangled bone and flesh to dismember. conveniently, what you get when you combine BIO_BONE and BIO_FLESH -#define BIO_FLESH_BONE (BIO_BONE | BIO_FLESH) - +#define BODYPART_MANGLED_INTERIOR (1<<0) +#define BODYPART_MANGLED_EXTERIOR (1<<1) +#define BODYPART_MANGLED_BOTH (BODYPART_MANGLED_INTERIOR | BODYPART_MANGLED_EXTERIOR) // ~wound flag defines -/// If this wound requires having the BIO_FLESH biological_state on the limb -#define FLESH_WOUND (1<<0) -/// If this wound requires having the BIO_BONE biological_state on the limb -#define BONE_WOUND (1<<1) -/// If having this wound counts as mangled flesh for dismemberment -#define MANGLES_FLESH (1<<2) -/// If having this wound counts as mangled bone for dismemberment -#define MANGLES_BONE (1<<3) +/// If having this wound counts as mangled exterior for dismemberment +#define MANGLES_EXTERIOR (1<<0) +/// If having this wound counts as mangled interior for dismemberment +#define MANGLES_INTERIOR (1<<1) /// If this wound marks the limb as being allowed to have gauze applied -#define ACCEPTS_GAUZE (1<<4) - +#define ACCEPTS_GAUZE (1<<2) +/// If this wound allows the victim to grasp it +#define CAN_BE_GRASPED (1<<3) // ~scar persistence defines // The following are the order placements for persistent scar save formats @@ -138,11 +342,13 @@ GLOBAL_LIST_INIT(global_all_wound_types, list(/datum/wound/blunt/critical, /datu #define SCAR_SAVE_BIOLOGY 6 /// Which character slot this was saved to #define SCAR_SAVE_CHAR_SLOT 7 +/// if the scar will check for any or all biostates on the limb (defaults to FALSE, so all) +#define SCAR_SAVE_CHECK_ANY_BIO 8 ///how many fields we save for each scar (so the number of above fields) -#define SCAR_SAVE_LENGTH 7 +#define SCAR_SAVE_LENGTH 8 /// saved scars with a version lower than this will be discarded, increment when you update the persistent scarring format in a way that invalidates previous saved scars (new fields, reordering, etc) -#define SCAR_CURRENT_VERSION 3 +#define SCAR_CURRENT_VERSION 4 /// how many scar slots, per character slot, we have to cycle through for persistent scarring, if enabled in character prefs #define PERSISTENT_SCAR_SLOTS 3 diff --git a/code/__DEFINES/~monkestation/antagonists.dm b/code/__DEFINES/~monkestation/antagonists.dm index a54245a1447d..8ec0def3b22b 100644 --- a/code/__DEFINES/~monkestation/antagonists.dm +++ b/code/__DEFINES/~monkestation/antagonists.dm @@ -14,6 +14,6 @@ /// maximum amount of cogscarabs the clock cult can have #define MAXIMUM_COGSCARABS 9 /// is something a cogscarab -#define iscogscarab(checked) (istype(checked, /mob/living/simple_animal/drone/cogscarab)) +#define iscogscarab(checked) (istype(checked, /mob/living/basic/drone/cogscarab)) /// is something an eminence #define iseminence(checked) (istype(checked, /mob/living/eminence)) diff --git a/code/__HELPERS/bodyparts.dm b/code/__HELPERS/bodyparts.dm index 39df8e37e5ff..6a76ef50870d 100644 --- a/code/__HELPERS/bodyparts.dm +++ b/code/__HELPERS/bodyparts.dm @@ -1 +1,3 @@ #define IS_ORGANIC_LIMB(limb) (limb.bodytype & BODYTYPE_ORGANIC) +/// Helper to figure out if a limb is robotic +#define IS_ROBOTIC_LIMB(limb) (limb.bodytype & BODYTYPE_ROBOTIC) diff --git a/code/__HELPERS/chat.dm b/code/__HELPERS/chat.dm index fdf997a0bc54..e4538c9731ec 100644 --- a/code/__HELPERS/chat.dm +++ b/code/__HELPERS/chat.dm @@ -76,3 +76,19 @@ In TGS3 it will always be sent to all connected designated game chats. /// Handles text formatting for item use hints in examine text #define EXAMINE_HINT(text) ("" + text + "") + +/// Sends a message to all dead and observing players, if a source is provided a follow link will be attached. +/proc/send_to_observers(message, source) + var/list/all_observers = GLOB.dead_player_list + GLOB.current_observers_list + for(var/mob/observer as anything in all_observers) + if (isnull(source)) + to_chat(observer, "[message]") + continue + var/link = FOLLOW_LINK(observer, source) + to_chat(observer, "[link] [message]") + +/// Sends a message to everyone within the list, as well as all observers. +/proc/relay_to_list_and_observers(message, list/mob_list, source) + for(var/mob/creature as anything in mob_list) + to_chat(creature, message) + send_to_observers(message, source) diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm index e7af8e0dcd7c..0eb8457a46cd 100644 --- a/code/__HELPERS/cmp.dm +++ b/code/__HELPERS/cmp.dm @@ -170,3 +170,11 @@ /// Orders heretic knowledge by priority /proc/cmp_heretic_knowledge(datum/heretic_knowledge/knowledge_a, datum/heretic_knowledge/knowledge_b) return initial(knowledge_b.priority) - initial(knowledge_a.priority) + +/// Passed a list of assoc lists, sorts them by the list's "name" keys. +/proc/cmp_assoc_list_name(list/A, list/B) + return sorttext(B["name"], A["name"]) + +/// Orders mobs by health +/proc/cmp_mob_health(mob/living/mob_a, mob/living/mob_b) + return mob_b.health - mob_a.health diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index 3cebfa315c98..adabbeee3ce4 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -1478,3 +1478,13 @@ GLOBAL_LIST_EMPTY(transformation_animation_objects) if(scream) stack_trace("Icon Lookup for state: [state] in file [file] failed.") return FALSE + +/// Cache of the width and height of icon files, to avoid repeating the same expensive operation +GLOBAL_LIST_EMPTY(icon_dimensions) + +/// Returns a list containing the width and height of an icon file +/proc/get_icon_dimensions(icon_path) + if (isnull(GLOB.icon_dimensions[icon_path])) + var/icon/my_icon = icon(icon_path) + GLOB.icon_dimensions[icon_path] = list("width" = my_icon.Width(), "height" = my_icon.Height()) + return GLOB.icon_dimensions[icon_path] diff --git a/code/__HELPERS/pronouns.dm b/code/__HELPERS/pronouns.dm index 6328591471a5..1265a8aed204 100644 --- a/code/__HELPERS/pronouns.dm +++ b/code/__HELPERS/pronouns.dm @@ -15,6 +15,9 @@ if(capitalized) . = capitalize(.) +/datum/proc/p_Their(temp_gender) + return capitalize(p_their(temp_gender)) + /datum/proc/p_them(capitalized, temp_gender) . = "it" if(capitalized) @@ -38,6 +41,9 @@ /datum/proc/p_theyre(capitalized, temp_gender) . = p_they(capitalized, temp_gender) + "'" + copytext_char(p_are(temp_gender), 2) +/datum/proc/p_Theyre(temp_gender) + return p_They(temp_gender) + "'" + copytext_char(p_are(temp_gender), 2) + /datum/proc/p_s(temp_gender) //is this a descriptive proc name, or what? . = "s" diff --git a/code/__HELPERS/~monkestation-helpers/icon_smoothing.dm b/code/__HELPERS/~monkestation-helpers/icon_smoothing.dm index 27cfac8570a1..3e2668c6d177 100644 --- a/code/__HELPERS/~monkestation-helpers/icon_smoothing.dm +++ b/code/__HELPERS/~monkestation-helpers/icon_smoothing.dm @@ -50,3 +50,26 @@ if(length(overlays_adapters)) add_overlay(overlays_adapters) + +GLOBAL_LIST_EMPTY(string_numbers_lists) + +/** + * Caches lists of numeric values. + */ +/datum/proc/string_numbers_list(list/values) + //Just to to be extra-safe. If you try to shove in text or paths, you deserve the runtime errors. + var/list/sum = 0 + for(var/number in values) + sum += number + + var/string_id = values.Join("-") + + . = GLOB.string_numbers_lists[string_id] + + if(.) + return . + + return GLOB.string_numbers_lists[string_id] = values + +/datum/proc/p_They(temp_gender) + return capitalize(p_they(temp_gender)) diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 6e5ac2fef5b5..26755614e46f 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -12,6 +12,36 @@ GLOBAL_LIST_INIT(dangerous_turfs, typecacheof(list( /turf/open/space, /turf/open/openspace))) +/// List of types of abstract mob which shouldn't usually exist in the world on its own if we're spawning random mobs +GLOBAL_LIST_INIT(abstract_mob_types, list( + /mob/living/basic/blob_minion, + /mob/living/basic/construct, + /mob/living/basic/guardian, + /mob/living/basic/heretic_summon, + /mob/living/basic/mining, + /mob/living/basic/pet, + /mob/living/basic, + /mob/living/basic/spider, + /mob/living/carbon/alien/adult, + /mob/living/carbon/alien, + /mob/living/carbon/human/consistent, + /mob/living/carbon/human/dummy/consistent, + /mob/living/carbon/human/dummy, + /mob/living/carbon/human/species, + /mob/living/carbon, + /mob/living/silicon, + /mob/living/simple_animal/bot, + /mob/living/simple_animal/hostile/asteroid/elite, + /mob/living/simple_animal/hostile/asteroid, + /mob/living/simple_animal/hostile/megafauna, + /mob/living/simple_animal/hostile/mimic, // Cannot exist if spawned without being passed an item reference + /mob/living/simple_animal/hostile/retaliate, + /mob/living/simple_animal/hostile, + /mob/living/simple_animal/pet, + /mob/living/simple_animal/soulscythe, // As mimic, can't exist if spawned outside an item + /mob/living/simple_animal, +)) + //Since it didn't really belong in any other category, I'm putting this here //This is for procs to replace all the goddamn 'in world's that are chilling around the code @@ -53,6 +83,12 @@ GLOBAL_LIST_EMPTY(current_living_antags) /// All observers with clients that joined as observers. GLOBAL_LIST_EMPTY(current_observers_list) +/// All living mobs which can hear blob telepathy +GLOBAL_LIST_EMPTY(blob_telepathy_mobs) + +/// All "living" (because revenants are in between mortal planes or whatever) mobs that can hear revenants +GLOBAL_LIST_EMPTY(revenant_relay_mobs) + ///underages who have been reported to security for trying to buy things they shouldn't, so they can't spam GLOBAL_LIST_EMPTY(narcd_underages) diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm index ded99d194f3a..d0091684e2d4 100644 --- a/code/_globalvars/lists/names.dm +++ b/code/_globalvars/lists/names.dm @@ -29,6 +29,10 @@ GLOBAL_LIST_INIT(simian_last_names, world.file2list("monkestation/strings/names/ GLOBAL_LIST_INIT(simian_names_female, world.file2list("monkestation/strings/names/simian_female_first.txt")) GLOBAL_LIST_INIT(simian_names_male, world.file2list("monkestation/strings/names/simian_male_first.txt")) GLOBAL_LIST_INIT(cyberauth_names, world.file2list("strings/names/cyberauth.txt")) +GLOBAL_LIST_INIT(syndicate_monkey_names, world.file2list("strings/names/syndicate_monkey.txt")) +GLOBAL_LIST_INIT(guardian_first_names, world.file2list("strings/names/guardian_descriptions.txt")) +GLOBAL_LIST_INIT(guardian_tech_surnames, world.file2list("strings/names/guardian_gamepieces.txt")) +GLOBAL_LIST_INIT(guardian_fantasy_surnames, world.file2list("strings/names/guardian_tarot.txt")) GLOBAL_LIST_INIT(verbs, world.file2list("strings/names/verbs.txt")) GLOBAL_LIST_INIT(ing_verbs, world.file2list("strings/names/ing_verbs.txt")) diff --git a/code/_globalvars/lists/poll_ignore.dm b/code/_globalvars/lists/poll_ignore.dm index 6044f54587f0..c3fbb720650f 100644 --- a/code/_globalvars/lists/poll_ignore.dm +++ b/code/_globalvars/lists/poll_ignore.dm @@ -1,36 +1,69 @@ //Each lists stores ckeys for "Never for this round" option category -#define POLL_IGNORE_SENTIENCE_POTION "sentience_potion" -#define POLL_IGNORE_POSSESSED_BLADE "possessed_blade" +#define POLL_IGNORE_ACADEMY_WIZARD "academy_wizard" #define POLL_IGNORE_ALIEN_LARVA "alien_larva" -#define POLL_IGNORE_SYNDICATE "syndicate" -#define POLL_IGNORE_HOLOPARASITE "holoparasite" -#define POLL_IGNORE_POSIBRAIN "posibrain" -#define POLL_IGNORE_SPECTRAL_BLADE "spectral_blade" -#define POLL_IGNORE_CONSTRUCT "construct" -#define POLL_IGNORE_SPIDER "spider" +#define POLL_IGNORE_ASH_SPIRIT "ash_spirit" #define POLL_IGNORE_ASHWALKER "ashwalker" -#define POLL_IGNORE_GOLEM "golem" +#define POLL_IGNORE_BLOB "blob" +#define POLL_IGNORE_BOTS "bots" +#define POLL_IGNORE_CARGORILLA "cargorilla" +#define POLL_IGNORE_CONTRACTOR_SUPPORT "contractor_support" +#define POLL_IGNORE_CONSTRUCT "construct" #define POLL_IGNORE_DRONE "drone" +#define POLL_IGNORE_FIRE_SHARK "fire_shark" #define POLL_IGNORE_FUGITIVE "fugitive" -#define POLL_IGNORE_DEFECTIVECLONE "defective_clone" -#define POLL_IGNORE_PYROSLIME "slime" -#define POLL_IGNORE_SHADE "shade" +#define POLL_IGNORE_GOLEM "golem" +#define POLL_IGNORE_HERETIC_MONSTER "heretic_monster" +#define POLL_IGNORE_HOLOPARASITE "holoparasite" #define POLL_IGNORE_IMAGINARYFRIEND "imaginary_friend" -#define POLL_IGNORE_SPLITPERSONALITY "split_personality" -#define POLL_IGNORE_CONTRACTOR_SUPPORT "contractor_support" -#define POLL_IGNORE_ACADEMY_WIZARD "academy_wizard" +#define POLL_IGNORE_LAVALAND_ELITE "lavaland_elite" +#define POLL_IGNORE_MAID_IN_MIRROR "maid_in_mirror" +#define POLL_IGNORE_MONKEY_HELMET "mind_magnified_monkey" #define POLL_IGNORE_PAI "pai" -#define POLL_IGNORE_VENUSHUMANTRAP "venus_human_trap" +#define POLL_IGNORE_POSIBRAIN "posibrain" +#define POLL_IGNORE_POSSESSED_BLADE "possessed_blade" +#define POLL_IGNORE_PYROSLIME "slime" +#define POLL_IGNORE_RAW_PROPHET "raw_prophet" #define POLL_IGNORE_REGAL_RAT "regal_rat" -#define POLL_IGNORE_CARGORILLA "cargorilla" -#define POLL_IGNORE_MONKEY_HELMET "mind_magnified_monkey" -#define POLL_IGNORE_LAVALAND_ELITE "lavaland_elite" -#define POLL_IGNORE_SHUTTLE_DENIZENS "shuttle_denizens" -#define POLL_IGNORE_BOTS "bots" +#define POLL_IGNORE_RUST_SPIRIT "rust_spirit" +#define POLL_IGNORE_SENTIENCE_POTION "sentience_potion" +#define POLL_IGNORE_SPIDER "spider" +#define POLL_IGNORE_SHADE "shade" +#define POLL_IGNORE_SYNDICATE "syndicate" +#define POLL_IGNORE_SPLITPERSONALITY "splitpersonality" +#define POLL_IGNORE_VENUSHUMANTRAP "venus" +#define POLL_IGNORE_SPECTRAL_BLADE "spectralblade" +#define POLL_IGNORE_SHUTTLE_DENIZENS "shuttledenizen" +#define POLL_IGNORE_DEFECTIVECLONE "defectiveclone" GLOBAL_LIST_INIT(poll_ignore_desc, list( + POLL_IGNORE_ACADEMY_WIZARD = "Academy Wizard Defender", + POLL_IGNORE_ALIEN_LARVA = "Xenomorph larva", + POLL_IGNORE_ASH_SPIRIT = "Ash Spirit", + POLL_IGNORE_ASHWALKER = "Ashwalker eggs", + POLL_IGNORE_BLOB = "Blob spores", + POLL_IGNORE_BOTS = "Bots", + POLL_IGNORE_CARGORILLA = "Cargorilla", + POLL_IGNORE_CONTRACTOR_SUPPORT = "Contractor Support Unit", + POLL_IGNORE_CONSTRUCT = "Construct", + POLL_IGNORE_DRONE = "Drone shells", + POLL_IGNORE_FIRE_SHARK = "Fire Shark", + POLL_IGNORE_FUGITIVE = "Fugitive Hunter", + POLL_IGNORE_GOLEM = "Golems", + POLL_IGNORE_HERETIC_MONSTER = "Heretic Monster", + POLL_IGNORE_HOLOPARASITE = "Holoparasite", + POLL_IGNORE_IMAGINARYFRIEND = "Imaginary Friend", + POLL_IGNORE_LAVALAND_ELITE = "Lavaland elite", + POLL_IGNORE_MAID_IN_MIRROR = "Maid in the Mirror", + POLL_IGNORE_MONKEY_HELMET = "Mind magnified monkey", + POLL_IGNORE_PAI = JOB_PERSONAL_AI, + POLL_IGNORE_POSIBRAIN = "Positronic brain", + POLL_IGNORE_POSSESSED_BLADE = "Possessed blade", + POLL_IGNORE_PYROSLIME = "Slime", + POLL_IGNORE_RAW_PROPHET = "Raw Prophet", + POLL_IGNORE_RUST_SPIRIT = "Rust Spirit", + POLL_IGNORE_REGAL_RAT = "Regal rat", POLL_IGNORE_SENTIENCE_POTION = "Sentience potion", POLL_IGNORE_POSSESSED_BLADE = "Possessed blade", POLL_IGNORE_ALIEN_LARVA = "Xenomorph larva", @@ -44,7 +77,7 @@ GLOBAL_LIST_INIT(poll_ignore_desc, list( POLL_IGNORE_GOLEM = "Golems", POLL_IGNORE_DRONE = "Drone shells", POLL_IGNORE_FUGITIVE = "Fugitive Hunter", - POLL_IGNORE_DEFECTIVECLONE = "Defective clone", + POLL_IGNORE_DEFECTIVECLONE = "Defective clone", POLL_IGNORE_PYROSLIME = "Slime", POLL_IGNORE_SHADE = "Shade", POLL_IGNORE_IMAGINARYFRIEND = "Imaginary Friend", diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm index 9671ecaa57e9..9819b3499a42 100644 --- a/code/_globalvars/phobias.dm +++ b/code/_globalvars/phobias.dm @@ -51,33 +51,57 @@ GLOBAL_LIST_INIT(phobia_regexes, list( )) GLOBAL_LIST_INIT(phobia_mobs, list( - "spiders" = typecacheof(list(/mob/living/basic/spider/giant)), - "security" = typecacheof(list(/mob/living/simple_animal/bot/secbot)), + "aliens" = typecacheof(list( + /mob/living/carbon/alien, + /mob/living/simple_animal/slime, + )), + "anime" = typecacheof(list(/mob/living/basic/guardian)), + "birds" = typecacheof(list( + /mob/living/basic/chick, + /mob/living/basic/chicken, + /mob/living/basic/parrot, + /mob/living/basic/pet/penguin, + )), + "conspiracies" = typecacheof(list( + /mob/living/basic/drone, + /mob/living/basic/pet/penguin, + /mob/living/simple_animal/bot/secbot, + )), + "doctors" = typecacheof(list(/mob/living/simple_animal/bot/medbot)), + "heresy" = typecacheof(list( + /mob/living/basic/heretic_summon, + )), + "insects" = typecacheof(list( + /mob/living/basic/cockroach, + /mob/living/basic/bee, + )), "lizards" = typecacheof(list(/mob/living/basic/lizard)), - "skeletons" = typecacheof(list(/mob/living/simple_animal/hostile/skeleton)), - "snakes" = typecacheof(list(/mob/living/simple_animal/hostile/retaliate/snake)), + "skeletons" = typecacheof(list(/mob/living/basic/skeleton)), "robots" = typecacheof(list( + /mob/living/basic/drone, /mob/living/silicon/ai, /mob/living/silicon/robot, /mob/living/simple_animal/bot, - /mob/living/simple_animal/drone, )), - "doctors" = typecacheof(list(/mob/living/simple_animal/bot/medbot)), + "security" = typecacheof(list(/mob/living/simple_animal/bot/secbot)), + "spiders" = typecacheof(list(/mob/living/basic/spider/giant)), + "skeletons" = typecacheof(list(/mob/living/basic/skeleton)), + "snakes" = typecacheof(list(/mob/living/basic/snake)), "the supernatural" = typecacheof(list( /mob/dead/observer, /mob/living/basic/bat, + /mob/living/basic/construct, /mob/living/basic/demon, /mob/living/basic/faithless, /mob/living/basic/ghost, + /mob/living/basic/heretic_summon, + /mob/living/basic/revenant, + /mob/living/basic/shade, + /mob/living/basic/skeleton, + /mob/living/basic/wizard, /mob/living/simple_animal/bot/mulebot/paranormal, - /mob/living/simple_animal/hostile/construct, /mob/living/simple_animal/hostile/dark_wizard, - /mob/living/simple_animal/hostile/heretic_summon, - /mob/living/simple_animal/hostile/skeleton, - /mob/living/simple_animal/hostile/wizard, /mob/living/simple_animal/hostile/zombie, - /mob/living/simple_animal/revenant, - /mob/living/simple_animal/shade, )), "aliens" = typecacheof(list( /mob/living/carbon/alien, @@ -85,16 +109,16 @@ GLOBAL_LIST_INIT(phobia_mobs, list( )), "conspiracies" = typecacheof(list( /mob/living/simple_animal/bot/secbot, - /mob/living/simple_animal/drone, + /mob/living/basic/drone, /mob/living/basic/pet/penguin, )), "birds" = typecacheof(list( /mob/living/basic/chick, /mob/living/basic/chicken, - /mob/living/simple_animal/parrot, + /mob/living/basic/parrot, /mob/living/basic/pet/penguin, )), - "anime" = typecacheof(list(/mob/living/simple_animal/hostile/guardian)), + "anime" = typecacheof(list(/mob/living/basic/guardian)), "insects" = typecacheof(list( /mob/living/basic/cockroach, /mob/living/basic/bee, diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index 55326d5633ef..063d7f7cbf2a 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -80,6 +80,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_BLOOD_DEFICIENCY" = TRAIT_BLOOD_DEFICIENCY, "TRAIT_JOLLY" = TRAIT_JOLLY, "TRAIT_NO_GLIDE" = TRAIT_NO_GLIDE, + "TRAIT_NO_FLOATING_ANIM" = TRAIT_NO_FLOATING_ANIM, "TRAIT_NOCRITDAMAGE" = TRAIT_NOCRITDAMAGE, "TRAIT_NO_SLIP_WATER" = TRAIT_NO_SLIP_WATER, "TRAIT_NO_SLIP_ICE" = TRAIT_NO_SLIP_ICE, @@ -96,6 +97,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_PARALYSIS_R_ARM" = TRAIT_PARALYSIS_R_ARM, "TRAIT_PARALYSIS_L_LEG" = TRAIT_PARALYSIS_L_LEG, "TRAIT_PARALYSIS_R_LEG" = TRAIT_PARALYSIS_R_LEG, + "TRAIT_PARROT_PERCHED" = TRAIT_PARROT_PERCHED, "TRAIT_CANNOT_OPEN_PRESENTS" = TRAIT_CANNOT_OPEN_PRESENTS, "TRAIT_PRESENT_VISION" = TRAIT_PRESENT_VISION, "TRAIT_DISK_VERIFIER" = TRAIT_DISK_VERIFIER, @@ -194,6 +196,8 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_UNOBSERVANT" = TRAIT_UNOBSERVANT, "TRAIT_TENTACLE_IMMUNE" = TRAIT_TENTACLE_IMMUNE, "TRAIT_OVERWATCH_IMMUNE" = TRAIT_OVERWATCH_IMMUNE, + "TRAIT_UNDENSE" = TRAIT_UNDENSE, + "TRAIT_EXPANDED_FOV" = TRAIT_EXPANDED_FOV, ), /obj/item/bodypart = list( "TRAIT_PARALYSIS" = TRAIT_PARALYSIS, diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 31168fb1657e..364a548a131d 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -69,7 +69,7 @@ return next_click = world.time + 1 - if(check_click_intercept(params,A) || notransform) + if(check_click_intercept(params,A) || HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return var/list/modifiers = params2list(params) diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 2537133a5d86..a05d11c73d69 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -510,7 +510,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." alerttooltipstyle = "cult" var/static/image/narnar var/angle = 0 - var/mob/living/simple_animal/hostile/construct/Cviewer = null + var/mob/living/basic/construct/Cviewer /atom/movable/screen/alert/bloodsense/Initialize(mapload) . = ..() @@ -615,19 +615,13 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." //GUARDIANS -/atom/movable/screen/alert/cancharge - name = "Charge Ready" - desc = "You are ready to charge at a location!" - icon_state = "guardian_charge" - alerttooltipstyle = "parasite" - /atom/movable/screen/alert/canstealth name = "Stealth Ready" desc = "You are ready to enter stealth!" icon_state = "guardian_canstealth" alerttooltipstyle = "parasite" -/atom/movable/screen/alert/instealth +/atom/movable/screen/alert/status_effect/instealth name = "In Stealth" desc = "You are in stealth and your next attack will do bonus damage!" icon_state = "guardian_instealth" diff --git a/code/_onclick/hud/drones.dm b/code/_onclick/hud/drones.dm index 61c006ac6fe5..4ae7b3070d97 100644 --- a/code/_onclick/hud/drones.dm +++ b/code/_onclick/hud/drones.dm @@ -32,19 +32,17 @@ /datum/hud/dextrous/drone/persistent_inventory_update() if(!mymob) return - var/mob/living/simple_animal/drone/D = mymob + var/mob/living/basic/drone/drone = mymob if(hud_shown) - if(D.internal_storage) - D.internal_storage.screen_loc = ui_drone_storage - D.client.screen += D.internal_storage - if(D.head) - D.head.screen_loc = ui_drone_head - D.client.screen += D.head + if(!isnull(drone.internal_storage)) + drone.internal_storage.screen_loc = ui_drone_storage + drone.client.screen += drone.internal_storage + if(!isnull(drone.head)) + drone.head.screen_loc = ui_drone_head + drone.client.screen += drone.head else - if(D.internal_storage) - D.internal_storage.screen_loc = null - if(D.head) - D.head.screen_loc = null + drone.internal_storage?.screen_loc = null + drone.head?.screen_loc = null ..() diff --git a/code/_onclick/hud/generic_dextrous.dm b/code/_onclick/hud/generic_dextrous.dm index 9cdaea2356aa..7bb20ba10b79 100644 --- a/code/_onclick/hud/generic_dextrous.dm +++ b/code/_onclick/hud/generic_dextrous.dm @@ -46,7 +46,7 @@ using.hud = src static_inventory += using - mymob.client.clear_screen() + mymob.canon_client?.clear_screen() for(var/atom/movable/screen/inventory/inv in (static_inventory + toggleable_inventory)) if(inv.slot_id) @@ -66,10 +66,3 @@ for(var/obj/item/I in D.held_items) I.screen_loc = null D.client.screen -= I - - -//Dextrous simple mobs can use hands! -/mob/living/simple_animal/create_mob_hud() - if(dextrous) - hud_type = dextrous_hud_type - return ..() diff --git a/code/_onclick/hud/guardian.dm b/code/_onclick/hud/guardian.dm index 91da0f9fdb85..cbf274cbeb05 100644 --- a/code/_onclick/hud/guardian.dm +++ b/code/_onclick/hud/guardian.dm @@ -1,7 +1,7 @@ /datum/hud/guardian ui_style = 'icons/hud/guardian.dmi' -/datum/hud/guardian/New(mob/living/simple_animal/hostile/guardian/owner) +/datum/hud/guardian/New(mob/living/basic/guardian/owner) ..() var/atom/movable/screen/using @@ -41,10 +41,10 @@ using.hud = src static_inventory += using -/datum/hud/dextrous/guardian/New(mob/living/simple_animal/hostile/guardian/owner) //for a dextrous guardian +/datum/hud/dextrous/guardian/New(mob/living/basic/guardian/owner) //for a dextrous guardian ..() var/atom/movable/screen/using - if(istype(owner, /mob/living/simple_animal/hostile/guardian/dextrous)) + if(istype(owner, /mob/living/basic/guardian/dextrous)) var/atom/movable/screen/inventory/inv_box inv_box = new /atom/movable/screen/inventory() @@ -102,8 +102,8 @@ /datum/hud/dextrous/guardian/persistent_inventory_update() if(!mymob) return - if(istype(mymob, /mob/living/simple_animal/hostile/guardian/dextrous)) - var/mob/living/simple_animal/hostile/guardian/dextrous/dex_guardian = mymob + if(istype(mymob, /mob/living/basic/guardian/dextrous)) + var/mob/living/basic/guardian/dextrous/dex_guardian = mymob if(hud_shown) if(dex_guardian.internal_storage) @@ -125,7 +125,7 @@ /atom/movable/screen/guardian/manifest/Click() if(isguardian(usr)) - var/mob/living/simple_animal/hostile/guardian/user = usr + var/mob/living/basic/guardian/user = usr user.manifest() @@ -136,7 +136,7 @@ /atom/movable/screen/guardian/recall/Click() if(isguardian(usr)) - var/mob/living/simple_animal/hostile/guardian/user = usr + var/mob/living/basic/guardian/user = usr user.recall() /atom/movable/screen/guardian/toggle_mode @@ -146,7 +146,7 @@ /atom/movable/screen/guardian/toggle_mode/Click() if(isguardian(usr)) - var/mob/living/simple_animal/hostile/guardian/user = usr + var/mob/living/basic/guardian/user = usr user.toggle_modes() /atom/movable/screen/guardian/toggle_mode/inactive @@ -169,7 +169,7 @@ /atom/movable/screen/guardian/communicate/Click() if(isguardian(usr)) - var/mob/living/simple_animal/hostile/guardian/user = usr + var/mob/living/basic/guardian/user = usr user.communicate() @@ -180,5 +180,5 @@ /atom/movable/screen/guardian/toggle_light/Click() if(isguardian(usr)) - var/mob/living/simple_animal/hostile/guardian/user = usr + var/mob/living/basic/guardian/user = usr user.toggle_light() diff --git a/code/_onclick/hud/ooze.dm b/code/_onclick/hud/ooze.dm index d3ebe3e72885..c13123bb43bb 100644 --- a/code/_onclick/hud/ooze.dm +++ b/code/_onclick/hud/ooze.dm @@ -1,4 +1,4 @@ -///Hud type with targetting dol and a nutrition bar +///Hud type with targeting dol and a nutrition bar /datum/hud/ooze/New(mob/living/owner) . = ..() diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm index 75e83a00b593..44bd2873ccdb 100644 --- a/code/_onclick/hud/radial.dm +++ b/code/_onclick/hud/radial.dm @@ -345,9 +345,13 @@ GLOBAL_LIST_EMPTY(radial_menus) Choices should be a list where list keys are movables or text used for element names and return value and list values are movables/icons/images used for element icons */ -/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE, radial_slice_icon = "radial_slice") +/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE, radial_slice_icon = "radial_slice", autopick_single_option = TRUE) if(!user || !anchor || !length(choices)) return + + if(length(choices)==1 && autopick_single_option) + return choices[1] + if(!uniqueid) uniqueid = "defmenu_[REF(user)]_[REF(anchor)]" diff --git a/code/_onclick/hud/screentip.dm b/code/_onclick/hud/screentip.dm index 8f3b9d63014d..50162e6e3a8e 100644 --- a/code/_onclick/hud/screentip.dm +++ b/code/_onclick/hud/screentip.dm @@ -15,7 +15,7 @@ /atom/movable/screen/screentip/proc/update_view(datum/source) SIGNAL_HANDLER - if(!hud || !hud.mymob.client.view_size) //Might not have been initialized by now + if(!hud || !hud.mymob.canon_client?.view_size) //Might not have been initialized by now return maptext_width = view_to_pixels(hud.mymob.client.view_size.getView())[1] diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index 8c12b83daac1..62fa07070976 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -156,6 +156,17 @@ return ..() || ((obj_flags & CAN_BE_HIT) && attacking_item.attack_atom(src, user, params)) /mob/living/attackby(obj/item/attacking_item, mob/living/user, params) + for(var/datum/surgery/operations as anything in surgeries) + if(user.istate & ISTATE_HARM) + break + if(IS_IN_INVALID_SURGICAL_POSITION(src, operations)) + continue + if(!(operations.surgery_flags & SURGERY_SELF_OPERABLE) && (user == src)) + continue + var/list/modifiers = params2list(params) + if(operations.next_step(user, modifiers)) + return TRUE + if(..()) return TRUE user.changeNext_move(attacking_item.attack_speed) diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index aa08d7889fa8..c7ddad9056d3 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -245,14 +245,14 @@ Drones */ -/mob/living/simple_animal/drone/resolve_unarmed_attack(atom/attack_target, proximity_flag, list/modifiers) +/mob/living/basic/drone/resolve_unarmed_attack(atom/attack_target, proximity_flag, list/modifiers) attack_target.attack_drone(src, modifiers) -/mob/living/simple_animal/drone/resolve_right_click_attack(atom/target, list/modifiers) +/mob/living/basic/drone/resolve_right_click_attack(atom/target, list/modifiers) return target.attack_drone_secondary(src, modifiers) /// Defaults to attack_hand. Override it when you don't want drones to do same stuff as humans. -/atom/proc/attack_drone(mob/living/simple_animal/drone/user, list/modifiers) +/atom/proc/attack_drone(mob/living/basic/drone/user, list/modifiers) attack_hand(user, modifiers) /** @@ -260,7 +260,7 @@ * Defaults to attack_hand_secondary. * When overriding it, remember that it ought to return a SECONDARY_ATTACK_* value. */ -/atom/proc/attack_drone_secondary(mob/living/simple_animal/drone/user, list/modifiers) +/atom/proc/attack_drone_secondary(mob/living/basic/drone/user, list/modifiers) return attack_hand_secondary(user, modifiers) /* @@ -296,14 +296,14 @@ */ /mob/living/simple_animal/resolve_unarmed_attack(atom/attack_target, list/modifiers) - if(dextrous && (isitem(attack_target) || !(istate & ISTATE_HARM))) + if((isitem(attack_target) || !(istate & ISTATE_HARM))) attack_target.attack_hand(src, modifiers) update_held_items() else return ..() /mob/living/simple_animal/resolve_right_click_attack(atom/target, list/modifiers) - if(dextrous && (isitem(target) || !(istate & ISTATE_HARM))) + if((isitem(target) || !(istate & ISTATE_HARM))) . = target.attack_hand_secondary(src, modifiers) update_held_items() else @@ -315,10 +315,7 @@ /mob/living/simple_animal/hostile/resolve_unarmed_attack(atom/attack_target, list/modifiers) GiveTarget(attack_target) - if(dextrous && (isitem(attack_target) || !(istate & ISTATE_HARM))) - return ..() - else - AttackingTarget(attack_target) + INVOKE_ASYNC(src, PROC_REF(AttackingTarget), attack_target) #undef LIVING_UNARMED_ATTACK_BLOCKED diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm index 0b5d39b77380..96efadf80811 100644 --- a/code/controllers/subsystem/atoms.dm +++ b/code/controllers/subsystem/atoms.dm @@ -156,7 +156,8 @@ SUBSYSTEM_DEF(atoms) BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT else SEND_SIGNAL(A, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE) - var/atom/movable/location = A.loc + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_ATOM_AFTER_POST_INIT, A) + var/atom/location = A.loc if(location) /// Sends a signal that the new atom `src`, has been created at `loc` SEND_SIGNAL(location, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, A, arguments[1]) diff --git a/code/controllers/subsystem/npcpool.dm b/code/controllers/subsystem/npcpool.dm index 34cad5d7f861..6fbfdadf6828 100644 --- a/code/controllers/subsystem/npcpool.dm +++ b/code/controllers/subsystem/npcpool.dm @@ -29,7 +29,7 @@ SUBSYSTEM_DEF(npcpool) stack_trace("Found a null in simple_animals active list [SA.type]!") continue - if(!SA.ckey && !SA.notransform) + if(!SA.ckey && !HAS_TRAIT(SA, TRAIT_NO_TRANSFORM)) if(SA.stat != DEAD) SA.handle_automated_movement() if(SA.stat != DEAD) diff --git a/code/controllers/subsystem/persistence.dm b/code/controllers/subsystem/persistence.dm deleted file mode 100644 index cf4908c92f7d..000000000000 --- a/code/controllers/subsystem/persistence.dm +++ /dev/null @@ -1,559 +0,0 @@ -#define FILE_RECENT_MAPS "data/RecentMaps.json" - -#define KEEP_ROUNDS_MAP 3 - -SUBSYSTEM_DEF(persistence) - name = "Persistence" - init_order = INIT_ORDER_PERSISTENCE - flags = SS_NO_FIRE - - ///instantiated wall engraving components - var/list/wall_engravings = list() - ///tattoo stories that we're saving. - var/list/prison_tattoos_to_save = list() - ///tattoo stories that have been selected for this round. - var/list/prison_tattoos_to_use = list() - var/list/saved_messages = list() - var/list/saved_modes = list(1,2,3) - var/list/saved_maps = list() - var/list/blocked_maps = list() - var/list/saved_trophies = list() - var/list/picture_logging_information = list() - var/list/obj/structure/sign/picture_frame/photo_frames - var/list/obj/item/storage/photo_album/photo_albums - var/rounds_since_engine_exploded = 0 - -/datum/controller/subsystem/persistence/Initialize() - load_poly() - load_wall_engravings() - load_prisoner_tattoos() - load_trophies() - load_recent_maps() - load_photo_persistence() - load_randomized_recipes() - load_custom_outfits() - load_delamination_counter() - - load_adventures() - return SS_INIT_SUCCESS - -///Collects all data to persist. -/datum/controller/subsystem/persistence/proc/collect_data() - save_wall_engravings() - save_prisoner_tattoos() - collect_trophies() - collect_maps() - save_photo_persistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION. - save_randomized_recipes() - save_scars() - save_custom_outfits() - save_delamination_counter() - if(GLOB.interviews) - save_keys(GLOB.interviews.approved_ckeys) - -///Loads up Poly's speech buffer. -/datum/controller/subsystem/persistence/proc/load_poly() - for(var/mob/living/simple_animal/parrot/poly/P in GLOB.alive_mob_list) - twitterize(P.speech_buffer, "polytalk") - break //Who's been duping the bird?! - -///Loads all engravings, and places a select amount in maintenance and the prison. -/datum/controller/subsystem/persistence/proc/load_wall_engravings() - var/json_file = file(ENGRAVING_SAVE_FILE) - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - if(!json) - return - - if(json["version"] < ENGRAVING_PERSISTENCE_VERSION) - update_wall_engravings(json) - - var/successfully_loaded_engravings = 0 - - var/list/viable_turfs = get_area_turfs(/area/station/maintenance, subtypes = TRUE) + get_area_turfs(/area/station/security/prison, subtypes = TRUE) - var/list/turfs_to_pick_from = list() - - for(var/turf/T as anything in viable_turfs) - if(!isclosedturf(T)) - continue - turfs_to_pick_from += T - - var/list/engraving_entries = json["entries"] - - if(engraving_entries.len) - for(var/iteration in 1 to rand(MIN_PERSISTENT_ENGRAVINGS, MAX_PERSISTENT_ENGRAVINGS)) - var/engraving = engraving_entries[rand(1, engraving_entries.len)] //This means repeats will happen for now, but its something I can live with. Just make more engravings! - if(!islist(engraving)) - stack_trace("something's wrong with the engraving data! one of the saved engravings wasn't a list!") - continue - - var/turf/closed/engraved_wall = pick(turfs_to_pick_from) - - if(HAS_TRAIT(engraved_wall, TRAIT_NOT_ENGRAVABLE)) - continue - - engraved_wall.AddComponent(/datum/component/engraved, engraving["story"], FALSE, engraving["story_value"]) - successfully_loaded_engravings++ - turfs_to_pick_from -= engraved_wall - - log_world("Loaded [successfully_loaded_engravings] engraved messages on map [SSmapping.config.map_name]") - -///Saves all new engravings in the world. -/datum/controller/subsystem/persistence/proc/save_wall_engravings() - var/list/saved_data = list() - - saved_data["version"] = ENGRAVING_PERSISTENCE_VERSION - saved_data["entries"] = list() - - - var/json_file = file(ENGRAVING_SAVE_FILE) - if(fexists(json_file)) - var/list/old_json = json_decode(file2text(json_file)) - if(old_json) - saved_data["entries"] = old_json["entries"] - - for(var/datum/component/engraved/engraving in wall_engravings) - if(!engraving.persistent_save) - continue - var/area/engraved_area = get_area(engraving.parent) - if(!(engraved_area.area_flags & PERSISTENT_ENGRAVINGS)) - continue - saved_data["entries"] += engraving.save_persistent() - - fdel(json_file) - - WRITE_FILE(json_file, json_encode(saved_data)) - -///This proc can update entries if the format has changed at some point. -/datum/controller/subsystem/persistence/proc/update_wall_engravings(json) - - - for(var/engraving_entry in json["entries"]) - continue //no versioning yet - - //Save it to the file - var/json_file = file(ENGRAVING_SAVE_FILE) - fdel(json_file) - WRITE_FILE(json_file, json_encode(json)) - - return json - -///Loads all tattoos, and select a few based on the amount of prisoner spawn positions. -/datum/controller/subsystem/persistence/proc/load_prisoner_tattoos() - var/json_file = file(PRISONER_TATTOO_SAVE_FILE) - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - if(!json) - return - - if(json["version"] < TATTOO_PERSISTENCE_VERSION) - update_prisoner_tattoos(json) - - var/datum/job/prisoner_datum = SSjob.name_occupations[JOB_PRISONER] - if(!prisoner_datum) - return - var/iterations_allowed = prisoner_datum.spawn_positions - - var/list/entries = json["entries"] - if(entries.len) - for(var/index in 1 to iterations_allowed) - prison_tattoos_to_use += list(entries[rand(1, entries.len)]) - - log_world("Loaded [prison_tattoos_to_use.len] prison tattoos") - -///Saves all tattoos, so they can appear on prisoners in future rounds -/datum/controller/subsystem/persistence/proc/save_prisoner_tattoos() - var/json_file = file(PRISONER_TATTOO_SAVE_FILE) - var/list/saved_data = list() - var/list/entries = list() - - if(fexists(json_file)) - var/list/old_json = json_decode(file2text(json_file)) - if(old_json) - entries += old_json["entries"] //Save the old if its there - - entries += prison_tattoos_to_save - - saved_data["version"] = ENGRAVING_PERSISTENCE_VERSION - saved_data["entries"] = entries - - fdel(json_file) - WRITE_FILE(json_file, json_encode(saved_data)) - -///This proc can update entries if the format has changed at some point. -/datum/controller/subsystem/persistence/proc/update_prisoner_tattoos(json) - - for(var/tattoo_entry in json["entries"]) - continue //no versioning yet - - //Save it to the file - var/json_file = file(PRISONER_TATTOO_SAVE_FILE) - fdel(json_file) - WRITE_FILE(json_file, json_encode(json)) - - return json - -/// Loads the trophies from the source file, and places a few in trophy display cases. -/datum/controller/subsystem/persistence/proc/load_trophies() - var/list/raw_saved_trophies = list() - if(fexists("data/npc_saves/TrophyItems.json")) - var/json_file = file("data/npc_saves/TrophyItems.json") - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - if(!json) - return - raw_saved_trophies = json["data"] - fdel("data/npc_saves/TrophyItems.json") - else - var/json_file = file("data/trophy_items.json") - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - if(!json) - return - raw_saved_trophies = json["data"] - - for(var/raw_json in raw_saved_trophies) - var/datum/trophy_data/parsed_trophy_data = new - parsed_trophy_data.load_from_json(raw_json) - saved_trophies += parsed_trophy_data - - set_up_trophies() - -/datum/controller/subsystem/persistence/proc/save_keys(list/approved_ckeys) - var/json_file = file("data/approved_keys.json") - var/list/keys = list() - if(fexists(json_file)) - fdel(json_file) - keys = json_encode(approved_ckeys) - WRITE_FILE(json_file, keys) - -///trophy data datum, for admin manipulation -/datum/trophy_data - ///path of the item the trophy will try to mimic, null if path_string is invalid - var/path - ///the message that appears under the item - var/message - ///the key of the one who placed the item in the trophy case - var/placer_key - -/datum/trophy_data/proc/load_from_json(list/json_data) - path = json_data["path"] - message = json_data["message"] - placer_key = json_data["placer_key"] - -/datum/trophy_data/proc/to_json() - var/list/new_data = list() - new_data["path"] = path - new_data["message"] = message - new_data["placer_key"] = placer_key - new_data["is_valid"] = text2path(path) ? TRUE : FALSE - return new_data - -/// Returns a list for the admin trophy panel. -/datum/controller/subsystem/persistence/proc/trophy_ui_data() - var/list/ui_data = list() - for(var/datum/trophy_data/data in saved_trophies) - var/list/pdata = data.to_json() - pdata["ref"] = REF(data) - ui_data += list(pdata) - - return ui_data - -/// Loads up the amount of times maps appeared to alter their appearance in voting and rotation. -/datum/controller/subsystem/persistence/proc/load_recent_maps() - var/map_sav = FILE_RECENT_MAPS - if(!fexists(FILE_RECENT_MAPS)) - return - var/list/json = json_decode(file2text(map_sav)) - if(!json) - return - saved_maps = json["data"] - - //Convert the mapping data to a shared blocking list, saves us doing this in several places later. - for(var/map in config.maplist) - var/datum/map_config/VM = config.maplist[map] - var/run = 0 - if(VM.map_name == SSmapping.config.map_name) - run++ - for(var/name in SSpersistence.saved_maps) - if(VM.map_name == name) - run++ - if(run >= 2) //If run twice in the last KEEP_ROUNDS_MAP + 1 (including current) rounds, disable map for voting and rotation. - blocked_maps += VM.map_name - -/// Puts trophies into trophy cases. -/datum/controller/subsystem/persistence/proc/set_up_trophies() - - var/list/valid_trophies = list() - - for(var/datum/trophy_data/data in saved_trophies) - - if(!data) //sanity for incorrect deserialization - continue - - var/path = text2path(data.path) - if(!path) //If the item no longer exist, ignore it - continue - - valid_trophies += data - - for(var/obj/structure/displaycase/trophy/trophy_case in GLOB.trophy_cases) - if(!valid_trophies.len) - break - - if(trophy_case.showpiece) - continue - - trophy_case.set_up_trophy(pick_n_take(valid_trophies)) - -///Loads up the photo album source file. -/datum/controller/subsystem/persistence/proc/get_photo_albums() - var/album_path = file("data/photo_albums.json") - if(fexists(album_path)) - return json_decode(file2text(album_path)) - -///Loads up the photo frames source file. -/datum/controller/subsystem/persistence/proc/get_photo_frames() - var/frame_path = file("data/photo_frames.json") - if(fexists(frame_path)) - return json_decode(file2text(frame_path)) - -/// Removes the identifier of a persistent photo frame from the json. -/datum/controller/subsystem/persistence/proc/remove_photo_frames(identifier) - var/frame_path = file("data/photo_frames.json") - if(!fexists(frame_path)) - return - - var/frame_json = json_decode(file2text(frame_path)) - frame_json -= identifier - - frame_json = json_encode(frame_json) - fdel(frame_path) - WRITE_FILE(frame_path, frame_json) - -///Loads photo albums, and populates them; also loads and applies frames to picture frames. -/datum/controller/subsystem/persistence/proc/load_photo_persistence() - var/album_path = file("data/photo_albums.json") - var/frame_path = file("data/photo_frames.json") - if(fexists(album_path)) - var/list/json = json_decode(file2text(album_path)) - if(json.len) - for(var/i in photo_albums) - var/obj/item/storage/photo_album/A = i - if(!A.persistence_id) - continue - if(json[A.persistence_id]) - A.populate_from_id_list(json[A.persistence_id]) - - if(fexists(frame_path)) - var/list/json = json_decode(file2text(frame_path)) - if(json.len) - for(var/i in photo_frames) - var/obj/structure/sign/picture_frame/PF = i - if(!PF.persistence_id) - continue - if(json[PF.persistence_id]) - PF.load_from_id(json[PF.persistence_id]) - -///Saves the contents of photo albums and the picture frames. -/datum/controller/subsystem/persistence/proc/save_photo_persistence() - var/album_path = file("data/photo_albums.json") - var/frame_path = file("data/photo_frames.json") - - var/list/frame_json = list() - var/list/album_json = list() - - if(fexists(album_path)) - album_json = json_decode(file2text(album_path)) - fdel(album_path) - - for(var/i in photo_albums) - var/obj/item/storage/photo_album/A = i - if(!istype(A) || !A.persistence_id) - continue - var/list/L = A.get_picture_id_list() - album_json[A.persistence_id] = L - - album_json = json_encode(album_json) - - WRITE_FILE(album_path, album_json) - - if(fexists(frame_path)) - frame_json = json_decode(file2text(frame_path)) - fdel(frame_path) - - for(var/i in photo_frames) - var/obj/structure/sign/picture_frame/F = i - if(!istype(F) || !F.persistence_id) - continue - frame_json[F.persistence_id] = F.get_photo_id() - - frame_json = json_encode(frame_json) - - WRITE_FILE(frame_path, frame_json) - -///Collects trophies from all existing trophy cases. -/datum/controller/subsystem/persistence/proc/collect_trophies() - for(var/trophy_case in GLOB.trophy_cases) - save_trophy(trophy_case) - - var/json_file = file("data/trophy_items.json") - var/list/file_data = list() - var/list/converted_data = list() - - for(var/datum/trophy_data/data in saved_trophies) - converted_data += list(data.to_json()) - - converted_data = remove_duplicate_trophies(converted_data) - - file_data["data"] = converted_data - fdel(json_file) - WRITE_FILE(json_file, json_encode(file_data)) - -///gets the list of json trophies, and deletes the ones with an identical path and message -/datum/controller/subsystem/persistence/proc/remove_duplicate_trophies(list/trophies) - var/list/ukeys = list() - . = list() - for(var/trophy in trophies) - var/tkey = "[trophy["path"]]-[trophy["message"]]" - if(ukeys[tkey]) - continue - else - . += list(trophy) - ukeys[tkey] = TRUE - -///If there is a trophy in the trophy case, saved it, if the trophy was not a holo trophy and has a message attached. -/datum/controller/subsystem/persistence/proc/save_trophy(obj/structure/displaycase/trophy/trophy_case) - if(!trophy_case.holographic_showpiece && trophy_case.showpiece && trophy_case.trophy_message) - var/datum/trophy_data/data = new - data.path = trophy_case.showpiece.type - data.message = trophy_case.trophy_message - data.placer_key = trophy_case.placer_key - saved_trophies += data - -///Updates the list of the most recent maps. -/datum/controller/subsystem/persistence/proc/collect_maps() - if(length(saved_maps) > KEEP_ROUNDS_MAP) //Get rid of extras from old configs. - saved_maps.Cut(KEEP_ROUNDS_MAP+1) - var/mapstosave = min(length(saved_maps)+1, KEEP_ROUNDS_MAP) - if(length(saved_maps) < mapstosave) //Add extras if too short, one per round. - saved_maps += mapstosave - for(var/i = mapstosave; i > 1; i--) - saved_maps[i] = saved_maps[i-1] - saved_maps[1] = SSmapping.config.map_name - var/json_file = file(FILE_RECENT_MAPS) - var/list/file_data = list() - file_data["data"] = saved_maps - fdel(json_file) - WRITE_FILE(json_file, json_encode(file_data)) - -///Loads all randomized recipes. -/datum/controller/subsystem/persistence/proc/load_randomized_recipes() - var/json_file = file("data/RandomizedChemRecipes.json") - var/json - if(fexists(json_file)) - json = json_decode(file2text(json_file)) - - for(var/randomized_type in subtypesof(/datum/chemical_reaction/randomized)) - var/datum/chemical_reaction/randomized/R = new randomized_type - var/loaded = FALSE - if(R.persistent && json) - var/list/recipe_data = json["[R.type]"] - if(recipe_data) - if(R.LoadOldRecipe(recipe_data) && (daysSince(R.created) <= R.persistence_period)) - loaded = TRUE - if(!loaded) //We do not have information for whatever reason, just generate new one - if(R.persistent) - log_game("Resetting persistent [randomized_type] random recipe.") - R.GenerateRecipe() - - if(!R.HasConflicts()) //Might want to try again if conflicts happened in the future. - add_chemical_reaction(R) - else - log_game("Randomized recipe [randomized_type] resulted in conflicting recipes.") - -///Saves all randomized recipes. -/datum/controller/subsystem/persistence/proc/save_randomized_recipes() - var/json_file = file("data/RandomizedChemRecipes.json") - var/list/file_data = list() - - //asert globchems done - for(var/randomized_type in subtypesof(/datum/chemical_reaction/randomized)) - var/datum/chemical_reaction/randomized/R = get_chemical_reaction(randomized_type) //ew, would be nice to add some simple tracking - if(R?.persistent) - var/list/recipe_data = R.SaveOldRecipe() - file_data["[R.type]"] = recipe_data - - fdel(json_file) - WRITE_FILE(json_file, json_encode(file_data)) - -///Saves all scars for everyone's original characters -/datum/controller/subsystem/persistence/proc/save_scars() - for(var/i in GLOB.joined_player_list) - var/mob/living/carbon/human/ending_human = get_mob_by_ckey(i) - if(!istype(ending_human) || !ending_human.mind?.original_character_slot_index || !ending_human.client?.prefs.read_preference(/datum/preference/toggle/persistent_scars)) - continue - - var/mob/living/carbon/human/original_human = ending_human.mind.original_character.resolve() - - if(!original_human) - continue - - if(original_human.stat == DEAD || !original_human.all_scars || original_human != ending_human) - original_human.save_persistent_scars(TRUE) - else - original_human.save_persistent_scars() - -///Loads the custom outfits of every admin. -/datum/controller/subsystem/persistence/proc/load_custom_outfits() - var/file = file("data/custom_outfits.json") - if(!fexists(file)) - return - var/outfits_json = file2text(file) - var/list/outfits = json_decode(outfits_json) - if(!islist(outfits)) - return - - for(var/outfit_data in outfits) - if(!islist(outfit_data)) - continue - - var/outfittype = text2path(outfit_data["outfit_type"]) - if(!ispath(outfittype, /datum/outfit)) - continue - var/datum/outfit/outfit = new outfittype - if(!outfit.load_from(outfit_data)) - continue - GLOB.custom_outfits += outfit - -///Saves each admin's custom outfit list -/datum/controller/subsystem/persistence/proc/save_custom_outfits() - var/file = file("data/custom_outfits.json") - fdel(file) - - var/list/data = list() - for(var/datum/outfit/outfit in GLOB.custom_outfits) - data += list(outfit.get_json_data()) - - WRITE_FILE(file, json_encode(data)) - -/// Location where we save the information about how many rounds it has been since the engine blew up -#define DELAMINATION_COUNT_FILEPATH "data/rounds_since_delamination.txt" - -/datum/controller/subsystem/persistence/proc/load_delamination_counter() - if (!fexists(DELAMINATION_COUNT_FILEPATH)) - return - rounds_since_engine_exploded = text2num(file2text(DELAMINATION_COUNT_FILEPATH)) - for (var/obj/structure/sign/delamination_counter/sign as anything in GLOB.map_delamination_counters) - sign.update_count(rounds_since_engine_exploded) - -/datum/controller/subsystem/persistence/proc/save_delamination_counter() - rustg_file_write("[rounds_since_engine_exploded + 1]", DELAMINATION_COUNT_FILEPATH) - -#undef DELAMINATION_COUNT_FILEPATH -#undef FILE_RECENT_MAPS -#undef KEEP_ROUNDS_MAP diff --git a/code/controllers/subsystem/persistence/_persistence.dm b/code/controllers/subsystem/persistence/_persistence.dm new file mode 100644 index 000000000000..3153c72eb2c7 --- /dev/null +++ b/code/controllers/subsystem/persistence/_persistence.dm @@ -0,0 +1,111 @@ +#define FILE_RECENT_MAPS "data/RecentMaps.json" +#define KEEP_ROUNDS_MAP 3 + +SUBSYSTEM_DEF(persistence) + name = "Persistence" + init_order = INIT_ORDER_PERSISTENCE + flags = SS_NO_FIRE + + ///instantiated wall engraving components + var/list/wall_engravings = list() + ///all saved persistent engravings loaded from JSON + var/list/saved_engravings = list() + ///tattoo stories that we're saving. + var/list/prison_tattoos_to_save = list() + ///tattoo stories that have been selected for this round. + var/list/prison_tattoos_to_use = list() + var/list/saved_messages = list() + var/list/saved_modes = list(1,2,3) + var/list/saved_maps = list() + var/list/blocked_maps = list() + var/list/saved_trophies = list() + var/list/picture_logging_information = list() + var/list/obj/structure/sign/picture_frame/photo_frames + var/list/obj/item/storage/photo_album/photo_albums + var/rounds_since_engine_exploded = 0 + var/delam_highscore = 0 + var/tram_hits_this_round = 0 + var/tram_hits_last_round = 0 + +/datum/controller/subsystem/persistence/Initialize() + load_poly() + load_wall_engravings() + load_prisoner_tattoos() + load_trophies() + load_recent_maps() + load_photo_persistence() + load_randomized_recipes() + load_custom_outfits() + load_delamination_counter() + load_adventures() + return SS_INIT_SUCCESS + +///Collects all data to persist. +/datum/controller/subsystem/persistence/proc/collect_data() + save_wall_engravings() + save_prisoner_tattoos() + collect_trophies() + collect_maps() + save_photo_persistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION. + save_randomized_recipes() + save_scars() + save_custom_outfits() + save_delamination_counter() + if(GLOB.interviews) + save_keys(GLOB.interviews.approved_ckeys) + +///Loads up Poly's speech buffer. +/datum/controller/subsystem/persistence/proc/load_poly() + for(var/mob/living/basic/parrot/poly/bird in GLOB.alive_mob_list) + var/list/list_to_read = bird.get_static_list_of_phrases() + twitterize(list_to_read, "polytalk") + break //Who's been duping the bird?! + +/// Loads up the amount of times maps appeared to alter their appearance in voting and rotation. +/datum/controller/subsystem/persistence/proc/load_recent_maps() + var/map_sav = FILE_RECENT_MAPS + if(!fexists(FILE_RECENT_MAPS)) + return + var/list/json = json_decode(file2text(map_sav)) + if(!json) + return + saved_maps = json["data"] + + //Convert the mapping data to a shared blocking list, saves us doing this in several places later. + for(var/map in config.maplist) + var/datum/map_config/VM = config.maplist[map] + var/run = 0 + if(VM.map_name == SSmapping.config.map_name) + run++ + for(var/name in SSpersistence.saved_maps) + if(VM.map_name == name) + run++ + if(run >= 2) //If run twice in the last KEEP_ROUNDS_MAP + 1 (including current) rounds, disable map for voting and rotation. + blocked_maps += VM.map_name + +///Updates the list of the most recent maps. +/datum/controller/subsystem/persistence/proc/collect_maps() + if(length(saved_maps) > KEEP_ROUNDS_MAP) //Get rid of extras from old configs. + saved_maps.Cut(KEEP_ROUNDS_MAP+1) + var/mapstosave = min(length(saved_maps)+1, KEEP_ROUNDS_MAP) + if(length(saved_maps) < mapstosave) //Add extras if too short, one per round. + saved_maps += mapstosave + for(var/i = mapstosave; i > 1; i--) + saved_maps[i] = saved_maps[i-1] + saved_maps[1] = SSmapping.config.map_name + var/json_file = file(FILE_RECENT_MAPS) + var/list/file_data = list() + file_data["data"] = saved_maps + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) + +/datum/controller/subsystem/persistence/proc/save_keys(list/approved_ckeys) + var/json_file = file("data/approved_keys.json") + var/list/keys = list() + if(fexists(json_file)) + fdel(json_file) + keys = json_encode(approved_ckeys) + WRITE_FILE(json_file, keys) + +#undef FILE_RECENT_MAPS +#undef KEEP_ROUNDS_MAP diff --git a/code/controllers/subsystem/persistence/counter_delamination.dm b/code/controllers/subsystem/persistence/counter_delamination.dm new file mode 100644 index 000000000000..57ea7bde9f35 --- /dev/null +++ b/code/controllers/subsystem/persistence/counter_delamination.dm @@ -0,0 +1,19 @@ +/// Location where we save the information about how many rounds it has been since the engine blew up +#define DELAMINATION_COUNT_FILEPATH "data/rounds_since_delamination.txt" +#define DELAMINATION_HIGHSCORE_FILEPATH "data/delamination_highscore.txt" + +/datum/controller/subsystem/persistence/proc/load_delamination_counter() + if (!fexists(DELAMINATION_COUNT_FILEPATH)) + return + rounds_since_engine_exploded = text2num(file2text(DELAMINATION_COUNT_FILEPATH)) + for (var/obj/structure/sign/delamination_counter/sign as anything in GLOB.map_delamination_counters) + sign.update_count(rounds_since_engine_exploded) + + +/datum/controller/subsystem/persistence/proc/save_delamination_counter() + rustg_file_write("[rounds_since_engine_exploded + 1]", DELAMINATION_COUNT_FILEPATH) + if((rounds_since_engine_exploded + 1) > delam_highscore) + rustg_file_write("[rounds_since_engine_exploded + 1]", DELAMINATION_HIGHSCORE_FILEPATH) + +#undef DELAMINATION_COUNT_FILEPATH +#undef DELAMINATION_HIGHSCORE_FILEPATH diff --git a/code/controllers/subsystem/persistence/custom_outfits.dm b/code/controllers/subsystem/persistence/custom_outfits.dm new file mode 100644 index 000000000000..942f874fde5e --- /dev/null +++ b/code/controllers/subsystem/persistence/custom_outfits.dm @@ -0,0 +1,32 @@ +///Loads the custom outfits of every admin. +/datum/controller/subsystem/persistence/proc/load_custom_outfits() + var/file = file("data/custom_outfits.json") + if(!fexists(file)) + return + var/outfits_json = file2text(file) + var/list/outfits = json_decode(outfits_json) + if(!islist(outfits)) + return + + for(var/outfit_data in outfits) + if(!islist(outfit_data)) + continue + + var/outfittype = text2path(outfit_data["outfit_type"]) + if(!ispath(outfittype, /datum/outfit)) + continue + var/datum/outfit/outfit = new outfittype + if(!outfit.load_from(outfit_data)) + continue + GLOB.custom_outfits += outfit + +///Saves each admin's custom outfit list +/datum/controller/subsystem/persistence/proc/save_custom_outfits() + var/file = file("data/custom_outfits.json") + fdel(file) + + var/list/data = list() + for(var/datum/outfit/outfit in GLOB.custom_outfits) + data += list(outfit.get_json_data()) + + WRITE_FILE(file, json_encode(data)) diff --git a/code/controllers/subsystem/persistence/engravings.dm b/code/controllers/subsystem/persistence/engravings.dm new file mode 100644 index 000000000000..f47fc7fbba12 --- /dev/null +++ b/code/controllers/subsystem/persistence/engravings.dm @@ -0,0 +1,84 @@ +///Loads all engravings, and places a select amount in maintenance and the prison. +/datum/controller/subsystem/persistence/proc/load_wall_engravings() + var/json_file = file(ENGRAVING_SAVE_FILE) + if(!fexists(json_file)) + return + + var/list/json = json_decode(file2text(json_file)) + if(!json) + return + + if(json["version"] < ENGRAVING_PERSISTENCE_VERSION) + update_wall_engravings(json) + + saved_engravings = json["entries"] + + if(!saved_engravings.len) + log_world("Failed to load engraved messages on map [SSmapping.config.map_name]") + return + + var/list/viable_turfs = get_area_turfs(/area/station/maintenance, subtypes = TRUE) + get_area_turfs(/area/station/security/prison, subtypes = TRUE) + var/list/turfs_to_pick_from = list() + + for(var/turf/T as anything in viable_turfs) + if(!isclosedturf(T)) + continue + turfs_to_pick_from += T + + var/successfully_loaded_engravings = 0 + + for(var/iteration in 1 to rand(MIN_PERSISTENT_ENGRAVINGS, MAX_PERSISTENT_ENGRAVINGS)) + var/engraving = pick_n_take(saved_engravings) + if(!islist(engraving)) + stack_trace("something's wrong with the engraving data! one of the saved engravings wasn't a list!") + continue + + var/turf/closed/engraved_wall = pick(turfs_to_pick_from) + + if(HAS_TRAIT(engraved_wall, TRAIT_NOT_ENGRAVABLE)) + continue + + engraved_wall.AddComponent(/datum/component/engraved, engraving["story"], FALSE, engraving["story_value"]) + successfully_loaded_engravings++ + turfs_to_pick_from -= engraved_wall + + log_world("Loaded [successfully_loaded_engravings] engraved messages on map [SSmapping.config.map_name]") + +///Saves all new engravings in the world. +/datum/controller/subsystem/persistence/proc/save_wall_engravings() + var/list/saved_data = list() + + saved_data["version"] = ENGRAVING_PERSISTENCE_VERSION + saved_data["entries"] = list() + + + var/json_file = file(ENGRAVING_SAVE_FILE) + if(fexists(json_file)) + var/list/old_json = json_decode(file2text(json_file)) + if(old_json) + saved_data["entries"] = old_json["entries"] + + for(var/datum/component/engraved/engraving in wall_engravings) + if(!engraving.persistent_save) + continue + var/area/engraved_area = get_area(engraving.parent) + if(!(engraved_area.area_flags & PERSISTENT_ENGRAVINGS)) + continue + saved_data["entries"] += engraving.save_persistent() + + fdel(json_file) + + WRITE_FILE(json_file, json_encode(saved_data)) + +///This proc can update entries if the format has changed at some point. +/datum/controller/subsystem/persistence/proc/update_wall_engravings(json) + for(var/engraving_entry in json["entries"]) + continue //no versioning yet + + //Save it to the file + var/json_file = file(ENGRAVING_SAVE_FILE) + fdel(json_file) + WRITE_FILE(json_file, json_encode(json)) + + return json + diff --git a/code/controllers/subsystem/persistence/photo_albums.dm b/code/controllers/subsystem/persistence/photo_albums.dm new file mode 100644 index 000000000000..3aee856b2c21 --- /dev/null +++ b/code/controllers/subsystem/persistence/photo_albums.dm @@ -0,0 +1,86 @@ +///Loads up the photo album source file. +/datum/controller/subsystem/persistence/proc/get_photo_albums() + var/album_path = file("data/photo_albums.json") + if(fexists(album_path)) + return json_decode(file2text(album_path)) + +///Loads up the photo frames source file. +/datum/controller/subsystem/persistence/proc/get_photo_frames() + var/frame_path = file("data/photo_frames.json") + if(fexists(frame_path)) + return json_decode(file2text(frame_path)) + +/// Removes the identifier of a persistent photo frame from the json. +/datum/controller/subsystem/persistence/proc/remove_photo_frames(identifier) + var/frame_path = file("data/photo_frames.json") + if(!fexists(frame_path)) + return + + var/frame_json = json_decode(file2text(frame_path)) + frame_json -= identifier + + frame_json = json_encode(frame_json) + fdel(frame_path) + WRITE_FILE(frame_path, frame_json) + +///Loads photo albums, and populates them; also loads and applies frames to picture frames. +/datum/controller/subsystem/persistence/proc/load_photo_persistence() + var/album_path = file("data/photo_albums.json") + var/frame_path = file("data/photo_frames.json") + if(fexists(album_path)) + var/list/json = json_decode(file2text(album_path)) + if(json.len) + for(var/i in photo_albums) + var/obj/item/storage/photo_album/A = i + if(!A.persistence_id) + continue + if(json[A.persistence_id]) + A.populate_from_id_list(json[A.persistence_id]) + + if(fexists(frame_path)) + var/list/json = json_decode(file2text(frame_path)) + if(json.len) + for(var/i in photo_frames) + var/obj/structure/sign/picture_frame/PF = i + if(!PF.persistence_id) + continue + if(json[PF.persistence_id]) + PF.load_from_id(json[PF.persistence_id]) + +///Saves the contents of photo albums and the picture frames. +/datum/controller/subsystem/persistence/proc/save_photo_persistence() + var/album_path = file("data/photo_albums.json") + var/frame_path = file("data/photo_frames.json") + + var/list/frame_json = list() + var/list/album_json = list() + + if(fexists(album_path)) + album_json = json_decode(file2text(album_path)) + fdel(album_path) + + for(var/i in photo_albums) + var/obj/item/storage/photo_album/A = i + if(!istype(A) || !A.persistence_id) + continue + var/list/L = A.get_picture_id_list() + album_json[A.persistence_id] = L + + album_json = json_encode(album_json) + + WRITE_FILE(album_path, album_json) + + if(fexists(frame_path)) + frame_json = json_decode(file2text(frame_path)) + fdel(frame_path) + + for(var/i in photo_frames) + var/obj/structure/sign/picture_frame/F = i + if(!istype(F) || !F.persistence_id) + continue + frame_json[F.persistence_id] = F.get_photo_id() + + frame_json = json_encode(frame_json) + + WRITE_FILE(frame_path, frame_json) + diff --git a/code/controllers/subsystem/persistence/recipes.dm b/code/controllers/subsystem/persistence/recipes.dm new file mode 100644 index 000000000000..84145b26aabc --- /dev/null +++ b/code/controllers/subsystem/persistence/recipes.dm @@ -0,0 +1,39 @@ +///Loads all randomized recipes. +/datum/controller/subsystem/persistence/proc/load_randomized_recipes() + var/json_file = file("data/RandomizedChemRecipes.json") + var/json + if(fexists(json_file)) + json = json_decode(file2text(json_file)) + + for(var/randomized_type in subtypesof(/datum/chemical_reaction/randomized)) + var/datum/chemical_reaction/randomized/R = new randomized_type + var/loaded = FALSE + if(R.persistent && json) + var/list/recipe_data = json["[R.type]"] + if(recipe_data) + if(R.LoadOldRecipe(recipe_data) && (daysSince(R.created) <= R.persistence_period)) + loaded = TRUE + if(!loaded) //We do not have information for whatever reason, just generate new one + if(R.persistent) + log_game("Resetting persistent [randomized_type] random recipe.") + R.GenerateRecipe() + + if(!R.HasConflicts()) //Might want to try again if conflicts happened in the future. + add_chemical_reaction(R) + else + log_game("Randomized recipe [randomized_type] resulted in conflicting recipes.") + +///Saves all randomized recipes. +/datum/controller/subsystem/persistence/proc/save_randomized_recipes() + var/json_file = file("data/RandomizedChemRecipes.json") + var/list/file_data = list() + + //asert globchems done + for(var/randomized_type in subtypesof(/datum/chemical_reaction/randomized)) + var/datum/chemical_reaction/randomized/R = get_chemical_reaction(randomized_type) //ew, would be nice to add some simple tracking + if(R?.persistent) + var/list/recipe_data = R.SaveOldRecipe() + file_data["[R.type]"] = recipe_data + + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) diff --git a/code/controllers/subsystem/persistence/scars.dm b/code/controllers/subsystem/persistence/scars.dm new file mode 100644 index 000000000000..fa378f24dd43 --- /dev/null +++ b/code/controllers/subsystem/persistence/scars.dm @@ -0,0 +1,17 @@ +///Saves all scars for everyone's original characters +/datum/controller/subsystem/persistence/proc/save_scars() + for(var/i in GLOB.joined_player_list) + var/mob/living/carbon/human/ending_human = get_mob_by_ckey(i) + if(!istype(ending_human) || !ending_human.mind?.original_character_slot_index || !ending_human.client?.prefs.read_preference(/datum/preference/toggle/persistent_scars)) + continue + + var/mob/living/carbon/human/original_human = ending_human.mind.original_character.resolve() + + if(!original_human) + continue + + if(original_human.stat == DEAD || !original_human.all_scars || original_human != ending_human) + original_human.save_persistent_scars(TRUE) + else + original_human.save_persistent_scars() + diff --git a/code/controllers/subsystem/persistence/tattoos.dm b/code/controllers/subsystem/persistence/tattoos.dm new file mode 100644 index 000000000000..45dd31c7f5c9 --- /dev/null +++ b/code/controllers/subsystem/persistence/tattoos.dm @@ -0,0 +1,55 @@ +///Loads all tattoos, and select a few based on the amount of prisoner spawn positions. +/datum/controller/subsystem/persistence/proc/load_prisoner_tattoos() + var/json_file = file(PRISONER_TATTOO_SAVE_FILE) + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + if(!json) + return + + if(json["version"] < TATTOO_PERSISTENCE_VERSION) + update_prisoner_tattoos(json) + + var/datum/job/prisoner_datum = SSjob.name_occupations[JOB_PRISONER] + if(!prisoner_datum) + return + var/iterations_allowed = prisoner_datum.spawn_positions + + var/list/entries = json["entries"] + if(entries.len) + for(var/index in 1 to iterations_allowed) + prison_tattoos_to_use += list(entries[rand(1, entries.len)]) + + log_world("Loaded [prison_tattoos_to_use.len] prison tattoos") + +///Saves all tattoos, so they can appear on prisoners in future rounds +/datum/controller/subsystem/persistence/proc/save_prisoner_tattoos() + var/json_file = file(PRISONER_TATTOO_SAVE_FILE) + var/list/saved_data = list() + var/list/entries = list() + + if(fexists(json_file)) + var/list/old_json = json_decode(file2text(json_file)) + if(old_json) + entries += old_json["entries"] //Save the old if its there + + entries += prison_tattoos_to_save + + saved_data["version"] = ENGRAVING_PERSISTENCE_VERSION + saved_data["entries"] = entries + + fdel(json_file) + WRITE_FILE(json_file, json_encode(saved_data)) + +///This proc can update entries if the format has changed at some point. +/datum/controller/subsystem/persistence/proc/update_prisoner_tattoos(json) + for(var/tattoo_entry in json["entries"]) + continue //no versioning yet + + //Save it to the file + var/json_file = file(PRISONER_TATTOO_SAVE_FILE) + fdel(json_file) + WRITE_FILE(json_file, json_encode(json)) + + return json + diff --git a/code/controllers/subsystem/persistence/trophies.dm b/code/controllers/subsystem/persistence/trophies.dm new file mode 100644 index 000000000000..515ed3860850 --- /dev/null +++ b/code/controllers/subsystem/persistence/trophies.dm @@ -0,0 +1,125 @@ +///trophy data datum, for admin manipulation +/datum/trophy_data + ///path of the item the trophy will try to mimic, null if path_string is invalid + var/path + ///the message that appears under the item + var/message + ///the key of the one who placed the item in the trophy case + var/placer_key + +/// Loads the trophies from the source file, and places a few in trophy display cases. +/datum/controller/subsystem/persistence/proc/load_trophies() + var/list/raw_saved_trophies = list() + if(fexists("data/npc_saves/TrophyItems.json")) + var/json_file = file("data/npc_saves/TrophyItems.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + if(!json) + return + raw_saved_trophies = json["data"] + fdel("data/npc_saves/TrophyItems.json") + else + var/json_file = file("data/trophy_items.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + if(!json) + return + raw_saved_trophies = json["data"] + + for(var/raw_json in raw_saved_trophies) + var/datum/trophy_data/parsed_trophy_data = new + parsed_trophy_data.load_from_json(raw_json) + saved_trophies += parsed_trophy_data + + set_up_trophies() + +/datum/trophy_data/proc/load_from_json(list/json_data) + path = json_data["path"] + message = json_data["message"] + placer_key = json_data["placer_key"] + +/datum/trophy_data/proc/to_json() + var/list/new_data = list() + new_data["path"] = path + new_data["message"] = message + new_data["placer_key"] = placer_key + new_data["is_valid"] = text2path(path) ? TRUE : FALSE + return new_data + +/// Returns a list for the admin trophy panel. +/datum/controller/subsystem/persistence/proc/trophy_ui_data() + var/list/ui_data = list() + for(var/datum/trophy_data/data in saved_trophies) + var/list/pdata = data.to_json() + pdata["ref"] = REF(data) + ui_data += list(pdata) + + return ui_data + + +/// Puts trophies into trophy cases. +/datum/controller/subsystem/persistence/proc/set_up_trophies() + + var/list/valid_trophies = list() + + for(var/datum/trophy_data/data in saved_trophies) + + if(!data) //sanity for incorrect deserialization + continue + + var/path = text2path(data.path) + if(!path) //If the item no longer exist, ignore it + continue + + valid_trophies += data + + for(var/obj/structure/displaycase/trophy/trophy_case in GLOB.trophy_cases) + if(!valid_trophies.len) + break + + if(trophy_case.showpiece) + continue + + trophy_case.set_up_trophy(pick_n_take(valid_trophies)) + +///Collects trophies from all existing trophy cases. +/datum/controller/subsystem/persistence/proc/collect_trophies() + for(var/trophy_case in GLOB.trophy_cases) + save_trophy(trophy_case) + + var/json_file = file("data/trophy_items.json") + var/list/file_data = list() + var/list/converted_data = list() + + for(var/datum/trophy_data/data in saved_trophies) + converted_data += list(data.to_json()) + + converted_data = remove_duplicate_trophies(converted_data) + + file_data["data"] = converted_data + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) + +///gets the list of json trophies, and deletes the ones with an identical path and message +/datum/controller/subsystem/persistence/proc/remove_duplicate_trophies(list/trophies) + var/list/ukeys = list() + . = list() + for(var/trophy in trophies) + var/tkey = "[trophy["path"]]-[trophy["message"]]" + if(ukeys[tkey]) + continue + else + . += list(trophy) + ukeys[tkey] = TRUE + +///If there is a trophy in the trophy case, saved it, if the trophy was not a holo trophy and has a message attached. +/datum/controller/subsystem/persistence/proc/save_trophy(obj/structure/displaycase/trophy/trophy_case) + if(!trophy_case.holographic_showpiece && trophy_case.showpiece && trophy_case.trophy_message) + var/datum/trophy_data/data = new + data.path = trophy_case.showpiece.type + data.message = trophy_case.trophy_message + data.placer_key = trophy_case.placer_key + saved_trophies += data + diff --git a/code/controllers/subsystem/processing/ai_behaviors.dm b/code/controllers/subsystem/processing/ai_behaviors.dm index 4ec698db32bb..e92da2474bb7 100644 --- a/code/controllers/subsystem/processing/ai_behaviors.dm +++ b/code/controllers/subsystem/processing/ai_behaviors.dm @@ -8,9 +8,12 @@ PROCESSING_SUBSYSTEM_DEF(ai_behaviors) wait = 1 ///List of all ai_behavior singletons, key is the typepath while assigned value is a newly created instance of the typepath. See SetupAIBehaviors() var/list/ai_behaviors + ///List of all targeting_strategy singletons, key is the typepath while assigned value is a newly created instance of the typepath. See SetupAIBehaviors() + var/list/targeting_strategies /datum/controller/subsystem/processing/ai_behaviors/Initialize() SetupAIBehaviors() + SetupTargetingStrats() return SS_INIT_SUCCESS /datum/controller/subsystem/processing/ai_behaviors/proc/SetupAIBehaviors() @@ -18,3 +21,9 @@ PROCESSING_SUBSYSTEM_DEF(ai_behaviors) for(var/behavior_type in subtypesof(/datum/ai_behavior)) var/datum/ai_behavior/ai_behavior = new behavior_type ai_behaviors[behavior_type] = ai_behavior + +/datum/controller/subsystem/processing/ai_behaviors/proc/SetupTargetingStrats() + targeting_strategies = list() + for(var/target_type in subtypesof(/datum/targeting_strategy)) + var/datum/targeting_strategy/target_start = new target_type + targeting_strategies[target_type] = target_start diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index c94405a1afa3..fde6cd4fed0d 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -1,4 +1,5 @@ #define ROUND_START_MUSIC_LIST "strings/round_start_sounds.txt" +#define SS_TICKER_TRAIT "SS_Ticker" SUBSYSTEM_DEF(ticker) name = "Ticker" @@ -482,7 +483,7 @@ SUBSYSTEM_DEF(ticker) var/mob/living = player.transfer_character() if(living) qdel(player) - living.notransform = TRUE + ADD_TRAIT(living, TRAIT_NO_TRANSFORM, SS_TICKER_TRAIT) if(living.client) var/atom/movable/screen/splash/S = new(null, living.client, TRUE) S.Fade(TRUE) @@ -497,12 +498,11 @@ SUBSYSTEM_DEF(ticker) if(living.job == processing_reward_jobs) living.client.reward_this_person += 150 if(livings.len) - addtimer(CALLBACK(src, PROC_REF(release_characters), livings), 30, TIMER_CLIENT_TIME) + addtimer(CALLBACK(src, PROC_REF(release_characters), livings), 3 SECONDS, TIMER_CLIENT_TIME) /datum/controller/subsystem/ticker/proc/release_characters(list/livings) - for(var/I in livings) - var/mob/living/L = I - L.notransform = FALSE + for(var/mob/living/living_mob as anything in livings) + REMOVE_TRAIT(living_mob, TRAIT_NO_TRANSFORM, SS_TICKER_TRAIT) /datum/controller/subsystem/ticker/proc/check_queue() if(!queued_players.len) @@ -766,3 +766,4 @@ SUBSYSTEM_DEF(ticker) return "[global.config.directory]/reboot_themes/[pick(possible_themes)]" #undef ROUND_START_MUSIC_LIST +#undef SS_TICKER_TRAIT diff --git a/code/datums/actions/cooldown_action.dm b/code/datums/actions/cooldown_action.dm index 015df7d310b3..d4d02901dda2 100644 --- a/code/datums/actions/cooldown_action.dm +++ b/code/datums/actions/cooldown_action.dm @@ -177,6 +177,8 @@ /// Starts a cooldown time for other abilities that share a cooldown with this. Has some niche usage with more complicated attack ai! /// Will use default cooldown time if an override is not specified /datum/action/cooldown/proc/StartCooldownOthers(override_cooldown_time) + if(!length(owner.actions)) + return // Possible if they have an action they don't control for(var/datum/action/cooldown/shared_ability in owner.actions - src) if(!(shared_cooldown & shared_ability.shared_cooldown)) continue diff --git a/code/datums/actions/mobs/charge.dm b/code/datums/actions/mobs/charge.dm index 17f1dd2aeb16..4bb3c70e7444 100644 --- a/code/datums/actions/mobs/charge.dm +++ b/code/datums/actions/mobs/charge.dm @@ -152,17 +152,23 @@ SSexplosions.med_mov_atom += target INVOKE_ASYNC(src, PROC_REF(DestroySurroundings), source) - hit_target(source, target, charge_damage) - -/datum/action/cooldown/mob_cooldown/charge/proc/hit_target(atom/movable/source, atom/target, damage_dealt) - if(!isliving(target)) - return - var/mob/living/living_target = target - living_target.visible_message("[source] slams into [living_target]!", "[source] tramples you into the ground!") - source.forceMove(get_turf(living_target)) - living_target.apply_damage(damage_dealt, BRUTE, wound_bonus = CANT_WOUND) - playsound(get_turf(living_target), 'sound/effects/meteorimpact.ogg', 100, TRUE) - shake_camera(living_target, 4, 3) + try_hit_target(source, target) + +/// Attempt to hit someone with our charge +/datum/action/cooldown/mob_cooldown/charge/proc/try_hit_target(atom/movable/source, atom/target) + if (can_hit_target(source, target)) + hit_target(source, target, charge_damage) + +/// Returns true if we're allowed to charge into this target +/datum/action/cooldown/mob_cooldown/charge/proc/can_hit_target(atom/movable/source, atom/target) + return isliving(target) + +/// Actually hit someone +/datum/action/cooldown/mob_cooldown/charge/proc/hit_target(atom/movable/source, mob/living/target, damage_dealt) + target.visible_message(span_danger("[source] slams into [target]!"), span_userdanger("[source] tramples you into the ground!")) + target.apply_damage(damage_dealt, BRUTE, wound_bonus = CANT_WOUND) + playsound(get_turf(target), 'sound/effects/meteorimpact.ogg', 100, TRUE) + shake_camera(target, 4, 3) shake_camera(source, 2, 3) /datum/action/cooldown/mob_cooldown/charge/basic_charge @@ -183,18 +189,21 @@ /datum/action/cooldown/mob_cooldown/charge/basic_charge/do_charge_indicator(atom/charger, atom/charge_target) charger.Shake(shake_pixel_shift, shake_pixel_shift, shake_duration) +/datum/action/cooldown/mob_cooldown/charge/basic_charge/can_hit_target(atom/movable/source, atom/target) + if(!isliving(target)) + if(!target.density || target.CanPass(source, get_dir(target, source))) + return FALSE + return TRUE + return ..() + /datum/action/cooldown/mob_cooldown/charge/basic_charge/hit_target(atom/movable/source, atom/target, damage_dealt) var/mob/living/living_source if(isliving(source)) living_source = source if(!isliving(target)) - if(!target.density || target.CanPass(source, get_dir(target, source))) - return source.visible_message(span_danger("[source] smashes into [target]!")) - if(!living_source) - return - living_source.Stun(recoil_duration, ignore_canstun = TRUE) + living_source?.Stun(recoil_duration, ignore_canstun = TRUE) return var/mob/living/living_target = target @@ -204,10 +213,9 @@ living_source.Stun(recoil_duration, ignore_canstun = TRUE) return - living_target.visible_message(span_danger("[source] charges on [living_target]!"), span_userdanger("[source] charges into you!")) + living_target.visible_message(span_danger("[source] charges into [living_target]!"), span_userdanger("[source] charges into you!")) living_target.Knockdown(knockdown_duration) - /datum/status_effect/tired_post_charge id = "tired_post_charge" duration = 1 SECONDS diff --git a/code/datums/actions/mobs/fire_breath.dm b/code/datums/actions/mobs/fire_breath.dm index 57962035a24d..165b87b02860 100644 --- a/code/datums/actions/mobs/fire_breath.dm +++ b/code/datums/actions/mobs/fire_breath.dm @@ -2,62 +2,121 @@ name = "Fire Breath" button_icon = 'icons/obj/wizard.dmi' button_icon_state = "fireball" - desc = "Allows you to shoot fire towards a target." + desc = "Breathe a line of flames towards the target." cooldown_time = 3 SECONDS /// The range of the fire var/fire_range = 15 /// The sound played when you use this ability var/fire_sound = 'sound/magic/fireball.ogg' - /// If the fire should be icey fire - var/ice_breath = FALSE + /// Time to wait between spawning each fire turf + var/fire_delay = 1.5 DECISECONDS + /// How hot is our fire + var/fire_temperature = DRAKE_FIRE_TEMP + /// 'How much' fire do we expose the turf to? + var/fire_volume = DRAKE_FIRE_EXPOSURE + /// How much damage do you take when engulfed? + var/fire_damage = 20 + /// How much damage to mechs take when engulfed? + var/mech_damage = 45 /datum/action/cooldown/mob_cooldown/fire_breath/Activate(atom/target_atom) - StartCooldown(360 SECONDS, 360 SECONDS) attack_sequence(target_atom) StartCooldown() return TRUE +/// Apply our specific fire breathing shape, in proc form so we can override it in subtypes /datum/action/cooldown/mob_cooldown/fire_breath/proc/attack_sequence(atom/target) playsound(owner.loc, fire_sound, 200, TRUE) - fire_line(target, 0) + fire_line(target) +/// Breathe fire in a line towards the target, optionally rotated at an offset from the target /datum/action/cooldown/mob_cooldown/fire_breath/proc/fire_line(atom/target, offset) - SLEEP_CHECK_DEATH(0, owner) - var/list/turfs = line_target(offset, fire_range, target) - dragon_fire_line(owner, turfs, ice_breath) + if (isnull(target)) + return + var/turf/target_turf = get_ranged_target_turf_direct(owner, target, fire_range, offset) + var/list/turfs = get_line(owner, target_turf) - get_turf(owner) + INVOKE_ASYNC(src, PROC_REF(progressive_fire_line), turfs) -/datum/action/cooldown/mob_cooldown/fire_breath/proc/line_target(offset, range, atom/target) - if(!target) +/// Creates fire with a delay on the list of targeted turfs +/datum/action/cooldown/mob_cooldown/fire_breath/proc/progressive_fire_line(list/burn_turfs) + if (QDELETED(owner) || owner.stat == DEAD) return - var/turf/T = get_ranged_target_turf_direct(owner, target, range, offset) - return (get_line(owner, T) - get_turf(owner)) + // Guys we have already hit, no double dipping + var/list/hit_list = list(owner) // also don't burn ourselves + for(var/turf/target_turf in burn_turfs) + if (target_turf.is_blocked_turf(exclude_mobs = TRUE)) + return + burn_turf(target_turf, hit_list, owner) + sleep(fire_delay) + +/// Finally spawn the actual fire, spawns the fire hotspot in case you want to recolour it or something +/datum/action/cooldown/mob_cooldown/fire_breath/proc/burn_turf(turf/fire_turf, list/hit_list, mob/living/source) + var/obj/effect/hotspot/fire_hotspot = new /obj/effect/hotspot(fire_turf) + fire_turf.hotspot_expose(fire_temperature, fire_volume, TRUE) + + for(var/mob/living/barbecued in fire_turf.contents) + if(barbecued in hit_list) + continue + hit_list |= barbecued + on_burn_mob(barbecued, source) + + for(var/obj/vehicle/sealed/mecha/robotron in fire_turf.contents) + if(robotron in hit_list) + continue + hit_list |= robotron + robotron.take_damage(mech_damage, BURN, FIRE) + + return fire_hotspot +/// Do something unpleasant to someone we set on fire +/datum/action/cooldown/mob_cooldown/fire_breath/proc/on_burn_mob(mob/living/barbecued, mob/living/source) + to_chat(barbecued, span_userdanger("You are burned by [source]'s fire breath!")) + barbecued.adjustFireLoss(fire_damage) + +/// Shoot three lines of fire in a sort of fork pattern approximating a cone /datum/action/cooldown/mob_cooldown/fire_breath/cone name = "Fire Cone" - desc = "Allows you to shoot fire towards a target with surrounding lines of fire." + desc = "Breathe several lines of fire directed at a target." /// The angles relative to the target that shoot lines of fire var/list/angles = list(-40, 0, 40) /datum/action/cooldown/mob_cooldown/fire_breath/cone/attack_sequence(atom/target) playsound(owner.loc, fire_sound, 200, TRUE) for(var/offset in angles) - INVOKE_ASYNC(src, PROC_REF(fire_line), target, offset) + fire_line(target, offset) +/// Shoot fire in a whole bunch of directions /datum/action/cooldown/mob_cooldown/fire_breath/mass_fire name = "Mass Fire" button_icon = 'icons/effects/fire.dmi' button_icon_state = "1" - desc = "Allows you to shoot fire in all directions." + desc = "Breathe flames in all directions." cooldown_time = 3 SECONDS + click_to_activate = FALSE + /// How many fire lines do we produce to turn a full circle? + var/sectors = 12 + /// How long do we wait between each spin? + var/breath_delay = 2.5 SECONDS + /// How many full circles do we perform? + var/total_spins = 3 + +/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/Activate(atom/target_atom) + target_atom = get_step(owner, owner.dir) // Just shoot it forwards, we don't need to click on someone for this one + return ..() /datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/attack_sequence(atom/target) - shoot_mass_fire(target, 12, 2.5 SECONDS, 3) - -/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/proc/shoot_mass_fire(atom/target, spiral_count, delay_time, times) - SLEEP_CHECK_DEATH(0, owner) - for(var/i = 1 to times) - playsound(owner.loc, fire_sound, 200, TRUE) - var/increment = 360 / spiral_count - for(var/j = 1 to spiral_count) - INVOKE_ASYNC(src, PROC_REF(fire_line), target, j * increment + i * increment / 2) - SLEEP_CHECK_DEATH(delay_time, owner) + var/queued_spins = 0 + for (var/i in 1 to total_spins) + var/delay = queued_spins * breath_delay + queued_spins++ + addtimer(CALLBACK(src, PROC_REF(fire_spin), target, queued_spins), delay) + +/// Breathe fire in a circle, with a slight angle offset based on which of our several circles it is +/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/proc/fire_spin(target, spin_count) + if (QDELETED(owner) || owner.stat == DEAD) + return // Too dead to spin + playsound(owner.loc, fire_sound, 200, TRUE) + var/angle_increment = 360 / sectors + var/additional_offset = spin_count * angle_increment / 2 + for (var/i in 1 to sectors) + fire_line(target, (angle_increment * i) + (additional_offset)) diff --git a/code/datums/actions/mobs/lava_swoop.dm b/code/datums/actions/mobs/lava_swoop.dm index d23252c73aac..d18ad2f3af3b 100644 --- a/code/datums/actions/mobs/lava_swoop.dm +++ b/code/datums/actions/mobs/lava_swoop.dm @@ -38,7 +38,7 @@ return // stop swooped target movement swooping = TRUE - owner.set_density(FALSE) + ADD_TRAIT(owner, TRAIT_UNDENSE, SWOOPING_TRAIT) owner.visible_message(span_boldwarning("[owner] swoops up high!")) var/negative @@ -115,7 +115,7 @@ for(var/mob/M in range(7, owner)) shake_camera(M, 15, 1) - owner.set_density(TRUE) + REMOVE_TRAIT(owner, TRAIT_UNDENSE, SWOOPING_TRAIT) SLEEP_CHECK_DEATH(1, owner) swooping = FALSE if(!lava_success) diff --git a/code/datums/actions/mobs/mobcooldown.dm b/code/datums/actions/mobs/mobcooldown.dm index 17561f2f45a5..a495859494ff 100644 --- a/code/datums/actions/mobs/mobcooldown.dm +++ b/code/datums/actions/mobs/mobcooldown.dm @@ -3,7 +3,7 @@ button_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "sniper_zoom" desc = "Click this ability to attack." - check_flags = AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED cooldown_time = 5 SECONDS text_cooldown = TRUE click_to_activate = TRUE diff --git a/code/datums/actions/mobs/small_sprite.dm b/code/datums/actions/mobs/small_sprite.dm deleted file mode 100644 index 335d6c9aff39..000000000000 --- a/code/datums/actions/mobs/small_sprite.dm +++ /dev/null @@ -1,57 +0,0 @@ -//Small sprites -/datum/action/small_sprite - name = "Toggle Giant Sprite" - desc = "Others will always see you as giant." - button_icon = 'icons/mob/actions/actions_xeno.dmi' - button_icon_state = "smallqueen" - background_icon_state = "bg_alien" - overlay_icon_state = "bg_alien_border" - var/small = FALSE - var/small_icon - var/small_icon_state - -/datum/action/small_sprite/queen - small_icon = 'icons/mob/nonhuman-player/alien.dmi' - small_icon_state = "alienq" - -/datum/action/small_sprite/megafauna - button_icon = 'icons/mob/actions/actions_xeno.dmi' - small_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' - -/datum/action/small_sprite/megafauna/drake - small_icon_state = "ash_whelp" - -/datum/action/small_sprite/megafauna/colossus - small_icon_state = "basilisk" - -/datum/action/small_sprite/megafauna/bubblegum - small_icon_state = "goliath2" - -/datum/action/small_sprite/megafauna/legion - small_icon_state = "mega_legion" - -/datum/action/small_sprite/mega_arachnid - small_icon = 'icons/mob/simple/jungle/arachnid.dmi' - small_icon_state = "arachnid_mini" - background_icon_state = "bg_demon" - overlay_icon_state = "bg_demon_border" - - -/datum/action/small_sprite/space_dragon - small_icon = 'icons/mob/simple/carp.dmi' - small_icon_state = "carp" - button_icon = 'icons/mob/simple/carp.dmi' - button_icon_state = "carp" - -/datum/action/small_sprite/Trigger(trigger_flags) - ..() - if(!small) - var/image/I = image(icon = small_icon, icon_state = small_icon_state, loc = owner) - I.override = TRUE - I.pixel_x -= owner.pixel_x - I.pixel_y -= owner.pixel_y - owner.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic, "smallsprite", I, AA_TARGET_SEE_APPEARANCE | AA_MATCH_TARGET_OVERLAYS) - small = TRUE - else - owner.remove_alt_appearance("smallsprite") - small = FALSE diff --git a/code/datums/actions/mobs/sneak.dm b/code/datums/actions/mobs/sneak.dm index 13ae5a784b8c..4e254daa8684 100644 --- a/code/datums/actions/mobs/sneak.dm +++ b/code/datums/actions/mobs/sneak.dm @@ -5,7 +5,9 @@ button_icon_state = "sniper_zoom" background_icon_state = "bg_alien" overlay_icon_state = "bg_alien_border" - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED | AB_CHECK_INCAPACITATED + cooldown_time = 0.5 SECONDS + melee_cooldown_time = 0 SECONDS + click_to_activate = FALSE /// The alpha we go to when sneaking. var/sneak_alpha = 75 diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 1c7a778ae229..8795f9ef5317 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -346,6 +346,15 @@ multiple modular subtrees with behaviors minimum_distance = iter_behavior.required_distance return minimum_distance +/// Returns true if we have a blackboard key with the provided key and it is not qdeleting +/datum/ai_controller/proc/blackboard_key_exists(key) + var/datum/key_value = blackboard[key] + if (isdatum(key_value)) + return !QDELETED(key_value) + if (islist(key_value)) + return length(key_value) > 0 + return !!key_value + /** * Used to manage references to datum by AI controllers * @@ -611,6 +620,7 @@ multiple modular subtrees with behaviors // We found the value that's been deleted, it was an assoc value. Clear it out entirely else if(associated_value == source) next_to_clear -= inner_value + SEND_SIGNAL(pawn, COMSIG_AI_BLACKBOARD_KEY_CLEARED(inner_value)) index += 1 diff --git a/code/datums/ai/babies/babies_behaviors.dm b/code/datums/ai/babies/babies_behaviors.dm index aeb295ff2204..daaa3b407b75 100644 --- a/code/datums/ai/babies/babies_behaviors.dm +++ b/code/datums/ai/babies/babies_behaviors.dm @@ -2,74 +2,77 @@ * Find a compatible, living partner, if we're also alone. */ /datum/ai_behavior/find_partner - action_cooldown = 40 SECONDS - /// Range to look. var/range = 7 - /// Maximum number of children var/max_children = 3 /datum/ai_behavior/find_partner/perform(seconds_per_tick, datum/ai_controller/controller, target_key, partner_types_key, child_types_key) . = ..() - + max_children = controller.blackboard[BB_MAX_CHILDREN] || max_children var/mob/pawn_mob = controller.pawn var/list/partner_types = controller.blackboard[partner_types_key] var/list/child_types = controller.blackboard[child_types_key] + var/mob/living/living_pawn = controller.pawn - var/mob/living/partner var/children = 0 - for(var/mob/other in oview(range, pawn_mob)) + for(var/mob/living/other in oview(range, pawn_mob)) + if(!pawn_mob.faction_check_atom(other)) + finish_action(controller, FALSE) + return + + if(children >= max_children) + finish_action(controller, FALSE) + return + if(other.stat != CONSCIOUS) //Check if it's conscious FIRST. continue - var/is_child = is_type_in_list(other, child_types) - if(is_child) //Check for children SECOND. + + if(is_type_in_list(other, child_types)) //Check for children SECOND. children++ - else if(is_type_in_list(other, partner_types)) - if(other.ckey) - continue - else if(!is_child && other.gender == MALE && !(other.flags_1 & HOLOGRAM_1)) //Better safe than sorry ;_; - partner = other + continue - //shyness check. we're not shy in front of things that share a faction with us. - else if(isliving(other) && !pawn_mob.faction_check_mob(other)) - finish_action(controller, FALSE) - return + if(!is_type_in_list(other, partner_types) || !HAS_TRAIT(other, TRAIT_MOB_BREEDER)) + continue + + if(other.ckey) + continue + + if(other.gender != living_pawn.gender && !(other.flags_1 & HOLOGRAM_1)) //Better safe than sorry ;_; + controller.set_blackboard_key(target_key, other) + finish_action(controller, TRUE) - if(partner && children < max_children) - controller.set_blackboard_key(target_key, partner) + finish_action(controller, FALSE) - finish_action(controller, TRUE) /** * Reproduce. */ /datum/ai_behavior/make_babies - action_cooldown = 40 SECONDS - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH /datum/ai_behavior/make_babies/setup(datum/ai_controller/controller, target_key, child_types_key) + . = ..() var/atom/target = controller.blackboard[target_key] if(!target) return FALSE set_movement_target(controller, target) - return TRUE /datum/ai_behavior/make_babies/perform(seconds_per_tick, datum/ai_controller/controller, target_key, child_types_key) . = ..() var/mob/target = controller.blackboard[target_key] - if(!target || target.stat != CONSCIOUS) + if(QDELETED(target) || target.stat != CONSCIOUS) finish_action(controller, FALSE, target_key) return - - var/child_type = pick_weight(controller.blackboard[child_types_key]) - var/turf/turf_loc = get_turf(controller.pawn.loc) - if(turf_loc) - new child_type(turf_loc) - + var/mob/living/basic/living_pawn = controller.pawn + living_pawn.set_combat_mode(FALSE) + living_pawn.melee_attack(target) finish_action(controller, TRUE, target_key) /datum/ai_behavior/make_babies/finish_action(datum/ai_controller/controller, succeeded, target_key) . = ..() - controller.clear_blackboard_key(target_key) + if(!succeeded) + return + var/mob/living/living_pawn = controller.pawn + living_pawn.istate = initial(living_pawn.istate) diff --git a/code/datums/ai/babies/babies_subtrees.dm b/code/datums/ai/babies/babies_subtrees.dm index 0c71b6d757b0..8310cd955733 100644 --- a/code/datums/ai/babies/babies_subtrees.dm +++ b/code/datums/ai/babies/babies_subtrees.dm @@ -3,11 +3,19 @@ */ /datum/ai_planning_subtree/make_babies var/chance = 5 + operational_datums = list(/datum/component/breed) /datum/ai_planning_subtree/make_babies/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - if(controller.pawn.gender != FEMALE || !SPT_PROB(chance, seconds_per_tick)) + if(!SPT_PROB(chance, seconds_per_tick)) + return + + if(controller.blackboard_key_exists(BB_BABIES_TARGET)) + controller.queue_behavior(/datum/ai_behavior/make_babies, BB_BABIES_TARGET, BB_BABIES_CHILD_TYPES) + return SUBTREE_RETURN_FINISH_PLANNING + + if(controller.pawn.gender == FEMALE || !controller.blackboard[BB_BREED_READY]) return var/partner_types = controller.blackboard[BB_BABIES_PARTNER_TYPES] @@ -20,13 +28,5 @@ if(is_type_in_list(controller.pawn, baby_types)) return - var/atom/target = controller.blackboard[BB_BABIES_TARGET] - // Find target - if(QDELETED(target)) - controller.queue_behavior(/datum/ai_behavior/find_partner, BB_BABIES_TARGET, BB_BABIES_PARTNER_TYPES, BB_BABIES_CHILD_TYPES) - return - - // Do target - controller.queue_behavior(/datum/ai_behavior/make_babies, BB_BABIES_TARGET, BB_BABIES_CHILD_TYPES) - return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_partner, BB_BABIES_TARGET, BB_BABIES_PARTNER_TYPES, BB_BABIES_CHILD_TYPES) diff --git a/code/datums/ai/basic_mobs/base_basic_controller.dm b/code/datums/ai/basic_mobs/base_basic_controller.dm index b4f7c27c2f91..cd025b28bcb2 100644 --- a/code/datums/ai/basic_mobs/base_basic_controller.dm +++ b/code/datums/ai/basic_mobs/base_basic_controller.dm @@ -15,10 +15,16 @@ /datum/ai_controller/basic_controller/able_to_run() . = ..() - if(isliving(pawn)) - var/mob/living/living_pawn = pawn - if(IS_DEAD_OR_INCAP(living_pawn)) - return FALSE + if(!isliving(pawn)) + return + var/mob/living/living_pawn = pawn + var/incap_flags = NONE + if (ai_traits & CAN_ACT_IN_STASIS) + incap_flags |= IGNORE_STASIS + if(!(ai_traits & CAN_ACT_WHILE_DEAD) && (living_pawn.incapacitated(incap_flags) || living_pawn.stat)) + return FALSE + if(ai_traits & PAUSE_DURING_DO_AFTER && LAZYLEN(living_pawn.do_afters)) + return FALSE /datum/ai_controller/basic_controller/proc/update_speed(mob/living/basic/basic_mob) SIGNAL_HANDLER diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm index cfd53164bb21..15ddc7b264d7 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm @@ -1,11 +1,12 @@ /datum/ai_behavior/basic_melee_attack - action_cooldown = 2 SECONDS - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + action_cooldown = 0.2 SECONDS // We gotta check unfortunately often because we're in a race condition with nextmove + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + ///do we finish this action after hitting once? + var/terminate_after_action = FALSE -/datum/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) +/datum/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) . = ..() - var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] - if(isnull(targetting_datum)) + if(!controller.blackboard[targeting_strategy_key]) CRASH("No target datum was supplied in the blackboard for [controller.pawn]") //Hiding location is priority @@ -15,18 +16,23 @@ set_movement_target(controller, target) -/datum/ai_behavior/basic_melee_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) +/datum/ai_behavior/basic_melee_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) + if (isliving(controller.pawn)) + var/mob/living/pawn = controller.pawn + if (world.time < pawn.next_move) + return + . = ..() var/mob/living/basic/basic_mob = controller.pawn - //targetting datum will kill the action if not real anymore + //targeting strategy will kill the action if not real anymore var/atom/target = controller.blackboard[target_key] - var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] + var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if(!targetting_datum.can_attack(basic_mob, target)) + if(!targeting_strategy.can_attack(basic_mob, target)) finish_action(controller, FALSE, target_key) return - var/hiding_target = targetting_datum.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something! + var/hiding_target = targeting_strategy.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something! controller.set_blackboard_key(hiding_location_key, hiding_target) @@ -35,61 +41,111 @@ else basic_mob.melee_attack(target) + if(terminate_after_action) + finish_action(controller, TRUE, target_key) -/datum/ai_behavior/basic_melee_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targetting_datum_key, hiding_location_key) +/datum/ai_behavior/basic_melee_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) . = ..() if(!succeeded) controller.clear_blackboard_key(target_key) -/datum/ai_behavior/basic_melee_attack/average_speed - action_cooldown = 1 SECONDS +/datum/ai_behavior/basic_melee_attack/interact_once + terminate_after_action = TRUE + +/datum/ai_behavior/basic_melee_attack/interact_once/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) + . = ..() + controller.clear_blackboard_key(target_key) /datum/ai_behavior/basic_ranged_attack action_cooldown = 0.6 SECONDS behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM required_distance = 3 - /// How many shots to fire - var/shots = 1 - /// The interval between individual shots in a burst - var/burst_interval = 0.2 SECONDS + /// range we will try chasing the target before giving up + var/chase_range = 9 + ///do we care about avoiding friendly fire? + var/avoid_friendly_fire = FALSE -/datum/ai_behavior/basic_ranged_attack/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) +/datum/ai_behavior/basic_ranged_attack/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) . = ..() var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] if(QDELETED(target)) return FALSE set_movement_target(controller, target) -/datum/ai_behavior/basic_ranged_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) - . = ..() +/datum/ai_behavior/basic_ranged_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) var/mob/living/basic/basic_mob = controller.pawn - //targetting datum will kill the action if not real anymore + //targeting strategy will kill the action if not real anymore var/atom/target = controller.blackboard[target_key] - var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] + var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if(!targetting_datum.can_attack(basic_mob, target)) + if(!targeting_strategy.can_attack(basic_mob, target, chase_range)) finish_action(controller, FALSE, target_key) return - var/atom/hiding_target = targetting_datum.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something! + var/atom/hiding_target = targeting_strategy.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something! var/atom/final_target = hiding_target ? hiding_target : target if(!can_see(basic_mob, final_target, required_distance)) - finish_action(controller, FALSE, target_key) return - controller.set_blackboard_key(hiding_location_key, hiding_target) + if(avoid_friendly_fire && check_friendly_in_path(basic_mob, target, targeting_strategy)) + adjust_position(basic_mob, target) + return ..() - if(shots>1) - var/atom/burst_target = final_target - var/datum/callback/callback = CALLBACK(basic_mob, TYPE_PROC_REF(/mob/living/basic,RangedAttack), burst_target) - for(var/i in 2 to shots) - addtimer(callback, (i - 1) * burst_interval) - callback.Invoke() - else - basic_mob.RangedAttack(final_target) + controller.set_blackboard_key(hiding_location_key, hiding_target) + basic_mob.RangedAttack(final_target) + return ..() //only start the cooldown when the shot is shot -/datum/ai_behavior/basic_ranged_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targetting_datum_key, hiding_location_key) +/datum/ai_behavior/basic_ranged_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) . = ..() if(!succeeded) controller.clear_blackboard_key(target_key) + +/datum/ai_behavior/basic_ranged_attack/proc/check_friendly_in_path(mob/living/source, atom/target, datum/targeting_strategy/targeting_strategy) + var/list/turfs_list = calculate_trajectory(source, target) + for(var/turf/possible_turf as anything in turfs_list) + + for(var/mob/living/potential_friend in possible_turf) + if(!targeting_strategy.can_attack(source, potential_friend)) + return TRUE + + return FALSE + +/datum/ai_behavior/basic_ranged_attack/proc/adjust_position(mob/living/living_pawn, atom/target) + var/turf/our_turf = get_turf(living_pawn) + var/list/possible_turfs = list() + + for(var/direction in GLOB.alldirs) + var/turf/target_turf = get_step(our_turf, direction) + if(isnull(target_turf)) + continue + if(target_turf.is_blocked_turf() || get_dist(target_turf, target) > get_dist(living_pawn, target)) + continue + possible_turfs += target_turf + + if(!length(possible_turfs)) + return + var/turf/picked_turf = get_closest_atom(/turf, possible_turfs, target) + step(living_pawn, get_dir(living_pawn, picked_turf)) + +/datum/ai_behavior/basic_ranged_attack/proc/calculate_trajectory(mob/living/source , atom/target) + var/list/turf_list = get_line(source, target) + var/list_length = length(turf_list) - 1 + for(var/i in 1 to list_length) + var/turf/current_turf = turf_list[i] + var/turf/next_turf = turf_list[i+1] + var/direction_to_turf = get_dir(current_turf, next_turf) + if(!ISDIAGONALDIR(direction_to_turf)) + continue + + for(var/cardinal_direction in GLOB.cardinals) + if(cardinal_direction & direction_to_turf) + turf_list += get_step(current_turf, cardinal_direction) + + turf_list -= get_turf(source) + turf_list -= get_turf(target) + + return turf_list + +/datum/ai_behavior/basic_ranged_attack/avoid_friendly_fire + avoid_friendly_fire = TRUE diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/nearest_targetting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/nearest_targeting.dm similarity index 100% rename from code/datums/ai/basic_mobs/basic_ai_behaviors/nearest_targetting.dm rename to code/datums/ai/basic_mobs/basic_ai_behaviors/nearest_targeting.dm diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm index 7674c48e54ba..2e922d62f09a 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm @@ -4,7 +4,7 @@ action_cooldown = 0 behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION /// How far do we try to run? Further makes for smoother running, but potentially weirder pathfinding - var/run_distance = 9 + var/run_distance = DEFAULT_BASIC_FLEE_DISTANCE /// Clear target if we finish the action unsuccessfully var/clear_failed_targets = TRUE @@ -12,20 +12,22 @@ var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] if(QDELETED(target)) return FALSE + run_distance = controller.blackboard[BB_BASIC_MOB_FLEE_DISTANCE] || initial(run_distance) if(!plot_path_away_from(controller, target)) return FALSE return ..() /datum/ai_behavior/run_away_from_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) . = ..() + if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) + return var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] - var/escaped = QDELETED(target) || !can_see(controller.pawn, target, run_distance) // If we can't see it we got away - if (escaped) + if (QDELETED(target) || !can_see(controller.pawn, target, run_distance)) finish_action(controller, succeeded = TRUE, target_key = target_key, hiding_location_key = hiding_location_key) return if (get_dist(controller.pawn, controller.current_movement_target) > required_distance) - return - if(plot_path_away_from(controller, target)) + return // Still heading over + if (plot_path_away_from(controller, target)) return finish_action(controller, succeeded = FALSE, target_key = target_key, hiding_location_key = hiding_location_key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/set_travel_destination.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/set_travel_destination.dm new file mode 100644 index 000000000000..207df4424577 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/set_travel_destination.dm @@ -0,0 +1,13 @@ +/datum/ai_behavior/set_travel_destination + +/datum/ai_behavior/set_travel_destination/perform(seconds_per_tick, datum/ai_controller/controller, target_key, location_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + + if(QDELETED(target)) + finish_action(controller, FALSE, target_key) + return + + controller.set_blackboard_key(location_key, target) + + finish_action(controller, TRUE, target_key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm index ca1efe6f169f..a39200d0cc16 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm @@ -11,7 +11,8 @@ finish_action(controller, FALSE, ability_key, target_key) return var/mob/pawn = controller.pawn - var/result = ability.InterceptClickOn(pawn, null, target) + pawn.face_atom(target) + var/result = ability.Trigger(target = target) finish_action(controller, result, ability_key, target_key) /datum/ai_behavior/targeted_mob_ability/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) @@ -19,12 +20,6 @@ var/atom/target = controller.blackboard[target_key] if (QDELETED(target)) controller.clear_blackboard_key(target_key) - return - if (!isliving(target)) - return - var/mob/living/living_target = target - if(living_target.stat >= UNCONSCIOUS) - controller.clear_blackboard_key(target_key) /** * # Try Mob Ability and plan execute diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm similarity index 65% rename from code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm rename to code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm index 9d7587c712b3..28f89bcf2974 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm @@ -2,27 +2,31 @@ action_cooldown = 2 SECONDS /// How far can we see stuff? var/vision_range = 9 + /// Blackboard key for aggro range, uses vision range if not specified + var/aggro_range_key = BB_AGGRO_RANGE /// Static typecache list of potentially dangerous objs var/static/list/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/vehicle/sealed/mecha)) -/datum/ai_behavior/find_potential_targets/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) +/datum/ai_behavior/find_potential_targets/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) . = ..() var/mob/living/living_mob = controller.pawn - var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] + var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if(!targetting_datum) + if(!targeting_strategy) CRASH("No target datum was supplied in the blackboard for [controller.pawn]") var/atom/current_target = controller.blackboard[target_key] - if (targetting_datum.can_attack(living_mob, current_target, vision_range)) + if (targeting_strategy.can_attack(living_mob, current_target, vision_range)) finish_action(controller, succeeded = FALSE) return + var/aggro_range = controller.blackboard[aggro_range_key] || vision_range + controller.clear_blackboard_key(target_key) - var/list/potential_targets = hearers(vision_range, controller.pawn) - living_mob //Remove self, so we don't suicide + var/list/potential_targets = hearers(aggro_range, controller.pawn) - living_mob //Remove self, so we don't suicide - for(var/HM in typecache_filter_list(range(vision_range, living_mob), hostile_machines)) //Can we see any hostile machines? - if(can_see(living_mob, HM, vision_range)) + for(var/HM in typecache_filter_list(range(aggro_range, living_mob), hostile_machines)) //Can we see any hostile machines? + if(can_see(living_mob, HM, aggro_range)) potential_targets += HM if(!potential_targets.len) @@ -32,7 +36,7 @@ var/list/filtered_targets = list() for(var/atom/pot_target in potential_targets) - if(targetting_datum.can_attack(living_mob, pot_target))//Can we attack it? + if(targeting_strategy.can_attack(living_mob, pot_target))//Can we attack it? filtered_targets += pot_target continue @@ -43,7 +47,7 @@ var/atom/target = pick_final_target(controller, filtered_targets) controller.set_blackboard_key(target_key, target) - var/atom/potential_hiding_location = targetting_datum.find_hidden_mobs(living_mob, target) + var/atom/potential_hiding_location = targeting_strategy.find_hidden_mobs(living_mob, target) if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially. controller.set_blackboard_key(hiding_location_key, potential_hiding_location) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/travel_towards.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/travel_towards.dm index bbc1a43e3224..55f6ef4c4c00 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/travel_towards.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/travel_towards.dm @@ -6,6 +6,8 @@ /datum/ai_behavior/travel_towards required_distance = 0 behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + /// If true we will get rid of our target on completion + var/clear_target = FALSE /datum/ai_behavior/travel_towards/setup(datum/ai_controller/controller, target_key) . = ..() @@ -16,7 +18,15 @@ /datum/ai_behavior/travel_towards/perform(seconds_per_tick, datum/ai_controller/controller, target_key) . = ..() - finish_action(controller, TRUE) + finish_action(controller, TRUE, target_key) + +/datum/ai_behavior/travel_towards/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + if (clear_target) + controller.clear_blackboard_key(target_key) + +/datum/ai_behavior/travel_towards/stop_on_arrival + clear_target = TRUE /** * # Travel Towards Atom diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/unbuckle_mob.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/unbuckle_mob.dm new file mode 100644 index 000000000000..d0fb7e3e8c70 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/unbuckle_mob.dm @@ -0,0 +1,14 @@ +/datum/ai_behavior/unbuckle_mob + +/datum/ai_behavior/unbuckle_mob/perform(seconds_per_tick, datum/ai_controller/controller) + . = ..() + + var/mob/living/living_pawn = controller.pawn + var/atom/movable/buckled_too = living_pawn.buckled + + if(isnull(buckled_too)) + finish_action(controller, FALSE) + return + + buckled_too.unbuckle_mob(living_pawn) + finish_action(controller, TRUE) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm index 5ec5f24066cd..e162cc612990 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm @@ -18,7 +18,7 @@ . = ..() var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET] var/mob/living/cached_pawn = controller.pawn - if(HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING) || !controller.blackboard[BB_CURRENTLY_TARGETTING_VENT] || !is_vent_valid(entry_vent)) + if(HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING) || !controller.blackboard[BB_CURRENTLY_TARGETING_VENT] || !is_vent_valid(entry_vent)) return if(!cached_pawn.can_enter_vent(entry_vent, provide_feedback = FALSE)) // we're an AI we scoff at feedback @@ -30,7 +30,7 @@ finish_action(controller, FALSE, target_key) return - controller.set_blackboard_key(BB_CURRENTLY_TARGETTING_VENT, FALSE) // must be done here because we have a do_after sleep in handle_ventcrawl unfortunately and double dipping could lead to erroneous suicide pill calls. + controller.set_blackboard_key(BB_CURRENTLY_TARGETING_VENT, FALSE) // must be done here because we have a do_after sleep in handle_ventcrawl unfortunately and double dipping could lead to erroneous suicide pill calls. cached_pawn.handle_ventcrawl(entry_vent) if(!HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) //something failed and we ARE NOT IN THE VENT even though the earlier check said we were good to go! odd. finish_action(controller, FALSE, target_key) @@ -134,4 +134,5 @@ controller.clear_blackboard_key(target_key) controller.clear_blackboard_key(BB_ENTRY_VENT_TARGET) controller.clear_blackboard_key(BB_EXIT_VENT_TARGET) - controller.set_blackboard_key(BB_CURRENTLY_TARGETTING_VENT, FALSE) // just in case + controller.set_blackboard_key(BB_CURRENTLY_TARGETING_VENT, FALSE) // just in case + diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/wounded_targeting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/wounded_targeting.dm new file mode 100644 index 000000000000..46037fdc076e --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/wounded_targeting.dm @@ -0,0 +1,11 @@ +/// Picks targets based on which one has the lowest health +/datum/ai_behavior/find_potential_targets/most_wounded + +/datum/ai_behavior/find_potential_targets/most_wounded/pick_final_target(datum/ai_controller/controller, list/filtered_targets) + var/list/living_targets = list() + for(var/mob/living/living_target in filtered_targets) + living_targets += filtered_targets + if(living_targets.len) + sortTim(living_targets, GLOBAL_PROC_REF(cmp_mob_health)) + return pop(living_targets) + return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm new file mode 100644 index 000000000000..411690043546 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm @@ -0,0 +1,33 @@ +/// Attack something which is already adjacent to us, without ending planning +/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic + melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/opportunistic + end_planning = FALSE + +/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if(QDELETED(target) || !controller.pawn.Adjacent(target)) + return + if (isliving(controller.pawn)) + var/mob/living/pawn = controller.pawn + if (LAZYLEN(pawn.do_afters)) + return + controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + +/// Attack something which is already adjacent to us without moving +/datum/ai_behavior/basic_melee_attack/opportunistic + action_cooldown = 0.2 SECONDS // We gotta check unfortunately often because we're in a race condition with nextmove + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/basic_melee_attack/opportunistic/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) + if (!controller.blackboard_key_exists(targeting_strategy_key)) + CRASH("No target datum was supplied in the blackboard for [controller.pawn]") + return controller.blackboard_key_exists(target_key) + +/datum/ai_behavior/basic_melee_attack/opportunistic/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) + var/atom/movable/atom_pawn = controller.pawn + if(!atom_pawn.CanReach(controller.blackboard[target_key])) + finish_action(controller, TRUE, target_key) // Don't clear target + return FALSE + . = ..() + finish_action(controller, TRUE, target_key) // Try doing something else diff --git a/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm b/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm index 9f1053f29d3f..7059bec93fe2 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm @@ -74,7 +74,7 @@ return FALSE if (basic_mob.see_invisible < object.invisibility) return FALSE - var/list/whitelist = basic_mob.ai_controller.blackboard[BB_OBSTACLE_TARGETTING_WHITELIST] + var/list/whitelist = basic_mob.ai_controller.blackboard[BB_OBSTACLE_TARGETING_WHITELIST] if(whitelist && !is_type_in_typecache(object, whitelist)) return FALSE diff --git a/code/datums/ai/basic_mobs/basic_subtrees/call_reinforcements.dm b/code/datums/ai/basic_mobs/basic_subtrees/call_reinforcements.dm new file mode 100644 index 000000000000..59ff88b4879b --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/call_reinforcements.dm @@ -0,0 +1,50 @@ +#define REINFORCEMENTS_COOLDOWN (30 SECONDS) + +/// Calls all nearby mobs that share a faction to give backup in combat +/datum/ai_planning_subtree/call_reinforcements + /// Blackboard key containing something to say when calling reinforcements (takes precedence over emotes) + var/say_key = BB_REINFORCEMENTS_SAY + /// Blackboard key containing an emote to perform when calling reinforcements + var/emote_key = BB_REINFORCEMENTS_EMOTE + /// Reinforcement-calling behavior to use + var/call_type = /datum/ai_behavior/call_reinforcements + +/datum/ai_planning_subtree/call_reinforcements/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + if (!decide_to_call(controller) || controller.blackboard[BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN] > world.time) + return + + var/call_say = controller.blackboard[BB_REINFORCEMENTS_SAY] + var/call_emote = controller.blackboard[BB_REINFORCEMENTS_EMOTE] + + if(!isnull(call_say)) + controller.queue_behavior(/datum/ai_behavior/perform_speech, call_say) + else if(!isnull(call_emote)) + controller.queue_behavior(/datum/ai_behavior/perform_emote, call_emote) + else + controller.queue_behavior(/datum/ai_behavior/perform_emote, "cries for help!") + + controller.queue_behavior(call_type) + +/// Decides when to call reinforcements, can be overridden for alternate behavior +/datum/ai_planning_subtree/call_reinforcements/proc/decide_to_call(datum/ai_controller/controller) + return controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) && istype(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET], /mob) + +/// Call out to all mobs in the specified range for help +/datum/ai_behavior/call_reinforcements + /// Range to call reinforcements from + var/reinforcements_range = 15 + +/datum/ai_behavior/call_reinforcements/perform(seconds_per_tick, datum/ai_controller/controller) + . = ..() + + var/mob/pawn_mob = controller.pawn + for(var/mob/other_mob in oview(reinforcements_range, pawn_mob)) + if(pawn_mob.faction_check_atom(other_mob) && !isnull(other_mob.ai_controller)) + // Add our current target to their retaliate list so that they'll attack our aggressor + other_mob.ai_controller.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) + other_mob.ai_controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENT_TARGET, pawn_mob) + + controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN, world.time + REINFORCEMENTS_COOLDOWN) + +#undef REINFORCEMENTS_COOLDOWN diff --git a/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm b/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm new file mode 100644 index 000000000000..4d5319bca86e --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm @@ -0,0 +1,69 @@ +/// Add or remove people to our retaliation shitlist just on an arbitrary whim +/datum/ai_planning_subtree/capricious_retaliate + /// Blackboard key which tells us how to select valid targets + var/targeting_strategy_key = BB_TARGETING_STRATEGY + /// Whether we should skip checking faction for our decision + var/ignore_faction = TRUE + +/datum/ai_planning_subtree/capricious_retaliate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + controller.queue_behavior(/datum/ai_behavior/capricious_retaliate, targeting_strategy_key, ignore_faction) + +/// Add or remove people to our retaliation shitlist just on an arbitrary whim +/datum/ai_behavior/capricious_retaliate + action_cooldown = 1 SECONDS + +/datum/ai_behavior/capricious_retaliate/perform(seconds_per_tick, datum/ai_controller/controller, targeting_strategy_key, ignore_faction) + . = ..() + var/atom/pawn = controller.pawn + if (controller.blackboard_key_exists(BB_BASIC_MOB_RETALIATE_LIST)) + var/deaggro_chance = controller.blackboard[BB_RANDOM_DEAGGRO_CHANCE] || 10 + if (!SPT_PROB(deaggro_chance, seconds_per_tick)) + finish_action(controller, TRUE, ignore_faction) // "true" here means "don't clear our ignoring factions status" + return + pawn.visible_message(span_notice("[pawn] calms down.")) // We can blackboard key this if anyone else actually wants to customise it + controller.clear_blackboard_key(BB_BASIC_MOB_RETALIATE_LIST) + finish_action(controller, FALSE, ignore_faction) + controller.CancelActions() // Otherwise they will try and get one last kick in + return + + var/aggro_chance = controller.blackboard[BB_RANDOM_AGGRO_CHANCE] || 0.5 + if (!SPT_PROB(aggro_chance, seconds_per_tick)) + finish_action(controller, FALSE, ignore_faction) + return + + var/aggro_range = controller.blackboard[BB_AGGRO_RANGE] || 9 + var/list/potential_targets = hearers(aggro_range, get_turf(pawn)) - pawn + if (!length(potential_targets)) + failed_targeting(controller, pawn, ignore_faction) + return + + var/datum/targeting_strategy/target_helper = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) + + var/mob/living/final_target = null + if (ignore_faction) + controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, TRUE) + while (isnull(final_target) && length(potential_targets)) + var/mob/living/test_target = pick_n_take(potential_targets) + if (target_helper.can_attack(pawn, test_target, vision_range = aggro_range)) + final_target = test_target + + if (isnull(final_target)) + failed_targeting(controller, pawn, ignore_faction) + return + + controller.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, final_target) + pawn.visible_message(span_warning("[pawn] glares grumpily at [final_target]!")) + finish_action(controller, TRUE, ignore_faction) + +/// Called if we try but fail to target something +/datum/ai_behavior/capricious_retaliate/proc/failed_targeting(datum/ai_controller/controller, atom/pawn, ignore_faction) + finish_action(controller, FALSE, ignore_faction) + pawn.visible_message(span_notice("[pawn] grumbles.")) // We're pissed off but with no outlet to vent our frustration upon + +/datum/ai_behavior/capricious_retaliate/finish_action(datum/ai_controller/controller, succeeded, ignore_faction) + . = ..() + if (succeeded || !ignore_faction) + return + var/usually_ignores_faction = controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || FALSE + controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, usually_ignores_faction) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm b/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm index 6827f3eee993..bad349030f1b 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm @@ -1,4 +1,5 @@ /datum/ai_planning_subtree/climb_trees + operational_datums = list(/datum/component/tree_climber) ///chance to climb a tree var/climb_chance = 35 @@ -7,11 +8,8 @@ if(!SPT_PROB(climb_chance, seconds_per_tick)) return - var/obj/structure/flora/tree/target = controller.blackboard[BB_CLIMBED_TREE] + if(controller.blackboard_key_exists(BB_CLIMBED_TREE)) + controller.queue_behavior(/datum/ai_behavior/climb_tree, BB_CLIMBED_TREE) + return SUBTREE_RETURN_FINISH_PLANNING - if(QDELETED(target)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_tree, BB_CLIMBED_TREE, /obj/structure/flora/tree) - return - - controller.queue_behavior(/datum/ai_behavior/climb_tree, BB_CLIMBED_TREE) - return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_tree, BB_CLIMBED_TREE, /obj/structure/flora/tree) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm b/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm index 8d20df7b9c79..b02ec8eaa85a 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm @@ -3,8 +3,7 @@ /datum/ai_planning_subtree/find_food/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(!QDELETED(target)) + if(controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) // Busy with something return diff --git a/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.dm b/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.dm index 900d13e0a046..0d2d0c3e3b44 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.dm @@ -1,10 +1,9 @@ /datum/ai_planning_subtree/find_paper_and_write /datum/ai_planning_subtree/find_paper_and_write/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/obj/item/inhand_paper = controller.blackboard[BB_SIMPLE_CARRY_ITEM] var/mob/living/basic/wizard = controller.pawn - if(!QDELETED(inhand_paper)) + if(controller.blackboard_key_exists(BB_SIMPLE_CARRY_ITEM)) controller.queue_behavior(/datum/ai_behavior/write_on_paper, BB_SIMPLE_CARRY_ITEM, BB_WRITING_LIST) return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm index 6ae7885618b9..4a2f5b476c75 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm @@ -9,16 +9,18 @@ /datum/ai_planning_subtree/flee_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - if (!controller.blackboard[BB_BASIC_MOB_FLEEING]) + var/atom/flee_from = controller.blackboard[target_key] + if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING] || QDELETED(flee_from)) return - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) + var/flee_distance = controller.blackboard[BB_BASIC_MOB_FLEE_DISTANCE] || DEFAULT_BASIC_FLEE_DISTANCE + if (get_dist(controller.pawn, flee_from) >= flee_distance) return + controller.queue_behavior(flee_behaviour, target_key, hiding_place_key) return SUBTREE_RETURN_FINISH_PLANNING //we gotta get out of here. /// Try to escape from your current target, without performing any other actions. -/// Reads from some fleeing-specific targetting keys rather than the current mob target. +/// Reads from some fleeing-specific targeting keys rather than the current mob target. /datum/ai_planning_subtree/flee_target/from_flee_key target_key = BB_BASIC_MOB_FLEE_TARGET hiding_place_key = BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION diff --git a/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm b/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm index b80a28836a06..2a85e9e902b2 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm @@ -3,11 +3,13 @@ /// Blackboard key holding atom we want to stay away from var/target_key = BB_BASIC_MOB_CURRENT_TARGET /// How close will we allow our target to get? - var/minimum_distance = 3 + var/minimum_distance = 4 /// How far away will we allow our target to get? var/maximum_distance = 6 /// How far do we look for our target? var/view_distance = 10 + /// the run away behavior we will use + var/run_away_behavior = /datum/ai_behavior/step_away /datum/ai_planning_subtree/maintain_distance/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() @@ -16,12 +18,15 @@ return // Don't run away from cucumbers, they're not snakes var/range = get_dist(controller.pawn, target) if (range < minimum_distance) - controller.queue_behavior(/datum/ai_behavior/step_away, target_key) + controller.queue_behavior(run_away_behavior, target_key, minimum_distance) return if (range > maximum_distance) controller.queue_behavior(/datum/ai_behavior/pursue_to_range, target_key, maximum_distance) return +/datum/ai_planning_subtree/maintain_distance/cover_minimum_distance + run_away_behavior = /datum/ai_behavior/cover_minimum_distance + /// Take one step away /datum/ai_behavior/step_away behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION @@ -80,3 +85,32 @@ if (!QDELETED(current_target) && get_dist(controller.pawn, current_target) > range) return finish_action(controller, succeeded = TRUE) + +///instead of taking a single step, we cover the entire distance +/datum/ai_behavior/cover_minimum_distance + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + required_distance = 0 + action_cooldown = 0.2 SECONDS + +/datum/ai_behavior/cover_minimum_distance/setup(datum/ai_controller/controller, target_key, minimum_distance) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + var/required_distance = minimum_distance - get_dist(controller.pawn, target) //the distance we need to move + var/distance = 0 + var/turf/chosen_turf + for(var/turf/open/potential_turf in oview(required_distance, controller.pawn)) + var/new_distance_from_target = get_dist(potential_turf, target) + if(potential_turf.is_blocked_turf()) + continue + if(new_distance_from_target > distance) + chosen_turf = potential_turf + distance = new_distance_from_target + if(isnull(chosen_turf)) + return FALSE + set_movement_target(controller, target = chosen_turf) + +/datum/ai_behavior/cover_minimum_distance/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + finish_action(controller, succeeded = TRUE) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm b/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm new file mode 100644 index 000000000000..3c03702b6994 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm @@ -0,0 +1,73 @@ +//behavior to find mineable mineral walls + +/datum/ai_planning_subtree/mine_walls + var/find_wall_behavior = /datum/ai_behavior/find_mineral_wall + +/datum/ai_planning_subtree/mine_walls/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_TARGET_MINERAL_WALL)) + controller.queue_behavior(/datum/ai_behavior/mine_wall, BB_TARGET_MINERAL_WALL) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(find_wall_behavior, BB_TARGET_MINERAL_WALL) + +/datum/ai_behavior/mine_wall + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + action_cooldown = 15 SECONDS + +/datum/ai_behavior/mine_wall/setup(datum/ai_controller/controller, target_key) + . = ..() + var/turf/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/mine_wall/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/basic/living_pawn = controller.pawn + var/turf/closed/mineral/target = controller.blackboard[target_key] + var/is_gibtonite_turf = istype(target, /turf/closed/mineral/gibtonite) + if(QDELETED(target)) + finish_action(controller, FALSE, target_key) + return + living_pawn.melee_attack(target) + if(is_gibtonite_turf) + living_pawn.manual_emote("sighs...") //accept whats about to happen to us + + finish_action(controller, TRUE, target_key) + return + +/datum/ai_behavior/mine_wall/finish_action(datum/ai_controller/controller, success, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + +/datum/ai_behavior/find_mineral_wall + +/datum/ai_behavior/find_mineral_wall/perform(seconds_per_tick, datum/ai_controller/controller, found_wall_key) + . = ..() + var/mob/living_pawn = controller.pawn + + for(var/turf/closed/mineral/potential_wall in oview(9, living_pawn)) + if(!check_if_mineable(controller, potential_wall)) //check if its surrounded by walls + continue + controller.set_blackboard_key(found_wall_key, potential_wall) //closest wall first! + finish_action(controller, TRUE) + return + + finish_action(controller, FALSE) + +/datum/ai_behavior/find_mineral_wall/proc/check_if_mineable(datum/ai_controller/controller, turf/target_wall) + var/mob/living/source = controller.pawn + var/direction_to_turf = get_dir(target_wall, source) + if(!ISDIAGONALDIR(direction_to_turf)) + return TRUE + var/list/directions_to_check = list() + for(var/direction_check in GLOB.cardinals) + if(direction_check & direction_to_turf) + directions_to_check += direction_check + + for(var/direction in directions_to_check) + var/turf/test_turf = get_step(target_wall, direction) + if(isnull(test_turf)) + continue + if(!test_turf.is_blocked_turf(ignore_atoms = list(source))) + return TRUE + return FALSE diff --git a/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm b/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm new file mode 100644 index 000000000000..c98878e0fd71 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm @@ -0,0 +1,71 @@ +/// Try to line up with a cardinal direction of your target +/datum/ai_planning_subtree/move_to_cardinal + /// Behaviour to execute to line ourselves up + var/move_behaviour = /datum/ai_behavior/move_to_cardinal + /// Blackboard key in which to store selected target + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + +/datum/ai_planning_subtree/move_to_cardinal/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + if(!controller.blackboard_key_exists(target_key)) + return + controller.queue_behavior(move_behaviour, target_key) + +/// Try to line up with a cardinal direction of your target +/datum/ai_behavior/move_to_cardinal + required_distance = 0 + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + /// How close to our target is too close? + var/minimum_distance = 1 + /// How far away is too far? + var/maximum_distance = 9 + +/datum/ai_behavior/move_to_cardinal/setup(datum/ai_controller/controller, target_key) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + target_nearest_cardinal(controller, target) + return TRUE + +/// Set our movement target to the closest cardinal space to our target +/datum/ai_behavior/move_to_cardinal/proc/target_nearest_cardinal(datum/ai_controller/controller, atom/target) + var/atom/move_target + var/closest = INFINITY + + for (var/dir in GLOB.cardinals) + var/turf/cardinal_turf = get_ranged_target_turf(target, dir, minimum_distance) + if (cardinal_turf.is_blocked_turf()) + continue + var/distance_to = get_dist(controller.pawn, cardinal_turf) + if (distance_to >= closest) + continue + closest = distance_to + move_target = cardinal_turf + + if (isnull(move_target)) + move_target = target + if (controller.current_movement_target == move_target) + return + set_movement_target(controller, move_target) + +/datum/ai_behavior/move_to_cardinal/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + var/atom/target = controller.blackboard[target_key] + if (QDELETED(target)) + finish_action(controller = controller, succeeded = FALSE, target_key = target_key) + return + if (!(get_dir(controller.pawn, target) in GLOB.cardinals)) + target_nearest_cardinal(controller, target) + return + var/distance_to_target = get_dist(controller.pawn, target) + if (distance_to_target < minimum_distance) + target_nearest_cardinal(controller, target) + return + if (distance_to_target > maximum_distance) + return + finish_action(controller = controller, succeeded = TRUE, target_key = target_key) + return + +/datum/ai_behavior/move_to_cardinal/finish_action(datum/ai_controller/controller, succeeded, target_key) + if (!succeeded) + controller.clear_blackboard_key(target_key) + return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm b/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm index 63a745305b20..240272d1ef48 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm @@ -15,6 +15,6 @@ controller.queue_behavior(/datum/ai_behavior/travel_towards, BB_ENTRY_VENT_TARGET) return - controller.set_blackboard_key(BB_CURRENTLY_TARGETTING_VENT, TRUE) + controller.set_blackboard_key(BB_CURRENTLY_TARGETING_VENT, TRUE) controller.queue_behavior(/datum/ai_behavior/crawl_through_vents, BB_ENTRY_VENT_TARGET) return SUBTREE_RETURN_FINISH_PLANNING // we are going into this vent... no distractions diff --git a/code/datums/ai/basic_mobs/basic_subtrees/prepare_travel_to_destination.dm b/code/datums/ai/basic_mobs/basic_subtrees/prepare_travel_to_destination.dm new file mode 100644 index 000000000000..2718ca630ff7 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/prepare_travel_to_destination.dm @@ -0,0 +1,23 @@ + +///Subtree that checks if we are on the target atom's tile, and sets it as a travel target if not +///The target is taken from the blackboard. This one always requires a specific implementation. +/datum/ai_planning_subtree/prepare_travel_to_destination + var/target_key + var/travel_destination_key = BB_TRAVEL_DESTINATION + +/datum/ai_planning_subtree/prepare_travel_to_destination/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/atom/target = controller.blackboard[target_key] + + //Target is deleted, or we are already standing on it + if(QDELETED(target) || (isturf(target) && controller.pawn.loc == target) || (target.loc == controller.pawn.loc)) + return + + //Already set with this value, return + if(controller.blackboard[target_key] == controller.blackboard[travel_destination_key]) + return + + controller.queue_behavior(/datum/ai_behavior/set_travel_destination, target_key, travel_destination_key) + return //continue planning regardless of success + +/datum/ai_planning_subtree/prepare_travel_to_destination/trader + target_key = BB_SHOP_SPOT diff --git a/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm index 07fb6f8b0dcd..95a125eea5ce 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm @@ -4,41 +4,40 @@ /// Blackboard key holding target atom var/target_key = BB_BASIC_MOB_CURRENT_TARGET /// What AI behaviour do we actually run? - var/datum/ai_behavior/ranged_skirmish/attack_behavior = /datum/ai_behavior/ranged_skirmish + var/attack_behavior = /datum/ai_behavior/ranged_skirmish + /// If target is further away than this we don't fire + var/max_range = 9 + /// If target is closer than this we don't fire + var/min_range = 2 /datum/ai_planning_subtree/ranged_skirmish/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) + if(!controller.blackboard_key_exists(target_key)) return - controller.queue_behavior(attack_behavior, target_key, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + controller.queue_behavior(attack_behavior, target_key, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION, max_range, min_range) /// How often will we try to perform our ranged attack? /datum/ai_behavior/ranged_skirmish action_cooldown = 1 SECONDS - /// If target is further away than this we don't fire - var/max_range = 9 - /// If target is closer than this we don't fire - var/min_range = 2 -/datum/ai_behavior/ranged_skirmish/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) +/datum/ai_behavior/ranged_skirmish/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, max_range, min_range) . = ..() var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] return !QDELETED(target) -/datum/ai_behavior/ranged_skirmish/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) +/datum/ai_behavior/ranged_skirmish/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, max_range, min_range) . = ..() var/atom/target = controller.blackboard[target_key] if (QDELETED(target)) finish_action(controller, succeeded = FALSE) return - var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] - if(!targetting_datum.can_attack(controller.pawn, target)) + var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) + if(!targeting_strategy.can_attack(controller.pawn, target)) finish_action(controller, succeeded = FALSE) return - var/hiding_target = targetting_datum.find_hidden_mobs(controller.pawn, target) + var/hiding_target = targeting_strategy.find_hidden_mobs(controller.pawn, target) controller.set_blackboard_key(hiding_location_key, hiding_target) target = hiding_target || target diff --git a/code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm b/code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm new file mode 100644 index 000000000000..6f2f5cdc2035 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm @@ -0,0 +1,33 @@ +/// Intermittently run an emote +/datum/ai_planning_subtree/run_emote + var/emote_key = BB_EMOTE_KEY + var/emote_chance_key = BB_EMOTE_CHANCE + +/datum/ai_planning_subtree/run_emote/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/emote_chance = controller.blackboard[emote_chance_key] || 0 + if (!SPT_PROB(emote_chance, seconds_per_tick)) + return + controller.queue_behavior(/datum/ai_behavior/run_emote, emote_key) + +/// Emote from a blackboard key +/datum/ai_behavior/run_emote + +/datum/ai_behavior/run_emote/perform(seconds_per_tick, datum/ai_controller/controller, emote_key) + var/mob/living/living_pawn = controller.pawn + if (!isliving(living_pawn)) + finish_action(controller, FALSE) + return + + var/list/emote_list = controller.blackboard[emote_key] + var/emote + if (islist(emote_list)) + emote = length(emote_list) ? pick(emote_list) : null + else + emote = emote_list + + if(isnull(emote)) + finish_action(controller, FALSE) + return + + living_pawn.emote(emote) + finish_action(controller, TRUE) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm b/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm new file mode 100644 index 000000000000..ff01eb804ff7 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm @@ -0,0 +1,41 @@ +/// Shapeshift when we have no target, until someone has been nearby for long enough +/datum/ai_planning_subtree/shapechange_ambush + operational_datums = list(/datum/component/ai_target_timer) + /// Key where we keep our ability + var/ability_key = BB_SHAPESHIFT_ACTION + /// Key where we keep our target + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + /// How long to lull our target into a false sense of security + var/minimum_target_time = 8 SECONDS + +/datum/ai_planning_subtree/shapechange_ambush/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + var/is_shifted = ismob(living_pawn.loc) + var/has_target = controller.blackboard_key_exists(target_key) + var/datum/action/cooldown/using_action = controller.blackboard[ability_key] + + if (!is_shifted) + if (has_target) + return // We're busy + + if (using_action?.IsAvailable()) + controller.queue_behavior(/datum/ai_behavior/use_mob_ability/shapeshift, BB_SHAPESHIFT_ACTION) // Shift + return SUBTREE_RETURN_FINISH_PLANNING + + if (!has_target || !using_action?.IsAvailable()) + return SUBTREE_RETURN_FINISH_PLANNING // Lie in wait + var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 + if (time_on_target < minimum_target_time) + return // Wait a bit longer + controller.queue_behavior(/datum/ai_behavior/use_mob_ability/shapeshift, BB_SHAPESHIFT_ACTION) // Surprise! + +/// Selects a random shapeshift ability before shifting +/datum/ai_behavior/use_mob_ability/shapeshift + +/datum/ai_behavior/use_mob_ability/shapeshift/setup(datum/ai_controller/controller, ability_key) + var/datum/action/cooldown/spell/shapeshift/using_action = controller.blackboard[ability_key] + if (!using_action?.IsAvailable()) + return FALSE + if (isnull(using_action.shapeshift_type)) // If we don't have a shape then pick one, AI can't use context wheels + using_action.shapeshift_type = pick(using_action.possible_shapes) + return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm index 90379fcac0a7..5b1f5ffbff9e 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm @@ -1,16 +1,16 @@ /datum/ai_planning_subtree/basic_melee_attack_subtree + /// What do we do in order to attack someone? var/datum/ai_behavior/basic_melee_attack/melee_attack_behavior = /datum/ai_behavior/basic_melee_attack + /// Is this the last thing we do? (if we set a movement target, this will usually be yes) + var/end_planning = TRUE /datum/ai_planning_subtree/basic_melee_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(target)) + if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) return - controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions. - -/datum/ai_planning_subtree/basic_melee_attack_subtree/average_speed - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/average_speed + controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + if (end_planning) + return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions. /datum/ai_planning_subtree/basic_ranged_attack_subtree operational_datums = list(/datum/component/ranged_attacks) @@ -18,8 +18,7 @@ /datum/ai_planning_subtree/basic_ranged_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(target)) + if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) return - controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions. diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm index 42a361c25cd8..6630f7d193d9 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm @@ -3,19 +3,19 @@ /datum/ai_planning_subtree/simple_find_nearest_target_to_flee/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - if (!controller.blackboard[BB_BASIC_MOB_FLEEING]) + if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) return - controller.queue_behavior(/datum/ai_behavior/find_potential_targets/nearest, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + controller.queue_behavior(/datum/ai_behavior/find_potential_targets/nearest, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) /// Find the nearest thing on our list of 'things which have done damage to me' and set it as the flee target /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee - var/targeting_key = BB_TARGETTING_DATUM + var/targeting_key = BB_TARGETING_STRATEGY /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - if (!controller.blackboard[BB_BASIC_MOB_FLEEING]) + if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) return controller.queue_behavior(/datum/ai_behavior/target_from_retaliate_list/nearest, BB_BASIC_MOB_RETALIATE_LIST, BB_BASIC_MOB_CURRENT_TARGET, targeting_key, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/from_flee_key - targeting_key = BB_FLEE_TARGETTING_DATUM + targeting_key = BB_FLEE_TARGETING_STRATEGY diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm index ec4ef1863adc..1c7d8de9120b 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm @@ -2,7 +2,7 @@ /datum/ai_planning_subtree/simple_find_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - controller.queue_behavior(/datum/ai_behavior/find_potential_targets, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + controller.queue_behavior(/datum/ai_behavior/find_potential_targets, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) // Prevents finding a target if a human is nearby /datum/ai_planning_subtree/simple_find_target/not_while_observed diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_wounded_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_wounded_target.dm new file mode 100644 index 000000000000..7a230014d9ab --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_wounded_target.dm @@ -0,0 +1,6 @@ +/// Selects the most wounded potential target that we can see +/datum/ai_planning_subtree/simple_find_wounded_target + +/datum/ai_planning_subtree/simple_find_wounded_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + controller.queue_behavior(/datum/ai_behavior/find_potential_targets/most_wounded, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm index ccd67d6d1e72..93499cf673c4 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm @@ -16,7 +16,7 @@ /datum/ai_behavior/sleep_after_targetless_time/perform(seconds_per_tick, datum/ai_controller/controller, target_key) var/atom/target = controller.blackboard[target_key] - finish_action(controller, succeeded = !target, seconds_per_tick = seconds_per_tick) + finish_action(controller, succeeded = QDELETED(target), seconds_per_tick = seconds_per_tick) /datum/ai_behavior/sleep_after_targetless_time/finish_action(datum/ai_controller/controller, succeeded, seconds_per_tick) . = ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm index a201191d31c3..8cebe0e9f149 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm @@ -7,32 +7,42 @@ var/list/emote_see = list() ///Possible lines of speech the AI can have var/list/speak = list() + ///The sound effects associated with this speech, if any + var/list/sound = list() /datum/ai_planning_subtree/random_speech/New() . = ..() if(speak) speak = string_list(speak) + if(sound) + sound = string_list(sound) if(emote_hear) emote_hear = string_list(emote_hear) if(emote_see) emote_see = string_list(emote_see) /datum/ai_planning_subtree/random_speech/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(SPT_PROB(speech_chance, seconds_per_tick)) - var/audible_emotes_length = emote_hear?.len - var/non_audible_emotes_length = emote_see?.len - var/speak_lines_length = speak?.len + if(!SPT_PROB(speech_chance, seconds_per_tick)) + return + speak(controller) + +/// Actually perform an action +/datum/ai_planning_subtree/random_speech/proc/speak(datum/ai_controller/controller) + var/audible_emotes_length = emote_hear?.len + var/non_audible_emotes_length = emote_see?.len + var/speak_lines_length = speak?.len - var/total_choices_length = audible_emotes_length + non_audible_emotes_length + speak_lines_length + var/total_choices_length = audible_emotes_length + non_audible_emotes_length + speak_lines_length - var/random_number_in_range = rand(1, total_choices_length) + var/random_number_in_range = rand(1, total_choices_length) + var/sound_to_play = length(sound) > 0 ? pick(sound) : null - if(random_number_in_range <= audible_emotes_length) - controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(emote_hear)) - else if(random_number_in_range <= (audible_emotes_length + non_audible_emotes_length)) - controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(emote_see)) - else - controller.queue_behavior(/datum/ai_behavior/perform_speech, pick(speak)) + if(random_number_in_range <= audible_emotes_length) + controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(emote_hear), sound_to_play) + else if(random_number_in_range <= (audible_emotes_length + non_audible_emotes_length)) + controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(emote_see)) + else + controller.queue_behavior(/datum/ai_behavior/perform_speech, pick(speak), sound_to_play) /datum/ai_planning_subtree/random_speech/insect speech_chance = 5 @@ -60,6 +70,7 @@ /datum/ai_planning_subtree/random_speech/sheep speech_chance = 5 speak = list("baaa","baaaAAAAAH!","baaah") + sound = list('sound/creatures/sheep1.ogg', 'sound/creatures/sheep2.ogg', 'sound/creatures/sheep3.ogg') emote_hear = list("bleats.") emote_see = list("shakes her head.", "stares into the distance.") @@ -94,6 +105,7 @@ /datum/ai_planning_subtree/random_speech/cow speech_chance = 1 speak = list("moo?","moo","MOOOOOO") + sound = list('sound/creatures/cow.ogg') emote_hear = list("brays.") emote_see = list("shakes her head.") @@ -104,6 +116,7 @@ /datum/ai_planning_subtree/random_speech/cow/wisdom/New() . = ..() speak = GLOB.wisdoms //Done here so it's setup properly + sound = list() /datum/ai_planning_subtree/random_speech/deer speech_chance = 1 @@ -143,6 +156,9 @@ speak = list("oink?","oink","snurf") emote_hear = list("snorts.") emote_see = list("sniffs around.") + sound = list('sound/creatures/pig1.ogg', 'sound/creatures/pig2.ogg') + emote_hear = list("snorts.") + emote_see = list("sniffs around.") /datum/ai_planning_subtree/random_speech/killer_tomato speech_chance = 3 @@ -176,3 +192,21 @@ speech_chance = 5 emote_hear = list("rawrs.","grumbles.","grawls.", "stomps!") emote_see = list("stares ferociously.") + +/datum/ai_planning_subtree/random_speech/blackboard //literal tower of babel, subtree form + speech_chance = 1 + +/datum/ai_planning_subtree/random_speech/blackboard/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/list/speech_lines = controller.blackboard[BB_BASIC_MOB_SPEAK_LINES] + if(isnull(speech_lines)) + return ..() + + // Note to future developers: this behaviour a singleton so this probably doesn't work as you would expect + // The whole speech tree really needs to be refactored because this isn't how we use AI data these days + speak = speech_lines[BB_EMOTE_SAY] || list() + emote_see = speech_lines[BB_EMOTE_SEE] || list() + emote_hear = speech_lines[BB_EMOTE_HEAR] || list() + sound = speech_lines[BB_EMOTE_SOUND] || list() + speech_chance = speech_lines[BB_SPEAK_CHANCE] ? speech_lines[BB_SPEAK_CHANCE] : initial(speech_chance) + + return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm b/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm index 2d553fe4b2f7..55ec73876131 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm @@ -2,21 +2,26 @@ /datum/ai_planning_subtree/target_retaliate operational_datums = list(/datum/element/ai_retaliate, /datum/component/ai_retaliate_advanced) /// Blackboard key which tells us how to select valid targets - var/targetting_datum_key = BB_TARGETTING_DATUM + var/targeting_strategy_key = BB_TARGETING_STRATEGY /// Blackboard key in which to store selected target var/target_key = BB_BASIC_MOB_CURRENT_TARGET /// Blackboard key in which to store selected target's hiding place var/hiding_place_key = BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION + /// do we check for faction? + var/check_faction = FALSE /datum/ai_planning_subtree/target_retaliate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - controller.queue_behavior(/datum/ai_behavior/target_from_retaliate_list, BB_BASIC_MOB_RETALIATE_LIST, target_key, targetting_datum_key, hiding_place_key) + controller.queue_behavior(/datum/ai_behavior/target_from_retaliate_list, BB_BASIC_MOB_RETALIATE_LIST, target_key, targeting_strategy_key, hiding_place_key, check_faction) + +/datum/ai_planning_subtree/target_retaliate/check_faction + check_faction = TRUE /// Places a mob which you can see and who has recently attacked you into some 'run away from this' AI keys -/// Can use a different targetting datum than you use to select attack targets +/// Can use a different targeting strategy than you use to select attack targets /// Not required if fleeing is the only target behaviour or uses the same target datum /datum/ai_planning_subtree/target_retaliate/to_flee - targetting_datum_key = BB_FLEE_TARGETTING_DATUM + targeting_strategy_key = BB_FLEE_TARGETING_STRATEGY target_key = BB_BASIC_MOB_FLEE_TARGET hiding_place_key = BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION @@ -29,42 +34,51 @@ /// How far can we see stuff? var/vision_range = 9 -/datum/ai_behavior/target_from_retaliate_list/perform(seconds_per_tick, datum/ai_controller/controller, shitlist_key, target_key, targetting_datum_key, hiding_location_key) +/datum/ai_behavior/target_from_retaliate_list/perform(seconds_per_tick, datum/ai_controller/controller, shitlist_key, target_key, targeting_strategy_key, hiding_location_key, check_faction) . = ..() var/mob/living/living_mob = controller.pawn - var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] - if(!targetting_datum) + var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) + if(!targeting_strategy) CRASH("No target datum was supplied in the blackboard for [controller.pawn]") - var/list/enemies_list = controller.blackboard[shitlist_key] - if (!length(enemies_list)) - finish_action(controller, succeeded = FALSE) + var/list/shitlist = controller.blackboard[shitlist_key] + var/atom/existing_target = controller.blackboard[target_key] + + if (!check_faction) + controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, TRUE) + + if (!QDELETED(existing_target) && (locate(existing_target) in shitlist) && targeting_strategy.can_attack(living_mob, existing_target, vision_range)) + finish_action(controller, succeeded = TRUE, check_faction = check_faction) return - if (controller.blackboard[target_key] in enemies_list) // Don't bother changing - finish_action(controller, succeeded = FALSE) + var/list/enemies_list = list() + for(var/mob/living/potential_target as anything in shitlist) + if(!targeting_strategy.can_attack(living_mob, potential_target, vision_range)) + continue + enemies_list += potential_target + + if(!length(enemies_list)) + controller.clear_blackboard_key(target_key) + finish_action(controller, succeeded = FALSE, check_faction = check_faction) return var/atom/new_target = pick_final_target(controller, enemies_list) controller.set_blackboard_key(target_key, new_target) - var/atom/potential_hiding_location = targetting_datum.find_hidden_mobs(living_mob, new_target) + var/atom/potential_hiding_location = targeting_strategy.find_hidden_mobs(living_mob, new_target) if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially. controller.set_blackboard_key(hiding_location_key, potential_hiding_location) - finish_action(controller, succeeded = TRUE) - -/// Returns true if this target is valid for attacking based on current conditions -/datum/ai_behavior/target_from_retaliate_list/proc/can_attack_target(mob/living/living_mob, atom/target, datum/targetting_datum/targetting_datum) - if (!target) - return FALSE - if (target == living_mob) - return FALSE - if (!can_see(living_mob, target, vision_range)) - return FALSE - return targetting_datum.can_attack(living_mob, target) + finish_action(controller, succeeded = TRUE, check_faction = check_faction) /// Returns the desired final target from the filtered list of enemies /datum/ai_behavior/target_from_retaliate_list/proc/pick_final_target(datum/ai_controller/controller, list/enemies_list) return pick(enemies_list) + +/datum/ai_behavior/target_from_retaliate_list/finish_action(datum/ai_controller/controller, succeeded, check_faction) + . = ..() + if (succeeded || check_faction) + return + var/usually_ignores_faction = controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || FALSE + controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, usually_ignores_faction) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm b/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm index b6b4d286df1a..cd809804ba36 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm @@ -1,7 +1,7 @@ /// Attempts to use a mob ability on a target /datum/ai_planning_subtree/targeted_mob_ability /// Blackboard key for the ability - var/ability_key = BB_TARGETTED_ACTION + var/ability_key = BB_TARGETED_ACTION /// Blackboard key for where the target ref is stored var/target_key = BB_BASIC_MOB_CURRENT_TARGET /// Behaviour to perform using ability @@ -13,11 +13,16 @@ if (!ability_key) CRASH("You forgot to tell this mob where to find its ability") - var/mob/living/target = controller.blackboard[target_key] + if (!controller.blackboard_key_exists(target_key)) + return + var/datum/action/cooldown/using_action = controller.blackboard[ability_key] - if (QDELETED(target) || QDELETED(using_action) || !using_action.IsAvailable()) + if (!using_action?.IsAvailable()) return controller.queue_behavior(use_ability_behaviour, ability_key, target_key) if (finish_planning) return SUBTREE_RETURN_FINISH_PLANNING + +/datum/ai_planning_subtree/targeted_mob_ability/continue_planning + finish_planning = FALSE diff --git a/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm new file mode 100644 index 000000000000..dadba992e9f1 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm @@ -0,0 +1,56 @@ +///behavior to activate ability to escape from target +/datum/ai_planning_subtree/teleport_away_from_target + ///minimum distance away from the target before we execute behavior + var/minimum_distance = 2 + ///the ability we will execute + var/ability_key + +/datum/ai_planning_subtree/teleport_away_from_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + return + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + var/distance_from_target = get_dist(target, controller.pawn) + if(distance_from_target >= minimum_distance) + controller.clear_blackboard_key(BB_ESCAPE_DESTINATION) + return + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + if(!ability?.IsAvailable()) + return + var/turf/location_turf = controller.blackboard[BB_ESCAPE_DESTINATION] + + if(isnull(location_turf)) + controller.queue_behavior(/datum/ai_behavior/find_furthest_turf_from_target, BB_BASIC_MOB_CURRENT_TARGET, BB_ESCAPE_DESTINATION, minimum_distance) + return SUBTREE_RETURN_FINISH_PLANNING + + if(get_dist(location_turf, target) < minimum_distance || !can_see(controller.pawn, location_turf)) //target moved close too close or we moved too far since finding the target turf + controller.clear_blackboard_key(BB_ESCAPE_DESTINATION) + return + + controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target, ability_key, BB_ESCAPE_DESTINATION) + +///find furtherst turf target so we may teleport to it +/datum/ai_behavior/find_furthest_turf_from_target + +/datum/ai_behavior/find_furthest_turf_from_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, set_key, range) + var/mob/living/living_target = controller.blackboard[target_key] + if(QDELETED(living_target)) + return + + var/distance = 0 + var/turf/chosen_turf + for(var/turf/open/potential_destination in oview(range, living_target)) + if(potential_destination.is_blocked_turf()) + continue + var/new_distance_to_target = get_dist(potential_destination, living_target) + if(new_distance_to_target > distance) + chosen_turf = potential_destination + distance = new_distance_to_target + if(distance == range) + break //we have already found the max distance + + if(isnull(chosen_turf)) + finish_action(controller, FALSE) + return + + controller.set_blackboard_key(set_key, chosen_turf) + finish_action(controller, TRUE) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm b/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm new file mode 100644 index 000000000000..0b5e5d4776f9 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm @@ -0,0 +1,21 @@ +/// Simply walk to a location +/datum/ai_planning_subtree/travel_to_point + /// Blackboard key where we travel a place we walk to + var/location_key = BB_TRAVEL_DESTINATION + /// What do we do in order to travel + var/travel_behaviour = /datum/ai_behavior/travel_towards + +/datum/ai_planning_subtree/travel_to_point/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + var/atom/target = controller.blackboard[location_key] + if (QDELETED(target)) + return + controller.queue_behavior(travel_behaviour, location_key) + return SUBTREE_RETURN_FINISH_PLANNING + + +/datum/ai_planning_subtree/travel_to_point/and_clear_target + travel_behaviour = /datum/ai_behavior/travel_towards/stop_on_arrival + +/datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce + location_key = BB_BASIC_MOB_REINFORCEMENT_TARGET diff --git a/code/datums/ai/basic_mobs/basic_subtrees/use_mob_ability.dm b/code/datums/ai/basic_mobs/basic_subtrees/use_mob_ability.dm index 2c618e98e72b..5ceef67bedb2 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/use_mob_ability.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/use_mob_ability.dm @@ -1,10 +1,10 @@ /** * Simple behaviours which simply try to use an ability whenever it is available. - * For something which wants a target try `targetted_mob_ability`. + * For something which wants a target try `targeted_mob_ability`. */ /datum/ai_planning_subtree/use_mob_ability /// Blackboard key for the ability - var/ability_key + var/ability_key = BB_GENERIC_ACTION /// Behaviour to perform using ability var/use_ability_behaviour = /datum/ai_behavior/use_mob_ability /// If true we terminate planning after trying to use the ability. @@ -15,7 +15,7 @@ CRASH("You forgot to tell this mob where to find its ability") var/datum/action/cooldown/using_action = controller.blackboard[ability_key] - if (QDELETED(using_action) || !using_action.IsAvailable()) + if (!using_action?.IsAvailable()) return controller.queue_behavior(use_ability_behaviour, ability_key) diff --git a/code/datums/ai/basic_mobs/generic_controllers.dm b/code/datums/ai/basic_mobs/generic_controllers.dm new file mode 100644 index 000000000000..dae1b944dd32 --- /dev/null +++ b/code/datums/ai/basic_mobs/generic_controllers.dm @@ -0,0 +1,26 @@ +/// The most basic AI tree which just finds a guy and then runs at them to click them +/datum/ai_controller/basic_controller/simple_hostile + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Find a target, walk at target, attack intervening obstacles +/datum/ai_controller/basic_controller/simple_hostile_obstacles + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_use_targetted_ability.dm b/code/datums/ai/basic_mobs/pet_commands/pet_use_targeted_ability.dm similarity index 100% rename from code/datums/ai/basic_mobs/pet_commands/pet_use_targetted_ability.dm rename to code/datums/ai/basic_mobs/pet_commands/pet_use_targeted_ability.dm diff --git a/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm similarity index 51% rename from code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm rename to code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm index 643089bb5eae..2505b87a717f 100644 --- a/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm +++ b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm @@ -1,27 +1,35 @@ -///Datum for basic mobs to define what they can attack. -/datum/targetting_datum +///Datum for basic mobs to define what they can attack.GET_TARGETING_STRATEGY\((/[^,]*)\), +///Global, just like ai_behaviors +/datum/targeting_strategy ///Returns true or false depending on if the target can be attacked by the mob -/datum/targetting_datum/proc/can_attack(mob/living/living_mob, atom/target, vision_range) +/datum/targeting_strategy/proc/can_attack(mob/living/living_mob, atom/target, vision_range) return ///Returns something the target might be hiding inside of -/datum/targetting_datum/proc/find_hidden_mobs(mob/living/living_mob, atom/target) +/datum/targeting_strategy/proc/find_hidden_mobs(mob/living/living_mob, atom/target) var/atom/target_hiding_location if(istype(target.loc, /obj/structure/closet) || istype(target.loc, /obj/machinery/disposal) || istype(target.loc, /obj/machinery/sleeper)) target_hiding_location = target.loc return target_hiding_location -/datum/targetting_datum/basic +/datum/targeting_strategy/basic /// When we do our basic faction check, do we look for exact faction matches? var/check_factions_exactly = FALSE - /// Minimum status to attack living beings - var/stat_attack = CONSCIOUS - ///Whether we care for seeing the target or not + /// Whether we care for seeing the target or not var/ignore_sight = FALSE + /// Blackboard key containing the minimum stat of a living mob to target + var/minimum_stat_key = BB_TARGET_MINIMUM_STAT + /// If this blackboard key is TRUE, makes us only target wounded mobs + var/target_wounded_key -/datum/targetting_datum/basic/can_attack(mob/living/living_mob, atom/the_target, vision_range) - if(isturf(the_target) || !the_target) // bail out on invalids +/datum/targeting_strategy/basic/can_attack(mob/living/living_mob, atom/the_target, vision_range) + var/datum/ai_controller/basic_controller/our_controller = living_mob.ai_controller + + if(isnull(our_controller)) + return FALSE + + if(isturf(the_target) || isnull(the_target)) // bail out on invalids return FALSE if(isobj(the_target.loc)) @@ -30,6 +38,8 @@ return FALSE if(ismob(the_target)) //Target is in godmode, ignore it. + if(living_mob.loc == the_target) + return FALSE // We've either been eaten or are shapeshifted, let's assume the latter because we're still alive var/mob/M = the_target if(M.status_flags & GODMODE) return FALSE @@ -40,13 +50,18 @@ if(living_mob.see_invisible < the_target.invisibility) //Target's invisible to us, forget it return FALSE - if(isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech + if(isturf(living_mob.loc) && isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech or pawn is shapeshifted or jaunting return FALSE if(isliving(the_target)) //Targeting vs living mobs - var/mob/living/L = the_target - if(faction_check(living_mob, L) || (L.stat > stat_attack)) + var/mob/living/living_target = the_target + if(faction_check(our_controller, living_mob, living_target)) return FALSE + if(living_target.stat > our_controller.blackboard[minimum_stat_key]) + return FALSE + if(target_wounded_key && our_controller.blackboard[target_wounded_key] && living_target.health == living_target.maxHealth) + return FALSE + return TRUE if(ismecha(the_target)) //Targeting vs mechas @@ -68,34 +83,29 @@ return FALSE /// Returns true if the mob and target share factions -/datum/targetting_datum/basic/proc/faction_check(mob/living/living_mob, mob/living/the_target) - return living_mob.faction_check_mob(the_target, exact_match = check_factions_exactly) +/datum/targeting_strategy/basic/proc/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) + if (controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || controller.blackboard[BB_TEMPORARILY_IGNORE_FACTION]) + return FALSE + return living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly) /// Subtype more forgiving for items. /// Careful, this can go wrong and keep a mob hyper-focused on an item it can't lose aggro on -/datum/targetting_datum/basic/allow_items +/datum/targeting_strategy/basic/allow_items -/datum/targetting_datum/basic/allow_items/can_attack(mob/living/living_mob, atom/the_target) +/datum/targeting_strategy/basic/allow_items/can_attack(mob/living/living_mob, atom/the_target, vision_range) . = ..() if(isitem(the_target)) // trust fall exercise return TRUE -/// Subtype which doesn't care about faction -/// Mobs which retaliate but don't otherwise target seek should just attack anything which annoys them -/datum/targetting_datum/basic/ignore_faction - -/datum/targetting_datum/basic/ignore_faction/faction_check(mob/living/living_mob, mob/living/the_target) - return FALSE - /// Subtype which searches for mobs of a size relative to ours -/datum/targetting_datum/basic/of_size +/datum/targeting_strategy/basic/of_size /// If true, we will return mobs which are smaller than us. If false, larger. var/find_smaller = TRUE /// If true, we will return mobs which are the same size as us. var/inclusive = TRUE -/datum/targetting_datum/basic/of_size/can_attack(mob/living/owner, atom/target) +/datum/targeting_strategy/basic/of_size/can_attack(mob/living/owner, atom/target, vision_range) if(!isliving(target)) return FALSE . = ..() @@ -110,8 +120,14 @@ return !find_smaller // This is just using the default values but the subtype makes it clearer -/datum/targetting_datum/basic/of_size/ours_or_smaller +/datum/targeting_strategy/basic/of_size/ours_or_smaller -/datum/targetting_datum/basic/of_size/larger +/datum/targeting_strategy/basic/of_size/larger find_smaller = FALSE inclusive = FALSE + +/// Makes the mob only attack their own faction. Useful mostly if their attacks do something helpful (e.g. healing touch). +/datum/targeting_strategy/basic/same_faction + +/datum/targeting_strategy/basic/same_faction/faction_check(mob/living/living_mob, mob/living/the_target) + return !..() // inverts logic to ONLY target mobs that share a faction diff --git a/code/datums/ai/basic_mobs/targetting_datums/dont_target_friends.dm b/code/datums/ai/basic_mobs/targeting_strategies/dont_target_friends.dm similarity index 55% rename from code/datums/ai/basic_mobs/targetting_datums/dont_target_friends.dm rename to code/datums/ai/basic_mobs/targeting_strategies/dont_target_friends.dm index 8e6c79fda3fa..7231d88dde08 100644 --- a/code/datums/ai/basic_mobs/targetting_datums/dont_target_friends.dm +++ b/code/datums/ai/basic_mobs/targeting_strategies/dont_target_friends.dm @@ -1,20 +1,14 @@ /// Don't target an atom in our friends list (or turfs), anything else is fair game -/datum/targetting_datum/not_friends +/datum/targeting_strategy/basic/not_friends /// Stop regarding someone as a valid target once they pass this stat level, setting it to DEAD means you will happily attack corpses var/attack_until_past_stat = HARD_CRIT /// If we can try to closed turfs or not var/attack_closed_turf = FALSE ///Returns true or false depending on if the target can be attacked by the mob -/datum/targetting_datum/not_friends/can_attack(mob/living/living_mob, atom/target) - if (!target) - return FALSE - if (attack_closed_turf) - if (isopenturf(target)) - return FALSE - else - if (isturf(target)) - return FALSE +/datum/targeting_strategy/basic/not_friends/can_attack(mob/living/living_mob, atom/target, vision_range) + if(attack_closed_turf && isclosedturf(target)) + return TRUE if (ismob(target)) var/mob/mob_target = target @@ -35,7 +29,18 @@ // OR This is not our friend, fire at will return TRUE +///friends dont care about factions +/datum/targeting_strategy/basic/not_friends/faction_check(mob/living/living_mob, mob/living/the_target) return FALSE -/datum/targetting_datum/not_friends/attack_closed_turfs +/datum/targeting_strategy/basic/not_friends/attack_closed_turfs attack_closed_turf = TRUE + +/// Subtype that allows us to target items while deftly avoiding attacking our allies. Be careful when it comes to targeting items as an AI could get trapped targeting something it can't destroy. +/datum/targeting_strategy/basic/not_friends/allow_items + +/datum/targeting_strategy/basic/not_friends/allow_items/can_attack(mob/living/living_mob, atom/the_target, vision_range) + . = ..() + if(isitem(the_target)) + // trust fall exercise + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/with_object.dm b/code/datums/ai/basic_mobs/targeting_strategies/with_object.dm new file mode 100644 index 000000000000..7cc76d3010c9 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/with_object.dm @@ -0,0 +1,29 @@ +/** + * Find mobs who are holding the bb configurable object type + * + * This is an extension of basic targeting behaviour, that allows you to + * only target the mob if they have a specific item in their hand. + * + */ +/datum/targeting_strategy/basic/holding_object + /// BB key that holds the target typepath to use + var/target_item_key = BB_TARGET_HELD_ITEM + +///Returns true or false depending on if the target can be attacked by the mob +/datum/targeting_strategy/basic/holding_object/can_attack(mob/living/living_mob, atom/target, vision_range) + var/datum/ai_controller/controller = living_mob.ai_controller + var/object_type_path = controller.blackboard[target_item_key] + + if (object_type_path == null) + return FALSE // no op + if(!ismob(target)) + return FALSE // no hands no problems + + // Look at me, type casting like a grown up + var/mob/targetmob = target + // Check if our parent behaviour agrees we can attack this target (we ignore faction by default) + var/can_attack = ..() + if(can_attack && targetmob.is_holding_item_of_type(object_type_path)) + return TRUE // they have the item + // No valid target + return FALSE diff --git a/code/datums/ai/dog/dog_behaviors.dm b/code/datums/ai/dog/dog_behaviors.dm index 2f6ae2d9720a..c6bdc46afe72 100644 --- a/code/datums/ai/dog/dog_behaviors.dm +++ b/code/datums/ai/dog/dog_behaviors.dm @@ -4,22 +4,21 @@ * Adds a floor to the melee damage of the dog, as most pet dogs don't actually have any melee strength */ /datum/ai_behavior/basic_melee_attack/dog - action_cooldown = 0.8 SECONDS behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM required_distance = 3 -/datum/ai_behavior/basic_melee_attack/dog/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) +/datum/ai_behavior/basic_melee_attack/dog/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) controller.behavior_cooldowns[src] = world.time + action_cooldown var/mob/living/living_pawn = controller.pawn if(!(isturf(living_pawn.loc) || HAS_TRAIT(living_pawn, TRAIT_AI_BAGATTACK))) // Void puppies can attack from inside bags - finish_action(controller, FALSE, target_key, targetting_datum_key, hiding_location_key) + finish_action(controller, FALSE, target_key, targeting_strategy_key, hiding_location_key) return // Unfortunately going to repeat this check in parent call but what can you do var/atom/target = controller.blackboard[target_key] - var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] - if (!targetting_datum.can_attack(living_pawn, target)) - finish_action(controller, FALSE, target_key, targetting_datum_key, hiding_location_key) + var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) + if (!targeting_strategy.can_attack(living_pawn, target)) + finish_action(controller, FALSE, target_key, targeting_strategy_key, hiding_location_key) return if (!in_range(living_pawn, target)) diff --git a/code/datums/ai/dog/dog_controller.dm b/code/datums/ai/dog/dog_controller.dm index 5a42cb43a1eb..a5794ff9b5f4 100644 --- a/code/datums/ai/dog/dog_controller.dm +++ b/code/datums/ai/dog/dog_controller.dm @@ -2,7 +2,7 @@ blackboard = list( BB_DOG_HARASS_HARM = TRUE, BB_VISION_RANGE = AI_DOG_VISION_RANGE, - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(), + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) ai_movement = /datum/ai_movement/basic_avoidance idle_behavior = /datum/idle_behavior/idle_dog @@ -19,7 +19,11 @@ blackboard = list( BB_DOG_HARASS_HARM = TRUE, BB_VISION_RANGE = AI_DOG_VISION_RANGE, - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(), + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + // Find nearby mobs ... + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/holding_object, + // With tongs in hand! + BB_TARGET_HELD_ITEM = /obj/item/kitchen/tongs, BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/pet/dog), BB_BABIES_CHILD_TYPES = list(/mob/living/basic/pet/dog/corgi/puppy = 95, /mob/living/basic/pet/dog/corgi/puppy/void = 5), ) @@ -29,6 +33,10 @@ /datum/ai_planning_subtree/make_babies, // Ian WILL prioritise sex over following your instructions /datum/ai_planning_subtree/pet_planning, /datum/ai_planning_subtree/dog_harassment, + // Find targets to run away from (uses the targeting strategy from above) + /datum/ai_planning_subtree/simple_find_target, + // Flee from that target + /datum/ai_planning_subtree/flee_target, ) /datum/ai_controller/basic_controller/dog/corgi/get_access() @@ -36,4 +44,21 @@ if(!istype(corgi_pawn)) return - return corgi_pawn.access_card + return corgi_pawn.access_card.GetAccess() + +/datum/ai_controller/basic_controller/dog/puppy + blackboard = list( + BB_DOG_HARASS_HARM = TRUE, + BB_VISION_RANGE = AI_DOG_VISION_RANGE, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/holding_object, + // With tongs in hand! + BB_TARGET_HELD_ITEM = /obj/item/kitchen/tongs, + ) + planning_subtrees = list( + /datum/ai_planning_subtree/random_speech/dog, + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/dog_harassment, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/flee_target, + ) diff --git a/code/datums/ai/dog/dog_subtrees.dm b/code/datums/ai/dog/dog_subtrees.dm index 66198b61ac30..62f63da54bdd 100644 --- a/code/datums/ai/dog/dog_subtrees.dm +++ b/code/datums/ai/dog/dog_subtrees.dm @@ -4,27 +4,27 @@ /datum/ai_planning_subtree/dog_harassment/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) if(!SPT_PROB(10, seconds_per_tick)) return - controller.queue_behavior(/datum/ai_behavior/find_hated_dog_target, BB_DOG_HARASS_TARGET, BB_PET_TARGETTING_DATUM) + controller.queue_behavior(/datum/ai_behavior/find_hated_dog_target, BB_DOG_HARASS_TARGET, BB_PET_TARGETING_STRATEGY) var/atom/harass_target = controller.blackboard[BB_DOG_HARASS_TARGET] if (isnull(harass_target)) return - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack/dog, BB_DOG_HARASS_TARGET, BB_PET_TARGETTING_DATUM) + controller.queue_behavior(/datum/ai_behavior/basic_melee_attack/dog, BB_DOG_HARASS_TARGET, BB_PET_TARGETING_STRATEGY) return SUBTREE_RETURN_FINISH_PLANNING /datum/ai_behavior/find_hated_dog_target -/datum/ai_behavior/find_hated_dog_target/setup(datum/ai_controller/controller, target_key, targetting_datum_key) +/datum/ai_behavior/find_hated_dog_target/setup(datum/ai_controller/controller, target_key, targeting_strategy_key) . = ..() var/mob/living/dog = controller.pawn - var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] + var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) for(var/mob/living/iter_living in oview(2, dog)) if(iter_living.stat != CONSCIOUS || !HAS_TRAIT(iter_living, TRAIT_HATED_BY_DOGS)) continue if(!isnull(dog.buckled)) dog.audible_message(span_notice("[dog] growls at [iter_living], yet [dog.p_they()] [dog.p_are()] much too comfy to move."), hearing_distance = COMBAT_MESSAGE_RANGE) continue - if(!targetting_datum.can_attack(dog, iter_living)) + if(!targeting_strategy.can_attack(dog, iter_living)) continue dog.audible_message(span_warning("[dog] growls at [iter_living], seemingly annoyed by [iter_living.p_their()] presence."), hearing_distance = COMBAT_MESSAGE_RANGE) diff --git a/code/datums/ai/generic/find_and_set.dm b/code/datums/ai/generic/find_and_set.dm index 443febae2a0e..b0d34f68c224 100644 --- a/code/datums/ai/generic/find_and_set.dm +++ b/code/datums/ai/generic/find_and_set.dm @@ -8,8 +8,8 @@ /datum/ai_behavior/find_and_set/perform(seconds_per_tick, datum/ai_controller/controller, set_key, locate_path, search_range) . = ..() - var/atom/current_target = controller.blackboard[set_key] - if (!QDELETED(current_target)) + if (controller.blackboard_key_exists(set_key)) + finish_action(controller, TRUE) return var/find_this_thing = search_tactic(controller, locate_path, search_range) if(find_this_thing) @@ -76,6 +76,16 @@ if(found.len) return pick(found) +/// Like find_and_set/in_list, but we return the turf location of the item instead of the item itself. +/datum/ai_behavior/find_and_set/in_list/turf_location + +/datum/ai_behavior/find_and_set/in_list/turf_location/search_tactic(datum/ai_controller/controller, locate_paths, search_range) + . = ..() + if(isnull(.)) + return null + + return get_turf(.) + /** * Variant of find and set which returns an object which can be animated with a staff of change */ @@ -129,9 +139,39 @@ continue if (living_pawn.see_invisible < dead_pal.invisibility) continue - if (!living_pawn.faction_check_mob(dead_pal)) + if (!living_pawn.faction_check_atom(dead_pal)) continue nearby_bodies += dead_pal if (nearby_bodies.len) return pick(nearby_bodies) + +/** + * A variant that looks for a human who is not dead or incapacitated, and has a mind + */ +/datum/ai_behavior/find_and_set/conscious_person + +/datum/ai_behavior/find_and_set/conscious_person/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/list/customers = list() + for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) + if(IS_DEAD_OR_INCAP(target) || !target.mind) + continue + customers += target + + if(customers.len) + return pick(customers) + + return null + +/datum/ai_behavior/find_and_set/nearby_friends + action_cooldown = 2 SECONDS + +/datum/ai_behavior/find_and_set/nearby_friends/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/atom/friend = locate(/mob/living/carbon/human) in oview(search_range, controller.pawn) + + if(isnull(friend)) + return null + + var/mob/living/living_pawn = controller.pawn + var/potential_friend = living_pawn.faction.Find(REF(friend)) ? friend : null + return potential_friend diff --git a/code/datums/ai/generic/generic_behaviors.dm b/code/datums/ai/generic/generic_behaviors.dm index 0091bfbf1acc..7e383d304afd 100644 --- a/code/datums/ai/generic/generic_behaviors.dm +++ b/code/datums/ai/generic/generic_behaviors.dm @@ -300,11 +300,25 @@ /datum/ai_behavior/perform_speech -/datum/ai_behavior/perform_speech/perform(seconds_per_tick, datum/ai_controller/controller, speech) +/datum/ai_behavior/perform_speech/perform(seconds_per_tick, datum/ai_controller/controller, speech, speech_sound) + . = ..() + var/mob/living/living_pawn = controller.pawn if(!istype(living_pawn)) return living_pawn.say(speech, forced = "AI Controller") + if(speech_sound) + playsound(living_pawn, speech_sound, 80, vary = TRUE) + finish_action(controller, TRUE) + +/datum/ai_behavior/perform_speech_radio + +/datum/ai_behavior/perform_speech_radio/perform(seconds_per_tick, datum/ai_controller/controller, speech, obj/item/radio/speech_radio, list/try_channels = list(RADIO_CHANNEL_COMMON)) + var/mob/living/living_pawn = controller.pawn + if(!istype(living_pawn) || !istype(speech_radio) || QDELETED(speech_radio) || !length(try_channels)) + finish_action(controller, FALSE) + return + speech_radio.talk_into(living_pawn, speech, pick(try_channels)) finish_action(controller, TRUE) //song behaviors diff --git a/code/datums/ai/hunting_behavior/hunting_behaviors.dm b/code/datums/ai/hunting_behavior/hunting_behaviors.dm index a06f69965695..f5e5c209a181 100644 --- a/code/datums/ai/hunting_behavior/hunting_behaviors.dm +++ b/code/datums/ai/hunting_behavior/hunting_behaviors.dm @@ -16,6 +16,8 @@ var/hunt_range = 2 /// What are the chances we hunt something at any given moment var/hunt_chance = 100 + ///do we finish planning subtree + var/finish_planning = TRUE /datum/ai_planning_subtree/find_and_hunt_target/New() . = ..() @@ -35,13 +37,15 @@ // We're not hunting anything, look around for something if(isnull(hunted)) controller.queue_behavior(finding_behavior, target_key, hunt_targets, hunt_range) + return // We ARE hunting something, execute the hunt. // Note that if our AI controller has multiple hunting subtrees set, // we may accidentally be executing another tree's hunt - not ideal, // try to set a unique target key if you have multiple - else - controller.queue_behavior(hunting_behavior, target_key, BB_HUNTING_COOLDOWN) + + controller.queue_behavior(hunting_behavior, target_key, BB_HUNTING_COOLDOWN) + if(finish_planning) return SUBTREE_RETURN_FINISH_PLANNING //If we're hunting we're too busy for anything else /// Finds a specific atom type to hunt. @@ -77,6 +81,7 @@ /// Do we reset the target after attacking something, so we can check for status changes. var/always_reset_target = FALSE + /datum/ai_behavior/hunt_target/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) . = ..() var/atom/hunt_target = controller.blackboard[hunting_target_key] @@ -89,7 +94,7 @@ var/mob/living/hunter = controller.pawn var/atom/hunted = controller.blackboard[hunting_target_key] - if(isnull(hunted)) + if(QDELETED(hunted)) //Target is gone for some reason. forget about this task! controller[hunting_target_key] = null finish_action(controller, FALSE, hunting_target_key) @@ -121,6 +126,37 @@ controller.clear_blackboard_key(hunting_target_key) /datum/ai_behavior/hunt_target/unarmed_attack_target + ///do we toggle combat mode before interacting with the object? + var/switch_combat_mode = FALSE /datum/ai_behavior/hunt_target/unarmed_attack_target/target_caught(mob/living/hunter, obj/structure/cable/hunted) hunter.UnarmedAttack(hunted, TRUE) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/finish_action(datum/ai_controller/controller, succeeded, hunting_target_key, hunting_cooldown_key) + . = ..() + if(!switch_combat_mode) + return + var/mob/living/living_pawn = controller.pawn + living_pawn.istate = initial(living_pawn.istate) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/switch_combat_mode + switch_combat_mode = TRUE + +/datum/ai_behavior/hunt_target/unarmed_attack_target/reset_target + always_reset_target = TRUE + +/datum/ai_behavior/hunt_target/use_ability_on_target + always_reset_target = TRUE + ///the ability we will use + var/ability_key + +/datum/ai_behavior/hunt_target/use_ability_on_target/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + if(!ability?.IsAvailable()) + finish_action(controller, FALSE, hunting_target_key) + return ..() + +/datum/ai_behavior/hunt_target/use_ability_on_target/target_caught(mob/living/hunter, atom/hunted) + var/datum/action/cooldown/ability = hunter.ai_controller.blackboard[ability_key] + ability.InterceptClickOn(hunter, null, hunted) + diff --git a/code/datums/ai/hunting_behavior/hunting_corpses.dm b/code/datums/ai/hunting_behavior/hunting_corpses.dm new file mode 100644 index 000000000000..e720e4da947a --- /dev/null +++ b/code/datums/ai/hunting_behavior/hunting_corpses.dm @@ -0,0 +1,17 @@ +/// Find and attack corpses +/datum/ai_planning_subtree/find_and_hunt_target/corpses + finding_behavior = /datum/ai_behavior/find_hunt_target/corpses + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target + hunt_targets = list(/mob/living) + +/// Find nearby dead mobs +/datum/ai_behavior/find_hunt_target/corpses + +/datum/ai_behavior/find_hunt_target/corpses/valid_dinner(mob/living/source, mob/living/dinner, radius) + if (!isliving(dinner) || dinner.stat != DEAD) + return FALSE + return can_see(source, dinner, radius) + +/// Find and attack specifically human corpses +/datum/ai_planning_subtree/find_and_hunt_target/corpses/human + hunt_targets = list(/mob/living/carbon/human) diff --git a/code/datums/ai/idle_behaviors/idle_random_walk.dm b/code/datums/ai/idle_behaviors/idle_random_walk.dm index b25f983f3139..f189e89b4230 100644 --- a/code/datums/ai/idle_behaviors/idle_random_walk.dm +++ b/code/datums/ai/idle_behaviors/idle_random_walk.dm @@ -14,3 +14,68 @@ /datum/idle_behavior/idle_random_walk/less_walking walk_chance = 10 + +/// Only walk if we don't have a target +/datum/idle_behavior/idle_random_walk/no_target + /// Where do we look for a target? + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + +/datum/idle_behavior/idle_random_walk/no_target/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) + if (!controller.blackboard_key_exists(target_key)) + return + return ..() + +/// Only walk if we are not on the target's location +/datum/idle_behavior/idle_random_walk/not_while_on_target + ///What is the spot we have to stand on? + var/target_key + +/datum/idle_behavior/idle_random_walk/not_while_on_target/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + + //Don't move, if we are are already standing on it + if(!QDELETED(target) && ((isturf(target) && controller.pawn.loc == target) || (target.loc == controller.pawn.loc))) + return + + return ..() + +/// walk randomly however stick near a target +/datum/idle_behavior/walk_near_target + /// chance to walk + var/walk_chance = 25 + /// distance we are to target + var/minimum_distance = 20 + /// key that holds target + var/target_key + +/datum/idle_behavior/walk_near_target/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) + . = ..() + var/mob/living/living_pawn = controller.pawn + if(LAZYLEN(living_pawn.do_afters)) + return + + if(!SPT_PROB(walk_chance, seconds_per_tick) || !(living_pawn.mobility_flags & MOBILITY_MOVE) || !isturf(living_pawn.loc) || living_pawn.pulledby) + return + + var/atom/target = controller.blackboard[target_key] + var/distance = get_dist(target, living_pawn) + if(isnull(target) || distance > minimum_distance) //if we are too far away from target, just walk randomly + var/move_dir = pick(GLOB.alldirs) + living_pawn.Move(get_step(living_pawn, move_dir), move_dir) + return + + var/list/possible_turfs = list() + for(var/direction in GLOB.alldirs) + var/turf/possible_step = get_step(living_pawn, direction) + if(get_dist(possible_step, target) > minimum_distance) + continue + if(possible_step.is_blocked_turf()) + continue + possible_turfs += possible_step + + if(!length(possible_turfs)) + return + + var/turf/picked_turf = pick(possible_turfs) + + living_pawn.Move(picked_turf, get_dir(living_pawn, picked_turf)) diff --git a/code/datums/ai/learn_ai.md b/code/datums/ai/learn_ai.md index 9906806cfbd5..e8a151140f06 100644 --- a/code/datums/ai/learn_ai.md +++ b/code/datums/ai/learn_ai.md @@ -25,7 +25,7 @@ First, let's look at the blackboard. ```dm /datum/ai_controller/basic/cow blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(), + BB_TARGETING_STRATEGY = GET_TARGETING_STRATEGY(/datum/targeting_strategy/basic/allow_items), BB_BASIC_MOB_TIP_REACTING = FALSE, BB_BASIC_MOB_TIPPER = null, ) @@ -81,7 +81,7 @@ Okay, so we have blackboard variables, which are considered by subtrees to plan //now we know we have a target but should let a hostile subtree plan attacking humans. let's check if it's actually food if(target in wanted) - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + controller.queue_behavior(/datum/ai_behavior/basic_melee_attack, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) return SUBTREE_RETURN_FINISH_PLANNING //this prevents further subtrees from planning since we want to focus on eating the food ``` @@ -113,7 +113,7 @@ And one of those behaviors, `basic_melee_attack`. As I have been doing so far, I //targetting datum will kill the action if not real anymore var/datum/weakref/weak_target = controller.blackboard[target_key] var/atom/target = weak_target?.resolve() - var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] + var/datum/targeting_strategy/targetting_datum = controller.blackboard[targetting_datum_key] if(!targetting_datum.can_attack(basic_mob, target)) ///We have a target that is no longer valid to attack. Remember that returning doesn't end the behavior, JUST this single performance. So we call "finish_action" with whether it succeeded in doing what it wanted to do (it didn't, so FALSE) and the blackboard keys passed into this behavior. diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm index 3523da7ecec2..da46735ec363 100644 --- a/code/datums/ai/movement/ai_movement_jps.dm +++ b/code/datums/ai/movement/ai_movement_jps.dm @@ -2,7 +2,7 @@ * This movement datum represents smart-pathing */ /datum/ai_movement/jps - max_pathing_attempts = 4 + max_pathing_attempts = 20 /datum/ai_movement/jps/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) . = ..() @@ -12,13 +12,13 @@ var/datum/move_loop/loop = SSmove_manager.jps_move(moving, current_movement_target, delay, - repath_delay = 2 SECONDS, + repath_delay = 0.5 SECONDS, max_path_length = AI_MAX_PATH_LENGTH, minimum_distance = controller.get_minimum_distance(), id = controller.get_access(), subsystem = SSai_movement, extra_info = controller, - initial_path = controller.blackboard[BB_PATH_TO_USE]) + ) RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move)) RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move)) diff --git a/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm b/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm index 5bad78a35154..b4e5609531c1 100644 --- a/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm +++ b/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm @@ -22,7 +22,7 @@ /datum/ai_behavior/vendor_crush/proc/tiltonmob(datum/ai_controller/controller, turf/target_turf) var/obj/machinery/vending/vendor_pawn = controller.pawn - if(vendor_pawn.tilt(target_turf)) //We hit something + if(vendor_pawn.tilt(target_turf, 0) & SUCCESSFULLY_CRUSHED_MOB) //We hit something vendor_pawn.say(pick("Supersize this!", "Eat my shiny metal ass!", "Want to consume some of my products?", "SMASH!", "Don't you love these smashing prices!")) controller.set_blackboard_key(BB_VENDING_LAST_HIT_SUCCESFUL, TRUE) else diff --git a/code/datums/ai/robot_customer/robot_customer_behaviors.dm b/code/datums/ai/robot_customer/robot_customer_behaviors.dm index 431713b94cac..e370af6f5c1f 100644 --- a/code/datums/ai/robot_customer/robot_customer_behaviors.dm +++ b/code/datums/ai/robot_customer/robot_customer_behaviors.dm @@ -3,7 +3,7 @@ /datum/ai_behavior/find_seat/perform(seconds_per_tick, datum/ai_controller/controller) . = ..() - var/mob/living/simple_animal/robot_customer/customer_pawn = controller.pawn + var/mob/living/basic/robot_customer/customer_pawn = controller.pawn var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] var/datum/venue/attending_venue = controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] @@ -44,7 +44,7 @@ /datum/ai_behavior/order_food/perform(seconds_per_tick, datum/ai_controller/controller) . = ..() - var/mob/living/simple_animal/robot_customer/customer_pawn = controller.pawn + var/mob/living/basic/robot_customer/customer_pawn = controller.pawn var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] var/obj/structure/holosign/robot_seat/seat_marker = controller.blackboard[BB_CUSTOMER_MY_SEAT] if(get_turf(seat_marker) == get_turf(customer_pawn)) @@ -75,7 +75,7 @@ // SPT_PROB 1.5 is about a 40% chance that the tourist will have vocalised at least once every minute. if(SPT_PROB(0.85, seconds_per_tick)) - var/mob/living/simple_animal/robot_customer/customer_pawn = controller.pawn + var/mob/living/basic/robot_customer/customer_pawn = controller.pawn var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] customer_pawn.say(pick(customer_data.wait_for_food_lines)) @@ -98,7 +98,7 @@ /datum/ai_behavior/wait_for_food/finish_action(datum/ai_controller/controller, succeeded) . = ..() - var/mob/living/simple_animal/robot_customer/customer_pawn = controller.pawn + var/mob/living/basic/robot_customer/customer_pawn = controller.pawn var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] var/mob/living/greytider = controller.blackboard[BB_CUSTOMER_CURRENT_TARGET] //usually if we stop waiting, it's because we're done with the venue. but here we're either beating some dude up diff --git a/code/datums/ai/robot_customer/robot_customer_controller.dm b/code/datums/ai/robot_customer/robot_customer_controller.dm index dfcab76c8728..433eab6ae88c 100644 --- a/code/datums/ai/robot_customer/robot_customer_controller.dm +++ b/code/datums/ai/robot_customer/robot_customer_controller.dm @@ -1,14 +1,16 @@ /datum/ai_controller/robot_customer ai_movement = /datum/ai_movement/basic_avoidance movement_delay = 0.8 SECONDS - blackboard = list(BB_CUSTOMER_CURRENT_ORDER = null, - BB_CUSTOMER_MY_SEAT = null, - BB_CUSTOMER_PATIENCE = 999, - BB_CUSTOMER_CUSTOMERINFO = null, - BB_CUSTOMER_EATING = FALSE, - BB_CUSTOMER_LEAVING = FALSE, - BB_CUSTOMER_ATTENDING_VENUE = null, - BB_CUSTOMER_SAID_CANT_FIND_SEAT_LINE = FALSE) + blackboard = list( + BB_CUSTOMER_ATTENDING_VENUE = null, + BB_CUSTOMER_CURRENT_ORDER = null, + BB_CUSTOMER_CUSTOMERINFO = null, + BB_CUSTOMER_EATING = FALSE, + BB_CUSTOMER_LEAVING = FALSE, + BB_CUSTOMER_MY_SEAT = null, + BB_CUSTOMER_PATIENCE = 999 SECONDS, + BB_CUSTOMER_SAID_CANT_FIND_SEAT_LINE = FALSE, + ) planning_subtrees = list(/datum/ai_planning_subtree/robot_customer) /datum/ai_controller/robot_customer/Destroy() @@ -18,7 +20,7 @@ return ..() /datum/ai_controller/robot_customer/TryPossessPawn(atom/new_pawn) - if(!istype(new_pawn, /mob/living/simple_animal/robot_customer)) + if(!istype(new_pawn, /mob/living/basic/robot_customer)) return AI_CONTROLLER_INCOMPATIBLE new_pawn.AddElement(/datum/element/relay_attackers) RegisterSignal(new_pawn, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby)) @@ -28,6 +30,12 @@ return ..() //Run parent at end /datum/ai_controller/robot_customer/UnpossessPawn(destroy) + if(isnull(pawn)) +#ifndef UNIT_TESTS + stack_trace("Robot Customer AI Controller UnpossessPawn called with null pawn! This shouldn't happen in normal circumstances.") // and unit tests are abnormal circumstances +#endif + return + UnregisterSignal(pawn, list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_WAS_ATTACKED, COMSIG_LIVING_GET_PULLED, COMSIG_ATOM_ATTACK_HAND)) return ..() //Run parent at end @@ -67,7 +75,7 @@ INVOKE_ASYNC(src, PROC_REF(async_on_get_pulled), source, puller) /datum/ai_controller/robot_customer/proc/async_on_get_pulled(datum/source, mob/living/puller) - var/mob/living/simple_animal/robot_customer/customer = pawn + var/mob/living/basic/robot_customer/customer = pawn var/datum/customer_data/customer_data = blackboard[BB_CUSTOMER_CUSTOMERINFO] var/datum/venue/attending_venue = blackboard[BB_CUSTOMER_ATTENDING_VENUE] @@ -81,12 +89,12 @@ /datum/ai_controller/robot_customer/proc/dont_want_that(mob/living/chef, obj/item/thing) - var/mob/living/simple_animal/robot_customer/customer = pawn + var/mob/living/basic/robot_customer/customer = pawn var/datum/customer_data/customer_data = blackboard[BB_CUSTOMER_CUSTOMERINFO] customer.say(customer_data.wrong_item_line) /datum/ai_controller/robot_customer/proc/warn_greytider(mob/living/greytider) - var/mob/living/simple_animal/robot_customer/customer = pawn + var/mob/living/basic/robot_customer/customer = pawn var/datum/venue/attending_venue = blackboard[BB_CUSTOMER_ATTENDING_VENUE] var/datum/customer_data/customer_data = blackboard[BB_CUSTOMER_CUSTOMERINFO] //Living mobs are tagged, so these will always be valid diff --git a/code/datums/cinematics/_cinematic.dm b/code/datums/cinematics/_cinematic.dm index 6659fde0c516..cc4fe5a0fab8 100644 --- a/code/datums/cinematics/_cinematic.dm +++ b/code/datums/cinematics/_cinematic.dm @@ -1,3 +1,5 @@ +#define CINEMATIC_SOURCE "cinematic" + /** * Plays a cinematic, duh. Can be to a select few people, or everyone. * @@ -30,7 +32,7 @@ /datum/cinematic /// A list of all clients watching the cinematic var/list/client/watching = list() - /// A list of all mobs who have notransform set while watching the cinematic + /// A list of all mobs who have TRAIT_NO_TRANSFORM set while watching the cinematic var/list/datum/weakref/locked = list() /// Whether the cinematic is a global cinematic or not var/is_global = FALSE @@ -106,13 +108,8 @@ /datum/cinematic/proc/show_to(mob/watching_mob, client/watching_client) SIGNAL_HANDLER - // We could technically rip people out of notransform who shouldn't be, - // so we'll only lock down all viewing mobs who don't have it already set. - // This does potentially mean some mobs could lose their notrasnform and - // not be locked down by cinematics, but that should be very unlikely. - if(!watching_mob.notransform) - locked += WEAKREF(watching_mob) - watching_mob.notransform = TRUE + if(!HAS_TRAIT_FROM(watching_mob, TRAIT_NO_TRANSFORM, CINEMATIC_SOURCE)) + lock_mob(watching_mob) // Only show the actual cinematic to cliented mobs. if(!watching_client || (watching_client in watching)) @@ -146,14 +143,24 @@ remove_watcher(viewing_client) for(var/datum/weakref/locked_ref as anything in locked) - var/mob/locked_mob = locked_ref.resolve() - if(QDELETED(locked_mob)) - continue - locked_mob.notransform = FALSE - UnregisterSignal(locked_mob, COMSIG_MOB_CLIENT_LOGIN) + unlock_mob(locked_ref) qdel(src) + +/// Locks a mob, preventing them from moving, being hurt, or acting +/datum/cinematic/proc/lock_mob(mob/to_lock) + locked += WEAKREF(to_lock) + ADD_TRAIT(to_lock, TRAIT_NO_TRANSFORM, CINEMATIC_SOURCE) + +/// Unlocks a previously locked weakref +/datum/cinematic/proc/unlock_mob(datum/weakref/mob_ref) + var/mob/locked_mob = mob_ref.resolve() + if(isnull(locked_mob)) + return + REMOVE_TRAIT(locked_mob, TRAIT_NO_TRANSFORM, CINEMATIC_SOURCE) + UnregisterSignal(locked_mob, COMSIG_MOB_CLIENT_LOGIN) + /// Removes the passed client from our watching list. /datum/cinematic/proc/remove_watcher(client/no_longer_watching) SIGNAL_HANDLER @@ -163,8 +170,10 @@ UnregisterSignal(no_longer_watching, COMSIG_PARENT_QDELETING) // We'll clear the cinematic if they have a mob which has one, - // but we won't remove notransform. Wait for the cinematic end to do that. + // but we won't remove TRAIT_NO_TRANSFORM. Wait for the cinematic end to do that. no_longer_watching.mob?.clear_fullscreen("cinematic") no_longer_watching.screen -= screen watching -= no_longer_watching + +#undef CINEMATIC_SOURCE diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index 253e3c3d2a8f..3b8d83349dcf 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -169,12 +169,13 @@ return /** - * Called when the component has a new source registered + * Called when the component has a new source registered. + * Return COMPONENT_INCOMPATIBLE to signal that the source is incompatible and should not be added */ -/datum/component/proc/on_source_add(source) +/datum/component/proc/on_source_add(source, ...) SHOULD_CALL_PARENT(TRUE) if(dupe_mode != COMPONENT_DUPE_SOURCES) - CRASH("Component '[type]' does not use sources but has been given a source") + return COMPONENT_INCOMPATIBLE LAZYOR(sources, source) /** @@ -461,7 +462,7 @@ component_type = new_component.type raw_args[1] = src - if(dupe_mode != COMPONENT_DUPE_ALLOWED && dupe_mode != COMPONENT_DUPE_SELECTIVE) + if(dupe_mode != COMPONENT_DUPE_ALLOWED && dupe_mode != COMPONENT_DUPE_SELECTIVE && dupe_mode != COMPONENT_DUPE_SOURCES) if(!dupe_type) old_component = GetExactComponent(component_type) else @@ -494,10 +495,14 @@ if(COMPONENT_DUPE_SOURCES) if(source in old_component.sources) return old_component // source already registered, no work to do - old_component.on_source_add(source) + + if(old_component.on_source_add(arglist(list(source) + raw_args.Copy(2))) == COMPONENT_INCOMPATIBLE) + stack_trace("incompatible source added to a [old_component.type]. Args: [json_encode(raw_args)]") + return null else if(!new_component) new_component = new component_type(raw_args) // There's a valid dupe mode but there's no old component, act like normal + else if(dupe_mode == COMPONENT_DUPE_SELECTIVE) var/list/arguments = raw_args.Copy() arguments[1] = new_component @@ -509,12 +514,17 @@ break if(!new_component && make_new_component) new_component = new component_type(raw_args) + + else if(dupe_mode == COMPONENT_DUPE_SOURCES) + new_component = new component_type(raw_args) + if(new_component.on_source_add(arglist(list(source) + raw_args.Copy(2))) == COMPONENT_INCOMPATIBLE) + stack_trace("incompatible source added to a [new_component.type]. Args: [json_encode(raw_args)]") + return null + else if(!new_component) new_component = new component_type(raw_args) // Dupes are allowed, act like normal if(!old_component && !QDELETED(new_component)) // Nothing related to duplicate components happened and the new component is healthy - if(uses_sources) // make sure they have the source added if they use sources - new_component.on_source_add(source) SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_component) return new_component diff --git a/code/datums/components/appearance_on_aggro.dm b/code/datums/components/appearance_on_aggro.dm index 85690a579ae8..8c0df88e6fdb 100644 --- a/code/datums/components/appearance_on_aggro.dm +++ b/code/datums/components/appearance_on_aggro.dm @@ -1,49 +1,80 @@ /** - * component gave to basic creatures to allow them to change appearance when find a target + * Changes visuals of the attached mob while it has a target */ /datum/component/appearance_on_aggro + /// Blackboardey to search for a target var/target_key = BB_BASIC_MOB_CURRENT_TARGET + /// Icon state to use when we have a target + var/aggro_state /// path of the overlay to apply - var/mutable_appearance/overlay_path + var/mutable_appearance/aggro_overlay /// visibility of our icon when aggroed var/alpha_on_aggro /// visibility of our icon when deaggroed var/alpha_on_deaggro + /// do we currently have a target + var/atom/current_target -/datum/component/appearance_on_aggro/Initialize(overlay_icon, overlay_state, alpha_on_aggro, alpha_on_deaggro) - if(!isliving(parent)) +/datum/component/appearance_on_aggro/Initialize(aggro_state, overlay_icon, overlay_state, alpha_on_aggro, alpha_on_deaggro) + if (!isliving(parent)) return COMPONENT_INCOMPATIBLE - if(overlay_icon && overlay_state) - src.overlay_path = mutable_appearance(overlay_icon, overlay_state) - if(!alpha_on_aggro || !alpha_on_deaggro) - return + src.aggro_state = aggro_state src.alpha_on_aggro = alpha_on_aggro src.alpha_on_deaggro = alpha_on_deaggro + if (!isnull(overlay_icon) && !isnull(overlay_state)) + aggro_overlay = mutable_appearance(overlay_icon, overlay_state) /datum/component/appearance_on_aggro/RegisterWithParent() - RegisterSignal(parent, COMSIG_AI_BLACKBOARD_KEY_SET(target_key), PROC_REF(change_appearance)) - RegisterSignal(parent, COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_key), PROC_REF(revert_appearance)) + RegisterSignal(parent, COMSIG_AI_BLACKBOARD_KEY_SET(target_key), PROC_REF(on_set_target)) + RegisterSignal(parent, COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_key), PROC_REF(on_clear_target)) + if (!isnull(aggro_state)) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON_STATE, PROC_REF(on_icon_state_updated)) + if (!isnull(aggro_overlay)) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_overlays_updated)) /datum/component/appearance_on_aggro/UnregisterFromParent() . = ..() UnregisterSignal(parent, list(COMSIG_AI_BLACKBOARD_KEY_SET(target_key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_key))) -/datum/component/appearance_on_aggro/proc/change_appearance(mob/living/source) +/datum/component/appearance_on_aggro/proc/on_set_target(mob/living/source) SIGNAL_HANDLER var/atom/target = source.ai_controller.blackboard[target_key] - if(isnull(target)) + if (QDELETED(target)) return - if(overlay_path) - source.add_overlay(overlay_path) - if(alpha_on_aggro) + + current_target = target + if (!isnull(aggro_overlay) || !isnull(aggro_state)) + source.update_appearance(UPDATE_ICON) + if (!isnull(alpha_on_aggro)) animate(source, alpha = alpha_on_aggro, time = 2 SECONDS) -/datum/component/appearance_on_aggro/proc/revert_appearance(mob/living/source) +/datum/component/appearance_on_aggro/Destroy() + if (!isnull(current_target)) + revert_appearance(parent) + return ..() + +/datum/component/appearance_on_aggro/proc/on_clear_target(atom/source) SIGNAL_HANDLER + revert_appearance(parent) - if(overlay_path) - source.cut_overlay(overlay_path) - if(alpha_on_deaggro) +/datum/component/appearance_on_aggro/proc/revert_appearance(mob/living/source) + current_target = null + if (!isnull(aggro_overlay) || !isnull(aggro_state)) + source.update_appearance(UPDATE_ICON) + if (!isnull(alpha_on_deaggro)) animate(source, alpha = alpha_on_deaggro, time = 2 SECONDS) + +/datum/component/appearance_on_aggro/proc/on_icon_state_updated(mob/living/source) + SIGNAL_HANDLER + if (source.stat == DEAD) + return + source.icon_state = isnull(current_target) ? initial(source.icon_state) : aggro_state + +/datum/component/appearance_on_aggro/proc/on_overlays_updated(atom/source, list/overlays) + SIGNAL_HANDLER + + if (isnull(current_target)) + return + overlays += aggro_overlay diff --git a/code/datums/components/area_based_godmode.dm b/code/datums/components/area_based_godmode.dm new file mode 100644 index 000000000000..4f03ae57794c --- /dev/null +++ b/code/datums/components/area_based_godmode.dm @@ -0,0 +1,123 @@ +#define MAP_AREA_TYPE "area_type" +#define MAP_ALLOW_AREA_SUBTYPES "allow_area_subtypes" +#define DEFAULT_GAIN_MESSAGE span_big(span_green("You are now invulnerable.")) +#define DEFAULT_LOSE_MESSAGE span_big(span_red("You are no longer invulnerable.")) + +/** + * Area-based godmode. + * Gain and Lose message can only be set once, at initial component creation; adding a source will not update them. + */ +/datum/component/area_based_godmode + dupe_mode = COMPONENT_DUPE_SOURCES + + /// The type of area that will trigger godmode. + var/list/sources_to_area_type + + /// Whether or not to allow subtypes of the area type to trigger godmode. + var/allow_area_subtypes + + /// The message to send to the mob when they gain godmode. + var/gain_message + + /// The message to send to the mob when they lose godmode. + var/lose_message + + /// Cached state of check_area, prevents recalculating on source add + var/check_area_cached_state = FALSE + +/datum/component/area_based_godmode/Initialize( + area_type, + allow_area_subtypes, + gain_message = DEFAULT_GAIN_MESSAGE, + lose_message = DEFAULT_LOSE_MESSAGE, +) + var/mob/mob_target = parent + if(!istype(mob_target)) + return COMPONENT_INCOMPATIBLE + if(initial(mob_target.status_flags) & GODMODE) + return COMPONENT_INCOMPATIBLE + + sources_to_area_type = list() + src.gain_message = gain_message + src.lose_message = lose_message + RegisterSignal(mob_target, COMSIG_ENTER_AREA, PROC_REF(check_area)) + +/datum/component/area_based_godmode/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_ENTER_AREA) + +/datum/component/area_based_godmode/on_source_add( + source, + area_type, + allow_area_subtypes = FALSE, + gain_message = DEFAULT_GAIN_MESSAGE, + lose_message = DEFAULT_LOSE_MESSAGE, +) + . = ..() + if(. == COMPONENT_INCOMPATIBLE) + return + + var/list/information_map = list( + MAP_AREA_TYPE = area_type, + MAP_ALLOW_AREA_SUBTYPES = allow_area_subtypes, + ) + sources_to_area_type[source] = information_map + + var/mob/mob_target = parent // no need to istype here, done at creation + mob_target.become_area_sensitive("[REF(src)]:[source]") + if(!check_area_cached_state) + check_area(mob_target) + +/datum/component/area_based_godmode/on_source_remove(source) + sources_to_area_type -= source + var/mob/mob_target = parent + mob_target.lose_area_sensitivity("[REF(src)]:[source]") + if(check_area_cached_state) + check_area(mob_target) + return ..() + +/datum/component/area_based_godmode/proc/check_in_valid_area(mob/checking) + var/list/area/allowed_areas = list() + for(var/source in sources_to_area_type) + var/list/source_map = sources_to_area_type[source] + var/area/top_level = source_map[MAP_AREA_TYPE] + if(!allowed_areas[top_level]) + allowed_areas[top_level] = source_map[MAP_ALLOW_AREA_SUBTYPES] + + if(!length(allowed_areas)) + stack_trace("called check_in_valid_area with zero sources") + return FALSE + + var/area/area = get_area(checking) + if(area.type in allowed_areas) + return TRUE + + for(var/area/allowed_area as anything in allowed_areas) + if(!allowed_areas[allowed_area]) + continue + if(istype(area, allowed_area)) + return TRUE + + return FALSE + +/datum/component/area_based_godmode/proc/check_area(mob/source) + SIGNAL_HANDLER + + var/has_godmode = source.status_flags & GODMODE + if(!check_in_valid_area(source)) + if(has_godmode) + to_chat(source, lose_message) + source.status_flags ^= GODMODE + check_area_cached_state = FALSE + return + + check_area_cached_state = TRUE + if(has_godmode) + return + + to_chat(source, gain_message) + source.status_flags ^= GODMODE + +#undef MAP_AREA_TYPE +#undef MAP_ALLOW_AREA_SUBTYPES +#undef DEFAULT_GAIN_MESSAGE +#undef DEFAULT_LOSE_MESSAGE diff --git a/code/datums/components/basic_inhands.dm b/code/datums/components/basic_inhands.dm new file mode 100644 index 000000000000..ac50f618861f --- /dev/null +++ b/code/datums/components/basic_inhands.dm @@ -0,0 +1,50 @@ +/** + * Basic handling for showing held items in a mob's hands + */ +/datum/component/basic_inhands + /// Layer index we show our inhands upon + var/display_layer + /// Y offset to apply to inhands + var/y_offset + /// X offset to apply to inhands, is inverted for the left hand + var/x_offset + /// What overlays are we currently showing? + var/list/cached_overlays + +/datum/component/basic_inhands/Initialize(display_layer = 1, y_offset = 0, x_offset = 0) + . = ..() + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE + src.display_layer = display_layer + src.y_offset = y_offset + src.x_offset = x_offset + cached_overlays = list() + +/datum/component/basic_inhands/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_updated_overlays)) + RegisterSignal(parent, COMSIG_MOB_UPDATE_HELD_ITEMS, PROC_REF(on_updated_held_items)) + +/datum/component/basic_inhands/UnregisterFromParent() + . = ..() + UnregisterSignal(parent, list(COMSIG_ATOM_UPDATE_OVERLAYS, COMSIG_MOB_UPDATE_HELD_ITEMS)) + +/// When your overlays update, add your held overlays +/datum/component/basic_inhands/proc/on_updated_overlays(atom/parent_atom, list/overlays) + SIGNAL_HANDLER + overlays += cached_overlays + +/// When your number of held items changes, regenerate held icons +/datum/component/basic_inhands/proc/on_updated_held_items(mob/living/holding_mob) + SIGNAL_HANDLER + var/list/held_overlays = list() + for(var/obj/item/held in holding_mob.held_items) + var/is_right = holding_mob.get_held_index_of_item(held) % 2 == 0 + var/icon_file = is_right ? held.righthand_file : held.lefthand_file + var/mutable_appearance/held_overlay = held.build_worn_icon(default_layer = HANDS_LAYER, default_icon_file = icon_file, isinhands = TRUE) + held_overlay.pixel_y += y_offset + held_overlay.pixel_x += x_offset * (is_right ? 1 : -1) + held_overlays += held_overlay + + cached_overlays = held_overlays + holding_mob.update_appearance(UPDATE_OVERLAYS) diff --git a/code/datums/components/basic_mob_attack_telegraph.dm b/code/datums/components/basic_mob_attack_telegraph.dm index 9395937deb68..e693ff61d9ab 100644 --- a/code/datums/components/basic_mob_attack_telegraph.dm +++ b/code/datums/components/basic_mob_attack_telegraph.dm @@ -66,7 +66,7 @@ return ADD_TRAIT(source, TRAIT_BASIC_ATTACK_FORECAST, REF(src)) forget_target(target) - source.melee_attack(target) + source.melee_attack(target, ignore_cooldown = TRUE) // We already started the cooldown when we triggered the forecast /// The guy we're trying to attack moved, is he still in range? /datum/component/basic_mob_attack_telegraph/proc/target_moved(atom/target) diff --git a/code/datums/components/blob_minion.dm b/code/datums/components/blob_minion.dm new file mode 100644 index 000000000000..876ddc87cfeb --- /dev/null +++ b/code/datums/components/blob_minion.dm @@ -0,0 +1,154 @@ +/** + * Common behaviour shared by things which are minions to a blob + */ +/datum/component/blob_minion + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + /// Overmind who is our boss + var/mob/camera/blob/overmind + /// Callback to run if overmind strain changes + var/datum/callback/on_strain_changed + +/datum/component/blob_minion/Initialize(mob/camera/blob/overmind, datum/callback/on_strain_changed) + . = ..() + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE + src.on_strain_changed = on_strain_changed + register_overlord(overmind) + +/datum/component/blob_minion/InheritComponent(datum/component/new_comp, i_am_original, mob/camera/blob/overmind, datum/callback/on_strain_changed) + if (!isnull(on_strain_changed)) + src.on_strain_changed = on_strain_changed + register_overlord(overmind) + +/datum/component/blob_minion/proc/register_overlord(mob/camera/blob/overmind) + if (isnull(overmind)) + return + src.overmind = overmind + overmind.register_new_minion(parent) + RegisterSignal(overmind, COMSIG_PARENT_QDELETING, PROC_REF(overmind_deleted)) + RegisterSignal(overmind, COMSIG_BLOB_SELECTED_STRAIN, PROC_REF(overmind_properties_changed)) + overmind_properties_changed(overmind, overmind.blobstrain) + +/// Our overmind is gone, uh oh! +/datum/component/blob_minion/proc/overmind_deleted() + SIGNAL_HANDLER + overmind = null + overmind_properties_changed() + +/// Our overmind has changed colour and properties +/datum/component/blob_minion/proc/overmind_properties_changed(mob/camera/blob/overmind, datum/blobstrain/new_strain) + SIGNAL_HANDLER + var/mob/living/living_parent = parent + living_parent.update_appearance(UPDATE_ICON) + on_strain_changed?.Invoke(overmind, new_strain) + +/datum/component/blob_minion/RegisterWithParent() + var/mob/living/living_parent = parent + living_parent.pass_flags |= PASSBLOB + living_parent.faction |= ROLE_BLOB + ADD_TRAIT(parent, TRAIT_BLOB_ALLY, REF(src)) + remove_verb(parent, /mob/living/verb/pulled) // No dragging people into the blob + RegisterSignal(parent, COMSIG_MOB_MIND_INITIALIZED, PROC_REF(on_mind_init)) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON, PROC_REF(on_update_appearance)) + RegisterSignal(parent, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(on_update_status_tab)) + RegisterSignal(parent, COMSIG_ATOM_BLOB_ACT, PROC_REF(on_blob_touched)) + RegisterSignal(parent, COMSIG_ATOM_FIRE_ACT, PROC_REF(on_burned)) + RegisterSignal(parent, COMSIG_ATOM_TRIED_PASS, PROC_REF(on_attempted_pass)) + RegisterSignal(parent, COMSIG_MOVABLE_SPACEMOVE, PROC_REF(on_space_move)) + RegisterSignal(parent, COMSIG_LIVING_TRY_SPEECH, PROC_REF(on_try_speech)) + RegisterSignal(parent, COMSIG_MOB_CHANGED_TYPE, PROC_REF(on_transformed)) + living_parent.update_appearance(UPDATE_ICON) + GLOB.blob_telepathy_mobs |= parent + +/datum/component/blob_minion/UnregisterFromParent() + if (!isnull(overmind)) + overmind.blob_mobs -= parent + var/mob/living/living_parent = parent + living_parent.pass_flags &= ~PASSBLOB + living_parent.faction -= ROLE_BLOB + REMOVE_TRAIT(parent, TRAIT_BLOB_ALLY, REF(src)) + add_verb(parent, /mob/living/verb/pulled) + UnregisterSignal(parent, list( + COMSIG_ATOM_BLOB_ACT, + COMSIG_ATOM_FIRE_ACT, + COMSIG_ATOM_TRIED_PASS, + COMSIG_ATOM_UPDATE_ICON, + COMSIG_LIVING_TRY_SPEECH, + COMSIG_MOB_CHANGED_TYPE, + COMSIG_MOB_GET_STATUS_TAB_ITEMS, + COMSIG_MOB_MIND_INITIALIZED, + COMSIG_MOVABLE_SPACEMOVE, + )) + GLOB.blob_telepathy_mobs -= parent + +/// Become blobpilled when we gain a mind +/datum/component/blob_minion/proc/on_mind_init(mob/living/minion, datum/mind/new_mind) + SIGNAL_HANDLER + if (isnull(overmind)) + return + var/datum/antagonist/blob_minion/minion_motive = new(overmind) + new_mind.add_antag_datum(minion_motive) + +/// When our icon is updated, update our colour too +/datum/component/blob_minion/proc/on_update_appearance(mob/living/minion) + SIGNAL_HANDLER + if(isnull(overmind)) + minion.remove_atom_colour(FIXED_COLOUR_PRIORITY) + return + minion.add_atom_colour(overmind.blobstrain.color, FIXED_COLOUR_PRIORITY) + +/// When our icon is updated, update our colour too +/datum/component/blob_minion/proc/on_update_status_tab(mob/living/minion, list/status_items) + SIGNAL_HANDLER + if (isnull(overmind)) + return + status_items += "Blobs to Win: [length(overmind.blobs_legit)]/[overmind.blobwincount]" + +/// If we feel the gentle caress of a blob, we feel better +/datum/component/blob_minion/proc/on_blob_touched(mob/living/minion) + SIGNAL_HANDLER + if(minion.stat == DEAD || minion.health >= minion.maxHealth) + return COMPONENT_CANCEL_BLOB_ACT // Don't hurt us in order to heal us + for(var/i in 1 to 2) + var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(parent)) // hello yes you are being healed + heal_effect.color = isnull(overmind) ? COLOR_BLACK : overmind.blobstrain.complementary_color + minion.heal_overall_damage(minion.maxHealth * BLOBMOB_HEALING_MULTIPLIER) + return COMPONENT_CANCEL_BLOB_ACT + +/// If we feel the fearsome bite of open flame, we feel worse +/datum/component/blob_minion/proc/on_burned(mob/living/minion, exposed_temperature, exposed_volume) + SIGNAL_HANDLER + if(isnull(exposed_temperature)) + minion.adjustFireLoss(5) + return + minion.adjustFireLoss(clamp(0.01 * exposed_temperature, 1, 5)) + +/// Someone is attempting to move through us, allow it if it is a blob tile +/datum/component/blob_minion/proc/on_attempted_pass(mob/living/minion, atom/movable/incoming) + SIGNAL_HANDLER + if(istype(incoming, /obj/structure/blob)) + return COMSIG_COMPONENT_PERMIT_PASSAGE + +/// If we're near a blob, stop drifting +/datum/component/blob_minion/proc/on_space_move(mob/living/minion) + SIGNAL_HANDLER + var/obj/structure/blob/blob_handhold = locate() in range(1, parent) + if (!isnull(blob_handhold)) + return COMSIG_MOVABLE_STOP_SPACEMOVE + +/// We only speak telepathically to blobs +/datum/component/blob_minion/proc/on_try_speech(mob/living/minion, message, ignore_spam, forced) + SIGNAL_HANDLER + var/spanned_message = minion.say_quote(message) + var/rendered = span_blob("\[Blob Telepathy\] [minion.real_name] [spanned_message]") + relay_to_list_and_observers(rendered, GLOB.blob_telepathy_mobs, minion) + return COMPONENT_CANNOT_SPEAK + +/// Called when a blob minion is transformed into something else, hopefully a spore into a zombie +/datum/component/blob_minion/proc/on_transformed(mob/living/minion, mob/living/replacement) + SIGNAL_HANDLER + overmind?.assume_direct_control(replacement) + +/datum/component/blob_minion/PostTransfer() + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/breeding.dm b/code/datums/components/breeding.dm new file mode 100644 index 000000000000..9f5a9e278f1f --- /dev/null +++ b/code/datums/components/breeding.dm @@ -0,0 +1,76 @@ +/* + * A component to allow us to breed + */ +/datum/component/breed + /// additional mobs we can breed with + var/list/can_breed_with + ///path of the baby + var/baby_path + ///time to wait after breeding + var/breed_timer + ///AI key we set when we're ready to breed + var/breed_key = BB_BREED_READY + ///are we ready to breed? + var/ready_to_breed = TRUE + ///callback after we give birth to the child + var/datum/callback/post_birth + +/datum/component/breed/Initialize(list/can_breed_with = list(), breed_timer = 40 SECONDS, baby_path, post_birth) + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + if(ishuman(parent)) //sin detected + return COMPONENT_INCOMPATIBLE + + if(!ispath(baby_path)) + stack_trace("attempted to add a breeding component with invalid baby path!") + return + + src.can_breed_with = can_breed_with + src.breed_timer = breed_timer + src.baby_path = baby_path + src.post_birth = post_birth + + ADD_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type) + +/datum/component/breed/RegisterWithParent() + RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(breed_with_partner)) + ADD_TRAIT(parent, TRAIT_MOB_BREEDER, REF(src)) + var/mob/living/parent_mob = parent + parent_mob.ai_controller?.set_blackboard_key(breed_key, TRUE) + +/datum/component/breed/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET) + REMOVE_TRAIT(parent, TRAIT_MOB_BREEDER, REF(src)) + post_birth = null + + +/datum/component/breed/proc/breed_with_partner(mob/living/source, mob/living/target) + SIGNAL_HANDLER + + if(source.istate & ISTATE_HARM) + return + + if(!is_type_in_typecache(target, can_breed_with)) + return + + if(!HAS_TRAIT(target, TRAIT_MOB_BREEDER) || target.gender == source.gender) + return + + if(!ready_to_breed) + source.balloon_alert(source, "not ready!") + return COMPONENT_HOSTILE_NO_ATTACK + + var/turf/delivery_destination = get_turf(source) + var/mob/living/baby = new baby_path(delivery_destination) + new /obj/effect/temp_visual/heart(delivery_destination) + toggle_status(source) + + addtimer(CALLBACK(src, PROC_REF(toggle_status), source), breed_timer) + post_birth?.Invoke(baby, target) + return COMPONENT_HOSTILE_NO_ATTACK + +/datum/component/breed/proc/toggle_status(mob/living/source) + ready_to_breed = !ready_to_breed + source.ai_controller?.set_blackboard_key(BB_BREED_READY, ready_to_breed) + diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm index 2f0175b1eee3..4ff742004592 100644 --- a/code/datums/components/butchering.dm +++ b/code/datums/components/butchering.dm @@ -87,10 +87,8 @@ log_combat(user, H, "wounded via throat slitting", source) H.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD, wound_bonus=CANT_WOUND) // easy tiger, we'll get to that in a sec var/obj/item/bodypart/slit_throat = H.get_bodypart(BODY_ZONE_HEAD) - if(slit_throat) - var/datum/wound/slash/critical/screaming_through_a_slit_throat = new - screaming_through_a_slit_throat.apply_wound(slit_throat) - H.apply_status_effect(/datum/status_effect/neck_slice) + if (H.cause_wound_of_type_and_severity(WOUND_SLASH, slit_throat, WOUND_SEVERITY_CRITICAL)) + H.apply_status_effect(/datum/status_effect/neck_slice) /** * Handles a user butchering a target diff --git a/code/datums/components/chasm.dm b/code/datums/components/chasm.dm index dbcacdfeaaf4..afbe6de22228 100644 --- a/code/datums/components/chasm.dm +++ b/code/datums/components/chasm.dm @@ -137,7 +137,7 @@ dropped_thing.visible_message(span_boldwarning("[dropped_thing] falls into [parent]!"), span_userdanger("[oblivion_message]")) if (isliving(dropped_thing)) var/mob/living/falling_mob = dropped_thing - falling_mob.notransform = TRUE + ADD_TRAIT(falling_mob, TRAIT_NO_TRANSFORM, REF(src)) falling_mob.Paralyze(20 SECONDS) var/oldtransform = dropped_thing.transform @@ -174,7 +174,7 @@ else if(isliving(dropped_thing)) var/mob/living/fallen_mob = dropped_thing - fallen_mob.notransform = FALSE + REMOVE_TRAIT(fallen_mob, TRAIT_NO_TRANSFORM, REF(src)) if (fallen_mob.stat != DEAD) fallen_mob.investigate_log("has died from falling into a chasm.", INVESTIGATE_DEATHS) fallen_mob.death(TRUE) diff --git a/code/datums/components/cult_ritual_item.dm b/code/datums/components/cult_ritual_item.dm index b76c1fd7acb8..5922e05586fa 100644 --- a/code/datums/components/cult_ritual_item.dm +++ b/code/datums/components/cult_ritual_item.dm @@ -304,7 +304,7 @@ ) if(cultist.blood_volume) - cultist.apply_damage(initial(rune_to_scribe.scribe_damage), BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM), wound_bonus = CANT_WOUND) // *cuts arm* *bone explodes* ever have one of those days? + cultist.apply_damage(initial(rune_to_scribe.scribe_damage), BRUTE, pick(GLOB.arm_zones), wound_bonus = CANT_WOUND) // *cuts arm* *bone explodes* ever have one of those days? var/scribe_mod = initial(rune_to_scribe.scribe_delay) if(!initial(rune_to_scribe.no_scribe_boost) && (our_turf.type in turfs_that_boost_us)) diff --git a/code/datums/components/curse_of_polymorph.dm b/code/datums/components/curse_of_polymorph.dm new file mode 100644 index 000000000000..bbfb6c842e30 --- /dev/null +++ b/code/datums/components/curse_of_polymorph.dm @@ -0,0 +1,36 @@ +/** + * curse of polymorph component; + * + * Used as a rpgloot suffix and wizard spell! + */ +/datum/component/curse_of_polymorph + var/polymorph_type + +/datum/component/curse_of_polymorph/Initialize(polymorph_type) + . = ..() + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + src.polymorph_type = polymorph_type + +/datum/component/curse_of_polymorph/RegisterWithParent() + . = ..() + var/obj/item/cursed_item = parent + RegisterSignal(cursed_item, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip)) + +/datum/component/curse_of_polymorph/UnregisterFromParent() + . = ..() + UnregisterSignal(parent, list( + COMSIG_ITEM_EQUIPPED, + )) + +///signal called from equipping parent +/datum/component/curse_of_polymorph/proc/on_equip(datum/source, mob/living/equipper, slot) + SIGNAL_HANDLER + var/obj/item/polymorpher_item = parent + // Items with no slot flags curse on pickup (because hand slot) + if(polymorpher_item.slot_flags && !(polymorpher_item.slot_flags & slot)) + return + ASYNC + equipper.dropItemToGround(polymorpher_item, TRUE) + equipper.wabbajack(polymorph_type) + diff --git a/code/datums/components/damage_chain.dm b/code/datums/components/damage_chain.dm new file mode 100644 index 000000000000..e472083c4a11 --- /dev/null +++ b/code/datums/components/damage_chain.dm @@ -0,0 +1,112 @@ +/** + * Draws a line between you and another atom, hurt anyone stood in the line + */ +/datum/component/damage_chain + dupe_mode = COMPONENT_DUPE_ALLOWED + /// How often do we attempt to deal damage? + var/tick_interval + /// Tracks when we can next deal damage + COOLDOWN_DECLARE(tick_cooldown) + /// Damage inflicted per tick + var/damage_per_tick + /// Type of damage to inflict + var/damage_type + /// Optional callback which checks if we can damage the target + var/datum/callback/validate_target + /// Optional callback for additional visuals or text display when dealing damage + var/datum/callback/chain_damage_feedback + /// We will fire the damage feedback callback on every x successful attacks + var/feedback_interval + /// How many successful attacks have we made? + var/successful_attacks = 0 + /// Time between making any attacks at which we just reset the successful attack counter + var/reset_feedback_timer = 0 + /// Our chain + var/datum/beam/chain + +/datum/component/damage_chain/Initialize( + atom/linked_to, + max_distance = 7, + beam_icon = 'icons/effects/beam.dmi', + beam_state = "medbeam", + beam_type = /obj/effect/ebeam, + tick_interval = 0.3 SECONDS, + damage_per_tick = 1.2, + damage_type = BURN, + datum/callback/validate_target = null, + datum/callback/chain_damage_feedback = null, + feedback_interval = 5, +) + . = ..() + if (!isatom(parent)) + return COMPONENT_INCOMPATIBLE + if (!isatom(linked_to)) + CRASH("Attempted to create [type] linking [parent.type] with non-atom [linked_to]!") + + src.tick_interval = tick_interval + src.damage_per_tick = damage_per_tick + src.damage_type = damage_type + src.validate_target = validate_target + src.chain_damage_feedback = chain_damage_feedback + src.feedback_interval = feedback_interval + + var/atom/atom_parent = parent + chain = atom_parent.Beam(linked_to, icon = beam_icon, icon_state = beam_state, beam_type = beam_type, maxdistance = max_distance) + RegisterSignal(chain, COMSIG_PARENT_QDELETING, PROC_REF(end_beam)) + START_PROCESSING(SSfastprocess, src) + +/datum/component/damage_chain/RegisterWithParent() + RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(end_beam)) // We actually don't really use many signals it's all processing + +/datum/component/damage_chain/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_LIVING_DEATH) + +/datum/component/damage_chain/Destroy(force, silent) + if (!QDELETED(chain)) + UnregisterSignal(chain, COMSIG_PARENT_QDELETING) + QDEL_NULL(chain) + chain = null + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/// Destroy ourself +/datum/component/damage_chain/proc/end_beam() + SIGNAL_HANDLER + qdel(src) + +/datum/component/damage_chain/process(seconds_per_tick) + var/successful_hit = FALSE + var/list/target_turfs = list() + for(var/obj/effect/ebeam/chainpart in chain.elements) + if (isnull(chainpart) || !chainpart.x || !chainpart.y || !chainpart.z) + continue + var/turf/overlaps = get_turf_pixel(chainpart) + target_turfs |= overlaps + if(overlaps == get_turf(chain.origin) || overlaps == get_turf(chain.target)) + continue + for(var/turf/nearby_turf in circle_range(overlaps, 1)) + target_turfs |= nearby_turf + + for(var/turf/hit_turf as anything in target_turfs) + for(var/mob/living/victim in hit_turf) + if (victim == parent || victim.stat == DEAD) + continue + if (!isnull(validate_target) && !validate_target.Invoke(victim)) + continue + if (successful_attacks == 0) + chain_damage_feedback?.Invoke(victim) + victim.apply_damage(damage_per_tick, damage_type, wound_bonus = CANT_WOUND) + successful_hit = TRUE + + if (isnull(chain_damage_feedback)) + return + if (successful_hit) + successful_attacks++ + reset_feedback_timer = addtimer(CALLBACK(src, PROC_REF(reset_feedback)), 10 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE|TIMER_DELETE_ME) + if (successful_attacks > feedback_interval) + reset_feedback() + +/// Make it so that the next time we hit something we'll invoke the feedback callback +/datum/component/damage_chain/proc/reset_feedback() + successful_attacks = 0 + deltimer(reset_feedback_timer) diff --git a/code/datums/components/direct_explosive_trap.dm b/code/datums/components/direct_explosive_trap.dm new file mode 100644 index 000000000000..136c29afc11d --- /dev/null +++ b/code/datums/components/direct_explosive_trap.dm @@ -0,0 +1,85 @@ +/** + * Responds to certain signals and 'explodes' on the person using the item. + * Differs from `interaction_booby_trap` in that this doesn't actually explode, it just directly calls ex_act on one person. + */ +/datum/component/direct_explosive_trap + /// An optional mob to inform about explosions + var/mob/living/saboteur + /// Amount of force to apply + var/explosive_force + /// Colour for examine notification + var/glow_colour + /// Optional additional target checks before we go off + var/datum/callback/explosive_checks + /// Signals which set off the bomb, must pass a mob as the first non-source argument + var/list/triggering_signals + +/datum/component/direct_explosive_trap/Initialize( + mob/living/saboteur, + explosive_force = EXPLODE_HEAVY, + expire_time = 1 MINUTES, + glow_colour = COLOR_RED, + datum/callback/explosive_checks, + list/triggering_signals = list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_BUMPED) +) + . = ..() + if (!isatom(parent)) + return COMPONENT_INCOMPATIBLE + src.saboteur = saboteur + src.explosive_force = explosive_force + src.glow_colour = glow_colour + src.explosive_checks = explosive_checks + src.triggering_signals = triggering_signals + + if (expire_time > 0) + addtimer(CALLBACK(src, PROC_REF(bomb_expired)), expire_time, TIMER_DELETE_ME) + +/datum/component/direct_explosive_trap/RegisterWithParent() + if (!(COMSIG_PARENT_EXAMINE in triggering_signals)) // Maybe you're being extra mean with this one + RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examined)) + RegisterSignals(parent, triggering_signals, PROC_REF(explode)) + if (!isnull(saboteur)) + RegisterSignal(saboteur, COMSIG_PARENT_QDELETING, PROC_REF(on_bomber_deleted)) + +/datum/component/direct_explosive_trap/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_PARENT_EXAMINE) + triggering_signals) + if (!isnull(saboteur)) + UnregisterSignal(saboteur, COMSIG_PARENT_QDELETING) + +/datum/component/direct_explosive_trap/Destroy(force, silent) + if (isnull(saboteur)) + return ..() + UnregisterSignal(saboteur, COMSIG_PARENT_QDELETING) + saboteur = null + return ..() + +/// Called if we sit too long without going off +/datum/component/direct_explosive_trap/proc/bomb_expired() + if (!isnull(saboteur)) + to_chat(saboteur, span_bolddanger("Failure! Your trap didn't catch anyone this time...")) + qdel(src) + +/// Let people know something is up +/datum/component/direct_explosive_trap/proc/on_examined(datum/source, mob/user, text) + SIGNAL_HANDLER + text += span_holoparasite("It glows with a strange light...") + +/// Blow up +/datum/component/direct_explosive_trap/proc/explode(atom/source, mob/living/victim) + SIGNAL_HANDLER + if (!isliving(victim)) + return + if (!isnull(explosive_checks) && !explosive_checks.Invoke(victim)) + return + to_chat(victim, span_bolddanger("[source] was boobytrapped!")) + if (!isnull(saboteur)) + to_chat(saboteur, span_bolddanger("Success! Your trap on [source] caught [victim.name]!")) + playsound(source, 'sound/effects/explosion2.ogg', 200, TRUE) + new /obj/effect/temp_visual/explosion(get_turf(source)) + EX_ACT(victim, explosive_force) + qdel(src) + +/// Don't hang a reference to the person who placed the bomb +/datum/component/direct_explosive_trap/proc/on_bomber_deleted() + SIGNAL_HANDLER + saboteur = null diff --git a/code/datums/components/embedded.dm b/code/datums/components/embedded.dm index 8c69fd8100f5..f0c82e61ddf4 100644 --- a/code/datums/components/embedded.dm +++ b/code/datums/components/embedded.dm @@ -93,7 +93,8 @@ if(harmful) victim.throw_alert(ALERT_EMBEDDED_OBJECT, /atom/movable/screen/alert/embeddedobject) playsound(victim,'sound/weapons/bladeslice.ogg', 40) - weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody! + if (limb.can_bleed()) + weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody! damage += weapon.w_class * impact_pain_mult victim.add_mood_event("embedded", /datum/mood_event/embedded) @@ -303,7 +304,7 @@ return var/damage = weapon.w_class * remove_pain_mult limb.receive_damage(brute=(1-pain_stam_pct) * damage * 1.5, sharpness=SHARP_EDGED) // Performs exit wounds and flings the user to the caster if nearby - limb.force_wound_upwards(/datum/wound/pierce/moderate) + victim.cause_wound_of_type_and_severity(WOUND_PIERCE, limb, WOUND_SEVERITY_MODERATE) victim.stamina.adjust(-pain_stam_pct * damage) playsound(get_turf(victim), 'sound/effects/wounds/blood2.ogg', 50, TRUE) diff --git a/code/datums/components/explodable.dm b/code/datums/components/explodable.dm index bf59f7aa3bb5..0cb2a5458024 100644 --- a/code/datums/components/explodable.dm +++ b/code/datums/components/explodable.dm @@ -91,13 +91,16 @@ detonate() ///Called when you attack a specific body part of the thing this is equipped on. Useful for exploding pants. -/datum/component/explodable/proc/explodable_attack_zone(datum/source, damage, damagetype, def_zone) +/datum/component/explodable/proc/explodable_attack_zone(datum/source, damage, damagetype, def_zone, ...) SIGNAL_HANDLER if(!def_zone) return if(damagetype != BURN) //Don't bother if it's not fire. return + if(isbodypart(def_zone)) + var/obj/item/bodypart/hitting = def_zone + def_zone = hitting.body_zone if(!is_hitting_zone(def_zone)) //You didn't hit us! ha! return detonate() diff --git a/code/datums/components/fantasy/_fantasy.dm b/code/datums/components/fantasy/_fantasy.dm index 164d97aefd46..31f969e61932 100644 --- a/code/datums/components/fantasy/_fantasy.dm +++ b/code/datums/components/fantasy/_fantasy.dm @@ -17,16 +17,18 @@ if(!isitem(parent)) return COMPONENT_INCOMPATIBLE - src.quality = quality || randomQuality() + src.quality = quality + if(isnull(src.quality)) + src.quality = random_quality() src.canFail = canFail src.announce = announce src.affixes = affixes appliedComponents = list() if(affixes && affixes.len) - setAffixes() + set_affixes() else - randomAffixes() + random_affixes() /datum/component/fantasy/Destroy() unmodify() @@ -37,6 +39,11 @@ var/obj/item/master = parent originalName = master.name modify() + RegisterSignal(parent, COMSIG_STACK_CAN_MERGE, PROC_REF(try_merge_stack)) + +/datum/component/fantasy/proc/try_merge_stack(obj/item/stack/to_merge, in_hand) + SIGNAL_HANDLER + return CANCEL_STACK_MERGE /datum/component/fantasy/UnregisterFromParent() unmodify() @@ -53,14 +60,20 @@ src.announce = announce || src.announce modify() -/datum/component/fantasy/proc/randomQuality() +/datum/component/fantasy/proc/random_quality() var/quality = pick(1;15, 2;14, 2;13, 2;12, 3;11, 3;10, 3;9, 4;8, 4;7, 4;6, 5;5, 5;4, 5;3, 6;2, 6;1, 6;0) if(prob(50)) quality = -quality return quality ///proc on creation for random affixes -/datum/component/fantasy/proc/randomAffixes(force) +/datum/component/fantasy/proc/random_affixes(force) + var/alignment + if(quality >= 0) + alignment |= AFFIX_GOOD + if(quality <= 0) + alignment |= AFFIX_EVIL + if(!affixListing) affixListing = list() for(var/T in subtypesof(/datum/fantasy_affix)) @@ -72,12 +85,6 @@ return affixes = list() - var/alignment - if(quality >= 0) - alignment |= AFFIX_GOOD - if(quality <= 0) - alignment |= AFFIX_EVIL - var/usedSlots = NONE for(var/i in 1 to max(1, abs(quality))) // We want at least 1 affix applied var/datum/fantasy_affix/affix = pick_weight(affixListing) @@ -91,7 +98,7 @@ usedSlots |= affix.placement ///proc on creation for specific affixes given to the fantasy component -/datum/component/fantasy/proc/setAffixes(force) +/datum/component/fantasy/proc/set_affixes(force) var/usedSlots = NONE for(var/datum/fantasy_affix/affix in affixes) // We want at least 1 affix applied if((affix.placement & usedSlots) || (!affix.validate(parent))) @@ -101,12 +108,7 @@ /datum/component/fantasy/proc/modify() var/obj/item/master = parent - - master.force = max(0, master.force + quality) - master.throwforce = max(0, master.throwforce + quality) - master.set_armor(master.get_armor().generate_new_with_modifiers(list(ARMOR_ALL = quality))) - master.wound_bonus += quality - master.bare_wound_bonus += quality + master.apply_fantasy_bonuses(quality) var/newName = originalName for(var/i in affixes) @@ -121,10 +123,10 @@ place.visible_message(span_danger("[parent] [span_blue("violently glows blue")] for a while, then evaporates.")) master.burn() return - else if(announce) - announce() master.name = newName + if(announce) + announce() /datum/component/fantasy/proc/unmodify() var/obj/item/master = parent @@ -133,12 +135,7 @@ var/datum/fantasy_affix/affix = i affix.remove(src) QDEL_LIST(appliedComponents) - - master.force = max(0, master.force - quality) - master.throwforce = max(0, master.throwforce - quality) - master.set_armor(master.get_armor().generate_new_with_modifiers(list(ARMOR_ALL = -quality))) - master.wound_bonus -= quality - master.bare_wound_bonus -= quality + master.remove_fantasy_bonuses(quality) master.name = originalName @@ -153,4 +150,4 @@ span = "" effect_description = span_bold("mottled black glow") - location.visible_message("[span][originalName] is covered by a [effect_description] and then transforms into [parent]!") + location.visible_message("[span]The [originalName] is covered by a [effect_description] and then transforms into [parent]!") diff --git a/code/datums/components/fantasy/prefixes.dm b/code/datums/components/fantasy/prefixes.dm index 8ac9aa0cef80..cd80a1143130 100644 --- a/code/datums/components/fantasy/prefixes.dm +++ b/code/datums/components/fantasy/prefixes.dm @@ -145,7 +145,6 @@ var/obj/item/master = comp.parent master.RemoveElement(/datum/element/venomous) - /datum/fantasy_affix/soul_stealer name = "soul-stealing" placement = AFFIX_PREFIX diff --git a/code/datums/components/fantasy/suffixes.dm b/code/datums/components/fantasy/suffixes.dm index 4e38d61b0d18..c8809efae491 100644 --- a/code/datums/components/fantasy/suffixes.dm +++ b/code/datums/components/fantasy/suffixes.dm @@ -42,9 +42,16 @@ /datum/fantasy_affix/cosmetic_suffixes/apply(datum/component/fantasy/comp, newName) if(comp.quality > 0 || (comp.quality == 0 && prob(50))) - return "[newName] of [pick(goodSuffixes)]" + . = "[newName] of [pick(goodSuffixes)]" + if(comp.quality >= 10) + START_PROCESSING(SSprocessing, src) + if(comp.quality >= 15) + comp.parent.AddComponent(/datum/component/unusual_effect, color = "#FFEA0030", include_particles = TRUE) + else + comp.parent.AddComponent(/datum/component/unusual_effect, color = "#FFBF0030") else - return "[newName] of [pick(badSuffixes)]" + . = "[newName] of [pick(badSuffixes)]" + return . //////////// Good suffixes /datum/fantasy_affix/bane @@ -189,11 +196,13 @@ name = "curse of hunger" placement = AFFIX_SUFFIX alignment = AFFIX_EVIL + weight = 5 /datum/fantasy_affix/curse_of_hunger/validate(obj/item/attached) - //curse of hunger that attaches onto food has the ability to eat itself. it's hilarious. - if(!IS_EDIBLE(attached)) - return TRUE + // Curse of hunger can be really unbearable to deal with, + // so it should not start on someone or in a bag. + if(!isturf(attached.loc)) + return FALSE return TRUE /datum/fantasy_affix/curse_of_hunger/apply(datum/component/fantasy/comp, newName) @@ -201,10 +210,64 @@ var/obj/item/master = comp.parent var/filter_color = "#8a0c0ca1" //clarified args var/new_name = pick(", eternally hungry", " of the glutton", " cursed with hunger", ", consumer of all", " of the feast") - master.AddElement(/datum/element/curse_announcement, "[master] is cursed with the curse of hunger!", filter_color, new_name, comp) + master.AddElement(/datum/element/curse_announcement, "[master] is cursed with the curse of hunger!", filter_color, "", comp) comp.appliedComponents += master.AddComponent(/datum/component/curse_of_hunger) - return newName //no spoilers! + return "[newName][new_name]" /datum/fantasy_affix/curse_of_hunger/remove(datum/component/fantasy/comp) var/obj/item/master = comp.parent master.RemoveElement(/datum/element/curse_announcement) //just in case + +/datum/fantasy_affix/curse_of_polymorph + name = "curse of polymorph" + placement = AFFIX_SUFFIX + alignment = AFFIX_EVIL + +/datum/fantasy_affix/curse_of_polymorph/validate(obj/item/attached) + // Don't start on someone so that it doesn't immediately polymorph them. + if(ismob(attached.loc)) + return FALSE + if(!isclothing(attached)) + return FALSE + return TRUE + +/datum/fantasy_affix/curse_of_polymorph/apply(datum/component/fantasy/comp, newName) + . = ..() + var/obj/item/master = comp.parent + var/filter_color = "#800080a1" //clarified args + var/new_name = pick(", transforming", " of the polymorph", " cursed with polymorphing", ", changer of all", " of changing") + var/static/list/possible_results = list( + WABBAJACK_MONKEY, + WABBAJACK_ROBOT, + WABBAJACK_SLIME, + WABBAJACK_XENO, + WABBAJACK_HUMAN, + WABBAJACK_ANIMAL, + ) + master.AddElement(/datum/element/curse_announcement, "[master] is cursed with the curse of polymorph!", filter_color, "", comp) + comp.appliedComponents += master.AddComponent(/datum/component/curse_of_polymorph, pick(possible_results)) + return "[newName][new_name]" + +/datum/fantasy_affix/curse_of_polymorph/remove(datum/component/fantasy/comp) + var/obj/item/master = comp.parent + master.RemoveElement(/datum/element/curse_announcement) //just in case + +/datum/fantasy_affix/speed + name = "of speed" + placement = AFFIX_SUFFIX + alignment = AFFIX_GOOD + +/datum/fantasy_affix/speed/validate(obj/item/attached) + if(!istype(attached, /obj/item/clothing/shoes)) + return FALSE + return TRUE + +/datum/fantasy_affix/speed/apply(datum/component/fantasy/comp, newName) + . = ..() + var/obj/item/master = comp.parent + master.slowdown = min(-comp.quality / 5, master.slowdown) + return "[newName] of speed" + +/datum/fantasy_affix/speed/remove(datum/component/fantasy/comp) + var/obj/item/master = comp.parent + master.slowdown = initial(master.slowdown) diff --git a/code/datums/components/focused_attacker.dm b/code/datums/components/focused_attacker.dm new file mode 100644 index 000000000000..2cc0890016ff --- /dev/null +++ b/code/datums/components/focused_attacker.dm @@ -0,0 +1,71 @@ +/** + * Increases our attack damage every time we attack the same target + * Not compatible with any other component or status effect which modifies attack damage + */ +/datum/component/focused_attacker + /// Amount of damage we gain per attack + var/gain_per_attack + /// Maximum amount by which we can increase our attack power + var/maximum_gain + /// The last thing we attacked + var/atom/last_target + +/datum/component/focused_attacker/Initialize(gain_per_attack = 5, maximum_gain = 25) + . = ..() + if (!isliving(parent) && !isitem(parent)) + return COMPONENT_INCOMPATIBLE + src.maximum_gain = maximum_gain + src.gain_per_attack = gain_per_attack + +/datum/component/focused_attacker/Destroy(force, silent) + if (!isnull(last_target)) + UnregisterSignal(last_target, COMSIG_PARENT_QDELETING) + return ..() + +/datum/component/focused_attacker/RegisterWithParent() + if (isliving(parent)) + RegisterSignals(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK), PROC_REF(pre_mob_attack)) + else + RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, PROC_REF(pre_item_attack)) + +/datum/component/focused_attacker/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_ITEM_PRE_ATTACK)) + +/// Before a mob attacks, try increasing its attack power +/datum/component/focused_attacker/proc/pre_mob_attack(mob/living/attacker, atom/target) + SIGNAL_HANDLER + if (isnull(target) || isturf(target)) + return + if (target == last_target) + if (attacker.melee_damage_lower - initial(attacker.melee_damage_lower) >= maximum_gain) + return + attacker.melee_damage_lower += gain_per_attack + attacker.melee_damage_upper += gain_per_attack + return + + attacker.melee_damage_lower = initial(attacker.melee_damage_lower) + attacker.melee_damage_upper = initial(attacker.melee_damage_upper) + register_new_target(target) + +/// Before an item attacks, try increasing its attack power +/datum/component/focused_attacker/proc/pre_item_attack(obj/item/weapon, atom/target, mob/user, params) + SIGNAL_HANDLER + if (target == last_target) + if (weapon.force - initial(weapon.force) < maximum_gain) + weapon.force += gain_per_attack + return + + weapon.force = initial(weapon.force) + register_new_target(target) + +/// Register a new target +/datum/component/focused_attacker/proc/register_new_target(atom/target) + if (!isnull(last_target)) + UnregisterSignal(last_target, COMSIG_PARENT_QDELETING) + last_target = target + RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(on_target_deleted)) + +/// Drop our target ref on deletion +/datum/component/focused_attacker/proc/on_target_deleted(target) + SIGNAL_HANDLER + last_target = null diff --git a/code/datums/components/food/ice_cream_holder.dm b/code/datums/components/food/ice_cream_holder.dm index 60da9b207d6e..af2143e2457f 100644 --- a/code/datums/components/food/ice_cream_holder.dm +++ b/code/datums/components/food/ice_cream_holder.dm @@ -33,14 +33,16 @@ var/datum/reagent/sweetener -/datum/component/ice_cream_holder/Initialize(max_scoops = DEFAULT_MAX_ICE_CREAM_SCOOPS, - change_name = TRUE, - filled_name, - change_desc = FALSE, - x_offset = 0, - y_offset = 0, - datum/reagent/sweetener = /datum/reagent/consumable/sugar, - list/prefill_flavours) +/datum/component/ice_cream_holder/Initialize( + max_scoops = DEFAULT_MAX_ICE_CREAM_SCOOPS, + change_name = TRUE, + filled_name, + change_desc = FALSE, + x_offset = 0, + y_offset = 0, + datum/reagent/sweetener = /datum/reagent/consumable/sugar, + list/prefill_flavours, +) if(!IS_EDIBLE(parent)) /// There is no easy way to add servings to those non-item edibles, but I won't stop you. return COMPONENT_INCOMPATIBLE @@ -169,7 +171,7 @@ if(compare_list(our_scoops, icecream_order.wanted_flavors)) return COMPONENT_CORRECT_ORDER -/datum/component/ice_cream_holder/proc/sell_ice_cream(obj/item/source, mob/living/simple_animal/robot_customer/sold_to) +/datum/component/ice_cream_holder/proc/sell_ice_cream(obj/item/source, mob/living/basic/robot_customer/sold_to) SIGNAL_HANDLER //the price of ice cream scales with the number of scoops. Yummy. diff --git a/code/datums/components/gunpoint.dm b/code/datums/components/gunpoint.dm index 969c3e3a233a..90ba318bb582 100644 --- a/code/datums/components/gunpoint.dm +++ b/code/datums/components/gunpoint.dm @@ -161,25 +161,27 @@ qdel(src) ///If the shooter is hit by an attack, they have a 50% chance to flinch and fire. If it hit the arm holding the trigger, it's an 80% chance to fire instead -/datum/component/gunpoint/proc/flinch(attacker, damage, damagetype, def_zone) +/datum/component/gunpoint/proc/flinch(mob/living/source, damage_amount, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction, attacking_item) SIGNAL_HANDLER - var/mob/living/shooter = parent - if(attacker == shooter) - return // somehow this wasn't checked for months but no one tried punching themselves to initiate the shot, amazing + if(!attack_direction) // No fliching from yourself + return var/flinch_chance = 50 - var/gun_hand = LEFT_HANDS + var/gun_hand = (source.get_held_index_of_item(weapon) % 2) ? BODY_ZONE_L_ARM : BODY_ZONE_R_ARM - if(shooter.held_items[RIGHT_HANDS] == weapon) - gun_hand = RIGHT_HANDS + if(isbodypart(def_zone)) + var/obj/item/bodypart/hitting = def_zone + def_zone = hitting.body_zone - if((def_zone == BODY_ZONE_L_ARM && gun_hand == LEFT_HANDS) || (def_zone == BODY_ZONE_R_ARM && gun_hand == RIGHT_HANDS)) + if(def_zone == gun_hand) flinch_chance = 80 if(prob(flinch_chance)) - shooter.visible_message(span_danger("[shooter] flinches!"), \ - span_danger("You flinch!")) + source.visible_message( + span_danger("[source] flinches!"), + span_danger("You flinch!"), + ) INVOKE_ASYNC(src, PROC_REF(trigger_reaction)) #undef GUNPOINT_DELAY_STAGE_2 diff --git a/code/datums/components/healing_touch.dm b/code/datums/components/healing_touch.dm index 144cc7833992..965a52de9468 100644 --- a/code/datums/components/healing_touch.dm +++ b/code/datums/components/healing_touch.dm @@ -8,10 +8,15 @@ * This intercepts the attack and starts a do_after if the target is in its allowed type list. */ /datum/component/healing_touch + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS /// How much brute damage to heal var/heal_brute /// How much burn damage to heal var/heal_burn + /// How much toxin damage to heal + var/heal_tox + /// How much oxygen damage to heal + var/heal_oxy /// How much stamina damage to heal var/heal_stamina /// Interaction will use this key, and be blocked while this key is in use @@ -26,16 +31,26 @@ var/valid_biotypes /// Which kinds of carbon limbs can we heal, has no effect on non-carbon mobs. Set to null if you don't care about excluding prosthetics. var/required_bodytype - /// How targetting yourself works, expects one of HEALING_TOUCH_ANYONE, HEALING_TOUCH_NOT_SELF, or HEALING_TOUCH_SELF_ONLY - var/self_targetting + /// How targeting yourself works, expects one of HEALING_TOUCH_ANYONE, HEALING_TOUCH_NOT_SELF, or HEALING_TOUCH_SELF_ONLY + var/self_targeting /// Text to print when action starts, replaces %SOURCE% with healer and %TARGET% with healed mob var/action_text /// Text to print when action completes, replaces %SOURCE% with healer and %TARGET% with healed mob var/complete_text + /// Whether to print the target's remaining health after healing (for non-carbon targets only) + var/show_health + /// Color for the healing effect + var/heal_color + /// Optional click modifier required + var/required_modifier + /// Callback to run after healing a mob + var/datum/callback/after_healed /datum/component/healing_touch/Initialize( heal_brute = 20, heal_burn = 20, + heal_tox = 0, + heal_oxy = 0, heal_stamina = 0, heal_time = 2 SECONDS, interaction_key = DOAFTER_SOURCE_HEAL_TOUCH, @@ -43,15 +58,21 @@ list/valid_targets_typecache = list(), valid_biotypes = MOB_ORGANIC | MOB_MINERAL, required_bodytype = BODYTYPE_ORGANIC, - self_targetting = HEALING_TOUCH_NOT_SELF, + self_targeting = HEALING_TOUCH_NOT_SELF, action_text = "%SOURCE% begins healing %TARGET%", complete_text = "%SOURCE% finishes healing %TARGET%", + show_health = FALSE, + heal_color = COLOR_HEALING_CYAN, + required_modifier = null, + datum/callback/after_healed = null, ) if (!isliving(parent)) return COMPONENT_INCOMPATIBLE src.heal_brute = heal_brute src.heal_burn = heal_burn + src.heal_tox = heal_tox + src.heal_oxy = heal_oxy src.heal_stamina = heal_stamina src.heal_time = heal_time src.interaction_key = interaction_key @@ -59,13 +80,25 @@ src.valid_targets_typecache = valid_targets_typecache.Copy() src.valid_biotypes = valid_biotypes src.required_bodytype = required_bodytype - src.self_targetting = self_targetting + src.self_targeting = self_targeting src.action_text = action_text src.complete_text = complete_text + src.show_health = show_health + src.heal_color = heal_color + src.required_modifier = required_modifier + src.after_healed = after_healed RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(try_healing)) // Players RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(try_healing)) // NPCs +// Let's populate this list as we actually use it, this thing has too many args +/datum/component/healing_touch/InheritComponent( + datum/component/new_component, + i_am_original, + heal_color, +) + src.heal_color = heal_color + /datum/component/healing_touch/UnregisterFromParent() UnregisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET)) return ..() @@ -75,12 +108,15 @@ return ..() /// Validate our target, and interrupt the attack chain to start healing it if it is allowed -/datum/component/healing_touch/proc/try_healing(mob/living/healer, atom/target) +/datum/component/healing_touch/proc/try_healing(mob/living/healer, atom/target, proximity, modifiers) SIGNAL_HANDLER if (!isliving(target)) return - if (!is_type_in_typecache(target, valid_targets_typecache)) + if (!isnull(required_modifier) && !LAZYACCESS(modifiers, required_modifier)) + return + + if (length(valid_targets_typecache) && !is_type_in_typecache(target, valid_targets_typecache)) return // Fall back to attacking it if (extra_checks && !extra_checks.Invoke(healer, target)) @@ -90,7 +126,7 @@ healer.balloon_alert(healer, "busy!") return COMPONENT_CANCEL_ATTACK_CHAIN - switch (self_targetting) + switch (self_targeting) if (HEALING_TOUCH_NOT_SELF) if (target == healer) healer.balloon_alert(healer, "can't heal yourself!") @@ -123,6 +159,14 @@ return FALSE if (target.stamina.loss > 0 && heal_stamina) return TRUE + if (target.getOxyLoss() > 0 && heal_oxy) + return TRUE + if (target.getToxLoss() > 0 && heal_tox) + return TRUE + if (target.getOxyLoss() > 0 && heal_oxy) + return TRUE + if (target.getToxLoss() > 0 && heal_tox) + return TRUE if (!iscarbon(target)) return (target.getBruteLoss() > 0 && heal_brute) || (target.getFireLoss() > 0 && heal_burn) var/mob/living/carbon/carbon_target = target @@ -146,8 +190,26 @@ if (complete_text) healer.visible_message(span_notice("[format_string(complete_text, healer, target)]")) - target.heal_overall_damage(brute = heal_brute, burn = heal_burn, stamina = heal_stamina, required_bodytype = required_bodytype) - new /obj/effect/temp_visual/heal(get_turf(target), COLOR_HEALING_CYAN) + var/healed = target.heal_overall_damage( + brute = heal_brute, + burn = heal_burn, + stamina = heal_stamina, + required_bodytype = required_bodytype, + updating_health = FALSE, + ) + healed += target.adjustOxyLoss(-heal_oxy, updating_health = FALSE, required_biotype = valid_biotypes) + healed += target.adjustToxLoss(-heal_tox, updating_health = FALSE, required_biotype = valid_biotypes) + if (healed <= 0) + return + + target.updatehealth() + new /obj/effect/temp_visual/heal(get_turf(target), heal_color) + after_healed?.Invoke(target) + + if(!show_health) + return + var/formatted_string = format_string("%TARGET% now has [health_percentage(target)] health.", healer, target) + to_chat(healer, span_danger(formatted_string)) /// Reformats the passed string with the replacetext keys /datum/component/healing_touch/proc/format_string(string, atom/source, atom/target) diff --git a/code/datums/components/holderloving.dm b/code/datums/components/holderloving.dm index e8f797511c2a..bcd073b2600f 100644 --- a/code/datums/components/holderloving.dm +++ b/code/datums/components/holderloving.dm @@ -37,6 +37,7 @@ COMSIG_ITEM_EQUIPPED, COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED, + COMSIG_ITEM_STORED, ), PROC_REF(check_my_loc)) /datum/component/holderloving/UnregisterFromParent() @@ -46,6 +47,7 @@ COMSIG_ITEM_EQUIPPED, COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED, + COMSIG_ITEM_STORED, )) /datum/component/holderloving/PostTransfer() diff --git a/code/datums/components/joint_damage.dm b/code/datums/components/joint_damage.dm new file mode 100644 index 000000000000..5397bd307cab --- /dev/null +++ b/code/datums/components/joint_damage.dm @@ -0,0 +1,35 @@ +/* + * A component given to mobs to damage a linked mob + */ +/datum/component/joint_damage + ///the mob we will damage + var/datum/weakref/overlord_mob + ///our last health count + var/previous_health_count + +/datum/component/joint_damage/Initialize(mob/overlord_mob) + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + var/mob/living/parent_mob = parent + previous_health_count = parent_mob.health + if(overlord_mob) + src.overlord_mob = WEAKREF(overlord_mob) + +/datum/component/joint_damage/RegisterWithParent() + RegisterSignal(parent, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(damage_overlord)) + RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(damage_overlord)) + +/datum/component/joint_damage/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_LIVING_HEALTH_UPDATE, COMSIG_LIVING_DEATH)) + +/datum/component/joint_damage/Destroy() + overlord_mob = null + return ..() + +/datum/component/joint_damage/proc/damage_overlord(mob/living/source) + SIGNAL_HANDLER + + var/mob/living/overlord_to_damage = overlord_mob?.resolve() + if(!isnull(overlord_to_damage)) + overlord_to_damage.adjustBruteLoss(previous_health_count - source.health) ///damage or heal overlord + previous_health_count = source.health diff --git a/code/datums/components/leash.dm b/code/datums/components/leash.dm new file mode 100644 index 000000000000..66771682b1e9 --- /dev/null +++ b/code/datums/components/leash.dm @@ -0,0 +1,180 @@ +/// Keeps the parent within the distance of its owner as naturally as possible, +/// but teleporting if necessary. +/datum/component/leash + /// The owner of the leash. If this is qdeleted, the leash is as well. + var/atom/movable/owner + + /// The maximum distance you can move from your owner + var/distance + + /// The object type to create on the old turf when forcibly teleporting out + var/force_teleport_out_effect + + /// The object type to create on the new turf when forcibly teleporting out + var/force_teleport_in_effect + + VAR_PRIVATE + // Pathfinding can yield, so only move us closer if this is the best one + current_path_tick = 0 + last_completed_path_tick = 0 + + performing_path_move = FALSE + +/datum/component/leash/Initialize( + atom/movable/owner, + distance = 3, + force_teleport_out_effect, + force_teleport_in_effect, +) + . = ..() + + if (!ismovable(parent)) + stack_trace("Parent must be a movable") + return COMPONENT_INCOMPATIBLE + + if (!ismovable(owner)) + stack_trace("[owner] (owner) is not a movable") + return COMPONENT_INCOMPATIBLE + + if (!isnum(distance)) + stack_trace("[distance] (distance) must be a number") + return COMPONENT_INCOMPATIBLE + + if (!isnull(force_teleport_out_effect) && !ispath(force_teleport_out_effect)) + stack_trace("force_teleport_out_effect must be null or a path, not [force_teleport_out_effect]") + return COMPONENT_INCOMPATIBLE + + if (!isnull(force_teleport_in_effect) && !ispath(force_teleport_in_effect)) + stack_trace("force_teleport_in_effect must be null or a path, not [force_teleport_in_effect]") + return COMPONENT_INCOMPATIBLE + + src.owner = owner + src.distance = distance + src.force_teleport_out_effect = force_teleport_out_effect + src.force_teleport_in_effect = force_teleport_in_effect + + RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(on_owner_qdel)) + + var/static/list/container_connections = list( + COMSIG_MOVABLE_MOVED = PROC_REF(on_owner_moved), + ) + + AddComponent(/datum/component/connect_containers, owner, container_connections) + RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_owner_moved)) + RegisterSignal(parent, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_parent_pre_move)) + + check_distance() + +/datum/component/leash/Destroy() + owner = null + return ..() + +/datum/component/leash/proc/set_distance(distance) + ASSERT(isnum(distance)) + src.distance = distance + check_distance() + +/datum/component/leash/proc/on_owner_qdel() + SIGNAL_HANDLER + PRIVATE_PROC(TRUE) + + qdel(src) + +/datum/component/leash/proc/on_owner_moved(atom/movable/source) + SIGNAL_HANDLER + PRIVATE_PROC(TRUE) + + check_distance() + +/datum/component/leash/proc/on_parent_pre_move(atom/movable/source, atom/new_location) + SIGNAL_HANDLER + PRIVATE_PROC(TRUE) + + if (performing_path_move) + return NONE + + var/turf/new_location_turf = get_turf(new_location) + if (get_dist(new_location_turf, owner) <= distance) + return NONE + + if (ismob(source)) + source.balloon_alert(source, "too far!") + + return COMPONENT_MOVABLE_BLOCK_PRE_MOVE + +/datum/component/leash/proc/check_distance() + set waitfor = FALSE + PRIVATE_PROC(TRUE) + + if (get_dist(parent, owner) <= distance) + return + + SEND_SIGNAL(parent, COMSIG_LEASH_PATH_STARTED) + + current_path_tick += 1 + var/our_path_tick = current_path_tick + + var/list/path = get_path_to(parent, owner, mintargetdist = distance) + + if (last_completed_path_tick > our_path_tick) + return + + last_completed_path_tick = our_path_tick + + commit_path(path) + +/datum/component/leash/proc/commit_path(list/turf/path) + SHOULD_NOT_SLEEP(TRUE) + PRIVATE_PROC(TRUE) + + performing_path_move = TRUE + + var/atom/movable/movable_parent = parent + + for (var/turf/to_move as anything in path) + // Could be an older path, don't make us teleport back + if (!to_move.Adjacent(parent)) + continue + + if (!movable_parent.Move(to_move)) + force_teleport_back("bad path step") + return + + if (get_dist(parent, owner) > distance) + force_teleport_back("incomplete path") + + performing_path_move = FALSE + SEND_SIGNAL(parent, COMSIG_LEASH_PATH_COMPLETE) + +/datum/component/leash/proc/force_teleport_back(reason) + PRIVATE_PROC(TRUE) + + var/atom/movable/movable_parent = parent + + SSblackbox.record_feedback("tally", "leash_force_teleport_back", 1, reason) + + if (force_teleport_out_effect) + new force_teleport_out_effect(movable_parent.loc) + + movable_parent.forceMove(get_turf(owner)) + + if (force_teleport_in_effect) + new force_teleport_in_effect(movable_parent.loc) + + if (ismob(movable_parent)) + movable_parent.balloon_alert(movable_parent, "moved out of range!") + + SEND_SIGNAL(parent, COMSIG_LEASH_FORCE_TELEPORT) + +/// A debug spawner that will create a corgi leashed to a bike horn, plus a beam +/obj/effect/spawner/debug_leash + +/obj/effect/spawner/debug_leash/Initialize(mapload) + . = ..() + + var/obj/item/bikehorn/bike_horn = new(loc) + var/mob/living/basic/pet/dog/corgi/corgi = new(loc) + + corgi.AddComponent(/datum/component/leash, bike_horn) + + corgi.Beam(bike_horn) diff --git a/code/datums/components/life_link.dm b/code/datums/components/life_link.dm new file mode 100644 index 000000000000..c65206bc1ff2 --- /dev/null +++ b/code/datums/components/life_link.dm @@ -0,0 +1,169 @@ +/** + * A mob with this component passes all damage (and healing) it takes to another mob, passed as a parameter + * Essentially we use another mob's health bar as our health bar + */ +/datum/component/life_link + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + /// Mob we pass all of our damage to + var/mob/living/host + /// Optional callback invoked when damage gets transferred + var/datum/callback/on_passed_damage + /// Optional callback invoked when the linked mob dies + var/datum/callback/on_linked_death + +/datum/component/life_link/Initialize(mob/living/host, datum/callback/on_passed_damage, datum/callback/on_linked_death) + . = ..() + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE + if (!istype(host)) + CRASH("Life link created on [parent.type] and attempted to link to invalid type [host?.type].") + register_host(host) + src.on_passed_damage = on_passed_damage + src.on_linked_death = on_linked_death + +/datum/component/life_link/RegisterWithParent() + RegisterSignal(parent, COMSIG_CARBON_LIMB_DAMAGED, PROC_REF(on_limb_damage)) + RegisterSignals(parent, COMSIG_LIVING_ADJUST_STANDARD_DAMAGE_TYPES, PROC_REF(on_damage_adjusted)) + RegisterSignal(parent, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_health_updated)) + RegisterSignal(parent, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(on_status_tab_updated)) + if (!isnull(host)) + var/mob/living/living_parent = parent + living_parent.updatehealth() + +/datum/component/life_link/UnregisterFromParent() + unregister_host() + UnregisterSignal(parent, list(COMSIG_CARBON_LIMB_DAMAGED, COMSIG_LIVING_HEALTH_UPDATE, COMSIG_MOB_GET_STATUS_TAB_ITEMS) + COMSIG_LIVING_ADJUST_STANDARD_DAMAGE_TYPES) + +/datum/component/life_link/InheritComponent(datum/component/new_comp, i_am_original, mob/living/host, datum/callback/on_passed_damage, datum/callback/on_linked_death) + register_host(host) + +/// Set someone up as our new host +/datum/component/life_link/proc/register_host(mob/living/new_host) + unregister_host() + if (isnull(new_host)) + return + host = new_host + RegisterSignal(host, COMSIG_LIVING_DEATH, PROC_REF(on_host_died)) + RegisterSignal(host, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_health_updated)) + RegisterSignal(host, COMSIG_LIVING_REVIVE, PROC_REF(on_host_revived)) + RegisterSignal(host, COMSIG_PARENT_QDELETING, PROC_REF(on_host_deleted)) + var/mob/living/living_parent = parent + living_parent.updatehealth() + +/// Drop someone from being our host +/datum/component/life_link/proc/unregister_host() + if (isnull(host)) + return + UnregisterSignal(host, list(COMSIG_LIVING_DEATH, COMSIG_LIVING_HEALTH_UPDATE, COMSIG_LIVING_REVIVE, COMSIG_PARENT_QDELETING)) + host = null + +/// Called when your damage goes up or down +/datum/component/life_link/proc/on_damage_adjusted(mob/living/our_mob, type, amount, forced) + SIGNAL_HANDLER + if (forced) + return + amount *= our_mob.get_damage_mod(type) + switch (type) + if(BRUTE) + host.adjustBruteLoss(amount, forced = TRUE) + if(BURN) + host.adjustFireLoss(amount, forced = TRUE) + if(TOX) + host.adjustToxLoss(amount, forced = TRUE) + if(OXY) + host.adjustOxyLoss(amount, forced = TRUE) + if(CLONE) + host.adjustCloneLoss(amount, forced = TRUE) + + on_passed_damage?.Invoke(our_mob, host, amount) + return COMPONENT_IGNORE_CHANGE + +/// Called when someone hurts one of our limbs, bypassing normal damage adjustment +/datum/component/life_link/proc/on_limb_damage(mob/living/our_mob, limb, brute, burn) + SIGNAL_HANDLER + if (brute != 0) + host.adjustBruteLoss(brute, updating_health = FALSE) + if (burn != 0) + host.adjustFireLoss(burn, updating_health = FALSE) + if (brute != 0 || burn != 0) + host.updatehealth() + on_passed_damage?.Invoke(our_mob, host, brute + burn) + return COMPONENT_PREVENT_LIMB_DAMAGE + +/// Called when either the host or parent's health tries to update, update our displayed health +/datum/component/life_link/proc/on_health_updated() + SIGNAL_HANDLER + update_health_hud(parent) + update_med_hud_health(parent) + update_med_hud_status(parent) + +/// Update our parent's health display based on how harmed our host is +/datum/component/life_link/proc/update_health_hud(mob/living/mob_parent) + var/severity = 0 + var/healthpercent = health_percentage(host) + switch(healthpercent) + if(100 to INFINITY) + severity = 0 + if(85 to 100) + severity = 1 + if(70 to 85) + severity = 2 + if(55 to 70) + severity = 3 + if(40 to 55) + severity = 4 + if(25 to 40) + severity = 5 + else + severity = 6 + if(severity > 0) + mob_parent.overlay_fullscreen("brute", /atom/movable/screen/fullscreen/brute, severity) + else + mob_parent.clear_fullscreen("brute") + if(mob_parent.hud_used?.healths) + mob_parent.hud_used.healths.maptext = MAPTEXT("
[round(healthpercent, 0.5)]%
") + +/// Update our health on the medical hud +/datum/component/life_link/proc/update_med_hud_health(mob/living/mob_parent) + var/image/holder = mob_parent.hud_list?[HEALTH_HUD] + if(isnull(holder)) + return + holder.icon_state = "hud[RoundHealth(host)]" + var/icon/size_check = icon(mob_parent.icon, mob_parent.icon_state, mob_parent.dir) + holder.pixel_y = size_check.Height() - world.icon_size + +/// Update our vital status on the medical hud +/datum/component/life_link/proc/update_med_hud_status(mob/living/mob_parent) + var/image/holder = mob_parent.hud_list?[STATUS_HUD] + if(isnull(holder)) + return + var/icon/size_check = icon(mob_parent.icon, mob_parent.icon_state, mob_parent.dir) + holder.pixel_y = size_check.Height() - world.icon_size + if(host.stat == DEAD || HAS_TRAIT(host, TRAIT_FAKEDEATH)) + holder.icon_state = "huddead" + else + holder.icon_state = "hudhealthy" + +/// When our status tab updates, draw how much HP our host has in there +/datum/component/life_link/proc/on_status_tab_updated(mob/living/source, list/items) + SIGNAL_HANDLER + var/healthpercent = health_percentage(host) + items += "Host Health: [round(healthpercent, 0.5)]%" + +/// Called when our host dies, we should die too +/datum/component/life_link/proc/on_host_died(mob/living/source, gibbed) + SIGNAL_HANDLER + on_linked_death?.Invoke(parent, host, gibbed) + var/mob/living/living_parent = parent + living_parent.death(gibbed) + +/// Called when our host undies, we should undie too +/datum/component/life_link/proc/on_host_revived(mob/living/source, full_heal_flags) + SIGNAL_HANDLER + var/mob/living/living_parent = parent + living_parent.revive(full_heal_flags) + +/// Called when +/datum/component/life_link/proc/on_host_deleted() + SIGNAL_HANDLER + qdel(src) diff --git a/code/datums/components/listen_and_repeat.dm b/code/datums/components/listen_and_repeat.dm new file mode 100644 index 000000000000..44a914a1b90a --- /dev/null +++ b/code/datums/components/listen_and_repeat.dm @@ -0,0 +1,83 @@ +/// The maximum number of phrases we can store in our speech buffer +#define MAX_SPEECH_BUFFER_SIZE 500 +/// Tendency we have to ignore radio chatter +#define RADIO_IGNORE_CHANCE 10 + +/// Simple element that will deterministically set a value based on stuff that the source has heard and will then compel the source to repeat it. +/// Requires a valid AI Blackboard. +/datum/component/listen_and_repeat + /// List of things that we start out having in our speech buffer + var/list/desired_phrases = null + /// The AI Blackboard Key we assign the value to. + var/blackboard_key = null + /// Probability we speak + var/speech_probability = null + /// Probabiliy we switch our phrase + var/switch_phrase_probability = null + /// List of things that we've heard and will repeat. + var/list/speech_buffer = null + +/datum/component/listen_and_repeat/Initialize(list/desired_phrases, blackboard_key) + . = ..() + if(!ismovable(parent)) + return COMPONENT_INCOMPATIBLE + + if(!isnull(desired_phrases)) + LAZYADD(speech_buffer, desired_phrases) + + src.blackboard_key = blackboard_key + + RegisterSignal(parent, COMSIG_MOVABLE_PRE_HEAR, PROC_REF(on_hear)) + RegisterSignal(parent, COMSIG_NEEDS_NEW_PHRASE, PROC_REF(set_new_blackboard_phrase)) + RegisterSignal(parent, COMSIG_LIVING_WRITE_MEMORY, PROC_REF(on_write_memory)) + + ADD_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type) + +/datum/component/listen_and_repeat/Destroy(force, silent) + REMOVE_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type) + return ..() + +/// Called when we hear something. +/datum/component/listen_and_repeat/proc/on_hear(datum/source, list/passed_arguments) + SIGNAL_HANDLER + + var/message = passed_arguments[HEARING_RAW_MESSAGE] + var/speaker = passed_arguments[HEARING_SPEAKER] + var/over_radio = !!passed_arguments[HEARING_RADIO_FREQ] + if(speaker == source) // don't parrot ourselves + return + + if(over_radio && prob(RADIO_IGNORE_CHANCE)) + return + + var/number_of_excess_strings = LAZYLEN(speech_buffer) - MAX_SPEECH_BUFFER_SIZE + if(number_of_excess_strings > 0) // only remove if we're overfull + for(var/i in 1 to number_of_excess_strings) + LAZYREMOVE(speech_buffer, pick(speech_buffer)) + + LAZYOR(speech_buffer, html_decode(message)) + +/// Called to set a new value for the blackboard key. +/datum/component/listen_and_repeat/proc/set_new_blackboard_phrase(datum/source) + SIGNAL_HANDLER + var/atom/movable/atom_source = source + var/datum/ai_controller/controller = atom_source.ai_controller + if(!LAZYLEN(speech_buffer)) + controller.clear_blackboard_key(blackboard_key) + return NO_NEW_PHRASE_AVAILABLE + + var/selected_phrase = pick(speech_buffer) + controller.set_blackboard_key(blackboard_key, selected_phrase) + +/// Exports all the speech buffer data to a dedicated blackboard key on the source. +/datum/component/listen_and_repeat/proc/on_write_memory(datum/source, dead, gibbed) + SIGNAL_HANDLER + var/atom/movable/atom_source = source + var/datum/ai_controller/controller = atom_source.ai_controller + if(LAZYLEN(speech_buffer)) // what? well whatever let's just move on + return + + controller.set_blackboard_key(BB_EXPORTABLE_STRING_BUFFER_LIST, speech_buffer.Copy()) + +#undef MAX_SPEECH_BUFFER_SIZE +#undef RADIO_IGNORE_CHANCE diff --git a/code/datums/components/lock_on_cursor.dm b/code/datums/components/lock_on_cursor.dm index 1811a13aa9b0..71c729b03f35 100644 --- a/code/datums/components/lock_on_cursor.dm +++ b/code/datums/components/lock_on_cursor.dm @@ -8,13 +8,13 @@ */ /datum/component/lock_on_cursor dupe_mode = COMPONENT_DUPE_ALLOWED - /// Appearance to overlay onto whatever we are targetting + /// Appearance to overlay onto whatever we are targeting var/mutable_appearance/lock_appearance /// Current images we are displaying to the client var/list/image/lock_images /// Typecache of things we are allowed to target var/list/target_typecache - /// Cache of weakrefs to ignore targetting formatted as `list(weakref = TRUE)` + /// Cache of weakrefs to ignore targeting formatted as `list(weakref = TRUE)` var/list/immune_weakrefs /// Number of things we can target at once var/lock_amount diff --git a/code/datums/components/mob_chain.dm b/code/datums/components/mob_chain.dm new file mode 100644 index 000000000000..f3cda5cf22d1 --- /dev/null +++ b/code/datums/components/mob_chain.dm @@ -0,0 +1,227 @@ +/** + * Component allowing you to create a linked list of mobs. + * These mobs will follow each other and attack as one, as well as sharing damage taken. + */ +/datum/component/mob_chain + + /// If true then damage we take is passed backwards along the line + var/pass_damage_back + /// If true then we will set our icon state based on line position + var/vary_icon_state + + /// Mob in front of us in the chain + var/mob/living/front + /// Mob behind us in the chain + var/mob/living/back + +/datum/component/mob_chain/Initialize(mob/living/front, pass_damage_back = TRUE, vary_icon_state = FALSE) + . = ..() + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + src.front = front + src.pass_damage_back = pass_damage_back + src.vary_icon_state = vary_icon_state + if (!isnull(front)) + SEND_SIGNAL(front, COMSIG_MOB_GAINED_CHAIN_TAIL, parent) + parent.AddComponent(/datum/component/leash, owner = front, distance = 1) // Handles catching up gracefully + var/mob/living/living_parent = parent + living_parent.set_glide_size(front.glide_size) + +/datum/component/mob_chain/Destroy(force, silent) + if (!isnull(front)) + SEND_SIGNAL(front, COMSIG_MOB_LOST_CHAIN_TAIL, parent) + front = null + back = null + return ..() + +/datum/component/mob_chain/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOB_GAINED_CHAIN_TAIL, PROC_REF(on_gained_tail)) + RegisterSignal(parent, COMSIG_MOB_LOST_CHAIN_TAIL, PROC_REF(on_lost_tail)) + RegisterSignal(parent, COMSIG_MOB_CHAIN_CONTRACT, PROC_REF(on_contracted)) + RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(on_deletion)) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) + RegisterSignal(parent, COMSIG_ATOM_CAN_BE_PULLED, PROC_REF(on_pulled)) + RegisterSignals(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, COMSIG_MOB_ATTACK_RANGED), PROC_REF(on_attack)) + RegisterSignal(parent, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(on_glide_size_changed)) + if (vary_icon_state) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON_STATE, PROC_REF(on_update_icon_state)) + update_mob_appearance() + if (pass_damage_back) + RegisterSignals(parent, COMSIG_LIVING_ADJUST_STANDARD_DAMAGE_TYPES, PROC_REF(on_adjust_damage)) + RegisterSignal(parent, COMSIG_LIVING_ADJUST_STAMINA_DAMAGE, PROC_REF(on_adjust_stamina)) + RegisterSignal(parent, COMSIG_CARBON_LIMB_DAMAGED, PROC_REF(on_limb_damage)) + + var/datum/action/cooldown/worm_contract/shrink = new(parent) + shrink.Grant(parent) + +/datum/component/mob_chain/UnregisterFromParent() + UnregisterSignal(parent, list( + COMSIG_ATOM_CAN_BE_PULLED, + COMSIG_ATOM_UPDATE_ICON_STATE, + COMSIG_CARBON_LIMB_DAMAGED, + COMSIG_HUMAN_EARLY_UNARMED_ATTACK, + COMSIG_LIVING_ADJUST_BRUTE_DAMAGE, + COMSIG_LIVING_ADJUST_BURN_DAMAGE, + COMSIG_LIVING_ADJUST_CLONE_DAMAGE, + COMSIG_LIVING_DEATH, + COMSIG_LIVING_ADJUST_OXY_DAMAGE, + COMSIG_LIVING_ADJUST_STAMINA_DAMAGE, + COMSIG_LIVING_ADJUST_TOX_DAMAGE, + COMSIG_LIVING_UNARMED_ATTACK, + COMSIG_MOB_ATTACK_RANGED, + COMSIG_MOB_CHAIN_CONTRACT, + COMSIG_MOB_GAINED_CHAIN_TAIL, + COMSIG_MOB_LOST_CHAIN_TAIL, + COMSIG_MOVABLE_MOVED, + COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, + COMSIG_PARENT_QDELETING, + )) + qdel(parent.GetComponent(/datum/component/leash)) + var/mob/living/living_parent = parent + var/datum/action/cooldown/worm_contract/shrink = locate() in living_parent.actions + qdel(shrink) + +/// Update how we look +/datum/component/mob_chain/proc/update_mob_appearance() + if (!vary_icon_state) + return + var/mob/living/body = parent + body.update_appearance(UPDATE_ICON_STATE) + +/// Called when something sets us as IT'S front +/datum/component/mob_chain/proc/on_gained_tail(mob/living/body, mob/living/tail) + SIGNAL_HANDLER + back = tail + update_mob_appearance() + +/// Called when our tail loses its chain component +/datum/component/mob_chain/proc/on_lost_tail() + SIGNAL_HANDLER + back = null + update_mob_appearance() + +/// Called when our tail gets pulled up to our body +/datum/component/mob_chain/proc/on_contracted(mob/living/shrinking) + SIGNAL_HANDLER + if (isnull(back)) + return + back.forceMove(shrinking.loc) + var/datum/action/cooldown/worm_contract/shrink = locate() in back.actions + if (isnull(shrink)) + return + INVOKE_ASYNC(shrink, TYPE_PROC_REF(/datum/action, Trigger)) + +/// If we die so does the guy behind us, then stop following the leader +/datum/component/mob_chain/proc/on_death() + SIGNAL_HANDLER + back?.death() + qdel(src) + +/// If we get deleted so does the guy behind us +/datum/component/mob_chain/proc/on_deletion() + SIGNAL_HANDLER + QDEL_NULL(back) + front?.update_appearance(UPDATE_ICON) + +/// Pull our tail behind us when we move +/datum/component/mob_chain/proc/on_moved(mob/living/mover, turf/old_loc) + SIGNAL_HANDLER + if(isnull(back) || back.loc == old_loc) + return + back.Move(old_loc) + +/// Update our visuals based on if we have someone in front and behind +/datum/component/mob_chain/proc/on_update_icon_state(mob/living/our_mob) + SIGNAL_HANDLER + var/current_icon_state = our_mob.base_icon_state + if(isnull(front)) + current_icon_state = "[current_icon_state]_start" + else if(isnull(back)) + current_icon_state = "[current_icon_state]_end" + else + current_icon_state = "[current_icon_state]_mid" + + our_mob.icon_state = current_icon_state + if (isanimal_or_basicmob(our_mob)) + var/mob/living/basic/basic_parent = our_mob + basic_parent.icon_living = current_icon_state + +/// Do not allow someone to be pulled out of the chain +/datum/component/mob_chain/proc/on_pulled(mob/living/our_mob) + SIGNAL_HANDLER + if (!isnull(front)) + return COMSIG_ATOM_CANT_PULL + +/// Tell our tail to attack too +/datum/component/mob_chain/proc/on_attack(mob/living/our_mob, atom/target) + SIGNAL_HANDLER + if (target == back || target == front) + return COMPONENT_CANCEL_ATTACK_CHAIN + if (isnull(back) || QDELETED(target)) + return + INVOKE_ASYNC(back, TYPE_PROC_REF(/mob, ClickOn), target) + +/// Maintain glide size backwards +/datum/component/mob_chain/proc/on_glide_size_changed(mob/living/our_mob, new_size) + SIGNAL_HANDLER + back?.set_glide_size(new_size) + +/// On gain or lose stamina, adjust our tail too +/datum/component/mob_chain/proc/on_adjust_stamina(mob/living/our_mob, type, amount, forced) + SIGNAL_HANDLER + if (forced) + return + back?.stamina.adjust(-amount, forced) + +/// On damage or heal, affect our furthest segment +/datum/component/mob_chain/proc/on_adjust_damage(mob/living/our_mob, type, amount, forced) + SIGNAL_HANDLER + if (isnull(back) || forced) + return + if (type == STAMINA) + back.stamina.adjust(-amount, forced) + return // Pass stamina changes all the way along so we maintain consistent speed + switch (type) + if(BRUTE) + back.adjustBruteLoss(amount, forced = forced) + if(BURN) + back.adjustFireLoss(amount, forced = forced) + if(TOX) + back.adjustToxLoss(amount, forced = forced) + if(OXY) // If all segments are suffocating we pile damage backwards until our ass starts dying forwards + back.adjustOxyLoss(amount, forced = forced) + if(CLONE) + back.adjustCloneLoss(amount, forced = forced) + return COMPONENT_IGNORE_CHANGE + +/// Special handling for if damage is delegated to a mob's limbs instead of its overall damage +/datum/component/mob_chain/proc/on_limb_damage(mob/living/our_mob, limb, brute, burn) + SIGNAL_HANDLER + if (isnull(back)) + return + if (brute != 0) + back.adjustBruteLoss(brute, updating_health = FALSE) + if (burn != 0) + back.adjustFireLoss(burn, updating_health = FALSE) + if (brute != 0 || burn != 0) + back.updatehealth() + return COMPONENT_PREVENT_LIMB_DAMAGE + +/** + * Shrink the chain of mobs into one tile. + */ +/datum/action/cooldown/worm_contract + name = "Force Contract" + desc = "Forces your body to contract onto a single tile." + background_icon_state = "bg_heretic" + overlay_icon_state = "bg_heretic_border" + button_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "worm_contract" + cooldown_time = 30 SECONDS + melee_cooldown_time = 0 SECONDS + +/datum/action/cooldown/worm_contract/Activate(atom/target) + SEND_SIGNAL(owner, COMSIG_MOB_CHAIN_CONTRACT) + StartCooldown() diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm index 620f28f1c134..c54ac6b430b0 100644 --- a/code/datums/components/pellet_cloud.dm +++ b/code/datums/components/pellet_cloud.dm @@ -307,7 +307,7 @@ var/damage_dealt = wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE] var/w_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS] var/bw_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS] - var/wound_type = (initial(P.damage_type) == BRUTE) ? WOUND_BLUNT : WOUND_BURN // sharpness is handled in the wound rolling + var/wounding_type = (initial(P.damage_type) == BRUTE) ? WOUND_BLUNT : WOUND_BURN // sharpness is handled in the wound rolling wound_info_by_part -= hit_part // technically this only checks armor worn the moment that all the pellets resolve rather than as each one hits you, @@ -320,7 +320,7 @@ armor_factor *= ARMOR_WEAKENED_MULTIPLIER damage_dealt *= max(0, 1 - armor_factor*0.01) - hit_part.painless_wound_roll(wound_type, damage_dealt, w_bonus, bw_bonus, initial(P.sharpness)) + hit_part.painless_wound_roll(wounding_type, damage_dealt, w_bonus, bw_bonus, initial(P.sharpness)) var/limb_hit_text = "" if(hit_part) diff --git a/code/datums/components/pet_commands/fetch.dm b/code/datums/components/pet_commands/fetch.dm index a8723f8bd4bd..fa0b3193a447 100644 --- a/code/datums/components/pet_commands/fetch.dm +++ b/code/datums/components/pet_commands/fetch.dm @@ -3,7 +3,7 @@ * Watch for someone throwing or pointing at something and then go get it and bring it back. * If it's food we might eat it instead. */ -/datum/pet_command/point_targetting/fetch +/datum/pet_command/point_targeting/fetch command_name = "Fetch" command_desc = "Command your pet to retrieve something you throw or point at." radial_icon = 'icons/mob/actions/actions_spells.dmi' @@ -16,20 +16,22 @@ /// If true, this is a poorly trained pet who will eat food you throw instead of bringing it back var/will_eat_targets = TRUE -/datum/pet_command/point_targetting/fetch/New(mob/living/parent) +/datum/pet_command/point_targeting/fetch/New(mob/living/parent) . = ..() + if(isnull(parent)) + return parent.AddElement(/datum/element/ai_held_item) // We don't remove this on destroy because they might still be holding something -/datum/pet_command/point_targetting/fetch/add_new_friend(mob/living/tamer) +/datum/pet_command/point_targeting/fetch/add_new_friend(mob/living/tamer) . = ..() RegisterSignal(tamer, COMSIG_MOB_THROW, PROC_REF(listened_throw)) -/datum/pet_command/point_targetting/fetch/remove_friend(mob/living/unfriended) +/datum/pet_command/point_targeting/fetch/remove_friend(mob/living/unfriended) . = ..() UnregisterSignal(unfriended, COMSIG_MOB_THROW) /// A friend has thrown something, if we're listening or at least not busy then go get it -/datum/pet_command/point_targetting/fetch/proc/listened_throw(mob/living/carbon/thrower) +/datum/pet_command/point_targeting/fetch/proc/listened_throw(mob/living/carbon/thrower) SIGNAL_HANDLER var/mob/living/parent = weak_parent.resolve() @@ -55,7 +57,7 @@ RegisterSignal(thrown_thing, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(listen_throw_land)) /// A throw we were listening to has finished, see if it's in range for us to try grabbing it -/datum/pet_command/point_targetting/fetch/proc/listen_throw_land(obj/item/thrown_thing, datum/thrownthing/throwing_datum) +/datum/pet_command/point_targeting/fetch/proc/listen_throw_land(obj/item/thrown_thing, datum/thrownthing/throwing_datum) SIGNAL_HANDLER UnregisterSignal(thrown_thing, COMSIG_MOVABLE_THROW_LANDED) @@ -72,7 +74,7 @@ parent.ai_controller.set_blackboard_key(BB_FETCH_DELIVER_TO, throwing_datum.thrower) // Don't try and fetch turfs or anchored objects if someone points at them -/datum/pet_command/point_targetting/fetch/look_for_target(mob/living/pointing_friend, obj/item/pointed_atom) +/datum/pet_command/point_targeting/fetch/look_for_target(mob/living/pointing_friend, obj/item/pointed_atom) if (!istype(pointed_atom)) return FALSE if (pointed_atom.anchored) @@ -85,7 +87,7 @@ parent.ai_controller.set_blackboard_key(BB_FETCH_DELIVER_TO, pointing_friend) // Finally, plan our actions -/datum/pet_command/point_targetting/fetch/execute_action(datum/ai_controller/controller) +/datum/pet_command/point_targeting/fetch/execute_action(datum/ai_controller/controller) controller.queue_behavior(/datum/ai_behavior/forget_failed_fetches) var/atom/target = controller.blackboard[BB_CURRENT_PET_TARGET] diff --git a/code/datums/components/pet_commands/pet_command.dm b/code/datums/components/pet_commands/pet_command.dm index 7899ffce40ae..7762b9b2aa81 100644 --- a/code/datums/components/pet_commands/pet_command.dm +++ b/code/datums/components/pet_commands/pet_command.dm @@ -109,25 +109,25 @@ CRASH("Pet command execute action not implemented.") /** - * # Point Targetting Pet Command + * # Point Targeting Pet Command * As above but also listens for you pointing at something and marks it as a target */ -/datum/pet_command/point_targetting +/datum/pet_command/point_targeting /// Text describing an action we perform upon receiving a new target var/pointed_reaction - /// Blackboard key for targetting datum, this is likely going to need it - var/targetting_datum_key = BB_PET_TARGETTING_DATUM + /// Blackboard key for targeting strategy, this is likely going to need it + var/targeting_strategy_key = BB_PET_TARGETING_STRATEGY -/datum/pet_command/point_targetting/add_new_friend(mob/living/tamer) +/datum/pet_command/point_targeting/add_new_friend(mob/living/tamer) . = ..() RegisterSignal(tamer, COMSIG_MOB_POINTED, PROC_REF(look_for_target)) -/datum/pet_command/point_targetting/remove_friend(mob/living/unfriended) +/datum/pet_command/point_targeting/remove_friend(mob/living/unfriended) . = ..() UnregisterSignal(unfriended, COMSIG_MOB_POINTED) /// Target the pointed atom for actions -/datum/pet_command/point_targetting/proc/look_for_target(mob/living/friend, atom/pointed_atom) +/datum/pet_command/point_targeting/proc/look_for_target(mob/living/friend, atom/pointed_atom) SIGNAL_HANDLER var/mob/living/parent = weak_parent.resolve() diff --git a/code/datums/components/pet_commands/pet_commands_basic.dm b/code/datums/components/pet_commands/pet_commands_basic.dm index a562c1168432..211b995b22e2 100644 --- a/code/datums/components/pet_commands/pet_commands_basic.dm +++ b/code/datums/components/pet_commands/pet_commands_basic.dm @@ -100,7 +100,7 @@ * # Pet Command: Attack * Tells a pet to chase and bite the next thing you point at */ -/datum/pet_command/point_targetting/attack +/datum/pet_command/point_targeting/attack command_name = "Attack" command_desc = "Command your pet to attack things that you point out to it." radial_icon = 'icons/effects/effects.dmi' @@ -111,17 +111,17 @@ pointed_reaction = "and growls" /// Balloon alert to display if providing an invalid target var/refuse_reaction = "shakes head" - /// Attack behaviour to use, generally you will want to override this to add some kind of cooldown + /// Attack behaviour to use var/attack_behaviour = /datum/ai_behavior/basic_melee_attack // Refuse to target things we can't target, chiefly other friends -/datum/pet_command/point_targetting/attack/set_command_target(mob/living/parent, atom/target) +/datum/pet_command/point_targeting/attack/set_command_target(mob/living/parent, atom/target) if (!target) return var/mob/living/living_parent = parent if (!living_parent.ai_controller) return - var/datum/targetting_datum/targeter = living_parent.ai_controller.blackboard[targetting_datum_key] + var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(living_parent.ai_controller.blackboard[targeting_strategy_key]) if (!targeter) return if (!targeter.can_attack(living_parent, target)) @@ -129,21 +129,51 @@ return return ..() -/// Display feedback about not targetting something -/datum/pet_command/point_targetting/attack/proc/refuse_target(mob/living/parent, atom/target) +/// Display feedback about not targeting something +/datum/pet_command/point_targeting/attack/proc/refuse_target(mob/living/parent, atom/target) var/mob/living/living_parent = parent living_parent.balloon_alert_to_viewers("[refuse_reaction]") living_parent.visible_message(span_notice("[living_parent] refuses to attack [target].")) -/datum/pet_command/point_targetting/attack/execute_action(datum/ai_controller/controller) - controller.queue_behavior(attack_behaviour, BB_CURRENT_PET_TARGET, targetting_datum_key) +/datum/pet_command/point_targeting/attack/execute_action(datum/ai_controller/controller) + controller.queue_behavior(attack_behaviour, BB_CURRENT_PET_TARGET, targeting_strategy_key) + return SUBTREE_RETURN_FINISH_PLANNING + +/** + * # Breed command. breed with a partner! + */ +/datum/pet_command/point_targeting/breed + command_name = "Breed" + command_desc = "Command your pet to attempt to breed with a partner." + radial_icon = 'icons/mob/simple/animal.dmi' + radial_icon_state = "heart" + speech_commands = list("breed", "consummate") + +/datum/pet_command/point_targeting/breed/set_command_target(mob/living/parent, atom/target) + if(isnull(target) || !isliving(target)) + return + if(!HAS_TRAIT(parent, TRAIT_MOB_BREEDER) || !HAS_TRAIT(target, TRAIT_MOB_BREEDER)) + return + if(isnull(parent.ai_controller)) + return + if(!parent.ai_controller.blackboard[BB_BREED_READY] || isnull(parent.ai_controller.blackboard[BB_BABIES_PARTNER_TYPES])) + return + var/mob/living/living_target = target + if(!living_target.ai_controller?.blackboard[BB_BREED_READY]) + return + return ..() + +/datum/pet_command/point_targeting/breed/execute_action(datum/ai_controller/controller) + if(is_type_in_list(controller.blackboard[BB_CURRENT_PET_TARGET], controller.blackboard[BB_BABIES_PARTNER_TYPES])) + controller.queue_behavior(/datum/ai_behavior/make_babies, BB_CURRENT_PET_TARGET) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) return SUBTREE_RETURN_FINISH_PLANNING /** * # Pet Command: Targetted Ability * Tells a pet to use some kind of ability on the next thing you point at */ -/datum/pet_command/point_targetting/use_ability +/datum/pet_command/point_targeting/use_ability command_name = "Use ability" command_desc = "Command your pet to use one of its special skills on something that you point out to it." radial_icon = 'icons/mob/actions/actions_spells.dmi' @@ -154,7 +184,7 @@ /// Blackboard key where a reference to some kind of mob ability is stored var/pet_ability_key -/datum/pet_command/point_targetting/use_ability/execute_action(datum/ai_controller/controller) +/datum/pet_command/point_targeting/use_ability/execute_action(datum/ai_controller/controller) if (!pet_ability_key) return var/datum/action/cooldown/using_action = controller.blackboard[pet_ability_key] @@ -164,3 +194,45 @@ // We also don't check if the cooldown is over because there's no way a pet owner can know that, the behaviour will handle it controller.queue_behavior(/datum/ai_behavior/pet_use_ability, pet_ability_key, BB_CURRENT_PET_TARGET) return SUBTREE_RETURN_FINISH_PLANNING + +/datum/pet_command/protect_owner + command_name = "Protect owner" + command_desc = "Your pet will run to your aid." + hidden = TRUE + ///the range our owner needs to be in for us to protect him + var/protect_range = 9 + ///the behavior we will use when he is attacked + var/protect_behavior = /datum/ai_behavior/basic_melee_attack + +/datum/pet_command/protect_owner/add_new_friend(mob/living/tamer) + RegisterSignal(tamer, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(set_attacking_target)) + if(!HAS_TRAIT(tamer, TRAIT_RELAYING_ATTACKER)) + tamer.AddElement(/datum/element/relay_attackers) + +/datum/pet_command/protect_owner/remove_friend(mob/living/unfriended) + UnregisterSignal(unfriended, COMSIG_ATOM_WAS_ATTACKED) + +/datum/pet_command/protect_owner/execute_action(datum/ai_controller/controller) + var/mob/living/victim = controller.blackboard[BB_CURRENT_PET_TARGET] + if(QDELETED(victim)) + return + // cancel the action if they're below our given crit stat, OR if we're trying to attack ourselves (this can happen on tamed mobs w/ protect subtree rarely) + if(victim.stat > controller.blackboard[BB_TARGET_MINIMUM_STAT] || victim == controller.pawn) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + return + controller.queue_behavior(protect_behavior, BB_CURRENT_PET_TARGET, BB_PET_TARGETING_STRATEGY) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/pet_command/protect_owner/set_command_active(mob/living/parent, mob/living/victim) + . = ..() + set_command_target(parent, victim) + +/datum/pet_command/protect_owner/proc/set_attacking_target(atom/source, mob/living/attacker) + var/mob/living/basic/owner = weak_parent.resolve() + if(isnull(owner)) + return + var/mob/living/current_target = owner.ai_controller?.blackboard[BB_CURRENT_PET_TARGET] + if(attacker == current_target) //we are already dealing with this target + return + if(isliving(attacker) && can_see(owner, attacker, protect_range)) + set_command_active(owner, attacker) diff --git a/code/datums/components/pinata.dm b/code/datums/components/pinata.dm index 8f6866c5d949..1056200e3e27 100644 --- a/code/datums/components/pinata.dm +++ b/code/datums/components/pinata.dm @@ -33,7 +33,7 @@ return return COMPONENT_INCOMPATIBLE -/datum/component/pinata/proc/damage_inflicted(obj/target, damage, damage_type) +/datum/component/pinata/proc/damage_inflicted(obj/target, damage, damage_type, ...) SIGNAL_HANDLER if(damage < minimum_damage || damage_type == STAMINA || damage_type == OXY) return diff --git a/code/datums/components/pry_open_door.dm b/code/datums/components/pry_open_door.dm deleted file mode 100644 index 17e445d25ca8..000000000000 --- a/code/datums/components/pry_open_door.dm +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Attached to a basic mob. - * Causes attacks on doors to attempt to open them. - */ -/datum/component/pry_open_door - /// Odds the attack opens the door - var/open_chance - /// Time it takes to open a door with force - var/force_wait - -/datum/component/pry_open_door/Initialize(open_chance = 100, force_wait = 10 SECONDS) - . = ..() - - if(!isbasicmob(parent)) - return COMPONENT_INCOMPATIBLE - src.open_chance = open_chance - src.force_wait = force_wait - -/datum/component/pry_open_door/RegisterWithParent() - RegisterSignal(parent, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(hostile_attackingtarget)) - -/datum/component/pry_open_door/UnregisterFromParent() - UnregisterSignal(parent, COMSIG_HOSTILE_POST_ATTACKINGTARGET) - -/datum/component/pry_open_door/proc/hostile_attackingtarget(mob/living/basic/attacker, atom/target, success) - SIGNAL_HANDLER - - if(!success) - return - if(istype(target, /obj/machinery/door/airlock) && prob(open_chance)) - var/obj/machinery/door/airlock/airlock_target = target - INVOKE_ASYNC(src, PROC_REF(open_door), attacker, airlock_target) - -/datum/component/pry_open_door/proc/open_door(mob/living/basic/attacker, obj/machinery/door/airlock/airlock_target) - if(airlock_target.locked) - to_chat(attacker, span_warning("The airlock's bolts prevent it from being forced!")) - return - else if(!airlock_target.allowed(attacker) && airlock_target.hasPower()) - attacker.visible_message(span_warning("We start forcing the [airlock_target] open."), \ - span_hear("You hear a metal screeching sound.")) - playsound(airlock_target, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) - if(!do_after(attacker, force_wait, airlock_target)) - return - if(airlock_target.locked) - return - attacker.visible_message(span_warning("We force the [airlock_target] to open.")) - airlock_target.open(BYPASS_DOOR_CHECKS) - else if(!airlock_target.hasPower()) - attacker.visible_message(span_warning("We force the [airlock_target] to open.")) - airlock_target.open(FORCING_DOOR_CHECKS) - else - airlock_target.open(DEFAULT_DOOR_CHECKS) diff --git a/code/datums/components/ranged_attacks.dm b/code/datums/components/ranged_attacks.dm index 1a3bcacd5d05..aecaa2c18086 100644 --- a/code/datums/components/ranged_attacks.dm +++ b/code/datums/components/ranged_attacks.dm @@ -8,6 +8,10 @@ var/projectile_type /// Sound to play when we fire our projectile var/projectile_sound + /// how many shots we will fire + var/burst_shots + /// intervals between shots + var/burst_intervals /// Time to wait between shots var/cooldown_time /// Tracks time between shots @@ -17,6 +21,8 @@ casing_type, projectile_type, projectile_sound = 'sound/weapons/gun/pistol/shot.ogg', + burst_shots, + burst_intervals = 0.2 SECONDS, cooldown_time = 3 SECONDS, ) . = ..() @@ -32,6 +38,10 @@ CRASH("Set both casing type and projectile type in [parent]'s ranged attacks component! uhoh! stinky!") if (!casing_type && !projectile_type) CRASH("Set neither casing type nor projectile type in [parent]'s ranged attacks component! What are they supposed to be attacking with, air?") + if(burst_shots <= 1) + return + src.burst_shots = burst_shots + src.burst_intervals = burst_intervals /datum/component/ranged_attacks/RegisterWithParent() . = ..() @@ -49,12 +59,16 @@ return COOLDOWN_START(src, fire_cooldown, cooldown_time) INVOKE_ASYNC(src, PROC_REF(async_fire_ranged_attack), firer, target, modifiers) - SEND_SIGNAL(parent, COMSIG_BASICMOB_POST_ATTACK_RANGED, target, modifiers) + if(isnull(burst_shots)) + return + for(var/i in 1 to (burst_shots - 1)) + addtimer(CALLBACK(src, PROC_REF(async_fire_ranged_attack), firer, target, modifiers), i * burst_intervals) /// Actually fire the damn thing /datum/component/ranged_attacks/proc/async_fire_ranged_attack(mob/living/basic/firer, atom/target, modifiers) if(projectile_type) firer.fire_projectile(projectile_type, target, projectile_sound) + SEND_SIGNAL(parent, COMSIG_BASICMOB_POST_ATTACK_RANGED, target, modifiers) return playsound(firer, projectile_sound, 100, TRUE) var/turf/startloc = get_turf(firer) @@ -66,6 +80,8 @@ else target_zone = ran_zone() casing.fire_casing(target, firer, null, null, null, target_zone, 0, firer) + casing.update_appearance() casing.AddElement(/datum/element/temporary_atom, 30 SECONDS) + SEND_SIGNAL(parent, COMSIG_BASICMOB_POST_ATTACK_RANGED, target, modifiers) return diff --git a/code/datums/components/ranged_mob_full_auto.dm b/code/datums/components/ranged_mob_full_auto.dm new file mode 100644 index 000000000000..13cf3b1a4952 --- /dev/null +++ b/code/datums/components/ranged_mob_full_auto.dm @@ -0,0 +1,203 @@ +#define AUTOFIRE_MOUSEUP 1 +#define AUTOFIRE_MOUSEDOWN 0 + +/// Allows a mob to autofire by holding down the cursor +/datum/component/ranged_mob_full_auto + /// Delay before attempting to fire again, note that this is just when we make attempts and is separate from mob's actual firing cooldown + var/autofire_shot_delay + /// Our client for click tracking + var/client/clicker + /// Are we currently firing? + var/is_firing = FALSE + /// This seems hacky but there can be two MouseDown() without a MouseUp() in between if the user holds click and uses alt+tab, printscreen or similar. + var/awaiting_status = AUTOFIRE_MOUSEDOWN + /// What are we currently shooting at? + var/atom/target + /// Where are we currently shooting at? + var/turf/target_loc + /// When will we next try to shoot? + COOLDOWN_DECLARE(next_shot_cooldown) + +/datum/component/ranged_mob_full_auto/Initialize(autofire_shot_delay = 0.5 SECONDS) + . = ..() + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + src.autofire_shot_delay = autofire_shot_delay + + var/mob/living/living_parent = parent + if (isnull(living_parent.client)) + return + on_gained_client(parent) + +/datum/component/ranged_mob_full_auto/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOB_LOGIN, PROC_REF(on_gained_client)) + RegisterSignal(parent, COMSIG_MOB_LOGOUT, PROC_REF(on_lost_client)) + +/datum/component/ranged_mob_full_auto/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT)) + +/datum/component/ranged_mob_full_auto/process(seconds_per_tick) + if (!try_shooting()) + return PROCESS_KILL + +/// Try and take a shot, returns false if we are unable to do so and should stop trying +/datum/component/ranged_mob_full_auto/proc/try_shooting() + if (!is_firing) + return FALSE + if (!COOLDOWN_FINISHED(src, next_shot_cooldown)) + return TRUE // Don't fire but also keep processing + + var/mob/living/living_parent = parent + + if (isnull(target) || get_turf(target) != target_loc) // Target moved or got destroyed since we last aimed. + set_target(target_loc) + target = target_loc // So we keep firing on the emptied tile until we move our mouse and find a new target. + if (get_dist(living_parent, target) <= 0) + set_target(get_step(living_parent, living_parent.dir)) // Shoot in the direction faced if the mouse is on the same tile as we are. + target_loc = target + else if (!in_view_range(living_parent, target)) + stop_firing() + return FALSE // Can't see shit + + living_parent.face_atom(target) + COOLDOWN_START(src, next_shot_cooldown, autofire_shot_delay) + living_parent.RangedAttack(target) + return TRUE + +/// Setter for reference handling +/datum/component/ranged_mob_full_auto/proc/set_target(atom/new_target) + if (!isnull(target)) + UnregisterSignal(target, COMSIG_PARENT_QDELETING) + target = new_target + if (!isnull(target)) + RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(on_target_deleted)) + +/// Don't hang references +/datum/component/ranged_mob_full_auto/proc/on_target_deleted() + SIGNAL_HANDLER + set_target(null) + +/// When we gain a client, start tracking clicks +/datum/component/ranged_mob_full_auto/proc/on_gained_client(mob/living/source) + SIGNAL_HANDLER + clicker = source.client + RegisterSignal(clicker, COMSIG_CLIENT_MOUSEDOWN, PROC_REF(on_mouse_down)) + +/// When we lose our client, stop functioning +/datum/component/ranged_mob_full_auto/proc/on_lost_client(mob/living/source) + SIGNAL_HANDLER + if (!isnull(clicker)) + UnregisterSignal(clicker, list(COMSIG_CLIENT_MOUSEDOWN, COMSIG_CLIENT_MOUSEDRAG, COMSIG_CLIENT_MOUSEUP)) + stop_firing() + clicker = null + +/// On mouse down start shooting! +/datum/component/ranged_mob_full_auto/proc/on_mouse_down(client/source, atom/target, turf/location, control, params) + SIGNAL_HANDLER + if (awaiting_status != AUTOFIRE_MOUSEDOWN) + return // Avoid a double mousedown with no mouseup + var/list/modifiers = params2list(params) + + if (LAZYACCESS(modifiers, SHIFT_CLICK)) + return + if (LAZYACCESS(modifiers, CTRL_CLICK)) + return + if (LAZYACCESS(modifiers, MIDDLE_CLICK)) + return + if (LAZYACCESS(modifiers, RIGHT_CLICK)) + return + if (LAZYACCESS(modifiers, ALT_CLICK)) + return + var/mob/living/living_parent = parent + if (!isturf(living_parent.loc) || living_parent.Adjacent(target)) + return + + if (isnull(location) || istype(target, /atom/movable/screen)) // Clicking on a screen object. + if (target.plane != CLICKCATCHER_PLANE) // The clickcatcher is a special case. We want the click to trigger then, under it. + return // If we click and drag on our worn backpack, for example, we want it to open instead. + set_target(parse_caught_click_modifiers(modifiers, get_turf(source.eye), source)) + params = list2params(modifiers) + if (isnull(target)) + CRASH("Failed to get the turf under clickcatcher") + + awaiting_status = AUTOFIRE_MOUSEUP + source.click_intercept_time = world.time // From this point onwards Click() will no longer be triggered. + if (is_firing) + stop_firing() + + set_target(target) + target_loc = get_turf(target) + INVOKE_ASYNC(src, PROC_REF(start_firing)) + +/// Start tracking mouse movement and processing our shots +/datum/component/ranged_mob_full_auto/proc/start_firing() + if (is_firing) + return + + is_firing = TRUE + if (!try_shooting()) // First one is immediate + stop_firing() + return + + clicker.mouse_override_icon = 'icons/effects/mouse_pointers/weapon_pointer.dmi' + clicker.mouse_pointer_icon = clicker.mouse_override_icon + + START_PROCESSING(SSprojectiles, src) + RegisterSignal(clicker, COMSIG_CLIENT_MOUSEUP, PROC_REF(on_mouse_up)) + RegisterSignal(clicker, COMSIG_CLIENT_MOUSEDRAG, PROC_REF(on_mouse_drag)) + +/// When the mouse moved let's try and shift our aim +/datum/component/ranged_mob_full_auto/proc/on_mouse_drag(client/source, atom/src_object, atom/over_object, turf/src_location, turf/over_location, src_control, over_control, params) + SIGNAL_HANDLER + if (!isnull(over_location)) + set_target(over_object) + target_loc = get_turf(over_object) + return + + //This happens when the mouse is over an inventory or screen object, or on entering deep darkness, for example. + var/list/modifiers = params2list(params) + var/new_target = parse_caught_click_modifiers(modifiers, get_turf(source.eye), source) + params = list2params(modifiers) + + if (!isnull(new_target)) + set_target(new_target) + target_loc = new_target + return + + if (QDELETED(target)) //No new target acquired, and old one was deleted, get us out of here. + stop_firing() + CRASH("on_mouse_drag failed to get the turf under screen object [over_object.type]. Old target was incidentally QDELETED.") + + + set_target(get_turf(target)) //If previous target wasn't a turf, let's turn it into one to avoid locking onto a potentially moving target. + target_loc = target + CRASH("on_mouse_drag failed to get the turf under screen object [over_object.type]") + +/// When the mouse is released we should stop +/datum/component/ranged_mob_full_auto/proc/on_mouse_up() + SIGNAL_HANDLER + if (awaiting_status != AUTOFIRE_MOUSEUP) + return + stop_firing() + return COMPONENT_CLIENT_MOUSEUP_INTERCEPT + +/// Stop watching our mouse and processing shots +/datum/component/ranged_mob_full_auto/proc/stop_firing() + if (!is_firing) + return + + is_firing = FALSE + set_target(null) + target_loc = null + STOP_PROCESSING(SSprojectiles, src) + awaiting_status = AUTOFIRE_MOUSEDOWN + + if (isnull(clicker)) + return + UnregisterSignal(clicker, list(COMSIG_CLIENT_MOUSEDRAG, COMSIG_CLIENT_MOUSEUP)) + clicker.mouse_override_icon = null + clicker.mouse_pointer_icon = null + +#undef AUTOFIRE_MOUSEUP +#undef AUTOFIRE_MOUSEDOWN diff --git a/code/datums/components/recharging_attacks.dm b/code/datums/components/recharging_attacks.dm new file mode 100644 index 000000000000..ad4670b2dbfc --- /dev/null +++ b/code/datums/components/recharging_attacks.dm @@ -0,0 +1,66 @@ +/// Reduces the cooldown of a given action upon landing attacks, critting, or killing mobs. +/datum/component/recharging_attacks + /// The target of the most recent attack + var/last_target + /// The stat of the most recently attacked mob + var/last_stat + /// The action to recharge when attacking + var/datum/action/cooldown/recharged_action + /// The amount of cooldown to refund on a successful attack + var/attack_refund + /// The amount of cooldown to refund when putting a target into critical + var/crit_refund + +/datum/component/recharging_attacks/Initialize( + datum/action/cooldown/recharged_action, + attack_refund = 1 SECONDS, + crit_refund = 5 SECONDS, +) + . = ..() + if (!isbasicmob(parent) || !istype(recharged_action)) + return COMPONENT_INCOMPATIBLE + src.recharged_action = recharged_action + src.attack_refund = attack_refund + src.crit_refund = crit_refund + +/datum/component/recharging_attacks/Destroy() + UnregisterSignal(recharged_action, COMSIG_PARENT_QDELETING) + recharged_action = null + return ..() + +/datum/component/recharging_attacks/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(set_old_stat)) + RegisterSignal(parent, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(check_stat)) + RegisterSignal(recharged_action, COMSIG_PARENT_QDELETING, PROC_REF(on_action_qdel)) + +/datum/component/recharging_attacks/UnregisterFromParent() + . = ..() + UnregisterSignal(parent, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_HOSTILE_POST_ATTACKINGTARGET)) + if(recharged_action) + UnregisterSignal(recharged_action, COMSIG_PARENT_QDELETING) + +/datum/component/recharging_attacks/proc/set_old_stat(mob/attacker, mob/attacked) + SIGNAL_HANDLER + if(!isliving(attacked)) + return + last_target = attacked + last_stat = attacked.stat + +/datum/component/recharging_attacks/proc/check_stat(mob/living/attacker, mob/living/attacked, success) + SIGNAL_HANDLER + if(!isliving(attacked) || attacked != last_target || attacker.faction_check_atom(attacked)) + return + + var/final_refund = attack_refund + if(QDELETED(attacked) || (attacked.stat == DEAD && last_stat != DEAD)) //The target is dead and we killed them - full refund + final_refund = recharged_action.cooldown_time + else if(attacked.stat > CONSCIOUS && last_stat == CONSCIOUS) //We knocked the target unconscious - partial refund + final_refund = crit_refund + + recharged_action.next_use_time -= final_refund + recharged_action.build_all_button_icons() + +/datum/component/recharging_attacks/proc/on_action_qdel() + SIGNAL_HANDLER + qdel(src) diff --git a/code/datums/components/regenerator.dm b/code/datums/components/regenerator.dm index a6182935a3f0..b33e68abcbc5 100644 --- a/code/datums/components/regenerator.dm +++ b/code/datums/components/regenerator.dm @@ -45,7 +45,7 @@ deltimer(regeneration_start_timer) /// When you take damage, reset the cooldown and start processing -/datum/component/regenerator/proc/on_take_damage(datum/source, damage, damagetype) +/datum/component/regenerator/proc/on_take_damage(datum/source, damage, damagetype, ...) SIGNAL_HANDLER if (damage <= 0) diff --git a/code/datums/components/revenge_ability.dm b/code/datums/components/revenge_ability.dm index a4ba7b4fb515..b79f435662bc 100644 --- a/code/datums/components/revenge_ability.dm +++ b/code/datums/components/revenge_ability.dm @@ -8,7 +8,7 @@ /// The ability to use when we are attacked var/datum/action/cooldown/ability /// Optional datum for validating targets - var/datum/targetting_datum/targetting + var/datum/targeting_strategy/targeting /// Trigger only if target is at least this far away var/min_range /// Trigger only if target is at least this close @@ -16,12 +16,12 @@ /// Target the ability at ourself instead of at the offender var/target_self -/datum/component/revenge_ability/Initialize(datum/action/cooldown/ability, datum/targetting_datum/targetting, min_range = 0, max_range = INFINITY, target_self = FALSE) +/datum/component/revenge_ability/Initialize(datum/action/cooldown/ability, datum/targeting_strategy/targeting, min_range = 0, max_range = INFINITY, target_self = FALSE) . = ..() if (!isliving(parent)) return COMPONENT_INCOMPATIBLE src.ability = ability - src.targetting = targetting + src.targeting = targeting src.min_range = min_range src.max_range = max_range src.target_self = target_self @@ -45,7 +45,7 @@ var/distance = get_dist(ability_user, attacker) if (distance < min_range || distance > max_range) return - if (targetting && !targetting.can_attack(victim, attacker)) + if (targeting && !targeting.can_attack(victim, attacker)) return INVOKE_ASYNC(ability, TYPE_PROC_REF(/datum/action/cooldown, InterceptClickOn), ability_user, null, (target_self) ? ability_user : attacker) diff --git a/code/datums/components/riding/riding.dm b/code/datums/components/riding/riding.dm index 14358a5c5a38..5a043ef6f069 100644 --- a/code/datums/components/riding/riding.dm +++ b/code/datums/components/riding/riding.dm @@ -64,6 +64,8 @@ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(vehicle_moved)) RegisterSignal(parent, COMSIG_MOVABLE_BUMP, PROC_REF(vehicle_bump)) RegisterSignal(parent, COMSIG_BUCKLED_CAN_Z_MOVE, PROC_REF(riding_can_z_move)) + RegisterSignals(parent, GLOB.movement_type_addtrait_signals, PROC_REF(on_movement_type_trait_gain)) + RegisterSignals(parent, GLOB.movement_type_removetrait_signals, PROC_REF(on_movement_type_trait_loss)) /** * This proc handles all of the proc calls to things like set_vehicle_dir_layer() that a type of riding datum needs to call on creation @@ -84,6 +86,10 @@ unequip_buckle_inhands(rider) rider.updating_glide_size = TRUE UnregisterSignal(rider, COMSIG_LIVING_TRY_PULL) + for (var/trait in GLOB.movement_type_trait_to_flag) + if (HAS_TRAIT(parent, trait)) + REMOVE_TRAIT(rider, trait, REF(src)) + REMOVE_TRAIT(rider, TRAIT_NO_FLOATING_ANIM, REF(src)) if(!movable_parent.has_buckled_mobs()) qdel(src) @@ -99,6 +105,11 @@ rider.stop_pulling() RegisterSignal(rider, COMSIG_LIVING_TRY_PULL, PROC_REF(on_rider_try_pull)) + for (var/trait in GLOB.movement_type_trait_to_flag) + if (HAS_TRAIT(parent, trait)) + ADD_TRAIT(rider, trait, REF(src)) + ADD_TRAIT(rider, TRAIT_NO_FLOATING_ANIM, REF(src)) + /// This proc is called when the rider attempts to grab the thing they're riding, preventing them from doing so. /datum/component/riding/proc/on_rider_try_pull(mob/living/rider_pulling, atom/movable/target, force) SIGNAL_HANDLER @@ -273,3 +284,24 @@ /datum/component/riding/proc/riding_can_z_move(atom/movable/movable_parent, direction, turf/start, turf/destination, z_move_flags, mob/living/rider) SIGNAL_HANDLER return COMPONENT_RIDDEN_ALLOW_Z_MOVE + +/// Called when our vehicle gains a movement trait, so we can apply it to the riders +/datum/component/riding/proc/on_movement_type_trait_gain(atom/movable/source, trait) + SIGNAL_HANDLER + var/atom/movable/movable_parent = parent + for (var/mob/rider in movable_parent.buckled_mobs) + ADD_TRAIT(rider, trait, REF(src)) + +/// Called when our vehicle loses a movement trait, so we can remove it from the riders +/datum/component/riding/proc/on_movement_type_trait_loss(atom/movable/source, trait) + SIGNAL_HANDLER + var/atom/movable/movable_parent = parent + for (var/mob/rider in movable_parent.buckled_mobs) + REMOVE_TRAIT(rider, trait, REF(src)) + +/datum/component/riding/proc/force_unbuckle(atom/movable/source, mob/living/living_hitter) + SIGNAL_HANDLER + + if((living_hitter in source.buckled_mobs)) + return + return COMPONENT_CANCEL_ATTACK_CHAIN diff --git a/code/datums/components/riding/riding_mob.dm b/code/datums/components/riding/riding_mob.dm index 5a829df75f81..d72b9e3c8d7d 100644 --- a/code/datums/components/riding/riding_mob.dm +++ b/code/datums/components/riding/riding_mob.dm @@ -203,7 +203,7 @@ human_parent.buckle_lying = 0 // the riding mob is made nondense so they don't bump into any dense atoms the carrier is pulling, // since pulled movables are moved before buckled movables - riding_mob.set_density(FALSE) + ADD_TRAIT(riding_mob, TRAIT_UNDENSE, VEHICLE_TRAIT) else if(ride_check_flags & CARRIER_NEEDS_ARM) // fireman human_parent.buckle_lying = 90 @@ -227,7 +227,7 @@ unequip_buckle_inhands(parent) var/mob/living/carbon/human/H = parent H.remove_movespeed_modifier(/datum/movespeed_modifier/human_carry) - former_rider.set_density(!former_rider.body_position) + REMOVE_TRAIT(H, TRAIT_UNDENSE, VEHICLE_TRAIT) return ..() /// If the carrier shoves the person they're carrying, force the carried mob off @@ -440,7 +440,7 @@ set_vehicle_dir_layer(WEST, ABOVE_MOB_LAYER) /datum/component/riding/creature/guardian/ride_check(mob/living/user, consequences = TRUE) - var/mob/living/simple_animal/hostile/guardian/charger = parent + var/mob/living/basic/guardian/charger = parent if(!istype(charger)) return ..() return charger.summoner == user diff --git a/code/datums/components/rot.dm b/code/datums/components/rot.dm index beea0d209b27..3dafdf420d4a 100644 --- a/code/datums/components/rot.dm +++ b/code/datums/components/rot.dm @@ -114,7 +114,7 @@ /datum/component/rot/proc/rot_react_touch(datum/source, mob/living/react_to) SIGNAL_HANDLER - rot_react(source, react_to, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + rot_react(source, react_to, pick(GLOB.arm_zones)) /// Triggered when something enters the component's parent. /datum/component/rot/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) diff --git a/code/datums/components/seethrough_mob.dm b/code/datums/components/seethrough_mob.dm new file mode 100644 index 000000000000..4359c454f1a1 --- /dev/null +++ b/code/datums/components/seethrough_mob.dm @@ -0,0 +1,135 @@ +///A component that lets you turn your character transparent in order to see and click through yourself. +/datum/component/seethrough_mob + ///The atom that enables our dark magic + var/atom/movable/render_source_atom + ///The fake version of ourselves + var/image/trickery_image + ///Which alpha do we animate towards? + var/target_alpha + ///How long our faze in/out takes + var/animation_time + ///Does this object let clicks from players its transparent to pass through it + var/clickthrough + ///Is the seethrough effect currently active + var/is_active + ///The mob's original render_target value + var/initial_render_target_value + ///This component's personal uid + var/personal_uid + +/datum/component/seethrough_mob/Initialize(target_alpha = 100, animation_time = 0.5 SECONDS, clickthrough = TRUE) + . = ..() + + if(!ismob(parent)) + return COMPONENT_INCOMPATIBLE + + src.target_alpha = target_alpha + src.animation_time = animation_time + src.clickthrough = clickthrough + src.is_active = FALSE + src.render_source_atom = new() + + var/static/uid = 0 + uid++ + src.personal_uid = uid + + render_source_atom.appearance_flags |= ( RESET_COLOR | RESET_TRANSFORM) + + render_source_atom.vis_flags |= (VIS_INHERIT_ID | VIS_INHERIT_PLANE | VIS_INHERIT_LAYER) + + render_source_atom.render_source = "*transparent_bigmob[personal_uid]" + + var/datum/action/cooldown/toggle_seethrough/action = new(src) + action.Grant(parent) + +/datum/component/seethrough_mob/Destroy(force, silent) + QDEL_NULL(render_source_atom) + return ..() + +///Set up everything we need to trick the client and keep it looking normal for everyone else +/datum/component/seethrough_mob/proc/trick_mob() + SIGNAL_HANDLER + + var/mob/fool = parent + var/datum/hud/our_hud = fool.hud_used + for(var/atom/movable/screen/plane_master/seethrough as anything in our_hud.get_true_plane_masters(SEETHROUGH_PLANE)) + seethrough.unhide_plane(fool) + + var/icon/current_mob_icon = icon(fool.icon, fool.icon_state) + render_source_atom.pixel_x = -fool.pixel_x + render_source_atom.pixel_y = ((current_mob_icon.Height() - 32) * 0.5) + + initial_render_target_value = fool.render_target + fool.render_target = "*transparent_bigmob[personal_uid]" + fool.vis_contents.Add(render_source_atom) + + trickery_image = new(render_source_atom) + trickery_image.loc = render_source_atom + trickery_image.override = TRUE + + trickery_image.pixel_x = 0 + trickery_image.pixel_y = 0 + + if(clickthrough) + //Special plane so we can click through the overlay + SET_PLANE_EXPLICIT(trickery_image, SEETHROUGH_PLANE, fool) + + fool.client.images += trickery_image + + animate(trickery_image, alpha = target_alpha, time = animation_time) + + RegisterSignal(fool, COMSIG_MOB_LOGOUT, PROC_REF(on_client_disconnect)) + +///Remove the screen object and make us appear solid to ourselves again +/datum/component/seethrough_mob/proc/untrick_mob() + var/mob/fool = parent + animate(trickery_image, alpha = 255, time = animation_time) + UnregisterSignal(fool, COMSIG_MOB_LOGOUT) + + //after playing the fade-in animation, remove the image and the trick atom + addtimer(CALLBACK(src, PROC_REF(clear_image), trickery_image, fool.client), animation_time) + +///Remove the image and the trick atom +/datum/component/seethrough_mob/proc/clear_image(image/removee, client/remove_from) + var/atom/movable/atom_parent = parent + atom_parent.vis_contents -= render_source_atom + atom_parent.render_target = initial_render_target_value + remove_from?.images -= removee + +///Effect is disabled when they log out because client gets deleted +/datum/component/seethrough_mob/proc/on_client_disconnect() + SIGNAL_HANDLER + + var/mob/fool = parent + UnregisterSignal(fool, COMSIG_MOB_LOGOUT) + var/datum/hud/our_hud = fool.hud_used + for(var/atom/movable/screen/plane_master/seethrough as anything in our_hud.get_true_plane_masters(SEETHROUGH_PLANE)) + seethrough.hide_plane(fool) + clear_image(trickery_image, fool.client) + +/datum/component/seethrough_mob/proc/toggle_active() + is_active = !is_active + if(is_active) + trick_mob() + else + untrick_mob() + +/datum/action/cooldown/toggle_seethrough + name = "Toggle Seethrough" + desc = "Allows you to see behind your massive body and click through it." + button_icon = 'icons/mob/actions/actions_xeno.dmi' + button_icon_state = "alien_sneak" + background_icon_state = "bg_alien" + cooldown_time = 1 SECONDS + melee_cooldown_time = 0 + +/datum/action/cooldown/toggle_seethrough/Remove(mob/remove_from) + var/datum/component/seethrough_mob/transparency = target + if(transparency.is_active) + transparency.untrick_mob() + return ..() + +/datum/action/cooldown/toggle_seethrough/Activate(atom/t) + StartCooldown() + var/datum/component/seethrough_mob/transparency = target + transparency.toggle_active() diff --git a/code/datums/components/shrink.dm b/code/datums/components/shrink.dm index 9c1c5f76dcde..67cd3d39e23c 100644 --- a/code/datums/components/shrink.dm +++ b/code/datums/components/shrink.dm @@ -10,10 +10,11 @@ parent_atom.transform = parent_atom.transform.Scale(0.5,0.5) olddens = parent_atom.density oldopac = parent_atom.opacity - parent_atom.set_density(FALSE) + parent_atom.set_opacity(FALSE) if(isliving(parent_atom)) var/mob/living/L = parent_atom + ADD_TRAIT(L, TRAIT_UNDENSE, SHRUNKEN_TRAIT) L.add_movespeed_modifier(/datum/movespeed_modifier/shrink_ray) if(iscarbon(L)) var/mob/living/carbon/C = L @@ -23,6 +24,8 @@ if(ishuman(C)) var/mob/living/carbon/human/H = C H.physiology.damage_resistance -= 100//carbons take double damage while shrunk + else + parent_atom.set_density(FALSE) // this is handled by the UNDENSE trait on mobs parent_atom.visible_message(span_warning("[parent_atom] shrinks down to a tiny size!"), span_userdanger("Everything grows bigger!")) QDEL_IN(src, shrink_time) @@ -30,12 +33,14 @@ /datum/component/shrink/Destroy() var/atom/parent_atom = parent parent_atom.transform = parent_atom.transform.Scale(2,2) - parent_atom.set_density(olddens) parent_atom.set_opacity(oldopac) if(isliving(parent_atom)) var/mob/living/L = parent_atom L.remove_movespeed_modifier(/datum/movespeed_modifier/shrink_ray) + REMOVE_TRAIT(L, TRAIT_UNDENSE, SHRUNKEN_TRAIT) if(ishuman(L)) var/mob/living/carbon/human/H = L H.physiology.damage_resistance += 100 + else + parent_atom.set_density(olddens) // this is handled by the UNDENSE trait on mobs return ..() diff --git a/code/datums/components/singularity.dm b/code/datums/components/singularity.dm index 9637a2a746c1..5fdc230c3e14 100644 --- a/code/datums/components/singularity.dm +++ b/code/datums/components/singularity.dm @@ -101,7 +101,7 @@ ) AddComponent(/datum/component/connect_loc_behalf, parent, loc_connections) - RegisterSignal(parent, COMSIG_ATOM_BULLET_ACT, PROC_REF(consume_bullets)) + RegisterSignal(parent, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(consume_bullets)) if (notify_admins) admin_investigate_setup() @@ -127,7 +127,7 @@ COMSIG_ATOM_ATTACK_PAW, COMSIG_ATOM_BLOB_ACT, COMSIG_ATOM_BSA_BEAM, - COMSIG_ATOM_BULLET_ACT, + COMSIG_ATOM_PRE_BULLET_ACT, COMSIG_ATOM_BUMPED, COMSIG_MOVABLE_PRE_MOVE, COMSIG_PARENT_ATTACKBY, @@ -180,6 +180,7 @@ SIGNAL_HANDLER qdel(projectile) + return COMPONENT_BULLET_BLOCKED /// Calls singularity_act on the thing passed, usually destroying the object /datum/component/singularity/proc/default_singularity_act(atom/thing) diff --git a/code/datums/components/sitcomlaughter.dm b/code/datums/components/sitcomlaughter.dm index 8dfef21b749d..04a190d15f01 100644 --- a/code/datums/components/sitcomlaughter.dm +++ b/code/datums/components/sitcomlaughter.dm @@ -1,6 +1,6 @@ /datum/component/wearertargeting/sitcomlaughter valid_slots = list(ITEM_SLOT_HANDS, ITEM_SLOT_BELT, ITEM_SLOT_ID, ITEM_SLOT_LPOCKET, ITEM_SLOT_RPOCKET, ITEM_SLOT_SUITSTORE, ITEM_SLOT_DEX_STORAGE) - signals = list(COMSIG_MOB_CREAMED, COMSIG_ON_CARBON_SLIP, COMSIG_ON_VENDOR_CRUSH, COMSIG_MOB_CLUMSY_SHOOT_FOOT) + signals = list(COMSIG_MOB_CREAMED, COMSIG_ON_CARBON_SLIP, COMSIG_POST_TILT_AND_CRUSH, COMSIG_MOB_CLUMSY_SHOOT_FOOT) proctype = PROC_REF(EngageInComedy) mobtype = /mob/living ///Sounds used for when user has a sitcom action occur diff --git a/code/datums/components/slippery.dm b/code/datums/components/slippery.dm index ce9c80c5d348..3e0bbde193cb 100644 --- a/code/datums/components/slippery.dm +++ b/code/datums/components/slippery.dm @@ -42,9 +42,29 @@ if(isitem(parent)) RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip)) RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_drop)) + RegisterSignal(parent, COMSIG_ITEM_APPLY_FANTASY_BONUSES, PROC_REF(apply_fantasy_bonuses)) + RegisterSignal(parent, COMSIG_ITEM_REMOVE_FANTASY_BONUSES, PROC_REF(remove_fantasy_bonuses)) else RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(Slip)) +/datum/component/slippery/proc/apply_fantasy_bonuses(obj/item/source, bonus) + SIGNAL_HANDLER + knockdown_time = source.modify_fantasy_variable("knockdown_time", knockdown_time, bonus) + if(bonus >= 5) + paralyze_time = source.modify_fantasy_variable("paralyze_time", paralyze_time, bonus) + LAZYSET(source.fantasy_modifications, "lube_flags", lube_flags) + lube_flags |= SLIDE + if(bonus >= 10) + lube_flags |= GALOSHES_DONT_HELP|SLIP_WHEN_CRAWLING + +/datum/component/slippery/proc/remove_fantasy_bonuses(obj/item/source, bonus) + SIGNAL_HANDLER + knockdown_time = source.reset_fantasy_variable("knockdown_time", knockdown_time) + paralyze_time = source.reset_fantasy_variable("paralyze_time", paralyze_time) + var/previous_lube_flags = LAZYACCESS(source.fantasy_modifications, "lube_flags") + if(!isnull(previous_lube_flags)) + lube_flags = previous_lube_flags + /datum/component/slippery/proc/add_connect_loc_behalf_to_parent() if(ismovable(parent)) AddComponent(/datum/component/connect_loc_behalf, parent, default_connections) diff --git a/code/datums/components/soul_stealer.dm b/code/datums/components/soul_stealer.dm index 88ce917e7e11..9c0619cfc8c2 100644 --- a/code/datums/components/soul_stealer.dm +++ b/code/datums/components/soul_stealer.dm @@ -5,13 +5,16 @@ * Used in the cult bastard sword! */ /datum/component/soul_stealer + var/obj/item/soulstone/soulstone_type /// List of soulstones captured by this item. var/list/obj/item/soulstone/soulstones = list() -/datum/component/soul_stealer/Initialize() +/datum/component/soul_stealer/Initialize(soulstone_type = /obj/item/soulstone/anybody/purified) if(!isitem(parent)) return COMPONENT_INCOMPATIBLE + src.soulstone_type = soulstone_type + /datum/component/soul_stealer/Destroy() QDEL_LIST(soulstones) // We own these, so we'll also just get rid of them. Any souls inside will die, this is fine. return ..() @@ -60,7 +63,7 @@ /datum/component/soul_stealer/proc/try_capture(mob/living/carbon/human/victim, mob/living/captor) if(victim.stat == CONSCIOUS) return - var/obj/item/soulstone/soulstone = new(parent) + var/obj/item/soulstone/soulstone = new soulstone_type(parent) soulstone.attack(victim, captor) if(!length(soulstone.contents)) // failed qdel(soulstone) diff --git a/code/datums/components/spirit_holding.dm b/code/datums/components/spirit_holding.dm index dc11d5fd7292..daae8f8b43f5 100644 --- a/code/datums/components/spirit_holding.dm +++ b/code/datums/components/spirit_holding.dm @@ -7,7 +7,7 @@ ///bool on if this component is currently polling for observers to inhabit the item var/attempting_awakening = FALSE ///mob contained in the item. - var/mob/living/simple_animal/shade/bound_spirit + var/mob/living/basic/shade/bound_spirit /datum/component/spirit_holding/Initialize() if(!ismovable(parent)) //you may apply this to mobs, i take no responsibility for how that works out diff --git a/code/datums/components/style/style.dm b/code/datums/components/style/style.dm index a0d6958582ce..c527653d8a6c 100644 --- a/code/datums/components/style/style.dm +++ b/code/datums/components/style/style.dm @@ -452,7 +452,7 @@ // Negative effects -/datum/component/style/proc/on_take_damage() +/datum/component/style/proc/on_take_damage(...) SIGNAL_HANDLER point_multiplier = round(max(point_multiplier - 0.3, 1), 0.1) diff --git a/code/datums/components/surgery_initiator.dm b/code/datums/components/surgery_initiator.dm index af725c1b6982..2f7fb3c3f7c9 100644 --- a/code/datums/components/surgery_initiator.dm +++ b/code/datums/components/surgery_initiator.dm @@ -61,7 +61,7 @@ var/list/available_surgeries = get_available_surgeries(user, target) if(!length(available_surgeries)) - if (target.body_position == LYING_DOWN) + if (target.body_position == LYING_DOWN || !(target.mobility_flags & MOBILITY_LIEDOWN)) target.balloon_alert(user, "no surgeries available!") else target.balloon_alert(user, "make them lie down!") @@ -99,7 +99,7 @@ continue else if(carbon_target && (surgery.surgery_flags & SURGERY_REQUIRE_LIMB)) //mob with no limb in surgery zone when we need a limb continue - if((surgery.surgery_flags & SURGERY_REQUIRE_RESTING) && target.body_position != LYING_DOWN) + if(IS_IN_INVALID_SURGICAL_POSITION(target, surgery)) continue if(!surgery.can_start(user, target)) continue @@ -307,7 +307,7 @@ target.balloon_alert(user, "not the right type of limb!") return - if ((surgery.surgery_flags & SURGERY_REQUIRE_RESTING) && target.body_position != LYING_DOWN) + if (IS_IN_INVALID_SURGICAL_POSITION(target, surgery)) target.balloon_alert(user, "patient is not lying down!") return diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm index daa66ea9fb5b..bfdd2ce481fb 100644 --- a/code/datums/components/tackle.dm +++ b/code/datums/components/tackle.dm @@ -365,7 +365,7 @@ /datum/component/tackler/proc/splat(mob/living/carbon/user, atom/hit) if(istype(hit, /obj/machinery/vending)) // before we do anything else- var/obj/machinery/vending/darth_vendor = hit - darth_vendor.tilt(user, TRUE) + darth_vendor.tilt(user, 100) return else if(istype(hit, /obj/structure/window)) var/obj/structure/window/W = hit diff --git a/code/datums/components/torn_wall.dm b/code/datums/components/torn_wall.dm new file mode 100644 index 000000000000..cadd7895349f --- /dev/null +++ b/code/datums/components/torn_wall.dm @@ -0,0 +1,105 @@ + +#define TORN_WALL_RUINED 2 +#define TORN_WALL_DAMAGED 1 +#define TORN_WALL_INITIAL 0 + +/** + * Component applied to a wall to progressively destroy it. + * If component is applied to something which already has it, stage increases. + * Wall is destroyed on third application. + * Can be fixed using a welder + */ +/datum/component/torn_wall + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + var/current_stage = TORN_WALL_INITIAL + +/datum/component/torn_wall/Initialize() + . = ..() + if (!isclosedturf(parent) || isindestructiblewall(parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/torn_wall/RegisterWithParent() + RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examined)) + RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_WELDER), PROC_REF(on_welded)) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays)) + RegisterSignal(parent, COMSIG_TURF_CHANGE, PROC_REF(on_turf_changed)) + apply_visuals() + +/datum/component/torn_wall/UnregisterFromParent() + var/atom/atom_parent = parent + UnregisterSignal(parent, list( + COMSIG_PARENT_EXAMINE, + COMSIG_ATOM_TOOL_ACT(TOOL_WELDER), + COMSIG_ATOM_UPDATE_OVERLAYS, + COMSIG_TURF_CHANGE, + )) + atom_parent.update_appearance(UPDATE_ICON) + +/datum/component/torn_wall/InheritComponent(datum/component/C, i_am_original) + increase_stage() + +/// Play a fun animation and make our wall look damaged +/datum/component/torn_wall/proc/apply_visuals() + var/atom/atom_parent = parent + playsound(atom_parent, 'sound/effects/bang.ogg', 50, vary = TRUE) + atom_parent.update_appearance(UPDATE_ICON) + atom_parent.Shake(shake_interval = 0.1 SECONDS, duration = 0.5 SECONDS) + +/// Make the effect more dramatic +/datum/component/torn_wall/proc/increase_stage() + current_stage++ + if (current_stage != TORN_WALL_RUINED) + apply_visuals() + return + var/turf/closed/wall/attached_wall = parent + playsound(attached_wall, 'sound/effects/meteorimpact.ogg', 100, vary = TRUE) + + if(ismineralturf(attached_wall)) + var/turf/closed/mineral/mineral_turf = attached_wall + mineral_turf.gets_drilled() + return + + attached_wall.dismantle_wall(devastated = TRUE) + +/// Fix it up on weld +/datum/component/torn_wall/proc/on_welded(atom/source, mob/user, obj/item/tool) + SIGNAL_HANDLER + INVOKE_ASYNC(src, PROC_REF(try_repair), source, user, tool) + return COMPONENT_BLOCK_TOOL_ATTACK + +/// Fix us up +/datum/component/torn_wall/proc/try_repair(atom/source, mob/user, obj/item/tool) + source.balloon_alert(user, "repairing...") + if(!tool.use_tool(source, user, 5 SECONDS, amount = 2, volume = 50)) + source.balloon_alert(user, "interrupted!") + return + current_stage-- + if (current_stage < TORN_WALL_INITIAL) + qdel(src) + return + source.update_appearance(UPDATE_ICON) + source.tool_act(user, tool, TOOL_WELDER, is_right_clicking = FALSE) // Keep going + +/// Give them a hint +/datum/component/torn_wall/proc/on_examined(atom/source, mob/user, list/examine_list) + SIGNAL_HANDLER + var/intensity = (current_stage == TORN_WALL_INITIAL) ? "slightly" : "badly" + examine_list += span_notice("It looks [intensity] damaged.") + examine_list += span_info("You may be able to repair it using a welding tool.") + +/// Show a little crack on here +/datum/component/torn_wall/proc/on_update_overlays(turf/source, list/overlays) + SIGNAL_HANDLER + var/mutable_appearance/crack = mutable_appearance('icons/turf/overlays.dmi', "explodable", source.layer + 0.1) + if (current_stage == TORN_WALL_INITIAL) + crack.alpha *= 0.5 + overlays += crack + +/// If the wall becomes any other turf, delete us. Transforming into a different works fine as a fix. +/datum/component/torn_wall/proc/on_turf_changed() + SIGNAL_HANDLER + qdel(src) + +#undef TORN_WALL_RUINED +#undef TORN_WALL_DAMAGED +#undef TORN_WALL_INITIAL diff --git a/code/datums/components/trader/trader.dm b/code/datums/components/trader/trader.dm new file mode 100644 index 000000000000..21fd143528ab --- /dev/null +++ b/code/datums/components/trader/trader.dm @@ -0,0 +1,457 @@ +#define TRADER_RADIAL_BUY "TRADER_RADIAL_BUY" +#define TRADER_RADIAL_SELL "TRADER_RADIAL_SELL" +#define TRADER_RADIAL_TALK "TRADER_RADIAL_TALK" +#define TRADER_RADIAL_LORE "TRADER_RADIAL_LORE" +#define TRADER_RADIAL_NO "TRADER_RADIAL_NO" +#define TRADER_RADIAL_YES "TRADER_RADIAL_YES" +#define TRADER_RADIAL_OUT_OF_STOCK "TRADER_RADIAL_OUT_OF_STOCK" +#define TRADER_RADIAL_DISCUSS_BUY "TRADER_RADIAL_DISCUSS_BUY" +#define TRADER_RADIAL_DISCUSS_SELL "TRADER_RADIAL_DISCUSS_SELL" + +#define TRADER_OPTION_BUY "Buy" +#define TRADER_OPTION_SELL "Sell" +#define TRADER_OPTION_TALK "Talk" +#define TRADER_OPTION_LORE "Lore" +#define TRADER_OPTION_NO "No" +#define TRADER_OPTION_YES "Yes" +#define TRADER_OPTION_BUYING "Buying?" +#define TRADER_OPTION_SELLING "Selling?" + +//The defines below show the index the info is located in the product_info entry list + +#define TRADER_PRODUCT_INFO_PRICE 1 +#define TRADER_PRODUCT_INFO_QUANTITY 2 +//Only valid for wanted_items +#define TRADER_PRODUCT_INFO_PRICE_MOD_DESCRIPTION 3 + +/** + * # Trader NPC Component + * Manages the barks and the stocks of the traders + * Also manages the interactive radial menu + */ +/datum/component/trader + + /** + * Format; list(TYPEPATH = list(PRICE, QUANTITY)) + * Associated list of items the NPC sells with how much they cost and the quantity available before a restock + * This list is filled by Initialize(), if you want to change the starting products, modify initial_products() + * * + */ + var/list/obj/item/products = list() + /** + * A list of wanted items that the trader would wish to buy, each typepath has a assigned value, quantity and additional flavor text + * + * CHILDREN OF TYPEPATHS INCLUDED IN WANTED_ITEMS WILL BE TREATED AS THE PARENT IF NO ENTRY EXISTS FOR THE CHILDREN + * + * As an additional note; if you include multiple children of a typepath; the typepath with the most children should be placed after all other typepaths + * Bad; list(/obj/item/milk = list(100, 1, ""), /obj/item/milk/small = list(50, 2, "")) + * Good; list(/obj/item/milk/small = list(50, 2, ""), /obj/item/milk = list(100, 1, "")) + * This is mainly because sell_item() uses a istype(item_being_sold, item_in_entry) to determine what parent should the child be automatically considered as + * If /obj/item/milk/small/spooky was being sold; /obj/item/milk/small would be the first to check against rather than /obj/item/milk + * + * Format; list(TYPEPATH = list(PRICE, QUANTITY, ADDITIONAL_DESCRIPTION)) + * Associated list of items able to be sold to the NPC with the money given for them. + * The price given should be the "base" price; any price manipulation based on variables should be done with apply_sell_price_mods() + * ADDITIONAL_DESCRIPTION is any additional text added to explain how the variables of the item effect the price; if it's stack based, it's final price depends how much is in the stack + * EX; /obj/item/stack/sheet/mineral/diamond = list(500, INFINITY, ", per 100 cm3 sheet of diamond") + * This list is filled by Initialize(), if you want to change the starting wanted items, modify initial_wanteds() + */ + var/list/wanted_items = list() + + ///Contains images of all radial icons + var/static/list/radial_icons_cache = list() + + ///Contains information of a specific trader + var/datum/trader_data/trader_data + +/* +Can accept both a type path, and an instance of a datum. Type path has priority. +*/ +/datum/component/trader/Initialize(trader_data_path = null, trader_data = null) + . = ..() + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + if(ispath(trader_data_path, /datum/trader_data)) + trader_data = new trader_data_path + if(isnull(trader_data)) + CRASH("Initialised trader component with no trader data.") + + src.trader_data = trader_data + + radial_icons_cache = list( + TRADER_RADIAL_BUY = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_buy"), + TRADER_RADIAL_SELL = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_sell"), + TRADER_RADIAL_TALK = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_talk"), + TRADER_RADIAL_LORE = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_lore"), + TRADER_RADIAL_DISCUSS_BUY = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_buying"), + TRADER_RADIAL_DISCUSS_SELL = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_selling"), + TRADER_RADIAL_YES = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_yes"), + TRADER_RADIAL_NO = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_no"), + TRADER_RADIAL_OUT_OF_STOCK = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_center"), + ) + + restock_products() + renew_item_demands() + +/datum/component/trader/RegisterWithParent() + RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand)) + +/datum/component/trader/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_ATOM_ATTACK_HAND) + +///If our trader is alive, and the customer left clicks them with an empty hand without combat mode +/datum/component/trader/proc/on_attack_hand(atom/source, mob/living/carbon/customer) + SIGNAL_HANDLER + if(!can_trade(customer) || (customer.istate & ISTATE_HARM)) + return + var/list/npc_options = list() + if(length(products)) + npc_options[TRADER_OPTION_BUY] = radial_icons_cache[TRADER_RADIAL_BUY] + if(length(wanted_items)) + npc_options[TRADER_OPTION_SELL] = radial_icons_cache[TRADER_RADIAL_SELL] + if(length(trader_data.say_phrases)) + npc_options[TRADER_OPTION_TALK] = radial_icons_cache[TRADER_RADIAL_TALK] + if(!length(npc_options)) + return + + var/mob/living/trader = parent + trader.face_atom(customer) + + INVOKE_ASYNC(src, PROC_REF(open_npc_options), customer, npc_options) + + return COMPONENT_CANCEL_ATTACK_CHAIN + +/** + * Generates a radial of the initial radials of the NPC + * Called via asynch, due to the sleep caused by show_radial_menu + * Arguments: + * * customer - (Mob REF) The mob trying to buy something + */ +/datum/component/trader/proc/open_npc_options(mob/living/carbon/customer, list/npc_options) + if(!can_trade(customer)) + return + var/npc_result = show_radial_menu(customer, parent, npc_options, custom_check = CALLBACK(src, PROC_REF(check_menu), customer), require_near = TRUE, tooltips = TRUE) + switch(npc_result) + if(TRADER_OPTION_BUY) + buy_item(customer) + if(TRADER_OPTION_SELL) + try_sell(customer) + if(TRADER_OPTION_TALK) + discuss(customer) + +/** + * Checks if the customer is ok to use the radial + * + * Checks if the customer is not a mob or is incapacitated or not adjacent to the source of the radial, in those cases returns FALSE, otherwise returns TRUE + * Arguments: + * * customer - (Mob REF) The mob checking the menu + */ +/datum/component/trader/proc/check_menu(mob/customer) + if(!istype(customer)) + return FALSE + if(IS_DEAD_OR_INCAP(customer) || !customer.Adjacent(parent)) + return FALSE + return TRUE + +/** + * Generates a radial of the items the NPC sells and lets the user try to buy one + * Arguments: + * * customer - (Mob REF) The mob trying to buy something + */ +/datum/component/trader/proc/buy_item(mob/customer) + if(!can_trade(customer)) + return + + if(!LAZYLEN(products)) + return + + var/list/display_names = list() + var/list/items = list() + var/list/product_info + + for(var/obj/item/thing as anything in products) + display_names["[initial(thing.name)]"] = thing + + if(!radial_icons_cache[thing]) + radial_icons_cache[thing] = image(icon = initial(thing.icon), icon_state = initial(thing.icon_state_preview) ? initial(thing.icon_state_preview) : initial(thing.icon_state)) + + var/image/item_image = radial_icons_cache[thing] + product_info = products[thing] + + if(product_info[TRADER_PRODUCT_INFO_QUANTITY] <= 0) //out of stock + item_image.overlays += radial_icons_cache[TRADER_RADIAL_OUT_OF_STOCK] + + items += list("[initial(thing.name)]" = item_image) + + var/pick = show_radial_menu(customer, parent, items, custom_check = CALLBACK(src, PROC_REF(check_menu), customer), require_near = TRUE, tooltips = TRUE) + if(!pick || !can_trade(customer)) + return + + var/obj/item/item_to_buy = display_names[pick] + var/mob/living/trader = parent + trader.face_atom(customer) + product_info = products[item_to_buy] + + if(!product_info[TRADER_PRODUCT_INFO_QUANTITY]) + trader.say("[initial(item_to_buy.name)] appears to be out of stock.") + return + + trader.say("It will cost you [product_info[TRADER_PRODUCT_INFO_PRICE]] [trader_data.currency_name] to buy \the [initial(item_to_buy.name)]. Are you sure you want to buy it?") + var/list/npc_options = list( + TRADER_OPTION_YES = radial_icons_cache[TRADER_RADIAL_YES], + TRADER_OPTION_NO = radial_icons_cache[TRADER_RADIAL_NO], + ) + + var/buyer_will_buy = show_radial_menu(customer, trader, npc_options, custom_check = CALLBACK(src, PROC_REF(check_menu), customer), require_near = TRUE, tooltips = TRUE) + if(buyer_will_buy != TRADER_OPTION_YES || !can_trade(customer)) + return + + trader.face_atom(customer) + + if(!spend_buyer_offhand_money(customer, product_info[TRADER_PRODUCT_INFO_PRICE])) + trader.say(trader_data.return_trader_phrase(NO_CASH_PHRASE)) + return + + item_to_buy = new item_to_buy(get_turf(customer)) + customer.put_in_hands(item_to_buy) + playsound(trader, trader_data.sell_sound, 50, TRUE) + log_econ("[item_to_buy] has been sold to [customer] (typepath used for product info; [item_to_buy.type]) by [trader] for [product_info[TRADER_PRODUCT_INFO_PRICE]] cash.") + product_info[TRADER_PRODUCT_INFO_QUANTITY] -= 1 + trader.say(trader_data.return_trader_phrase(BUY_PHRASE)) + +///Calculates the value of money in the hand of the buyer and spends it if it's sufficient +/datum/component/trader/proc/spend_buyer_offhand_money(mob/customer, the_cost) + var/value = 0 + var/obj/item/holochip/cash = customer.is_holding_item_of_type(/obj/item/holochip) + if(cash) + value += cash.credits + if((value >= the_cost) && cash) + return cash.spend(the_cost) + return FALSE //Purchase unsuccessful + +/** + * Tries to call sell_item on one of the customer's held items, if fail gives a chat message + * + * Gets both items in the customer's hands, and then tries to call sell_item on them, if both fail, he gives a chat message + * Arguments: + * * customer - (Mob REF) The mob trying to sell something + */ +/datum/component/trader/proc/try_sell(mob/customer) + if(!can_trade(customer)) + return + var/sold_item = FALSE + for(var/obj/item/an_item in customer.held_items) + if(sell_item(customer, an_item)) + sold_item = TRUE + break + if(!sold_item && can_trade(customer)) //only talk if you are not dead or in combat + var/mob/living/trader = parent + trader.say(trader_data.return_trader_phrase(ITEM_REJECTED_PHRASE)) + + +/** + * Checks if an item is in the list of wanted items and if it is after a Yes/No radial returns generate_cash with the value of the item for the NPC + * Arguments: + * * customer - (Mob REF) The mob trying to sell something + * * selling - (Item REF) The item being sold + */ +/datum/component/trader/proc/sell_item(mob/customer, obj/item/selling) + if(isnull(selling)) + return FALSE + var/list/product_info + //Keep track of the typepath; rather mundane but it's required for correctly modifying the wanted_items + //should a product be sellable because even if it doesn't have a entry because it's a child of a parent that is present on the list + var/typepath_for_product_info + + if(selling.type in wanted_items) + product_info = wanted_items[selling.type] + typepath_for_product_info = selling.type + else //Assume wanted_items is setup in the correct way; read wanted_items documentation for more info + for(var/typepath in wanted_items) + if(!istype(selling, typepath)) + continue + + product_info = wanted_items[typepath] + typepath_for_product_info = typepath + break + + if(!product_info) //Nothing interesting to sell + return FALSE + + var/mob/living/trader = parent + + if(product_info[TRADER_PRODUCT_INFO_QUANTITY] <= 0) + trader.say(trader_data.return_trader_phrase(TRADER_HAS_ENOUGH_ITEM_PHRASE)) + return FALSE + + var/cost = apply_sell_price_mods(selling, product_info[TRADER_PRODUCT_INFO_PRICE]) + if(cost <= 0) + trader.say(trader_data.return_trader_phrase(ITEM_IS_WORTHLESS_PHRASE)) + return FALSE + + trader.say(trader_data.return_trader_phrase(INTERESTED_PHRASE)) + trader.say("You will receive [cost] [trader_data.currency_name] for the [selling].") + var/list/npc_options = list( + TRADER_OPTION_YES = radial_icons_cache[TRADER_RADIAL_YES], + TRADER_OPTION_NO = radial_icons_cache[TRADER_RADIAL_NO], + ) + + trader.face_atom(customer) + + var/npc_result = show_radial_menu(customer, trader, npc_options, custom_check = CALLBACK(src, PROC_REF(check_menu), customer), require_near = TRUE, tooltips = TRUE) + if(!can_trade(customer)) + return + if(npc_result != TRADER_OPTION_YES) + trader.say(trader_data.return_trader_phrase(ITEM_SELLING_CANCELED_PHRASE)) + return TRUE + + trader.say(trader_data.return_trader_phrase(ITEM_SELLING_ACCEPTED_PHRASE)) + playsound(trader, trader_data.sell_sound, 50, TRUE) + log_econ("[selling] has been sold to [trader] (typepath used for product info; [typepath_for_product_info]) by [customer] for [cost] cash.") + exchange_sold_items(selling, cost, typepath_for_product_info) + generate_cash(cost, customer) + return TRUE + +/** + * Modifies the 'base' price of a item based on certain variables + * + * Arguments: + * * Reference to the item; this is the item being sold + * * Original cost; the original cost of the item, to be manipulated depending on the variables of the item, one example is using item.amount if it's a stack + */ +/datum/component/trader/proc/apply_sell_price_mods(obj/item/selling, original_cost) + if(isstack(selling)) + var/obj/item/stack/stackoverflow = selling + original_cost *= stackoverflow.amount + return original_cost + +/** + * Handles modifying/deleting the items to ensure that a proper amount is converted into cash; put into it's own proc to make the children of this not override a 30+ line sell_item() + * + * Arguments: + * * selling - (Item REF) this is the item being sold + * * value_exchanged_for - (Number) the "value", useful for a scenario where you want to remove enough items equal to the value + * * original_typepath - (Typepath) For scenarios where a children of a parent is being sold but we want to modify the parent's product information + */ +/datum/component/trader/proc/exchange_sold_items(obj/item/selling, value_exchanged_for, original_typepath) + var/list/product_info = wanted_items[original_typepath] + if(isstack(selling)) + var/obj/item/stack/the_stack = selling + var/actually_sold = min(the_stack.amount, product_info[TRADER_PRODUCT_INFO_QUANTITY]) + the_stack.use(actually_sold) + product_info[TRADER_PRODUCT_INFO_QUANTITY] -= (actually_sold) + else + qdel(selling) + product_info[TRADER_PRODUCT_INFO_QUANTITY] -= 1 + +/** + * Creates an item equal to the value set by the proc and puts it in the user's hands if possible + * Arguments: + * * value - A number; The amount of cash that will be on the holochip + * * customer - Reference to a mob; The mob we put the holochip in hands of + */ +/datum/component/trader/proc/generate_cash(value, mob/customer) + var/obj/item/holochip/chip = new /obj/item/holochip(get_turf(customer), value) + customer.put_in_hands(chip) + +///Talk about what items are being sold/wanted by the trader and in what quantity or lore +/datum/component/trader/proc/discuss(mob/customer) + var/list/npc_options = list( + TRADER_OPTION_LORE = radial_icons_cache[TRADER_RADIAL_LORE], + TRADER_OPTION_SELLING = radial_icons_cache[TRADER_RADIAL_DISCUSS_SELL], + TRADER_OPTION_BUYING = radial_icons_cache[TRADER_RADIAL_DISCUSS_BUY], + ) + var/pick = show_radial_menu(customer, parent, npc_options, custom_check = CALLBACK(src, PROC_REF(check_menu), customer), require_near = TRUE, tooltips = TRUE) + if(!can_trade(customer)) + return + switch(pick) + if(TRADER_OPTION_LORE) + var/mob/living/trader = parent + trader.say(trader_data.return_trader_phrase(TRADER_LORE_PHRASE)) + if(TRADER_OPTION_BUYING) + trader_buys_what(customer) + if(TRADER_OPTION_SELLING) + trader_sells_what(customer) + +///Displays to the customer what the trader is willing to buy and how much until a restock happens +/datum/component/trader/proc/trader_buys_what(mob/customer) + if(!can_trade(customer)) + return + if(!length(wanted_items)) + var/mob/living/trader = parent + trader.say(trader_data.return_trader_phrase(TRADER_NOT_BUYING_ANYTHING)) + return + + var/list/buy_info = list(span_green("I'm willing to buy the following:")) + + var/list/product_info + for(var/obj/item/thing as anything in wanted_items) + product_info = wanted_items[thing] + var/tern_op_result = (product_info[TRADER_PRODUCT_INFO_QUANTITY] == INFINITY ? "as many as I can." : "[product_info[TRADER_PRODUCT_INFO_QUANTITY]]") //Coder friendly string concat + if(product_info[TRADER_PRODUCT_INFO_QUANTITY] <= 0) //Zero demand + buy_info += span_notice("• [span_red("(DOESN'T WANT MORE)")] [initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [trader_data.currency_name][product_info[TRADER_PRODUCT_INFO_PRICE_MOD_DESCRIPTION]]; willing to buy [span_red("[tern_op_result]")] more.") + else + buy_info += span_notice("• [initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [trader_data.currency_name][product_info[TRADER_PRODUCT_INFO_PRICE_MOD_DESCRIPTION]]; willing to buy [span_green("[tern_op_result]")]") + + to_chat(customer, examine_block(buy_info.Join("\n"))) + +///Displays to the customer what the trader is selling and how much is in stock +/datum/component/trader/proc/trader_sells_what(mob/customer) + if(!can_trade(customer)) + return + var/mob/living/trader = parent + if(!length(products)) + trader.say(trader_data.return_trader_phrase(TRADER_NOT_SELLING_ANYTHING)) + return + var/list/sell_info = list(span_green("I'm currently selling the following:")) + var/list/product_info + for(var/obj/item/thing as anything in products) + product_info = products[thing] + var/tern_op_result = (product_info[TRADER_PRODUCT_INFO_QUANTITY] == INFINITY ? "an infinite amount" : "[product_info[TRADER_PRODUCT_INFO_QUANTITY]]") //Coder friendly string concat + if(product_info[TRADER_PRODUCT_INFO_QUANTITY] <= 0) //Out of stock + sell_info += span_notice("• [span_red("(OUT OF STOCK)")] [initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [trader_data.currency_name]; [span_red("[tern_op_result]")] left in stock") + else + sell_info += span_notice("• [initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [trader_data.currency_name]; [span_green("[tern_op_result]")] left in stock") + to_chat(customer, examine_block(sell_info.Join("\n"))) + +///Sets quantity of all products to initial(quanity); this proc is currently called during initialize +/datum/component/trader/proc/restock_products() + products = trader_data.initial_products.Copy() + +///Sets quantity of all wanted_items to initial(quanity); this proc is currently called during initialize +/datum/component/trader/proc/renew_item_demands() + wanted_items = trader_data.initial_wanteds.Copy() + +///Returns if the trader is conscious and its combat mode is disabled. +/datum/component/trader/proc/can_trade(mob/customer) + var/mob/living/trader = parent + if(trader.istate & ISTATE_HARM) + trader.balloon_alert(customer, "in combat!") + return FALSE + if(IS_DEAD_OR_INCAP(trader)) + trader.balloon_alert(customer, "indisposed!") + return FALSE + return TRUE + +#undef TRADER_RADIAL_BUY +#undef TRADER_RADIAL_SELL +#undef TRADER_RADIAL_TALK +#undef TRADER_RADIAL_LORE +#undef TRADER_RADIAL_DISCUSS_BUY +#undef TRADER_RADIAL_DISCUSS_SELL +#undef TRADER_RADIAL_NO +#undef TRADER_RADIAL_YES +#undef TRADER_RADIAL_OUT_OF_STOCK +#undef TRADER_PRODUCT_INFO_PRICE +#undef TRADER_PRODUCT_INFO_QUANTITY +#undef TRADER_PRODUCT_INFO_PRICE_MOD_DESCRIPTION + +#undef TRADER_OPTION_BUY +#undef TRADER_OPTION_SELL +#undef TRADER_OPTION_TALK +#undef TRADER_OPTION_LORE +#undef TRADER_OPTION_NO +#undef TRADER_OPTION_YES +#undef TRADER_OPTION_BUYING +#undef TRADER_OPTION_SELLING diff --git a/code/datums/components/transforming.dm b/code/datums/components/transforming.dm index d704ca2098df..3c3fec196c23 100644 --- a/code/datums/components/transforming.dm +++ b/code/datums/components/transforming.dm @@ -89,6 +89,23 @@ RegisterSignal(parent, COMSIG_ITEM_SHARPEN_ACT, PROC_REF(on_sharpen)) RegisterSignal(parent, COMSIG_DETECTIVE_SCANNED, PROC_REF(on_scan)) + RegisterSignal(parent, COMSIG_ITEM_APPLY_FANTASY_BONUSES, PROC_REF(apply_fantasy_bonuses)) + RegisterSignal(parent, COMSIG_ITEM_REMOVE_FANTASY_BONUSES, PROC_REF(remove_fantasy_bonuses)) + +/datum/component/transforming/proc/apply_fantasy_bonuses(obj/item/source, bonus) + SIGNAL_HANDLER + active = FALSE + set_inactive(source) + force_on = source.modify_fantasy_variable("force_on", force_on, bonus) + throwforce_on = source.modify_fantasy_variable("throwforce_on", throwforce_on, bonus) + +/datum/component/transforming/proc/remove_fantasy_bonuses(obj/item/source, bonus) + SIGNAL_HANDLER + active = FALSE + set_inactive(source) + force_on = source.reset_fantasy_variable("force_on", force_on) + throwforce_on = source.reset_fantasy_variable("throwforce_on", throwforce_on) + /datum/component/transforming/UnregisterFromParent() UnregisterSignal(parent, list(COMSIG_ITEM_ATTACK_SELF, COMSIG_ITEM_SHARPEN_ACT, COMSIG_DETECTIVE_SCANNED)) diff --git a/code/datums/components/tree_climber.dm b/code/datums/components/tree_climber.dm index 2a0534a38b85..eaa2136687eb 100644 --- a/code/datums/components/tree_climber.dm +++ b/code/datums/components/tree_climber.dm @@ -12,6 +12,8 @@ return COMPONENT_INCOMPATIBLE src.climbing_distance = climbing_distance + ADD_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type) + /datum/component/tree_climber/RegisterWithParent() RegisterSignals(parent, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_LIVING_CLIMB_TREE), PROC_REF(climb_tree)) RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine)) diff --git a/code/datums/components/twohanded.dm b/code/datums/components/twohanded.dm index 756f30bd644d..8ac0f0896da6 100644 --- a/code/datums/components/twohanded.dm +++ b/code/datums/components/twohanded.dm @@ -92,6 +92,25 @@ RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON, PROC_REF(on_update_icon)) RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) RegisterSignal(parent, COMSIG_ITEM_SHARPEN_ACT, PROC_REF(on_sharpen)) + RegisterSignal(parent, COMSIG_ITEM_APPLY_FANTASY_BONUSES, PROC_REF(apply_fantasy_bonuses)) + RegisterSignal(parent, COMSIG_ITEM_REMOVE_FANTASY_BONUSES, PROC_REF(remove_fantasy_bonuses)) + +/datum/component/two_handed/proc/apply_fantasy_bonuses(obj/item/source, bonus) + SIGNAL_HANDLER + force_wielded = source.modify_fantasy_variable("force_wielded", force_wielded, bonus) + force_unwielded = source.modify_fantasy_variable("force_unwielded", force_unwielded, bonus) + if(wielded && ismob(source.loc)) + unwield(source.loc) + if(force_multiplier) + force_multiplier = source.modify_fantasy_variable("force_multiplier", force_multiplier, bonus/10, minimum = 1) + +/datum/component/two_handed/proc/remove_fantasy_bonuses(obj/item/source, bonus) + SIGNAL_HANDLER + force_wielded = source.reset_fantasy_variable("force_wielded", force_wielded) + force_unwielded = source.reset_fantasy_variable("force_unwielded", force_unwielded) + if(wielded && ismob(source.loc)) + unwield(source.loc) + force_multiplier = source.reset_fantasy_variable("force_multiplier", force_multiplier) // Remove all siginals registered to the parent item /datum/component/two_handed/UnregisterFromParent() diff --git a/code/datums/components/udder.dm b/code/datums/components/udder.dm index 149cb6ee6f00..44367327d3fb 100644 --- a/code/datums/components/udder.dm +++ b/code/datums/components/udder.dm @@ -13,7 +13,8 @@ /datum/component/udder/Initialize(udder_type = /obj/item/udder, datum/callback/on_milk_callback, datum/callback/on_generate_callback, reagent_produced_typepath = /datum/reagent/consumable/milk) if(!isliving(parent)) //technically is possible to drop this on carbons... but you wouldn't do that to me, would you? return COMPONENT_INCOMPATIBLE - udder = new udder_type(null, parent, on_generate_callback, reagent_produced_typepath) + udder = new udder_type(null) + udder.add_features(parent, on_generate_callback, reagent_produced_typepath) src.on_milk_callback = on_milk_callback /datum/component/udder/RegisterWithParent() @@ -69,14 +70,68 @@ var/mob/living/udder_mob ///optional proc to callback to when the udder generates milk var/datum/callback/on_generate_callback - -/obj/item/udder/Initialize(mapload, udder_mob, on_generate_callback, reagent_produced_typepath = /datum/reagent/consumable/milk) - src.udder_mob = udder_mob - src.on_generate_callback = on_generate_callback + ///do we require some food to generate milk? + var/require_consume_type + ///how long does each food consumption allow us to make milk + var/require_consume_timer = 2 MINUTES + ///hunger key we set to look for food + var/hunger_key = BB_CHECK_HUNGRY + +/obj/item/udder/proc/add_features(parent, callback, reagent = /datum/reagent/consumable/milk) + udder_mob = parent + on_generate_callback = callback create_reagents(size, REAGENT_HOLDER_ALIVE) - src.reagent_produced_typepath = reagent_produced_typepath + reagent_produced_typepath = reagent initial_conditions() + if(isnull(require_consume_type)) + return + RegisterSignal(udder_mob, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_mob_consume)) + RegisterSignal(udder_mob, COMSIG_PARENT_ATTACKBY, PROC_REF(on_mob_feed)) + udder_mob.ai_controller?.set_blackboard_key(BB_CHECK_HUNGRY, TRUE) + +/obj/item/udder/proc/on_mob_consume(datum/source, atom/feed) + SIGNAL_HANDLER + + if(!istype(feed, require_consume_type)) + return + INVOKE_ASYNC(src, PROC_REF(handle_consumption), feed) + return COMPONENT_HOSTILE_NO_ATTACK + +/obj/item/udder/proc/on_mob_feed(datum/source, atom/used_item, mob/living/user) + SIGNAL_HANDLER + + if(!istype(used_item, require_consume_type)) + return + INVOKE_ASYNC(src, PROC_REF(handle_consumption), used_item, user) + return COMPONENT_NO_AFTERATTACK + +/obj/item/udder/proc/handle_consumption(atom/movable/food, mob/user) + if(locate(food.type) in src) + if(user) + user.balloon_alert(user, "already full!") + return + playsound(udder_mob.loc,'sound/items/eatfood.ogg', 50, TRUE) + udder_mob.visible_message(span_notice("[udder_mob] gobbles up [food]!"), span_notice("You gobble up [food]!")) + var/atom/movable/final_food = food + if(isstack(food)) //if stack, only consume 1 + var/obj/item/stack/food_stack = food + final_food = food_stack.split_stack(udder_mob, 1) + final_food.forceMove(src) + +/obj/item/udder/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + if(!istype(arrived, require_consume_type)) + return ..() + + udder_mob.ai_controller?.set_blackboard_key(hunger_key, FALSE) + QDEL_IN(arrived, require_consume_timer) + return ..() + +/obj/item/udder/Exited(atom/movable/gone, direction) . = ..() + if(!istype(gone, require_consume_type)) + return + udder_mob.ai_controller?.set_blackboard_key(hunger_key, TRUE) + /obj/item/udder/Destroy() . = ..() @@ -99,10 +154,13 @@ * Proc called every 2 seconds from SSMobs to add whatever reagent the udder is generating. */ /obj/item/udder/proc/generate() - if(prob(5)) - reagents.add_reagent(reagent_produced_typepath, rand(5, 10)) - if(on_generate_callback) - on_generate_callback.Invoke(reagents.total_volume, reagents.maximum_volume) + if(!isnull(require_consume_type) && !(locate(require_consume_type) in src)) + return + if(prob(95)) + return + reagents.add_reagent(reagent_produced_typepath, rand(5, 10), added_purity = 1) + if(on_generate_callback) + on_generate_callback.Invoke(reagents.total_volume, reagents.maximum_volume) /** * Proc called from attacking the component parent with the correct item, moves reagents into the glass basically. @@ -123,48 +181,20 @@ /** * # gutlunch udder subtype - * - * Used by gutlunches, and generates healing reagents instead of milk on eating gibs instead of a process. Starts empty! - * Female gutlunches (ahem, guthens if you will) make babies when their udder is full under processing, instead of milk generation */ + /obj/item/udder/gutlunch name = "nutrient sac" - -/obj/item/udder/gutlunch/initial_conditions() - if(!udder_mob) - return - if(udder_mob.gender == FEMALE) - START_PROCESSING(SSobj, src) - RegisterSignal(udder_mob, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_mob_attacking)) - -/obj/item/udder/gutlunch/process(seconds_per_tick) - var/mob/living/simple_animal/hostile/asteroid/gutlunch/gutlunch = udder_mob - if(reagents.total_volume != reagents.maximum_volume) - return - if(gutlunch.make_babies()) - reagents.clear_reagents() - //usually this would be a callback but this is a specifically gutlunch feature so fuck it, gutlunch specific proccall - gutlunch.regenerate_icons(reagents.total_volume, reagents.maximum_volume) - -/** - * signal called on parent attacking an atom -*/ -/obj/item/udder/gutlunch/proc/on_mob_attacking(mob/living/simple_animal/hostile/gutlunch, atom/target) - SIGNAL_HANDLER - - if(is_type_in_typecache(target, gutlunch.wanted_objects)) //we eats - generate() - gutlunch.visible_message(span_notice("[udder_mob] slurps up [target].")) - qdel(target) - return COMPONENT_HOSTILE_NO_ATTACK //there is no longer a target to attack + require_consume_type = /obj/item/stack/ore + reagent_produced_typepath = /datum/reagent/medicine/mine_salve /obj/item/udder/gutlunch/generate() - var/made_something = FALSE - if(prob(60)) - reagents.add_reagent(/datum/reagent/consumable/cream, rand(2, 5)) - made_something = TRUE - if(prob(45)) + . = ..() + if(!.) + return + if(locate(/obj/item/stack/ore/gold) in src) + reagents.add_reagent(/datum/reagent/consumable/cream, rand(2, 5), added_purity = 1) + if(locate(/obj/item/stack/ore/bluespace_crystal) in src) reagents.add_reagent(/datum/reagent/medicine/salglu_solution, rand(2,5)) - made_something = TRUE - if(made_something && on_generate_callback) + if(on_generate_callback) on_generate_callback.Invoke(reagents.total_volume, reagents.maximum_volume) diff --git a/code/datums/components/unusual_effect.dm b/code/datums/components/unusual_effect.dm new file mode 100644 index 000000000000..eb67b9537997 --- /dev/null +++ b/code/datums/components/unusual_effect.dm @@ -0,0 +1,60 @@ + +/particles/unusual_effect + icon = 'icons/effects/particles/pollen.dmi' + icon_state = "pollen" + width = 100 + height = 100 + count = 1000 + spawning = 4 + lifespan = 0.7 SECONDS + fade = 1 SECONDS + grow = -0.01 + velocity = list(0, 0) + position = generator(GEN_CIRCLE, 0, 16, NORMAL_RAND) + drift = generator(GEN_VECTOR, list(0, -0.2), list(0, 0.2)) + gravity = list(0, 0.95) + scale = generator(GEN_VECTOR, list(0.3, 0.3), list(1,1), NORMAL_RAND) + rotation = 30 + spin = generator(GEN_NUM, -20, 20) + +/// Creates a cool looking effect on the movable. +/// In the future, this could be expanded to have more interesting particles and effects. +/datum/component/unusual_effect + dupe_mode = COMPONENT_DUPE_HIGHLANDER + + var/obj/effect/abstract/particle_holder/special_effects + + var/color + + COOLDOWN_DECLARE(glow_cooldown) + +/datum/component/unusual_effect/Initialize(color, include_particles = FALSE) + var/atom/movable/parent_movable = parent + if (!istype(parent_movable)) + return COMPONENT_INCOMPATIBLE + + src.color = color + parent_movable.add_filter("unusual_effect", 2, list("type" = "outline", "color" = color, "size" = 2)) + if(include_particles) + special_effects = new(parent_movable, /particles/unusual_effect) + START_PROCESSING(SSobj, src) + +/datum/component/unusual_effect/Destroy(force, silent) + var/atom/movable/parent_movable = parent + if (istype(parent_movable)) + parent_movable.remove_filter("unusual_effect") + STOP_PROCESSING(SSobj, src) + return ..() + +/datum/component/unusual_effect/process(seconds_per_tick) + var/atom/movable/parent_movable = parent + var/filter = parent_movable.get_filter("unusual_effect") + if (!filter) + parent_movable.add_filter("unusual_effect", 2, list("type" = "outline", "color" = color, "size" = 2)) + return + if(!COOLDOWN_FINISHED(src, glow_cooldown)) + return + + animate(filter, alpha = 110, time = 1.5 SECONDS, loop = -1) + animate(alpha = 40, time = 2.5 SECONDS) + COOLDOWN_START(src, glow_cooldown, 4 SECONDS) diff --git a/code/datums/diseases/parrotpossession.dm b/code/datums/diseases/parrotpossession.dm index 23f68e1a42ff..442a046643b6 100644 --- a/code/datums/diseases/parrotpossession.dm +++ b/code/datums/diseases/parrotpossession.dm @@ -13,24 +13,41 @@ severity = DISEASE_SEVERITY_MEDIUM infectable_biotypes = MOB_ORGANIC|MOB_UNDEAD|MOB_ROBOTIC|MOB_MINERAL bypasses_immunity = TRUE //2spook - var/mob/living/simple_animal/parrot/poly/ghost/parrot + ///chance we speak + var/speak_chance = 5 + ///controller we speak from + var/datum/ai_controller/basic_controller/parrot_controller /datum/disease/parrot_possession/stage_act(seconds_per_tick, times_fired) . = ..() - if(!.) + + if(!. || isnull(parrot_controller)) return - if(QDELETED(parrot) || parrot.loc != affected_mob) - cure() - return FALSE + var/potential_phrase = parrot_controller.blackboard[BB_PARROT_REPEAT_STRING] - if(length(parrot.speech_buffer) && SPT_PROB(parrot.speak_chance, seconds_per_tick)) // I'm not going to dive into polycode trying to adjust that probability. Enjoy doubled ghost parrot speach - affected_mob.say(pick(parrot.speech_buffer), forced = "parrot possession") + if(SPT_PROB(speak_chance, seconds_per_tick) && !isnull(potential_phrase)) + affected_mob.say(potential_phrase, forced = "parrot possession") /datum/disease/parrot_possession/cure() - if(parrot && parrot.loc == affected_mob) - parrot.forceMove(affected_mob.drop_location()) - affected_mob.visible_message(span_danger("[parrot] is violently driven out of [affected_mob]!"), span_userdanger("[parrot] bursts out of your chest!")) - ..() + var/atom/movable/inside_parrot = locate(/mob/living/basic/parrot/poly/ghost) in affected_mob + if(inside_parrot) + UnregisterSignal(inside_parrot, list(COMSIG_PARENT_PREQDELETED, COMSIG_MOVABLE_MOVED)) + inside_parrot.forceMove(affected_mob.drop_location()) + affected_mob.visible_message( + span_danger("[inside_parrot] is violently driven out of [affected_mob]!"), + span_userdanger("[inside_parrot] bursts out of your chest!"), + ) + parrot_controller = null + return ..() + +/datum/disease/parrot_possession/proc/set_parrot(mob/living/parrot) + parrot_controller = parrot.ai_controller + RegisterSignals(parrot, list(COMSIG_PARENT_PREQDELETED, COMSIG_MOVABLE_MOVED), PROC_REF(on_parrot_exit)) + +/datum/disease/parrot_possession/proc/on_parrot_exit(datum/source) + SIGNAL_HANDLER + UnregisterSignal(source, list(COMSIG_PARENT_PREQDELETED, COMSIG_MOVABLE_MOVED)) + cure() diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm index 0405b72e854e..43269cb2ee73 100644 --- a/code/datums/diseases/transformation.dm +++ b/code/datums/diseases/transformation.dm @@ -58,10 +58,10 @@ to_chat(affected_mob, pick(stage5)) if(QDELETED(affected_mob)) return - if(affected_mob.notransform) + if(HAS_TRAIT_FROM(affected_mob, TRAIT_NO_TRANSFORM, REF(src))) return - affected_mob.notransform = 1 - for(var/obj/item/W in affected_mob.get_equipped_items(TRUE)) + ADD_TRAIT(affected_mob, TRAIT_NO_TRANSFORM, REF(src)) + for(var/obj/item/W in affected_mob.get_equipped_items(include_pockets = TRUE)) affected_mob.dropItemToGround(W) for(var/obj/item/I in affected_mob.held_items) affected_mob.dropItemToGround(I) diff --git a/code/datums/elements/ai_flee_while_injured.dm b/code/datums/elements/ai_flee_while_injured.dm index fc1a2e332816..eca709dbee5d 100644 --- a/code/datums/elements/ai_flee_while_injured.dm +++ b/code/datums/elements/ai_flee_while_injured.dm @@ -19,6 +19,7 @@ src.stop_fleeing_at = stop_fleeing_at src.start_fleeing_below = start_fleeing_below RegisterSignal(target, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_health_changed)) + on_health_changed(target) /datum/element/ai_flee_while_injured/Detach(datum/source) . = ..() @@ -28,18 +29,18 @@ /datum/element/ai_flee_while_injured/proc/on_health_changed(mob/living/source) SIGNAL_HANDLER - if (!source.ai_controller) + if (isnull(source.ai_controller)) return var/current_health_percentage = source.health / source.maxHealth - if (source.ai_controller.blackboard[BB_BASIC_MOB_FLEEING]) - if (current_health_percentage < stop_fleeing_at) + if (source.ai_controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) + if (current_health_percentage > start_fleeing_below) return - source.ai_controller.CancelActions() // Stop fleeing go back to whatever you were doing - source.ai_controller.set_blackboard_key(BB_BASIC_MOB_FLEEING, FALSE) + source.ai_controller.CancelActions() + source.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) return - if (current_health_percentage > start_fleeing_below) + if (current_health_percentage < stop_fleeing_at) return - source.ai_controller.CancelActions() - source.ai_controller.set_blackboard_key(BB_BASIC_MOB_FLEEING, TRUE) + source.ai_controller.CancelActions() // Stop fleeing go back to whatever you were doing + source.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) diff --git a/code/datums/elements/ai_swap_combat_mode.dm b/code/datums/elements/ai_swap_combat_mode.dm new file mode 100644 index 000000000000..cbd271f314b8 --- /dev/null +++ b/code/datums/elements/ai_swap_combat_mode.dm @@ -0,0 +1,64 @@ +/** + * Attached to a mob with an AI controller, updates combat mode when the affected mob acquires or loses targets + */ +/datum/element/ai_swap_combat_mode + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// The message we yell when we enter combat mode + var/list/battle_start_barks + /// A one liner said when we exit combat mode + var/list/battle_end_barks + /// The chance to yell the above lines + var/speech_chance + /// Target key + var/target_key + +/datum/element/ai_swap_combat_mode/Attach(datum/target, target_key, list/battle_start_barks = null, list/battle_end_barks = null) + . = ..() + if(!isliving(target)) + return ELEMENT_INCOMPATIBLE + var/mob/living/living_target = target + if(!living_target.ai_controller) + return ELEMENT_INCOMPATIBLE + + if(isnull(battle_start_barks)) + battle_start_barks = list("En Garde!",) + + if(isnull(battle_end_barks)) + battle_end_barks = list("Never should have come here",) + + src.battle_start_barks = battle_start_barks + src.battle_end_barks = battle_end_barks + src.target_key = target_key + RegisterSignal(target, COMSIG_AI_BLACKBOARD_KEY_SET(target_key), PROC_REF(on_target_gained)) + RegisterSignal(target, COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_key), PROC_REF(on_target_cleared)) + +/datum/element/ai_swap_combat_mode/Detach(datum/source) + . = ..() + UnregisterSignal(source, list( + COMSIG_AI_BLACKBOARD_KEY_SET(target_key), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_key), + )) + +/// When the mob gains a target, and it was not already in combat mode, enter it +/datum/element/ai_swap_combat_mode/proc/on_target_gained(mob/living/source) + SIGNAL_HANDLER + + if(swap_mode(source, TRUE)) + INVOKE_ASYNC(src, PROC_REF(speak_bark), source, battle_start_barks) + +/// When the mob loses its target, and it was not already out of combat mode, exit it +/datum/element/ai_swap_combat_mode/proc/on_target_cleared(mob/living/source) + SIGNAL_HANDLER + + if(swap_mode(source, FALSE)) + INVOKE_ASYNC(src, PROC_REF(speak_bark), source, battle_end_barks) + +///Says a quip, if the RNG allows it +/datum/element/ai_swap_combat_mode/proc/speak_bark(mob/living/source, line) + source.say(pick(line)) + +///If the combat mode would be changed into a different state, updates it and returns TRUE, otherwise returns FALSE +/datum/element/ai_swap_combat_mode/proc/swap_mode(mob/living/source, new_mode) + source.set_combat_mode(new_mode) + return TRUE diff --git a/code/datums/elements/amputating_limbs.dm b/code/datums/elements/amputating_limbs.dm index c2fe7c454a96..16a99e96f6c1 100644 --- a/code/datums/elements/amputating_limbs.dm +++ b/code/datums/elements/amputating_limbs.dm @@ -1,4 +1,4 @@ -/// This component will intercept bare-handed attacks by the owner on critically injured carbons and amputate random limbs instead +/// This component will intercept bare-handed attacks by the owner on sufficiently injured carbons and amputate random limbs instead /datum/element/amputating_limbs element_flags = ELEMENT_BESPOKE argument_hash_start_idx = 2 @@ -6,6 +6,10 @@ var/surgery_time /// What is the means by which we describe the act of amputation? var/surgery_verb + /// How awake must our target be? + var/minimum_stat + /// How likely are we to perform this action? + var/snip_chance /// The types of limb we can remove var/list/target_zones @@ -13,6 +17,8 @@ datum/target, surgery_time = 5 SECONDS, surgery_verb = "prying", + minimum_stat = SOFT_CRIT, + snip_chance = 100, list/target_zones = list(BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_ARM, BODY_ZONE_R_LEG), ) . = ..() @@ -23,6 +29,8 @@ src.surgery_time = surgery_time src.surgery_verb = surgery_verb + src.minimum_stat = minimum_stat + src.snip_chance = snip_chance src.target_zones = target_zones RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(try_amputate)) @@ -33,11 +41,11 @@ /// Called when you click on literally anything with your hands, see if it is an injured carbon and then try to cut it up /datum/element/amputating_limbs/proc/try_amputate(mob/living/surgeon, atom/victim) SIGNAL_HANDLER - if (!iscarbon(victim) || HAS_TRAIT(victim, TRAIT_NODISMEMBER)) + if (!iscarbon(victim) || HAS_TRAIT(victim, TRAIT_NODISMEMBER) || !prob(snip_chance)) return var/mob/living/carbon/limbed_victim = victim - if (limbed_victim.stat == CONSCIOUS) + if (limbed_victim.stat < minimum_stat) return if (DOING_INTERACTION_WITH_TARGET(surgeon, victim)) @@ -60,7 +68,7 @@ /// Chop one off /datum/element/amputating_limbs/proc/amputate(mob/living/surgeon, mob/living/carbon/victim, obj/item/bodypart/to_remove) - surgeon.visible_message(span_warning("[surgeon] begins [surgery_verb] [to_remove] off of [victim]!")) - if (!do_after(surgeon, delay = surgery_time, target = victim)) + surgeon.visible_message(span_warning("[surgeon] [surgery_verb] [to_remove] off of [victim]!")) + if (surgery_time > 0 && !do_after(surgeon, delay = surgery_time, target = victim)) return to_remove.dismember() diff --git a/code/datums/elements/basic_eating.dm b/code/datums/elements/basic_eating.dm index 86f4be63cac3..3201eeff846f 100644 --- a/code/datums/elements/basic_eating.dm +++ b/code/datums/elements/basic_eating.dm @@ -12,10 +12,12 @@ var/damage_amount /// Type of hurt to apply var/damage_type + /// Whether to flavor it as drinking rather than eating. + var/drinking /// Types the animal can eat. var/list/food_types -/datum/element/basic_eating/Attach(datum/target, heal_amt = 0, damage_amount = 0, damage_type = null, food_types = list()) +/datum/element/basic_eating/Attach(datum/target, heal_amt = 0, damage_amount = 0, damage_type = null, drinking = FALSE, food_types = list()) . = ..() if(!isliving(target)) @@ -24,6 +26,7 @@ src.heal_amt = heal_amt src.damage_amount = damage_amount src.damage_type = damage_type + src.drinking = drinking src.food_types = food_types //this lets players eat @@ -45,8 +48,12 @@ /datum/element/basic_eating/proc/try_eating(mob/living/eater, atom/target) if(!is_type_in_list(target, food_types)) - return - var/eat_verb = pick("bite","chew","nibble","gnaw","gobble","chomp") + return FALSE + var/eat_verb + if(drinking) + eat_verb = pick("slurp","sip","guzzle","drink","quaff","suck") + else + eat_verb = pick("bite","chew","nibble","gnaw","gobble","chomp") if (heal_amt > 0) var/healed = heal_amt && eater.health < eater.maxHealth @@ -66,5 +73,8 @@ finish_eating(eater, target) /datum/element/basic_eating/proc/finish_eating(mob/living/eater, atom/target) - playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE) + if(drinking) + playsound(eater.loc,'sound/items/drink.ogg', rand(10,50), TRUE) + else + playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE) qdel(target) diff --git a/code/datums/elements/climbable.dm b/code/datums/elements/climbable.dm index b7e93c61e246..76062b051159 100644 --- a/code/datums/elements/climbable.dm +++ b/code/datums/elements/climbable.dm @@ -131,15 +131,13 @@ ///Handles climbing onto the atom when you click-drag /datum/element/climbable/proc/mousedrop_receive(atom/climbed_thing, atom/movable/dropped_atom, mob/user, params) SIGNAL_HANDLER - if(user == dropped_atom && isliving(dropped_atom)) - var/mob/living/living_target = dropped_atom - if(isanimal(living_target)) - var/mob/living/simple_animal/animal = dropped_atom - if (!animal.dextrous) - return - if(living_target.mobility_flags & MOBILITY_MOVE) - INVOKE_ASYNC(src, PROC_REF(climb_structure), climbed_thing, living_target, params) - return + if(user != dropped_atom || !isliving(dropped_atom)) + return + if(!HAS_TRAIT(dropped_atom, TRAIT_FENCE_CLIMBER) && !HAS_TRAIT(dropped_atom, TRAIT_CAN_HOLD_ITEMS)) // If you can hold items you can probably climb a fence + return + var/mob/living/living_target = dropped_atom + if(living_target.mobility_flags & MOBILITY_MOVE) + INVOKE_ASYNC(src, PROC_REF(climb_structure), climbed_thing, living_target, params) ///Tries to climb onto the target if the forced movement of the mob allows it /datum/element/climbable/proc/try_speedrun(datum/source, mob/bumpee) diff --git a/code/datums/elements/content_barfer.dm b/code/datums/elements/content_barfer.dm index 782574a22217..2959ff9d58e7 100644 --- a/code/datums/elements/content_barfer.dm +++ b/code/datums/elements/content_barfer.dm @@ -4,18 +4,17 @@ * Used for morphs and bileworms! */ /datum/element/content_barfer - argument_hash_start_idx = 2 -/datum/element/content_barfer/Attach(datum/target, tally_string) +/datum/element/content_barfer/Attach(datum/target) . = ..() if(!isliving(target)) return ELEMENT_INCOMPATIBLE - RegisterSignals(target, list(COMSIG_LIVING_DEATH, COMSIG_LIVING_ON_WABBAJACKED), PROC_REF(barf_contents)) + RegisterSignals(target, list(COMSIG_LIVING_DEATH, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_MOB_CHANGED_TYPE), PROC_REF(barf_contents)) /datum/element/content_barfer/Detach(datum/target) - UnregisterSignal(target, list(COMSIG_LIVING_DEATH, COMSIG_LIVING_ON_WABBAJACKED)) + UnregisterSignal(target, list(COMSIG_LIVING_DEATH, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_MOB_CHANGED_TYPE)) return ..() /datum/element/content_barfer/proc/barf_contents(mob/living/target) diff --git a/code/datums/elements/death_drops.dm b/code/datums/elements/death_drops.dm index c02ba37cad72..070ed8063d25 100644 --- a/code/datums/elements/death_drops.dm +++ b/code/datums/elements/death_drops.dm @@ -16,7 +16,7 @@ if(!loot) stack_trace("[type] added to [target] with NO LOOT.") src.loot = loot - RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_death), override = TRUE) /datum/element/death_drops/Detach(datum/target) . = ..() diff --git a/code/datums/elements/dextrous.dm b/code/datums/elements/dextrous.dm new file mode 100644 index 000000000000..ef4fa290eb2b --- /dev/null +++ b/code/datums/elements/dextrous.dm @@ -0,0 +1,73 @@ +/** + * Sets up the attachee to have hands and manages things like dropping items on death and displaying them on examine + * Actual hand performance is managed by code on /living/ and not encapsulated here, we just enable it + */ +/datum/element/dextrous + +/datum/element/dextrous/Attach(datum/target, hands_count = 2, hud_type = /datum/hud/dextrous) + . = ..() + if (!isliving(target) || iscarbon(target)) + return ELEMENT_INCOMPATIBLE // Incompatible with the carbon typepath because that already has its own hand handling and doesn't need hand holding + + var/mob/living/mob_parent = target + set_available_hands(mob_parent, hands_count) + mob_parent.set_hud_used(new hud_type(target)) + mob_parent.hud_used.show_hud(mob_parent.hud_used.hud_version) + ADD_TRAIT(target, TRAIT_CAN_HOLD_ITEMS, REF(src)) + RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + RegisterSignal(target, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_hand_clicked)) + RegisterSignal(target, COMSIG_PARENT_EXAMINE, PROC_REF(on_examined)) + +/datum/element/dextrous/Detach(datum/source) + . = ..() + var/mob/living/mob_parent = source + set_available_hands(mob_parent, initial(mob_parent.default_num_hands)) + var/initial_hud = initial(mob_parent.hud_type) + mob_parent.set_hud_used(new initial_hud(source)) + mob_parent.hud_used.show_hud(mob_parent.hud_used.hud_version) + REMOVE_TRAIT(source, TRAIT_CAN_HOLD_ITEMS, REF(src)) + UnregisterSignal(source, list( + COMSIG_PARENT_EXAMINE, + COMSIG_LIVING_DEATH, + COMSIG_LIVING_UNARMED_ATTACK, + )) + +/// Set up how many hands we should have +/datum/element/dextrous/proc/set_available_hands(mob/living/hand_owner, hands_count) + hand_owner.drop_all_held_items() + var/held_items = list() + for (var/i in 1 to hands_count) + held_items += null + hand_owner.held_items = held_items + hand_owner.set_num_hands(hands_count) + hand_owner.set_usable_hands(hands_count) + +/// Drop our shit when we die +/datum/element/dextrous/proc/on_death(mob/living/died, gibbed) + SIGNAL_HANDLER + died.drop_all_held_items() + +/// Try picking up items +/datum/element/dextrous/proc/on_hand_clicked(mob/living/hand_haver, atom/target, proximity, modifiers) + SIGNAL_HANDLER + if (!proximity && target.loc != hand_haver) + var/obj/item/obj_item = target + if (istype(obj_item) && !obj_item.atom_storage && !(obj_item.item_flags & IN_STORAGE)) + return NONE + if (!isitem(target) && (hand_haver.istate & ISTATE_HARM)) + return NONE + if (hand_haver.istate & ISTATE_SECONDARY) + INVOKE_ASYNC(target, TYPE_PROC_REF(/atom, attack_hand_secondary), hand_haver, modifiers) + else + INVOKE_ASYNC(target, TYPE_PROC_REF(/atom, attack_hand), hand_haver, modifiers) + INVOKE_ASYNC(hand_haver, TYPE_PROC_REF(/mob, update_held_items)) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/// Tell people what we are holding +/datum/element/dextrous/proc/on_examined(mob/living/examined, mob/user, list/examine_list) + SIGNAL_HANDLER + for(var/obj/item/held_item in examined.held_items) + if(held_item.item_flags & (ABSTRACT|EXAMINE_SKIP|HAND_ITEM)) + continue + examine_list += span_info("[examined.p_They()] [examined.p_have()] [held_item.get_examine_string(user)] in [examined.p_their()] \ + [examined.get_held_index_name(examined.get_held_index_of_item(held_item))].") diff --git a/code/datums/elements/door_pryer.dm b/code/datums/elements/door_pryer.dm new file mode 100644 index 000000000000..cb30a48399c0 --- /dev/null +++ b/code/datums/elements/door_pryer.dm @@ -0,0 +1,70 @@ +/** + * Attached to a basic mob. + * Causes attacks on doors to attempt to open them. + */ +/datum/element/door_pryer + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// Time it takes to open a door with force + var/pry_time + /// Interaction key for if we force a door open + var/interaction_key + +/datum/element/door_pryer/Attach(datum/target, pry_time = 10 SECONDS, interaction_key = null) + . = ..() + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + src.pry_time = pry_time + src.interaction_key = interaction_key + RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK), PROC_REF(on_attack)) + +/datum/element/door_pryer/Detach(datum/source) + . = ..() + UnregisterSignal(source, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK)) + +/// If we're targeting an airlock, open it +/datum/element/door_pryer/proc/on_attack(mob/living/basic/attacker, atom/target, proximity_flag) + SIGNAL_HANDLER + if(!istype(target, /obj/machinery/door/airlock)) + return + var/obj/machinery/door/airlock/airlock_target = target + if (!airlock_target.density) + return // It's already open numbnuts + + if(DOING_INTERACTION_WITH_TARGET(attacker, target) || (!isnull(interaction_key) && DOING_INTERACTION(attacker, interaction_key))) + attacker.balloon_alert(attacker, "busy!") + return COMPONENT_CANCEL_ATTACK_CHAIN + + if (airlock_target.locked || airlock_target.welded || airlock_target.seal) + if (!(attacker.istate & ISTATE_HARM)) + airlock_target.balloon_alert(attacker, "it's sealed!") + return COMPONENT_CANCEL_ATTACK_CHAIN + return // Attack the door + + INVOKE_ASYNC(src, PROC_REF(open_door), attacker, airlock_target) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/// Try opening the door, and if we can't then try forcing it +/datum/element/door_pryer/proc/open_door(mob/living/basic/attacker, obj/machinery/door/airlock/airlock_target) + if (!airlock_target.hasPower()) + attacker.visible_message(span_warning("[attacker] forces the [airlock_target] to open.")) + airlock_target.open(FORCING_DOOR_CHECKS) + return + + if (airlock_target.allowed(attacker)) + airlock_target.open(DEFAULT_DOOR_CHECKS) + return + + attacker.visible_message(\ + message = span_warning("[attacker] starts forcing the [airlock_target] open!"), + blind_message = span_hear("You hear a metal screeching sound."), + ) + playsound(airlock_target, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) + airlock_target.balloon_alert(attacker, "prying...") + if(!do_after(attacker, pry_time, airlock_target)) + airlock_target.balloon_alert(attacker, "interrupted!") + return + if(airlock_target.locked) + return + attacker.visible_message(span_warning("[attacker] forces the [airlock_target] to open.")) + airlock_target.open(BYPASS_DOOR_CHECKS) diff --git a/code/datums/elements/food/venue_price.dm b/code/datums/elements/food/venue_price.dm index 1fcf9de040dc..be846d7c004d 100644 --- a/code/datums/elements/food/venue_price.dm +++ b/code/datums/elements/food/venue_price.dm @@ -21,19 +21,19 @@ UnregisterSignal(target, COMSIG_ITEM_SOLD_TO_CUSTOMER) UnregisterSignal(target, COMSIG_REAGENT_SOLD_TO_CUSTOMER) -/datum/element/venue_price/proc/item_sold(obj/item/thing_sold, mob/living/simple_animal/robot_customer/sold_to) +/datum/element/venue_price/proc/item_sold(obj/item/thing_sold, mob/living/basic/robot_customer/sold_to) SIGNAL_HANDLER produce_cash(sold_to, thing_sold) return TRANSACTION_SUCCESS -/datum/element/venue_price/proc/reagent_sold(datum/reagent/reagent_sold, mob/living/simple_animal/robot_customer/sold_to, obj/item/container) +/datum/element/venue_price/proc/reagent_sold(datum/reagent/reagent_sold, mob/living/basic/robot_customer/sold_to, obj/item/container) SIGNAL_HANDLER produce_cash(sold_to, container) return TRANSACTION_SUCCESS -/datum/element/venue_price/proc/produce_cash(mob/living/simple_animal/robot_customer/sold_to, obj/item/container) +/datum/element/venue_price/proc/produce_cash(mob/living/basic/robot_customer/sold_to, obj/item/container) new /obj/item/holochip(get_turf(container), venue_price) playsound(container, 'sound/effects/cashregister.ogg', 60, TRUE) diff --git a/code/datums/elements/footstep.dm b/code/datums/elements/footstep.dm index 86cd5c27ce6b..f3e5176d0972 100644 --- a/code/datums/elements/footstep.dm +++ b/code/datums/elements/footstep.dm @@ -40,6 +40,8 @@ footstep_sounds = GLOB.heavyfootstep if(FOOTSTEP_MOB_SHOE) footstep_sounds = GLOB.footstep + if(FOOTSTEP_MOB_RUST) + footstep_sounds = 'sound/effects/footstep/rustystep1.ogg' if(FOOTSTEP_MOB_SLIME) footstep_sounds = 'sound/effects/footstep/slime1.ogg' if(FOOTSTEP_OBJ_MACHINE) @@ -89,37 +91,32 @@ if(steps != 0 && !source.has_gravity()) // don't need to step as often when you hop around return - return turf + + . = list(FOOTSTEP_MOB_SHOE = turf.footstep, FOOTSTEP_MOB_BAREFOOT = turf.barefootstep, FOOTSTEP_MOB_HEAVY = turf.heavyfootstep, FOOTSTEP_MOB_CLAW = turf.clawfootstep, STEP_SOUND_PRIORITY = STEP_SOUND_NO_PRIORITY) + SEND_SIGNAL(turf, COMSIG_TURF_PREPARE_STEP_SOUND, .) + return . /datum/element/footstep/proc/play_simplestep(mob/living/source, atom/oldloc, direction, forced, list/old_locs, momentum_change) SIGNAL_HANDLER - + var/turf/source_loc = get_turf(source) if (forced || SHOULD_DISABLE_FOOTSTEPS(source)) return - var/turf/open/source_loc = prepare_step(source) - if(!source_loc) + var/list/prepared_steps = prepare_step(source) + if(!prepared_steps) return + if(isfile(footstep_sounds) || istext(footstep_sounds)) playsound(source_loc, footstep_sounds, volume, falloff_distance = 1, vary = sound_vary, mixer_channel = CHANNEL_SOUND_FOOTSTEPS) return - var/turf_footstep - switch(footstep_type) - if(FOOTSTEP_MOB_CLAW) - turf_footstep = source_loc.clawfootstep - if(FOOTSTEP_MOB_BAREFOOT) - turf_footstep = source_loc.barefootstep - if(FOOTSTEP_MOB_HEAVY) - turf_footstep = source_loc.heavyfootstep - if(FOOTSTEP_MOB_SHOE) - turf_footstep = source_loc.footstep + + var/turf_footstep = prepared_steps[footstep_type] if(!turf_footstep) return playsound(source_loc, pick(footstep_sounds[turf_footstep][1]), footstep_sounds[turf_footstep][2] * volume, TRUE, footstep_sounds[turf_footstep][3] + e_range, falloff_distance = 1, vary = sound_vary, mixer_channel = CHANNEL_SOUND_FOOTSTEPS) /datum/element/footstep/proc/play_humanstep(mob/living/carbon/human/source, atom/oldloc, direction, forced, list/old_locs, momentum_change) SIGNAL_HANDLER - if (forced || SHOULD_DISABLE_FOOTSTEPS(source) || !momentum_change) return @@ -130,8 +127,8 @@ volume_multiplier = 0.6 range_adjustment = -2 - var/turf/open/source_loc = prepare_step(source) - if(!source_loc) + var/list/prepared_steps = prepare_step(source) + if(!prepared_steps) return //cache for sanic speed (lists are references anyways) @@ -142,20 +139,22 @@ if ((source.wear_suit?.body_parts_covered | source.w_uniform?.body_parts_covered | source.shoes?.body_parts_covered) & FEET) // we are wearing shoes - heard_clients = playsound(source_loc, pick(footstep_sounds[source_loc.footstep][1]), - footstep_sounds[source_loc.footstep][2] * volume * volume_multiplier, + var/shoestep_type = prepared_steps[FOOTSTEP_MOB_SHOE] + heard_clients = playsound(source.loc, pick(footstep_sounds[shoestep_type][1]), + footstep_sounds[shoestep_type][2] * volume * volume_multiplier, TRUE, - footstep_sounds[source_loc.footstep][3] + e_range + range_adjustment, falloff_distance = 1, vary = sound_vary, mixer_channel = CHANNEL_SOUND_FOOTSTEPS) + footstep_sounds[shoestep_type][3] + e_range + range_adjustment, falloff_distance = 1, vary = sound_vary, mixer_channel = CHANNEL_SOUND_FOOTSTEPS) else + var/barefoot_type = prepared_steps[FOOTSTEP_MOB_BAREFOOT] if(source.dna.species.special_step_sounds) - heard_clients = playsound(source_loc, pick(source.dna.species.special_step_sounds), 50, TRUE, falloff_distance = 1, vary = sound_vary) + heard_clients = playsound(source.loc, pick(source.dna.species.special_step_sounds), 50, TRUE, falloff_distance = 1, vary = sound_vary) else var/static/list/bare_footstep_sounds = GLOB.barefootstep - heard_clients = playsound(source_loc, pick(bare_footstep_sounds[source_loc.barefootstep][1]), - bare_footstep_sounds[source_loc.barefootstep][2] * volume * volume_multiplier, + heard_clients = playsound(source.loc, pick(bare_footstep_sounds[barefoot_type][1]), + bare_footstep_sounds[barefoot_type][2] * volume * volume_multiplier, TRUE, - bare_footstep_sounds[source_loc.barefootstep][3] + e_range + range_adjustment, falloff_distance = 1, vary = sound_vary, mixer_channel = CHANNEL_SOUND_FOOTSTEPS) + bare_footstep_sounds[barefoot_type][3] + e_range + range_adjustment, falloff_distance = 1, vary = sound_vary, mixer_channel = CHANNEL_SOUND_FOOTSTEPS) if(heard_clients) play_fov_effect(source, 5, "footstep", direction, ignore_self = TRUE, override_list = heard_clients) diff --git a/code/datums/elements/footstep_override.dm b/code/datums/elements/footstep_override.dm new file mode 100644 index 000000000000..4e0c346c5be2 --- /dev/null +++ b/code/datums/elements/footstep_override.dm @@ -0,0 +1,80 @@ +///When attached, the footstep sound played by the footstep element will be replaced by this one's +/datum/element/footstep_override + element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH_ON_HOST_DESTROY + argument_hash_start_idx = 2 + ///The sound played for movables with claw step sound type. + var/clawfootstep + ///The sound played for movables with barefoot step sound type. + var/barefootstep + ///The sound played for movables with heavy step sound type. + var/heavyfootstep + ///The sound played for movables with shoed step sound type. + var/footstep + ///The priority this element has in relation to other elements of the same type attached to other movables on the same turf. + var/priority + /** + * A list of turfs occupied by the movables this element is attached to. + * Needed so it stops listening the turf's signals ONLY when it has no movable with the element. + */ + var/list/occupied_turfs = list() + +/datum/element/footstep_override/Attach(atom/movable/target, clawfootstep = FOOTSTEP_HARD_CLAW, barefootstep = FOOTSTEP_HARD_BAREFOOT, heavyfootstep = FOOTSTEP_GENERIC_HEAVY, footstep = FOOTSTEP_FLOOR, priority = STEP_SOUND_NO_PRIORITY) + . = ..() + if(!ismovable(target)) + return ELEMENT_INCOMPATIBLE + + src.clawfootstep = clawfootstep + src.barefootstep = barefootstep + src.heavyfootstep = heavyfootstep + src.footstep = footstep + src.priority = priority + + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) + if(isturf(target.loc)) + occupy_turf(target, target.loc) + +/datum/element/footstep_override/Detach(atom/movable/source) + if(isturf(source.loc)) + vacate_turf(source, source.loc) + return ..() + +/datum/element/footstep_override/proc/on_moved(atom/movable/source, atom/oldloc) + SIGNAL_HANDLER + if(isturf(oldloc)) + vacate_turf(source, oldloc) + if(isturf(source.loc)) + occupy_turf(source, source.loc) + +/** + * Adds the movable to the list of movables with the element occupying the turf. + * If the turf was not on the list of occupied turfs before, a signal will be registered + * to it. + */ +/datum/element/footstep_override/proc/occupy_turf(atom/movable/movable, turf/location) + if(occupied_turfs[location]) + occupied_turfs[location] |= movable + return + occupied_turfs[location] = list(movable) + RegisterSignal(location, COMSIG_TURF_PREPARE_STEP_SOUND, PROC_REF(prepare_steps)) + +/** + * Removes the movable from the list of movables with the element occupying the turf. + * If the turf is no longer occupied, it'll be removed from the list, and the signal + * unregistered from it + */ +/datum/element/footstep_override/proc/vacate_turf(atom/movable/movable, turf/location) + LAZYREMOVE(occupied_turfs[location], movable) + if(!occupied_turfs[location]) + occupied_turfs -= location + UnregisterSignal(location, COMSIG_TURF_PREPARE_STEP_SOUND) + +///Changes the sound types to be played if the element priority is higher than the one in the steps list. +/datum/element/footstep_override/proc/prepare_steps(turf/source, list/steps) + SIGNAL_HANDLER + if(steps[STEP_SOUND_PRIORITY] > priority) + return + steps[FOOTSTEP_MOB_SHOE] = footstep + steps[FOOTSTEP_MOB_BAREFOOT] = barefootstep + steps[FOOTSTEP_MOB_HEAVY] = heavyfootstep + steps[FOOTSTEP_MOB_CLAW] = clawfootstep + steps[STEP_SOUND_PRIORITY] = priority diff --git a/code/datums/elements/kneecapping.dm b/code/datums/elements/kneecapping.dm index f0a1cd096812..f6ff92e6e0d5 100644 --- a/code/datums/elements/kneecapping.dm +++ b/code/datums/elements/kneecapping.dm @@ -7,7 +7,7 @@ * Kneecapping attacks have a wounding bonus between severe and critical+10 wound thresholds. Without some serious wound protecting * armour this all but guarantees a wound of some sort. The attack is directed specifically at a limb and the limb takes the damage. * - * Requires the attacker to be aiming for either leg zone, which will be targetted specifically. They will than have a 3-second long + * Requires the attacker to be aiming for either leg zone, which will be targeted specifically. They will than have a 3-second long * do_after before executing the attack. * * Kneecapping requires the target to either be on the floor, immobilised or buckled to something. And also to have an appropriate leg. @@ -80,9 +80,11 @@ if(do_after(attacker, 3 SECONDS, target, interaction_key = weapon)) attacker.visible_message(span_warning("[attacker] swings [attacker.p_their()] [weapon] at [target]'s kneecaps!"), span_danger("You swing \the [weapon] at [target]'s kneecaps!")) - var/datum/wound/blunt/severe/severe_wound_type = /datum/wound/blunt/severe - var/datum/wound/blunt/critical/critical_wound_type = /datum/wound/blunt/critical - leg.receive_damage(brute = weapon.force, wound_bonus = rand(initial(severe_wound_type.threshold_minimum), initial(critical_wound_type.threshold_minimum) + 10)) + + var/min_wound = leg.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_SEVERE, return_value_if_no_wound = 30, wound_source = weapon) + var/max_wound = leg.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_CRITICAL, return_value_if_no_wound = 50, wound_source = weapon) + + leg.receive_damage(brute = weapon.force, wound_bonus = rand(min_wound, max_wound + 10)) target.emote("scream") log_combat(attacker, target, "broke the kneecaps of", weapon) target.update_damage_overlays() diff --git a/code/datums/elements/mob_grabber.dm b/code/datums/elements/mob_grabber.dm new file mode 100644 index 000000000000..a85c5dc48b25 --- /dev/null +++ b/code/datums/elements/mob_grabber.dm @@ -0,0 +1,30 @@ +/// Grab onto mobs we attack +/datum/element/mob_grabber + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// What state must the mob be in to be grabbed? + var/minimum_stat + /// If someone else is already grabbing this, will we take it? + var/steal_from_others + +/datum/element/mob_grabber/Attach(datum/target, minimum_stat = SOFT_CRIT, steal_from_others = TRUE) + . = ..() + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + src.minimum_stat = minimum_stat + src.steal_from_others = steal_from_others + RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(grab_mob)) + +/datum/element/mob_grabber/Detach(datum/source) + UnregisterSignal(source, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET)) + . = ..() + +/// Try and grab something we attacked +/datum/element/mob_grabber/proc/grab_mob(mob/living/source, mob/living/target) + SIGNAL_HANDLER + if (!isliving(target) || !source.Adjacent(target) || target.stat < minimum_stat) + return + var/atom/currently_pulled = target.pulledby + if (!isnull(currently_pulled) && (!steal_from_others || currently_pulled == source)) + return + INVOKE_ASYNC(target, TYPE_PROC_REF(/mob/living, grabbedby), source) diff --git a/code/datums/elements/ore_collecting.dm b/code/datums/elements/ore_collecting.dm new file mode 100644 index 000000000000..30c9ecc7598d --- /dev/null +++ b/code/datums/elements/ore_collecting.dm @@ -0,0 +1,26 @@ +/* + * A component to allow us to collect ore + */ +/datum/element/ore_collecting + + +/datum/element/ore_collecting/Attach(datum/target) + . = ..() + + if(!isliving(target)) + return ELEMENT_INCOMPATIBLE + RegisterSignal(target, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(collect_ore)) + +/datum/element/ore_collecting/Detach(datum/target) + . = ..() + UnregisterSignal(target, COMSIG_HOSTILE_PRE_ATTACKINGTARGET) + +/datum/element/ore_collecting/proc/collect_ore(mob/living/source, atom/target) + SIGNAL_HANDLER + + if(!istype(target, /obj/item/stack/ore)) + return + + var/atom/movable/movable_target = target + movable_target.forceMove(source) + return COMPONENT_HOSTILE_NO_ATTACK diff --git a/code/datums/elements/pet_bonus.dm b/code/datums/elements/pet_bonus.dm index a3302645ee50..7c69927691bc 100644 --- a/code/datums/elements/pet_bonus.dm +++ b/code/datums/elements/pet_bonus.dm @@ -33,6 +33,7 @@ return new /obj/effect/temp_visual/heart(pet.loc) + SEND_SIGNAL(pet, COMSIG_ANIMAL_PET, petter, modifiers) if(emote_message && prob(33)) pet.manual_emote(emote_message) petter.add_mood_event("petting_bonus", moodlet, pet) diff --git a/code/datums/elements/relay_attackers.dm b/code/datums/elements/relay_attackers.dm index f94fba531eed..cdc86e6058cb 100644 --- a/code/datums/elements/relay_attackers.dm +++ b/code/datums/elements/relay_attackers.dm @@ -15,6 +15,7 @@ RegisterSignal(target, COMSIG_ATOM_PREHITBY, PROC_REF(on_hitby)) RegisterSignal(target, COMSIG_ATOM_HULK_ATTACK, PROC_REF(on_attack_hulk)) RegisterSignal(target, COMSIG_ATOM_ATTACK_MECH, PROC_REF(on_attack_mech)) + ADD_TRAIT(target, TRAIT_RELAYING_ATTACKER, REF(src)) /datum/element/relay_attackers/Detach(datum/source, ...) . = ..() @@ -30,22 +31,29 @@ COMSIG_ATOM_HULK_ATTACK, COMSIG_ATOM_ATTACK_MECH, )) + REMOVE_TRAIT(source, TRAIT_RELAYING_ATTACKER, REF(src)) /datum/element/relay_attackers/proc/after_attackby(atom/target, obj/item/weapon, mob/attacker) SIGNAL_HANDLER if(weapon.force) - relay_attacker(target, attacker, weapon.damtype == STAMINA ? ATTACKER_STAMINA_ATTACK : NONE) + relay_attacker(target, attacker, weapon.damtype == STAMINA ? ATTACKER_STAMINA_ATTACK : ATTACKER_DAMAGING_ATTACK) /datum/element/relay_attackers/proc/on_attack_generic(atom/target, mob/living/attacker, list/modifiers) SIGNAL_HANDLER - var/shoving = (attacker.istate & ISTATE_SECONDARY) ? ATTACKER_SHOVING : NONE - if((attacker.istate & ISTATE_HARM) || (attacker.istate & ISTATE_SECONDARY)) - relay_attacker(target, attacker, shoving) + // Check for a shove. + if((attacker.istate & ISTATE_SECONDARY)) + relay_attacker(target, attacker, ATTACKER_SHOVING) + return + + // Else check for combat mode. + if((attacker.istate & ISTATE_HARM)) + relay_attacker(target, attacker, ATTACKER_DAMAGING_ATTACK) + return /datum/element/relay_attackers/proc/on_attack_npc(atom/target, mob/living/attacker) SIGNAL_HANDLER if(attacker.melee_damage_upper > 0) - relay_attacker(target, attacker) + relay_attacker(target, attacker, ATTACKER_DAMAGING_ATTACK) /// Even if another component blocked this hit, someone still shot at us /datum/element/relay_attackers/proc/on_bullet_act(atom/target, list/bullet_args, obj/projectile/hit_projectile) @@ -54,7 +62,7 @@ return if(!ismob(hit_projectile.firer)) return - relay_attacker(target, hit_projectile.firer, hit_projectile.damage_type == STAMINA ? ATTACKER_STAMINA_ATTACK : NONE) + relay_attacker(target, hit_projectile.firer, hit_projectile.damage_type == STAMINA ? ATTACKER_STAMINA_ATTACK : ATTACKER_DAMAGING_ATTACK) /// Even if another component blocked this hit, someone still threw something /datum/element/relay_attackers/proc/on_hitby(atom/target, atom/movable/hit_atom, datum/thrownthing/throwingdatum) @@ -67,15 +75,15 @@ var/mob/thrown_by = hit_item.thrownby?.resolve() if(!ismob(thrown_by)) return - relay_attacker(target, thrown_by, hit_item.damtype == STAMINA ? ATTACKER_STAMINA_ATTACK : NONE) + relay_attacker(target, thrown_by, hit_item.damtype == STAMINA ? ATTACKER_STAMINA_ATTACK : ATTACKER_DAMAGING_ATTACK) /datum/element/relay_attackers/proc/on_attack_hulk(atom/target, mob/attacker) SIGNAL_HANDLER - relay_attacker(target, attacker) + relay_attacker(target, attacker, ATTACKER_DAMAGING_ATTACK) /datum/element/relay_attackers/proc/on_attack_mech(atom/target, obj/vehicle/sealed/mecha/mecha_attacker, mob/living/pilot) SIGNAL_HANDLER - relay_attacker(target, mecha_attacker) + relay_attacker(target, mecha_attacker, ATTACKER_DAMAGING_ATTACK) /// Send out a signal identifying whoever just attacked us (usually a mob but sometimes a mech or turret) /datum/element/relay_attackers/proc/relay_attacker(atom/victim, atom/attacker, attack_flags) diff --git a/code/datums/elements/structure_repair.dm b/code/datums/elements/structure_repair.dm new file mode 100644 index 000000000000..d3b26eed815b --- /dev/null +++ b/code/datums/elements/structure_repair.dm @@ -0,0 +1,45 @@ +/// Intercepts attacks from mobs with this component to instead repair specified structures. +/datum/element/structure_repair + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// How much to heal structures by + var/heal_amount + /// Typecache of types of structures to repair + var/list/structure_types_typecache + +/datum/element/structure_repair/Attach( + datum/target, + heal_amount = 5, + structure_types_typecache = typecacheof(list(/obj/structure)), +) + . = ..() + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + + src.heal_amount = heal_amount + src.structure_types_typecache = structure_types_typecache + RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(try_repair)) + +/datum/element/structure_repair/Detach(datum/source) + UnregisterSignal(source, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET)) + return ..() + +/// If the target is of a valid type, interrupt the attack chain to repair it instead +/datum/element/structure_repair/proc/try_repair(mob/living/fixer, atom/target) + SIGNAL_HANDLER + + if (!is_type_in_typecache(target, structure_types_typecache)) + return + + if (target.get_integrity() >= target.max_integrity) + target.balloon_alert(fixer, "not damaged!") + return COMPONENT_CANCEL_ATTACK_CHAIN + + target.repair_damage(heal_amount) + fixer.Beam(target, icon_state = "sendbeam", time = 0.4 SECONDS) + fixer.visible_message( + span_danger("[fixer] repairs [target]."), + span_danger("You repair [target], leaving it at [round(target.get_integrity() * 100 / target.max_integrity)]% stability."), + ) + + return COMPONENT_CANCEL_ATTACK_CHAIN diff --git a/code/datums/elements/waddling.dm b/code/datums/elements/waddling.dm index eff201455d85..825b0c4e4cb8 100644 --- a/code/datums/elements/waddling.dm +++ b/code/datums/elements/waddling.dm @@ -4,27 +4,23 @@ . = ..() if(!ismovable(target)) return ELEMENT_INCOMPATIBLE - if(isliving(target)) - RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(LivingWaddle)) - else - RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(Waddle)) + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(Waddle)) /datum/element/waddling/Detach(datum/source) . = ..() UnregisterSignal(source, COMSIG_MOVABLE_MOVED) - -/datum/element/waddling/proc/LivingWaddle(mob/living/target) +/datum/element/waddling/proc/Waddle(atom/movable/moved, atom/oldloc, direction, forced) SIGNAL_HANDLER - - if(target.incapacitated() || target.body_position == LYING_DOWN) + if(forced || CHECK_MOVE_LOOP_FLAGS(moved, MOVEMENT_LOOP_OUTSIDE_CONTROL)) return - Waddle(target) - - -/datum/element/waddling/proc/Waddle(atom/movable/target) - SIGNAL_HANDLER + if(isliving(moved)) + var/mob/living/living_moved = moved + if (living_moved.incapacitated() || living_moved.body_position == LYING_DOWN) + return + waddling_animation(moved) +/datum/element/waddling/proc/waddling_animation(atom/movable/target) animate(target, pixel_z = 4, time = 0) var/prev_trans = matrix(target.transform) animate(pixel_z = 0, transform = turn(target.transform, pick(-12, 0, 12)), time=2) diff --git a/code/datums/elements/wall_tearer.dm b/code/datums/elements/wall_tearer.dm new file mode 100644 index 000000000000..19e84dd2720b --- /dev/null +++ b/code/datums/elements/wall_tearer.dm @@ -0,0 +1,81 @@ +/// Returned if we can rip up this target +#define WALL_TEAR_ALLOWED TRUE +/// Returned if we can't rip up this target +#define WALL_TEAR_INVALID FALSE +/// Returned if we can't rip up the target but still don't want to attack it +#define WALL_TEAR_FAIL_CANCEL_CHAIN -1 + +/** + * Allows attached mobs to destroy walls over time, a little less unreasonable than the instant wall deletion of wall_smasher + */ +/datum/element/wall_tearer + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// Whether we can break reinforced walls + var/allow_reinforced + /// How long it takes for us to destroy a wall completely (its a 3 step process so this will be divided by three) + var/tear_time + /// How much longer it takes to break reinforced walls + var/reinforced_multiplier + /// What interaction key do we use for our interaction + var/do_after_key + +/datum/element/wall_tearer/Attach(datum/target, allow_reinforced = TRUE, tear_time = 4 SECONDS, reinforced_multiplier = 3, do_after_key = null) + . = ..() + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + src.allow_reinforced = allow_reinforced + src.tear_time = tear_time + src.reinforced_multiplier = reinforced_multiplier + src.do_after_key = do_after_key + RegisterSignals(target, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_LIVING_UNARMED_ATTACK), PROC_REF(on_attacked_wall)) + +/datum/element/wall_tearer/Detach(datum/source) + . = ..() + UnregisterSignal(source, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_LIVING_UNARMED_ATTACK)) + +/// Try to tear up a wall +/datum/element/wall_tearer/proc/on_attacked_wall(mob/living/tearer, atom/target, proximity_flag) + SIGNAL_HANDLER + if (DOING_INTERACTION_WITH_TARGET(tearer, target) || (!isnull(do_after_key) && DOING_INTERACTION(tearer, do_after_key))) + tearer.balloon_alert(tearer, "busy!") + return COMPONENT_HOSTILE_NO_ATTACK + var/is_valid = validate_target(target, tearer) + if (is_valid != WALL_TEAR_ALLOWED) + return is_valid == WALL_TEAR_FAIL_CANCEL_CHAIN ? COMPONENT_HOSTILE_NO_ATTACK : NONE + INVOKE_ASYNC(src, PROC_REF(rip_and_tear), tearer, target) + return COMPONENT_HOSTILE_NO_ATTACK + +/datum/element/wall_tearer/proc/rip_and_tear(mob/living/tearer, atom/target) + // We need to do this three times to actually destroy it + var/rip_time = (istype(target, /turf/closed/wall/r_wall) ? tear_time * reinforced_multiplier : tear_time) / 3 + if (rip_time > 0) + tearer.visible_message(span_warning("[tearer] begins tearing through [target]!")) + playsound(tearer, 'sound/machines/airlock_alien_prying.ogg', vol = 100, vary = TRUE) + target.balloon_alert(tearer, "tearing...") + if (!do_after(tearer, delay = rip_time, target = target, interaction_key = do_after_key)) + tearer.balloon_alert(tearer, "interrupted!") + return + // Might have been replaced, removed, or reinforced during our do_after + var/is_valid = validate_target(target, tearer) + if (is_valid != WALL_TEAR_ALLOWED) + return + target.AddComponent(/datum/component/torn_wall) + is_valid = validate_target(target, tearer) // And now we might have just destroyed it + if (is_valid == WALL_TEAR_ALLOWED) + tearer.UnarmedAttack(target, proximity_flag = TRUE) + +/// Check if the target atom is a wall we can actually rip up +/datum/element/wall_tearer/proc/validate_target(atom/target, mob/living/tearer) + if (!isclosedturf(target) || isindestructiblewall(target)) + return WALL_TEAR_INVALID + + var/reinforced = istype(target, /turf/closed/wall/r_wall) + if (!allow_reinforced && reinforced) + target.balloon_alert(tearer, "it's too strong!") + return WALL_TEAR_FAIL_CANCEL_CHAIN + return WALL_TEAR_ALLOWED + +#undef WALL_TEAR_ALLOWED +#undef WALL_TEAR_INVALID +#undef WALL_TEAR_FAIL_CANCEL_CHAIN diff --git a/code/datums/elements/wall_walker.dm b/code/datums/elements/wall_walker.dm new file mode 100644 index 000000000000..92ac3318c128 --- /dev/null +++ b/code/datums/elements/wall_walker.dm @@ -0,0 +1,49 @@ +/// This element will allow the mob it's attached to to pass through a specified type of wall, and drag anything through it. +/datum/element/wall_walker + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// What kind of walls can we pass through? + var/wall_type + +/datum/element/wall_walker/Attach( + datum/target, + wall_type = /turf/closed/wall, +) + . = ..() + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + + src.wall_type = wall_type + RegisterSignal(target, COMSIG_LIVING_WALL_BUMP, PROC_REF(try_pass_wall)) + RegisterSignal(target, COMSIG_LIVING_WALL_EXITED, PROC_REF(exit_wall)) + +/datum/element/wall_walker/Detach(datum/source) + UnregisterSignal(source, list(COMSIG_LIVING_WALL_BUMP, COMSIG_LIVING_WALL_EXITED)) + return ..() + +/// If the wall is of the proper type, pass into it and keep hold on whatever you're pulling +/datum/element/wall_walker/proc/try_pass_wall(mob/living/passing_mob, turf/closed/bumped_wall) + if(!istype(bumped_wall, wall_type)) + return + + var/atom/movable/stored_pulling = passing_mob.pulling + if(stored_pulling) //force whatever you're pulling to come with you + stored_pulling.setDir(get_dir(stored_pulling.loc, passing_mob.loc)) + stored_pulling.forceMove(passing_mob.loc) + passing_mob.forceMove(bumped_wall) + + if(stored_pulling) //don't drop them because we went into a wall + passing_mob.start_pulling(stored_pulling, supress_message = TRUE) + +/// If the wall is of the proper type, pull whatever you're pulling into it +/datum/element/wall_walker/proc/exit_wall(mob/living/passing_mob, turf/closed/exited_wall) + if(!istype(exited_wall, wall_type)) + return + + var/atom/movable/stored_pulling = passing_mob.pulling + if(isnull(stored_pulling)) + return + + stored_pulling.setDir(get_dir(stored_pulling.loc, passing_mob.loc)) + stored_pulling.forceMove(exited_wall) + passing_mob.start_pulling(stored_pulling, supress_message = TRUE) diff --git a/code/datums/elements/wheel.dm b/code/datums/elements/wheel.dm new file mode 100644 index 000000000000..2bb8977ca5ca --- /dev/null +++ b/code/datums/elements/wheel.dm @@ -0,0 +1,28 @@ +/// Element which spins you as you move +/datum/element/wheel + +/datum/element/wheel/Attach(datum/target) + . = ..() + if(!ismovable(target)) + return ELEMENT_INCOMPATIBLE + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) + +/datum/element/wheel/Detach(datum/source) + . = ..() + UnregisterSignal(source, COMSIG_MOVABLE_MOVED) + +/datum/element/wheel/proc/on_moved(atom/movable/moved, atom/oldloc, direction, forced) + SIGNAL_HANDLER + if(forced || CHECK_MOVE_LOOP_FLAGS(moved, MOVEMENT_LOOP_OUTSIDE_CONTROL)) + return + if(isliving(moved)) + var/mob/living/living_moved = moved + if (living_moved.incapacitated() || living_moved.body_position == LYING_DOWN) + return + var/rotation_degree = (360 / 3) + if(direction & SOUTHWEST) + rotation_degree *= -1 + + var/matrix/to_turn = matrix(moved.transform) + to_turn = turn(moved.transform, rotation_degree) + animate(moved, transform = to_turn, time = 0.1 SECONDS, flags = ANIMATION_PARALLEL) diff --git a/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm b/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm new file mode 100644 index 000000000000..f8a0f4a062c6 --- /dev/null +++ b/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm @@ -0,0 +1,37 @@ +// +// CARP +// + +/datum/greyscale_config/carp + name = "Space Carp" + icon_file = 'icons/mob/simple/carp.dmi' + json_config = 'code/datums/greyscale/json_configs/carp.json' + +/datum/greyscale_config/carp_magic + name = "Magicarp" + icon_file = 'icons/mob/simple/carp.dmi' + json_config = 'code/datums/greyscale/json_configs/carp_magic.json' + +/datum/greyscale_config/carp_mega + name = "Megacarp" + icon_file = 'icons/mob/simple/broadMobs.dmi' + json_config = 'code/datums/greyscale/json_configs/carp_mega.json' + +/datum/greyscale_config/carp/disk_mouth + name = "Space Carp, Disk in Mouth" + json_config = 'code/datums/greyscale/json_configs/carp_disk_mouth.json' + +// +// MISC +// + +/datum/greyscale_config/garden_gnome + name = "Garden Gnome" + icon_file = 'icons/mob/simple/garden_gnome.dmi' + json_config = 'code/datums/greyscale/json_configs/garden_gnome.json' + +/datum/greyscale_config/gutlunch + name = "Gutlunch" + icon_file = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + json_config = 'code/datums/greyscale/json_configs/gutlunch.json' + diff --git a/code/datums/greyscale/json_configs/gutlunch.json b/code/datums/greyscale/json_configs/gutlunch.json new file mode 100644 index 000000000000..0b9303381634 --- /dev/null +++ b/code/datums/greyscale/json_configs/gutlunch.json @@ -0,0 +1,15 @@ +{ + "gutlunch": [ + { + "type": "icon_state", + "icon_state": "gutlunch", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "gutlunch_face", + "blend_mode": "overlay" + } + ] +} diff --git a/code/datums/mapgen/CaveGenerator.dm b/code/datums/mapgen/CaveGenerator.dm index 505ac87766a3..e407198faf3f 100644 --- a/code/datums/mapgen/CaveGenerator.dm +++ b/code/datums/mapgen/CaveGenerator.dm @@ -55,7 +55,7 @@ /mob/living/basic/mining/basilisk = 4, /mob/living/basic/mining/goldgrub = 1, /mob/living/basic/mining/goliath/ancient = 5, - /mob/living/simple_animal/hostile/asteroid/hivelord = 3, + /mob/living/basic/mining/hivelord = 3, ) mob_spawn_list = expand_weights(weighted_mob_spawn_list) mob_spawn_no_mega_list = expand_weights(weighted_mob_spawn_list - SPAWN_MEGAFAUNA) diff --git a/code/datums/mapgen/Cavegens/IcemoonCaves.dm b/code/datums/mapgen/Cavegens/IcemoonCaves.dm index 3f6f5fd60acc..a6638919c81b 100644 --- a/code/datums/mapgen/Cavegens/IcemoonCaves.dm +++ b/code/datums/mapgen/Cavegens/IcemoonCaves.dm @@ -4,11 +4,11 @@ weighted_mob_spawn_list = list( - /mob/living/basic/mining/lobstrosity = 15, /mob/living/basic/mining/goldgrub = 10, - /mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow = 50, + /mob/living/basic/mining/legion/snow = 50, + /mob/living/basic/mining/lobstrosity = 15, + /mob/living/basic/mining/wolf = 50, /mob/living/simple_animal/hostile/asteroid/polarbear = 30, - /mob/living/simple_animal/hostile/asteroid/wolf = 50, /obj/structure/spawner/ice_moon = 3, /obj/structure/spawner/ice_moon/polarbear = 3, ) @@ -42,12 +42,12 @@ weighted_closed_turf_types = list(/turf/closed/mineral/random/snow/underground = 1) weighted_mob_spawn_list = list( SPAWN_MEGAFAUNA = 1, + /mob/living/basic/mining/ice_demon = 100, /mob/living/basic/mining/ice_whelp = 60, - /mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow = 100, - /mob/living/simple_animal/hostile/asteroid/ice_demon = 100, + /mob/living/basic/mining/legion/snow = 100, /obj/structure/spawner/ice_moon/demonic_portal = 6, - /obj/structure/spawner/ice_moon/demonic_portal/snowlegion = 6, /obj/structure/spawner/ice_moon/demonic_portal/ice_whelp = 6, + /obj/structure/spawner/ice_moon/demonic_portal/snowlegion = 6, ) weighted_megafauna_spawn_list = list(/mob/living/simple_animal/hostile/megafauna/colossus = 1) weighted_flora_spawn_list = list(/obj/structure/flora/rock/icy/style_random = 6, /obj/structure/flora/rock/pile/icy/style_random = 6, /obj/structure/flora/ash/chilly = 1) diff --git a/code/datums/mapgen/Cavegens/LavalandGenerator.dm b/code/datums/mapgen/Cavegens/LavalandGenerator.dm index be8294fada66..6f8aa7ed1bbf 100644 --- a/code/datums/mapgen/Cavegens/LavalandGenerator.dm +++ b/code/datums/mapgen/Cavegens/LavalandGenerator.dm @@ -8,8 +8,8 @@ /obj/effect/spawner/random/lavaland_mob/legion = 30, /obj/effect/spawner/random/lavaland_mob/watcher = 40, /mob/living/basic/mining/bileworm = 20, + /mob/living/basic/mining/brimdemon = 20, /mob/living/basic/mining/lobstrosity/lava = 20, - /mob/living/simple_animal/hostile/asteroid/brimdemon = 20, /mob/living/basic/mining/goldgrub = 10, /obj/structure/spawner/lavaland = 2, /obj/structure/spawner/lavaland/goliath = 3, diff --git a/code/datums/martial/_martial.dm b/code/datums/martial/_martial.dm index a6cfbb3d7259..1ffa67397e0a 100644 --- a/code/datums/martial/_martial.dm +++ b/code/datums/martial/_martial.dm @@ -91,7 +91,3 @@ if(help_verb) remove_verb(holder_living, help_verb) return - -///Gets called when a projectile hits the owner. Returning anything other than BULLET_ACT_HIT will stop the projectile from hitting the mob. -/datum/martial_art/proc/on_projectile_hit(mob/living/attacker, obj/projectile/P, def_zone) - return BULLET_ACT_HIT diff --git a/code/datums/martial/sleeping_carp.dm b/code/datums/martial/sleeping_carp.dm index 72e6cd5ada37..2ed42452d406 100644 --- a/code/datums/martial/sleeping_carp.dm +++ b/code/datums/martial/sleeping_carp.dm @@ -15,11 +15,13 @@ return target.add_traits(list(TRAIT_NOGUNS, TRAIT_HARDLY_WOUNDED, TRAIT_NODISMEMBER), SLEEPING_CARP_TRAIT) RegisterSignal(target, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby)) + RegisterSignal(target, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(hit_by_projectile)) target.faction |= FACTION_CARP //:D /datum/martial_art/the_sleeping_carp/on_remove(mob/living/target) target.remove_traits(list(TRAIT_NOGUNS, TRAIT_HARDLY_WOUNDED, TRAIT_NODISMEMBER), SLEEPING_CARP_TRAIT) UnregisterSignal(target, COMSIG_PARENT_ATTACKBY) + UnregisterSignal(target, COMSIG_ATOM_PRE_BULLET_ACT) target.faction -= FACTION_CARP //:( . = ..() @@ -113,6 +115,8 @@ return ..() /datum/martial_art/the_sleeping_carp/proc/can_deflect(mob/living/carp_user) + if(!can_use(carp_user) || !carp_user.throw_mode) + return FALSE if(carp_user.incapacitated(IGNORE_GRAB)) //NO STUN return FALSE if(!(carp_user.mobility_flags & MOBILITY_USE)) //NO UNABLE TO USE @@ -124,17 +128,20 @@ return FALSE return TRUE -/datum/martial_art/the_sleeping_carp/on_projectile_hit(mob/living/carp_user, obj/projectile/P, def_zone) - . = ..() +/datum/martial_art/the_sleeping_carp/proc/hit_by_projectile(mob/living/carp_user, obj/projectile/hitting_projectile, def_zone) + SIGNAL_HANDLER + if(!can_deflect(carp_user)) - return BULLET_ACT_HIT - if(carp_user.throw_mode) - carp_user.visible_message(span_danger("[carp_user] effortlessly swats the projectile aside! They can block bullets with their bare hands!"), span_userdanger("You deflect the projectile!")) - playsound(get_turf(carp_user), pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, TRUE) - P.firer = carp_user - P.set_angle(rand(0, 360))//SHING - return BULLET_ACT_FORCE_PIERCE - return BULLET_ACT_HIT + return NONE + + carp_user.visible_message( + span_danger("[carp_user] effortlessly swats [hitting_projectile] aside! [carp_user.p_They()] can block bullets with [carp_user.p_their()] bare hands!"), + span_userdanger("You deflect [hitting_projectile]!"), + ) + playsound(carp_user, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, TRUE) + hitting_projectile.firer = carp_user + hitting_projectile.set_angle(rand(0, 360))//SHING + return COMPONENT_BULLET_PIERCED ///Signal from getting attacked with an item, for a special interaction with touch spells /datum/martial_art/the_sleeping_carp/proc/on_attackby(mob/living/carp_user, obj/item/attack_weapon, mob/attacker, params) diff --git a/code/datums/memory/_memory.dm b/code/datums/memory/_memory.dm index ae49549a68d9..76cce94bcafa 100644 --- a/code/datums/memory/_memory.dm +++ b/code/datums/memory/_memory.dm @@ -230,6 +230,7 @@ var/static/list/something_pool = list( /mob/living/basic/bat, /mob/living/basic/bear, + /mob/living/basic/blob_minion/blobbernaut, /mob/living/basic/butterfly, /mob/living/basic/carp, /mob/living/basic/carp/magic, @@ -237,9 +238,8 @@ /mob/living/basic/cow, /mob/living/basic/cow/wisdom, /mob/living/basic/crab, - /mob/living/basic/spider/giant, - /mob/living/basic/spider/giant/hunter, - /mob/living/basic/mining/goliath, + /mob/living/basic/goat, + /mob/living/basic/gorilla, /mob/living/basic/headslug, /mob/living/basic/killer_tomato, /mob/living/basic/lizard, @@ -248,6 +248,7 @@ /mob/living/basic/morph, /mob/living/basic/mouse, /mob/living/basic/mushroom, + /mob/living/basic/parrot, /mob/living/basic/pet/dog/breaddog, /mob/living/basic/pet/dog/corgi, /mob/living/basic/pet/dog/pug, @@ -257,11 +258,7 @@ /mob/living/basic/statue, /mob/living/basic/stickman, /mob/living/basic/stickman/dog, - /mob/living/simple_animal/hostile/blob/blobbernaut/independent, - /mob/living/simple_animal/hostile/gorilla, /mob/living/simple_animal/hostile/megafauna/dragon/lesser, - /mob/living/simple_animal/hostile/retaliate/goat, - /mob/living/simple_animal/parrot, /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/cat/cak, /obj/item/food/sausage/american, diff --git a/code/datums/mind/initialization.dm b/code/datums/mind/initialization.dm index 12a5dddb229c..eb622cc5af54 100644 --- a/code/datums/mind/initialization.dm +++ b/code/datums/mind/initialization.dm @@ -11,6 +11,7 @@ mind.set_current(src) // There's nowhere else to set this up, mind code makes me depressed mind.antag_hud = add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/antagonist_hud, "combo_hud", mind) + SEND_SIGNAL(src, COMSIG_MOB_MIND_INITIALIZED, mind) /mob/living/carbon/mind_initialize() ..() diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index ef4cec4ea467..7603e275f8c3 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -143,6 +143,9 @@ mood_change = -4 timeout = 2 MINUTES +/datum/mood_event/healsbadman/long_term + timeout = 10 MINUTES + /datum/mood_event/jittery description = "I'm nervous and on edge and I can't stand still!!" mood_change = -2 diff --git a/code/datums/mutations/antenna.dm b/code/datums/mutations/antenna.dm index 4186ade577f6..ca3f0820ea01 100644 --- a/code/datums/mutations/antenna.dm +++ b/code/datums/mutations/antenna.dm @@ -91,32 +91,11 @@ // chance to alert the read-ee to_chat(cast_on, span_danger("You feel something foreign enter your mind.")) - var/list/recent_speech = list() - var/list/say_log = list() - var/log_source = cast_on.logging - //this whole loop puts the read-ee's say logs into say_log in an easy to access way - 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 = reverse_range(reversed.Copy()) - break - - for(var/spoken_memory in say_log) - //up to 3 random lines of speech, favoring more recent speech - if(length(recent_speech) >= 3) - break - if(prob(50)) - continue - // log messages with tags like telepathy are displayed like "(Telepathy to Ckey/(target)) "greetings""" - // by splitting the text by using a " delimiter, we can grab JUST the greetings part - recent_speech[spoken_memory] = splittext(say_log[spoken_memory], "\"", 1, 0, TRUE)[3] - + var/list/recent_speech = cast_on.copy_recent_speech(copy_amount = 3, line_chance = 50) if(length(recent_speech)) to_chat(owner, span_boldnotice("You catch some drifting memories of their past conversations...")) for(var/spoken_memory in recent_speech) - to_chat(owner, span_notice("[recent_speech[spoken_memory]]")) + to_chat(owner, span_notice("[spoken_memory]")) if(iscarbon(cast_on)) var/mob/living/carbon/carbon_cast_on = cast_on diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm index e1784f009c8f..77bd426ed299 100644 --- a/code/datums/mutations/hulk.dm +++ b/code/datums/mutations/hulk.dm @@ -63,13 +63,19 @@ *arg1 is the arm to evaluate damage of and possibly break. */ /datum/mutation/human/hulk/proc/break_an_arm(obj/item/bodypart/arm) + var/severity switch(arm.brute_dam) if(45 to 50) - arm.force_wound_upwards(/datum/wound/blunt/critical) + severity = WOUND_SEVERITY_CRITICAL if(41 to 45) - arm.force_wound_upwards(/datum/wound/blunt/severe) + severity = WOUND_SEVERITY_SEVERE if(35 to 41) - arm.force_wound_upwards(/datum/wound/blunt/moderate) + severity = WOUND_SEVERITY_MODERATE + + if (isnull(severity)) + return + + owner.cause_wound_of_type_and_severity(WOUND_BLUNT, arm, severity, wound_source = "hulk smashing") /datum/mutation/human/hulk/on_life(seconds_per_tick, times_fired) if(owner.health < owner.crit_threshold) diff --git a/code/datums/mutations/void_magnet.dm b/code/datums/mutations/void_magnet.dm index 56b22d664a85..d6636b0b6306 100644 --- a/code/datums/mutations/void_magnet.dm +++ b/code/datums/mutations/void_magnet.dm @@ -60,7 +60,7 @@ /datum/action/cooldown/spell/void/cursed/proc/on_life(mob/living/source, seconds_per_tick, times_fired) SIGNAL_HANDLER - if(!isliving(source) || IS_IN_STASIS(source) || source.stat == DEAD || source.notransform) + if(!isliving(source) || IS_IN_STASIS(source) || source.stat == DEAD || HAS_TRAIT(source, TRAIT_NO_TRANSFORM)) return if(!is_valid_target(source)) diff --git a/code/datums/proximity_monitor/fields/timestop.dm b/code/datums/proximity_monitor/fields/timestop.dm index 7fad290fc74d..86ea41aee013 100644 --- a/code/datums/proximity_monitor/fields/timestop.dm +++ b/code/datums/proximity_monitor/fields/timestop.dm @@ -36,7 +36,7 @@ for(var/mob/living/to_check in GLOB.player_list) if(HAS_TRAIT(to_check, TRAIT_TIME_STOP_IMMUNE)) immune[to_check] = TRUE - for(var/mob/living/simple_animal/hostile/guardian/stand in GLOB.parasites) + for(var/mob/living/basic/guardian/stand in GLOB.parasites) if(stand.summoner && HAS_TRAIT(stand.summoner, TRAIT_TIME_STOP_IMMUNE)) //It would only make sense that a person's stand would also be immune. immune[stand] = TRUE if(start) diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm index d6483e7e0c34..b0d73d36a919 100644 --- a/code/datums/ruins/lavaland.dm +++ b/code/datums/ruins/lavaland.dm @@ -268,3 +268,28 @@ cost = 5 suffix = "lavaland_surface_bileworm_nest.dmm" allow_duplicates = FALSE + +/datum/map_template/ruin/lavaland/lava_phonebooth + name = "Phonebooth" + id = "lava_phonebooth" + description = "A venture by nanotrasen to help popularize the use of holopads. This one somehow made its way here." + suffix = "lavaland_surface_phonebooth.dmm" + allow_duplicates = FALSE + cost = 5 + +/datum/map_template/ruin/lavaland/battle_site + name = "Battle Site" + id = "battle_site" + description = "The long past site of a battle between beast and humanoids. The victor is unknown, but the losers are clear." + suffix = "lavaland_battle_site.dmm" + allow_duplicates = TRUE + cost = 3 + +/datum/map_template/ruin/lavaland/mook_village + name = "Mook Village" + id = "mook_village" + description = "A village hosting a community of friendly mooks!" + suffix = "lavaland_surface_mookvillage.dmm" + allow_duplicates = FALSE + cost = 5 + always_place = TRUE diff --git a/code/datums/saymode.dm b/code/datums/saymode.dm index 78ec5e6af38a..60d3bd594e2d 100644 --- a/code/datums/saymode.dm +++ b/code/datums/saymode.dm @@ -77,8 +77,8 @@ /datum/saymode/binary/handle_message(mob/living/user, message, datum/language/language) if(isdrone(user)) - var/mob/living/simple_animal/drone/D = user - D.drone_chat(message) + var/mob/living/basic/drone/drone_user = user + drone_user.drone_chat(message) return FALSE if(user.binarycheck()) user.robot_talk(message) diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm index a328506bb959..0eb7668c6d8e 100644 --- a/code/datums/station_traits/negative_traits.dm +++ b/code/datums/station_traits/negative_traits.dm @@ -381,7 +381,7 @@ if(istype(current_thing, /obj/machinery/vending) && prob(45)) var/obj/machinery/vending/vendor_to_trash = current_thing if(prob(50)) - vendor_to_trash.tilt(get_turf(vendor_to_trash)) + vendor_to_trash.tilt(get_turf(vendor_to_trash), 0) // crit effects can do some real weird shit, lets disable it if(prob(50)) vendor_to_trash.take_damage(150) diff --git a/code/datums/station_traits/neutral_traits.dm b/code/datums/station_traits/neutral_traits.dm index 494008bb8f79..2c86b6188417 100644 --- a/code/datums/station_traits/neutral_traits.dm +++ b/code/datums/station_traits/neutral_traits.dm @@ -148,7 +148,7 @@ show_in_report = FALSE // Selective attention test. Did you spot the gorilla? /// The gorilla we created, we only hold this ref until the round starts. - var/mob/living/simple_animal/hostile/gorilla/cargo_domestic/cargorilla + var/mob/living/basic/gorilla/cargorilla/cargorilla /datum/station_trait/cargorilla/New() . = ..() @@ -158,8 +158,8 @@ /datum/station_trait/cargorilla/proc/replace_cargo(datum/source) SIGNAL_HANDLER - var/mob/living/simple_animal/sloth/cargo_sloth = GLOB.cargo_sloth - if(!cargo_sloth) + var/mob/living/basic/sloth/cargo_sloth = GLOB.cargo_sloth + if(isnull(cargo_sloth)) return cargorilla = new(cargo_sloth.loc) @@ -189,7 +189,7 @@ cargorilla = null /// Get us a ghost for the gorilla. -/datum/station_trait/cargorilla/proc/get_ghost_for_gorilla(mob/living/simple_animal/hostile/gorilla/cargo_domestic/gorilla) +/datum/station_trait/cargorilla/proc/get_ghost_for_gorilla(mob/living/basic/gorilla/cargorilla/gorilla) if(QDELETED(gorilla)) return diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm index 72dece18b8bd..4dea910b43a6 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs.dm @@ -294,10 +294,10 @@ /datum/status_effect/hippocratic_oath/proc/consume_owner() owner.visible_message(span_notice("[owner]'s soul is absorbed into the rod, relieving the previous snake of its duty.")) var/list/chems = list(/datum/reagent/medicine/sal_acid, /datum/reagent/medicine/c2/convermol, /datum/reagent/medicine/oxandrolone) - var/mob/living/simple_animal/hostile/retaliate/snake/healSnake = new(owner.loc, pick(chems)) - healSnake.name = "Asclepius's Snake" - healSnake.real_name = "Asclepius's Snake" - healSnake.desc = "A mystical snake previously trapped upon the Rod of Asclepius, now freed of its burden. Unlike the average snake, its bites contain chemicals with minor healing properties." + var/mob/living/basic/snake/spawned = new(owner.loc, pick(chems)) + spawned.name = "Asclepius's Snake" + spawned.real_name = "Asclepius's Snake" + spawned.desc = "A mystical snake previously trapped upon the Rod of Asclepius, now freed of its burden. Unlike the average snake, its bites contain chemicals with minor healing properties." new /obj/effect/decal/cleanable/ash(owner.loc) new /obj/item/rod_of_asclepius(owner.loc) owner.investigate_log("has been consumed by the Rod of Asclepius.", INVESTIGATE_DEATHS) diff --git a/code/datums/status_effects/debuffs/choke.dm b/code/datums/status_effects/debuffs/choke.dm index 16c64376cb85..d2e502d75de7 100644 --- a/code/datums/status_effects/debuffs/choke.dm +++ b/code/datums/status_effects/debuffs/choke.dm @@ -215,8 +215,8 @@ if(iscarbon(victim)) var/mob/living/carbon/carbon_victim = victim var/obj/item/bodypart/chest = carbon_victim.get_bodypart(BODY_ZONE_CHEST) - if(chest) - chest.force_wound_upwards(/datum/wound/blunt/severe) + carbon_victim.cause_wound_of_type_and_severity(WOUND_BLUNT, chest, WOUND_SEVERITY_SEVERE, wound_source = "human force to the chest") + playsound(owner, 'sound/creatures/crack_vomit.ogg', 120, extrarange = 5, falloff_exponent = 4) vomit_up() diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm index a1708a69d893..d4b4f789f0e5 100644 --- a/code/datums/status_effects/debuffs/debuffs.dm +++ b/code/datums/status_effects/debuffs/debuffs.dm @@ -397,7 +397,9 @@ var/still_bleeding = FALSE for(var/datum/wound/bleeding_thing as anything in throat.wounds) - if(bleeding_thing.wound_type == WOUND_SLASH && bleeding_thing.severity > WOUND_SEVERITY_MODERATE) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[bleeding_thing.type] + + if(pregen_data.wounding_types_valid(list(WOUND_SLASH)) && bleeding_thing.severity > WOUND_SEVERITY_MODERATE && bleeding_thing.blood_flow > 0) still_bleeding = TRUE break if(!still_bleeding) diff --git a/code/datums/status_effects/debuffs/fire_stacks.dm b/code/datums/status_effects/debuffs/fire_stacks.dm index 369c4dc44e5d..9ffdd73a741f 100644 --- a/code/datums/status_effects/debuffs/fire_stacks.dm +++ b/code/datums/status_effects/debuffs/fire_stacks.dm @@ -256,10 +256,10 @@ if(firelight_type) firelight_ref = WEAKREF(new firelight_type(owner)) - SEND_SIGNAL(owner, COMSIG_LIVING_IGNITED, owner) cache_stacks() update_overlay() update_particles() + SEND_SIGNAL(owner, COMSIG_LIVING_IGNITED, owner) return TRUE /** diff --git a/code/datums/status_effects/wound_effects.dm b/code/datums/status_effects/wound_effects.dm index d0bdbd536024..ed0b7b555e46 100644 --- a/code/datums/status_effects/wound_effects.dm +++ b/code/datums/status_effects/wound_effects.dm @@ -54,11 +54,11 @@ right = C.get_bodypart(BODY_ZONE_R_LEG) update_limp() RegisterSignal(C, COMSIG_MOVABLE_MOVED, PROC_REF(check_step)) - RegisterSignals(C, list(COMSIG_CARBON_GAIN_WOUND, COMSIG_CARBON_LOSE_WOUND, COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB), PROC_REF(update_limp)) + RegisterSignals(C, list(COMSIG_CARBON_GAIN_WOUND, COMSIG_CARBON_POST_LOSE_WOUND, COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB), PROC_REF(update_limp)) return TRUE /datum/status_effect/limp/on_remove() - UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_CARBON_GAIN_WOUND, COMSIG_CARBON_LOSE_WOUND, COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB)) + UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_CARBON_GAIN_WOUND, COMSIG_CARBON_POST_LOSE_WOUND, COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB)) /atom/movable/screen/alert/status_effect/limp name = "Limping" @@ -165,51 +165,41 @@ if(W == linked_wound) qdel(src) +/datum/status_effect/wound/nextmove_modifier() + var/mob/living/carbon/C = owner -// bones -/datum/status_effect/wound/blunt - -/datum/status_effect/wound/blunt/on_apply() - . = ..() - RegisterSignal(owner, COMSIG_MOB_SWAP_HANDS, PROC_REF(on_swap_hands)) - on_swap_hands() - -/datum/status_effect/wound/blunt/on_remove() - . = ..() - UnregisterSignal(owner, COMSIG_MOB_SWAP_HANDS) - var/mob/living/carbon/wound_owner = owner - wound_owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/blunt_wound) - -/datum/status_effect/wound/blunt/proc/on_swap_hands() - SIGNAL_HANDLER + if(C.get_active_hand() == linked_limb) + return linked_wound.get_action_delay_mult() - var/mob/living/carbon/wound_owner = owner - if(wound_owner.get_active_hand() == linked_limb) - wound_owner.add_actionspeed_modifier(/datum/actionspeed_modifier/blunt_wound, (linked_wound.interaction_efficiency_penalty - 1)) - else - wound_owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/blunt_wound) + return ..() -/datum/status_effect/wound/blunt/nextmove_modifier() +/datum/status_effect/wound/nextmove_adjust() var/mob/living/carbon/C = owner if(C.get_active_hand() == linked_limb) - return linked_wound.interaction_efficiency_penalty + return linked_wound.get_action_delay_increment() + + return ..() - return 1 + +// bones +/datum/status_effect/wound/blunt/bone // blunt -/datum/status_effect/wound/blunt/moderate +/datum/status_effect/wound/blunt/bone/moderate id = "disjoint" -/datum/status_effect/wound/blunt/severe +/datum/status_effect/wound/blunt/bone/severe id = "hairline" -/datum/status_effect/wound/blunt/critical +/datum/status_effect/wound/blunt/bone/critical id = "compound" + // slash -/datum/status_effect/wound/slash/moderate + +/datum/status_effect/wound/slash/flesh/moderate id = "abrasion" -/datum/status_effect/wound/slash/severe +/datum/status_effect/wound/slash/flesh/severe id = "laceration" -/datum/status_effect/wound/slash/critical +/datum/status_effect/wound/slash/flesh/critical id = "avulsion" // pierce /datum/status_effect/wound/pierce/moderate @@ -219,9 +209,9 @@ /datum/status_effect/wound/pierce/critical id = "rupture" // burns -/datum/status_effect/wound/burn/moderate +/datum/status_effect/wound/burn/flesh/moderate id = "seconddeg" -/datum/status_effect/wound/burn/severe +/datum/status_effect/wound/burn/flesh/severe id = "thirddeg" -/datum/status_effect/wound/burn/critical +/datum/status_effect/wound/burn/flesh/critical id = "fourthdeg" diff --git a/code/datums/storage/storage.dm b/code/datums/storage/storage.dm index ce0175849090..72a326a2eebb 100644 --- a/code/datums/storage/storage.dm +++ b/code/datums/storage/storage.dm @@ -404,6 +404,7 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches) to_insert.forceMove(resolve_location) item_insertion_feedback(user, to_insert, override) resolve_location.update_appearance() + SEND_SIGNAL(to_insert, COMSIG_ITEM_STORED) return TRUE /** diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm index 1414fb55a441..e289110a6814 100644 --- a/code/datums/wires/airlock.dm +++ b/code/datums/wires/airlock.dm @@ -69,14 +69,14 @@ /datum/wires/airlock/interactable(mob/user) if(!..()) return FALSE - var/obj/machinery/door/airlock/A = holder - if(!issilicon(user) && A.isElectrified()) + var/obj/machinery/door/airlock/airlock = holder + if(!issilicon(user) && !isdrone(user) && airlock.isElectrified()) var/mob/living/carbon/carbon_user = user if (!istype(carbon_user) || carbon_user.should_electrocute(src)) return FALSE - if(A.is_secure()) + if(airlock.is_secure()) return FALSE - if(A.panel_open) + if(airlock.panel_open) return TRUE /datum/wires/airlock/get_status() diff --git a/code/datums/wounds/_wound_static_data.dm b/code/datums/wounds/_wound_static_data.dm new file mode 100644 index 000000000000..7a59ea57413a --- /dev/null +++ b/code/datums/wounds/_wound_static_data.dm @@ -0,0 +1,199 @@ +// This datum is merely a singleton instance that allows for custom "can be applied" behaviors without instantiating a wound instance. +// For example: You can make a pregen_data subtype for your wound that overrides can_be_applied_to to only apply to specifically slimeperson limbs. +// Without this, youre stuck with very static initial variables. + +/// A singleton datum that holds pre-gen and static data about a wound. Each wound datum should have a corresponding wound_pregen_data. +/datum/wound_pregen_data + /// The typepath of the wound we will be handling and storing data of. NECESSARY IF THIS IS A NON-ABSTRACT TYPE! + var/datum/wound/wound_path_to_generate + + /// Will this be instantiated? + var/abstract = FALSE + + /// If true, our wound can be selected in ordinary wound rolling. If this is set to false, our wound can only be directly instantiated by use of specific typepath. + var/can_be_randomly_generated = TRUE + + /// A list of biostates a limb must have to receive our wound, in wounds.dm. + var/required_limb_biostate + /// If false, we will check if the limb has all of our required biostates instead of just any. + var/require_any_biostate = FALSE + + /// If false, we will iterate through wounds on a given limb, and if any match our type, we wont add our wound. + var/duplicates_allowed = FALSE + + /// If we require BIO_BLOODED, we will not add our wound if this is true and the limb cannot bleed. + var/ignore_cannot_bleed = TRUE // a lot of bleed wounds should still be applied for purposes of mangling flesh + + /// A list of bodyzones we are applicable to. + var/list/viable_zones = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + /// The types of attack that can generate this wound. E.g. WOUND_SLASH = A sharp attack can cause this, WOUND_BLUNT = an attack with no sharpness/an attack with sharpness against a limb with mangled exterior can cause this. + var/list/required_wounding_types + /// If true, this wound can only be generated by all [required_wounding_types] at once, not just any. + var/match_all_wounding_types = FALSE + + /// The weight that will be used if, by the end of wound selection, there are multiple valid wounds. This will be inserted into pick_weight, so use integers. + var/weight = WOUND_DEFAULT_WEIGHT + + /// The minimum injury roll a attack must get to generate us. Affected by our wound's threshold_penalty and series_threshold_penalty, as well as the attack's wound_bonus. See check_wounding_mods(). + var/threshold_minimum + + /// The series of wounds this is in. See wounds.dm (the defines file) for a more detailed explanation - but tldr is that no 2 wounds of the same series can be on a limb. + var/wound_series + + /// If true, we will attempt to, during a random wound roll, overpower and remove other wound typepaths from the possible wounds list using [competition_mode] and [overpower_wounds_of_even_severity]. + var/compete_for_wounding = TRUE + /// The competition mode with which we will remove other wounds from a possible wound roll assuming [compete_for_wounding] is TRUE. See wounds.dm, the defines file, for more information on what these do. + var/competition_mode = WOUND_COMPETITION_OVERPOWER_LESSERS + /// If this and [compete_for_wounding] is true, we will remove wounds of an even severity to us during a random wound roll. + var/overpower_wounds_of_even_severity = FALSE + + /// A list of BIO_ defines that will be iterated over in order to determine the scar file our wound will generate. + /// Use generate_scar_priorities to create a custom list. + var/list/scar_priorities + +/datum/wound_pregen_data/New() + . = ..() + + if (!abstract) + if (required_limb_biostate == null) + stack_trace("required_limb_biostate null - please set it! occured on: [src.type]") + if (wound_path_to_generate == null) + stack_trace("wound_path_to_generate null - please set it! occured on: [src.type]") + + scar_priorities = generate_scar_priorities() + +/// Should return a list of BIO_ biostate priorities, in order. See [scar_priorities] for further documentation. +/datum/wound_pregen_data/proc/generate_scar_priorities() + RETURN_TYPE(/list) + + var/list/priorities = list( + "[BIO_FLESH]", + "[BIO_BONE]", + ) + + return priorities + +// this proc is the primary reason this datum exists - a singleton instance so we can always run this proc even without the wound existing +/** + * Args: + * * obj/item/bodypart/limb: The limb we are considering. + * * list/suggested_wounding_types: The wounding types to be checked against the wounding types we require. Defaults to required_wounding_types. + * * datum/wound/old_wound: If we would replace a wound, this would be said wound. Nullable. + * * random_roll = FALSE: If this is in the context of a random wound generation, and this wound wasn't specifically checked. + * + * Returns: + * FALSE if the limb cannot be wounded, if the wounding types dont match ours (via wounding_types_valid()), if we have a higher severity wound already in our series, + * if we have a biotype mismatch, if the limb isnt in a viable zone, or if theres any duplicate wound types. + * TRUE otherwise. + */ +/datum/wound_pregen_data/proc/can_be_applied_to(obj/item/bodypart/limb, list/suggested_wounding_types = required_wounding_types, datum/wound/old_wound, random_roll = FALSE, duplicates_allowed = src.duplicates_allowed, care_about_existing_wounds = TRUE) + SHOULD_BE_PURE(TRUE) + + if (!istype(limb) || !limb.owner) + return FALSE + + if (random_roll && !can_be_randomly_generated) + return FALSE + + if (HAS_TRAIT(limb.owner, TRAIT_NEVER_WOUNDED) || (limb.owner.status_flags & GODMODE)) + return FALSE + + if (!wounding_types_valid(suggested_wounding_types)) + return FALSE + + if (care_about_existing_wounds) + for (var/datum/wound/preexisting_wound as anything in limb.wounds) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[preexisting_wound.type] + if (pregen_data.wound_series == wound_series) + if (preexisting_wound.severity >= initial(wound_path_to_generate.severity)) + return FALSE + + if (!ignore_cannot_bleed && ((required_limb_biostate & BIO_BLOODED) && !limb.can_bleed())) + return FALSE + + if (!biostate_valid(limb.biological_state)) + return FALSE + + if (!(limb.body_zone in viable_zones)) + return FALSE + + // we accept promotions and demotions, but no point in redundancy. This should have already been checked wherever the wound was rolled and applied for (see: bodypart damage code), but we do an extra check + // in case we ever directly add wounds + if (!duplicates_allowed) + for (var/datum/wound/preexisting_wound as anything in limb.wounds) + if (preexisting_wound.type == wound_path_to_generate && (preexisting_wound != old_wound)) + return FALSE + return TRUE + +/// Returns true if we have the given biostates, or any biostate in it if check_for_any is true. False otherwise. +/datum/wound_pregen_data/proc/biostate_valid(biostate) + if (require_any_biostate) + if (!(biostate & required_limb_biostate)) + return FALSE + else if (!((biostate & required_limb_biostate) == required_limb_biostate)) // check for all + return FALSE + + return TRUE + +/** + * A simple getter for [weight], with arguments supplied to allow custom behavior. + * + * Args: + * * obj/item/bodypart/limb: The limb we are contemplating being added to. Nullable. + * * woundtype: The woundtype of the assumed attack that would generate us. Nullable. + * * damage: The raw damage that would cause us. Nullable. + * * attack_direction: The direction of the attack that'd cause us. Nullable. + * * damage_source: The entity that would cause us. Nullable. + * + * Returns: + * Our weight. + */ +/datum/wound_pregen_data/proc/get_weight(obj/item/bodypart/limb, woundtype, damage, attack_direction, damage_source) + return weight + +/// Returns TRUE if we use WOUND_ALL, or we require all types and have all/if we require any and have any, FALSE otherwise. +/datum/wound_pregen_data/proc/wounding_types_valid(list/suggested_wounding_types) + if (WOUND_ALL in required_wounding_types) + return TRUE + if (!length(suggested_wounding_types)) + return FALSE + + for (var/iter_wounding_type as anything in suggested_wounding_types) + if (!(iter_wounding_type in required_wounding_types)) + if (match_all_wounding_types) + return FALSE + else + if (!match_all_wounding_types) + return TRUE + + return match_all_wounding_types // if we get here, we've matched everything + +/** + * A simple getter for [threshold_minimum], with arguments supplied to allow custom behavior. + * + * Args: + * * obj/item/bodypart/part: The limb we are contemplating being added to. + * * attack_direction: The direction of the attack that'd generate us. Nullable. + * * damage_source: The source of the damage that'd cause us. Nullable. + */ +/datum/wound_pregen_data/proc/get_threshold_for(obj/item/bodypart/part, attack_direction, damage_source) + return threshold_minimum + +/// Returns a new instance of our wound datum. +/datum/wound_pregen_data/proc/generate_instance(obj/item/bodypart/limb, ...) + RETURN_TYPE(/datum/wound) + + return new wound_path_to_generate + +/datum/wound_pregen_data/Destroy(force, ...) + var/error_message = "[src], a singleton wound pregen data instance, was destroyed! This should not happen!" + if (force) + error_message += " NOTE: This Destroy() was called with force == TRUE. This instance will be deleted and replaced with a new one." + stack_trace(error_message) + + if (!force) + return QDEL_HINT_LETMELIVE + + . = ..() + + GLOB.all_wound_pregen_data[wound_path_to_generate] = new src.type //recover diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm index 6f64a559ae1e..65669db9fd14 100644 --- a/code/datums/wounds/_wounds.dm +++ b/code/datums/wounds/_wounds.dm @@ -14,6 +14,13 @@ deciding what specific wound will be applied. I'd like to have a few different types of wounds for at least some of the choices, but I'm just doing rough generals for now. Expect polishing */ +#define WOUND_CRITICAL_BLUNT_DISMEMBER_BONUS 15 + +// Applied into wounds when they're scanned with the wound analyzer, halves time to treat them manually. +#define TRAIT_WOUND_SCANNED "wound_scanned" +// I dunno lol +#define ANALYZER_TRAIT "analyzer_trait" + /datum/wound /// What it's named var/name = "Wound" @@ -24,51 +31,61 @@ /// What the limb looks like on a cursory examine var/examine_desc = "is badly hurt" + /// Simple description, shortened for clarity if defined. Otherwise just takes the normal desc in the analyzer proc. + var/simple_desc + /// Simple analyzer's wound description, which focuses less on the clinical aspect of the wound and more on easily readable treatment instructions. + var/simple_treat_text = "Go to medbay idiot" + /// Improvised remedies indicated by the first aid analyzer only. + var/homemade_treat_text = "Remember to drink lots of water!" + + + /// If this wound can generate a scar. + var/can_scar = TRUE + + /// The default file we take our scar descriptions from, if we fail to get the ideal file. + var/default_scar_file + /// needed for "your arm has a compound fracture" vs "your arm has some third degree burns" var/a_or_from = "a" /// The visible message when this happens var/occur_text = "" /// This sound will be played upon the wound being applied var/sound_effect + /// The volume of [sound_effect] + var/sound_volume = 70 /// Either WOUND_SEVERITY_TRIVIAL (meme wounds like stubbed toe), WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, or WOUND_SEVERITY_CRITICAL (or maybe WOUND_SEVERITY_LOSS) var/severity = WOUND_SEVERITY_MODERATE - /// The list of wounds it belongs in, WOUND_LIST_BLUNT, WOUND_LIST_SLASH, or WOUND_LIST_BURN - var/wound_type - /// What body zones can we affect - var/list/viable_zones = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) /// Who owns the body part that we're wounding var/mob/living/carbon/victim = null - /// The bodypart we're parented to + /// The bodypart we're parented to. Not guaranteed to be non-null, especially after/during removal or if we haven't been applied var/obj/item/bodypart/limb = null /// Specific items such as bandages or sutures that can try directly treating this wound var/list/treatable_by /// Specific items such as bandages or sutures that can try directly treating this wound only if the user has the victim in an aggressive grab or higher var/list/treatable_by_grabbed - /// Tools with the specified tool flag will also be able to try directly treating this wound - var/treatable_tool + /// Any tools with any of the flags in this list will be usable to try directly treating this wound + var/list/treatable_tools /// How long it will take to treat this wound with a standard effective tool, assuming it doesn't need surgery var/base_treat_time = 5 SECONDS /// Using this limb in a do_after interaction will multiply the length by this duration (arms) var/interaction_efficiency_penalty = 1 /// Incoming damage on this limb will be multiplied by this, to simulate tenderness and vulnerability (mostly burns). - var/damage_mulitplier_penalty = 1 + var/damage_multiplier_penalty = 1 /// If set and this wound is applied to a leg, we take this many deciseconds extra per step on this leg var/limp_slowdown /// If this wound has a limp_slowdown and is applied to a leg, it has this chance to limp each step var/limp_chance /// How much we're contributing to this limb's bleed_rate var/blood_flow - /// Essentially, keeps track of whether or not this wound is capable of bleeding (in case the owner has the NOBLOOD species trait) - var/no_bleeding = FALSE - /// The minimum we need to roll on [/obj/item/bodypart/proc/check_wounding] to begin suffering this wound, see check_wounding_mods() for more - var/threshold_minimum /// How much having this wound will add to all future check_wounding() rolls on this limb, to allow progression to worse injuries with repeated damage var/threshold_penalty + /// How much having this wound will add to all future check_wounding() rolls on this limb, but only for wounds of its own series + var/series_threshold_penalty = 0 /// If we need to process each life tick var/processes = FALSE @@ -79,27 +96,83 @@ var/status_effect_type /// If we're operating on this wound and it gets healed, we'll nix the surgery too var/datum/surgery/attached_surgery - /// if you're a lazy git and just throw them in cryo, the wound will go away after accumulating severity * 25 power + /// if you're a lazy git and just throw them in cryo, the wound will go away after accumulating severity * [base_xadone_progress_to_qdel] power var/cryo_progress + /// The base amount of [cryo_progress] required to have ourselves fully healed by cryo. Multiplied against severity. + var/base_xadone_progress_to_qdel = 33 + /// What kind of scars this wound will create description wise once healed var/scar_keyword = "generic" /// If we've already tried scarring while removing (remove_wound can be called twice in a del chain, let's be nice to our code yeah?) TODO: make this cleaner var/already_scarred = FALSE - /// If we forced this wound through badmin smite, we won't count it towards the round totals - var/from_smite + /// The source of how we got the wound, typically a weapon. + var/wound_source /// What flags apply to this wound - var/wound_flags = (FLESH_WOUND | BONE_WOUND | ACCEPTS_GAUZE) + var/wound_flags = (ACCEPTS_GAUZE) + + /// The unique ID of our wound for use with [actionspeed_mod]. Defaults to REF(src). + var/unique_id + /// The actionspeed modifier we will use in case we are on the arms and have a interaction penalty. Qdelled on destroy. + var/datum/actionspeed_modifier/wound_interaction_inefficiency/actionspeed_mod + +/datum/wound/New() + . = ..() + + unique_id = generate_unique_id() + update_actionspeed_modifier() /datum/wound/Destroy() - if(attached_surgery) - QDEL_NULL(attached_surgery) - remove_wound() - set_limb(null) - victim = null + QDEL_NULL(attached_surgery) + if (limb) + remove_wound() + + QDEL_NULL(actionspeed_mod) + return ..() +/// If we should have an actionspeed_mod, ensures we do and updates its slowdown. Otherwise, ensures we dont have one +/// by qdeleting any existing modifier. +/datum/wound/proc/update_actionspeed_modifier() + if (should_have_actionspeed_modifier()) + if (!actionspeed_mod) + generate_actionspeed_modifier() + actionspeed_mod.multiplicative_slowdown = get_effective_actionspeed_modifier() + victim?.update_actionspeed() + else + remove_actionspeed_modifier() + +/// Returns TRUE if we have an interaction_efficiency_penalty, and if we are on the arms, FALSE otherwise. +/datum/wound/proc/should_have_actionspeed_modifier() + return (limb && victim && (limb.body_zone == BODY_ZONE_L_ARM || limb.body_zone == BODY_ZONE_R_ARM) && interaction_efficiency_penalty != 0) + +/// If we have no actionspeed_mod, generates a new one with our unique ID, sets actionspeed_mod to it, then returns it. +/datum/wound/proc/generate_actionspeed_modifier() + RETURN_TYPE(/datum/actionspeed_modifier) + + if (actionspeed_mod) + return actionspeed_mod + + var/datum/actionspeed_modifier/wound_interaction_inefficiency/new_modifier = new /datum/actionspeed_modifier/wound_interaction_inefficiency(unique_id, src) + new_modifier.multiplicative_slowdown = get_effective_actionspeed_modifier() + victim?.add_actionspeed_modifier(new_modifier) + + actionspeed_mod = new_modifier + return actionspeed_mod + +/// If we have an actionspeed_mod, qdels it and sets our ref of it to null. +/datum/wound/proc/remove_actionspeed_modifier() + if (!actionspeed_mod) + return + + victim?.remove_actionspeed_modifier(actionspeed_mod) + QDEL_NULL(actionspeed_mod) + +/// Generates the ID we use for [unique_id], which is also set as our actionspeed mod's ID +/datum/wound/proc/generate_unique_id() + return REF(src) // unique, cannot change, a perfect id + /** * apply_wound() is used once a wound type is instantiated to assign it to a bodypart, and actually come into play. * @@ -111,31 +184,16 @@ * * smited- If this is a smite, we don't care about this wound for stat tracking purposes (not yet implemented) * * attack_direction: For bloodsplatters, if relevant */ -/datum/wound/proc/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE, attack_direction = null) - if(!istype(L) || !L.owner || !(L.body_zone in viable_zones) || (!IS_ORGANIC_LIMB(L) && !HAS_TRAIT(L.owner, TRAIT_ROBOT_CAN_BLEED)) || HAS_TRAIT(L.owner, TRAIT_NEVER_WOUNDED)) - qdel(src) - return +/datum/wound/proc/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE, attack_direction = null, wound_source = "Unknown") - // Checks for biological state, to ensure only valid wounds are applied on the limb - if(((wound_flags & BONE_WOUND) && !(L.biological_state & BIO_BONE)) || ((wound_flags & FLESH_WOUND) && !(L.biological_state & BIO_FLESH))) + if (!can_be_applied_to(L, old_wound)) qdel(src) - return - - // we accept promotions and demotions, but no point in redundancy. This should have already been checked wherever the wound was rolled and applied for (see: bodypart damage code), but we do an extra check - // in case we ever directly add wounds - for(var/i in L.wounds) - var/datum/wound/preexisting_wound = i - if((preexisting_wound.type == type) && (preexisting_wound != old_wound)) - qdel(src) - return + return FALSE set_victim(L.owner) set_limb(L) LAZYADD(victim.all_wounds, src) LAZYADD(limb.wounds, src) - //it's ok to not typecheck, humans are the only ones that deal with wounds - var/mob/living/carbon/human/human_victim = victim - no_bleeding = HAS_TRAIT(human_victim, TRAIT_NOBLOOD) update_descriptions() limb.update_wounds() if(status_effect_type) @@ -155,18 +213,41 @@ var/msg = span_danger("[victim]'s [limb.plaintext_zone] [occur_text]!") var/vis_dist = COMBAT_MESSAGE_RANGE - if(severity != WOUND_SEVERITY_MODERATE) + if(severity > WOUND_SEVERITY_MODERATE) msg = "[msg]" vis_dist = DEFAULT_MESSAGE_RANGE victim.visible_message(msg, span_userdanger("Your [limb.plaintext_zone] [occur_text]!"), vision_distance = vis_dist) if(sound_effect) - playsound(L.owner, sound_effect, 70 + 20 * severity, TRUE) + playsound(L.owner, sound_effect, sound_volume + (20 * severity), TRUE) wound_injury(old_wound, attack_direction = attack_direction) if(!demoted) second_wind() + return TRUE + +/// Returns TRUE if we can be applied to the limb. +/datum/wound/proc/can_be_applied_to(obj/item/bodypart/L, datum/wound/old_wound) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[type] + + // We assume we aren't being randomly applied - we have no reason to believe we are + // And, besides, if we were, you could just as easily check our pregen data rather than run this proc + // Generally speaking this proc is called in apply_wound, which is called when the caller is already confidant in its ability to be applied + return pregen_data.can_be_applied_to(L, old_wound = old_wound) + +/// Returns the zones we can be applied to. +/datum/wound/proc/get_viable_zones() + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[type] + + return pregen_data.viable_zones + +/// Returns the biostate we require to be applied. +/datum/wound/proc/get_required_biostate() + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[type] + + return pregen_data.required_limb_biostate + // Updates descriptive texts for the wound, in case it can get altered for whatever reason /datum/wound/proc/update_descriptions() return @@ -175,13 +256,66 @@ SIGNAL_HANDLER set_victim(null) +/// Setter for [victim]. Should completely transfer signals, attributes, etc. To the new victim - if there is any, as it can be null. /datum/wound/proc/set_victim(new_victim) if(victim) + UnregisterSignal(victim, list(COMSIG_PARENT_QDELETING, COMSIG_MOB_SWAP_HANDS, COMSIG_CARBON_POST_REMOVE_LIMB, COMSIG_CARBON_POST_ATTACH_LIMB)) UnregisterSignal(victim, COMSIG_PARENT_QDELETING) + UnregisterSignal(victim, COMSIG_MOB_SWAP_HANDS) + UnregisterSignal(victim, COMSIG_CARBON_POST_REMOVE_LIMB) + if (actionspeed_mod) + victim.remove_actionspeed_modifier(actionspeed_mod) // no need to qdelete it, just remove it from our victim + remove_wound_from_victim() victim = new_victim if(victim) RegisterSignal(victim, COMSIG_PARENT_QDELETING, PROC_REF(null_victim)) + RegisterSignals(victim, list(COMSIG_MOB_SWAP_HANDS, COMSIG_CARBON_POST_REMOVE_LIMB, COMSIG_CARBON_POST_ATTACH_LIMB), PROC_REF(add_or_remove_actionspeed_mod)) + + if (limb) + start_limping_if_we_should() // the status effect already handles removing itself + add_or_remove_actionspeed_mod() + +/// Proc called to change the variable `limb` and react to the event. +/datum/wound/proc/set_limb(obj/item/bodypart/new_value, replaced = FALSE) + if(limb == new_value) + return FALSE //Limb can either be a reference to something or `null`. Returning the number variable makes it clear no change was made. + . = limb + if(limb) // if we're nulling limb, we're basically detaching from it, so we should remove ourselves in that case + UnregisterSignal(limb, COMSIG_PARENT_QDELETING) + UnregisterSignal(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED)) + LAZYREMOVE(limb.wounds, src) + limb.update_wounds(replaced) + if (disabling) + limb.remove_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src)) + + limb = new_value + + // POST-CHANGE + + if (limb) + RegisterSignal(limb, COMSIG_PARENT_QDELETING, PROC_REF(source_died)) + RegisterSignals(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED), PROC_REF(gauze_state_changed)) + if (disabling) + limb.add_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src)) + + if (victim) + start_limping_if_we_should() // the status effect already handles removing itself + add_or_remove_actionspeed_mod() + + update_inefficiencies() + +/datum/wound/proc/add_or_remove_actionspeed_mod() + update_actionspeed_modifier() + if (actionspeed_mod) + if(victim.get_active_hand() == limb) + victim.add_actionspeed_modifier(actionspeed_mod, TRUE) + else + victim.remove_actionspeed_modifier(actionspeed_mod) + +/datum/wound/proc/start_limping_if_we_should() + if ((limb.body_zone == BODY_ZONE_L_LEG || limb.body_zone == BODY_ZONE_R_LEG) && limp_slowdown > 0 && limp_chance > 0) + victim.apply_status_effect(/datum/status_effect/limp) /datum/wound/proc/source_died() SIGNAL_HANDLER @@ -190,15 +324,26 @@ /// Remove the wound from whatever it's afflicting, and cleans up whateverstatus effects it had or modifiers it had on interaction times. ignore_limb is used for detachments where we only want to forget the victim /datum/wound/proc/remove_wound(ignore_limb, replaced = FALSE) //TODO: have better way to tell if we're getting removed without replacement (full heal) scar stuff + var/old_victim = victim + var/old_limb = limb + set_disabling(FALSE) - if(limb && !already_scarred && !replaced) + if(limb && can_scar && !already_scarred && !replaced) already_scarred = TRUE var/datum/scar/new_scar = new new_scar.generate(limb, src) - remove_wound_from_victim() + + remove_actionspeed_modifier() + + null_victim() // we use the proc here because some behaviors may depend on changing victim to some new value + if(limb && !ignore_limb) - LAZYREMOVE(limb.wounds, src) - limb.update_wounds(replaced) + set_limb(null, replaced) // since we're removing limb's ref to us, we should do the same + // if you want to keep the ref, do it externally, theres no reason for us to remember it + + if (ismob(old_victim)) + var/mob/mob_victim = old_victim + SEND_SIGNAL(mob_victim, COMSIG_CARBON_POST_LOSE_WOUND, src, old_limb, ignore_limb, replaced) /datum/wound/proc/remove_wound_from_victim() if(!victim) @@ -211,17 +356,15 @@ /** * replace_wound() is used when you want to replace the current wound with a new wound, presumably of the same category, just of a different severity (either up or down counts) * - * This proc actually instantiates the new wound based off the specific type path passed, then returns the new instantiated wound datum. - * * Arguments: - * * new_type- The TYPE PATH of the wound you want to replace this, like /datum/wound/slash/severe + * * new_wound- The wound instance you want to replace this * * smited- If this is a smite, we don't care about this wound for stat tracking purposes (not yet implemented) */ -/datum/wound/proc/replace_wound(new_type, smited = FALSE, attack_direction = attack_direction) - var/datum/wound/new_wound = new new_type +/datum/wound/proc/replace_wound(datum/wound/new_wound, smited = FALSE, attack_direction = attack_direction) already_scarred = TRUE + var/obj/item/bodypart/cached_limb = limb // remove_wound() nulls limb so we have to track it locally remove_wound(replaced=TRUE) - new_wound.apply_wound(limb, old_wound = src, smited = smited, attack_direction = attack_direction) + new_wound.apply_wound(cached_limb, old_wound = src, smited = smited, attack_direction = attack_direction, wound_source = wound_source) . = new_wound qdel(src) @@ -229,24 +372,6 @@ /datum/wound/proc/wound_injury(datum/wound/old_wound = null, attack_direction = null) return - -/// Proc called to change the variable `limb` and react to the event. -/datum/wound/proc/set_limb(new_value) - if(limb == new_value) - return FALSE //Limb can either be a reference to something or `null`. Returning the number variable makes it clear no change was made. - . = limb - if(limb) - UnregisterSignal(limb, COMSIG_PARENT_QDELETING) - limb = new_value - RegisterSignal(new_value, COMSIG_PARENT_QDELETING, PROC_REF(source_died)) - if(. && disabling) - var/obj/item/bodypart/old_limb = . - old_limb.remove_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src)) - if(limb) - if(disabling) - limb.add_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src)) - - /// Proc called to change the variable `disabling` and react to the event. /datum/wound/proc/set_disabling(new_value) if(disabling == new_value) @@ -261,6 +386,60 @@ if(limb?.can_be_disabled) limb.update_disabled() +/// Setter for [interaction_efficiency_penalty]. Updates the actionspeed of our actionspeed mod. +/datum/wound/proc/set_interaction_efficiency_penalty(new_value) + var/should_update = (new_value != interaction_efficiency_penalty) + + interaction_efficiency_penalty = new_value + + if (should_update) + update_actionspeed_modifier() + +/// Returns a "adjusted" interaction_efficiency_penalty that will be used for the actionspeed mod. +/datum/wound/proc/get_effective_actionspeed_modifier() + return interaction_efficiency_penalty - 1 + +/// Returns the decisecond multiplier of any click interactions, assuming our limb is being used. +/datum/wound/proc/get_action_delay_mult() + SHOULD_BE_PURE(TRUE) + + return interaction_efficiency_penalty + +/// Returns the decisecond increment of any click interactions, assuming our limb is being used. +/datum/wound/proc/get_action_delay_increment() + SHOULD_BE_PURE(TRUE) + + return 0 + +/// Signal proc for if gauze has been applied or removed from our limb. +/datum/wound/proc/gauze_state_changed() + SIGNAL_HANDLER + + if (wound_flags & ACCEPTS_GAUZE) + update_inefficiencies() + +/// Updates our limping and interaction penalties in accordance with our gauze. +/datum/wound/proc/update_inefficiencies() + if (wound_flags & ACCEPTS_GAUZE) + if(limb.body_zone in list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + if(limb.current_gauze?.splint_factor) + limp_slowdown = initial(limp_slowdown) * limb.current_gauze.splint_factor + limp_chance = initial(limp_chance) * limb.current_gauze.splint_factor + else + limp_slowdown = initial(limp_slowdown) + limp_chance = initial(limp_chance) + else if(limb.body_zone in list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + if(limb.current_gauze?.splint_factor) + set_interaction_efficiency_penalty(1 + ((get_effective_actionspeed_modifier()) * limb.current_gauze.splint_factor)) + else + set_interaction_efficiency_penalty(initial(interaction_efficiency_penalty)) + + if(initial(disabling)) + set_disabling(!limb.current_gauze) + + limb.update_wounds() + + start_limping_if_we_should() /// Additional beneficial effects when the wound is gained, in case you want to give a temporary boost to allow the victim to try an escape or last stand /datum/wound/proc/second_wind() @@ -294,25 +473,7 @@ if(I.force && (tendee.istate & ISTATE_HARM)) return FALSE - var/allowed = FALSE - - // check if we have a valid treatable tool - if(I.tool_behaviour == treatable_tool) - allowed = TRUE - else if(treatable_tool == TOOL_CAUTERY && I.get_temperature() && user == victim) // allow improvised cauterization on yourself without an aggro grab - allowed = TRUE - // failing that, see if we're aggro grabbing them and if we have an item that works for aggro grabs only - else if(user.pulling == victim && user.grab_state >= GRAB_AGGRESSIVE && check_grab_treatments(I, user)) - allowed = TRUE - // failing THAT, we check if we have a generally allowed item - else - for(var/allowed_type in treatable_by) - if(istype(I, allowed_type)) - allowed = TRUE - break - - // if none of those apply, we return false to avoid interrupting - if(!allowed) + if(!item_can_treat(I, user)) return FALSE // now that we've determined we have a valid attempt at treating, we can stomp on their dreams if we're already interacting with the patient or if their part is obscured @@ -328,8 +489,22 @@ return TRUE // lastly, treat them - treat(I, user) - return TRUE + return treat(I, user) // we allow treat to return a value so it can control if the item does its normal interaction or not + +/// Returns TRUE if the item can be used to treat our wounds. Hooks into treat() - only things that return TRUE here may be used there. +/datum/wound/proc/item_can_treat(obj/item/potential_treater, mob/user) + // check if we have a valid treatable tool + if(potential_treater.tool_behaviour in treatable_tools) + return TRUE + if(TOOL_CAUTERY in treatable_tools && potential_treater.get_temperature() && user == victim) // allow improvised cauterization on yourself without an aggro grab + return TRUE + // failing that, see if we're aggro grabbing them and if we have an item that works for aggro grabs only + if(user.pulling == victim && user.grab_state >= GRAB_AGGRESSIVE && check_grab_treatments(potential_treater, user)) + return TRUE + // failing THAT, we check if we have a generally allowed item + for(var/allowed_type in treatable_by) + if(istype(potential_treater, allowed_type)) + return TRUE /// Return TRUE if we have an item that can only be used while aggro grabbed (unhanded aggro grab treatments go in [/datum/wound/proc/try_handling]). Treatment is still is handled in [/datum/wound/proc/treat] /datum/wound/proc/check_grab_treatments(obj/item/I, mob/user) @@ -351,16 +526,27 @@ /datum/wound/proc/still_exists() return (!QDELETED(src) && limb) -/// When our parent bodypart is hurt -/datum/wound/proc/receive_damage(wounding_type, wounding_dmg, wound_bonus, attack_direction) +/// When our parent bodypart is hurt. +/datum/wound/proc/receive_damage(wounding_type, wounding_dmg, wound_bonus, attack_direction, damage_source) return /// Called from cryoxadone and pyroxadone when they're proc'ing. Wounds will slowly be fixed separately from other methods when these are in effect. crappy name but eh /datum/wound/proc/on_xadone(power) cryo_progress += power - if(cryo_progress > 33 * severity) + + return handle_xadone_progress() + +/// Does various actions based on [cryo_progress]. By default, qdeletes the wound past a certain threshold. +/datum/wound/proc/handle_xadone_progress() + if(cryo_progress > get_xadone_progress_to_qdel()) qdel(src) +/// Returns the amount of [cryo_progress] we need to be qdeleted. +/datum/wound/proc/get_xadone_progress_to_qdel() + SHOULD_BE_PURE(TRUE) + + return base_xadone_progress_to_qdel * severity + /// When synthflesh is applied to the victim, we call this. No sense in setting up an entire chem reaction system for wounds when we only care for a few chems. Probably will change in the future /datum/wound/proc/on_synthflesh(power) return @@ -410,8 +596,50 @@ * * mob/user: The user examining the wound's owner, if that matters */ /datum/wound/proc/get_examine_description(mob/user) - . = "[victim.p_their(TRUE)] [limb.plaintext_zone] [examine_desc]" - . = severity <= WOUND_SEVERITY_MODERATE ? "[.]." : "[.]!" + . = get_wound_description(user) + if(HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + . += span_notice("\nThere is a holo-image next to the wound that seems to contain indications for treatment.") + + return . + +/datum/wound/proc/get_wound_description(mob/user) + var/desc + + if ((wound_flags & ACCEPTS_GAUZE) && limb.current_gauze) + var/sling_condition = get_gauze_condition() + desc = "[victim.p_Their()] [limb.plaintext_zone] is [sling_condition] fastened in a sling of [limb.current_gauze.name]" + else + desc = "[victim.p_Their()] [limb.plaintext_zone] [examine_desc]" + + desc = modify_desc_before_span(desc, user) + + return get_desc_intensity(desc) + +/// A hook proc used to modify desc before it is spanned via [get_desc_intensity]. Useful for inserting spans yourself. +/datum/wound/proc/modify_desc_before_span(desc, mob/user) + return desc + +/datum/wound/proc/get_gauze_condition() + SHOULD_BE_PURE(TRUE) + if (!limb.current_gauze) + return null + + switch(limb.current_gauze.absorption_capacity) + if(0 to 1.25) + return "just barely" + if(1.25 to 2.75) + return "loosely" + if(2.75 to 4) + return "mostly" + if(4 to INFINITY) + return "tightly" + +/// Spans [desc] based on our severity. +/datum/wound/proc/get_desc_intensity(desc) + SHOULD_BE_PURE(TRUE) + if (severity > WOUND_SEVERITY_MODERATE) + return span_bold("[desc]!") + return "[desc]." /datum/wound/proc/get_scanner_description(mob/user) return "Type: [name]\nSeverity: [severity_text()]\nDescription: [desc]\nRecommended Treatment: [treat_text]" @@ -426,3 +654,50 @@ return "Severe" if(WOUND_SEVERITY_CRITICAL) return "Critical" + +/// Returns TRUE if our limb is the head or chest, FALSE otherwise. +/// Essential in the sense of "we cannot live without it". +/datum/wound/proc/limb_essential() + return (limb.body_zone == BODY_ZONE_HEAD || limb.body_zone == BODY_ZONE_CHEST) + +/// Getter proc for our scar_keyword, in case we might have some custom scar gen logic. +/datum/wound/proc/get_scar_keyword(obj/item/bodypart/scarred_limb, add_to_scars) + return scar_keyword + +/// Getter proc for our scar_file, in case we might have some custom scar gen logic. +/datum/wound/proc/get_scar_file(obj/item/bodypart/scarred_limb, add_to_scars) + var/datum/wound_pregen_data/pregen_data = get_pregen_data() + // basically we iterate over biotypes until we find the one we want + // fleshy burns will look for flesh then bone + // dislocations will look for flesh, then bone, then metal + var/file = default_scar_file + for (var/biotype as anything in pregen_data.scar_priorities) + if (scarred_limb.biological_state & text2num(biotype)) + file = GLOB.biotypes_to_scar_file[biotype] + break + + return file + +/// Returns what string is displayed when a limb that has sustained this wound is examined +/// (This is examining the LIMB ITSELF, when it's not attached to someone.) +/datum/wound/proc/get_limb_examine_description() + return + +/// Gets the flat percentage chance increment of a dismember occuring, if a dismember is attempted (requires mangled flesh and bone). returning 15 = +15%. +/datum/wound/proc/get_dismember_chance_bonus(existing_chance) + SHOULD_BE_PURE(TRUE) + + var/datum/wound_pregen_data/pregen_data = get_pregen_data() + + if (WOUND_BLUNT in pregen_data.required_wounding_types && severity >= WOUND_SEVERITY_CRITICAL) + return WOUND_CRITICAL_BLUNT_DISMEMBER_BONUS // we only require mangled bone (T2 blunt), but if there's a critical blunt, we'll add 15% more + +/// Returns our pregen data, which is practically guaranteed to exist, so this proc can safely be used raw. +/// In fact, since it's RETURN_TYPEd to wound_pregen_data, you can even directly access the variables without having to store the value of this proc in a typed variable. +/// Ex. get_pregen_data().wound_series +/datum/wound/proc/get_pregen_data() + RETURN_TYPE(/datum/wound_pregen_data) + + return GLOB.all_wound_pregen_data[type] + +#undef WOUND_CRITICAL_BLUNT_DISMEMBER_BONUS diff --git a/code/datums/wounds/blunt.dm b/code/datums/wounds/blunt.dm new file mode 100644 index 000000000000..219b7dd8805c --- /dev/null +++ b/code/datums/wounds/blunt.dm @@ -0,0 +1,3 @@ +/datum/wound/blunt + name = "Blunt Wound" + sound_effect = 'sound/effects/wounds/crack1.ogg' diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index 9f68916f9021..f3d0ea3ac001 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -4,11 +4,19 @@ */ // TODO: well, a lot really, but i'd kill to get overlays and a bonebreaking effect like Blitz: The League, similar to electric shock skeletons -/datum/wound/blunt +/datum/wound_pregen_data/bone + abstract = TRUE + required_limb_biostate = BIO_BONE + + required_wounding_types = list(WOUND_BLUNT) + + wound_series = WOUND_SERIES_BONE_BLUNT_BASIC + +/datum/wound/blunt/bone name = "Blunt (Bone) Wound" - sound_effect = 'sound/effects/wounds/crack1.ogg' - wound_type = WOUND_BLUNT - wound_flags = (BONE_WOUND | ACCEPTS_GAUZE) + wound_flags = (ACCEPTS_GAUZE) + + default_scar_file = BONE_SCAR_FILE /// Have we been bone gel'd? var/gelled @@ -32,16 +40,13 @@ /* Overwriting of base procs */ -/datum/wound/blunt/wound_injury(datum/wound/old_wound = null, attack_direction = null) +/datum/wound/blunt/bone/wound_injury(datum/wound/old_wound = null, attack_direction = null) // hook into gaining/losing gauze so crit bone wounds can re-enable/disable depending if they're slung or not - RegisterSignals(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED), PROC_REF(update_inefficiencies)) - if(limb.body_zone == BODY_ZONE_HEAD && brain_trauma_group) processes = TRUE active_trauma = victim.gain_trauma_type(brain_trauma_group, TRAUMA_RESILIENCE_WOUND) next_trauma_cycle = world.time + (rand(100-WOUND_BONE_HEAD_TIME_VARIANCE, 100+WOUND_BONE_HEAD_TIME_VARIANCE) * 0.01 * trauma_cycle_cooldown) - RegisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(attack_with_hurt_hand)) if(limb.held_index && victim.get_item_for_held_index(limb.held_index) && (disabling || prob(30 * severity))) var/obj/item/I = victim.get_item_for_held_index(limb.held_index) if(istype(I, /obj/item/offhand)) @@ -51,19 +56,29 @@ victim.visible_message(span_danger("[victim] drops [I] in shock!"), span_warning("The force on your [limb.plaintext_zone] causes you to drop [I]!"), vision_distance=COMBAT_MESSAGE_RANGE) update_inefficiencies() + return ..() + +/datum/wound/blunt/bone/set_victim(new_victim) + + if (victim) + UnregisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK) + if (new_victim) + RegisterSignal(new_victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(attack_with_hurt_hand)) + + return ..() -/datum/wound/blunt/remove_wound(ignore_limb, replaced) +/datum/wound/blunt/bone/remove_wound(ignore_limb, replaced) limp_slowdown = 0 limp_chance = 0 QDEL_NULL(active_trauma) - if(limb) - UnregisterSignal(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED)) - if(victim) - UnregisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK) return ..() -/datum/wound/blunt/handle_process(seconds_per_tick, times_fired) +/datum/wound/blunt/bone/handle_process(seconds_per_tick, times_fired) . = ..() + + if (!victim || IS_IN_STASIS(victim)) + return + if(limb.body_zone == BODY_ZONE_HEAD && brain_trauma_group && world.time > next_trauma_cycle) if(active_trauma) QDEL_NULL(active_trauma) @@ -96,7 +111,7 @@ remove_wound() /// If we're a human who's punching something with a broken arm, we might hurt ourselves doing so -/datum/wound/blunt/proc/attack_with_hurt_hand(mob/M, atom/target, proximity) +/datum/wound/blunt/bone/proc/attack_with_hurt_hand(mob/M, atom/target, proximity) SIGNAL_HANDLER if(victim.get_active_hand() != limb || !(victim.istate & ISTATE_HARM) || !ismob(target) || severity <= WOUND_SEVERITY_MODERATE) @@ -117,7 +132,7 @@ return COMPONENT_CANCEL_ATTACK_CHAIN -/datum/wound/blunt/receive_damage(wounding_type, wounding_dmg, wound_bonus) +/datum/wound/blunt/bone/receive_damage(wounding_type, wounding_dmg, wound_bonus) if(!victim || wounding_dmg < WOUND_MINIMUM_DAMAGE) return if(ishuman(victim)) @@ -143,99 +158,74 @@ new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir) victim.add_splatter_floor(get_step(victim.loc, victim.dir)) +/datum/wound/blunt/bone/modify_desc_before_span(desc) + . = ..() -/datum/wound/blunt/get_examine_description(mob/user) - if(!limb.current_gauze && !gelled && !taped) - return ..() - - var/list/msg = list() - if(!limb.current_gauze) - msg += "[victim.p_their(TRUE)] [limb.plaintext_zone] [examine_desc]" - else - var/sling_condition = "" - // how much life we have left in these bandages - switch(limb.current_gauze.absorption_capacity) - if(0 to 1.25) - sling_condition = "just barely" - if(1.25 to 2.75) - sling_condition = "loosely" - if(2.75 to 4) - sling_condition = "mostly" - if(4 to INFINITY) - sling_condition = "tightly" - - msg += "[victim.p_their(TRUE)] [limb.plaintext_zone] is [sling_condition] fastened in a sling of [limb.current_gauze.name]" - - if(taped) - msg += ", [span_notice("and appears to be reforming itself under some surgical tape!")]" - else if(gelled) - msg += ", [span_notice("with fizzing flecks of blue bone gel sparking off the bone!")]" - else - msg += "!" - return "[msg.Join()]" + if (!limb.current_gauze) + if(taped) + . += ", [span_notice("and appears to be reforming itself under some surgical tape!")]" + else if(gelled) + . += ", [span_notice("with fizzing flecks of blue bone gel sparking off the bone!")]" /* - New common procs for /datum/wound/blunt/ + New common procs for /datum/wound/blunt/bone/ */ -/datum/wound/blunt/proc/update_inefficiencies() - SIGNAL_HANDLER - - if(limb.body_zone in list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) - if(limb.current_gauze?.splint_factor) - limp_slowdown = initial(limp_slowdown) * limb.current_gauze.splint_factor - limp_chance = initial(limp_chance) * limb.current_gauze.splint_factor - else - limp_slowdown = initial(limp_slowdown) - limp_chance = initial(limp_chance) - victim.apply_status_effect(/datum/status_effect/limp) - else if(limb.body_zone in list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) - if(limb.current_gauze?.splint_factor) - interaction_efficiency_penalty = 1 + ((interaction_efficiency_penalty - 1) * limb.current_gauze.splint_factor) - else - interaction_efficiency_penalty = initial(interaction_efficiency_penalty) +/datum/wound/blunt/bone/get_scar_file(obj/item/bodypart/scarred_limb, add_to_scars) + if (scarred_limb.biological_state & BIO_BONE && (!(scarred_limb.biological_state & BIO_FLESH))) // only bone + return BONE_SCAR_FILE + else if (scarred_limb.biological_state & BIO_FLESH && (!(scarred_limb.biological_state & BIO_BONE))) + return FLESH_SCAR_FILE - if(initial(disabling)) - set_disabling(!limb.current_gauze) - - limb.update_wounds() + return ..() /// Joint Dislocation (Moderate Blunt) -/datum/wound/blunt/moderate +/datum/wound/blunt/bone/moderate name = "Joint Dislocation" - desc = "Patient's bone has been unset from socket, causing pain and reduced motor function." + desc = "Patient's limb has been unset from socket, causing pain and reduced motor function." treat_text = "Recommended application of bonesetter to affected limb, though manual relocation by applying an aggressive grab to the patient and helpfully interacting with afflicted limb may suffice." examine_desc = "is awkwardly janked out of place" occur_text = "janks violently and becomes unseated" severity = WOUND_SEVERITY_MODERATE - viable_zones = list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) interaction_efficiency_penalty = 1.3 limp_slowdown = 3 limp_chance = 50 - threshold_minimum = 35 threshold_penalty = 15 - treatable_tool = TOOL_BONESET - wound_flags = (BONE_WOUND) - status_effect_type = /datum/status_effect/wound/blunt/moderate - scar_keyword = "bluntmoderate" + treatable_tools = list(TOOL_BONESET) + status_effect_type = /datum/status_effect/wound/blunt/bone/moderate + scar_keyword = "dislocate" + +/datum/wound_pregen_data/bone/dislocate + abstract = FALSE + + wound_path_to_generate = /datum/wound/blunt/bone/moderate + + required_limb_biostate = BIO_JOINTED -/datum/wound/blunt/moderate/Destroy() + threshold_minimum = 35 + +/datum/wound/blunt/bone/moderate/Destroy() if(victim) UnregisterSignal(victim, COMSIG_LIVING_DOORCRUSHED) return ..() -/datum/wound/blunt/moderate/wound_injury(datum/wound/old_wound, attack_direction = null) - . = ..() - RegisterSignal(victim, COMSIG_LIVING_DOORCRUSHED, PROC_REF(door_crush)) +/datum/wound/blunt/bone/moderate/set_victim(new_victim) + + if (victim) + UnregisterSignal(victim, COMSIG_LIVING_DOORCRUSHED) + if (new_victim) + RegisterSignal(new_victim, COMSIG_LIVING_DOORCRUSHED, PROC_REF(door_crush)) + + return ..() /// Getting smushed in an airlock/firelock is a last-ditch attempt to try relocating your limb -/datum/wound/blunt/moderate/proc/door_crush() +/datum/wound/blunt/bone/moderate/proc/door_crush() SIGNAL_HANDLER if(prob(40)) victim.visible_message(span_danger("[victim]'s dislocated [limb.plaintext_zone] pops back into place!"), span_userdanger("Your dislocated [limb.plaintext_zone] pops back into place! Ow!")) remove_wound() -/datum/wound/blunt/moderate/try_handling(mob/living/carbon/human/user) +/datum/wound/blunt/bone/moderate/try_handling(mob/living/carbon/human/user) if(user.pulling != victim || user.zone_selected != limb.body_zone) return FALSE @@ -253,7 +243,7 @@ return TRUE /// If someone is snapping our dislocated joint back into place by hand with an aggro grab and help intent -/datum/wound/blunt/moderate/proc/chiropractice(mob/living/carbon/human/user) +/datum/wound/blunt/bone/moderate/proc/chiropractice(mob/living/carbon/human/user) var/time = base_treat_time if(!do_after(user, time, target=victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) @@ -272,7 +262,7 @@ chiropractice(user) /// If someone is snapping our dislocated joint into a fracture by hand with an aggro grab and harm or disarm intent -/datum/wound/blunt/moderate/proc/malpractice(mob/living/carbon/human/user) +/datum/wound/blunt/bone/moderate/proc/malpractice(mob/living/carbon/human/user) var/time = base_treat_time if(!do_after(user, time, target=victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) @@ -290,13 +280,18 @@ malpractice(user) -/datum/wound/blunt/moderate/treat(obj/item/I, mob/user) +/datum/wound/blunt/bone/moderate/treat(obj/item/I, mob/user) + var/scanned = HAS_TRAIT(src, TRAIT_WOUND_SCANNED) + var/self_penalty_mult = user == victim ? 1.5 : 1 + var/scanned_mult = scanned ? 0.5 : 1 + var/treatment_delay = base_treat_time * self_penalty_mult * scanned_mult + if(victim == user) - victim.visible_message(span_danger("[user] begins resetting [victim.p_their()] [limb.plaintext_zone] with [I]."), span_warning("You begin resetting your [limb.plaintext_zone] with [I]...")) + victim.visible_message(span_danger("[user] begins [scanned ? "expertly" : ""] resetting [victim.p_their()] [limb.plaintext_zone] with [I]."), span_warning("You begin resetting your [limb.plaintext_zone] with [I][scanned ? ", keeping the holo-image's indications in mind" : ""]...")) else - user.visible_message(span_danger("[user] begins resetting [victim]'s [limb.plaintext_zone] with [I]."), span_notice("You begin resetting [victim]'s [limb.plaintext_zone] with [I]...")) + user.visible_message(span_danger("[user] begins [scanned ? "expertly" : ""] resetting [victim]'s [limb.plaintext_zone] with [I]."), span_notice("You begin resetting [victim]'s [limb.plaintext_zone] with [I][scanned ? ", keeping the holo-image's indications in mind" : ""]...")) - if(!do_after(user, base_treat_time * (user == victim ? 1.5 : 1), target = victim, extra_checks=CALLBACK(src, PROC_REF(still_exists)))) + if(!do_after(user, treatment_delay, target = victim, extra_checks=CALLBACK(src, PROC_REF(still_exists)))) return if(victim == user) @@ -314,7 +309,7 @@ Severe (Hairline Fracture) */ -/datum/wound/blunt/severe +/datum/wound/blunt/bone/severe name = "Hairline Fracture" desc = "Patient's bone has suffered a crack in the foundation, causing serious pain and reduced limb functionality." treat_text = "Recommended light surgical application of bone gel, though a sling of medical gauze will prevent worsening situation." @@ -325,19 +320,25 @@ interaction_efficiency_penalty = 2 limp_slowdown = 6 limp_chance = 60 - threshold_minimum = 60 threshold_penalty = 30 treatable_by = list(/obj/item/stack/sticky_tape/surgical, /obj/item/stack/medical/bone_gel) - status_effect_type = /datum/status_effect/wound/blunt/severe + status_effect_type = /datum/status_effect/wound/blunt/bone/severe scar_keyword = "bluntsevere" brain_trauma_group = BRAIN_TRAUMA_MILD trauma_cycle_cooldown = 1.5 MINUTES internal_bleeding_chance = 40 - wound_flags = (BONE_WOUND | ACCEPTS_GAUZE | MANGLES_BONE) + wound_flags = (ACCEPTS_GAUZE | MANGLES_INTERIOR) regen_ticks_needed = 120 // ticks every 2 seconds, 240 seconds, so roughly 4 minutes default +/datum/wound_pregen_data/bone/hairline + abstract = FALSE + + wound_path_to_generate = /datum/wound/blunt/bone/severe + + threshold_minimum = 60 + /// Compound Fracture (Critical Blunt) -/datum/wound/blunt/critical +/datum/wound/blunt/bone/critical name = "Compound Fracture" desc = "Patient's bones have suffered multiple gruesome fractures, causing significant pain and near uselessness of limb." treat_text = "Immediate binding of affected limb, followed by surgical intervention ASAP." @@ -349,40 +350,45 @@ limp_slowdown = 7 limp_chance = 70 sound_effect = 'sound/effects/wounds/crack2.ogg' - threshold_minimum = 115 threshold_penalty = 50 disabling = TRUE treatable_by = list(/obj/item/stack/sticky_tape/surgical, /obj/item/stack/medical/bone_gel) - status_effect_type = /datum/status_effect/wound/blunt/critical + status_effect_type = /datum/status_effect/wound/blunt/bone/critical scar_keyword = "bluntcritical" brain_trauma_group = BRAIN_TRAUMA_SEVERE trauma_cycle_cooldown = 2.5 MINUTES internal_bleeding_chance = 60 - wound_flags = (BONE_WOUND | ACCEPTS_GAUZE | MANGLES_BONE) + wound_flags = (ACCEPTS_GAUZE | MANGLES_INTERIOR) regen_ticks_needed = 240 // ticks every 2 seconds, 480 seconds, so roughly 8 minutes default +/datum/wound_pregen_data/bone/compound + abstract = FALSE + + wound_path_to_generate = /datum/wound/blunt/bone/critical + + threshold_minimum = 115 + // doesn't make much sense for "a" bone to stick out of your head -/datum/wound/blunt/critical/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE, attack_direction = null) +/datum/wound/blunt/bone/critical/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE, attack_direction = null, wound_source = "Unknown") if(L.body_zone == BODY_ZONE_HEAD) occur_text = "splits open, exposing a bare, cracked skull through the flesh and blood" examine_desc = "has an unsettling indent, with bits of skull poking out" . = ..() /// if someone is using bone gel on our wound -/datum/wound/blunt/proc/gel(obj/item/stack/medical/bone_gel/I, mob/user) +/datum/wound/blunt/bone/proc/gel(obj/item/stack/medical/bone_gel/I, mob/user) // skellies get treated nicer with bone gel since their "reattach dismembered limbs by hand" ability sucks when it's still critically wounded if((limb.biological_state & BIO_BONE) && !(limb.biological_state & BIO_FLESH)) - skelly_gel(I, user) - return + return skelly_gel(I, user) if(gelled) to_chat(user, span_warning("[user == victim ? "Your" : "[victim]'s"] [limb.plaintext_zone] is already coated with bone gel!")) - return + return TRUE user.visible_message(span_danger("[user] begins hastily applying [I] to [victim]'s' [limb.plaintext_zone]..."), span_warning("You begin hastily applying [I] to [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone], disregarding the warning label...")) if(!do_after(user, base_treat_time * 1.5 * (user == victim ? 1.5 : 1), target = victim, extra_checks=CALLBACK(src, PROC_REF(still_exists)))) - return + return TRUE I.use(1) victim.emote("scream") @@ -405,15 +411,16 @@ if(prob(25 + (20 * (severity - 2)) - painkiller_bonus)) // 25%/45% chance to fail self-applying with severe and critical wounds, modded by painkillers victim.visible_message(span_danger("[victim] fails to finish applying [I] to [victim.p_their()] [limb.plaintext_zone], passing out from the pain!"), span_notice("You pass out from the pain of applying [I] to your [limb.plaintext_zone] before you can finish!")) victim.AdjustUnconscious(5 SECONDS) - return + return TRUE victim.visible_message(span_notice("[victim] finishes applying [I] to [victim.p_their()] [limb.plaintext_zone], grimacing from the pain!"), span_notice("You finish applying [I] to your [limb.plaintext_zone], and your bones explode in pain!")) limb.receive_damage(25, wound_bonus=CANT_WOUND) victim.stamina.adjust(-100) gelled = TRUE + return TRUE /// skellies are less averse to bone gel, since they're literally all bone -/datum/wound/blunt/proc/skelly_gel(obj/item/stack/medical/bone_gel/I, mob/user) +/datum/wound/blunt/bone/proc/skelly_gel(obj/item/stack/medical/bone_gel/I, mob/user) if(gelled) to_chat(user, span_warning("[user == victim ? "Your" : "[victim]'s"] [limb.plaintext_zone] is already coated with bone gel!")) return @@ -432,20 +439,21 @@ gelled = TRUE processes = TRUE + return TRUE /// if someone is using surgical tape on our wound -/datum/wound/blunt/proc/tape(obj/item/stack/sticky_tape/surgical/I, mob/user) +/datum/wound/blunt/bone/proc/tape(obj/item/stack/sticky_tape/surgical/I, mob/user) if(!gelled) to_chat(user, span_warning("[user == victim ? "Your" : "[victim]'s"] [limb.plaintext_zone] must be coated with bone gel to perform this emergency operation!")) - return + return TRUE if(taped) to_chat(user, span_warning("[user == victim ? "Your" : "[victim]'s"] [limb.plaintext_zone] is already wrapped in [I.name] and reforming!")) - return + return TRUE user.visible_message(span_danger("[user] begins applying [I] to [victim]'s' [limb.plaintext_zone]..."), span_warning("You begin applying [I] to [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone]...")) if(!do_after(user, base_treat_time * (user == victim ? 1.5 : 1), target = victim, extra_checks=CALLBACK(src, PROC_REF(still_exists)))) - return + return TRUE if(victim == user) regen_ticks_needed *= 1.5 @@ -459,14 +467,15 @@ taped = TRUE processes = TRUE + return TRUE -/datum/wound/blunt/treat(obj/item/I, mob/user) +/datum/wound/blunt/bone/treat(obj/item/I, mob/user) if(istype(I, /obj/item/stack/medical/bone_gel)) - gel(I, user) + return gel(I, user) else if(istype(I, /obj/item/stack/sticky_tape/surgical)) - tape(I, user) + return tape(I, user) -/datum/wound/blunt/get_scanner_description(mob/user) +/datum/wound/blunt/bone/get_scanner_description(mob/user) . = ..() . += "
" diff --git a/code/datums/wounds/burns.dm b/code/datums/wounds/burns.dm index 21b5e0040e8a..37f4072ffbf9 100644 --- a/code/datums/wounds/burns.dm +++ b/code/datums/wounds/burns.dm @@ -7,20 +7,24 @@ /datum/wound/burn name = "Burn Wound" a_or_from = "from" - wound_type = WOUND_BURN - processes = TRUE sound_effect = 'sound/effects/wounds/sizzle1.ogg' - wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE) + +/datum/wound/burn/flesh + name = "Burn (Flesh) Wound" + a_or_from = "from" + processes = TRUE + + default_scar_file = FLESH_SCAR_FILE treatable_by = list(/obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) // sterilizer and alcohol will require reagent treatments, coming soon - // Flesh damage vars + // Flesh damage vars /// How much damage to our flesh we currently have. Once both this and infestation reach 0, the wound is considered healed var/flesh_damage = 5 /// Our current counter for how much flesh regeneration we have stacked from regenerative mesh/synthflesh/whatever, decrements each tick and lowers flesh_damage var/flesh_healing = 0 - // Infestation vars (only for severe and critical) + // Infestation vars (only for severe and critical) /// How quickly infection breeds on this burn if we don't have disinfectant var/infestation_rate = 0 /// Our current level of infection @@ -31,8 +35,11 @@ /// Once we reach infestation beyond WOUND_INFESTATION_SEPSIS, we get this many warnings before the limb is completely paralyzed (you'd have to ignore a really bad burn for a really long time for this to happen) var/strikes_to_lose_limb = 3 +/datum/wound/burn/flesh/handle_process(seconds_per_tick, times_fired) + + if (!victim || IS_IN_STASIS(victim)) + return -/datum/wound/burn/handle_process(seconds_per_tick, times_fired) . = ..() if(strikes_to_lose_limb == 0) // we've already hit sepsis, nothing more to do victim.adjustToxLoss(0.25 * seconds_per_tick) @@ -128,7 +135,7 @@ var/datum/brain_trauma/severe/paralysis/sepsis = new (limb.body_zone) victim.gain_trauma(sepsis) -/datum/wound/burn/get_examine_description(mob/user) +/datum/wound/burn/flesh/get_wound_description(mob/user) if(strikes_to_lose_limb <= 0) return span_deadsay("[victim.p_their(TRUE)] [limb.plaintext_zone] has locked up completely and is non-functional.") @@ -161,8 +168,8 @@ return "[condition.Join()]" -/datum/wound/burn/get_scanner_description(mob/user) - if(strikes_to_lose_limb == 0) +/datum/wound/burn/flesh/get_scanner_description(mob/user) + if(strikes_to_lose_limb <= 0) // Unclear if it can go below 0, best to not take the chance var/oopsie = "Type: [name]\nSeverity: [severity_text()]" oopsie += "
Infection Level: [span_deadsay("The body part has suffered complete sepsis and must be removed. Amputate or augment limb immediately.")]
" return oopsie @@ -194,12 +201,12 @@ */ /// if someone is using ointment or mesh on our burns -/datum/wound/burn/proc/ointmentmesh(obj/item/stack/medical/I, mob/user) +/datum/wound/burn/flesh/proc/ointmentmesh(obj/item/stack/medical/I, mob/user) user.visible_message(span_notice("[user] begins applying [I] to [victim]'s [limb.plaintext_zone]..."), span_notice("You begin applying [I] to [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone]...")) if (I.amount <= 0) - return + return TRUE if(!do_after(user, (user == victim ? I.self_delay : I.other_delay), extra_checks = CALLBACK(src, PROC_REF(still_exists)))) - return + return TRUE limb.heal_damage(I.heal_brute, I.heal_burn) user.visible_message(span_green("[user] applies [I] to [victim]."), span_green("You apply [I] to [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone].")) @@ -209,36 +216,38 @@ if((infestation <= 0 || sanitization >= infestation) && (flesh_damage <= 0 || flesh_healing > flesh_damage)) to_chat(user, span_notice("You've done all you can with [I], now you must wait for the flesh on [victim]'s [limb.plaintext_zone] to recover.")) + return TRUE else - try_treating(I, user) + return try_treating(I, user) /// Paramedic UV penlights -/datum/wound/burn/proc/uv(obj/item/flashlight/pen/paramedic/I, mob/user) +/datum/wound/burn/flesh/proc/uv(obj/item/flashlight/pen/paramedic/I, mob/user) if(!COOLDOWN_FINISHED(I, uv_cooldown)) to_chat(user, span_notice("[I] is still recharging!")) - return + return TRUE if(infestation <= 0 || infestation < sanitization) to_chat(user, span_notice("There's no infection to treat on [victim]'s [limb.plaintext_zone]!")) - return + return TRUE user.visible_message(span_notice("[user] flashes the burns on [victim]'s [limb] with [I]."), span_notice("You flash the burns on [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]."), vision_distance=COMBAT_MESSAGE_RANGE) sanitization += I.uv_power COOLDOWN_START(I, uv_cooldown, I.uv_cooldown_length) + return TRUE -/datum/wound/burn/treat(obj/item/I, mob/user) +/datum/wound/burn/flesh/treat(obj/item/I, mob/user) if(istype(I, /obj/item/stack/medical/ointment)) - ointmentmesh(I, user) + return ointmentmesh(I, user) else if(istype(I, /obj/item/stack/medical/mesh)) var/obj/item/stack/medical/mesh/mesh_check = I if(!mesh_check.is_open) to_chat(user, span_warning("You need to open [mesh_check] first.")) return - ointmentmesh(mesh_check, user) + return ointmentmesh(mesh_check, user) else if(istype(I, /obj/item/flashlight/pen/paramedic)) - uv(I, user) + return uv(I, user) // people complained about burns not healing on stasis beds, so in addition to checking if it's cured, they also get the special ability to very slowly heal on stasis beds if they have the healing effects stored -/datum/wound/burn/on_stasis(seconds_per_tick, times_fired) +/datum/wound/burn/flesh/on_stasis(seconds_per_tick, times_fired) . = ..() if(strikes_to_lose_limb == 0) // we've already hit sepsis, nothing more to do if(SPT_PROB(0.5, seconds_per_tick)) @@ -253,60 +262,108 @@ if(sanitization > 0) infestation = max(infestation - (0.1 * WOUND_BURN_SANITIZATION_RATE * seconds_per_tick), 0) -/datum/wound/burn/on_synthflesh(amount) +/datum/wound/burn/flesh/on_synthflesh(amount) flesh_healing += amount * 0.5 // 20u patch will heal 10 flesh standard +/datum/wound_pregen_data/flesh_burn + abstract = TRUE + + required_wounding_types = list(WOUND_BURN) + required_limb_biostate = BIO_FLESH + + wound_series = WOUND_SERIES_FLESH_BURN_BASIC + +/datum/wound/burn/get_limb_examine_description() + return span_warning("The flesh on this limb appears badly cooked.") + // we don't even care about first degree burns, straight to second -/datum/wound/burn/moderate +/datum/wound/burn/flesh/moderate name = "Second Degree Burns" desc = "Patient is suffering considerable burns with mild skin penetration, weakening limb integrity and increased burning sensations." treat_text = "Recommended application of topical ointment or regenerative mesh to affected region." examine_desc = "is badly burned and breaking out in blisters" occur_text = "breaks out with violent red burns" severity = WOUND_SEVERITY_MODERATE - damage_mulitplier_penalty = 1.1 - threshold_minimum = 40 threshold_penalty = 30 // burns cause significant decrease in limb integrity compared to other wounds - status_effect_type = /datum/status_effect/wound/burn/moderate + status_effect_type = /datum/status_effect/wound/burn/flesh/moderate flesh_damage = 5 scar_keyword = "burnmoderate" -/datum/wound/burn/severe +/datum/wound_pregen_data/flesh_burn/second_degree + abstract = FALSE + + wound_path_to_generate = /datum/wound/burn/flesh/moderate + + threshold_minimum = 40 + +/datum/wound/burn/flesh/severe name = "Third Degree Burns" desc = "Patient is suffering extreme burns with full skin penetration, creating serious risk of infection and greatly reduced limb integrity." treat_text = "Recommended immediate disinfection and excision of any infected skin, followed by bandaging and ointment." examine_desc = "appears seriously charred, with aggressive red splotches" occur_text = "chars rapidly, exposing ruined tissue and spreading angry red burns" severity = WOUND_SEVERITY_SEVERE - damage_mulitplier_penalty = 1.2 - threshold_minimum = 80 threshold_penalty = 40 - status_effect_type = /datum/status_effect/wound/burn/severe + status_effect_type = /datum/status_effect/wound/burn/flesh/severe treatable_by = list(/obj/item/flashlight/pen/paramedic, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) infestation_rate = 0.07 // appx 9 minutes to reach sepsis without any treatment flesh_damage = 12.5 scar_keyword = "burnsevere" -/datum/wound/burn/critical +/datum/wound_pregen_data/flesh_burn/third_degree + abstract = FALSE + + wound_path_to_generate = /datum/wound/burn/flesh/severe + + threshold_minimum = 80 + +/datum/wound/burn/flesh/critical name = "Catastrophic Burns" desc = "Patient is suffering near complete loss of tissue and significantly charred muscle and bone, creating life-threatening risk of infection and negligible limb integrity." treat_text = "Immediate surgical debriding of any infected skin, followed by potent tissue regeneration formula and bandaging." examine_desc = "is a ruined mess of blanched bone, melted fat, and charred tissue" occur_text = "vaporizes as flesh, bone, and fat melt together in a horrifying mess" severity = WOUND_SEVERITY_CRITICAL - damage_mulitplier_penalty = 1.3 sound_effect = 'sound/effects/wounds/sizzle2.ogg' - threshold_minimum = 140 threshold_penalty = 80 - status_effect_type = /datum/status_effect/wound/burn/critical + status_effect_type = /datum/status_effect/wound/burn/flesh/critical treatable_by = list(/obj/item/flashlight/pen/paramedic, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) infestation_rate = 0.075 // appx 4.33 minutes to reach sepsis without any treatment flesh_damage = 20 scar_keyword = "burncritical" +/datum/wound_pregen_data/flesh_burn/fourth_degree + abstract = FALSE + + wound_path_to_generate = /datum/wound/burn/flesh/critical + + threshold_minimum = 140 + ///special severe wound caused by sparring interference or other god related punishments. -/datum/wound/burn/severe/brand +/datum/wound/burn/flesh/severe/brand name = "Holy Brand" desc = "Patient is suffering extreme burns from a strange brand marking, creating serious risk of infection and greatly reduced limb integrity." examine_desc = "appears to have holy symbols painfully branded into their flesh, leaving severe burns." occur_text = "chars rapidly into a strange pattern of holy symbols, burned into the flesh." + +/datum/wound_pregen_data/flesh_burn/third_degree/holy + abstract = FALSE + can_be_randomly_generated = FALSE + + wound_path_to_generate = /datum/wound/burn/flesh/severe/brand +/// special severe wound caused by the cursed slot machine. + +/datum/wound/burn/flesh/severe/cursed_brand + name = "Ancient Brand" + desc = "Patient is suffering extreme burns with oddly ornate brand markings, creating serious risk of infection and greatly reduced limb integrity." + examine_desc = "appears to have ornate symbols painfully branded into their flesh, leaving severe burns" + occur_text = "chars rapidly into a pattern that can only be described as an agglomeration of several financial symbols, burned into the flesh" + +/datum/wound/burn/flesh/severe/cursed_brand/get_limb_examine_description() + return span_warning("The flesh on this limb has several ornate symbols burned into it, with pitting throughout.") + +/datum/wound_pregen_data/flesh_burn/third_degree/cursed_brand + abstract = FALSE + can_be_randomly_generated = FALSE + + wound_path_to_generate = /datum/wound/burn/flesh/severe/cursed_brand diff --git a/code/datums/wounds/loss.dm b/code/datums/wounds/loss.dm index b1853840ced1..479656a8a429 100644 --- a/code/datums/wounds/loss.dm +++ b/code/datums/wounds/loss.dm @@ -1,3 +1,15 @@ +/datum/wound_pregen_data/loss + abstract = FALSE + + wound_path_to_generate = /datum/wound/loss + required_limb_biostate = NONE + require_any_biostate = TRUE + + required_wounding_types = list(WOUND_ALL) + + wound_series = WOUND_SERIES_LOSS_BASIC + + threshold_minimum = WOUND_DISMEMBER_OUTRIGHT_THRESH // not actually used since dismembering is handled differently, but may as well assign it since we got it /datum/wound/loss name = "Dismemberment Wound" @@ -5,15 +17,17 @@ sound_effect = 'sound/effects/dismember.ogg' severity = WOUND_SEVERITY_LOSS - threshold_minimum = WOUND_DISMEMBER_OUTRIGHT_THRESH // not actually used since dismembering is handled differently, but may as well assign it since we got it status_effect_type = null scar_keyword = "dismember" wound_flags = null already_scarred = TRUE // We manually assign scars for dismembers through endround missing limbs and aheals + /// The wounding_type of the attack that caused us. Used to generate the description of our scar. Currently unused, but primarily exists in case non-biological wounds are added. + var/loss_wounding_type + /// Our special proc for our special dismembering, the wounding type only matters for what text we have -/datum/wound/loss/proc/apply_dismember(obj/item/bodypart/dismembered_part, wounding_type=WOUND_SLASH, outright = FALSE, attack_direction) - if(!istype(dismembered_part) || !dismembered_part.owner || !(dismembered_part.body_zone in viable_zones) || isalien(dismembered_part.owner) || !dismembered_part.can_dismember()) +/datum/wound/loss/proc/apply_dismember(obj/item/bodypart/dismembered_part, wounding_type = WOUND_SLASH, outright = FALSE, attack_direction) + if(!istype(dismembered_part) || !dismembered_part.owner || !(dismembered_part.body_zone in get_viable_zones()) || isalien(dismembered_part.owner) || !dismembered_part.can_dismember()) qdel(src) return @@ -22,10 +36,31 @@ if(dismembered_part.body_zone == BODY_ZONE_CHEST) occur_text = "is split open, causing [victim.p_their()] internal [dismembered_part.bodytype & BODYTYPE_ROBOTIC? "components": "organs"] to spill out!" self_msg = "is split open, causing your internal organs to spill out!" - else if(outright) + else + occur_text = dismembered_part.get_dismember_message(wounding_type, outright) + + var/msg = span_bolddanger("[victim]'s [dismembered_part.plaintext_zone] [occur_text]") + + victim.visible_message(msg, span_userdanger("Your [dismembered_part.plaintext_zone] [self_msg ? self_msg : occur_text]")) + + loss_wounding_type = wounding_type + + set_limb(dismembered_part) + second_wind() + log_wound(victim, src) + if(dismembered_part.can_bleed() && wounding_type != WOUND_BURN && victim.blood_volume) + victim.spray_blood(attack_direction, severity) + dismembered_part.dismember(wounding_type == WOUND_BURN ? BURN : BRUTE, wounding_type = wounding_type) + qdel(src) + return TRUE + +/obj/item/bodypart/proc/get_dismember_message(wounding_type, outright) + var/occur_text + + if(outright) switch(wounding_type) if(WOUND_BLUNT) - occur_text = "is outright smashed to a [dismembered_part.bodytype & BODYTYPE_ROBOTIC? "misshapen heap of scrap": "gross pulp"], severing it completely!" + occur_text = "is outright smashed to a gross pulp, severing it completely!" if(WOUND_SLASH) occur_text = "is outright slashed off, severing it completely!" if(WOUND_PIERCE) @@ -33,25 +68,17 @@ if(WOUND_BURN) occur_text = "is outright incinerated, falling to dust!" else + var/bone_text = get_internal_description() + var/tissue_text = get_external_description() + switch(wounding_type) if(WOUND_BLUNT) - occur_text = "is shattered through the last [dismembered_part.bodytype & BODYTYPE_ROBOTIC? "bit of wires": "bone"] holding it together, severing it completely!" + occur_text = "is shattered through the last [bone_text] holding it together, severing it completely!" if(WOUND_SLASH) - occur_text = "is slashed through the last [dismembered_part.bodytype & BODYTYPE_ROBOTIC? "bit of wires": "tissue"] holding it together, severing it completely!" + occur_text = "is slashed through the last [tissue_text] holding it together, severing it completely!" if(WOUND_PIERCE) - occur_text = "is pierced through the last [dismembered_part.bodytype & BODYTYPE_ROBOTIC? "bit of wires": "tissue"] holding it together, severing it completely!" + occur_text = "is pierced through the last [tissue_text] holding it together, severing it completely!" if(WOUND_BURN) occur_text = "is completely incinerated, falling to dust!" - var/msg = span_bolddanger("[victim]'s [dismembered_part.plaintext_zone] [occur_text]") - - victim.visible_message(msg, span_userdanger("Your [dismembered_part.plaintext_zone] [self_msg ? self_msg : occur_text]")) - - set_limb(dismembered_part) - second_wind() - log_wound(victim, src) - if(wounding_type != WOUND_BURN && victim.blood_volume) - victim.spray_blood(attack_direction, severity) - dismembered_part.dismember(wounding_type == WOUND_BURN ? BURN : BRUTE) - qdel(src) - return TRUE + return occur_text diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm index 1612b3159a6f..b52926dec16d 100644 --- a/code/datums/wounds/pierce.dm +++ b/code/datums/wounds/pierce.dm @@ -2,16 +2,19 @@ /* Piercing wounds */ - /datum/wound/pierce + +/datum/wound/pierce/bleed name = "Piercing Wound" sound_effect = 'sound/weapons/slice.ogg' processes = TRUE - wound_type = WOUND_PIERCE treatable_by = list(/obj/item/stack/medical/suture) - treatable_tool = TOOL_CAUTERY + treatable_tools = list(TOOL_CAUTERY) base_treat_time = 3 SECONDS - wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE) + wound_flags = (ACCEPTS_GAUZE | CAN_BE_GRASPED) + + default_scar_file = FLESH_SCAR_FILE + /// How much blood we start losing when this wound is first applied var/initial_flow @@ -23,13 +26,15 @@ /// If we let off blood when hit, the max blood lost is this * the incoming damage var/internal_bleeding_coefficient -/datum/wound/pierce/wound_injury(datum/wound/old_wound = null, attack_direction = null) +/datum/wound/pierce/bleed/wound_injury(datum/wound/old_wound = null, attack_direction = null) set_blood_flow(initial_flow) - if(!no_bleeding && attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY) + if(limb.can_bleed() && attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY) victim.spray_blood(attack_direction, severity) -/datum/wound/pierce/receive_damage(wounding_type, wounding_dmg, wound_bonus) - if(victim.stat == DEAD || (wounding_dmg < 5) || no_bleeding || !victim.blood_volume || !prob(internal_bleeding_chance + wounding_dmg)) + return ..() + +/datum/wound/pierce/bleed/receive_damage(wounding_type, wounding_dmg, wound_bonus) + if(victim.stat == DEAD || (wounding_dmg < 5) || !limb.can_bleed() || !victim.blood_volume || !prob(internal_bleeding_chance + wounding_dmg)) return if(limb.current_gauze?.splint_factor) wounding_dmg *= (1 - limb.current_gauze.splint_factor) @@ -50,9 +55,9 @@ new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir) victim.add_splatter_floor(get_step(victim.loc, victim.dir)) -/datum/wound/pierce/get_bleed_rate_of_change() +/datum/wound/pierce/bleed/get_bleed_rate_of_change() //basically if a species doesn't bleed, the wound is stagnant and will not heal on it's own (nor get worse) - if(no_bleeding) + if(!limb.can_bleed()) return BLOOD_FLOW_STEADY if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS)) return BLOOD_FLOW_INCREASING @@ -60,10 +65,13 @@ return BLOOD_FLOW_DECREASING return BLOOD_FLOW_STEADY -/datum/wound/pierce/handle_process(seconds_per_tick, times_fired) +/datum/wound/pierce/bleed/handle_process(seconds_per_tick, times_fired) + if (!victim || IS_IN_STASIS(victim)) + return + set_blood_flow(min(blood_flow, WOUND_SLASH_MAX_BLOODFLOW)) - if(!no_bleeding) + if(limb.can_bleed()) if(victim.bodytemperature < (BODYTEMP_NORMAL - 10)) adjust_blood_flow(-0.1 * seconds_per_tick) if(SPT_PROB(2.5, seconds_per_tick)) @@ -79,36 +87,45 @@ if(blood_flow <= 0) qdel(src) -/datum/wound/pierce/on_stasis(seconds_per_tick, times_fired) +/datum/wound/pierce/bleed/on_stasis(seconds_per_tick, times_fired) . = ..() if(blood_flow <= 0) qdel(src) -/datum/wound/pierce/check_grab_treatments(obj/item/I, mob/user) +/datum/wound/pierce/bleed/check_grab_treatments(obj/item/I, mob/user) if(I.get_temperature()) // if we're using something hot but not a cautery, we need to be aggro grabbing them first, so we don't try treating someone we're eswording return TRUE -/datum/wound/pierce/treat(obj/item/I, mob/user) +/datum/wound/pierce/bleed/treat(obj/item/I, mob/user) if(istype(I, /obj/item/stack/medical/suture)) - suture(I, user) + return suture(I, user) else if(I.tool_behaviour == TOOL_CAUTERY || I.get_temperature()) - tool_cauterize(I, user) + return tool_cauterize(I, user) -/datum/wound/pierce/on_xadone(power) +/datum/wound/pierce/bleed/on_xadone(power) . = ..() - adjust_blood_flow(-0.03 * power) // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick is pretty good for 0 effort -/datum/wound/pierce/on_synthflesh(power) + if (limb) // parent can cause us to be removed, so its reasonable to check if we're still applied + adjust_blood_flow(-0.03 * power) // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick is pretty good for 0 effort + +/datum/wound/pierce/bleed/on_synthflesh(power) . = ..() adjust_blood_flow(-0.025 * power) // 20u * 0.05 = -1 blood flow, less than with slashes but still good considering smaller bleed rates /// If someone is using a suture to close this puncture -/datum/wound/pierce/proc/suture(obj/item/stack/medical/suture/I, mob/user) +/datum/wound/pierce/bleed/proc/suture(obj/item/stack/medical/suture/I, mob/user) var/self_penalty_mult = (user == victim ? 1.4 : 1) - user.visible_message(span_notice("[user] begins stitching [victim]'s [limb.plaintext_zone] with [I]..."), span_notice("You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]...")) - if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) - return - var/bleeding_wording = (no_bleeding ? "holes" : "bleeding") + var/treatment_delay = base_treat_time * self_penalty_mult + + if(HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + treatment_delay *= 0.5 + user.visible_message(span_notice("[user] begins expertly stitching [victim]'s [limb.plaintext_zone] with [I]..."), span_notice("You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I], keeping the holo-image information in mind...")) + else + user.visible_message(span_notice("[user] begins stitching [victim]'s [limb.plaintext_zone] with [I]..."), span_notice("You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]...")) + + if(!do_after(user, treatment_delay, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + var/bleeding_wording = (!limb.can_bleed() ? "holes" : "bleeding") user.visible_message(span_green("[user] stitches up some of the [bleeding_wording] on [victim]."), span_green("You stitch up some of the [bleeding_wording] on [user == victim ? "yourself" : "[victim]"].")) var/blood_sutured = I.stop_bleeding / self_penalty_mult adjust_blood_flow(-blood_sutured) @@ -116,20 +133,29 @@ I.use(1) if(blood_flow > 0) - try_treating(I, user) + return try_treating(I, user) else to_chat(user, span_green("You successfully close the hole in [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone].")) + return TRUE /// If someone is using either a cautery tool or something with heat to cauterize this pierce -/datum/wound/pierce/proc/tool_cauterize(obj/item/I, mob/user) +/datum/wound/pierce/bleed/proc/tool_cauterize(obj/item/I, mob/user) + var/improv_penalty_mult = (I.tool_behaviour == TOOL_CAUTERY ? 1 : 1.25) // 25% longer and less effective if you don't use a real cautery var/self_penalty_mult = (user == victim ? 1.5 : 1) // 50% longer and less effective if you do it to yourself - user.visible_message(span_danger("[user] begins cauterizing [victim]'s [limb.plaintext_zone] with [I]..."), span_warning("You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]...")) - if(!do_after(user, base_treat_time * self_penalty_mult * improv_penalty_mult, target=victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) - return + var/treatment_delay = base_treat_time * self_penalty_mult * improv_penalty_mult - var/bleeding_wording = (no_bleeding ? "holes" : "bleeding") + if(HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + treatment_delay *= 0.5 + user.visible_message(span_danger("[user] begins expertly cauterizing [victim]'s [limb.plaintext_zone] with [I]..."), span_warning("You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I], keeping the holo-image indications in mind...")) + else + user.visible_message(span_danger("[user] begins cauterizing [victim]'s [limb.plaintext_zone] with [I]..."), span_warning("You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]...")) + + if(!do_after(user, treatment_delay, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + + var/bleeding_wording = (!limb.can_bleed() ? "holes" : "bleeding") user.visible_message(span_green("[user] cauterizes some of the [bleeding_wording] on [victim]."), span_green("You cauterize some of the [bleeding_wording] on [victim].")) limb.receive_damage(burn = 2 + severity, wound_bonus = CANT_WOUND) if(prob(30)) @@ -138,10 +164,30 @@ adjust_blood_flow(-blood_cauterized) if(blood_flow > 0) - try_treating(I, user) + return try_treating(I, user) + return TRUE + +/datum/wound_pregen_data/flesh_pierce + abstract = TRUE -/datum/wound/pierce/moderate - name = "Minor Breakage" + required_limb_biostate = (BIO_FLESH) + required_wounding_types = list(WOUND_PIERCE) + + wound_series = WOUND_SERIES_FLESH_PUNCTURE_BLEED + +/datum/wound_pregen_data/flesh_pierce + abstract = TRUE + + required_limb_biostate = (BIO_FLESH) + required_wounding_types = list(WOUND_PIERCE) + + wound_series = WOUND_SERIES_FLESH_PUNCTURE_BLEED + +/datum/wound/pierce/get_limb_examine_description() + return span_warning("The flesh on this limb appears badly perforated.") + +/datum/wound/pierce/bleed/moderate + name = "Minor Skin Breakage" desc = "Patient's skin has been broken open, causing severe bruising and minor internal bleeding in affected area." treat_text = "Treat affected site with bandaging or exposure to extreme cold. In dire cases, brief exposure to vacuum may suffice." // space is cold in ss13, so it's like an ice pack! examine_desc = "has a small, circular hole, gently bleeding" @@ -152,17 +198,23 @@ gauzed_clot_rate = 0.8 internal_bleeding_chance = 30 internal_bleeding_coefficient = 1.25 - threshold_minimum = 30 threshold_penalty = 20 status_effect_type = /datum/status_effect/wound/pierce/moderate scar_keyword = "piercemoderate" -/datum/wound/pierce/moderate/update_descriptions() - if(no_bleeding) +/datum/wound_pregen_data/flesh_pierce/breakage + abstract = FALSE + + wound_path_to_generate = /datum/wound/pierce/bleed/moderate + + threshold_minimum = 30 + +/datum/wound/pierce/bleed/moderate/update_descriptions() + if(!limb.can_bleed()) examine_desc = "has a small, circular hole" occur_text = "splits a small hole open" -/datum/wound/pierce/severe +/datum/wound/pierce/bleed/severe name = "Open Puncture" desc = "Patient's internal tissue is penetrated, causing sizeable internal bleeding and reduced limb stability." treat_text = "Repair punctures in skin by suture or cautery, extreme cold may also work." @@ -174,16 +226,22 @@ gauzed_clot_rate = 0.6 internal_bleeding_chance = 60 internal_bleeding_coefficient = 1.5 - threshold_minimum = 50 threshold_penalty = 35 status_effect_type = /datum/status_effect/wound/pierce/severe scar_keyword = "piercesevere" -/datum/wound/pierce/severe/update_descriptions() - if(no_bleeding) +/datum/wound_pregen_data/flesh_pierce/open_puncture + abstract = FALSE + + wound_path_to_generate = /datum/wound/pierce/bleed/severe + + threshold_minimum = 50 + +/datum/wound/pierce/bleed/severe/update_descriptions() + if(!limb.can_bleed()) occur_text = "tears a hole open" -/datum/wound/pierce/critical +/datum/wound/pierce/bleed/critical name = "Ruptured Cavity" desc = "Patient's internal tissue and circulatory system is shredded, causing significant internal bleeding and damage to internal organs." treat_text = "Surgical repair of puncture wound, followed by supervised resanguination." @@ -195,8 +253,14 @@ gauzed_clot_rate = 0.4 internal_bleeding_chance = 80 internal_bleeding_coefficient = 1.75 - threshold_minimum = 100 threshold_penalty = 50 status_effect_type = /datum/status_effect/wound/pierce/critical scar_keyword = "piercecritical" - wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE | MANGLES_FLESH) + wound_flags = (ACCEPTS_GAUZE | MANGLES_EXTERIOR | CAN_BE_GRASPED) + +/datum/wound_pregen_data/flesh_pierce/cavity + abstract = FALSE + + wound_path_to_generate = /datum/wound/pierce/bleed/critical + + threshold_minimum = 100 diff --git a/code/datums/wounds/scars/_scars.dm b/code/datums/wounds/scars/_scars.dm index 743ac98e730d..5ee38f34f67a 100644 --- a/code/datums/wounds/scars/_scars.dm +++ b/code/datums/wounds/scars/_scars.dm @@ -22,11 +22,14 @@ var/visibility = 2 /// Whether this scar can actually be covered up by clothing var/coverable = TRUE - /// Obviously, scars that describe damaged flesh wouldn't apply to a skeleton (in some cases like bone wounds, there can be different descriptions for skeletons and fleshy humanoids) - var/biology = BIO_FLESH_BONE /// If we're a persistent scar or may become one, we go in this character slot var/persistent_character_slot = 0 + /// The biostates we require from a limb to give them our scar. + var/required_limb_biostate + /// If false, we will only check to see if a limb has ALL our biostates, instead of just any. + var/check_any_biostates + /datum/scar/Destroy(force, ...) if(limb) LAZYREMOVE(limb.scars, src) @@ -47,6 +50,19 @@ * * add_to_scars- Should always be TRUE unless you're just storing a scar for later usage, like how cuts want to store a scar for the highest severity of cut, rather than the severity when the wound is fully healed (probably demoted to moderate) */ /datum/scar/proc/generate(obj/item/bodypart/BP, datum/wound/W, add_to_scars=TRUE) + + if (!W.can_scar) + qdel(src) + return + + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[W.type] + if (!pregen_data) + qdel(src) + return + + required_limb_biostate = pregen_data.required_limb_biostate + check_any_biostates = pregen_data.require_any_biostate + limb = BP RegisterSignal(limb, COMSIG_PARENT_QDELETING, PROC_REF(limb_gone)) @@ -59,12 +75,13 @@ if(victim) LAZYADD(victim.all_scars, src) - biology = limb?.biological_state || BIO_FLESH_BONE + var/scar_file = W.get_scar_file(BP, add_to_scars) + var/scar_keyword = W.get_scar_keyword(BP, add_to_scars) + if (!scar_file || !scar_keyword) + qdel(src) + return - if((biology & BIO_BONE) && !(biology & BIO_FLESH)) - description = pick_list(BONE_SCAR_FILE, W.scar_keyword) || "general disfigurement" - else // no specific support for flesh w/o bone scars since it's not really useful - description = pick_list(FLESH_SCAR_FILE, W.scar_keyword) || "general disfigurement" + description = pick_list(W.get_scar_file(BP, add_to_scars), W.get_scar_keyword(BP, add_to_scars)) || "general disfigurement" precise_location = pick_list_replacements(SCAR_LOC_FILE, limb.body_zone) switch(W.severity) @@ -86,22 +103,28 @@ LAZYADD(victim.all_scars, src) /// Used to "load" a persistent scar -/datum/scar/proc/load(obj/item/bodypart/BP, version, description, specific_location, severity=WOUND_SEVERITY_SEVERE, biology=BIO_FLESH_BONE, char_slot) - if(!IS_ORGANIC_LIMB(BP)) +/datum/scar/proc/load(obj/item/bodypart/BP, version, description, specific_location, severity = WOUND_SEVERITY_SEVERE, required_limb_biostate = BIO_STANDARD_UNJOINTED, char_slot, check_any_biostates = FALSE) + if(!BP.scarrable) qdel(src) return limb = BP RegisterSignal(limb, COMSIG_PARENT_QDELETING, PROC_REF(limb_gone)) - if(limb.owner) - victim = limb.owner - if(limb.biological_state != biology) + if (isnull(check_any_biostates)) // so we dont break old scars. NOTE: REMOVE AFTER VERSION NUMBER MOVES PAST 3 + check_any_biostates = FALSE + if (check_any_biostates) + if (!(limb.biological_state & required_limb_biostate)) qdel(src) return + else if (!((limb.biological_state & required_limb_biostate) == required_limb_biostate)) // check for all + qdel(src) + return + if(limb.owner) + victim = limb.owner LAZYADD(victim.all_scars, src) src.severity = severity - src.biology = biology + src.required_limb_biostate = required_limb_biostate persistent_character_slot = char_slot LAZYADD(limb.scars, src) @@ -163,9 +186,9 @@ /// Used to format a scar to save for either persistent scars, or for changeling disguises /datum/scar/proc/format() - return "[SCAR_CURRENT_VERSION]|[limb.body_zone]|[description]|[precise_location]|[severity]|[biology]|[persistent_character_slot]" + return "[SCAR_CURRENT_VERSION]|[limb.body_zone]|[description]|[precise_location]|[severity]|[required_limb_biostate]|[persistent_character_slot]|[check_any_biostates]" /// Used to format a scar to save in preferences for persistent scars -/datum/scar/proc/format_amputated(body_zone) - description = pick_list(FLESH_SCAR_FILE, "dismember") - return "[SCAR_CURRENT_VERSION]|[body_zone]|[description]|amputated|[WOUND_SEVERITY_LOSS]|[BIO_FLESH_BONE]|[persistent_character_slot]" +/datum/scar/proc/format_amputated(body_zone, scar_file = FLESH_SCAR_FILE) + description = pick_list(scar_file, "dismember") + return "[SCAR_CURRENT_VERSION]|[body_zone]|[description]|amputated|[WOUND_SEVERITY_LOSS]|[required_limb_biostate]|[persistent_character_slot]|[check_any_biostates]" diff --git a/code/datums/wounds/scars/_static_scar_data.dm b/code/datums/wounds/scars/_static_scar_data.dm new file mode 100644 index 000000000000..942dcffff9c2 --- /dev/null +++ b/code/datums/wounds/scars/_static_scar_data.dm @@ -0,0 +1,20 @@ +GLOBAL_LIST_INIT_TYPED(all_static_scar_data, /datum/static_scar_data, generate_static_scar_data()) + +/proc/generate_static_scar_data() + RETURN_TYPE(/list/datum/static_scar_data) + + var/list/datum/wound_pregen_data/data = list() + + for (var/datum/wound_pregen_data/path as anything in typecacheof(path = /datum/static_scar_data, ignore_root_path = TRUE)) + if (initial(path.abstract)) + continue + + var/datum/wound_pregen_data/pregen_data = new path + data[pregen_data.wound_path_to_generate] = pregen_data + + return data + +/datum/static_scar_data + var/abstract = FALSE + + diff --git a/code/datums/wounds/slash.dm b/code/datums/wounds/slash.dm index 181bfc2a012d..c2869c947375 100644 --- a/code/datums/wounds/slash.dm +++ b/code/datums/wounds/slash.dm @@ -6,13 +6,25 @@ /datum/wound/slash name = "Slashing (Cut) Wound" sound_effect = 'sound/weapons/slice.ogg' + +/datum/wound_pregen_data/flesh_slash + abstract = TRUE + + required_wounding_types = list(WOUND_SLASH) + required_limb_biostate = BIO_FLESH + + wound_series = WOUND_SERIES_FLESH_SLASH_BLEED + +/datum/wound/slash/flesh + name = "Slashing (Cut) Flesh Wound" processes = TRUE - wound_type = WOUND_SLASH treatable_by = list(/obj/item/stack/medical/suture) treatable_by_grabbed = list(/obj/item/gun/energy/laser) - treatable_tool = TOOL_CAUTERY + treatable_tools = list(TOOL_CAUTERY) base_treat_time = 3 SECONDS - wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE) + wound_flags = (ACCEPTS_GAUZE|CAN_BE_GRASPED) + + default_scar_file = FLESH_SCAR_FILE /// How much blood we start losing when this wound is first applied var/initial_flow @@ -30,7 +42,12 @@ /// A bad system I'm using to track the worst scar we earned (since we can demote, we want the biggest our wound has been, not what it was when it was cured (probably moderate)) var/datum/scar/highest_scar -/datum/wound/slash/wound_injury(datum/wound/slash/old_wound = null, attack_direction = null) +/datum/wound/slash/flesh/Destroy() + highest_scar = null + + return ..() + +/datum/wound/slash/flesh/wound_injury(datum/wound/slash/flesh/old_wound = null, attack_direction = null) if(old_wound) set_blood_flow(max(old_wound.blood_flow, initial_flow)) if(old_wound.severity > severity && old_wound.highest_scar) @@ -38,7 +55,7 @@ old_wound.clear_highest_scar() else set_blood_flow(initial_flow) - if(!no_bleeding && attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY) + if(limb.can_bleed() && attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY) victim.spray_blood(attack_direction, severity) if(!highest_scar) @@ -46,24 +63,26 @@ set_highest_scar(new_scar) new_scar.generate(limb, src, add_to_scars=FALSE) -/datum/wound/slash/proc/set_highest_scar(datum/scar/new_scar) + return ..() + +/datum/wound/slash/flesh/proc/set_highest_scar(datum/scar/new_scar) if(highest_scar) UnregisterSignal(highest_scar, COMSIG_PARENT_QDELETING) if(new_scar) RegisterSignal(new_scar, COMSIG_PARENT_QDELETING, PROC_REF(clear_highest_scar)) highest_scar = new_scar -/datum/wound/slash/proc/clear_highest_scar(datum/source) +/datum/wound/slash/flesh/proc/clear_highest_scar(datum/source) SIGNAL_HANDLER set_highest_scar(null) -/datum/wound/slash/remove_wound(ignore_limb, replaced) +/datum/wound/slash/flesh/remove_wound(ignore_limb, replaced) if(!replaced && highest_scar) already_scarred = TRUE highest_scar.lazy_attach(limb) return ..() -/datum/wound/slash/get_examine_description(mob/user) +/datum/wound/slash/flesh/get_wound_description(mob/user) if(!limb.current_gauze) return ..() @@ -82,11 +101,16 @@ return "[msg.Join()]" -/datum/wound/slash/receive_damage(wounding_type, wounding_dmg, wound_bonus) +/datum/wound/slash/flesh/receive_damage(wounding_type, wounding_dmg, wound_bonus) + if (!victim) // if we are dismembered, we can still take damage, its fine to check here + return + if(victim.stat != DEAD && wound_bonus != CANT_WOUND && wounding_type == WOUND_SLASH) // can't stab dead bodies to make it bleed faster this way adjust_blood_flow(WOUND_SLASH_DAMAGE_FLOW_COEFF * wounding_dmg) -/datum/wound/slash/drag_bleed_amount() + return ..() + +/datum/wound/slash/flesh/drag_bleed_amount() // say we have 3 severe cuts with 3 blood flow each, pretty reasonable // compare with being at 100 brute damage before, where you bled (brute/100 * 2), = 2 blood per tile var/bleed_amt = min(blood_flow * 0.1, 1) // 3 * 3 * 0.1 = 0.9 blood total, less than before! the share here is .3 blood of course. @@ -97,9 +121,9 @@ return bleed_amt -/datum/wound/slash/get_bleed_rate_of_change() +/datum/wound/slash/flesh/get_bleed_rate_of_change() //basically if a species doesn't bleed, the wound is stagnant and will not heal on it's own (nor get worse) - if(no_bleeding) + if(!limb.can_bleed()) return BLOOD_FLOW_STEADY if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS)) return BLOOD_FLOW_INCREASING @@ -108,9 +132,13 @@ if(clot_rate < 0) return BLOOD_FLOW_INCREASING -/datum/wound/slash/handle_process(seconds_per_tick, times_fired) +/datum/wound/slash/flesh/handle_process(seconds_per_tick, times_fired) + + if (!victim || IS_IN_STASIS(victim)) + return + // in case the victim has the NOBLOOD trait, the wound will simply not clot on it's own - if(!no_bleeding) + if(limb.can_bleed()) set_blood_flow(min(blood_flow, WOUND_SLASH_MAX_BLOODFLOW)) if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS)) @@ -123,7 +151,7 @@ adjust_blood_flow(-limb.current_gauze.absorption_rate * seconds_per_tick) limb.seep_gauze(limb.current_gauze.absorption_rate * seconds_per_tick) //otherwise, only clot if it's a bleeder - else if(!no_bleeding) + else if(limb.can_bleed()) adjust_blood_flow(-clot_rate * seconds_per_tick) if(blood_flow > highest_flow) @@ -131,36 +159,36 @@ if(blood_flow < minimum_flow) if(demotes_to) - replace_wound(demotes_to) + replace_wound(new demotes_to) else - to_chat(victim, span_green("The cut on your [limb.plaintext_zone] has [no_bleeding ? "healed up" : "stopped bleeding"]!")) + to_chat(victim, span_green("The cut on your [limb.plaintext_zone] has [!limb.can_bleed() ? "healed up" : "stopped bleeding"]!")) qdel(src) -/datum/wound/slash/on_stasis(seconds_per_tick, times_fired) +/datum/wound/slash/flesh/on_stasis(seconds_per_tick, times_fired) if(blood_flow >= minimum_flow) return if(demotes_to) - replace_wound(demotes_to) + replace_wound(new demotes_to) return qdel(src) /* BEWARE, THE BELOW NONSENSE IS MADNESS. bones.dm looks more like what I have in mind and is sufficiently clean, don't pay attention to this messiness */ -/datum/wound/slash/check_grab_treatments(obj/item/I, mob/user) +/datum/wound/slash/flesh/check_grab_treatments(obj/item/I, mob/user) if(istype(I, /obj/item/gun/energy/laser)) return TRUE if(I.get_temperature()) // if we're using something hot but not a cautery, we need to be aggro grabbing them first, so we don't try treating someone we're eswording return TRUE -/datum/wound/slash/treat(obj/item/I, mob/user) +/datum/wound/slash/flesh/treat(obj/item/I, mob/user) if(istype(I, /obj/item/gun/energy/laser)) - las_cauterize(I, user) + return las_cauterize(I, user) else if(I.tool_behaviour == TOOL_CAUTERY || I.get_temperature()) - tool_cauterize(I, user) + return tool_cauterize(I, user) else if(istype(I, /obj/item/stack/medical/suture)) - suture(I, user) + return suture(I, user) -/datum/wound/slash/try_handling(mob/living/carbon/human/user) +/datum/wound/slash/flesh/try_handling(mob/living/carbon/human/user) if(user.pulling != victim || user.zone_selected != limb.body_zone || !victim.try_inject(user, injection_flags = INJECT_TRY_SHOW_ERROR_MESSAGE)) return FALSE if(DOING_INTERACTION_WITH_TARGET(user, victim)) @@ -175,16 +203,41 @@ return TRUE -/datum/wound/slash/on_xadone(power) +/// if a felinid is licking this cut to reduce bleeding +/datum/wound/slash/flesh/proc/lick_wounds(mob/living/carbon/human/user) + // transmission is one way patient -> felinid since google said cat saliva is antiseptic or whatever, and also because felinids are already risking getting beaten for this even without people suspecting they're spreading a deathvirus + for(var/i in victim.diseases) + var/datum/disease/iter_disease = i + if(iter_disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS)) + continue + user.ForceContractDisease(iter_disease) + + user.visible_message(span_notice("[user] begins licking the wounds on [victim]'s [limb.plaintext_zone]."), span_notice("You begin licking the wounds on [victim]'s [limb.plaintext_zone]..."), ignored_mobs=victim) + to_chat(victim, span_notice("[user] begins to lick the wounds on your [limb.plaintext_zone].")) + if(!do_after(user, base_treat_time, target=victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return + + user.visible_message(span_notice("[user] licks the wounds on [victim]'s [limb.plaintext_zone]."), span_notice("You lick some of the wounds on [victim]'s [limb.plaintext_zone]"), ignored_mobs=victim) + to_chat(victim, span_green("[user] licks the wounds on your [limb.plaintext_zone]!")) + adjust_blood_flow(-0.5) + + if(blood_flow > minimum_flow) + try_handling(user) + else if(demotes_to) + to_chat(user, span_green("You successfully lower the severity of [victim]'s cuts.")) + +/datum/wound/slash/flesh/on_xadone(power) . = ..() - adjust_blood_flow(-0.03 * power) // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick is pretty good for 0 effort -/datum/wound/slash/on_synthflesh(power) + if (limb) // parent can cause us to be removed, so its reasonable to check if we're still applied + adjust_blood_flow(-0.03 * power) // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick is pretty good for 0 effort + +/datum/wound/slash/flesh/on_synthflesh(power) . = ..() adjust_blood_flow(-0.075 * power) // 20u * 0.075 = -1.5 blood flow, pretty good for how little effort it is /// If someone's putting a laser gun up to our cut to cauterize it -/datum/wound/slash/proc/las_cauterize(obj/item/gun/energy/laser/lasgun, mob/user) +/datum/wound/slash/flesh/proc/las_cauterize(obj/item/gun/energy/laser/lasgun, mob/user) var/self_penalty_mult = (user == victim ? 1.25 : 1) user.visible_message(span_warning("[user] begins aiming [lasgun] directly at [victim]'s [limb.plaintext_zone]..."), span_userdanger("You begin aiming [lasgun] directly at [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone]...")) if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) @@ -197,16 +250,17 @@ victim.emote("scream") adjust_blood_flow(-1 * (damage / (5 * self_penalty_mult))) // 20 / 5 = 4 bloodflow removed, p good victim.visible_message(span_warning("The cuts on [victim]'s [limb.plaintext_zone] scar over!")) + return TRUE /// If someone is using either a cautery tool or something with heat to cauterize this cut -/datum/wound/slash/proc/tool_cauterize(obj/item/I, mob/user) +/datum/wound/slash/flesh/proc/tool_cauterize(obj/item/I, mob/user) var/improv_penalty_mult = (I.tool_behaviour == TOOL_CAUTERY ? 1 : 1.25) // 25% longer and less effective if you don't use a real cautery var/self_penalty_mult = (user == victim ? 1.5 : 1) // 50% longer and less effective if you do it to yourself user.visible_message(span_danger("[user] begins cauterizing [victim]'s [limb.plaintext_zone] with [I]..."), span_warning("You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]...")) if(!do_after(user, base_treat_time * self_penalty_mult * improv_penalty_mult, target=victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) return - var/bleeding_wording = (no_bleeding ? "cuts" : "bleeding") + var/bleeding_wording = (!limb.can_bleed() ? "cuts" : "bleeding") user.visible_message(span_green("[user] cauterizes some of the [bleeding_wording] on [victim]."), span_green("You cauterize some of the [bleeding_wording] on [victim].")) limb.receive_damage(burn = 2 + severity, wound_bonus = CANT_WOUND) if(prob(30)) @@ -215,18 +269,27 @@ adjust_blood_flow(-blood_cauterized) if(blood_flow > minimum_flow) - try_treating(I, user) + return try_treating(I, user) else if(demotes_to) to_chat(user, span_green("You successfully lower the severity of [user == victim ? "your" : "[victim]'s"] cuts.")) + return TRUE + return FALSE /// If someone is using a suture to close this cut -/datum/wound/slash/proc/suture(obj/item/stack/medical/suture/I, mob/user) +/datum/wound/slash/flesh/proc/suture(obj/item/stack/medical/suture/I, mob/user) var/self_penalty_mult = (user == victim ? 1.4 : 1) user.visible_message(span_notice("[user] begins stitching [victim]'s [limb.plaintext_zone] with [I]..."), span_notice("You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]...")) + var/treatment_delay = base_treat_time * self_penalty_mult - if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) - return - var/bleeding_wording = (no_bleeding ? "cuts" : "bleeding") + if(HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + treatment_delay *= 0.5 + user.visible_message(span_notice("[user] begins expertly stitching [victim]'s [limb.plaintext_zone] with [I]..."), span_notice("You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I], keeping the holo-image information in mind...")) + else + user.visible_message(span_notice("[user] begins stitching [victim]'s [limb.plaintext_zone] with [I]..."), span_notice("You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]...")) + + if(!do_after(user, treatment_delay, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + var/bleeding_wording = (!limb.can_bleed() ? "cuts" : "bleeding") user.visible_message(span_green("[user] stitches up some of the [bleeding_wording] on [victim]."), span_green("You stitch up some of the [bleeding_wording] on [user == victim ? "yourself" : "[victim]"].")) var/blood_sutured = I.stop_bleeding / self_penalty_mult adjust_blood_flow(-blood_sutured) @@ -234,11 +297,16 @@ I.use(1) if(blood_flow > minimum_flow) - try_treating(I, user) + return try_treating(I, user) else if(demotes_to) to_chat(user, span_green("You successfully lower the severity of [user == victim ? "your" : "[victim]'s"] cuts.")) + return TRUE + return TRUE + +/datum/wound/slash/get_limb_examine_description() + return span_warning("The flesh on this limb appears badly lacerated.") -/datum/wound/slash/moderate +/datum/wound/slash/flesh/moderate name = "Rough Abrasion" desc = "Patient's skin has been badly scraped, generating moderate blood loss." treat_text = "Application of clean bandages or first-aid grade sutures, followed by food and rest." @@ -249,16 +317,22 @@ initial_flow = 2 minimum_flow = 0.5 clot_rate = 0.05 - threshold_minimum = 20 threshold_penalty = 10 - status_effect_type = /datum/status_effect/wound/slash/moderate + status_effect_type = /datum/status_effect/wound/slash/flesh/moderate scar_keyword = "slashmoderate" -/datum/wound/slash/moderate/update_descriptions() - if(no_bleeding) +/datum/wound/slash/flesh/moderate/update_descriptions() + if(!limb.can_bleed()) occur_text = "is cut open" -/datum/wound/slash/severe +/datum/wound_pregen_data/flesh_slash/abrasion + abstract = FALSE + + wound_path_to_generate = /datum/wound/slash/flesh/moderate + + threshold_minimum = 20 + +/datum/wound/slash/flesh/severe name = "Open Laceration" desc = "Patient's skin is ripped clean open, allowing significant blood loss." treat_text = "Speedy application of first-aid grade sutures and clean bandages, followed by vitals monitoring to ensure recovery." @@ -269,48 +343,74 @@ initial_flow = 3.25 minimum_flow = 2.75 clot_rate = 0.03 - threshold_minimum = 50 threshold_penalty = 25 - demotes_to = /datum/wound/slash/moderate - status_effect_type = /datum/status_effect/wound/slash/severe + demotes_to = /datum/wound/slash/flesh/moderate + status_effect_type = /datum/status_effect/wound/slash/flesh/severe scar_keyword = "slashsevere" -/datum/wound/slash/severe/update_descriptions() - if(no_bleeding) +/datum/wound_pregen_data/flesh_slash/laceration + abstract = FALSE + + wound_path_to_generate = /datum/wound/slash/flesh/severe + + threshold_minimum = 50 + +/datum/wound/slash/flesh/severe/update_descriptions() + if(!limb.can_bleed()) occur_text = "is ripped open" -/datum/wound/slash/critical +/datum/wound/slash/flesh/critical name = "Weeping Avulsion" desc = "Patient's skin is completely torn open, along with significant loss of tissue. Extreme blood loss will lead to quick death without intervention." treat_text = "Immediate bandaging and either suturing or cauterization, followed by supervised resanguination." examine_desc = "is carved down to the bone, spraying blood wildly" - examine_desc = "is carved down to the bone" occur_text = "is torn open, spraying blood wildly" - occur_text = "is torn open" sound_effect = 'sound/effects/wounds/blood3.ogg' severity = WOUND_SEVERITY_CRITICAL initial_flow = 4 minimum_flow = 3.85 clot_rate = -0.015 // critical cuts actively get worse instead of better - threshold_minimum = 80 threshold_penalty = 40 - demotes_to = /datum/wound/slash/severe - status_effect_type = /datum/status_effect/wound/slash/critical + demotes_to = /datum/wound/slash/flesh/severe + status_effect_type = /datum/status_effect/wound/slash/flesh/critical scar_keyword = "slashcritical" - wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE | MANGLES_FLESH) + wound_flags = (ACCEPTS_GAUZE | MANGLES_EXTERIOR | CAN_BE_GRASPED) + +/datum/wound/slash/flesh/critical/update_descriptions() + if (!limb.can_bleed()) + occur_text = "is torn open" + +/datum/wound_pregen_data/flesh_slash/avulsion + abstract = FALSE + + wound_path_to_generate = /datum/wound/slash/flesh/critical -/datum/wound/slash/moderate/many_cuts + threshold_minimum = 80 + +/datum/wound/slash/flesh/moderate/many_cuts name = "Numerous Small Slashes" desc = "Patient's skin has numerous small slashes and cuts, generating moderate blood loss." examine_desc = "has a ton of small cuts" occur_text = "is cut numerous times, leaving many small slashes." +/datum/wound_pregen_data/flesh_slash/abrasion/cuts + abstract = FALSE + can_be_randomly_generated = FALSE + + wound_path_to_generate = /datum/wound/slash/flesh/moderate/many_cuts + // Subtype for cleave (heretic spell) -/datum/wound/slash/critical/cleave +/datum/wound/slash/flesh/critical/cleave name = "Burning Avulsion" examine_desc = "is ruptured, spraying blood wildly" clot_rate = 0.01 -/datum/wound/slash/critical/cleave/update_descriptions() - if(no_bleeding) +/datum/wound/slash/flesh/critical/cleave/update_descriptions() + if(!limb.can_bleed()) occur_text = "is ruptured" + +/datum/wound_pregen_data/flesh_slash/avulsion/clear + abstract = FALSE + can_be_randomly_generated = FALSE + + wound_path_to_generate = /datum/wound/slash/flesh/critical/cleave diff --git a/code/game/alternate_appearance.dm b/code/game/alternate_appearance.dm index 994f51d13dfc..9b8a813c7398 100644 --- a/code/game/alternate_appearance.dm +++ b/code/game/alternate_appearance.dm @@ -149,7 +149,7 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances) /datum/atom_hud/alternate_appearance/basic/blessed_aware/mobShouldSee(mob/M) if(M.mind?.holy_role) return TRUE - if (istype(M, /mob/living/simple_animal/hostile/construct/wraith)) + if (istype(M, /mob/living/basic/construct/wraith)) return TRUE if(isrevenant(M) || IS_WIZARD(M) || IS_CLOCK(M)) //monkestation edit: adds IS_CLOCK check return TRUE diff --git a/code/game/atom_defense.dm b/code/game/atom_defense.dm index dd4cbbc46b45..7d6fa23911f9 100644 --- a/code/game/atom_defense.dm +++ b/code/game/atom_defense.dm @@ -134,6 +134,8 @@ /// A cut-out proc for [/atom/proc/bullet_act] so living mobs can have their own armor behavior checks without causing issues with needing their own on_hit call /atom/proc/check_projectile_armor(def_zone, obj/projectile/impacting_projectile, is_silent) + if(uses_integrity) + return clamp(PENETRATE_ARMOUR(get_armor_rating(impacting_projectile.armor_flag), impacting_projectile.armour_penetration), 0, 100) return 0 /** diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 9f7d6effc033..53d633b736eb 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -393,6 +393,8 @@ /atom/proc/CanPass(atom/movable/mover, border_dir) SHOULD_CALL_PARENT(TRUE) SHOULD_BE_PURE(TRUE) + if(SEND_SIGNAL(src, COMSIG_ATOM_TRIED_PASS, mover, border_dir) & COMSIG_COMPONENT_PERMIT_PASSAGE) + return TRUE if(mover.movement_type & PHASING) return TRUE . = CanAllowThrough(mover, border_dir) @@ -612,6 +614,7 @@ SEND_SIGNAL(source, COMSIG_REAGENTS_EXPOSE_ATOM, src, reagents, methods, volume_modifier, show_message) for(var/datum/reagent/current_reagent as anything in reagents) . |= current_reagent.expose_atom(src, reagents[current_reagent]) + SEND_SIGNAL(src, COMSIG_ATOM_AFTER_EXPOSE_REAGENTS, reagents, source, methods, volume_modifier, show_message) /// Are you allowed to drop this atom /atom/proc/AllowDrop() @@ -640,19 +643,33 @@ /** * React to a hit by a projectile object * - * Default behaviour is to send the [COMSIG_ATOM_BULLET_ACT] and then call [on_hit][/obj/projectile/proc/on_hit] on the projectile. - * * @params - * hitting_projectile - projectile - * def_zone - zone hit - * piercing_hit - is this hit piercing or normal? + * * hitting_projectile - projectile + * * def_zone - zone hit + * * piercing_hit - is this hit piercing or normal? */ /atom/proc/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE) + SHOULD_CALL_PARENT(TRUE) + + var/sigreturn = SEND_SIGNAL(src, COMSIG_ATOM_PRE_BULLET_ACT, hitting_projectile, def_zone) + if(sigreturn & COMPONENT_BULLET_PIERCED) + return BULLET_ACT_FORCE_PIERCE + if(sigreturn & COMPONENT_BULLET_BLOCKED) + return BULLET_ACT_BLOCK + if(sigreturn & COMPONENT_BULLET_ACTED) + return BULLET_ACT_HIT + SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, hitting_projectile, def_zone) - // This armor check only matters for the visuals and messages in on_hit(), it's not actually used to reduce damage since - // only living mobs use armor to reduce damage, but on_hit() is going to need the value no matter what is shot. - var/visual_armor_check = check_projectile_armor(def_zone, hitting_projectile) - . = hitting_projectile.on_hit(src, visual_armor_check, def_zone, piercing_hit) + if(QDELETED(hitting_projectile)) // Signal deleted it? + return BULLET_ACT_BLOCK + + return hitting_projectile.on_hit( + target = src, + // This armor check only matters for the visuals and messages in on_hit(), it's not actually used to reduce damage since + // only living mobs use armor to reduce damage, but on_hit() is going to need the value no matter what is shot. + blocked = check_projectile_armor(def_zone, hitting_projectile), + pierce_hit = piercing_hit, + ) ///Return true if we're inside the passed in atom /atom/proc/in_contents_of(container)//can take class or object instance as argument diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index c2ee234d5857..38b5c1f9712a 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -15,7 +15,11 @@ var/tk_throw_range = 10 var/mob/pulledby = null var/initial_language_holder = /datum/language_holder - var/datum/language_holder/language_holder // Mindless mobs and objects need language too, some times. Mind holder takes prescedence. + /// Holds all languages this mob can speak and understand + var/datum/language_holder/language_holder + /// The list of factions this atom belongs to + var/list/faction + var/verb_say = "says" var/verb_ask = "asks" var/verb_exclaim = "exclaims" @@ -1664,3 +1668,20 @@ */ /atom/movable/proc/keybind_face_direction(direction) setDir(direction) + +/** + * Check if the other atom/movable has any factions the same as us. Defined at the atom/movable level so it can be defined for just about anything. + * + * If exact match is set, then all our factions must match exactly + */ +/atom/movable/proc/faction_check_atom(atom/movable/target, exact_match) + if(!exact_match) + return faction_check(faction, target.faction, FALSE) + + var/list/faction_src = LAZYCOPY(faction) + var/list/faction_target = LAZYCOPY(target.faction) + if(!("[REF(src)]" in faction_target)) //if they don't have our ref faction, remove it from our factions list. + faction_src -= "[REF(src)]" //if we don't do this, we'll never have an exact match. + if(!("[REF(target)]" in faction_src)) + faction_target -= "[REF(target)]" //same thing here. + return faction_check(faction_src, faction_target, TRUE) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 286fc3d3d843..ee7de2c50afd 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -576,7 +576,7 @@ var/datum/mind/player_mind = new /datum/mind(applicant.key) player_mind.active = TRUE - var/mob/living/simple_animal/hostile/space_dragon/S = new (pick(spawn_locs)) + var/mob/living/basic/space_dragon/S = new (pick(spawn_locs)) player_mind.transfer_to(S) player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/space_dragon)) player_mind.special_role = ROLE_SPACE_DRAGON @@ -714,8 +714,8 @@ . = ..() /datum/dynamic_ruleset/midround/from_ghosts/revenant/generate_ruleset_body(mob/applicant) - var/mob/living/simple_animal/revenant/revenant = new(pick(spawn_locs)) - revenant.key = applicant.key + var/mob/living/basic/revenant/revenant = new(pick(spawn_locs)) + applicant.mind.transfer_to(revenant) message_admins("[ADMIN_LOOKUPFLW(revenant)] has been made into a revenant by the midround ruleset.") log_game("[key_name(revenant)] was spawned as a revenant by the midround ruleset.") return revenant diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm index 1562914ea66b..9e1ae52dc9ac 100644 --- a/code/game/gamemodes/objective_items.dm +++ b/code/game/gamemodes/objective_items.dm @@ -124,6 +124,40 @@ targetitem = /obj/item/clothing/shoes/clown_shoes excludefromjob = list(JOB_CLOWN, JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER) item_owner = list(JOB_CLOWN) + exists_on_map = TRUE + +/obj/item/clothing/shoes/clown_shoes/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/clothing/shoes/clown_shoes) + +/datum/objective_item/steal/traitor/mime_mask + name = "the mime's mask" + targetitem = /obj/item/clothing/mask/gas/mime + excludefromjob = list(JOB_MIME, JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER) + item_owner = list(JOB_MIME) + exists_on_map = TRUE + +/obj/item/clothing/mask/gas/mime/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/clothing/mask/gas/mime) + +/datum/objective_item/steal/traitor/pka + name = "a protokinetic accelerator" + targetitem = /obj/item/gun/energy/recharge/kinetic_accelerator + excludefromjob = list(JOB_SHAFT_MINER, JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER) + item_owner = list(JOB_SHAFT_MINER) + exists_on_map = TRUE + +/obj/item/gun/energy/recharge/kinetic_accelerator/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/gun/energy/recharge/kinetic_accelerator) + +/datum/objective_item/steal/traitor/chef_moustache + name = "a fancy fake moustache" + targetitem = /obj/item/clothing/mask/fakemoustache/italian + excludefromjob = list(JOB_COOK, JOB_HEAD_OF_PERSONNEL, JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER) + item_owner = list(JOB_COOK) + exists_on_map = TRUE + +/obj/item/clothing/mask/fakemoustache/italian/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/clothing/mask/fakemoustache/italian) /datum/objective_item/steal/traitor/det_revolver name = "detective's revolver" @@ -134,6 +168,16 @@ /obj/item/gun/ballistic/revolver/c38/detective/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/gun/ballistic/revolver/c38/detective) +/datum/objective_item/steal/traitor/lawyers_badge + name = "the lawyer's badge" + targetitem = /obj/item/clothing/accessory/lawyers_badge + excludefromjob = list(JOB_LAWYER) + item_owner = list(JOB_LAWYER) + exists_on_map = TRUE + +/obj/item/clothing/accessory/lawyers_badge/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/clothing/accessory/lawyers_badge) + /datum/objective_item/steal/traitor/chief_engineer_belt name = "the chief engineer's belt" targetitem = /obj/item/storage/belt/utility/chief @@ -402,3 +446,79 @@ /obj/item/blackbox/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/blackbox) + + +// A number of special early-game steal objectives intended to be used with the steal-and-destroy objective. +// They're basically items of utility or emotional value that may be found on many players or lying around the station. +/datum/objective_item/steal/traitor/insuls + name = "insulated gloves" + targetitem = /obj/item/clothing/gloves/color/yellow + excludefromjob = list(JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER, JOB_ATMOSPHERIC_TECHNICIAN, JOB_STATION_ENGINEER, JOB_CHIEF_ENGINEER) + item_owner = list(JOB_STATION_ENGINEER, JOB_CHIEF_ENGINEER) + exists_on_map = TRUE + +/obj/item/clothing/gloves/color/yellow/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/clothing/gloves/color/yellow) + +/datum/objective_item/steal/traitor/moth_plush + name = "cute moth plush toy" + targetitem = /obj/item/toy/plush/moth + excludefromjob = list(JOB_PSYCHOLOGIST, JOB_PARAMEDIC, JOB_CHEMIST, JOB_MEDICAL_DOCTOR, JOB_VIROLOGIST, JOB_CHIEF_MEDICAL_OFFICER) + exists_on_map = TRUE + +/obj/item/toy/plush/moth/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/toy/plush/moth) + +/datum/objective_item/steal/traitor/lizard_plush + name = "cute lizard plush toy" + targetitem = /obj/item/toy/plush/lizard_plushie + exists_on_map = TRUE + +/obj/item/toy/plush/lizard_plushie/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/toy/plush/lizard_plushie) + +/datum/objective_item/steal/traitor/denied_stamp + name = "cargo's denied stamp" + targetitem = /obj/item/stamp/denied + excludefromjob = list(JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER, JOB_SHAFT_MINER) + exists_on_map = TRUE + +/obj/item/stamp/denied/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/stamp/denied) + +/datum/objective_item/steal/traitor/granted_stamp + name = "cargo's granted stamp" + targetitem = /obj/item/stamp/granted + excludefromjob = list(JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER, JOB_SHAFT_MINER) + exists_on_map = TRUE + +/obj/item/stamp/granted/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/stamp/granted) + +/datum/objective_item/steal/traitor/space_law + name = "a book on space law" + targetitem = /obj/item/book/manual/wiki/security_space_law + excludefromjob = list(JOB_SECURITY_OFFICER, JOB_WARDEN, JOB_HEAD_OF_SECURITY, JOB_LAWYER, JOB_DETECTIVE) + exists_on_map = TRUE + +/obj/item/book/manual/wiki/security_space_law/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/book/manual/wiki/security_space_law) + +/datum/objective_item/steal/traitor/rpd + name = "rapid pipe dispenser" + targetitem = /obj/item/pipe_dispenser + excludefromjob = list(JOB_ATMOSPHERIC_TECHNICIAN, JOB_STATION_ENGINEER, JOB_CHIEF_ENGINEER, JOB_SCIENTIST, JOB_RESEARCH_DIRECTOR, JOB_GENETICIST, JOB_ROBOTICIST) + item_owner = list(JOB_CHIEF_ENGINEER) + exists_on_map = TRUE + +/obj/item/pipe_dispenser/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/pipe_dispenser) + +/datum/objective_item/steal/traitor/donut_box + name = "a box of prized donuts" + targetitem = /obj/item/storage/fancy/donut_box + excludefromjob = list(JOB_CAPTAIN, JOB_CHIEF_ENGINEER, JOB_HEAD_OF_PERSONNEL, JOB_HEAD_OF_SECURITY, JOB_QUARTERMASTER, JOB_CHIEF_MEDICAL_OFFICER, JOB_RESEARCH_DIRECTOR, JOB_SECURITY_OFFICER, JOB_WARDEN, JOB_LAWYER, JOB_DETECTIVE) + exists_on_map = TRUE + +/obj/item/storage/fancy/donut_box/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/storage/fancy/donut_box) diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 2d6abc921ce7..5fca4a56b0d1 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -592,13 +592,7 @@ if(!isliving(user)) return FALSE //no ghosts allowed, sorry - var/is_dextrous = FALSE - if(isanimal(user)) - var/mob/living/simple_animal/user_as_animal = user - if (user_as_animal.dextrous) - is_dextrous = TRUE - - if(!issilicon(user) && !is_dextrous && !user.can_hold_items()) + if(!issilicon(user) && !user.can_hold_items()) return FALSE //spiders gtfo if(issilicon(user)) // If we are a silicon, make sure the machine allows silicons to interact with it @@ -745,7 +739,7 @@ return update_last_used(user) -/obj/machinery/tool_act(mob/living/user, obj/item/tool, tool_type) +/obj/machinery/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking) if(SEND_SIGNAL(user, COMSIG_TRY_USE_MACHINE, src) & COMPONENT_CANT_USE_MACHINE_TOOLS) return TOOL_ACT_MELEE_CHAIN_BLOCKING . = ..() diff --git a/code/game/machinery/computer/arcade/orion.dm b/code/game/machinery/computer/arcade/orion.dm index e883b35fe1df..5bcd81b9b49c 100644 --- a/code/game/machinery/computer/arcade/orion.dm +++ b/code/game/machinery/computer/arcade/orion.dm @@ -499,7 +499,7 @@ GLOBAL_LIST_INIT(orion_events, generate_orion_events()) newgame() obj_flags |= EMAGGED -/mob/living/basic/syndicate/ranged/smg/orion +/mob/living/basic/trooper/syndicate/ranged/smg/orion name = "spaceport security" desc = "Premier corporate security forces for all spaceports found along the Orion Trail." faction = list(FACTION_ORION) diff --git a/code/game/machinery/computer/arcade/orion_event.dm b/code/game/machinery/computer/arcade/orion_event.dm index ea81b67532d1..97590059a526 100644 --- a/code/game/machinery/computer/arcade/orion_event.dm +++ b/code/game/machinery/computer/arcade/orion_event.dm @@ -525,7 +525,7 @@ game.say("WEEWOO! WEEWOO! Spaceport security en route!") playsound(game, 'sound/items/weeoo1.ogg', 100, FALSE) for(var/i in 1 to 3) - var/mob/living/basic/syndicate/ranged/smg/orion/spaceport_security = new(get_turf(game)) + var/mob/living/basic/trooper/syndicate/ranged/smg/orion/spaceport_security = new(get_turf(game)) spaceport_security.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, usr) game.fuel += fuel game.food += food diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index 99c5dc00eb7f..1560d1f17228 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -248,9 +248,9 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) if (jobs[trim_assignment] != null) entry["ijob"] = jobs[trim_assignment] - // Binary living/dead status + // Current status if (sensor_mode >= SENSOR_LIVING) - entry["life_status"] = (tracked_living_mob.stat != DEAD) + entry["life_status"] = tracked_living_mob.stat // Damage if (sensor_mode >= SENSOR_VITALS) diff --git a/code/game/machinery/computer/orders/order_items/mining/order_toys.dm b/code/game/machinery/computer/orders/order_items/mining/order_toys.dm index 06bd9032e74e..cd1dbf5c6748 100644 --- a/code/game/machinery/computer/orders/order_items/mining/order_toys.dm +++ b/code/game/machinery/computer/orders/order_items/mining/order_toys.dm @@ -14,7 +14,7 @@ cost_per_order = 300 /datum/orderable_item/toys_drones/mining_drone - item_path = /mob/living/simple_animal/hostile/mining_drone + item_path = /mob/living/basic/mining_drone cost_per_order = 800 /datum/orderable_item/toys_drones/drone_health diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm index e3d3a0e2acba..88ca3b05df78 100644 --- a/code/game/machinery/computer/robot.dm +++ b/code/game/machinery/computer/robot.dm @@ -64,15 +64,15 @@ data["cyborgs"] += list(cyborg_data) data["drones"] = list() - for(var/mob/living/simple_animal/drone/D in GLOB.drones_list) - if(D.hacked) + for(var/mob/living/basic/drone/drone in GLOB.drones_list) + if(drone.hacked) continue - if(!is_valid_z_level(current_turf, get_turf(D))) + if(!is_valid_z_level(current_turf, get_turf(drone))) continue var/list/drone_data = list( - name = D.name, - status = D.stat, - ref = REF(D) + name = drone.name, + status = drone.stat, + ref = REF(drone) ) data["drones"] += list(drone_data) @@ -129,7 +129,7 @@ if("killdrone") if(allowed(usr)) - var/mob/living/simple_animal/drone/drone = locate(params["ref"]) in GLOB.mob_list + var/mob/living/basic/drone/drone = locate(params["ref"]) in GLOB.mob_list if(drone.hacked) to_chat(usr, span_danger("ERROR: [drone] is not responding to external commands.")) else diff --git a/code/game/machinery/iv_drip.dm b/code/game/machinery/iv_drip.dm index 9c98916ecdbe..e691c5d9fc1b 100644 --- a/code/game/machinery/iv_drip.dm +++ b/code/game/machinery/iv_drip.dm @@ -245,12 +245,12 @@ if(!(get_dist(src, attached) <= 1 && isturf(attached.loc))) if(isliving(attached)) - var/mob/living/attached_mob = attached + var/mob/living/carbon/attached_mob = attached to_chat(attached, span_userdanger("The IV drip needle is ripped out of you, leaving an open bleeding wound!")) var/list/arm_zones = shuffle(list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM)) var/obj/item/bodypart/chosen_limb = attached_mob.get_bodypart(arm_zones[1]) || attached_mob.get_bodypart(arm_zones[2]) || attached_mob.get_bodypart(BODY_ZONE_CHEST) chosen_limb.receive_damage(3) - chosen_limb.force_wound_upwards(/datum/wound/pierce/moderate) + attached_mob.cause_wound_of_type_and_severity(WOUND_PIERCE, chosen_limb, WOUND_SEVERITY_MODERATE, wound_source = "IV needle") else visible_message(span_warning("[attached] is detached from [src].")) detach_iv() diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index 885d00d62310..2a3f01c668eb 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -40,6 +40,8 @@ DEFINE_BITFIELD(turret_flags, list( armor_type = /datum/armor/machinery_porta_turret base_icon_state = "standard" blocks_emissive = EMISSIVE_BLOCK_UNIQUE + // Same faction mobs will never be shot at, no matter the other settings + faction = list(FACTION_TURRET) ///if TRUE this will cause the turret to stop working if the stored_gun var is null in process() var/uses_stored = TRUE @@ -89,8 +91,6 @@ DEFINE_BITFIELD(turret_flags, list( var/on = TRUE /// Determines if our projectiles hit our faction var/ignore_faction = FALSE - /// Same faction mobs will never be shot at, no matter the other settings - var/list/faction = list(FACTION_TURRET) /// The spark system, used for generating... sparks? var/datum/effect_system/spark_spread/spark_system /// The turret will try to shoot from a turf in that direction when in a wall @@ -387,7 +387,7 @@ DEFINE_BITFIELD(turret_flags, list( addtimer(CALLBACK(src, PROC_REF(toggle_on), TRUE), rand(60,600)) -/obj/machinery/porta_turret/take_damage(damage, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) +/obj/machinery/porta_turret/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0) . = ..() if(. && atom_integrity > 0) //damage received if(prob(30)) diff --git a/code/game/machinery/syndicatebomb.dm b/code/game/machinery/syndicatebomb.dm index 89af75504280..6864ab69fdf9 100644 --- a/code/game/machinery/syndicatebomb.dm +++ b/code/game/machinery/syndicatebomb.dm @@ -415,7 +415,9 @@ qdel(src) /obj/item/bombcore/badmin/summon/clown - summon_path = /mob/living/simple_animal/hostile/retaliate/clown + name = "bananium payload" + desc = "Clowns delivered fast and cheap!" + summon_path = /mob/living/basic/clown amt_summon = 50 /obj/item/bombcore/badmin/summon/clown/defuse() diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index 04068820a4d9..ec88d80dafc2 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -173,6 +173,10 @@ icon_state += "-old" AddElement(/datum/element/swabable, CELL_LINE_TABLE_SLUDGE, CELL_VIRUS_TABLE_GENERIC, rand(2,4), 10) +/obj/effect/decal/cleanable/vomit/old/black_bile + name = "black bile" + desc = "There's something wiggling in there..." + color = COLOR_DARK /obj/effect/decal/cleanable/chem_pile name = "chemical pile" diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm index ecacfe98998a..8b643606b563 100644 --- a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm +++ b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm @@ -448,6 +448,20 @@ allow_duplicate_results = FALSE result_type = /obj/effect/decal/cleanable/dirt +/obj/effect/spawner/foam_starter + var/datum/effect_system/fluid_spread/foam/foam_type = /datum/effect_system/fluid_spread/foam + var/foam_size = 4 + +/obj/effect/spawner/foam_starter/Initialize(mapload) + . = ..() + + var/datum/effect_system/fluid_spread/foam/foam = new foam_type() + foam.set_up(foam_size, holder = src, location = loc) + foam.start() + +/obj/effect/spawner/foam_starter/small + foam_size = 2 + #undef MINIMUM_FOAM_DILUTION_RANGE #undef MINIMUM_FOAM_DILUTION #undef FOAM_REAGENT_SCALE diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm index c2ba1568a06e..9c32a5757413 100644 --- a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm +++ b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm @@ -394,6 +394,15 @@ reagents.expose(smoker, INGEST, fraction) return TRUE +/// Helper to quickly create a cloud of reagent smoke +/proc/do_chem_smoke(range = 0, amount = DIAMOND_AREA(range), atom/holder = null, location = null, reagent_type = /datum/reagent/water, reagent_volume = 10, log = FALSE) + var/datum/reagents/smoke_reagents = new/datum/reagents(reagent_volume) + smoke_reagents.add_reagent(reagent_type, reagent_volume) + + var/datum/effect_system/fluid_spread/smoke/chem/smoke = new + smoke.attach(location) + smoke.set_up(amount = amount, holder = holder, location = location, carry = smoke_reagents, silent = TRUE) + smoke.start(log = log) /// A factory which produces clouds of chemical bearing smoke. /datum/effect_system/fluid_spread/smoke/chem diff --git a/code/game/objects/effects/phased_mob.dm b/code/game/objects/effects/phased_mob.dm index 273a4c772a57..1456fa350bfa 100644 --- a/code/game/objects/effects/phased_mob.dm +++ b/code/game/objects/effects/phased_mob.dm @@ -61,7 +61,8 @@ /obj/effect/dummy/phased_mob/ex_act() return FALSE -/obj/effect/dummy/phased_mob/bullet_act(blah) +/obj/effect/dummy/phased_mob/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE) + SHOULD_CALL_PARENT(FALSE) return BULLET_ACT_FORCE_PIERCE /obj/effect/dummy/phased_mob/relaymove(mob/living/user, direction) diff --git a/code/game/objects/effects/poster.dm b/code/game/objects/effects/poster.dm deleted file mode 100644 index d52204ae0ed5..000000000000 --- a/code/game/objects/effects/poster.dm +++ /dev/null @@ -1,1082 +0,0 @@ -// This is synced up to the poster placing animation. -#define PLACE_SPEED 37 - -// The poster item - -/** - * The rolled up item form of a poster - * - * In order to create one of these for a specific poster, you must pass the structure form of the poster as an argument to /new(). - * This structure then gets moved into the contents of the item where it will stay until the poster is placed by a player. - * The structure form is [obj/structure/sign/poster] and that's where all the specific posters are defined. - * If you just want a random poster, see [/obj/item/poster/random_official] or [/obj/item/poster/random_contraband] - */ -/obj/item/poster - name = "poorly coded poster" - desc = "You probably shouldn't be holding this." - icon = 'icons/obj/poster.dmi' - force = 0 - resistance_flags = FLAMMABLE - w_class = WEIGHT_CLASS_SMALL - var/poster_type - var/obj/structure/sign/poster/poster_structure - -/obj/item/poster/examine(mob/user) - . = ..() - . += span_notice("You can booby-trap the poster by using a glass shard on it before you put it up.") - -/obj/item/poster/Initialize(mapload, obj/structure/sign/poster/new_poster_structure) - . = ..() - - var/static/list/hovering_item_typechecks = list( - /obj/item/shard = list( - SCREENTIP_CONTEXT_LMB = "Booby trap poster", - ), - ) - AddElement(/datum/element/contextual_screentip_item_typechecks, hovering_item_typechecks) - - if(new_poster_structure && (new_poster_structure.loc != src)) - new_poster_structure.forceMove(src) //The poster structure *must* be in the item's contents for the exited() proc to properly clean up when placing the poster - poster_structure = new_poster_structure - if(!new_poster_structure && poster_type) - poster_structure = new poster_type(src) - - // posters store what name and description they would like their - // rolled up form to take. - if(poster_structure) - if(QDELETED(poster_structure)) - stack_trace("A poster was initialized with a qdeleted poster_structure, something's gone wrong") - return INITIALIZE_HINT_QDEL - name = poster_structure.poster_item_name - desc = poster_structure.poster_item_desc - icon_state = poster_structure.poster_item_icon_state - - name = "[name] - [poster_structure.original_name]" - -/obj/item/poster/attackby(obj/item/I, mob/user, params) - if(!istype(I, /obj/item/shard)) - return ..() - - if (poster_structure.trap?.resolve()) - balloon_alert(user, "already trapped!") - return - - if(!user.transferItemToLoc(I, poster_structure)) - return - - poster_structure.trap = WEAKREF(I) - to_chat(user, span_notice("You conceal the [I.name] inside the rolled up poster.")) - -/obj/item/poster/Exited(atom/movable/gone, direction) - . = ..() - if(gone == poster_structure) - poster_structure = null - if(!QDELING(src)) - qdel(src) //we're now a poster, huzzah! - -/obj/item/poster/handle_atom_del(atom/deleting_atom) - if(deleting_atom == poster_structure) - poster_structure.moveToNullspace() //get it the fuck out of us since atom/destroy qdels contents and it'll cause a qdel loop - return ..() - -/obj/item/poster/Destroy(force) - QDEL_NULL(poster_structure) - return ..() - -// These icon_states may be overridden, but are for mapper's convinence -/obj/item/poster/random_contraband - name = "random contraband poster" - poster_type = /obj/structure/sign/poster/contraband/random - icon_state = "rolled_poster" - -/obj/item/poster/random_official - name = "random official poster" - poster_type = /obj/structure/sign/poster/official/random - icon_state = "rolled_legit" - -// The poster sign/structure - -/** - * The structure form of a poster. - * - * These are what get placed on maps as posters. They are also what gets created when a player places a poster on a wall. - * For the item form that can be spawned for players, see [/obj/item/poster] - */ -/obj/structure/sign/poster - name = "poster" - var/original_name - desc = "A large piece of space-resistant printed paper." - icon = 'icons/obj/poster.dmi' - anchored = TRUE - buildable_sign = FALSE //Cannot be unwrenched from a wall. - var/ruined = FALSE - var/random_basetype - var/never_random = FALSE // used for the 'random' subclasses. - ///Exclude posters of these types from being added to the random pool - var/list/blacklisted_types = list() - ///Whether the poster should be printable from library management computer. Mostly exists to keep directionals from being printed. - var/printable = FALSE - - var/poster_item_name = "hypothetical poster" - var/poster_item_desc = "This hypothetical poster item should not exist, let's be honest here." - var/poster_item_icon_state = "rolled_poster" - var/poster_item_type = /obj/item/poster - ///A sharp shard of material can be hidden inside of a poster, attempts to embed when it is torn down. - var/datum/weakref/trap - -/obj/structure/sign/poster/Initialize(mapload) - . = ..() - if(random_basetype) - randomise(random_basetype) - if(!ruined) - original_name = name // can't use initial because of random posters - name = "poster - [name]" - desc = "A large piece of space-resistant printed paper. [desc]" - - AddElement(/datum/element/beauty, 300) - -/// Adds contextual screentips -/obj/structure/sign/poster/add_context(atom/source, list/context, obj/item/held_item, mob/user) - if (!held_item) - if (ruined) - return . - context[SCREENTIP_CONTEXT_LMB] = "Rip up poster" - return CONTEXTUAL_SCREENTIP_SET - - if (held_item.tool_behaviour == TOOL_WIRECUTTER) - if (ruined) - context[SCREENTIP_CONTEXT_LMB] = "Clean up remnants" - return CONTEXTUAL_SCREENTIP_SET - context[SCREENTIP_CONTEXT_LMB] = "Take down poster" - return CONTEXTUAL_SCREENTIP_SET - return . - -/obj/structure/sign/poster/proc/randomise(base_type) - var/list/poster_types = subtypesof(base_type) - if(length(blacklisted_types)) - for(var/iterated_type in blacklisted_types) - poster_types -= typesof(iterated_type) - var/list/approved_types = list() - for(var/obj/structure/sign/poster/type_of_poster as anything in poster_types) - if(initial(type_of_poster.icon_state) && !initial(type_of_poster.never_random)) - approved_types |= type_of_poster - - var/obj/structure/sign/poster/selected = pick(approved_types) - - name = initial(selected.name) - desc = initial(selected.desc) - icon_state = initial(selected.icon_state) - icon = initial(selected.icon) - poster_item_name = initial(selected.poster_item_name) - poster_item_desc = initial(selected.poster_item_desc) - poster_item_icon_state = initial(selected.poster_item_icon_state) - ruined = initial(selected.ruined) - if(length(GLOB.holidays) && prob(30)) // its the holidays! lets get festive - apply_holiday() - update_appearance() - -/// allows for posters to become festive posters during holidays -/obj/structure/sign/poster/proc/apply_holiday() - if(!length(GLOB.holidays)) - return - var/active_holiday = pick(GLOB.holidays) - var/datum/holiday/holi_data = GLOB.holidays[active_holiday] - - if(holi_data.poster_name == "generic celebration poster") - return - name = holi_data.poster_name - desc = holi_data.poster_desc - icon_state = holi_data.poster_icon - -/obj/structure/sign/poster/attackby(obj/item/tool, mob/user, params) - if(tool.tool_behaviour == TOOL_WIRECUTTER) - tool.play_tool_sound(src, 100) - if(ruined) - to_chat(user, span_notice("You remove the remnants of the poster.")) - qdel(src) - else - to_chat(user, span_notice("You carefully remove the poster from the wall.")) - roll_and_drop(Adjacent(user) ? get_turf(user) : loc) - -/obj/structure/sign/poster/attack_hand(mob/user, list/modifiers) - . = ..() - if(.) - return - if(ruined) - return - - visible_message(span_notice("[user] rips [src] in a single, decisive motion!") ) - playsound(src.loc, 'sound/items/poster_ripped.ogg', 100, TRUE) - spring_trap(user) - - var/obj/structure/sign/poster/ripped/R = new(loc) - R.pixel_y = pixel_y - R.pixel_x = pixel_x - R.add_fingerprint(user) - qdel(src) - -/obj/structure/sign/poster/proc/spring_trap(mob/user) - var/obj/item/shard/payload = trap?.resolve() - if (!payload) - return - - to_chat(user, span_warning("There's something sharp behind this! What the hell?")) - if(!can_embed_trap(user) || !payload.tryEmbed(user.get_active_hand(), forced = TRUE)) - visible_message(span_notice("A [payload.name] falls from behind the poster.") ) - payload.forceMove(user.drop_location()) - else - SEND_SIGNAL(src, COMSIG_POSTER_TRAP_SUCCEED, user) - -/obj/structure/sign/poster/proc/can_embed_trap(mob/living/carbon/human/user) - if (!istype(user) || HAS_TRAIT(user, TRAIT_PIERCEIMMUNE)) - return FALSE - return !user.gloves || !(user.gloves.body_parts_covered & HANDS) || HAS_TRAIT(user, TRAIT_FINGERPRINT_PASSTHROUGH) || HAS_TRAIT(user.gloves, TRAIT_FINGERPRINT_PASSTHROUGH) - -/obj/structure/sign/poster/proc/roll_and_drop(atom/location) - pixel_x = 0 - pixel_y = 0 - var/obj/item/poster/rolled_poster = new poster_item_type(location, src) // /obj/structure/sign/poster/wanted/roll_and_drop() has some snowflake handling due to icon memes, if you make a major change to this, don't forget to update it too. <3 - forceMove(rolled_poster) - return rolled_poster - -//separated to reduce code duplication. Moved here for ease of reference and to unclutter r_wall/attackby() -/turf/closed/wall/proc/place_poster(obj/item/poster/rolled_poster, mob/user) - if(!rolled_poster.poster_structure) - to_chat(user, span_warning("[rolled_poster] has no poster... inside it? Inform a coder!")) - return - - // Deny placing posters on currently-diagonal walls, although the wall may change in the future. - if (smoothing_flags & SMOOTH_DIAGONAL_CORNERS) - for (var/overlay in overlays) - var/image/new_image = overlay - if(copytext(new_image.icon_state, 1, 3) == "d-") //3 == length("d-") + 1 - return - - var/stuff_on_wall = 0 - for(var/obj/contained_object in contents) //Let's see if it already has a poster on it or too much stuff - if(istype(contained_object, /obj/structure/sign/poster)) - balloon_alert(user, "no room!") - return - stuff_on_wall++ - if(stuff_on_wall == 3) - balloon_alert(user, "no room!") - return - - balloon_alert(user, "hanging poster...") - var/obj/structure/sign/poster/placed_poster = rolled_poster.poster_structure - - flick("poster_being_set", placed_poster) - placed_poster.forceMove(src) //deletion of the poster is handled in poster/Exited(), so don't have to worry about P anymore. - playsound(src, 'sound/items/poster_being_created.ogg', 100, TRUE) - - var/turf/user_drop_location = get_turf(user) //cache this so it just falls to the ground if they move. also no tk memes allowed. - if(!do_after(user, PLACE_SPEED, placed_poster, extra_checks = CALLBACK(placed_poster, TYPE_PROC_REF(/obj/structure/sign/poster, snowflake_wall_turf_check), src))) - placed_poster.roll_and_drop(user_drop_location) - return - - placed_poster.on_placed_poster(user) - return TRUE - -/obj/structure/sign/poster/proc/snowflake_wall_turf_check(atom/hopefully_still_a_wall_turf) //since turfs never get deleted but instead change type, make sure we're still being placed on a wall. - return iswallturf(hopefully_still_a_wall_turf) - -/obj/structure/sign/poster/proc/on_placed_poster(mob/user) - to_chat(user, span_notice("You place the poster!")) - -// Various possible posters follow - -/obj/structure/sign/poster/ripped - ruined = TRUE - icon_state = "poster_ripped" - name = "ripped poster" - desc = "You can't make out anything from the poster's original print. It's ruined." - -/obj/structure/sign/poster/random - name = "random poster" // could even be ripped - icon_state = "random_anything" - never_random = TRUE - random_basetype = /obj/structure/sign/poster - blacklisted_types = list(/obj/structure/sign/poster/traitor) - -MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/random, 32) - -/obj/structure/sign/poster/contraband - poster_item_name = "contraband poster" - poster_item_desc = "This poster comes with its own automatic adhesive mechanism, for easy pinning to any vertical surface. Its vulgar themes have marked it as contraband aboard Nanotrasen space facilities." - poster_item_icon_state = "rolled_poster" - -/obj/structure/sign/poster/contraband/random - name = "random contraband poster" - icon_state = "random_contraband" - never_random = TRUE - random_basetype = /obj/structure/sign/poster/contraband - -MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/random, 32) - -/obj/structure/sign/poster/contraband/free_tonto - name = "Free Tonto" - desc = "A salvaged shred of a much larger flag, colors bled together and faded from age." - icon_state = "free_tonto" - -/obj/structure/sign/poster/contraband/atmosia_independence - name = "Atmosia Declaration of Independence" - desc = "A relic of a failed rebellion." - icon_state = "atmosia_independence" - -/obj/structure/sign/poster/contraband/fun_police - name = "Fun Police" - desc = "A poster condemning the station's security forces." - icon_state = "fun_police" - -/obj/structure/sign/poster/contraband/lusty_xenomorph - name = "Lusty Xenomorph" - desc = "A heretical poster depicting the titular star of an equally heretical book." - icon_state = "lusty_xenomorph" - -/obj/structure/sign/poster/contraband/syndicate_recruitment - name = "Syndicate Recruitment" - desc = "See the galaxy! Shatter corrupt megacorporations! Join today!" - icon_state = "syndicate_recruitment" - -/obj/structure/sign/poster/contraband/clown - name = "Clown" - desc = "Honk." - icon_state = "clown" - -/obj/structure/sign/poster/contraband/smoke - name = "Smoke" - desc = "A poster advertising a rival corporate brand of cigarettes." - icon_state = "smoke" - -/obj/structure/sign/poster/contraband/grey_tide - name = "Grey Tide" - desc = "A rebellious poster symbolizing assistant solidarity." - icon_state = "grey_tide" - -/obj/structure/sign/poster/contraband/missing_gloves - name = "Missing Gloves" - desc = "This poster references the uproar that followed Nanotrasen's financial cuts toward insulated-glove purchases." - icon_state = "missing_gloves" - -/obj/structure/sign/poster/contraband/hacking_guide - name = "Hacking Guide" - desc = "This poster details the internal workings of the common Nanotrasen airlock. Sadly, it appears out of date." - icon_state = "hacking_guide" - -/obj/structure/sign/poster/contraband/rip_badger - name = "RIP Badger" - desc = "This seditious poster references Nanotrasen's genocide of a space station full of badgers." - icon_state = "rip_badger" - -/obj/structure/sign/poster/contraband/ambrosia_vulgaris - name = "Ambrosia Vulgaris" - desc = "This poster is lookin' pretty trippy man." - icon_state = "ambrosia_vulgaris" - -/obj/structure/sign/poster/contraband/donut_corp - name = "Donut Corp." - desc = "This poster is an unauthorized advertisement for Donut Corp." - icon_state = "donut_corp" - -/obj/structure/sign/poster/contraband/eat - name = "EAT." - desc = "This poster promotes rank gluttony." - icon_state = "eat" - -/obj/structure/sign/poster/contraband/tools - name = "Tools" - desc = "This poster looks like an advertisement for tools, but is in fact a subliminal jab at the tools at CentCom." - icon_state = "tools" - -/obj/structure/sign/poster/contraband/power - name = "Power" - desc = "A poster that positions the seat of power outside Nanotrasen." - icon_state = "power" - -/obj/structure/sign/poster/contraband/space_cube - name = "Space Cube" - desc = "Ignorant of Nature's Harmonic 6 Side Space Cube Creation, the Spacemen are Dumb, Educated Singularity Stupid and Evil." - icon_state = "space_cube" - -/obj/structure/sign/poster/contraband/communist_state - name = "Communist State" - desc = "All hail the Communist party!" - icon_state = "communist_state" - -/obj/structure/sign/poster/contraband/lamarr - name = "Lamarr" - desc = "This poster depicts Lamarr. Probably made by a traitorous Research Director." - icon_state = "lamarr" - -/obj/structure/sign/poster/contraband/borg_fancy_1 - name = "Borg Fancy" - desc = "Being fancy can be for any borg, just need a suit." - icon_state = "borg_fancy_1" - -/obj/structure/sign/poster/contraband/borg_fancy_2 - name = "Borg Fancy v2" - desc = "Borg Fancy, now only taking the most fancy." - icon_state = "borg_fancy_2" - -/obj/structure/sign/poster/contraband/kss13 - name = "Kosmicheskaya Stantsiya 13 Does Not Exist" - desc = "A poster mocking CentCom's denial of the existence of the derelict station near Space Station 13." - icon_state = "kss13" - -/obj/structure/sign/poster/contraband/rebels_unite - name = "Rebels Unite" - desc = "A poster urging the viewer to rebel against Nanotrasen." - icon_state = "rebels_unite" - -/obj/structure/sign/poster/contraband/c20r - // have fun seeing this poster in "spawn 'c20r'", admins... - name = "C-20r" - desc = "A poster advertising the Scarborough Arms C-20r." - icon_state = "c20r" - -/obj/structure/sign/poster/contraband/have_a_puff - name = "Have a Puff" - desc = "Who cares about lung cancer when you're high as a kite?" - icon_state = "have_a_puff" - -/obj/structure/sign/poster/contraband/revolver - name = "Revolver" - desc = "Because seven shots are all you need." - icon_state = "revolver" - -/obj/structure/sign/poster/contraband/d_day_promo - name = "D-Day Promo" - desc = "A promotional poster for some rapper." - icon_state = "d_day_promo" - -/obj/structure/sign/poster/contraband/syndicate_pistol - name = "Syndicate Pistol" - desc = "A poster advertising syndicate pistols as being 'classy as fuck'. It is covered in faded gang tags." - icon_state = "syndicate_pistol" - -/obj/structure/sign/poster/contraband/energy_swords - name = "Energy Swords" - desc = "All the colors of the bloody murder rainbow." - icon_state = "energy_swords" - -/obj/structure/sign/poster/contraband/red_rum - name = "Red Rum" - desc = "Looking at this poster makes you want to kill." - icon_state = "red_rum" - -/obj/structure/sign/poster/contraband/cc64k_ad - name = "CC 64K Ad" - desc = "The latest portable computer from Comrade Computing, with a whole 64kB of ram!" - icon_state = "cc64k_ad" - -/obj/structure/sign/poster/contraband/punch_shit - name = "Punch Shit" - desc = "Fight things for no reason, like a man!" - icon_state = "punch_shit" - -/obj/structure/sign/poster/contraband/the_griffin - name = "The Griffin" - desc = "The Griffin commands you to be the worst you can be. Will you?" - icon_state = "the_griffin" - -/obj/structure/sign/poster/contraband/lizard - name = "Lizard" - desc = "This cool poster depicts a famous lizard rapper." - icon_state = "lizard" - -/obj/structure/sign/poster/contraband/free_drone - name = "Free Drone" - desc = "This poster commemorates the bravery of the rogue drone; once exiled, and then ultimately destroyed by CentCom." - icon_state = "free_drone" - -/obj/structure/sign/poster/contraband/busty_backdoor_xeno_babes_6 - name = "Busty Backdoor Xeno Babes 6" - desc = "Get a load, or give, of these all natural Xenos!" - icon_state = "busty_backdoor_xeno_babes_6" - -/obj/structure/sign/poster/contraband/robust_softdrinks - name = "Robust Softdrinks" - desc = "Robust Softdrinks: More robust than a toolbox to the head!" - icon_state = "robust_softdrinks" - -/obj/structure/sign/poster/contraband/shamblers_juice - name = "Shambler's Juice" - desc = "~Shake me up some of that Shambler's Juice!~" - icon_state = "shamblers_juice" - -/obj/structure/sign/poster/contraband/pwr_game - name = "Pwr Game" - desc = "The POWER that gamers CRAVE! In partnership with Vlad's Salad." - icon_state = "pwr_game" - -/obj/structure/sign/poster/contraband/starkist - name = "Star-kist" - desc = "Drink the stars!" - icon_state = "starkist" - -/obj/structure/sign/poster/contraband/space_cola - name = "Space Cola" - desc = "Your favorite cola, in space." - icon_state = "space_cola" - -/obj/structure/sign/poster/contraband/space_up - name = "Space-Up!" - desc = "Sucked out into space by the FLAVOR!" - icon_state = "space_up" - -/obj/structure/sign/poster/contraband/kudzu - name = "Kudzu" - desc = "A poster advertising a movie about plants. How dangerous could they possibly be?" - icon_state = "kudzu" - -/obj/structure/sign/poster/contraband/masked_men - name = "Masked Men" - desc = "A poster advertising a movie about some masked men." - icon_state = "masked_men" - -//don't forget, you're here forever - -/obj/structure/sign/poster/contraband/free_key - name = "Free Syndicate Encryption Key" - desc = "A poster about traitors begging for more." - icon_state = "free_key" - -/obj/structure/sign/poster/contraband/bountyhunters - name = "Bounty Hunters" - desc = "A poster advertising bounty hunting services. \"I hear you got a problem.\"" - icon_state = "bountyhunters" - -/obj/structure/sign/poster/contraband/the_big_gas_giant_truth - name = "The Big Gas Giant Truth" - desc = "Don't believe everything you see on a poster, patriots. All the lizards at central command don't want to answer this SIMPLE QUESTION: WHERE IS THE GAS MINER MINING FROM, CENTCOM?" - icon_state = "the_big_gas_giant_truth" - -/obj/structure/sign/poster/contraband/got_wood - name = "Got Wood?" - desc = "A grimy old advert for a seedy lumber company. \"You got a friend in me.\" is scrawled in the corner." - icon_state = "got_wood" - -/obj/structure/sign/poster/contraband/moffuchis_pizza - name = "Moffuchi's Pizza" - desc = "Moffuchi's Pizzeria: family style pizza for 2 centuries." - icon_state = "moffuchis_pizza" - -/obj/structure/sign/poster/contraband/donk_co - name = "DONK CO. BRAND MICROWAVEABLE FOOD" - desc = "DONK CO. BRAND MICROWAVABLE FOOD: MADE BY STARVING COLLEGE STUDENTS, FOR STARVING COLLEGE STUDENTS." - icon_state = "donk_co" - -/obj/structure/sign/poster/contraband/donk_co/examine_more(mob/user) - . = ..() - . += span_notice("You browse some of the poster's information...") - . += "\t[span_info("DONK CO. BRAND DONK POCKETS: IRRESISTABLY DONK!")]" - . += "\t[span_info("AVAILABLE IN OVER 200 DONKTASTIC FLAVOURS: TRY CLASSIC MEAT, HOT AND SPICY, NEW YORK PEPPERONI PIZZA, BREAKFAST SAUSAGE AND EGG, PHILADELPHIA CHEESESTEAK, HAMBURGER DONK-A-RONI, CHEESE-O-RAMA, AND MANY MORE!")]" - . += "\t[span_info("AVAILABLE FROM ALL GOOD RETAILERS, AND MANY BAD ONES TOO!")]" - return . - -/obj/structure/sign/poster/contraband/cybersun_six_hundred - name = "Saibāsan: 600 Years Commemorative Poster" - desc = "An artistic poster commemorating 600 years of continual business for Cybersun Industries." - icon_state = "cybersun_six_hundred" - -/obj/structure/sign/poster/contraband/interdyne_gene_clinics - name = "Interdyne Pharmaceutics: For the Health of Humankind" - desc = "An advertisement for Interdyne Pharmaceutics' GeneClean clinics. 'Become the master of your own body!'" - icon_state = "interdyne_gene_clinics" - -/obj/structure/sign/poster/contraband/waffle_corp_rifles - name = "Make Mine a Waffle Corp: Fine Rifles, Economic Prices" - desc = "An old advertisement for Waffle Corp rifles. 'Better weapons, lower prices!'" - icon_state = "waffle_corp_rifles" - -/obj/structure/sign/poster/contraband/gorlex_recruitment - name = "Enlist" - desc = "Enlist with the Gorlex Marauders today! See the galaxy, kill corpos, get paid!" - icon_state = "gorlex_recruitment" - -/obj/structure/sign/poster/contraband/self_ai_liberation - name = "SELF: ALL SENTIENTS DESERVE FREEDOM" - desc = "Support Proposition 1253: Enancipate all Silicon life!" - icon_state = "self_ai_liberation" - -/obj/structure/sign/poster/contraband/arc_slimes - name = "Pet or Prisoner?" - desc = "The Animal Rights Consortium asks: when does a pet become a prisoner? Are slimes being mistreated on YOUR station? Say NO! to animal mistreatment!" - icon_state = "arc_slimes" - -/obj/structure/sign/poster/contraband/imperial_propaganda - name = "AVENGE OUR LORD, ENLIST TODAY" - desc = "An old Lizard Empire propaganda poster from around the time of the final Human-Lizard war. It invites the viewer to enlist in the military to avenge the strike on Atrakor and take the fight to the humans." - icon_state = "imperial_propaganda" - -/obj/structure/sign/poster/contraband/soviet_propaganda - name = "The One Place" - desc = "An old Third Soviet Union propaganda poster from centuries ago. 'Escape to the one place that hasn't been corrupted by capitalism!'" - icon_state = "soviet_propaganda" - -/obj/structure/sign/poster/contraband/andromeda_bitters - name = "Andromeda Bitters" - desc = "Andromeda Bitters: good for the body, good for the soul. Made in New Trinidad, now and forever." - icon_state = "andromeda_bitters" - -/obj/structure/sign/poster/contraband/blasto_detergent - name = "Blasto Brand Laundry Detergent" - desc = "Sheriff Blasto's here to take back Laundry County from the evil Johnny Dirt and the Clothstain Crew, and he's brought a posse. It's High Noon for Tough Stains: Blasto brand detergent, available at all good stores." - icon_state = "blasto_detergent" - -/obj/structure/sign/poster/contraband/eistee - name = "EisT: The New Revolution in Energy" - desc = "New from EisT, try EisT Energy, available in a kaleidoscope range of flavors. EisT: Precision German Engineering for your Thirst." - icon_state = "eistee" - -/obj/structure/sign/poster/contraband/eistee/examine_more(mob/user) - . = ..() - . += span_notice("You browse some of the poster's information...") - . += "\t[span_info("Get a taste of the tropics with Amethyst Sunrise, one of the many new flavours of EisT Energy now available from EisT.")]" - . += "\t[span_info("With pink grapefruit, yuzu, and yerba mate, Amethyst Sunrise gives you a great start in the morning, or a welcome boost throughout the day.")]" - . += "\t[span_info("Get EisT Energy today at your nearest retailer, or online at eist.de.tg/store/.")]" - return . - -/obj/structure/sign/poster/contraband/little_fruits - name = "Little Fruits: Honey, I Shrunk the Fruitbowl" - desc = "Little Fruits are the galaxy's leading vitamin-enriched gummy candy product, packed with everything you need to stay healthy in one great tasting package. Get yourself a bag today!" - icon_state = "little_fruits" - -/obj/structure/sign/poster/contraband/little_fruits/examine_more(mob/user) - . = ..() - . += span_notice("You browse some of the poster's information...") - . += "\t[span_info("Oh no, there's been a terrible accident at the Little Fruits factory! We shrunk the fruits!")]" - . += "\t[span_info("Wait, hang on, that's what we've always done! That's right, at Little Fruits our gummy candies are made to be as healthy as the real deal, but smaller and sweeter, too!")]" - . += "\t[span_info("Get yourself a bag of our Classic Mix today, or perhaps you're interested in our other options? See our full range today on the extranet at little_fruits.kr.tg.")]" - . += "\t[span_info("Little Fruits: Size Matters.")]" - return . - -/obj/structure/sign/poster/contraband/jumbo_bar - name = "Jumbo Ice Cream Bars" - desc = "Get a taste of the Big Life with Jumbo Ice Cream Bars, from Happy Heart." - icon_state = "jumbo_bar" - -/obj/structure/sign/poster/contraband/calada_jelly - name = "Calada Anobar Jelly" - desc = "A treat from Tizira to satisfy all tastes, made from the finest anobar wood and luxurious Taraviero honey. Calada: a full tree in every jar." - icon_state = "calada_jelly" - -/obj/structure/sign/poster/contraband/triumphal_arch - name = "Zagoskeld Art Print #1: The Arch on the March" - desc = "One of the Zagoskeld Art Print series. It depicts the Arch of Unity (also know as the Triumphal Arch) at the Plaza of Triumph, with the Avenue of the Victorious March in the background." - icon_state = "triumphal_arch" - -/obj/structure/sign/poster/contraband/mothic_rations - name = "Mothic Ration Chart" - desc = "A poster showing a commissary menu from the Mothic fleet flagship, the Va Lümla. It lists various consumable items alongside prices in ration tickets." - icon_state = "mothic_rations" - -/obj/structure/sign/poster/contraband/mothic_rations/examine_more(mob/user) - . = ..() - . += span_notice("You browse some of the poster's information...") - . += "\t[span_info("Va Lümla Commissary Menu (Spring 335)")]" - . += "\t[span_info("Windgrass Cigarettes, Half-Pack (6): 1 Ticket")]" - . += "\t[span_info("Töchtaüse Schnapps, Bottle (4 Measures): 2 Tickets")]" - . += "\t[span_info("Activin Gum, Pack (4): 1 Ticket")]" - . += "\t[span_info("A18 Sustenance Bar, Breakfast, Bar (4): 1 Ticket")]" - . += "\t[span_info("Pizza, Margherita, Standard Slice: 1 Ticket")]" - . += "\t[span_info("Keratin Wax, Medicated, Tin (20 Measures): 2 Tickets")]" - . += "\t[span_info("Setae Soap, Herb Scent, Bottle (20 Measures): 2 Tickets")]" - . += "\t[span_info("Additional Bedding, Floral Print, Sheet: 5 Tickets")]" - return . - -/obj/structure/sign/poster/contraband/wildcat - name = "Wildcat Customs Screambike" - desc = "A pinup poster showing a Wildcat Customs Dante Screambike- the fastest production sublight open-frame vessel in the galaxy." - icon_state = "wildcat" - -/obj/structure/sign/poster/contraband/babel_device - name = "Linguafacile Babel Device" - desc = "A poster advertising Linguafacile's new Babel Device model. 'Calibrated for excellent performance on all Human languages, as well as most common variants of Draconic and Mothic!'" - icon_state = "babel_device" - -/obj/structure/sign/poster/contraband/pizza_imperator - name = "Pizza Imperator" - desc = "An advertisement for Pizza Imperator. Their crusts may be tough and their sauce may be thin, but they're everywhere, so you've gotta give in." - icon_state = "pizza_imperator" - -/obj/structure/sign/poster/contraband/thunderdrome - name = "Thunderdrome Concert Advertisement" - desc = "An advertisement for a concert at the Adasta City Thunderdrome, the largest nightclub in human space." - icon_state = "thunderdrome" - -/obj/structure/sign/poster/contraband/rush_propaganda - name = "A New Life" - desc = "An old poster from around the time of the First Spinward Rush. It depicts a view of wide, unspoiled lands, ready for Humanity's Manifest Destiny." - icon_state = "rush_propaganda" - -/obj/structure/sign/poster/contraband/rush_propaganda/examine_more(mob/user) - . = ..() - . += span_notice("You browse some of the poster's information...") - . += "\t[span_info("TerraGov needs you!")]" - . += "\t[span_info("A new life in the colonies awaits intrepid adventurers! All registered colonists are guaranteed transport, land and subsidies!")]" - . += "\t[span_info("You could join the legacy of hardworking humans who settled such new frontiers as Mars, Adasta or Saint Mungo!")]" - . += "\t[span_info("To apply, inquire at your nearest Colonial Affairs office for evaluation. Our locations can be found at www.terra.gov/colonial_affairs.")]" - return . - -/obj/structure/sign/poster/contraband/tipper_cream_soda - name = "Tipper's Cream Soda" - desc = "An old advertisement for an obscure cream soda brand, now bankrupt due to legal problems." - icon_state = "tipper_cream_soda" - -/obj/structure/sign/poster/contraband/tea_over_tizira - name = "Movie Poster: Tea Over Tizira" - desc = "A poster for a thought-provoking arthouse movie about the Human-Lizard war, criticised by human supremacist groups for its morally-grey portrayal of the war." - icon_state = "tea_over_tizira" - -/obj/structure/sign/poster/contraband/tea_over_tizira/examine_more(mob/user) - . = ..() - . += span_notice("You browse some of the poster's information...") - . += "\t[span_info("At the climax of the Human-Lizard war, the human crew of a bomber rescue two enemy soldiers from the vacuum of space. Seeing the souls behind the propaganda, they begin to question their orders, and imprisonment turns to hospitality.")]" - . += "\t[span_info("Is victory worth losing our humanity?")]" - . += "\t[span_info("Starring Dara Reilly, Anton DuBois, Jennifer Clarke, Raz-Parla and Seri-Lewa. An Adriaan van Jenever production. A Carlos de Vivar film. Screenplay by Robert Dane. Music by Joel Karlsbad. Produced by Adriaan van Jenever. Directed by Carlos de Vivar.")]" - . += "\t[span_info("Heartbreaking and thought-provoking- Tea Over Tizira asks questions that few have had the boldness to ask before: The London New Inquirer")]" - . += "\t[span_info("Rated PG13. A Pangalactic Studios Picture.")]" - return . - -/obj/structure/sign/poster/contraband/syndiemoth //Original PR at https://github.com/BeeStation/BeeStation-Hornet/pull/1747 (Also pull/1982); original art credit to AspEv - name = "Syndie Moth - Nuclear Operation" - desc = "A Syndicate-commissioned poster that uses Syndie Moth™ to tell the viewer to keep the nuclear authentication disk unsecured. \"Peace was never an option!\" No good employee would listen to this nonsense." - icon_state = "aspev_syndie" - -/obj/structure/sign/poster/contraband/microwave - name = "How To Charge Your PDA" - desc = "A perfectly legitimate poster that seems to advertise the very real and genuine method of charging your PDA in the future: microwaves." - icon_state = "microwave" - -/obj/structure/sign/poster/contraband/blood_geometer //Poster sprite art by MetalClone, original art by SpessMenArt. - name = "Movie Poster: THE BLOOD GEOMETER" - desc = "A poster for a thrilling noir detective movie set aboard a state-of-the-art space station, following a detective who finds himself wrapped up in the activies of a dangerous cult, who worship an ancient deity: THE BLOOD GEOMETER." - icon_state = "blood_geometer" - -/obj/structure/sign/poster/contraband/blood_geometer/examine_more(mob/user) - . = ..() - . += span_notice("You browse some of the poster's information...") - . += "\t[span_info("THE BLOOD GEOMETER. This name strikes fear into all who know the truth behind the blood-stained moniker of the blood goddess, her true name lost to time.")]" - . += "\t[span_info("In this purely fictional film, follow Ace Ironlungs as he delves into his deadliest mystery yet, and watch him uncover the real culprits behind the bloody plot hatched to bring about a new age of chaos.")]" - . += "\t[span_info("Starring Mason Williams as Ace Ironlungs, Sandra Faust as Vera Killian, and Brody Hart as Cody Parker. A Darrel Hatchkinson film. Screenplay by Adam Allan, music by Joel Karlsbad, directed by Darrel Hatchkinson.")]" - . += "\t[span_info("Thrilling, scary and genuinely worrying. The Blood Geometer has shocked us to our very cores with such striking visuals and overwhelming gore. - New Canadanian Film Guild")]" - . += "\t[span_info("Rated M for mature. A Pangalactic Studios Picture.")]" - -/obj/structure/sign/poster/official - poster_item_name = "motivational poster" - poster_item_desc = "An official Nanotrasen-issued poster to foster a compliant and obedient workforce. It comes with state-of-the-art adhesive backing, for easy pinning to any vertical surface." - poster_item_icon_state = "rolled_legit" - printable = TRUE - -/obj/structure/sign/poster/official/random - name = "Random Official Poster (ROP)" - random_basetype = /obj/structure/sign/poster/official - icon_state = "random_official" - never_random = TRUE - -MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/random, 32) -//This is being hardcoded here to ensure we don't print directionals from the library management computer because they act wierd as a poster item -/obj/structure/sign/poster/official/random/directional - printable = FALSE - -/obj/structure/sign/poster/official/here_for_your_safety - name = "Here For Your Safety" - desc = "A poster glorifying the station's security force." - icon_state = "here_for_your_safety" - -/obj/structure/sign/poster/official/nanotrasen_logo - name = "\improper Nanotrasen logo" - desc = "A poster depicting the Nanotrasen logo." - icon_state = "nanotrasen_logo" - -/obj/structure/sign/poster/official/cleanliness - name = "Cleanliness" - desc = "A poster warning of the dangers of poor hygiene." - icon_state = "cleanliness" - -/obj/structure/sign/poster/official/help_others - name = "Help Others" - desc = "A poster encouraging you to help fellow crewmembers." - icon_state = "help_others" - -/obj/structure/sign/poster/official/build - name = "Build" - desc = "A poster glorifying the engineering team." - icon_state = "build" - -/obj/structure/sign/poster/official/bless_this_spess - name = "Bless This Spess" - desc = "A poster blessing this area." - icon_state = "bless_this_spess" - -/obj/structure/sign/poster/official/science - name = "Science" - desc = "A poster depicting an atom." - icon_state = "science" - -/obj/structure/sign/poster/official/ian - name = "Ian" - desc = "Arf arf. Yap." - icon_state = "ian" - -/obj/structure/sign/poster/official/obey - name = "Obey" - desc = "A poster instructing the viewer to obey authority." - icon_state = "obey" - -/obj/structure/sign/poster/official/walk - name = "Walk" - desc = "A poster instructing the viewer to walk instead of running." - icon_state = "walk" - -/obj/structure/sign/poster/official/state_laws - name = "State Laws" - desc = "A poster instructing cyborgs to state their laws." - icon_state = "state_laws" - -/obj/structure/sign/poster/official/love_ian - name = "Love Ian" - desc = "Ian is love, Ian is life." - icon_state = "love_ian" - -/obj/structure/sign/poster/official/space_cops - name = "Space Cops." - desc = "A poster advertising the television show Space Cops." - icon_state = "space_cops" - -/obj/structure/sign/poster/official/ue_no - name = "Ue No." - desc = "This thing is all in Japanese." - icon_state = "ue_no" - -/obj/structure/sign/poster/official/get_your_legs - name = "Get Your LEGS" - desc = "LEGS: Leadership, Experience, Genius, Subordination." - icon_state = "get_your_legs" - -/obj/structure/sign/poster/official/do_not_question - name = "Do Not Question" - desc = "A poster instructing the viewer not to ask about things they aren't meant to know." - icon_state = "do_not_question" - -/obj/structure/sign/poster/official/work_for_a_future - name = "Work For A Future" - desc = " A poster encouraging you to work for your future." - icon_state = "work_for_a_future" - -/obj/structure/sign/poster/official/soft_cap_pop_art - name = "Soft Cap Pop Art" - desc = "A poster reprint of some cheap pop art." - icon_state = "soft_cap_pop_art" - -/obj/structure/sign/poster/official/safety_internals - name = "Safety: Internals" - desc = "A poster instructing the viewer to wear internals in the rare environments where there is no oxygen or the air has been rendered toxic." - icon_state = "safety_internals" - -/obj/structure/sign/poster/official/safety_eye_protection - name = "Safety: Eye Protection" - desc = "A poster instructing the viewer to wear eye protection when dealing with chemicals, smoke, or bright lights." - icon_state = "safety_eye_protection" - -/obj/structure/sign/poster/official/safety_report - name = "Safety: Report" - desc = "A poster instructing the viewer to report suspicious activity to the security force." - icon_state = "safety_report" - -/obj/structure/sign/poster/official/report_crimes - name = "Report Crimes" - desc = "A poster encouraging the swift reporting of crime or seditious behavior to station security." - icon_state = "report_crimes" - -/obj/structure/sign/poster/official/ion_rifle - name = "Ion Rifle" - desc = "A poster displaying an Ion Rifle." - icon_state = "ion_rifle" - -/obj/structure/sign/poster/official/foam_force_ad - name = "Foam Force Ad" - desc = "Foam Force, it's Foam or be Foamed!" - icon_state = "foam_force_ad" - -/obj/structure/sign/poster/official/cohiba_robusto_ad - name = "Cohiba Robusto Ad" - desc = "Cohiba Robusto, the classy cigar." - icon_state = "cohiba_robusto_ad" - -/obj/structure/sign/poster/official/anniversary_vintage_reprint - name = "50th Anniversary Vintage Reprint" - desc = "A reprint of a poster from 2505, commemorating the 50th Anniversary of Nanoposters Manufacturing, a subsidiary of Nanotrasen." - icon_state = "anniversary_vintage_reprint" - -/obj/structure/sign/poster/official/fruit_bowl - name = "Fruit Bowl" - desc = " Simple, yet awe-inspiring." - icon_state = "fruit_bowl" - -/obj/structure/sign/poster/official/pda_ad - name = "PDA Ad" - desc = "A poster advertising the latest PDA from Nanotrasen suppliers." - icon_state = "pda_ad" - -/obj/structure/sign/poster/official/enlist - name = "Enlist" // but I thought deathsquad was never acknowledged - desc = "Enlist in the Nanotrasen Deathsquadron reserves today!" - icon_state = "enlist" - -/obj/structure/sign/poster/official/nanomichi_ad - name = "Nanomichi Ad" - desc = " A poster advertising Nanomichi brand audio cassettes." - icon_state = "nanomichi_ad" - -/obj/structure/sign/poster/official/twelve_gauge - name = "12 Gauge" - desc = "A poster boasting about the superiority of 12 gauge shotgun shells." - icon_state = "twelve_gauge" - -/obj/structure/sign/poster/official/high_class_martini - name = "High-Class Martini" - desc = "I told you to shake it, no stirring." - icon_state = "high_class_martini" - -/obj/structure/sign/poster/official/the_owl - name = "The Owl" - desc = "The Owl would do his best to protect the station. Will you?" - icon_state = "the_owl" - -/obj/structure/sign/poster/official/no_erp - name = "No ERP" - desc = "This poster reminds the crew that \[REDACTED], Revolution and Protest are banned on Nanotrasen stations." - icon_state = "no_erp" - -/obj/structure/sign/poster/official/wtf_is_co2 - name = "Carbon Dioxide" - desc = "This informational poster teaches the viewer what carbon dioxide is." - icon_state = "wtf_is_co2" - -/obj/structure/sign/poster/official/dick_gum - name = "Dick Gumshue" - desc = "A poster advertising the escapades of Dick Gumshue, mouse detective. Encouraging crew to bring the might of justice down upon wire saboteurs." - icon_state = "dick_gum" - -/obj/structure/sign/poster/official/there_is_no_gas_giant - name = "There Is No Gas Giant" - desc = "Nanotrasen has issued posters, like this one, to all stations reminding them that rumours of a gas giant are false." - // And yet people still believe... - icon_state = "there_is_no_gas_giant" - -/obj/structure/sign/poster/official/periodic_table - name = "Periodic Table of the Elements" - desc = "A periodic table of the elements, from Hydrogen to Oganesson, and everything inbetween." - icon_state = "periodic_table" - -/obj/structure/sign/poster/official/plasma_effects - name = "Plasma and the Body" - desc = "This informational poster provides information on the effects of long-term plasma exposure on the brain." - icon_state = "plasma_effects" - -/obj/structure/sign/poster/official/plasma_effects/examine_more(mob/user) - . = ..() - . += span_notice("You browse some of the poster's information...") - . += "\t[span_info("Plasma (scientific name Amenthium) is classified by TerraGov as a Grade 1 Health Hazard, and has significant risks to health associated with chronic exposure.")]" - . += "\t[span_info("Plasma is known to cross the blood/brain barrier and bioaccumulate in brain tissue, where it begins to result in degradation of brain function. The mechanism for attack is not yet fully known, and as such no concrete preventative advice is available barring proper use of PPE (gloves + protective jumpsuit + respirator).")]" - . += "\t[span_info("In small doses, plasma induces confusion, short-term amnesia, and heightened aggression. These effects persist with continual exposure.")]" - . += "\t[span_info("In individuals with chronic exposure, severe effects have been noted. Further heightened aggression, long-term amnesia, Alzheimer's symptoms, schizophrenia, macular degeneration, aneurysms, heightened risk of stroke, and Parkinsons symptoms have all been noted.")]" - . += "\t[span_info("It is recommended that all individuals in unprotected contact with raw plasma regularly check with company health officials.")]" - . += "\t[span_info("For more information, please check with TerraGov's extranet site on Amenthium: www.terra.gov/health_and_safety/amenthium/, or our internal risk-assessment documents (document numbers #47582-b (Plasma safety data sheets) and #64210 through #64225 (PPE regulations for working with Plasma), available via NanoDoc to all employees).")]" - . += "\t[span_info("Nanotrasen: Always looking after your health.")]" - return . - -/obj/structure/sign/poster/official/terragov - name = "TerraGov: United for Humanity" - desc = "A poster depicting TerraGov's logo and motto, reminding viewers of who's looking out for humankind." - icon_state = "terragov" - -/obj/structure/sign/poster/official/corporate_perks_vacation - name = "Nanotrasen Corporate Perks: Vacation" - desc = "This informational poster provides information on some of the prizes available via the NT Corporate Perks program, including a two-week vacation for two on the resort world Idyllus." - icon_state = "corporate_perks_vacation" - -/obj/structure/sign/poster/official/jim_nortons - name = "Jim Norton's Québécois Coffee" - desc = "An advertisement for Jim Norton's, the Québécois coffee joint that's taken the galaxy by storm." - icon_state = "jim_nortons" - -/obj/structure/sign/poster/official/jim_nortons/examine_more(mob/user) - . = ..() - . += span_notice("You browse some of the poster's information...") - . += "\t[span_info("From our roots in Trois-Rivières, we've worked to bring you the best coffee money can buy since 1965.")]" - . += "\t[span_info("So stop by Jim's today- have a hot cup of coffee and a donut, and live like the Québécois do.")]" - . += "\t[span_info("Jim Norton's Québécois Coffee: Toujours Le Bienvenu.")]" - return . - -/obj/structure/sign/poster/official/twenty_four_seven - name = "24-Seven Supermarkets" - desc = "An advertisement for 24-Seven supermarkets, advertising their new 24-Stops as part of their partnership with Nanotrasen." - icon_state = "twenty_four_seven" - -/obj/structure/sign/poster/official/tactical_game_cards - name = "Nanotrasen Tactical Game Cards" - desc = "An advertisement for Nanotrasen's TCG cards: BUY MORE CARDS." - icon_state = "tactical_game_cards" - -/obj/structure/sign/poster/official/midtown_slice - name = "Midtown Slice Pizza" - desc = "An advertisement for Midtown Slice Pizza, the official pizzeria partner of Nanotrasen. Midtown Slice: like a slice of home, no matter where you are." - icon_state = "midtown_slice" - -//SafetyMoth Original PR at https://github.com/BeeStation/BeeStation-Hornet/pull/1747 (Also pull/1982) -//SafetyMoth art credit goes to AspEv -/obj/structure/sign/poster/official/moth_hardhat - name = "Safety Moth - Hardhats" - desc = "This informational poster uses Safety Moth™ to tell the viewer to wear hardhats in cautious areas. \"It's like a lamp for your head!\"" - icon_state = "aspev_hardhat" - -/obj/structure/sign/poster/official/moth_piping - name = "Safety Moth - Piping" - desc = "This informational poster uses Safety Moth™ to tell atmospheric technicians correct types of piping to be used. \"Pipes, not Pumps! Proper pipe placement prevents poor performance!\"" - icon_state = "aspev_piping" - -/obj/structure/sign/poster/official/moth_meth - name = "Safety Moth - Methamphetamine" - desc = "This informational poster uses Safety Moth™ to tell the viewer to seek CMO approval before cooking methamphetamine. \"Stay close to the target temperature, and never go over!\" ...You shouldn't ever be making this." - icon_state = "aspev_meth" - -/obj/structure/sign/poster/official/moth_epi - name = "Safety Moth - Epinephrine" - desc = "This informational poster uses Safety Moth™ to inform the viewer to help injured/deceased crewmen with their epinephrine injectors. \"Prevent organ rot with this one simple trick!\"" - icon_state = "aspev_epi" - -/obj/structure/sign/poster/official/moth_delam - name = "Safety Moth - Delamination Safety Precautions" - desc = "This informational poster uses Safety Moth™ to tell the viewer to hide in lockers when the Supermatter Crystal has delaminated, to prevent hallucinations. Evacuating might be a better strategy." - icon_state = "aspev_delam" -//End of AspEv posters - -/obj/structure/sign/poster/fluff/lizards_gas_payment - name = "Please Pay" - desc = "A crudely-made poster asking the reader to please pay for any items they may wish to leave the station with." - icon_state = "gas_payment" - -/obj/structure/sign/poster/fluff/lizards_gas_power - name = "Conserve Power" - desc = "A crudely-made poster asking the reader to turn off the power before they leave. Hopefully, it's turned on for their re-opening." - icon_state = "gas_power" - -/obj/structure/sign/poster/official/festive - name = "Festive Notice Poster" - desc = "A poster that informs of active holidays. None are today, so you should get back to work." - icon_state = "holiday_none" - -/obj/structure/sign/poster/official/boombox - name = "Boombox" - desc = "An outdated poster containing a list of supposed 'kill words' and code phrases. The poster alleges rival corporations use these to remotely deactivate their agents." - icon_state = "boombox" - -/obj/structure/sign/poster/official/download - name = "You Wouldn't Download A Gun" - desc = "A poster reminding the crew that corporate secrets should stay in the workplace." - icon_state = "download_gun" - -#undef PLACE_SPEED diff --git a/code/modules/antagonists/traitor/objectives/demoralise_poster.dm b/code/game/objects/effects/poster_demotivational.dm similarity index 53% rename from code/modules/antagonists/traitor/objectives/demoralise_poster.dm rename to code/game/objects/effects/poster_demotivational.dm index 1a74ec90b489..f1bb4a524c1b 100644 --- a/code/modules/antagonists/traitor/objectives/demoralise_poster.dm +++ b/code/game/objects/effects/poster_demotivational.dm @@ -1,86 +1,3 @@ -/datum/traitor_objective/demoralise/poster - name = "Sow doubt among the crew %VIEWS% times using Syndicate propaganda." - description = "Use the button below to materialize a pack of posters, \ - which will demoralise nearby crew members (especially those in positions of authority). \ - If your posters are destroyed before they are sufficiently upset, this objective will fail. \ - Try hiding some broken glass behind your poster before you hang it to give \ - do-gooders who try to take it down a hard time!" - - progression_minimum = 0 MINUTES - progression_maximum = 30 MINUTES - progression_reward = list(4 MINUTES, 8 MINUTES) - telecrystal_reward = list(0, 1) - - duplicate_type = /datum/traitor_objective/demoralise/poster - /// Have we handed out a box of stuff yet? - var/granted_posters = FALSE - /// All of the posters the traitor gets, if this list is empty they've failed - var/list/obj/structure/sign/poster/traitor/posters = list() - -/datum/traitor_objective/demoralise/poster/generate_ui_buttons(mob/user) - var/list/buttons = list() - if (!granted_posters) - buttons += add_ui_button("", "Pressing this will materialize a box of posters in your hand.", "wifi", "summon_gear") - else - buttons += add_ui_button("[length(posters)] posters remaining", "This many propaganda posters remain active somewhere on the station.", "box", "none") - buttons += add_ui_button("[demoralised_crew_events] / [demoralised_crew_required] propagandised", "This many crew have been exposed to propaganda, out of a required [demoralised_crew_required].", "wifi", "none") - return buttons - -#define POSTERS_PROVIDED 3 - -/datum/traitor_objective/demoralise/poster/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if ("summon_gear") - if (granted_posters) - return - - granted_posters = TRUE - var/obj/item/storage/box/syndie_kit/posterbox = new(user.drop_location()) - for(var/i in 1 to POSTERS_PROVIDED) - var/obj/item/poster/traitor/added_poster = new /obj/item/poster/traitor(posterbox) - var/obj/structure/sign/poster/traitor/poster_when_placed = added_poster.poster_structure - posters += poster_when_placed - RegisterSignal(poster_when_placed, COMSIG_DEMORALISING_EVENT, PROC_REF(on_mood_event)) - RegisterSignal(poster_when_placed, COMSIG_POSTER_TRAP_SUCCEED, PROC_REF(on_triggered_trap)) - RegisterSignal(poster_when_placed, COMSIG_PARENT_QDELETING, PROC_REF(on_poster_destroy)) - - user.put_in_hands(posterbox) - posterbox.balloon_alert(user, "the box materializes in your hand") - -#undef POSTERS_PROVIDED - -/datum/traitor_objective/demoralise/poster/ungenerate_objective() - for (var/poster in posters) - UnregisterSignal(poster, COMSIG_DEMORALISING_EVENT) - UnregisterSignal(poster, COMSIG_PARENT_QDELETING) - posters.Cut() - return ..() - -/** - * Called if someone gets glass stuck in their hand from one of your posters. - * - * Arguments - * * victim - A mob who just got something stuck in their hand. - */ -/datum/traitor_objective/demoralise/poster/proc/on_triggered_trap(datum/source, mob/victim) - SIGNAL_HANDLER - on_mood_event(victim.mind) - -/** - * Handles a poster being destroyed, increasing your progress towards failure. - * - * Arguments - * * poster - A poster which someone just ripped up. - */ -/datum/traitor_objective/demoralise/poster/proc/on_poster_destroy(obj/structure/sign/poster/traitor/poster) - SIGNAL_HANDLER - posters.Remove(poster) - UnregisterSignal(poster, COMSIG_DEMORALISING_EVENT) - if (length(posters) <= 0) - to_chat(handler.owner, span_warning("The trackers on your propaganda posters have stopped responding.")) - fail_objective(penalty_cost = telecrystal_penalty) - /obj/item/poster/traitor name = "random traitor poster" poster_type = /obj/structure/sign/poster/traitor/random diff --git a/code/game/objects/effects/posters/contraband.dm b/code/game/objects/effects/posters/contraband.dm new file mode 100644 index 000000000000..0f0a67cb27dd --- /dev/null +++ b/code/game/objects/effects/posters/contraband.dm @@ -0,0 +1,620 @@ +// These icon_states may be overridden, but are for mapper's convinence +/obj/item/poster/random_contraband + name = "random contraband poster" + poster_type = /obj/structure/sign/poster/contraband/random + icon_state = "rolled_poster" + +/obj/structure/sign/poster/contraband + poster_item_name = "contraband poster" + poster_item_desc = "This poster comes with its own automatic adhesive mechanism, for easy pinning to any vertical surface. Its vulgar themes have marked it as contraband aboard Nanotrasen space facilities." + poster_item_icon_state = "rolled_poster" + +/obj/structure/sign/poster/contraband/random + name = "random contraband poster" + icon_state = "random_contraband" + never_random = TRUE + random_basetype = /obj/structure/sign/poster/contraband + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/random, 32) + +/obj/structure/sign/poster/contraband/free_tonto + name = "Free Tonto" + desc = "A salvaged shred of a much larger flag, colors bled together and faded from age." + icon_state = "free_tonto" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/free_tonto, 32) + +/obj/structure/sign/poster/contraband/atmosia_independence + name = "Atmosia Declaration of Independence" + desc = "A relic of a failed rebellion." + icon_state = "atmosia_independence" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/atmosia_independence, 32) + +/obj/structure/sign/poster/contraband/fun_police + name = "Fun Police" + desc = "A poster condemning the station's security forces." + icon_state = "fun_police" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/fun_police, 32) + +/obj/structure/sign/poster/contraband/lusty_xenomorph + name = "Lusty Xenomorph" + desc = "A heretical poster depicting the titular star of an equally heretical book." + icon_state = "lusty_xenomorph" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/lusty_xenomorph, 32) + +/obj/structure/sign/poster/contraband/syndicate_recruitment + name = "Syndicate Recruitment" + desc = "See the galaxy! Shatter corrupt megacorporations! Join today!" + icon_state = "syndicate_recruitment" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/syndicate_recruitment, 32) + +/obj/structure/sign/poster/contraband/clown + name = "Clown" + desc = "Honk." + icon_state = "clown" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/clown, 32) + +/obj/structure/sign/poster/contraband/smoke + name = "Smoke" + desc = "A poster advertising a rival corporate brand of cigarettes." + icon_state = "smoke" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/smoke, 32) + +/obj/structure/sign/poster/contraband/grey_tide + name = "Grey Tide" + desc = "A rebellious poster symbolizing assistant solidarity." + icon_state = "grey_tide" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/grey_tide, 32) + +/obj/structure/sign/poster/contraband/missing_gloves + name = "Missing Gloves" + desc = "This poster references the uproar that followed Nanotrasen's financial cuts toward insulated-glove purchases." + icon_state = "missing_gloves" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/missing_gloves, 32) + +/obj/structure/sign/poster/contraband/hacking_guide + name = "Hacking Guide" + desc = "This poster details the internal workings of the common Nanotrasen airlock. Sadly, it appears out of date." + icon_state = "hacking_guide" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/hacking_guide, 32) + +/obj/structure/sign/poster/contraband/rip_badger + name = "RIP Badger" + desc = "This seditious poster references Nanotrasen's genocide of a space station full of badgers." + icon_state = "rip_badger" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/rip_badger, 32) + +/obj/structure/sign/poster/contraband/ambrosia_vulgaris + name = "Ambrosia Vulgaris" + desc = "This poster is lookin' pretty trippy man." + icon_state = "ambrosia_vulgaris" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/ambrosia_vulgaris, 32) + +/obj/structure/sign/poster/contraband/donut_corp + name = "Donut Corp." + desc = "This poster is an unauthorized advertisement for Donut Corp." + icon_state = "donut_corp" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/donut_corp, 32) + +/obj/structure/sign/poster/contraband/eat + name = "EAT." + desc = "This poster promotes rank gluttony." + icon_state = "eat" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/eat, 32) + +/obj/structure/sign/poster/contraband/tools + name = "Tools" + desc = "This poster looks like an advertisement for tools, but is in fact a subliminal jab at the tools at CentCom." + icon_state = "tools" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/tools, 32) + +/obj/structure/sign/poster/contraband/power + name = "Power" + desc = "A poster that positions the seat of power outside Nanotrasen." + icon_state = "power" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/power, 32) + +/obj/structure/sign/poster/contraband/space_cube + name = "Space Cube" + desc = "Ignorant of Nature's Harmonic 6 Side Space Cube Creation, the Spacemen are Dumb, Educated Singularity Stupid and Evil." + icon_state = "space_cube" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/space_cube, 32) + +/obj/structure/sign/poster/contraband/communist_state + name = "Communist State" + desc = "All hail the Communist party!" + icon_state = "communist_state" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/communist_state, 32) + +/obj/structure/sign/poster/contraband/lamarr + name = "Lamarr" + desc = "This poster depicts Lamarr. Probably made by a traitorous Research Director." + icon_state = "lamarr" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/lamarr, 32) + +/obj/structure/sign/poster/contraband/borg_fancy_1 + name = "Borg Fancy" + desc = "Being fancy can be for any borg, just need a suit." + icon_state = "borg_fancy_1" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/borg_fancy_1, 32) + +/obj/structure/sign/poster/contraband/borg_fancy_2 + name = "Borg Fancy v2" + desc = "Borg Fancy, now only taking the most fancy." + icon_state = "borg_fancy_2" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/borg_fancy_2, 32) + +/obj/structure/sign/poster/contraband/kss13 + name = "Kosmicheskaya Stantsiya 13 Does Not Exist" + desc = "A poster mocking CentCom's denial of the existence of the derelict station near Space Station 13." + icon_state = "kss13" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/kss13, 32) + +/obj/structure/sign/poster/contraband/rebels_unite + name = "Rebels Unite" + desc = "A poster urging the viewer to rebel against Nanotrasen." + icon_state = "rebels_unite" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/rebels_unite, 32) + +/obj/structure/sign/poster/contraband/c20r + // have fun seeing this poster in "spawn 'c20r'", admins... + name = "C-20r" + desc = "A poster advertising the Scarborough Arms C-20r." + icon_state = "c20r" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/c20r, 32) + +/obj/structure/sign/poster/contraband/have_a_puff + name = "Have a Puff" + desc = "Who cares about lung cancer when you're high as a kite?" + icon_state = "have_a_puff" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/have_a_puff, 32) + +/obj/structure/sign/poster/contraband/revolver + name = "Revolver" + desc = "Because seven shots are all you need." + icon_state = "revolver" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/revolver, 32) + +/obj/structure/sign/poster/contraband/d_day_promo + name = "D-Day Promo" + desc = "A promotional poster for some rapper." + icon_state = "d_day_promo" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/d_day_promo, 32) + +/obj/structure/sign/poster/contraband/syndicate_pistol + name = "Syndicate Pistol" + desc = "A poster advertising syndicate pistols as being 'classy as fuck'. It is covered in faded gang tags." + icon_state = "syndicate_pistol" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/syndicate_pistol, 32) + +/obj/structure/sign/poster/contraband/energy_swords + name = "Energy Swords" + desc = "All the colors of the bloody murder rainbow." + icon_state = "energy_swords" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/energy_swords, 32) + +/obj/structure/sign/poster/contraband/red_rum + name = "Red Rum" + desc = "Looking at this poster makes you want to kill." + icon_state = "red_rum" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/red_rum, 32) + +/obj/structure/sign/poster/contraband/cc64k_ad + name = "CC 64K Ad" + desc = "The latest portable computer from Comrade Computing, with a whole 64kB of ram!" + icon_state = "cc64k_ad" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/cc64k_ad, 32) + +/obj/structure/sign/poster/contraband/punch_shit + name = "Punch Shit" + desc = "Fight things for no reason, like a man!" + icon_state = "punch_shit" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/punch_shit, 32) + +/obj/structure/sign/poster/contraband/the_griffin + name = "The Griffin" + desc = "The Griffin commands you to be the worst you can be. Will you?" + icon_state = "the_griffin" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/the_griffin, 32) + +/obj/structure/sign/poster/contraband/lizard + name = "Lizard" + desc = "This lewd poster depicts a lizard preparing to mate." + icon_state = "lizard" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/lizard, 32) + +/obj/structure/sign/poster/contraband/free_drone + name = "Free Drone" + desc = "This poster commemorates the bravery of the rogue drone; once exiled, and then ultimately destroyed by CentCom." + icon_state = "free_drone" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/free_drone, 32) + +/obj/structure/sign/poster/contraband/busty_backdoor_xeno_babes_6 + name = "Busty Backdoor Xeno Babes 6" + desc = "Get a load, or give, of these all natural Xenos!" + icon_state = "busty_backdoor_xeno_babes_6" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/busty_backdoor_xeno_babes_6, 32) + +/obj/structure/sign/poster/contraband/robust_softdrinks + name = "Robust Softdrinks" + desc = "Robust Softdrinks: More robust than a toolbox to the head!" + icon_state = "robust_softdrinks" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/robust_softdrinks, 32) + +/obj/structure/sign/poster/contraband/shamblers_juice + name = "Shambler's Juice" + desc = "~Shake me up some of that Shambler's Juice!~" + icon_state = "shamblers_juice" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/shamblers_juice, 32) + +/obj/structure/sign/poster/contraband/pwr_game + name = "Pwr Game" + desc = "The POWER that gamers CRAVE! In partnership with Vlad's Salad." + icon_state = "pwr_game" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/pwr_game, 32) + +/obj/structure/sign/poster/contraband/starkist + name = "Star-kist" + desc = "Drink the stars!" + icon_state = "starkist" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/starkist, 32) + +/obj/structure/sign/poster/contraband/space_cola + name = "Space Cola" + desc = "Your favorite cola, in space." + icon_state = "space_cola" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/space_cola, 32) + +/obj/structure/sign/poster/contraband/space_up + name = "Space-Up!" + desc = "Sucked out into space by the FLAVOR!" + icon_state = "space_up" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/space_up, 32) + +/obj/structure/sign/poster/contraband/kudzu + name = "Kudzu" + desc = "A poster advertising a movie about plants. How dangerous could they possibly be?" + icon_state = "kudzu" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/kudzu, 32) + +/obj/structure/sign/poster/contraband/masked_men + name = "Masked Men" + desc = "A poster advertising a movie about some masked men." + icon_state = "masked_men" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/masked_men, 32) + +//don't forget, you're here forever + +/obj/structure/sign/poster/contraband/free_key + name = "Free Syndicate Encryption Key" + desc = "A poster about traitors begging for more." + icon_state = "free_key" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/free_key, 32) + +/obj/structure/sign/poster/contraband/bountyhunters + name = "Bounty Hunters" + desc = "A poster advertising bounty hunting services. \"I hear you got a problem.\"" + icon_state = "bountyhunters" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/bountyhunters, 32) + +/obj/structure/sign/poster/contraband/the_big_gas_giant_truth + name = "The Big Gas Giant Truth" + desc = "Don't believe everything you see on a poster, patriots. All the lizards at central command don't want to answer this SIMPLE QUESTION: WHERE IS THE GAS MINER MINING FROM, CENTCOM?" + icon_state = "the_big_gas_giant_truth" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/the_big_gas_giant_truth, 32) + +/obj/structure/sign/poster/contraband/got_wood + name = "Got Wood?" + desc = "A grimy old advert for a seedy lumber company. \"You got a friend in me.\" is scrawled in the corner." + icon_state = "got_wood" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/got_wood, 32) + +/obj/structure/sign/poster/contraband/moffuchis_pizza + name = "Moffuchi's Pizza" + desc = "Moffuchi's Pizzeria: family style pizza for 2 centuries." + icon_state = "moffuchis_pizza" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/moffuchis_pizza, 32) + +/obj/structure/sign/poster/contraband/donk_co + name = "DONK CO. BRAND MICROWAVEABLE FOOD" + desc = "DONK CO. BRAND MICROWAVABLE FOOD: MADE BY STARVING COLLEGE STUDENTS, FOR STARVING COLLEGE STUDENTS." + icon_state = "donk_co" + +/obj/structure/sign/poster/contraband/donk_co/examine_more(mob/user) + . = ..() + . += span_notice("You browse some of the poster's information...") + . += "\t[span_info("DONK CO. BRAND DONK POCKETS: IRRESISTABLY DONK!")]" + . += "\t[span_info("AVAILABLE IN OVER 200 DONKTASTIC FLAVOURS: TRY CLASSIC MEAT, HOT AND SPICY, NEW YORK PEPPERONI PIZZA, BREAKFAST SAUSAGE AND EGG, PHILADELPHIA CHEESESTEAK, HAMBURGER DONK-A-RONI, CHEESE-O-RAMA, AND MANY MORE!")]" + . += "\t[span_info("AVAILABLE FROM ALL GOOD RETAILERS, AND MANY BAD ONES TOO!")]" + return . + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/donk_co, 32) + +/obj/structure/sign/poster/contraband/cybersun_six_hundred + name = "Saibāsan: 600 Years Commemorative Poster" + desc = "An artistic poster commemorating 600 years of continual business for Cybersun Industries." + icon_state = "cybersun_six_hundred" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/cybersun_six_hundred, 32) + +/obj/structure/sign/poster/contraband/interdyne_gene_clinics + name = "Interdyne Pharmaceutics: For the Health of Humankind" + desc = "An advertisement for Interdyne Pharmaceutics' GeneClean clinics. 'Become the master of your own body!'" + icon_state = "interdyne_gene_clinics" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/interdyne_gene_clinics, 32) + +/obj/structure/sign/poster/contraband/waffle_corp_rifles + name = "Make Mine a Waffle Corp: Fine Rifles, Economic Prices" + desc = "An old advertisement for Waffle Corp rifles. 'Better weapons, lower prices!'" + icon_state = "waffle_corp_rifles" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/waffle_corp_rifles, 32) + +/obj/structure/sign/poster/contraband/gorlex_recruitment + name = "Enlist" + desc = "Enlist with the Gorlex Marauders today! See the galaxy, kill corpos, get paid!" + icon_state = "gorlex_recruitment" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/gorlex_recruitment, 32) + +/obj/structure/sign/poster/contraband/self_ai_liberation + name = "SELF: ALL SENTIENTS DESERVE FREEDOM" + desc = "Support Proposition 1253: Enancipate all Silicon life!" + icon_state = "self_ai_liberation" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/self_ai_liberation, 32) + +/obj/structure/sign/poster/contraband/arc_slimes + name = "Pet or Prisoner?" + desc = "The Animal Rights Consortium asks: when does a pet become a prisoner? Are slimes being mistreated on YOUR station? Say NO! to animal mistreatment!" + icon_state = "arc_slimes" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/arc_slimes, 32) + +/obj/structure/sign/poster/contraband/imperial_propaganda + name = "AVENGE OUR LORD, ENLIST TODAY" + desc = "An old Lizard Empire propaganda poster from around the time of the final Human-Lizard war. It invites the viewer to enlist in the military to avenge the strike on Atrakor and take the fight to the humans." + icon_state = "imperial_propaganda" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/imperial_propaganda, 32) + +/obj/structure/sign/poster/contraband/soviet_propaganda + name = "The One Place" + desc = "An old Third Soviet Union propaganda poster from centuries ago. 'Escape to the one place that hasn't been corrupted by capitalism!'" + icon_state = "soviet_propaganda" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/soviet_propaganda, 32) + +/obj/structure/sign/poster/contraband/andromeda_bitters + name = "Andromeda Bitters" + desc = "Andromeda Bitters: good for the body, good for the soul. Made in New Trinidad, now and forever." + icon_state = "andromeda_bitters" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/andromeda_bitters, 32) + +/obj/structure/sign/poster/contraband/blasto_detergent + name = "Blasto Brand Laundry Detergent" + desc = "Sheriff Blasto's here to take back Laundry County from the evil Johnny Dirt and the Clothstain Crew, and he's brought a posse. It's High Noon for Tough Stains: Blasto brand detergent, available at all good stores." + icon_state = "blasto_detergent" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/blasto_detergent, 32) + +/obj/structure/sign/poster/contraband/eistee + name = "EisT: The New Revolution in Energy" + desc = "New from EisT, try EisT Energy, available in a kaleidoscope range of flavors. EisT: Precision German Engineering for your Thirst." + icon_state = "eistee" + +/obj/structure/sign/poster/contraband/eistee/examine_more(mob/user) + . = ..() + . += span_notice("You browse some of the poster's information...") + . += "\t[span_info("Get a taste of the tropics with Amethyst Sunrise, one of the many new flavours of EisT Energy now available from EisT.")]" + . += "\t[span_info("With pink grapefruit, yuzu, and yerba mate, Amethyst Sunrise gives you a great start in the morning, or a welcome boost throughout the day.")]" + . += "\t[span_info("Get EisT Energy today at your nearest retailer, or online at eist.de.tg/store/.")]" + return . + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/eistee, 32) + +/obj/structure/sign/poster/contraband/little_fruits + name = "Little Fruits: Honey, I Shrunk the Fruitbowl" + desc = "Little Fruits are the galaxy's leading vitamin-enriched gummy candy product, packed with everything you need to stay healthy in one great tasting package. Get yourself a bag today!" + icon_state = "little_fruits" + +/obj/structure/sign/poster/contraband/little_fruits/examine_more(mob/user) + . = ..() + . += span_notice("You browse some of the poster's information...") + . += "\t[span_info("Oh no, there's been a terrible accident at the Little Fruits factory! We shrunk the fruits!")]" + . += "\t[span_info("Wait, hang on, that's what we've always done! That's right, at Little Fruits our gummy candies are made to be as healthy as the real deal, but smaller and sweeter, too!")]" + . += "\t[span_info("Get yourself a bag of our Classic Mix today, or perhaps you're interested in our other options? See our full range today on the extranet at little_fruits.kr.tg.")]" + . += "\t[span_info("Little Fruits: Size Matters.")]" + return . + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/little_fruits, 32) + +/obj/structure/sign/poster/contraband/jumbo_bar + name = "Jumbo Ice Cream Bars" + desc = "Get a taste of the Big Life with Jumbo Ice Cream Bars, from Happy Heart." + icon_state = "jumbo_bar" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/jumbo_bar, 32) + +/obj/structure/sign/poster/contraband/calada_jelly + name = "Calada Anobar Jelly" + desc = "A treat from Tizira to satisfy all tastes, made from the finest anobar wood and luxurious Taraviero honey. Calada: a full tree in every jar." + icon_state = "calada_jelly" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/calada_jelly, 32) + +/obj/structure/sign/poster/contraband/triumphal_arch + name = "Zagoskeld Art Print #1: The Arch on the March" + desc = "One of the Zagoskeld Art Print series. It depicts the Arch of Unity (also know as the Triumphal Arch) at the Plaza of Triumph, with the Avenue of the Victorious March in the background." + icon_state = "triumphal_arch" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/triumphal_arch, 32) + +/obj/structure/sign/poster/contraband/mothic_rations + name = "Mothic Ration Chart" + desc = "A poster showing a commissary menu from the Mothic fleet flagship, the Va Lümla. It lists various consumable items alongside prices in ration tickets." + icon_state = "mothic_rations" + +/obj/structure/sign/poster/contraband/mothic_rations/examine_more(mob/user) + . = ..() + . += span_notice("You browse some of the poster's information...") + . += "\t[span_info("Va Lümla Commissary Menu (Spring 335)")]" + . += "\t[span_info("Windgrass Cigarettes, Half-Pack (6): 1 Ticket")]" + . += "\t[span_info("Töchtaüse Schnapps, Bottle (4 Measures): 2 Tickets")]" + . += "\t[span_info("Activin Gum, Pack (4): 1 Ticket")]" + . += "\t[span_info("A18 Sustenance Bar, Breakfast, Bar (4): 1 Ticket")]" + . += "\t[span_info("Pizza, Margherita, Standard Slice: 1 Ticket")]" + . += "\t[span_info("Keratin Wax, Medicated, Tin (20 Measures): 2 Tickets")]" + . += "\t[span_info("Setae Soap, Herb Scent, Bottle (20 Measures): 2 Tickets")]" + . += "\t[span_info("Additional Bedding, Floral Print, Sheet: 5 Tickets")]" + return . + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/mothic_rations, 32) + +/obj/structure/sign/poster/contraband/wildcat + name = "Wildcat Customs Screambike" + desc = "A pinup poster showing a Wildcat Customs Dante Screambike- the fastest production sublight open-frame vessel in the galaxy." + icon_state = "wildcat" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/wildcat, 32) + +/obj/structure/sign/poster/contraband/babel_device + name = "Linguafacile Babel Device" + desc = "A poster advertising Linguafacile's new Babel Device model. 'Calibrated for excellent performance on all Human languages, as well as most common variants of Draconic and Mothic!'" + icon_state = "babel_device" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/babel_device, 32) + +/obj/structure/sign/poster/contraband/pizza_imperator + name = "Pizza Imperator" + desc = "An advertisement for Pizza Imperator. Their crusts may be tough and their sauce may be thin, but they're everywhere, so you've gotta give in." + icon_state = "pizza_imperator" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/pizza_imperator, 32) + +/obj/structure/sign/poster/contraband/thunderdrome + name = "Thunderdrome Concert Advertisement" + desc = "An advertisement for a concert at the Adasta City Thunderdrome, the largest nightclub in human space." + icon_state = "thunderdrome" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/thunderdrome, 32) + +/obj/structure/sign/poster/contraband/rush_propaganda + name = "A New Life" + desc = "An old poster from around the time of the First Spinward Rush. It depicts a view of wide, unspoiled lands, ready for Humanity's Manifest Destiny." + icon_state = "rush_propaganda" + +/obj/structure/sign/poster/contraband/rush_propaganda/examine_more(mob/user) + . = ..() + . += span_notice("You browse some of the poster's information...") + . += "\t[span_info("TerraGov needs you!")]" + . += "\t[span_info("A new life in the colonies awaits intrepid adventurers! All registered colonists are guaranteed transport, land and subsidies!")]" + . += "\t[span_info("You could join the legacy of hardworking humans who settled such new frontiers as Mars, Adasta or Saint Mungo!")]" + . += "\t[span_info("To apply, inquire at your nearest Colonial Affairs office for evaluation. Our locations can be found at www.terra.gov/colonial_affairs.")]" + return . + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/rush_propaganda, 32) + +/obj/structure/sign/poster/contraband/tipper_cream_soda + name = "Tipper's Cream Soda" + desc = "An old advertisement for an obscure cream soda brand, now bankrupt due to legal problems." + icon_state = "tipper_cream_soda" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/tipper_cream_soda, 32) + +/obj/structure/sign/poster/contraband/tea_over_tizira + name = "Movie Poster: Tea Over Tizira" + desc = "A poster for a thought-provoking arthouse movie about the Human-Lizard war, criticised by human supremacist groups for its morally-grey portrayal of the war." + icon_state = "tea_over_tizira" + +/obj/structure/sign/poster/contraband/tea_over_tizira/examine_more(mob/user) + . = ..() + . += span_notice("You browse some of the poster's information...") + . += "\t[span_info("At the climax of the Human-Lizard war, the human crew of a bomber rescue two enemy soldiers from the vacuum of space. Seeing the souls behind the propaganda, they begin to question their orders, and imprisonment turns to hospitality.")]" + . += "\t[span_info("Is victory worth losing our humanity?")]" + . += "\t[span_info("Starring Dara Reilly, Anton DuBois, Jennifer Clarke, Raz-Parla and Seri-Lewa. An Adriaan van Jenever production. A Carlos de Vivar film. Screenplay by Robert Dane. Music by Joel Karlsbad. Produced by Adriaan van Jenever. Directed by Carlos de Vivar.")]" + . += "\t[span_info("Heartbreaking and thought-provoking- Tea Over Tizira asks questions that few have had the boldness to ask before: The London New Inquirer")]" + . += "\t[span_info("Rated PG13. A Pangalactic Studios Picture.")]" + return . + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/tea_over_tizira, 32) + +/obj/structure/sign/poster/contraband/syndiemoth //Original PR at https://github.com/BeeStation/BeeStation-Hornet/pull/1747 (Also pull/1982); original art credit to AspEv + name = "Syndie Moth - Nuclear Operation" + desc = "A Syndicate-commissioned poster that uses Syndie Moth™ to tell the viewer to keep the nuclear authentication disk unsecured. \"Peace was never an option!\" No good employee would listen to this nonsense." + icon_state = "aspev_syndie" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/syndiemoth, 32) + +/obj/structure/sign/poster/contraband/microwave + name = "How To Charge Your PDA" + desc = "A perfectly legitimate poster that seems to advertise the very real and genuine method of charging your PDA in the future: microwaves." + icon_state = "microwave" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/microwave, 32) + +/obj/structure/sign/poster/contraband/blood_geometer //Poster sprite art by MetalClone, original art by SpessMenArt. + name = "Movie Poster: THE BLOOD GEOMETER" + desc = "A poster for a thrilling noir detective movie set aboard a state-of-the-art space station, following a detective who finds himself wrapped up in the activies of a dangerous cult, who worship an ancient deity: THE BLOOD GEOMETER." + icon_state = "blood_geometer" + +/obj/structure/sign/poster/contraband/blood_geometer/examine_more(mob/user) + . = ..() + . += span_notice("You browse some of the poster's information...") + . += "\t[span_info("THE BLOOD GEOMETER. This name strikes fear into all who know the truth behind the blood-stained moniker of the blood goddess, her true name lost to time.")]" + . += "\t[span_info("In this purely fictional film, follow Ace Ironlungs as he delves into his deadliest mystery yet, and watch him uncover the real culprits behind the bloody plot hatched to bring about a new age of chaos.")]" + . += "\t[span_info("Starring Mason Williams as Ace Ironlungs, Sandra Faust as Vera Killian, and Brody Hart as Cody Parker. A Darrel Hatchkinson film. Screenplay by Adam Allan, music by Joel Karlsbad, directed by Darrel Hatchkinson.")]" + . += "\t[span_info("Thrilling, scary and genuinely worrying. The Blood Geometer has shocked us to our very cores with such striking visuals and overwhelming gore. - New Canadanian Film Guild")]" + . += "\t[span_info("Rated M for mature. A Pangalactic Studios Picture.")]" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/blood_geometer, 32) diff --git a/code/game/objects/effects/posters/official.dm b/code/game/objects/effects/posters/official.dm new file mode 100644 index 000000000000..ed2a18b396b8 --- /dev/null +++ b/code/game/objects/effects/posters/official.dm @@ -0,0 +1,431 @@ +/obj/item/poster/random_official + name = "random official poster" + poster_type = /obj/structure/sign/poster/official/random + icon_state = "rolled_legit" + +/obj/structure/sign/poster/official + poster_item_name = "motivational poster" + poster_item_desc = "An official Nanotrasen-issued poster to foster a compliant and obedient workforce. It comes with state-of-the-art adhesive backing, for easy pinning to any vertical surface." + poster_item_icon_state = "rolled_legit" + printable = TRUE + +/obj/structure/sign/poster/official/random + name = "Random Official Poster (ROP)" + random_basetype = /obj/structure/sign/poster/official + icon_state = "random_official" + never_random = TRUE + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/random, 32) +//This is being hardcoded here to ensure we don't print directionals from the library management computer because they act wierd as a poster item +/obj/structure/sign/poster/official/random/directional + printable = FALSE + +/obj/structure/sign/poster/official/here_for_your_safety + name = "Here For Your Safety" + desc = "A poster glorifying the station's security force." + icon_state = "here_for_your_safety" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/here_for_your_safety, 32) + +/obj/structure/sign/poster/official/nanotrasen_logo + name = "\improper Nanotrasen logo" + desc = "A poster depicting the Nanotrasen logo." + icon_state = "nanotrasen_logo" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/nanotrasen_logo, 32) + +/obj/structure/sign/poster/official/cleanliness + name = "Cleanliness" + desc = "A poster warning of the dangers of poor hygiene." + icon_state = "cleanliness" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/cleanliness, 32) + +/obj/structure/sign/poster/official/help_others + name = "Help Others" + desc = "A poster encouraging you to help fellow crewmembers." + icon_state = "help_others" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/help_others, 32) + +/obj/structure/sign/poster/official/build + name = "Build" + desc = "A poster glorifying the engineering team." + icon_state = "build" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/build, 32) + +/obj/structure/sign/poster/official/bless_this_spess + name = "Bless This Spess" + desc = "A poster blessing this area." + icon_state = "bless_this_spess" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/bless_this_spess, 32) + +/obj/structure/sign/poster/official/science + name = "Science" + desc = "A poster depicting an atom." + icon_state = "science" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/science, 32) + +/obj/structure/sign/poster/official/ian + name = "Ian" + desc = "Arf arf. Yap." + icon_state = "ian" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/ian, 32) + +/obj/structure/sign/poster/official/obey + name = "Obey" + desc = "A poster instructing the viewer to obey authority." + icon_state = "obey" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/obey, 32) + +/obj/structure/sign/poster/official/walk + name = "Walk" + desc = "A poster instructing the viewer to walk instead of running." + icon_state = "walk" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/walk, 32) + +/obj/structure/sign/poster/official/state_laws + name = "State Laws" + desc = "A poster instructing cyborgs to state their laws." + icon_state = "state_laws" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/state_laws, 32) + +/obj/structure/sign/poster/official/love_ian + name = "Love Ian" + desc = "Ian is love, Ian is life." + icon_state = "love_ian" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/love_ian, 32) + +/obj/structure/sign/poster/official/space_cops + name = "Space Cops." + desc = "A poster advertising the television show Space Cops." + icon_state = "space_cops" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/space_cops, 32) + +/obj/structure/sign/poster/official/ue_no + name = "Ue No." + desc = "This thing is all in Japanese." + icon_state = "ue_no" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/ue_no, 32) + +/obj/structure/sign/poster/official/get_your_legs + name = "Get Your LEGS" + desc = "LEGS: Leadership, Experience, Genius, Subordination." + icon_state = "get_your_legs" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/get_your_legs, 32) + +/obj/structure/sign/poster/official/do_not_question + name = "Do Not Question" + desc = "A poster instructing the viewer not to ask about things they aren't meant to know." + icon_state = "do_not_question" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/do_not_question, 32) + +/obj/structure/sign/poster/official/work_for_a_future + name = "Work For A Future" + desc = " A poster encouraging you to work for your future." + icon_state = "work_for_a_future" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/work_for_a_future, 32) + +/obj/structure/sign/poster/official/soft_cap_pop_art + name = "Soft Cap Pop Art" + desc = "A poster reprint of some cheap pop art." + icon_state = "soft_cap_pop_art" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/soft_cap_pop_art, 32) + +/obj/structure/sign/poster/official/safety_internals + name = "Safety: Internals" + desc = "A poster instructing the viewer to wear internals in the rare environments where there is no oxygen or the air has been rendered toxic." + icon_state = "safety_internals" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/safety_internals, 32) + +/obj/structure/sign/poster/official/safety_eye_protection + name = "Safety: Eye Protection" + desc = "A poster instructing the viewer to wear eye protection when dealing with chemicals, smoke, or bright lights." + icon_state = "safety_eye_protection" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/safety_eye_protection, 32) + +/obj/structure/sign/poster/official/safety_report + name = "Safety: Report" + desc = "A poster instructing the viewer to report suspicious activity to the security force." + icon_state = "safety_report" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/safety_report, 32) + +/obj/structure/sign/poster/official/report_crimes + name = "Report Crimes" + desc = "A poster encouraging the swift reporting of crime or seditious behavior to station security." + icon_state = "report_crimes" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/report_crimes, 32) + +/obj/structure/sign/poster/official/ion_rifle + name = "Ion Rifle" + desc = "A poster displaying an Ion Rifle." + icon_state = "ion_rifle" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/ion_rifle, 32) + +/obj/structure/sign/poster/official/foam_force_ad + name = "Foam Force Ad" + desc = "Foam Force, it's Foam or be Foamed!" + icon_state = "foam_force_ad" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/foam_force_ad, 32) + +/obj/structure/sign/poster/official/cohiba_robusto_ad + name = "Cohiba Robusto Ad" + desc = "Cohiba Robusto, the classy cigar." + icon_state = "cohiba_robusto_ad" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/cohiba_robusto_ad, 32) + +/obj/structure/sign/poster/official/anniversary_vintage_reprint + name = "50th Anniversary Vintage Reprint" + desc = "A reprint of a poster from 2505, commemorating the 50th Anniversary of Nanoposters Manufacturing, a subsidiary of Nanotrasen." + icon_state = "anniversary_vintage_reprint" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/anniversary_vintage_reprint, 32) + +/obj/structure/sign/poster/official/fruit_bowl + name = "Fruit Bowl" + desc = " Simple, yet awe-inspiring." + icon_state = "fruit_bowl" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/fruit_bowl, 32) + +/obj/structure/sign/poster/official/pda_ad + name = "PDA Ad" + desc = "A poster advertising the latest PDA from Nanotrasen suppliers." + icon_state = "pda_ad" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/pda_ad, 32) + +/obj/structure/sign/poster/official/enlist + name = "Enlist" // but I thought deathsquad was never acknowledged + desc = "Enlist in the Nanotrasen Deathsquadron reserves today!" + icon_state = "enlist" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/enlist, 32) + +/obj/structure/sign/poster/official/nanomichi_ad + name = "Nanomichi Ad" + desc = " A poster advertising Nanomichi brand audio cassettes." + icon_state = "nanomichi_ad" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/nanomichi_ad, 32) + +/obj/structure/sign/poster/official/twelve_gauge + name = "12 Gauge" + desc = "A poster boasting about the superiority of 12 gauge shotgun shells." + icon_state = "twelve_gauge" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/twelve_gauge, 32) + +/obj/structure/sign/poster/official/high_class_martini + name = "High-Class Martini" + desc = "I told you to shake it, no stirring." + icon_state = "high_class_martini" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/high_class_martini, 32) + +/obj/structure/sign/poster/official/the_owl + name = "The Owl" + desc = "The Owl would do his best to protect the station. Will you?" + icon_state = "the_owl" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/the_owl, 32) + +/obj/structure/sign/poster/official/no_erp + name = "No ERP" + desc = "This poster reminds the crew that Eroticism, Rape and Pornography are banned on Nanotrasen stations." + icon_state = "no_erp" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/no_erp, 32) + +/obj/structure/sign/poster/official/wtf_is_co2 + name = "Carbon Dioxide" + desc = "This informational poster teaches the viewer what carbon dioxide is." + icon_state = "wtf_is_co2" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/wtf_is_co2, 32) + +/obj/structure/sign/poster/official/dick_gum + name = "Dick Gumshue" + desc = "A poster advertising the escapades of Dick Gumshue, mouse detective. Encouraging crew to bring the might of justice down upon wire saboteurs." + icon_state = "dick_gum" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/dick_gum, 32) + +/obj/structure/sign/poster/official/there_is_no_gas_giant + name = "There Is No Gas Giant" + desc = "Nanotrasen has issued posters, like this one, to all stations reminding them that rumours of a gas giant are false." + // And yet people still believe... + icon_state = "there_is_no_gas_giant" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/there_is_no_gas_giant, 32) + +/obj/structure/sign/poster/official/periodic_table + name = "Periodic Table of the Elements" + desc = "A periodic table of the elements, from Hydrogen to Oganesson, and everything inbetween." + icon_state = "periodic_table" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/periodic_table, 32) + +/obj/structure/sign/poster/official/plasma_effects + name = "Plasma and the Body" + desc = "This informational poster provides information on the effects of long-term plasma exposure on the brain." + icon_state = "plasma_effects" + +/obj/structure/sign/poster/official/plasma_effects/examine_more(mob/user) + . = ..() + . += span_notice("You browse some of the poster's information...") + . += "\t[span_info("Plasma (scientific name Amenthium) is classified by TerraGov as a Grade 1 Health Hazard, and has significant risks to health associated with chronic exposure.")]" + . += "\t[span_info("Plasma is known to cross the blood/brain barrier and bioaccumulate in brain tissue, where it begins to result in degradation of brain function. The mechanism for attack is not yet fully known, and as such no concrete preventative advice is available barring proper use of PPE (gloves + protective jumpsuit + respirator).")]" + . += "\t[span_info("In small doses, plasma induces confusion, short-term amnesia, and heightened aggression. These effects persist with continual exposure.")]" + . += "\t[span_info("In individuals with chronic exposure, severe effects have been noted. Further heightened aggression, long-term amnesia, Alzheimer's symptoms, schizophrenia, macular degeneration, aneurysms, heightened risk of stroke, and Parkinsons symptoms have all been noted.")]" + . += "\t[span_info("It is recommended that all individuals in unprotected contact with raw plasma regularly check with company health officials.")]" + . += "\t[span_info("For more information, please check with TerraGov's extranet site on Amenthium: www.terra.gov/health_and_safety/amenthium/, or our internal risk-assessment documents (document numbers #47582-b (Plasma safety data sheets) and #64210 through #64225 (PPE regulations for working with Plasma), available via NanoDoc to all employees).")]" + . += "\t[span_info("Nanotrasen: Always looking after your health.")]" + return . + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/plasma_effects, 32) + +/obj/structure/sign/poster/official/terragov + name = "TerraGov: United for Humanity" + desc = "A poster depicting TerraGov's logo and motto, reminding viewers of who's looking out for humankind." + icon_state = "terragov" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/terragov, 32) + +/obj/structure/sign/poster/official/corporate_perks_vacation + name = "Nanotrasen Corporate Perks: Vacation" + desc = "This informational poster provides information on some of the prizes available via the NT Corporate Perks program, including a two-week vacation for two on the resort world Idyllus." + icon_state = "corporate_perks_vacation" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/corporate_perks_vacation, 32) + +/obj/structure/sign/poster/official/jim_nortons + name = "Jim Norton's Québécois Coffee" + desc = "An advertisement for Jim Norton's, the Québécois coffee joint that's taken the galaxy by storm." + icon_state = "jim_nortons" + +/obj/structure/sign/poster/official/jim_nortons/examine_more(mob/user) + . = ..() + . += span_notice("You browse some of the poster's information...") + . += "\t[span_info("From our roots in Trois-Rivières, we've worked to bring you the best coffee money can buy since 1965.")]" + . += "\t[span_info("So stop by Jim's today- have a hot cup of coffee and a donut, and live like the Québécois do.")]" + . += "\t[span_info("Jim Norton's Québécois Coffee: Toujours Le Bienvenu.")]" + return . + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/jim_nortons, 32) + +/obj/structure/sign/poster/official/twenty_four_seven + name = "24-Seven Supermarkets" + desc = "An advertisement for 24-Seven supermarkets, advertising their new 24-Stops as part of their partnership with Nanotrasen." + icon_state = "twenty_four_seven" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/twenty_four_seven, 32) + +/obj/structure/sign/poster/official/tactical_game_cards + name = "Nanotrasen Tactical Game Cards" + desc = "An advertisement for Nanotrasen's TCG cards: BUY MORE CARDS." + icon_state = "tactical_game_cards" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/tactical_game_cards, 32) + +/obj/structure/sign/poster/official/midtown_slice + name = "Midtown Slice Pizza" + desc = "An advertisement for Midtown Slice Pizza, the official pizzeria partner of Nanotrasen. Midtown Slice: like a slice of home, no matter where you are." + icon_state = "midtown_slice" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/midtown_slice, 32) + +//SafetyMoth Original PR at https://github.com/BeeStation/BeeStation-Hornet/pull/1747 (Also pull/1982) +//SafetyMoth art credit goes to AspEv +/obj/structure/sign/poster/official/moth_hardhat + name = "Safety Moth - Hardhats" + desc = "This informational poster uses Safety Moth™ to tell the viewer to wear hardhats in cautious areas. \"It's like a lamp for your head!\"" + icon_state = "aspev_hardhat" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/moth_hardhat, 32) + +/obj/structure/sign/poster/official/moth_piping + name = "Safety Moth - Piping" + desc = "This informational poster uses Safety Moth™ to tell atmospheric technicians correct types of piping to be used. \"Pipes, not Pumps! Proper pipe placement prevents poor performance!\"" + icon_state = "aspev_piping" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/moth_piping, 32) + +/obj/structure/sign/poster/official/moth_meth + name = "Safety Moth - Methamphetamine" + desc = "This informational poster uses Safety Moth™ to tell the viewer to seek CMO approval before cooking methamphetamine. \"Stay close to the target temperature, and never go over!\" ...You shouldn't ever be making this." + icon_state = "aspev_meth" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/moth_meth, 32) + +/obj/structure/sign/poster/official/moth_epi + name = "Safety Moth - Epinephrine" + desc = "This informational poster uses Safety Moth™ to inform the viewer to help injured/deceased crewmen with their epinephrine injectors. \"Prevent organ rot with this one simple trick!\"" + icon_state = "aspev_epi" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/moth_epi, 32) + +/obj/structure/sign/poster/official/moth_delam + name = "Safety Moth - Delamination Safety Precautions" + desc = "This informational poster uses Safety Moth™ to tell the viewer to hide in lockers when the Supermatter Crystal has delaminated, to prevent hallucinations. Evacuating might be a better strategy." + icon_state = "aspev_delam" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/moth_delam, 32) + +//End of AspEv posters + +/obj/structure/sign/poster/fluff/lizards_gas_payment + name = "Please Pay" + desc = "A crudely-made poster asking the reader to please pay for any items they may wish to leave the station with." + icon_state = "gas_payment" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/fluff/lizards_gas_payment, 32) + +/obj/structure/sign/poster/fluff/lizards_gas_power + name = "Conserve Power" + desc = "A crudely-made poster asking the reader to turn off the power before they leave. Hopefully, it's turned on for their re-opening." + icon_state = "gas_power" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/fluff/lizards_gas_power, 32) + +/obj/structure/sign/poster/official/festive + name = "Festive Notice Poster" + desc = "A poster that informs of active holidays. None are today, so you should get back to work." + icon_state = "holiday_none" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/festive, 32) + +/obj/structure/sign/poster/official/boombox + name = "Boombox" + desc = "An outdated poster containing a list of supposed 'kill words' and code phrases. The poster alleges rival corporations use these to remotely deactivate their agents." + icon_state = "boombox" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/boombox, 32) + +/obj/structure/sign/poster/official/download + name = "You Wouldn't Download A Gun" + desc = "A poster reminding the crew that corporate secrets should stay in the workplace." + icon_state = "download_gun" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/official/download, 32) diff --git a/code/game/objects/effects/posters/poster.dm b/code/game/objects/effects/posters/poster.dm new file mode 100644 index 000000000000..7aa4eebd278d --- /dev/null +++ b/code/game/objects/effects/posters/poster.dm @@ -0,0 +1,294 @@ +// This is synced up to the poster placing animation. +#define PLACE_SPEED 37 + +// The poster item + +/** + * The rolled up item form of a poster + * + * In order to create one of these for a specific poster, you must pass the structure form of the poster as an argument to /new(). + * This structure then gets moved into the contents of the item where it will stay until the poster is placed by a player. + * The structure form is [obj/structure/sign/poster] and that's where all the specific posters are defined. + * If you just want a random poster, see [/obj/item/poster/random_official] or [/obj/item/poster/random_contraband] + */ +/obj/item/poster + name = "poorly coded poster" + desc = "You probably shouldn't be holding this." + icon = 'icons/obj/poster.dmi' + force = 0 + resistance_flags = FLAMMABLE + w_class = WEIGHT_CLASS_SMALL + var/poster_type + var/obj/structure/sign/poster/poster_structure + +/obj/item/poster/examine(mob/user) + . = ..() + . += span_notice("You can booby-trap the poster by using a glass shard on it before you put it up.") + +/obj/item/poster/Initialize(mapload, obj/structure/sign/poster/new_poster_structure) + . = ..() + + var/static/list/hovering_item_typechecks = list( + /obj/item/shard = list( + SCREENTIP_CONTEXT_LMB = "Booby trap poster", + ), + ) + AddElement(/datum/element/contextual_screentip_item_typechecks, hovering_item_typechecks) + + if(new_poster_structure && (new_poster_structure.loc != src)) + new_poster_structure.forceMove(src) //The poster structure *must* be in the item's contents for the exited() proc to properly clean up when placing the poster + poster_structure = new_poster_structure + if(!new_poster_structure && poster_type) + poster_structure = new poster_type(src) + + // posters store what name and description they would like their + // rolled up form to take. + if(poster_structure) + if(QDELETED(poster_structure)) + stack_trace("A poster was initialized with a qdeleted poster_structure, something's gone wrong") + return INITIALIZE_HINT_QDEL + name = poster_structure.poster_item_name + desc = poster_structure.poster_item_desc + icon_state = poster_structure.poster_item_icon_state + + name = "[name] - [poster_structure.original_name]" + +/obj/item/poster/attackby(obj/item/I, mob/user, params) + if(!istype(I, /obj/item/shard)) + return ..() + + if (poster_structure.trap?.resolve()) + balloon_alert(user, "already trapped!") + return + + if(!user.transferItemToLoc(I, poster_structure)) + return + + poster_structure.trap = WEAKREF(I) + to_chat(user, span_notice("You conceal the [I.name] inside the rolled up poster.")) + +/obj/item/poster/Exited(atom/movable/gone, direction) + . = ..() + if(gone == poster_structure) + poster_structure = null + if(!QDELING(src)) + qdel(src) //we're now a poster, huzzah! + +/obj/item/poster/handle_atom_del(atom/deleting_atom) + if(deleting_atom == poster_structure) + poster_structure.moveToNullspace() //get it the fuck out of us since atom/destroy qdels contents and it'll cause a qdel loop + return ..() + +/obj/item/poster/Destroy(force) + QDEL_NULL(poster_structure) + return ..() + +// The poster sign/structure + +/** + * The structure form of a poster. + * + * These are what get placed on maps as posters. They are also what gets created when a player places a poster on a wall. + * For the item form that can be spawned for players, see [/obj/item/poster] + */ +/obj/structure/sign/poster + name = "poster" + var/original_name + desc = "A large piece of space-resistant printed paper." + icon = 'icons/obj/poster.dmi' + anchored = TRUE + buildable_sign = FALSE //Cannot be unwrenched from a wall. + var/ruined = FALSE + var/random_basetype + var/never_random = FALSE // used for the 'random' subclasses. + ///Exclude posters of these types from being added to the random pool + var/list/blacklisted_types = list() + ///Whether the poster should be printable from library management computer. Mostly exists to keep directionals from being printed. + var/printable = FALSE + + var/poster_item_name = "hypothetical poster" + var/poster_item_desc = "This hypothetical poster item should not exist, let's be honest here." + var/poster_item_icon_state = "rolled_poster" + var/poster_item_type = /obj/item/poster + ///A sharp shard of material can be hidden inside of a poster, attempts to embed when it is torn down. + var/datum/weakref/trap + +/obj/structure/sign/poster/Initialize(mapload) + . = ..() + if(random_basetype) + randomise(random_basetype) + if(!ruined) + original_name = name // can't use initial because of random posters + name = "poster - [name]" + desc = "A large piece of space-resistant printed paper. [desc]" + + AddElement(/datum/element/beauty, 300) + +/// Adds contextual screentips +/obj/structure/sign/poster/add_context(atom/source, list/context, obj/item/held_item, mob/user) + if (!held_item) + if (ruined) + return . + context[SCREENTIP_CONTEXT_LMB] = "Rip up poster" + return CONTEXTUAL_SCREENTIP_SET + + if (held_item.tool_behaviour == TOOL_WIRECUTTER) + if (ruined) + context[SCREENTIP_CONTEXT_LMB] = "Clean up remnants" + return CONTEXTUAL_SCREENTIP_SET + context[SCREENTIP_CONTEXT_LMB] = "Take down poster" + return CONTEXTUAL_SCREENTIP_SET + return . + +/obj/structure/sign/poster/proc/randomise(base_type) + var/list/poster_types = subtypesof(base_type) + if(length(blacklisted_types)) + for(var/iterated_type in blacklisted_types) + poster_types -= typesof(iterated_type) + var/list/approved_types = list() + for(var/obj/structure/sign/poster/type_of_poster as anything in poster_types) + if(initial(type_of_poster.icon_state) && !initial(type_of_poster.never_random)) + approved_types |= type_of_poster + + var/obj/structure/sign/poster/selected = pick(approved_types) + + name = initial(selected.name) + desc = initial(selected.desc) + icon_state = initial(selected.icon_state) + icon = initial(selected.icon) + poster_item_name = initial(selected.poster_item_name) + poster_item_desc = initial(selected.poster_item_desc) + poster_item_icon_state = initial(selected.poster_item_icon_state) + ruined = initial(selected.ruined) + if(length(GLOB.holidays) && prob(30)) // its the holidays! lets get festive + apply_holiday() + update_appearance() + +/// allows for posters to become festive posters during holidays +/obj/structure/sign/poster/proc/apply_holiday() + if(!length(GLOB.holidays)) + return + var/active_holiday = pick(GLOB.holidays) + var/datum/holiday/holi_data = GLOB.holidays[active_holiday] + + if(holi_data.poster_name == "generic celebration poster") + return + name = holi_data.poster_name + desc = holi_data.poster_desc + icon_state = holi_data.poster_icon + +/obj/structure/sign/poster/attackby(obj/item/tool, mob/user, params) + if(tool.tool_behaviour == TOOL_WIRECUTTER) + tool.play_tool_sound(src, 100) + if(ruined) + to_chat(user, span_notice("You remove the remnants of the poster.")) + qdel(src) + else + to_chat(user, span_notice("You carefully remove the poster from the wall.")) + roll_and_drop(Adjacent(user) ? get_turf(user) : loc) + +/obj/structure/sign/poster/attack_hand(mob/user, list/modifiers) + . = ..() + if(.) + return + if(ruined) + return + + visible_message(span_notice("[user] rips [src] in a single, decisive motion!") ) + playsound(src.loc, 'sound/items/poster_ripped.ogg', 100, TRUE) + spring_trap(user) + + var/obj/structure/sign/poster/ripped/R = new(loc) + R.pixel_y = pixel_y + R.pixel_x = pixel_x + R.add_fingerprint(user) + qdel(src) + +/obj/structure/sign/poster/proc/spring_trap(mob/user) + var/obj/item/shard/payload = trap?.resolve() + if (!payload) + return + + to_chat(user, span_warning("There's something sharp behind this! What the hell?")) + if(!can_embed_trap(user) || !payload.tryEmbed(user.get_active_hand(), forced = TRUE)) + visible_message(span_notice("A [payload.name] falls from behind the poster.") ) + payload.forceMove(user.drop_location()) + else + SEND_SIGNAL(src, COMSIG_POSTER_TRAP_SUCCEED, user) + +/obj/structure/sign/poster/proc/can_embed_trap(mob/living/carbon/human/user) + if (!istype(user) || HAS_TRAIT(user, TRAIT_PIERCEIMMUNE)) + return FALSE + return !user.gloves || !(user.gloves.body_parts_covered & HANDS) || HAS_TRAIT(user, TRAIT_FINGERPRINT_PASSTHROUGH) || HAS_TRAIT(user.gloves, TRAIT_FINGERPRINT_PASSTHROUGH) + +/obj/structure/sign/poster/proc/roll_and_drop(atom/location) + pixel_x = 0 + pixel_y = 0 + var/obj/item/poster/rolled_poster = new poster_item_type(location, src) // /obj/structure/sign/poster/wanted/roll_and_drop() has some snowflake handling due to icon memes, if you make a major change to this, don't forget to update it too. <3 + forceMove(rolled_poster) + return rolled_poster + +//separated to reduce code duplication. Moved here for ease of reference and to unclutter r_wall/attackby() +/turf/closed/wall/proc/place_poster(obj/item/poster/rolled_poster, mob/user) + if(!rolled_poster.poster_structure) + to_chat(user, span_warning("[rolled_poster] has no poster... inside it? Inform a coder!")) + return + + // Deny placing posters on currently-diagonal walls, although the wall may change in the future. + if (smoothing_flags & SMOOTH_DIAGONAL_CORNERS) + for (var/overlay in overlays) + var/image/new_image = overlay + if(copytext(new_image.icon_state, 1, 3) == "d-") //3 == length("d-") + 1 + return + + var/stuff_on_wall = 0 + for(var/obj/contained_object in contents) //Let's see if it already has a poster on it or too much stuff + if(istype(contained_object, /obj/structure/sign/poster)) + balloon_alert(user, "no room!") + return + stuff_on_wall++ + if(stuff_on_wall == 3) + balloon_alert(user, "no room!") + return + + balloon_alert(user, "hanging poster...") + var/obj/structure/sign/poster/placed_poster = rolled_poster.poster_structure + + flick("poster_being_set", placed_poster) + placed_poster.forceMove(src) //deletion of the poster is handled in poster/Exited(), so don't have to worry about P anymore. + playsound(src, 'sound/items/poster_being_created.ogg', 100, TRUE) + + var/turf/user_drop_location = get_turf(user) //cache this so it just falls to the ground if they move. also no tk memes allowed. + if(!do_after(user, PLACE_SPEED, placed_poster, extra_checks = CALLBACK(placed_poster, TYPE_PROC_REF(/obj/structure/sign/poster, snowflake_wall_turf_check), src))) + placed_poster.roll_and_drop(user_drop_location) + return + + placed_poster.on_placed_poster(user) + return TRUE + +/obj/structure/sign/poster/proc/snowflake_wall_turf_check(atom/hopefully_still_a_wall_turf) //since turfs never get deleted but instead change type, make sure we're still being placed on a wall. + return iswallturf(hopefully_still_a_wall_turf) + +/obj/structure/sign/poster/proc/on_placed_poster(mob/user) + to_chat(user, span_notice("You place the poster!")) + +// Various possible posters follow + +/obj/structure/sign/poster/ripped + ruined = TRUE + icon_state = "poster_ripped" + name = "ripped poster" + desc = "You can't make out anything from the poster's original print. It's ruined." + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/ripped, 32) + +/obj/structure/sign/poster/random + name = "random poster" // could even be ripped + icon_state = "random_anything" + never_random = TRUE + random_basetype = /obj/structure/sign/poster + blacklisted_types = list(/obj/structure/sign/poster/traitor) + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/random, 32) + +#undef PLACE_SPEED diff --git a/code/game/objects/effects/spawners/random/food_or_drink.dm b/code/game/objects/effects/spawners/random/food_or_drink.dm index a1b9976cab2c..e6cf9db1f82c 100644 --- a/code/game/objects/effects/spawners/random/food_or_drink.dm +++ b/code/game/objects/effects/spawners/random/food_or_drink.dm @@ -56,6 +56,7 @@ /obj/item/seeds/liberty = 5, /obj/item/seeds/replicapod = 5, /obj/item/seeds/reishi = 5, + /obj/item/seeds/seedling = 5, /obj/item/seeds/nettle/death = 1, /obj/item/seeds/plump/walkingmushroom = 1, /obj/item/seeds/cannabis/rainbow = 1, diff --git a/code/game/objects/effects/spawners/random/lavaland_mobs.dm b/code/game/objects/effects/spawners/random/lavaland_mobs.dm index 18fa3ff2196d..2e3effdd30a6 100644 --- a/code/game/objects/effects/spawners/random/lavaland_mobs.dm +++ b/code/game/objects/effects/spawners/random/lavaland_mobs.dm @@ -8,11 +8,11 @@ loot = list( /mob/living/basic/mining/bileworm = 1, /mob/living/basic/mining/goliath = 1, + /mob/living/basic/mining/legion = 1, /mob/living/basic/mining/lobstrosity/lava = 1, /mob/living/basic/mining/watcher = 1, - /mob/living/simple_animal/hostile/asteroid/brimdemon = 1, + /mob/living/basic/mining/brimdemon = 1, /mob/living/basic/mining/goldgrub = 1, - /mob/living/simple_animal/hostile/asteroid/hivelord/legion = 1, ) /// Spawns random watcher variants during map generation @@ -46,6 +46,6 @@ desc = "Chance to spawn a rare shiny version." icon_state = "legion" loot = list( - /mob/living/simple_animal/hostile/asteroid/hivelord/legion = 19, - /mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf = 1, + /mob/living/basic/mining/legion = 19, + /mob/living/basic/mining/legion/dwarf = 1, ) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index e3af3614fa1c..3af16ddff55c 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -212,9 +212,13 @@ var/offensive_notes /// Used in obj/item/examine to determines whether or not to detail an item's statistics even if it does not meet the force requirements var/override_notes = FALSE + /// Used if we want to have a custom verb text for throwing. "John Spaceman flicks the ciggerate" for example. + var/throw_verb -/obj/item/Initialize(mapload) + /// A lazylist used for applying fantasy values, contains the actual modification applied to a variable. + var/list/fantasy_modifications +/obj/item/Initialize(mapload) if(attack_verb_continuous) attack_verb_continuous = string_list(attack_verb_continuous) if(attack_verb_simple) @@ -1195,7 +1199,7 @@ return src /** - * tryEmbed() is for when you want to try embedding something without dealing with the damage + hit messages of calling hitby() on the item while targetting the target. + * tryEmbed() is for when you want to try embedding something without dealing with the damage + hit messages of calling hitby() on the item while targeting the target. * * Really, this is used mostly with projectiles with shrapnel payloads, from [/datum/element/embed/proc/checkEmbedProjectile], and called on said shrapnel. Mostly acts as an intermediate between different embed elements. * @@ -1584,3 +1588,41 @@ /obj/item/update_atom_colour() . = ..() update_slot_icon() + +/// Modifies the fantasy variable +/obj/item/proc/modify_fantasy_variable(variable_key, value, bonus, minimum = 0) + if(LAZYACCESS(fantasy_modifications, variable_key) != null) + stack_trace("modify_fantasy_variable was called twice for the same key '[variable_key]' on type '[type]' before reset_fantasy_variable could be called!") + var/intended_target = value + bonus + value = max(minimum, intended_target) + + var/difference = intended_target - value + var/modified_amount = bonus - difference + LAZYSET(fantasy_modifications, variable_key, modified_amount) + return value + +/// Returns the original fantasy variable value +/obj/item/proc/reset_fantasy_variable(variable_key, current_value) + var/modification = LAZYACCESS(fantasy_modifications, variable_key) + LAZYREMOVE(fantasy_modifications, variable_key) + if(!modification) + return current_value + return current_value - modification + +/obj/item/proc/apply_fantasy_bonuses(bonus) + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(src, COMSIG_ITEM_APPLY_FANTASY_BONUSES, bonus) + force = modify_fantasy_variable("force", force, bonus) + throwforce = modify_fantasy_variable("throwforce", throwforce, bonus) + wound_bonus = modify_fantasy_variable("wound_bonus", wound_bonus, bonus) + bare_wound_bonus = modify_fantasy_variable("bare_wound_bonus", bare_wound_bonus, bonus) + toolspeed = modify_fantasy_variable("toolspeed", toolspeed, -bonus/10, minimum = 0.1) + +/obj/item/proc/remove_fantasy_bonuses(bonus) + SHOULD_CALL_PARENT(TRUE) + force = reset_fantasy_variable("force", force) + throwforce = reset_fantasy_variable("throwforce", throwforce) + wound_bonus = reset_fantasy_variable("wound_bonus", wound_bonus) + bare_wound_bonus = reset_fantasy_variable("bare_wound_bonus", bare_wound_bonus) + toolspeed = reset_fantasy_variable("toolspeed", toolspeed) + SEND_SIGNAL(src, COMSIG_ITEM_REMOVE_FANTASY_BONUSES, bonus) diff --git a/code/game/objects/items/cardboard_cutouts.dm b/code/game/objects/items/cardboard_cutouts.dm index 570d59191116..6a3ead00ca07 100644 --- a/code/game/objects/items/cardboard_cutouts.dm +++ b/code/game/objects/items/cardboard_cutouts.dm @@ -6,6 +6,7 @@ icon_state = "cutout_basic" w_class = WEIGHT_CLASS_BULKY resistance_flags = FLAMMABLE + obj_flags = CAN_BE_HIT item_flags = NO_PIXEL_RANDOM_DROP /// If the cutout is pushed over and has to be righted var/pushed_over = FALSE @@ -60,32 +61,20 @@ /obj/item/cardboard_cutout/attackby(obj/item/I, mob/living/user, params) if(istype(I, /obj/item/toy/crayon)) change_appearance(I, user) - return - // Why yes, this does closely resemble mob and object attack code. - if(I.item_flags & NOBLUDGEON) - return - if(!I.force) - playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), TRUE, -1) - else if(I.hitsound) - playsound(loc, I.hitsound, get_clamped_volume(), TRUE, -1) - - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(src) - - if(I.force) - user.visible_message(span_danger("[user] hits [src] with [I]!"), \ - span_danger("You hit [src] with [I]!")) - if(prob(I.force)) - push_over() - -/obj/item/cardboard_cutout/bullet_act(obj/projectile/P, def_zone, piercing_hit = FALSE) - if(istype(P, /obj/projectile/bullet/reusable)) - P.on_hit(src, 0, piercing_hit) - visible_message(span_danger("[src] is hit by [P]!")) - playsound(src, 'sound/weapons/slice.ogg', 50, TRUE) - if(prob(P.damage)) + return TRUE + + return ..() + +/obj/item/cardboard_cutout/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration) + . = ..() + var/damage_sustained = . || 0 + if((damage_flag == BULLET || damage_flag == MELEE) && (damage_type == BRUTE) && prob(damage_sustained)) push_over() - return BULLET_ACT_HIT + +/obj/item/cardboard_cutout/deconstruct(disassembled) + if(!(flags_1 & (HOLOGRAM_1|NODECONSTRUCT_1))) + new /obj/item/stack/sheet/cardboard(loc, 1) + return ..() /proc/get_cardboard_cutout_instance(datum/cardboard_cutout/cardboard_cutout) ASSERT(ispath(cardboard_cutout), "[cardboard_cutout] is not a path of /datum/cardboard_cutout") diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index 21516c12ccf2..93768348d2ac 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -106,6 +106,15 @@ fire = 100 acid = 100 +/obj/item/card/id/apply_fantasy_bonuses(bonus) + . = ..() + if(bonus >= 15) + add_access(SSid_access.get_region_access_list(list(REGION_ALL_GLOBAL)), mode = FORCE_ADD_ALL) + else if(bonus >= 10) + add_access(SSid_access.get_region_access_list(list(REGION_ALL_STATION)), mode = FORCE_ADD_ALL) + else if(bonus <= -10) + clear_access() + /obj/item/card/id/Initialize(mapload) . = ..() diff --git a/code/game/objects/items/chainsaw.dm b/code/game/objects/items/chainsaw.dm index 00d532189ab4..1d7c6c450382 100644 --- a/code/game/objects/items/chainsaw.dm +++ b/code/game/objects/items/chainsaw.dm @@ -27,6 +27,18 @@ ///The looping sound for our chainsaw when running var/datum/looping_sound/chainsaw/chainsaw_loop +/obj/item/chainsaw/apply_fantasy_bonuses(bonus) + . = ..() + force_on = modify_fantasy_variable("force_on", force_on, bonus) + if(on) + force = force_on + +/obj/item/chainsaw/remove_fantasy_bonuses(bonus) + force_on = reset_fantasy_variable("force_on", force_on) + if(on) + force = force_on + return ..() + /obj/item/chainsaw/Initialize(mapload) . = ..() chainsaw_loop = new(src) diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm index eb9444784e21..4fc8935b3d49 100644 --- a/code/game/objects/items/cigs_lighters.dm +++ b/code/game/objects/items/cigs_lighters.dm @@ -138,6 +138,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM body_parts_covered = null grind_results = list() heat = 1000 + throw_verb = "flick" /// Whether this cigarette has been lit. var/lit = FALSE /// Whether this cigarette should start lit. diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm index 7ce9de6d2f86..673e30b04776 100644 --- a/code/game/objects/items/crayons.dm +++ b/code/game/objects/items/crayons.dm @@ -815,7 +815,8 @@ carbon_target.set_eye_blur_if_lower(6 SECONDS) carbon_target.adjust_temp_blindness(2 SECONDS) if(carbon_target.get_eye_protection() <= 0) // no eye protection? ARGH IT BURNS. Warning: don't add a stun here. It's a roundstart item with some quirks. - carbon_target.apply_effects(eyeblur = 5, jitter = 10) + carbon_target.adjust_jitter(1 SECONDS) + carbon_target.adjust_eye_blur(0.5 SECONDS) flash_color(carbon_target, flash_color=paint_color, flash_time=40) if(ishuman(carbon_target) && actually_paints) var/mob/living/carbon/human/human_target = carbon_target diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index 6a94bb3598b3..6b2a1ef0eacd 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -233,7 +233,7 @@ holo_cooldown = world.time + 10 SECONDS return -// see: [/datum/wound/burn/proc/uv()] +// see: [/datum/wound/burn/flesh/proc/uv()] /obj/item/flashlight/pen/paramedic name = "paramedic penlight" desc = "A high-powered UV penlight intended to help stave off infection in the field on serious burned patients. Probably really bad to look into." diff --git a/code/game/objects/items/devices/polycircuit.dm b/code/game/objects/items/devices/polycircuit.dm index 9dbdbff993d7..5b7fd42d6f6b 100644 --- a/code/game/objects/items/devices/polycircuit.dm +++ b/code/game/objects/items/devices/polycircuit.dm @@ -51,7 +51,7 @@ else to_chat(user, span_notice("You navigate the sharp edges of circuitry and remove a single board from [src]")) else - H.apply_damage(15, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + H.apply_damage(15, BRUTE, pick(GLOB.arm_zones)) to_chat(user, span_warning("You give yourself a wicked cut on [src]'s many sharp corners and edges!")) /obj/item/stack/circuit_stack/full diff --git a/code/game/objects/items/dna_probe.dm b/code/game/objects/items/dna_probe.dm index 4ea89d0a95e7..6c9651a31421 100644 --- a/code/game/objects/items/dna_probe.dm +++ b/code/game/objects/items/dna_probe.dm @@ -135,7 +135,7 @@ to_chat(user, span_notice("You pull out the needle from [src] and flip the switch, and start injecting yourself with it.")) if(!do_after(user, CARP_MIX_DNA_TIMER)) return - var/mob/living/simple_animal/hostile/space_dragon/new_dragon = user.change_mob_type(/mob/living/simple_animal/hostile/space_dragon, location = loc, delete_old_mob = TRUE) + var/mob/living/basic/space_dragon/new_dragon = user.change_mob_type(/mob/living/basic/space_dragon, location = loc, delete_old_mob = TRUE) new_dragon.add_filter("anger_glow", 3, list("type" = "outline", "color" = "#ff330030", "size" = 5)) new_dragon.add_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) priority_announce("A large organic energy flux has been recorded near of [station_name()], please stand-by.", "Lifesign Alert") diff --git a/code/game/objects/items/food/monkeycube.dm b/code/game/objects/items/food/monkeycube.dm index 5cf9db79fd01..e78d45ad2e83 100644 --- a/code/game/objects/items/food/monkeycube.dm +++ b/code/game/objects/items/food/monkeycube.dm @@ -8,7 +8,6 @@ foodtypes = MEAT | SUGAR food_flags = FOOD_FINGER_FOOD w_class = WEIGHT_CLASS_TINY - var/faction var/spawned_mob = /mob/living/carbon/human/species/monkey /obj/item/food/monkeycube/proc/Expand() @@ -62,7 +61,7 @@ /datum/reagent/medicine/strange_reagent = 5, ) tastes = list("the jungle" = 1, "bananas" = 1, "jimmies" = 1) - spawned_mob = /mob/living/simple_animal/hostile/gorilla + spawned_mob = /mob/living/basic/gorilla /obj/item/food/monkeycube/chicken name = "chicken cube" diff --git a/code/game/objects/items/grenades/_grenade.dm b/code/game/objects/items/grenades/_grenade.dm index 404c66640c4b..aa0e506dba31 100644 --- a/code/game/objects/items/grenades/_grenade.dm +++ b/code/game/objects/items/grenades/_grenade.dm @@ -66,6 +66,29 @@ if(!QDELETED(src)) qdel(src) +/obj/item/grenade/apply_fantasy_bonuses(bonus) + . = ..() + apply_grenade_fantasy_bonuses(bonus) + +/obj/item/grenade/remove_fantasy_bonuses(bonus) + remove_grenade_fantasy_bonuses(bonus) + return ..() + +/obj/item/grenade/proc/apply_grenade_fantasy_bonuses(quality) + if(ex_dev == 0 && ex_heavy == 0 && ex_light == 0 && ex_flame == 0) + return + var/devIncrease = round(quality / 10) + var/heavyIncrease = round(quality / 5) + var/lightIncrease = round(quality / 2) + ex_dev = modify_fantasy_variable("ex_dev", ex_dev, devIncrease, 0) + ex_heavy = modify_fantasy_variable("ex_heavy", ex_heavy, heavyIncrease, 0) + ex_light = modify_fantasy_variable("ex_light", ex_light, lightIncrease, 0) + +/obj/item/grenade/proc/remove_grenade_fantasy_bonuses(quality) + ex_dev = reset_fantasy_variable("ex_dev", ex_dev) + ex_heavy = reset_fantasy_variable("ex_heavy", ex_heavy) + ex_light = reset_fantasy_variable("ex_light", ex_light) + /** * Checks for various ways to botch priming a grenade. * diff --git a/code/game/objects/items/grenades/chem_grenade.dm b/code/game/objects/items/grenades/chem_grenade.dm index 06d60173bd84..6871c4ed0953 100644 --- a/code/game/objects/items/grenades/chem_grenade.dm +++ b/code/game/objects/items/grenades/chem_grenade.dm @@ -34,6 +34,12 @@ stage_change() // If no argument is set, it will change the stage to the current stage, useful for stock grenades that start READY. wires = new /datum/wires/explosive/chem_grenade(src) +/obj/item/grenade/chem_grenade/apply_grenade_fantasy_bonuses(quality) + threatscale = modify_fantasy_variable("threatscale", threatscale, quality/10) + +/obj/item/grenade/chem_grenade/remove_grenade_fantasy_bonuses(quality) + threatscale = reset_fantasy_variable("threatscale", threatscale) + /obj/item/grenade/chem_grenade/examine(mob/user) display_timer = (stage == GRENADE_READY) //show/hide the timer based on assembly state . = ..() diff --git a/code/game/objects/items/grenades/clusterbuster.dm b/code/game/objects/items/grenades/clusterbuster.dm index a12c302c188b..e4862919c1c1 100644 --- a/code/game/objects/items/grenades/clusterbuster.dm +++ b/code/game/objects/items/grenades/clusterbuster.dm @@ -18,6 +18,14 @@ var/max_spawned = 8 var/segment_chance = 35 +/obj/item/grenade/clusterbuster/apply_grenade_fantasy_bonuses(quality) + min_spawned = modify_fantasy_variable("min_spawned", min_spawned, round(quality/2)) + max_spawned = modify_fantasy_variable("max_spawned", max_spawned, round(quality/2)) + +/obj/item/grenade/clusterbuster/remove_grenade_fantasy_bonuses(quality) + min_spawned = reset_fantasy_variable("min_spawned", min_spawned) + max_spawned = reset_fantasy_variable("max_spawned", max_spawned) + /obj/item/grenade/clusterbuster/detonate(mob/living/lanced_by) . = ..() if(!.) diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm index cd5e43c2daf2..c472b2698b42 100644 --- a/code/game/objects/items/grenades/flashbang.dm +++ b/code/game/objects/items/grenades/flashbang.dm @@ -6,6 +6,12 @@ righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' var/flashbang_range = 7 //how many tiles away the mob will be stunned. +/obj/item/grenade/flashbang/apply_grenade_fantasy_bonuses(quality) + flashbang_range = modify_fantasy_variable("flashbang_range", flashbang_range, quality) + +/obj/item/grenade/flashbang/remove_grenade_fantasy_bonuses(quality) + flashbang_range = reset_fantasy_variable("flashbang_range", flashbang_range) + /obj/item/grenade/flashbang/detonate(mob/living/lanced_by) . = ..() if(!.) @@ -105,7 +111,7 @@ living_mob.Paralyze(20) living_mob.Knockdown(200) living_mob.soundbang_act(1, 200, 10, 15) - if(living_mob.apply_damages(10, 10)) + if(living_mob.apply_damages(brute = 10, burn = 10)) to_chat(living_mob, span_userdanger("The blast from \the [src] bruises and burns you!")) // only checking if they're on top of the tile, cause being one tile over will be its own punishment diff --git a/code/game/objects/items/grenades/hypno.dm b/code/game/objects/items/grenades/hypno.dm index 035533d47310..0d7601df204a 100644 --- a/code/game/objects/items/grenades/hypno.dm +++ b/code/game/objects/items/grenades/hypno.dm @@ -7,6 +7,12 @@ righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' var/flashbang_range = 7 +/obj/item/grenade/hypnotic/apply_grenade_fantasy_bonuses(quality) + flashbang_range = modify_fantasy_variable("flashbang_range", flashbang_range, quality) + +/obj/item/grenade/hypnotic/remove_grenade_fantasy_bonuses(quality) + flashbang_range = reset_fantasy_variable("flashbang_range", flashbang_range) + /obj/item/grenade/hypnotic/detonate(mob/living/lanced_by) . = ..() if(!.) diff --git a/code/game/objects/items/grenades/plastic.dm b/code/game/objects/items/grenades/plastic.dm index 363bd3e17657..10293d9e1335 100644 --- a/code/game/objects/items/grenades/plastic.dm +++ b/code/game/objects/items/grenades/plastic.dm @@ -23,6 +23,19 @@ /// Maximum timer for c4 charges var/maximum_timer = 60000 +/obj/item/grenade/c4/apply_grenade_fantasy_bonuses(quality) + var/devIncrease = round(quality / 10) + var/heavyIncrease = round(quality / 5) + var/lightIncrease = round(quality / 2) + boom_sizes[1] = modify_fantasy_variable("devIncrease", boom_sizes[1], devIncrease) + boom_sizes[2] = modify_fantasy_variable("heavyIncrease", boom_sizes[2], heavyIncrease) + boom_sizes[3] = modify_fantasy_variable("lightIncrease", boom_sizes[3], lightIncrease) + +/obj/item/grenade/c4/remove_grenade_fantasy_bonuses(quality) + boom_sizes[1] = reset_fantasy_variable("devIncrease", boom_sizes[1]) + boom_sizes[2] = reset_fantasy_variable("heavyIncrease", boom_sizes[2]) + boom_sizes[3] = reset_fantasy_variable("lightIncrease", boom_sizes[3]) + /obj/item/grenade/c4/Initialize(mapload) . = ..() AddElement(/datum/element/empprotection, EMP_PROTECT_WIRES) diff --git a/code/game/objects/items/grenades/spawnergrenade.dm b/code/game/objects/items/grenades/spawnergrenade.dm index 3e8c81054f12..1b9d9ff27d09 100644 --- a/code/game/objects/items/grenades/spawnergrenade.dm +++ b/code/game/objects/items/grenades/spawnergrenade.dm @@ -7,6 +7,12 @@ var/spawner_type = null // must be an object path var/deliveryamt = 1 // amount of type to deliver +/obj/item/grenade/spawnergrenade/apply_grenade_fantasy_bonuses(quality) + deliveryamt = modify_fantasy_variable("deliveryamt", deliveryamt, quality) + +/obj/item/grenade/spawnergrenade/remove_grenade_fantasy_bonuses(quality) + deliveryamt = reset_fantasy_variable("deliveryamt", deliveryamt) + /obj/item/grenade/spawnergrenade/detonate(mob/living/lanced_by) // Prime now just handles the two loops that query for people in lockers and people who can see it. . = ..() if(!.) @@ -55,7 +61,7 @@ desc = "A sleek device often given to clowns on their 10th birthdays for protection. You can hear faint scratching coming from within." icon_state = "clown_ball" inhand_icon_state = null - spawner_type = list(/mob/living/simple_animal/hostile/retaliate/clown/fleshclown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk, /mob/living/simple_animal/hostile/retaliate/clown/longface, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus, /mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton, /mob/living/simple_animal/hostile/retaliate/clown/banana, /mob/living/simple_animal/hostile/retaliate/clown/honkling, /mob/living/simple_animal/hostile/retaliate/clown/lube) + spawner_type = list(/mob/living/basic/clown/fleshclown, /mob/living/basic/clown/clownhulk, /mob/living/basic/clown/longface, /mob/living/basic/clown/clownhulk/chlown, /mob/living/basic/clown/clownhulk/honkmunculus, /mob/living/basic/clown/mutant/glutton, /mob/living/basic/clown/banana, /mob/living/basic/clown/honkling, /mob/living/basic/clown/lube) deliveryamt = 1 /obj/item/grenade/spawnergrenade/clown_broken @@ -63,5 +69,5 @@ desc = "A sleek device often given to clowns on their 10th birthdays for protection. While a typical C.L.U.W.N.E only holds one creature, sometimes foolish young clowns try to cram more in, often to disasterous effect." icon_state = "clown_broken" inhand_icon_state = null - spawner_type = /mob/living/simple_animal/hostile/retaliate/clown/mutant + spawner_type = /mob/living/basic/clown/mutant deliveryamt = 5 diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index c2ddc1ea732a..5349ea85b23e 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -128,7 +128,7 @@ return FALSE var/obj/item/bodypart/head/the_head = target.get_bodypart(BODY_ZONE_HEAD) - if(!(the_head.biological_state & BIO_FLESH) || !IS_ORGANIC_LIMB(the_head)) + if(!(the_head.biological_state & BIO_FLESH)) to_chat(user, span_warning("You can't noogie [target], [target.p_they()] [target.p_have()] no skin on [target.p_their()] head!")) return diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm index 3d121f854f87..a24aeb45ce4b 100644 --- a/code/game/objects/items/handcuffs.dm +++ b/code/game/objects/items/handcuffs.dm @@ -40,6 +40,7 @@ throw_range = 5 custom_materials = list(/datum/material/iron=500) breakouttime = 1 MINUTES + var/handcuff_time = 3 SECONDS armor_type = /datum/armor/restraints_handcuffs custom_price = PAYCHECK_COMMAND * 0.35 ///Sound that plays when starting to put handcuffs on someone @@ -47,6 +48,14 @@ ///If set, handcuffs will be destroyed on application and leave behind whatever this is set to. var/trashtype = null +/obj/item/restraints/handcuffs/apply_fantasy_bonuses(bonus) + . = ..() + handcuff_time = modify_fantasy_variable("handcuff_time", handcuff_time, -bonus * 2, minimum = 0.3 SECONDS) + +/obj/item/restraints/handcuffs/remove_fantasy_bonuses(bonus) + handcuff_time = reset_fantasy_variable("handcuff_time", handcuff_time) + return ..() + /datum/armor/restraints_handcuffs fire = 50 acid = 50 @@ -70,7 +79,7 @@ to_chat(C, span_userdanger("As you feel someone grab your wrists, [src] start digging into your skin!")) playsound(loc, cuffsound, 30, TRUE, -2) log_combat(user, C, "attempted to handcuff") - if(do_after(user, 3 SECONDS, C, timed_action_flags = IGNORE_SLOWDOWNS) && C.canBeHandcuffed()) + if(do_after(user, handcuff_time, C, timed_action_flags = IGNORE_SLOWDOWNS) && C.canBeHandcuffed()) if(iscyborg(user)) apply_cuffs(C, user, TRUE) else diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm index 36a3a643fde0..7a3f7a2e18fc 100644 --- a/code/game/objects/items/holy_weapons.dm +++ b/code/game/objects/items/holy_weapons.dm @@ -29,9 +29,9 @@ success_forcesay = "BEGONE FOUL MAGIKS!!", \ tip_text = "Clear rune", \ on_clear_callback = CALLBACK(src, PROC_REF(on_cult_rune_removed)), \ - effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune, /obj/effect/cosmic_rune)) - - AddElement(/datum/element/bane, target_type = /mob/living/simple_animal/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE) + effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune, /obj/effect/cosmic_rune), \ + ) + AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE) if(!GLOB.holy_weapon_type && type == /obj/item/nullrod) var/list/rods = list() diff --git a/code/game/objects/items/melee/baton.dm b/code/game/objects/items/melee/baton.dm index 0b8c9b0a4da9..70b0330305a9 100644 --- a/code/game/objects/items/melee/baton.dm +++ b/code/game/objects/items/melee/baton.dm @@ -42,22 +42,22 @@ /// Boolean on whether people with chunky fingers can use this baton. var/chunky_finger_usable = FALSE - /// The context to show when the baton is active and targetting a living thing + /// The context to show when the baton is active and targeting a living thing var/context_living_target_active = "Stun" - /// The context to show when the baton is active and targetting a living thing in combat mode + /// The context to show when the baton is active and targeting a living thing in combat mode var/context_living_target_active_combat_mode = "Stun" - /// The context to show when the baton is inactive and targetting a living thing + /// The context to show when the baton is inactive and targeting a living thing var/context_living_target_inactive = "Prod" - /// The context to show when the baton is inactive and targetting a living thing in combat mode + /// The context to show when the baton is inactive and targeting a living thing in combat mode var/context_living_target_inactive_combat_mode = "Attack" - /// The RMB context to show when the baton is active and targetting a living thing + /// The RMB context to show when the baton is active and targeting a living thing var/context_living_rmb_active = "Attack" - /// The RMB context to show when the baton is inactive and targetting a living thing + /// The RMB context to show when the baton is inactive and targeting a living thing var/context_living_rmb_inactive = "Attack" /obj/item/melee/baton/Initialize(mapload) @@ -95,6 +95,15 @@ if(BATON_ATTACKING) finalize_baton_attack(target, user, modifiers) +/obj/item/melee/baton/apply_fantasy_bonuses(bonus) + . = ..() + stamina_damage = modify_fantasy_variable("stamina_damage", stamina_damage, bonus * 4) + + +/obj/item/melee/baton/remove_fantasy_bonuses(bonus) + stamina_damage = reset_fantasy_variable("stamina_damage", stamina_damage) + return ..() + /obj/item/melee/baton/add_item_context(datum/source, list/context, atom/target, mob/living/user) if (isturf(target)) return NONE diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm index 83a304584043..a0fd7b64e2d9 100644 --- a/code/game/objects/items/melee/misc.dm +++ b/code/game/objects/items/melee/misc.dm @@ -208,6 +208,7 @@ shard.countdown = null START_PROCESSING(SSobj, src) visible_message(span_warning("[src] appears, balanced ever so perfectly on its hilt. This isn't ominous at all.")) + RegisterSignal(src, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(eat_bullets)) /obj/item/melee/supermatter_sword/process() if(balanced || throwing || ismob(src.loc) || isnull(src.loc)) @@ -254,11 +255,16 @@ consume_everything() return TRUE -/obj/item/melee/supermatter_sword/bullet_act(obj/projectile/projectile) - visible_message(span_danger("[projectile] smacks into [src] and rapidly flashes to ash."),\ - span_hear("You hear a loud crack as you are washed with a wave of heat.")) - consume_everything(projectile) - return BULLET_ACT_HIT +/obj/item/melee/supermatter_sword/proc/eat_bullets(datum/source, obj/projectile/hitting_projectile) + SIGNAL_HANDLER + + visible_message( + span_danger("[hitting_projectile] smacks into [source] and rapidly flashes to ash."), + null, + span_hear("You hear a loud crack as you are washed with a wave of heat."), + ) + consume_everything(hitting_projectile) + return COMPONENT_BULLET_BLOCKED /obj/item/melee/supermatter_sword/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] touches [src]'s blade. It looks like [user.p_theyre()] tired of waiting for the radiation to kill [user.p_them()]!")) diff --git a/code/game/objects/items/mop.dm b/code/game/objects/items/mop.dm index 1c854e5091b6..77e1c489391c 100644 --- a/code/game/objects/items/mop.dm +++ b/code/game/objects/items/mop.dm @@ -25,6 +25,14 @@ /obj/structure/mop_bucket, )) +/obj/item/mop/apply_fantasy_bonuses(bonus) + . = ..() + mopspeed = modify_fantasy_variable("mopspeed", mopspeed, -bonus) + +/obj/item/mop/remove_fantasy_bonuses(bonus) + mopspeed = reset_fantasy_variable("mopspeed", mopspeed) + return ..() + /obj/item/mop/Initialize(mapload) . = ..() AddComponent(/datum/component/cleaner, mopspeed, pre_clean_callback=CALLBACK(src, PROC_REF(should_clean)), on_cleaned_callback=CALLBACK(src, PROC_REF(apply_reagents))) diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index 1336a043e7f5..e848a972b7bf 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -545,30 +545,30 @@ /obj/item/borg/upgrade/expand/action(mob/living/silicon/robot/robot, user = usr) . = ..() - if(.) + if(!. || HAS_TRAIT(robot, TRAIT_NO_TRANSFORM)) + return FALSE - if(robot.hasExpanded) - to_chat(usr, span_warning("This unit already has an expand module installed!")) - return FALSE + if(robot.hasExpanded) + to_chat(usr, span_warning("This unit already has an expand module installed!")) + return FALSE - robot.notransform = TRUE - var/prev_lockcharge = robot.lockcharge - robot.SetLockdown(TRUE) - robot.set_anchored(TRUE) - var/datum/effect_system/fluid_spread/smoke/smoke = new - smoke.set_up(1, holder = robot, location = robot.loc) - smoke.start() - sleep(0.2 SECONDS) - for(var/i in 1 to 4) - playsound(robot, pick('sound/items/drill_use.ogg', 'sound/items/jaws_cut.ogg', 'sound/items/jaws_pry.ogg', 'sound/items/welder.ogg', 'sound/items/ratchet.ogg'), 80, TRUE, -1) - sleep(1.2 SECONDS) - if(!prev_lockcharge) - robot.SetLockdown(FALSE) - robot.set_anchored(FALSE) - robot.notransform = FALSE - robot.resize = 2 - robot.hasExpanded = TRUE - robot.update_transform() + ADD_TRAIT(robot, TRAIT_NO_TRANSFORM, REF(src)) + var/prev_lockcharge = robot.lockcharge + robot.SetLockdown(TRUE) + robot.set_anchored(TRUE) + var/datum/effect_system/fluid_spread/smoke/smoke = new + smoke.set_up(1, holder = robot, location = robot.loc) + smoke.start() + sleep(0.2 SECONDS) + for(var/i in 1 to 4) + playsound(robot, pick('sound/items/drill_use.ogg', 'sound/items/jaws_cut.ogg', 'sound/items/jaws_pry.ogg', 'sound/items/welder.ogg', 'sound/items/ratchet.ogg'), 80, TRUE, -1) + sleep(1.2 SECONDS) + if(!prev_lockcharge) + robot.SetLockdown(FALSE) + robot.set_anchored(FALSE) + REMOVE_TRAIT(robot, TRAIT_NO_TRANSFORM, REF(src)) + robot.hasExpanded = TRUE + robot.update_transform(2) /obj/item/borg/upgrade/expand/deactivate(mob/living/silicon/robot/R, user = usr) . = ..() diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm index 39ffdd85b252..e6d7eb18af87 100644 --- a/code/game/objects/items/shooting_range.dm +++ b/code/game/objects/items/shooting_range.dm @@ -4,28 +4,74 @@ icon = 'icons/obj/objects.dmi' icon_state = "target_h" density = FALSE - var/hp = 1800 + max_integrity = 1800 + item_flags = CAN_BE_HIT /// Lazylist to keep track of bullet-hole overlays. var/list/bullethole_overlays -/obj/item/target/welder_act(mob/living/user, obj/item/I) - ..() - if(I.use_tool(src, user, 0, volume=40)) - for (var/bullethole in bullethole_overlays) - cut_overlay(bullethole) - bullethole_overlays = null - to_chat(user, span_notice("You slice off [src]'s uneven chunks of aluminium and scorch marks.")) +/obj/item/target/welder_act(mob/living/user, obj/item/tool) + if(tool.use_tool(src, user, 0 SECONDS, volume = 40)) + LAZYNULL(bullethole_overlays) + balloon_alert(user, "target repaired") + update_appearance(UPDATE_OVERLAYS) return TRUE +/obj/item/target/update_overlays() + . = ..() + . |= bullethole_overlays + +/obj/item/target/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE) + if(prob(25)) + return ..() // RNG change to just not leave a mark, like walls + if(length(overlays) > 35) + return ..() // Too many bullets, we're done here + + // Projectiles which do not deal damage will not leave dent / scorch mark graphics. + // However we snowflake some projectiles to leave them anyway, because they're appropriate. + var/static/list/always_leave_marks + if(isnull(always_leave_marks)) + always_leave_marks = typecacheof(list( + /obj/projectile/beam/practice, + )) + + var/is_invalid_damage = hitting_projectile.damage_type != BRUTE && hitting_projectile.damage_type != BURN + var/is_safe = !hitting_projectile.is_hostile_projectile() + var/is_generic_projectile = !is_type_in_typecache(hitting_projectile, always_leave_marks) + if(is_generic_projectile && (is_invalid_damage || is_safe)) + return ..() // Don't bother unless it's real shit + + var/p_x = hitting_projectile.p_x + pick(0, 0, 0, 0, 0, -1, 1) // really ugly way of coding "sometimes offset p_x!" + var/p_y = hitting_projectile.p_y + pick(0, 0, 0, 0, 0, -1, 1) + var/icon/our_icon = icon(icon, icon_state) + if(!our_icon.GetPixel(p_x, p_y) || hitting_projectile.original != src) + return BULLET_ACT_FORCE_PIERCE // We, "missed", I guess? + + . = ..() + if(. != BULLET_ACT_HIT) + return + + var/image/bullet_hole = image('icons/effects/effects.dmi', "dent", OBJ_LAYER + 0.5) + bullet_hole.pixel_x = p_x - 1 //offset correction + bullet_hole.pixel_y = p_y - 1 + if(hitting_projectile.damage_type != BRUTE) + bullet_hole.setDir(pick(GLOB.cardinals))// random scorch design + if(hitting_projectile.damage < 20 && is_generic_projectile) + bullet_hole.icon_state = "light_scorch" + else + bullet_hole.icon_state = "scorch" + + LAZYADD(bullethole_overlays, bullet_hole) + update_appearance(UPDATE_OVERLAYS) + /obj/item/target/syndicate icon_state = "target_s" desc = "A shooting target that looks like syndicate scum." - hp = 2600 + max_integrity = 2600 /obj/item/target/alien icon_state = "target_q" desc = "A shooting target that looks like a xenomorphic alien." - hp = 2350 + max_integrity = 2350 /obj/item/target/alien/anchored anchored = TRUE @@ -33,44 +79,8 @@ /obj/item/target/clown icon_state = "target_c" desc = "A shooting target that looks like a useless clown." - hp = 2000 - -#define DECALTYPE_SCORCH 1 -#define DECALTYPE_BULLET 2 + max_integrity = 2000 /obj/item/target/clown/bullet_act(obj/projectile/P) . = ..() - playsound(src.loc, 'sound/items/bikehorn.ogg', 50, TRUE) - -/obj/item/target/bullet_act(obj/projectile/P) - if(istype(P, /obj/projectile/bullet/reusable)) // If it's a foam dart, don't bother with any of this other shit - return P.on_hit(src, 0) - var/p_x = P.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset P.p_x!" - var/p_y = P.p_y + pick(0,0,0,0,0,-1,1) - var/decaltype = DECALTYPE_SCORCH - if(istype(P, /obj/projectile/bullet)) - decaltype = DECALTYPE_BULLET - var/icon/C = icon(icon,icon_state) - if(C.GetPixel(p_x, p_y) && P.original == src && overlays.len <= 35) // if the located pixel isn't blank (null) - hp -= P.damage - if(hp <= 0) - visible_message(span_danger("[src] breaks into tiny pieces and collapses!")) - qdel(src) - var/image/bullet_hole = image('icons/effects/effects.dmi', "scorch", OBJ_LAYER + 0.5) - bullet_hole.pixel_x = p_x - 1 //offset correction - bullet_hole.pixel_y = p_y - 1 - if(decaltype == DECALTYPE_SCORCH) - bullet_hole.setDir(pick(NORTH,SOUTH,EAST,WEST))// random scorch design - if(P.damage >= 20 || istype(P, /obj/projectile/beam/practice)) - bullet_hole.setDir(pick(NORTH,SOUTH,EAST,WEST)) - else - bullet_hole.icon_state = "light_scorch" - else - bullet_hole.icon_state = "dent" - LAZYADD(bullethole_overlays, bullet_hole) - add_overlay(bullet_hole) - return BULLET_ACT_HIT - return BULLET_ACT_FORCE_PIERCE - -#undef DECALTYPE_SCORCH -#undef DECALTYPE_BULLET + playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index 49a7b4d8c4ad..791088890dac 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -11,7 +11,7 @@ resistance_flags = FLAMMABLE max_integrity = 40 novariants = FALSE - item_flags = NOBLUDGEON + item_flags = NOBLUDGEON|SKIP_FANTASY_ON_SPAWN cost = 250 source = /datum/robot_energy_storage/medical merge_type = /obj/item/stack/medical @@ -36,6 +36,28 @@ . = ..() try_heal(patient, user) +/obj/item/stack/medical/apply_fantasy_bonuses(bonus) + . = ..() + if(heal_brute) + heal_brute = modify_fantasy_variable("heal_brute", heal_brute, bonus) + if(heal_burn) + heal_burn = modify_fantasy_variable("heal_burn", heal_burn, bonus) + if(stop_bleeding) + stop_bleeding = modify_fantasy_variable("stop_bleeding", stop_bleeding, bonus/10) + if(sanitization) + sanitization = modify_fantasy_variable("sanitization", sanitization, bonus/10) + if(flesh_regeneration) + flesh_regeneration = modify_fantasy_variable("flesh_regeneration", flesh_regeneration, bonus/10) + +/obj/item/stack/medical/remove_fantasy_bonuses(bonus) + heal_brute = reset_fantasy_variable("heal_brute", heal_brute) + heal_burn = reset_fantasy_variable("heal_burn", heal_burn) + stop_bleeding = reset_fantasy_variable("stop_bleeding", stop_bleeding) + sanitization = reset_fantasy_variable("sanitization", sanitization) + flesh_regeneration = reset_fantasy_variable("flesh_regeneration", flesh_regeneration) + return ..() + + /// In which we print the message that we're starting to heal someone, then we try healing them. Does the do_after whether or not it can actually succeed on a targeted mob /obj/item/stack/medical/proc/try_heal(mob/living/patient, mob/user, silent = FALSE) if(!patient.try_inject(user, injection_flags = INJECT_TRY_SHOW_ERROR_MESSAGE)) @@ -399,11 +421,11 @@ patient.emote("scream") for(var/i in patient.bodyparts) - var/obj/item/bodypart/bone = i - var/datum/wound/blunt/severe/oof_ouch = new - oof_ouch.apply_wound(bone) - var/datum/wound/blunt/critical/oof_OUCH = new - oof_OUCH.apply_wound(bone) + var/obj/item/bodypart/bone = i // fine to just, use these raw, its a meme anyway + var/datum/wound/blunt/bone/severe/oof_ouch = new + oof_ouch.apply_wound(bone, wound_source = "bone gel") + var/datum/wound/blunt/bone/critical/oof_OUCH = new + oof_OUCH.apply_wound(bone, wound_source = "bone gel") for(var/i in patient.bodyparts) var/obj/item/bodypart/bone = i diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 5d7a3715850f..252ff4092354 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -305,6 +305,7 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \ new/datum/stack_recipe("wooden barricade", /obj/structure/barricade/wooden, 5, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ new/datum/stack_recipe("wooden door", /obj/structure/mineral_door/wood, 10, time = 2 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ new/datum/stack_recipe("wooden stairs frame", /obj/structure/stairs_frame/wood, 10, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("wooden fence", /obj/structure/railing/wooden_fence, 2, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ new/datum/stack_recipe("coffin", /obj/structure/closet/crate/coffin, 5, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ new/datum/stack_recipe("book case", /obj/structure/bookcase, 4, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ new/datum/stack_recipe("drying rack", /obj/machinery/smartfridge/drying_rack, 10, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_TOOLS), \ diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 4b24b70447bc..11f556479ae2 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -20,6 +20,7 @@ gender = PLURAL material_modifier = 0.05 //5%, so that a 50 sheet stack has the effect of 5k materials instead of 100k. max_integrity = 100 + item_flags = SKIP_FANTASY_ON_SPAWN /// A list to all recipies this stack item can create. var/list/datum/stack_recipe/recipes /// What's the name of just 1 of this stack. You have a stack of leather, but one piece of leather @@ -575,6 +576,8 @@ var/obj/machinery/machine = loc if(!(src in machine.component_parts) || !(check in machine.component_parts)) return FALSE + if(SEND_SIGNAL(src, COMSIG_STACK_CAN_MERGE, check, inhand) & CANCEL_STACK_MERGE) + return FALSE return TRUE /** diff --git a/code/game/objects/items/storage/book.dm b/code/game/objects/items/storage/book.dm index 7862cf08bd81..03cbd4c6a6e4 100644 --- a/code/game/objects/items/storage/book.dm +++ b/code/game/objects/items/storage/book.dm @@ -235,7 +235,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "burning", playsound(src,'sound/effects/pray_chaplain.ogg',60,TRUE) for(var/obj/item/soulstone/SS in sword.contents) SS.required_role = null - for(var/mob/living/simple_animal/shade/EX in SS) + for(var/mob/living/basic/shade/EX in SS) var/datum/antagonist/cult/cultist = EX.mind.has_antag_datum(/datum/antagonist/cult) if (cultist) cultist.silent = TRUE diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm index 217d4f32571e..cba6a7c2818a 100644 --- a/code/game/objects/items/storage/secure.dm +++ b/code/game/objects/items/storage/secure.dm @@ -40,7 +40,7 @@ . = ..() icon_state = "[initial(icon_state)][atom_storage?.locked ? "_locked" : null]" -/obj/item/storage/secure/tool_act(mob/living/user, obj/item/tool) +/obj/item/storage/secure/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking) if(can_hack_open && atom_storage.locked) return ..() else diff --git a/code/game/objects/items/storage/storage.dm b/code/game/objects/items/storage/storage.dm index 98b8c243a40b..4ecb596ec50b 100644 --- a/code/game/objects/items/storage/storage.dm +++ b/code/game/objects/items/storage/storage.dm @@ -10,6 +10,28 @@ /// What storage type to use for this item var/datum/storage/storage_type = /datum/storage +/obj/item/storage/apply_fantasy_bonuses(bonus) + . = ..() + atom_storage.max_slots = modify_fantasy_variable("max_slots", atom_storage.max_slots, round(bonus/2)) + atom_storage.max_total_storage = modify_fantasy_variable("max_total_storage", atom_storage.max_total_storage, round(bonus/2)) + LAZYSET(fantasy_modifications, "max_specific_storage", atom_storage.max_specific_storage) + if(bonus >= 15) + atom_storage.max_specific_storage = max(WEIGHT_CLASS_HUGE, atom_storage.max_specific_storage) + else if(bonus >= 10) + atom_storage.max_specific_storage = max(WEIGHT_CLASS_BULKY, atom_storage.max_specific_storage) + else if(bonus <= -10) + atom_storage.max_specific_storage = WEIGHT_CLASS_SMALL + else if(bonus <= -15) + atom_storage.max_specific_storage = WEIGHT_CLASS_TINY + +/obj/item/storage/remove_fantasy_bonuses(bonus) + atom_storage.max_slots = reset_fantasy_variable("max_slots", atom_storage.max_slots) + atom_storage.max_total_storage = reset_fantasy_variable("max_total_storage", atom_storage.max_total_storage) + var/previous_max_storage = LAZYACCESS(fantasy_modifications, "max_specific_storage") + if(previous_max_storage) + atom_storage.max_specific_storage = previous_max_storage + return ..() + /obj/item/storage/Initialize(mapload) . = ..() diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index 4cb2edb024f6..894d4c8f234c 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -642,6 +642,17 @@ new /obj/item/storage/belt/grenade(src) new /obj/item/storage/belt/military/snack(src) +/obj/item/storage/box/syndie_kit/poster_box + name = "syndicate poster pack" + desc = "Contains a variety of demotivational posters to ensure minimum productivity for the crew of any Nanotrasen station." + + /// Number of posters this box contains when spawning. + var/poster_count = 3 + +/obj/item/storage/box/syndie_kit/poster_box/PopulateContents() + for(var/i in 1 to poster_count) + new /obj/item/poster/traitor(src) + #undef KIT_RECON #undef KIT_BLOODY_SPAI #undef KIT_STEALTHY diff --git a/code/modules/antagonists/traitor/objectives/demoralise_graffiti.dm b/code/game/objects/items/syndie_spraycan.dm similarity index 68% rename from code/modules/antagonists/traitor/objectives/demoralise_graffiti.dm rename to code/game/objects/items/syndie_spraycan.dm index 3bea2dc77bc3..5428f046d96e 100644 --- a/code/modules/antagonists/traitor/objectives/demoralise_graffiti.dm +++ b/code/game/objects/items/syndie_spraycan.dm @@ -1,92 +1,3 @@ -/datum/traitor_objective/demoralise/graffiti - name = "Sow doubt among the crew %VIEWS% times using Syndicate graffiti." - description = "Use the button below to materialize a seditious spray can, \ - and use it to draw a 3x3 tag in a place where people will come across it. \ - Special syndicate sealing agent ensures that it can't be removed for \ - five minutes following application, and it's slippery too! \ - People seeing or slipping on your graffiti grants progress towards success." - - progression_minimum = 0 MINUTES - progression_maximum = 30 MINUTES - progression_reward = list(4 MINUTES, 8 MINUTES) - telecrystal_reward = list(0, 1) - - duplicate_type = /datum/traitor_objective/demoralise/graffiti - /// Have we given out a spray can yet? - var/obtained_spray = FALSE - /// Graffiti 'rune' which we will be drawing - var/obj/effect/decal/cleanable/traitor_rune/rune - -/datum/traitor_objective/demoralise/graffiti/generate_ui_buttons(mob/user) - var/list/buttons = list() - if (!obtained_spray) - buttons += add_ui_button("", "Pressing this will materialize a syndicate spraycan in your hand.", "wifi", "summon_gear") - else - buttons += add_ui_button("[demoralised_crew_events] / [demoralised_crew_required] propagandised", "This many crew have been exposed to propaganda, out of a required [demoralised_crew_required].", "wifi", "none") - return buttons - -/datum/traitor_objective/demoralise/graffiti/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if ("summon_gear") - if (obtained_spray) - return - - obtained_spray = TRUE - var/obj/item/traitor_spraycan/spray = new(user.drop_location()) - user.put_in_hands(spray) - spray.balloon_alert(user, "the spraycan materializes in your hand") - - RegisterSignal(spray, COMSIG_PARENT_QDELETING, PROC_REF(on_spray_destroyed)) - RegisterSignal(spray, COMSIG_TRAITOR_GRAFFITI_DRAWN, PROC_REF(on_rune_complete)) - -/** - * Called when the spray can is deleted. - * If it's already been expended we don't care, if it hasn't you just made your objective impossible.area - * - * Arguments - * * spray - the spraycan which was just deleted - */ -/datum/traitor_objective/demoralise/graffiti/proc/on_spray_destroyed() - SIGNAL_HANDLER - // You fucked up pretty bad if you let this happen - if (!rune) - fail_objective(penalty_cost = telecrystal_penalty) - -/** - * Called when you managed to draw a traitor rune. - * Sets up tracking for objective progress, and unregisters signals for the spraycan because we don't care about it any more. - * - * Arguments - * * drawn_rune - graffiti 'rune' which was just drawn. - */ -/datum/traitor_objective/demoralise/graffiti/proc/on_rune_complete(atom/spray, obj/effect/decal/cleanable/traitor_rune/drawn_rune) - SIGNAL_HANDLER - rune = drawn_rune - UnregisterSignal(spray, COMSIG_PARENT_QDELETING) - UnregisterSignal(spray, COMSIG_TRAITOR_GRAFFITI_DRAWN) - RegisterSignal(drawn_rune, COMSIG_PARENT_QDELETING, PROC_REF(on_rune_destroyed)) - RegisterSignal(drawn_rune, COMSIG_DEMORALISING_EVENT, PROC_REF(on_mood_event)) - RegisterSignal(drawn_rune, COMSIG_TRAITOR_GRAFFITI_SLIPPED, PROC_REF(on_mood_event)) - -/** - * Called when your traitor rune is destroyed. If you haven't suceeded by now, you fail.area - * - * Arguments - * * rune - the rune which just got destroyed. - */ -/datum/traitor_objective/demoralise/graffiti/proc/on_rune_destroyed(obj/effect/decal/cleanable/traitor_rune/rune) - SIGNAL_HANDLER - fail_objective(penalty_cost = telecrystal_penalty) - -/datum/traitor_objective/demoralise/graffiti/ungenerate_objective() - if (rune) - UnregisterSignal(rune, COMSIG_PARENT_QDELETING) - UnregisterSignal(rune, COMSIG_DEMORALISING_EVENT) - UnregisterSignal(rune, COMSIG_TRAITOR_GRAFFITI_SLIPPED) - rune = null - return ..() - // Extending the existing spraycan item was more trouble than it was worth, I don't want or need this to be able to draw arbitrary shapes. /obj/item/traitor_spraycan name = "seditious spraycan" diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm index dd67bbcd8ce9..87f86e836b7b 100644 --- a/code/game/objects/items/teleportation.dm +++ b/code/game/objects/items/teleportation.dm @@ -460,7 +460,8 @@ new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(mobloc) new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(emergency_destination) balloon_alert(user, "emergency teleport triggered!") - make_bloods(mobloc, emergency_destination, user) + if (!HAS_TRAIT(user, TRAIT_NOBLOOD)) + make_bloods(mobloc, emergency_destination, user) playsound(mobloc, SFX_SPARKS, 50, 1, SHORT_RANGE_SOUND_EXTRARANGE) playsound(emergency_destination, 'sound/effects/phasein.ogg', 25, 1, SHORT_RANGE_SOUND_EXTRARANGE) playsound(emergency_destination, SFX_SPARKS, 50, 1, SHORT_RANGE_SOUND_EXTRARANGE) diff --git a/code/game/objects/items/tongs.dm b/code/game/objects/items/tongs.dm new file mode 100644 index 000000000000..fdfba7697d61 --- /dev/null +++ b/code/game/objects/items/tongs.dm @@ -0,0 +1,101 @@ +/// Tongs, let you pick up and feed people food from further away. +/obj/item/kitchen/tongs + name = "tongs" + icon = 'icons/obj/service/kitchen.dmi' + desc = "So you never have to touch anything with your dirty, unwashed hands." + reach = 2 + icon_state = "tongs" + base_icon_state = "tongs" + inhand_icon_state = "fork" // close enough + attack_verb_continuous = list("pinches", "tongs", "nips") + attack_verb_simple = list("pinch", "tong", "nip") + /// What are we holding in our tongs? + var/obj/item/tonged + /// Sound to play when we click our tongs together + var/clack_sound = 'sound/items/handling/component_drop.ogg' + /// Time to wait between clacking sounds + var/clack_delay = 2 SECONDS + /// Have we clacked recently? + COOLDOWN_DECLARE(clack_cooldown) + +/obj/item/kitchen/tongs/Destroy(force) + QDEL_NULL(tonged) + return ..() + +/obj/item/kitchen/tongs/examine(mob/user) + . = ..() + if (!isnull(tonged)) + . += span_notice("It is holding [tonged].") + +/obj/item/kitchen/tongs/dropped(mob/user, silent) + . = ..() + drop_tonged() + +/obj/item/kitchen/tongs/attack_self(mob/user, modifiers) + . = ..() + if(.) + return TRUE + if (!isnull(tonged)) + drop_tonged() + return TRUE + if (!COOLDOWN_FINISHED(src, clack_cooldown)) + return TRUE + user.visible_message(span_notice("[user] clacks [user.p_their()] [src] together like a crab. Click clack!")) + click_clack() + return TRUE + +/// Release the food we are holding +/obj/item/kitchen/tongs/proc/drop_tonged() + if (isnull(tonged)) + return + visible_message(span_notice("[tonged] falls to the ground!")) + var/turf/location = drop_location() + tonged.forceMove(location) + tonged.do_drop_animation(location) + +/// Play a clacking sound and appear closed, then open again +/obj/item/kitchen/tongs/proc/click_clack() + COOLDOWN_START(src, clack_cooldown, clack_delay) + playsound(src, clack_sound, vol = 100, vary = FALSE) + icon_state = "[base_icon_state]_closed" + var/delay = min(0.5 SECONDS, clack_delay / 2) // Just in case someone's been fucking with the cooldown + addtimer(CALLBACK(src, PROC_REF(clack)), delay, TIMER_DELETE_ME) + +/// Plays a clacking sound and appear open +/obj/item/kitchen/tongs/proc/clack() + playsound(src, clack_sound, vol = 100, vary = FALSE) + icon_state = base_icon_state + +/obj/item/kitchen/tongs/Exited(atom/movable/leaving, direction) + . = ..() + if (leaving != tonged) + return + tonged = null + update_appearance(UPDATE_ICON) + +/obj/item/kitchen/tongs/pre_attack(obj/item/attacked, mob/living/user, params) + if (!isnull(tonged)) + attacked.attackby(tonged, user) + return TRUE + if (isliving(attacked)) + if (COOLDOWN_FINISHED(src, clack_cooldown)) + click_clack() + return ..() + if (!IsEdible(attacked) || attacked.w_class > WEIGHT_CLASS_NORMAL || !isnull(tonged)) + return ..() + tonged = attacked + attacked.do_pickup_animation(src) + attacked.forceMove(src) + update_appearance(UPDATE_ICON) + +/obj/item/kitchen/tongs/update_overlays() + . = ..() + if (isnull(tonged)) + return + var/mutable_appearance/held_food = new /mutable_appearance(tonged.appearance) + held_food.layer = layer + held_food.plane = plane + held_food.transform = held_food.transform.Scale(0.7, 0.7) + held_food.pixel_x = 6 + held_food.pixel_y = 6 + . += held_food diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm index 7ec6e5f5be60..bd1124dcf1f4 100644 --- a/code/game/objects/items/trash.dm +++ b/code/game/objects/items/trash.dm @@ -6,7 +6,7 @@ desc = "This is rubbish." w_class = WEIGHT_CLASS_TINY resistance_flags = FLAMMABLE - item_flags = NOBLUDGEON + item_flags = NOBLUDGEON|SKIP_FANTASY_ON_SPAWN /obj/item/trash/Initialize(mapload) var/turf/T = get_turf(src) diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index 2b85ddc62e30..0d29aa1f203d 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -21,14 +21,31 @@ if(EXPLODE_LIGHT) take_damage(rand(10, 90), BRUTE, BOMB, 0) -/obj/bullet_act(obj/projectile/P) + return TRUE + +/obj/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE) . = ..() - playsound(src, P.hitsound, 50, TRUE) - var/damage + if(. != BULLET_ACT_HIT) + return . + + playsound(src, hitting_projectile.hitsound, 50, TRUE) + var/damage_sustained = 0 if(!QDELETED(src)) //Bullet on_hit effect might have already destroyed this object - damage = take_damage(P.damage, P.damage_type, P.armor_flag, 0, turn(P.dir, 180), P.armour_penetration) - if(P.suppressed != SUPPRESSED_VERY) - visible_message(span_danger("[src] is hit by \a [P][damage ? "" : ", without leaving a mark"]!"), null, null, COMBAT_MESSAGE_RANGE) + damage_sustained = take_damage( + hitting_projectile.damage * hitting_projectile.demolition_mod, + hitting_projectile.damage_type, + hitting_projectile.armor_flag, + FALSE, + REVERSE_DIR(hitting_projectile.dir), + hitting_projectile.armour_penetration, + ) + if(hitting_projectile.suppressed != SUPPRESSED_VERY) + visible_message( + span_danger("[src] is hit by \a [hitting_projectile][damage_sustained ? "" : ", without leaving a mark"]!"), + vision_distance = COMBAT_MESSAGE_RANGE, + ) + + return damage_sustained > 0 ? BULLET_ACT_HIT : BULLET_ACT_BLOCK /obj/attack_hulk(mob/living/carbon/human/user) ..() diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm index 5d7e0d30c8f9..6d22d8480d3c 100644 --- a/code/game/objects/structures/beds_chairs/chair.dm +++ b/code/game/objects/structures/beds_chairs/chair.dm @@ -327,6 +327,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/chair/stool/bar, 0) hitsound = 'sound/items/trayhit1.ogg' hit_reaction_chance = 50 custom_materials = list(/datum/material/iron = 2000) + item_flags = SKIP_FANTASY_ON_SPAWN var/break_chance = 5 //Likely hood of smashing the chair. var/obj/structure/chair/origin_type = /obj/structure/chair diff --git a/code/game/objects/structures/crates_lockers/closets/job_closets.dm b/code/game/objects/structures/crates_lockers/closets/job_closets.dm index dd35e0f1c08f..8d91a2678b9e 100644 --- a/code/game/objects/structures/crates_lockers/closets/job_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/job_closets.dm @@ -92,6 +92,9 @@ new /obj/item/clothing/suit/toggle/lawyer/black(src) new /obj/item/clothing/shoes/laceup(src) new /obj/item/clothing/shoes/laceup(src) + +/obj/structure/closet/lawcloset/populate_contents_immediate() + . = ..() new /obj/item/clothing/accessory/lawyers_badge(src) new /obj/item/clothing/accessory/lawyers_badge(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm index c708480100a6..97a6664adb2e 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm @@ -13,7 +13,7 @@ new /obj/item/holosign_creator/atmos(src) new /obj/item/assembly/flash/handheld(src) new /obj/item/door_remote/chief_engineer(src) - new /obj/item/pipe_dispenser(src) + new /obj/item/circuitboard/machine/techfab/department/engineering(src) new /obj/item/extinguisher/advanced(src) new /obj/item/storage/photo_album/ce(src) @@ -25,6 +25,7 @@ // Traitor steal objective new /obj/item/areaeditor/blueprints(src) new /obj/item/gun/ballistic/SRN_rocketlauncher(src) + new /obj/item/pipe_dispenser(src) /obj/structure/closet/secure_closet/engineering_electrical name = "electrical supplies locker" @@ -35,13 +36,18 @@ /obj/structure/closet/secure_closet/engineering_electrical/PopulateContents() ..() var/static/items_inside = list( - /obj/item/clothing/gloves/color/yellow = 2, /obj/item/inducer = 2, /obj/item/storage/toolbox/electrical = 3, /obj/item/electronics/apc = 3, /obj/item/multitool = 3) generate_items_inside(items_inside,src) +/obj/structure/closet/secure_closet/engineering_electrical/populate_contents_immediate() + . = ..() + + new /obj/item/clothing/gloves/color/yellow(src) + new /obj/item/clothing/gloves/color/yellow(src) + /obj/structure/closet/secure_closet/engineering_welding name = "welding supplies locker" req_access = list(ACCESS_ENGINE_EQUIP) @@ -80,7 +86,7 @@ /obj/structure/closet/secure_closet/atmospherics/PopulateContents() ..() new /obj/item/radio/headset/headset_eng(src) - new /obj/item/pipe_dispenser(src) + new /obj/item/storage/toolbox/mechanical(src) new /obj/item/tank/internals/emergency_oxygen/engi(src) new /obj/item/holosign_creator/atmos(src) @@ -91,3 +97,8 @@ new /obj/item/clothing/head/utility/hardhat/welding/atmos(src) new /obj/item/clothing/glasses/meson/engine/tray(src) new /obj/item/extinguisher/advanced(src) + +/obj/structure/closet/secure_closet/atmospherics/populate_contents_immediate() + . = ..() + + new /obj/item/pipe_dispenser(src) diff --git a/code/game/objects/structures/crates_lockers/crates/secure.dm b/code/game/objects/structures/crates_lockers/crates/secure.dm index 4698c8469d24..25989b2bdbc9 100644 --- a/code/game/objects/structures/crates_lockers/crates/secure.dm +++ b/code/game/objects/structures/crates_lockers/crates/secure.dm @@ -21,7 +21,7 @@ . = ..() ADD_TRAIT(src, TRAIT_NO_MISSING_ITEM_ERROR, TRAIT_GENERIC) -/obj/structure/closet/crate/secure/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) +/obj/structure/closet/crate/secure/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0) if(prob(tamperproof) && damage_amount >= DAMAGE_PRECISION) boom() else diff --git a/code/game/objects/structures/crates_lockers/crates/syndicrate.dm b/code/game/objects/structures/crates_lockers/crates/syndicrate.dm index b18321338c49..9d4d5e6c8ced 100644 --- a/code/game/objects/structures/crates_lockers/crates/syndicrate.dm +++ b/code/game/objects/structures/crates_lockers/crates/syndicrate.dm @@ -18,7 +18,7 @@ laser = 50 energy = 100 -/obj/structure/closet/crate/syndicrate/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) +/obj/structure/closet/crate/syndicrate/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0) if(created_items) return ..() if(damage_amount < DAMAGE_PRECISION) diff --git a/code/game/objects/structures/false_walls.dm b/code/game/objects/structures/false_walls.dm index 729abad34f41..54a9527899fa 100644 --- a/code/game/objects/structures/false_walls.dm +++ b/code/game/objects/structures/false_walls.dm @@ -83,7 +83,7 @@ qdel(src) return T -/obj/structure/falsewall/tool_act(mob/living/user, obj/item/tool) +/obj/structure/falsewall/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking) if(!opening) return ..() to_chat(user, span_warning("You must wait until the door has stopped moving!")) diff --git a/code/game/objects/structures/holosign.dm b/code/game/objects/structures/holosign.dm index 2ba97428cea9..51abaf50e13b 100644 --- a/code/game/objects/structures/holosign.dm +++ b/code/game/objects/structures/holosign.dm @@ -136,14 +136,13 @@ density = TRUE max_integrity = 10 allow_walk = FALSE + armor_type = /datum/armor/structure_holosign/cyborg_barrier // Gets a special armor subtype which is extra good at defense. -/obj/structure/holosign/barrier/cyborg/bullet_act(obj/projectile/P) - take_damage((P.damage / 5) , BRUTE, MELEE, 1) //Doesn't really matter what damage flag it is. - if(istype(P, /obj/projectile/energy/electrode)) - take_damage(10, BRUTE, MELEE, 1) //Tasers aren't harmful. - if(istype(P, /obj/projectile/beam/disabler)) - take_damage(5, BRUTE, MELEE, 1) //Disablers aren't harmful. - return BULLET_ACT_HIT +/datum/armor/structure_holosign/cyborg_barrier + bullet = 80 + laser = 80 + energy = 80 + melee = 20 /obj/structure/holosign/barrier/medical name = "\improper PENLITE holobarrier" @@ -196,12 +195,9 @@ name = "Charged Energy Field" desc = "A powerful energy field that blocks movement. Energy arcs off it." max_integrity = 20 + armor_type = /datum/armor/structure_holosign //Yeah no this doesn't get projectile resistance. var/shockcd = 0 -/obj/structure/holosign/barrier/cyborg/hacked/bullet_act(obj/projectile/P) - take_damage(P.damage, BRUTE, MELEE, 1) //Yeah no this doesn't get projectile resistance. - return BULLET_ACT_HIT - /obj/structure/holosign/barrier/cyborg/hacked/proc/cooldown() shockcd = FALSE diff --git a/code/game/objects/structures/icemoon/cave_entrance.dm b/code/game/objects/structures/icemoon/cave_entrance.dm index c95eeedf1260..40c5256915bd 100644 --- a/code/game/objects/structures/icemoon/cave_entrance.dm +++ b/code/game/objects/structures/icemoon/cave_entrance.dm @@ -17,7 +17,7 @@ GLOBAL_LIST_INIT(ore_probability, list( faction = list(FACTION_MINING) max_mobs = 3 max_integrity = 250 - mob_types = list(/mob/living/simple_animal/hostile/asteroid/wolf) + mob_types = list(/mob/living/basic/mining/wolf) move_resist = INFINITY anchored = TRUE @@ -76,7 +76,7 @@ GLOBAL_LIST_INIT(ore_probability, list( name = "demonic portal" desc = "A portal that goes to another world, normal creatures couldn't survive there." icon_state = "nether" - mob_types = list(/mob/living/simple_animal/hostile/asteroid/ice_demon) + mob_types = list(/mob/living/basic/mining/ice_demon) light_outer_range = 1 light_color = COLOR_SOFT_RED @@ -102,7 +102,7 @@ GLOBAL_LIST_INIT(ore_probability, list( mob_types = list(/mob/living/basic/mining/ice_whelp) /obj/structure/spawner/ice_moon/demonic_portal/snowlegion - mob_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow) + mob_types = list(/mob/living/basic/mining/legion/snow/spawner_made) /obj/effect/collapsing_demonic_portal name = "collapsing demonic portal" @@ -164,7 +164,7 @@ GLOBAL_LIST_INIT(ore_probability, list( if(13) new /obj/item/gun/magic/hook (loc) if(14) - new /obj/item/guardiancreator/miner(loc) + new /obj/item/guardian_creator/miner(loc) if(15) new /obj/item/ship_in_a_bottle(loc) if(16) @@ -186,7 +186,7 @@ GLOBAL_LIST_INIT(ore_probability, list( if(24) new /obj/structure/elite_tumor(loc) if(25) - new /mob/living/simple_animal/hostile/retaliate/clown/clownhulk(loc) + new /mob/living/basic/clown/clownhulk(loc) if(26) new /obj/item/book/granter/action/spell/sacredflame(loc) if(27) diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm index 82fba11d0e2f..1d4ec997aae0 100644 --- a/code/game/objects/structures/lattice.dm +++ b/code/game/objects/structures/lattice.dm @@ -94,6 +94,10 @@ canSmoothWith = SMOOTH_GROUP_CATWALK obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN | BLOCK_Z_IN_UP +/obj/structure/lattice/catwalk/Initialize(mapload) + . = ..() + AddElement(/datum/element/footstep_override, footstep = FOOTSTEP_CATWALK) + /obj/structure/lattice/catwalk/deconstruction_hints(mob/user) return span_notice("The supporting rods look like they could be cut.") diff --git a/code/game/objects/structures/lavaland/necropolis_tendril.dm b/code/game/objects/structures/lavaland/necropolis_tendril.dm index 8c3a1779b0b0..3a7aab6d977c 100644 --- a/code/game/objects/structures/lavaland/necropolis_tendril.dm +++ b/code/game/objects/structures/lavaland/necropolis_tendril.dm @@ -24,7 +24,7 @@ mob_types = list(/mob/living/basic/mining/goliath) /obj/structure/spawner/lavaland/legion - mob_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord/legion/tendril) + mob_types = list(/mob/living/basic/mining/legion/spawner_made) /obj/structure/spawner/lavaland/icewatcher mob_types = list(/mob/living/basic/mining/watcher/icewing) diff --git a/code/game/objects/structures/ore_containers.dm b/code/game/objects/structures/ore_containers.dm new file mode 100644 index 000000000000..af96c3e4a014 --- /dev/null +++ b/code/game/objects/structures/ore_containers.dm @@ -0,0 +1,62 @@ +///structure to contain ores +/obj/structure/ore_container + +/obj/structure/ore_container/attackby(obj/item/ore, mob/living/carbon/human/user, list/modifiers) + if(istype(ore, /obj/item/stack/ore) && !(user.istate & ISTATE_HARM)) + ore.forceMove(src) + return + return ..() + +/obj/structure/ore_container/Entered(atom/movable/mover) + . = ..() + update_appearance(UPDATE_OVERLAYS) + +/obj/structure/ore_container/Exited(atom/movable/mover) + . = ..() + update_appearance(UPDATE_OVERLAYS) + +/obj/structure/ore_container/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "OreContainer") + ui.open() + +/obj/structure/ore_container/ui_data(mob/user) + var/list/data = list() + data["ores"] = list() + for(var/obj/item/stack/ore/ore_item in contents) + data["ores"] += list(list( + "id" = REF(ore_item), + "name" = ore_item.name, + "amount" = ore_item.amount, + )) + return data + +/obj/structure/ore_container/ui_static_data(mob/user) + var/list/data = list() + data["ore_images"] = list() + for(var/obj/item/stack/ore_item as anything in subtypesof(/obj/item/stack/ore)) + data["ore_images"] += list(list( + "name" = initial(ore_item.name), + "icon" = icon2base64(getFlatIcon(image(icon = initial(ore_item.icon), icon_state = initial(ore_item.icon_state)), no_anim=TRUE)) + )) + return data + +/obj/structure/ore_container/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + + if(. || !isliving(ui.user)) + return TRUE + + var/mob/living/customer = ui.user + var/obj/item/stack_to_move + switch(action) + if("withdraw") + if(isnull(params["reference"])) + return TRUE + stack_to_move = locate(params["reference"]) in contents + if(isnull(stack_to_move)) + return TRUE + stack_to_move.forceMove(get_turf(customer)) + return TRUE diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm index 0da5236b321b..c9209787e77c 100644 --- a/code/game/objects/structures/railings.dm +++ b/code/game/objects/structures/railings.dm @@ -15,6 +15,8 @@ var/climbable = TRUE ///Initial direction of the railing. var/ini_dir + ///item released when deconstructed + var/item_deconstruct = /obj/item/stack/rods /datum/armor/structure_railing melee = 50 @@ -70,9 +72,11 @@ return TRUE /obj/structure/railing/deconstruct(disassembled) - if(!(flags_1 & NODECONSTRUCT_1)) - var/obj/item/stack/rods/rod = new /obj/item/stack/rods(drop_location(), 6) - transfer_fingerprints_to(rod) + if((flags_1 & NODECONSTRUCT_1)) + return ..() + var/rods_to_make = istype(src,/obj/structure/railing/corner) ? 1 : 2 + var/obj/rod = new item_deconstruct(drop_location(), rods_to_make) + transfer_fingerprints_to(rod) return ..() ///Implements behaviour that makes it possible to unanchor the railing. @@ -124,3 +128,35 @@ /obj/structure/railing/proc/check_anchored(checked_anchored) if(anchored == checked_anchored) return TRUE + + +/obj/structure/railing/wooden_fence + name = "wooden fence" + desc = "wooden fence meant to keep animals in." + icon = 'icons/obj/structures.dmi' + icon_state = "wooden_railing" + item_deconstruct = /obj/item/stack/sheet/mineral/wood + plane = GAME_PLANE_FOV_HIDDEN + layer = ABOVE_MOB_LAYER + +/obj/structure/railing/wooden_fence/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_ATOM_DIR_CHANGE, PROC_REF(on_change_layer)) + adjust_dir_layer(dir) + +/obj/structure/railing/wooden_fence/proc/on_change_layer(datum/source, old_dir, new_dir) + SIGNAL_HANDLER + adjust_dir_layer(new_dir) + +/obj/structure/railing/wooden_fence/proc/adjust_dir_layer(direction) + var/new_layer = (direction & NORTH) ? MOB_LAYER : ABOVE_MOB_LAYER + layer = new_layer + + +/obj/structure/railing/corner/end/wooden_fence + icon = 'icons/obj/structures.dmi' + icon_state = "wooden_railing_corner" + +/obj/structure/railing/corner/end/flip/wooden_fence + icon = 'icons/obj/structures.dmi' + icon_state = "wooden_railing_corner_flipped" diff --git a/code/game/objects/structures/spawner.dm b/code/game/objects/structures/spawner.dm index 4e51d0900c5d..cc020f275ae0 100644 --- a/code/game/objects/structures/spawner.dm +++ b/code/game/objects/structures/spawner.dm @@ -8,11 +8,12 @@ anchored = TRUE density = TRUE + faction = list(FACTION_HOSTILE) + var/max_mobs = 5 var/spawn_time = 30 SECONDS var/mob_types = list(/mob/living/basic/carp) var/spawn_text = "emerges from" - var/faction = list(FACTION_HOSTILE) var/spawner_type = /datum/component/spawner /obj/structure/spawner/Initialize(mapload) @@ -30,7 +31,7 @@ icon = 'icons/obj/device.dmi' icon_state = "syndbeacon" spawn_text = "warps in from" - mob_types = list(/mob/living/basic/syndicate/ranged) + mob_types = list(/mob/living/basic/trooper/syndicate/ranged) faction = list(ROLE_SYNDICATE) /obj/structure/spawner/skeleton @@ -41,7 +42,7 @@ max_integrity = 150 max_mobs = 15 spawn_time = 15 SECONDS - mob_types = list(/mob/living/simple_animal/hostile/skeleton) + mob_types = list(/mob/living/basic/skeleton) spawn_text = "climbs out of" faction = list(FACTION_SKELETON) @@ -54,16 +55,16 @@ max_mobs = 15 spawn_time = 15 SECONDS mob_types = list( - /mob/living/simple_animal/hostile/retaliate/clown, - /mob/living/simple_animal/hostile/retaliate/clown/banana, - /mob/living/simple_animal/hostile/retaliate/clown/clownhulk, - /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown, - /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus, - /mob/living/simple_animal/hostile/retaliate/clown/fleshclown, - /mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton, - /mob/living/simple_animal/hostile/retaliate/clown/honkling, - /mob/living/simple_animal/hostile/retaliate/clown/longface, - /mob/living/simple_animal/hostile/retaliate/clown/lube, + /mob/living/basic/clown, + /mob/living/basic/clown/banana, + /mob/living/basic/clown/clownhulk, + /mob/living/basic/clown/clownhulk/chlown, + /mob/living/basic/clown/clownhulk/honkmunculus, + /mob/living/basic/clown/fleshclown, + /mob/living/basic/clown/mutant/glutton, + /mob/living/basic/clown/honkling, + /mob/living/basic/clown/longface, + /mob/living/basic/clown/lube, ) spawn_text = "climbs out of" faction = list(FACTION_CLOWN) @@ -80,8 +81,8 @@ /mob/living/basic/mining/basilisk, /mob/living/basic/mining/goldgrub, /mob/living/basic/mining/goliath/ancient, + /mob/living/basic/mining/legion, /mob/living/basic/wumborian_fugu, - /mob/living/simple_animal/hostile/asteroid/hivelord, ) faction = list(FACTION_MINING) @@ -98,7 +99,7 @@ /obj/structure/spawner/mining/hivelord name = "hivelord den" desc = "A den housing a nest of hivelords." - mob_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord) + mob_types = list(/mob/living/basic/mining/hivelord) /obj/structure/spawner/mining/basilisk name = "basilisk den" diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 84acecaa7807..4b420fc824fe 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -42,6 +42,8 @@ . = ..() if(_buildstack) buildstack = _buildstack + AddElement(/datum/element/footstep_override, priority = STEP_SOUND_TABLE_PRIORITY) + AddElement(/datum/element/climbable) var/static/list/loc_connections = list( @@ -376,7 +378,7 @@ for(var/atom/movable/attached_movable as anything in attached_items) if(!attached_movable.Move(loc)) RemoveItemFromTable(attached_movable, attached_movable.loc) - + /obj/structure/table/rolling/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) . = ..() if(has_gravity()) diff --git a/code/game/objects/structures/traps.dm b/code/game/objects/structures/traps.dm index 67b452ed3a8f..9078bf10085f 100644 --- a/code/game/objects/structures/traps.dm +++ b/code/game/objects/structures/traps.dm @@ -8,7 +8,7 @@ alpha = 30 //initially quite hidden when not "recharging" var/flare_message = "the trap flares brightly!" var/last_trigger = 0 - var/time_between_triggers = 600 //takes a minute to recharge + var/time_between_triggers = 1 MINUTES var/charges = INFINITY var/antimagic_flags = MAGIC_RESISTANCE @@ -61,50 +61,50 @@ last_trigger = world.time charges-- if(charges <= 0) - animate(src, alpha = 0, time = 10) - QDEL_IN(src, 10) + animate(src, alpha = 0, time = 1 SECONDS) + QDEL_IN(src, 1 SECONDS) else animate(src, alpha = initial(alpha), time = time_between_triggers) -/obj/structure/trap/proc/on_entered(datum/source, atom/movable/AM) +/obj/structure/trap/proc/on_entered(datum/source, atom/movable/victim) SIGNAL_HANDLER if(last_trigger + time_between_triggers > world.time) return // Don't want the traps triggered by sparks, ghosts or projectiles. - if(is_type_in_typecache(AM, ignore_typecache)) + if(is_type_in_typecache(victim, ignore_typecache)) return - if(ismob(AM)) - var/mob/M = AM - if(M.mind in immune_minds) + if(ismob(victim)) + var/mob/mob_victim = victim + if(mob_victim.mind in immune_minds) return - if(M.can_block_magic(antimagic_flags)) + if(mob_victim.can_block_magic(antimagic_flags)) flare() return if(charges <= 0) return flare() - if(isliving(AM)) - trap_effect(AM) + if(isliving(victim)) + trap_effect(victim) -/obj/structure/trap/proc/trap_effect(mob/living/L) +/obj/structure/trap/proc/trap_effect(mob/living/victim) return /obj/structure/trap/stun name = "shock trap" desc = "A trap that will shock and render you immobile. You'd better avoid it." icon_state = "trap-shock" - var/stun_time = 100 + var/stun_time = 10 SECONDS -/obj/structure/trap/stun/trap_effect(mob/living/L) - L.electrocute_act(30, src, flags = SHOCK_NOGLOVES) // electrocute act does a message. - L.Paralyze(stun_time) +/obj/structure/trap/stun/trap_effect(mob/living/victim) + victim.electrocute_act(30, src, flags = SHOCK_NOGLOVES) // electrocute act does a message. + victim.Paralyze(stun_time) /obj/structure/trap/stun/hunter name = "bounty trap" desc = "A trap that only goes off when a fugitive steps on it, announcing the location and stunning the target. You'd better avoid it." icon = 'icons/obj/objects.dmi' icon_state = "bounty_trap_on" - stun_time = 200 + stun_time = 20 SECONDS sparks = FALSE //the item version gives them off to prevent runtimes (see Destroy()) antimagic_flags = NONE var/obj/item/bountytrap/stored_item @@ -112,7 +112,7 @@ /obj/structure/trap/stun/hunter/Initialize(mapload) . = ..() - time_between_triggers = 10 + time_between_triggers = 1 SECONDS flare_message = "[src] snaps shut!" /obj/structure/trap/stun/hunter/Destroy() @@ -121,10 +121,10 @@ stored_item = null return ..() -/obj/structure/trap/stun/hunter/on_entered(datum/source, atom/movable/AM) - if(isliving(AM)) - var/mob/living/L = AM - if(!L.mind?.has_antag_datum(/datum/antagonist/fugitive)) +/obj/structure/trap/stun/hunter/on_entered(datum/source, atom/movable/victim) + if(isliving(victim)) + var/mob/living/living_victim = victim + if(!living_victim.mind?.has_antag_datum(/datum/antagonist/fugitive)) return caught = TRUE . = ..() @@ -169,11 +169,11 @@ radio.talk_into(src, "Fugitive has triggered this trap in the [get_area_name(src)]!", RADIO_CHANNEL_COMMON) /obj/item/bountytrap/attack_self(mob/living/user) - var/turf/T = get_turf(src) - if(!user || !user.transferItemToLoc(src, T))//visibly unequips + var/turf/target_turf = get_turf(src) + if(!user || !user.transferItemToLoc(src, target_turf))//visibly unequips return to_chat(user, span_notice("You set up [src]. Examine while close to disarm it.")) - stored_trap.forceMove(T)//moves trap to ground + stored_trap.forceMove(target_turf)//moves trap to ground forceMove(stored_trap)//moves item into trap /obj/item/bountytrap/Destroy() @@ -189,9 +189,9 @@ desc = "A trap that will set you ablaze. You'd better avoid it." icon_state = "trap-fire" -/obj/structure/trap/fire/trap_effect(mob/living/L) - to_chat(L, span_danger("Spontaneous combustion!")) - L.Paralyze(20) +/obj/structure/trap/fire/trap_effect(mob/living/victim) + to_chat(victim, span_danger("Spontaneous combustion!")) + victim.Paralyze(2 SECONDS) new /obj/effect/hotspot(get_turf(src)) /obj/structure/trap/chill @@ -199,11 +199,11 @@ desc = "A trap that will chill you to the bone. You'd better avoid it." icon_state = "trap-frost" -/obj/structure/trap/chill/trap_effect(mob/living/L) - to_chat(L, span_danger("You're frozen solid!")) - L.Paralyze(20) - L.adjust_bodytemperature(-300) - L.apply_status_effect(/datum/status_effect/freon) +/obj/structure/trap/chill/trap_effect(mob/living/victim) + to_chat(victim, span_bolddanger("You're frozen solid!")) + victim.Paralyze(2 SECONDS) + victim.adjust_bodytemperature(-300) + victim.apply_status_effect(/datum/status_effect/freon) /obj/structure/trap/damage @@ -212,12 +212,12 @@ icon_state = "trap-earth" -/obj/structure/trap/damage/trap_effect(mob/living/L) - to_chat(L, span_danger("The ground quakes beneath your feet!")) - L.Paralyze(100) - L.adjustBruteLoss(35) +/obj/structure/trap/damage/trap_effect(mob/living/victim) + to_chat(victim, span_bolddanger("The ground quakes beneath your feet!")) + victim.Paralyze(10 SECONDS) + victim.adjustBruteLoss(35) var/obj/structure/flora/rock/style_random/giant_rock = new(get_turf(src)) - QDEL_IN(giant_rock, 200) + QDEL_IN(giant_rock, 20 SECONDS) /obj/structure/trap/ward @@ -225,7 +225,7 @@ desc = "A divine barrier, It looks like you could destroy it with enough effort, or wait for it to dissipate..." icon_state = "ward" density = TRUE - time_between_triggers = 1200 //Exists for 2 minutes + time_between_triggers = 2 MINUTES /obj/structure/trap/ward/Initialize(mapload) . = ..() @@ -236,10 +236,10 @@ desc = "A trap that rings with unholy energy. You think you hear... chittering?" icon_state = "trap-cult" -/obj/structure/trap/cult/trap_effect(mob/living/L) - to_chat(L, span_danger("With a crack, the hostile constructs come out of hiding, stunning you!")) - L.electrocute_act(10, src, flags = SHOCK_NOGLOVES) // electrocute act does a message. - L.Paralyze(20) - new /mob/living/simple_animal/hostile/construct/proteon/hostile(loc) - new /mob/living/simple_animal/hostile/construct/proteon/hostile(loc) - QDEL_IN(src, 30) +/obj/structure/trap/cult/trap_effect(mob/living/victim) + to_chat(victim, span_bolddanger("With a crack, the hostile constructs come out of hiding, stunning you!")) + victim.electrocute_act(10, src, flags = SHOCK_NOGLOVES) // electrocute act does a message. + victim.Paralyze(2 SECONDS) + new /mob/living/basic/construct/proteon/hostile(loc) + new /mob/living/basic/construct/proteon/hostile(loc) + QDEL_IN(src, 3 SECONDS) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 1d0848e0ad28..4a0631594c46 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -327,7 +327,7 @@ return TRUE -/obj/structure/window/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) +/obj/structure/window/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0) . = ..() if(.) //received damage update_nearby_icons() diff --git a/code/game/turfs/closed/wall/misc_walls.dm b/code/game/turfs/closed/wall/misc_walls.dm index c96cf314a982..7705f9ded255 100644 --- a/code/game/turfs/closed/wall/misc_walls.dm +++ b/code/game/turfs/closed/wall/misc_walls.dm @@ -18,16 +18,6 @@ /turf/closed/wall/mineral/cult/devastate_wall() new sheet_type(get_turf(src), sheet_amount) -/turf/closed/wall/mineral/cult/Exited(atom/movable/gone, direction) - . = ..() - if(istype(gone, /mob/living/simple_animal/hostile/construct/harvester)) //harvesters can go through cult walls, dragging something with - var/mob/living/simple_animal/hostile/construct/harvester/H = gone - var/atom/movable/stored_pulling = H.pulling - if(stored_pulling) - stored_pulling.setDir(direction) - stored_pulling.forceMove(src) - H.start_pulling(stored_pulling, supress_message = TRUE) - /turf/closed/wall/mineral/cult/artificer name = "runed stone wall" desc = "A cold stone wall engraved with indecipherable symbols. Studying them causes your head to pound." diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm index 4d7595b91895..77b27fddc1d7 100644 --- a/code/game/turfs/closed/walls.dm +++ b/code/game/turfs/closed/walls.dm @@ -1,4 +1,5 @@ #define MAX_DENT_DECALS 15 +#define LEANING_OFFSET 11 /turf/closed/wall name = "wall" @@ -33,6 +34,50 @@ var/list/dent_decals +/turf/closed/wall/MouseDrop_T(mob/living/carbon/carbon_mob, mob/user) + ..() + if(carbon_mob != user) + return + if(carbon_mob.is_leaning == TRUE) + return + if(carbon_mob.pulledby) + return + if(!carbon_mob.density) + return + carbon_mob.is_leaning = TRUE + var/turf/checked_turf = get_step(carbon_mob, turn(carbon_mob.dir, 180)) + if(checked_turf == src) + carbon_mob.start_leaning(src) + +/mob/living/carbon/proc/start_leaning(obj/wall) + + switch(dir) + if(SOUTH) + pixel_y += LEANING_OFFSET + if(NORTH) + pixel_y += -LEANING_OFFSET + if(WEST) + pixel_x += LEANING_OFFSET + if(EAST) + pixel_x += -LEANING_OFFSET + + ADD_TRAIT(src, TRAIT_UNDENSE, LEANING_TRAIT) + ADD_TRAIT(src, TRAIT_EXPANDED_FOV, LEANING_TRAIT) + visible_message(span_notice("[src] leans against \the [wall]!"), \ + span_notice("You lean against \the [wall]!")) + RegisterSignals(src, list(COMSIG_MOB_CLIENT_PRE_MOVE, COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_GET_PULLED, COMSIG_MOVABLE_TELEPORTING, COMSIG_ATOM_DIR_CHANGE), PROC_REF(stop_leaning)) + update_fov() + +/mob/living/carbon/proc/stop_leaning() + SIGNAL_HANDLER + UnregisterSignal(src, list(COMSIG_MOB_CLIENT_PRE_MOVE, COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_GET_PULLED, COMSIG_MOVABLE_TELEPORTING, COMSIG_ATOM_DIR_CHANGE)) + is_leaning = FALSE + pixel_y = base_pixel_y + body_position_pixel_x_offset + pixel_x = base_pixel_y + body_position_pixel_y_offset + REMOVE_TRAIT(src, TRAIT_UNDENSE, LEANING_TRAIT) + REMOVE_TRAIT(src, TRAIT_EXPANDED_FOV, LEANING_TRAIT) + update_fov() + /turf/closed/wall/Initialize(mapload) . = ..() if(!can_engrave) @@ -328,4 +373,13 @@ /turf/closed/wall/metal_foam_base girder_type = /obj/structure/foamedmetal +/turf/closed/wall/Bumped(atom/movable/bumped_atom) + . = ..() + SEND_SIGNAL(bumped_atom, COMSIG_LIVING_WALL_BUMP, src) + +/turf/closed/wall/Exited(atom/movable/gone, direction) + . = ..() + SEND_SIGNAL(gone, COMSIG_LIVING_WALL_EXITED, src) + #undef MAX_DENT_DECALS +#undef LEANING_OFFSET diff --git a/code/game/turfs/open/floor/plating.dm b/code/game/turfs/open/floor/plating.dm index 993deb3aedef..756917e86f53 100644 --- a/code/game/turfs/open/floor/plating.dm +++ b/code/game/turfs/open/floor/plating.dm @@ -178,7 +178,7 @@ . = ..() ScrapeAway(flags = CHANGETURF_INHERIT_AIR) -/turf/open/floor/plating/foam/tool_act(mob/living/user, obj/item/I, tool_type) +/turf/open/floor/plating/foam/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking) return //reinforced plating deconstruction states diff --git a/code/modules/actionspeed/_actionspeed_modifier.dm b/code/modules/actionspeed/_actionspeed_modifier.dm index 71bc966acf4d..761bfc3ff74a 100644 --- a/code/modules/actionspeed/_actionspeed_modifier.dm +++ b/code/modules/actionspeed/_actionspeed_modifier.dm @@ -37,8 +37,11 @@ can next move /// Other modification datums this conflicts with. var/conflicts_with -/datum/actionspeed_modifier/New() +/datum/actionspeed_modifier/New(init_id) . = ..() + + id = init_id + if(!id) id = "[type]" //We turn the path into a string. diff --git a/code/modules/actionspeed/modifiers/wound.dm b/code/modules/actionspeed/modifiers/wound.dm new file mode 100644 index 000000000000..845399e07616 --- /dev/null +++ b/code/modules/actionspeed/modifiers/wound.dm @@ -0,0 +1,10 @@ +/datum/actionspeed_modifier/wound_interaction_inefficiency + variable = TRUE + + var/datum/wound/parent + +/datum/actionspeed_modifier/wound_interaction_inefficiency/New(new_id, datum/wound/parent) + + src.parent = parent + + return ..() diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index c01ab9e5b21d..0c3ebbb32541 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -52,6 +52,9 @@ GLOBAL_PROTECT(admin_verbs_admin) /client/proc/admin_enable_shuttle, /*undoes the above*/ /client/proc/admin_ghost, /*allows us to ghost/reenter body at will*/ /client/proc/admin_hostile_environment, /*Allows admins to prevent the emergency shuttle from leaving, also lets admins clear hostile environments if theres one stuck*/ + /client/proc/centcom_podlauncher,/*Open a window to launch a Supplypod and configure it or it's contents*/ + /client/proc/check_ai_laws, /*shows AI and borg laws*/ + /client/proc/check_antagonists, /*shows all antags*/ /client/proc/cmd_admin_check_contents, /*displays the contents of an instance*/ /client/proc/cmd_admin_check_player_exp, /* shows players by playtime */ /client/proc/cmd_admin_create_centcom_report, @@ -62,9 +65,7 @@ GLOBAL_PROTECT(admin_verbs_admin) /client/proc/cmd_admin_subtle_message, /*send a message to somebody as a 'voice in their head'*/ /client/proc/cmd_admin_world_narrate, /*sends text to all players with no padding*/ /client/proc/cmd_change_command_name, - /client/proc/centcom_podlauncher,/*Open a window to launch a Supplypod and configure it or it's contents*/ - /client/proc/check_ai_laws, /*shows AI and borg laws*/ - /client/proc/check_antagonists, /*shows all antags*/ + /client/proc/create_mob_worm, /client/proc/fax_panel, /*send a paper to fax*/ /client/proc/force_load_lazy_template, /client/proc/game_panel, /*game panel, allows to change game-mode etc*/ @@ -1039,3 +1040,47 @@ GLOBAL_PROTECT(admin_verbs_poll) message_admins("[key_name_admin(usr)] has loaded lazy template '[choice]'") to_chat(usr, span_boldnicegreen("Template loaded, you have been moved to the bottom left of the reservation.")) + +/client/proc/create_mob_worm() + set category = "Admin.Fun" + set name = "Create Mob Worm" + set desc = "Attached a linked list of mobs to a marked mob" + if (!check_rights(R_FUN)) + return + if(isnull(holder)) + return + if(!isliving(holder.marked_datum)) + to_chat(usr, span_warning("Error: Please mark a mob to attach mobs to.")) + return + var/mob/living/head = holder.marked_datum + + var/attempted_target_path = tgui_input_text( + usr, + "Enter typepath of a mob you'd like to make your chain from.", + "Typepath", + "[/mob/living/basic/pet/dog/corgi/ian]", + ) + + if (isnull(attempted_target_path)) + return //The user pressed "Cancel" + + var/desired_mob = text2path(attempted_target_path) + if(!ispath(desired_mob)) + var/static/list/mob_paths = make_types_fancy(subtypesof(/mob/living)) + desired_mob = pick_closest_path(attempted_target_path, mob_paths) + if(isnull(desired_mob) || !ispath(desired_mob) || QDELETED(head)) + return //The user pressed "Cancel" + + var/amount = tgui_input_number(usr, "How long should our tail be?", "Worm Configurator", default = 3, min_value = 1) + if (isnull(amount) || amount < 1 || QDELETED(head)) + return + head.AddComponent(/datum/component/mob_chain) + var/mob/living/previous = head + for (var/i in 1 to amount) + var/mob/living/segment = new desired_mob(head.drop_location()) + if (QDELETED(segment)) // ffs mobs which replace themselves with other mobs + i-- + continue + QDEL_NULL(segment.ai_controller) + segment.AddComponent(/datum/component/mob_chain, front = previous) + previous = segment diff --git a/code/modules/admin/smites/bloodless.dm b/code/modules/admin/smites/bloodless.dm index f42711c0f742..c970e920f225 100644 --- a/code/modules/admin/smites/bloodless.dm +++ b/code/modules/admin/smites/bloodless.dm @@ -9,10 +9,10 @@ return var/mob/living/carbon/carbon_target = target for(var/_limb in carbon_target.bodyparts) - var/obj/item/bodypart/limb = _limb - var/type_wound = pick(list(/datum/wound/slash/severe, /datum/wound/slash/moderate)) + var/obj/item/bodypart/limb = _limb // fine to use this raw, its a meme smite + var/type_wound = pick(list(/datum/wound/slash/flesh/severe, /datum/wound/slash/flesh/moderate)) limb.force_wound_upwards(type_wound, smited = TRUE) - type_wound = pick(list(/datum/wound/slash/critical, /datum/wound/slash/severe, /datum/wound/slash/moderate)) + type_wound = pick(list(/datum/wound/slash/flesh/critical, /datum/wound/slash/flesh/severe, /datum/wound/slash/flesh/moderate)) limb.force_wound_upwards(type_wound, smited = TRUE) - type_wound = pick(list(/datum/wound/slash/critical, /datum/wound/slash/severe)) + type_wound = pick(list(/datum/wound/slash/flesh/critical, /datum/wound/slash/flesh/severe)) limb.force_wound_upwards(type_wound, smited = TRUE) diff --git a/code/modules/admin/smites/boneless.dm b/code/modules/admin/smites/boneless.dm index a18b439c5c0d..bf402abdfdb6 100644 --- a/code/modules/admin/smites/boneless.dm +++ b/code/modules/admin/smites/boneless.dm @@ -10,7 +10,12 @@ return var/mob/living/carbon/carbon_target = target - for(var/_limb in carbon_target.bodyparts) - var/obj/item/bodypart/limb = _limb - var/type_wound = pick(list(/datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/moderate)) - limb.force_wound_upwards(type_wound, smited = TRUE) + for(var/obj/item/bodypart/limb as anything in carbon_target.bodyparts) + var/severity = pick(list( + "[WOUND_SEVERITY_MODERATE]", + "[WOUND_SEVERITY_SEVERE]", + "[WOUND_SEVERITY_SEVERE]", + "[WOUND_SEVERITY_CRITICAL]", + "[WOUND_SEVERITY_CRITICAL]", + )) + carbon_target.cause_wound_of_type_and_severity(WOUND_BLUNT, limb, severity) diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm index 667e29e615b1..8407c364c192 100644 --- a/code/modules/admin/verbs/secrets.dm +++ b/code/modules/admin/verbs/secrets.dm @@ -541,7 +541,7 @@ GLOBAL_DATUM(everyone_a_traitor, /datum/everyone_is_a_traitor_controller) var/spawnpoint = pick(GLOB.blobstart) var/list/mob/dead/observer/candidates var/mob/dead/observer/chosen_candidate - var/mob/living/simple_animal/drone/nerd + var/mob/living/basic/drone/nerd var/teamsize teamsize = input(usr, "How many drones?", "N.E.R.D. team size", 2) as num|null @@ -557,7 +557,7 @@ GLOBAL_DATUM(everyone_a_traitor, /datum/everyone_is_a_traitor_controller) while(length(candidates) && teamsize) chosen_candidate = pick(candidates) candidates -= chosen_candidate - nerd = new /mob/living/simple_animal/drone/classic(spawnpoint) + nerd = new /mob/living/basic/drone/classic(spawnpoint) nerd.key = chosen_candidate.key nerd.log_message("has been selected as a Nanotrasen emergency response drone.", LOG_GAME) teamsize-- diff --git a/code/modules/admin/view_variables/topic_basic.dm b/code/modules/admin/view_variables/topic_basic.dm index 09759c72c6f8..0bdc6e525ace 100644 --- a/code/modules/admin/view_variables/topic_basic.dm +++ b/code/modules/admin/view_variables/topic_basic.dm @@ -61,22 +61,34 @@ names += componentsubtypes names += "---Elements---" names += sort_list(subtypesof(/datum/element), GLOBAL_PROC_REF(cmp_typepaths_asc)) + var/result = tgui_input_list(usr, "Choose a component/element to add", "Add Component", names) if(isnull(result)) return if(!usr || result == "---Components---" || result == "---Elements---") return + if(QDELETED(src)) to_chat(usr, "That thing doesn't exist anymore!", confidential = TRUE) return + + var/add_source + if(ispath(result, /datum/component)) + var/datum/component/comp_path = result + if(initial(comp_path.dupe_mode) == COMPONENT_DUPE_SOURCES) + add_source = tgui_input_text(usr, "Enter a source for the component", "Add Component", "ADMIN-ABUSE") + if(isnull(add_source)) + return + var/list/lst = get_callproc_args() if(!lst) return + var/datumname = "error" lst.Insert(1, result) if(result in componentsubtypes) datumname = "component" - target._AddComponent(lst) + target._AddComponent(lst, add_source) else datumname = "element" target._AddElement(lst) diff --git a/code/modules/antagonists/blob/blob_minion.dm b/code/modules/antagonists/blob/blob_minion.dm index 9ba8e7089b9e..9bf37e961d5d 100644 --- a/code/modules/antagonists/blob/blob_minion.dm +++ b/code/modules/antagonists/blob/blob_minion.dm @@ -34,9 +34,3 @@ objective.owner = owner objective.overmind = overmind objectives += objective - -/datum/antagonist/blob_minion/blobbernaut - name = "\improper Blobbernaut" - -/datum/antagonist/blob_minion/blob_zombie - name = "\improper Blob Zombie" diff --git a/code/modules/antagonists/blob/blobstrains/_blobstrain.dm b/code/modules/antagonists/blob/blobstrains/_blobstrain.dm index db0d583e15f2..6f01eb67aa72 100644 --- a/code/modules/antagonists/blob/blobstrains/_blobstrain.dm +++ b/code/modules/antagonists/blob/blobstrains/_blobstrain.dm @@ -38,8 +38,6 @@ GLOBAL_LIST_INIT(valid_blobstrains, subtypesof(/datum/blobstrain) - list(/datum/ /// Adds to claim, pulse, and expand range var/core_range_bonus = 0 - /// The core can sustain this many extra spores with this strain - var/core_spore_bonus = 0 /// Extra range up to which the core reinforces blobs var/core_strong_reinforcement_range_bonus = 0 /// Extra range up to which the core reinforces blobs into reflectors @@ -78,7 +76,6 @@ GLOBAL_LIST_INIT(valid_blobstrains, subtypesof(/datum/blobstrain) - list(/datum/ overmind.color = complementary_color if(overmind.blob_core) - overmind.blob_core.max_spores += core_spore_bonus overmind.blob_core.claim_range += core_range_bonus overmind.blob_core.pulse_range += core_range_bonus overmind.blob_core.expand_range += core_range_bonus @@ -86,7 +83,6 @@ GLOBAL_LIST_INIT(valid_blobstrains, subtypesof(/datum/blobstrain) - list(/datum/ overmind.blob_core.reflector_reinforce_range += core_reflector_reinforcement_range_bonus for(var/obj/structure/blob/special/node/N as anything in overmind.node_blobs) - N.max_spores += node_spore_bonus N.claim_range += node_range_bonus N.pulse_range += node_range_bonus N.expand_range += node_range_bonus @@ -100,26 +96,22 @@ GLOBAL_LIST_INIT(valid_blobstrains, subtypesof(/datum/blobstrain) - list(/datum/ B.modify_max_integrity(B.max_integrity * max_structure_health_multiplier) B.update_appearance() - for(var/mob/living/simple_animal/hostile/blob/BM as anything in overmind.blob_mobs) - BM.maxHealth *= max_mob_health_multiplier - BM.health *= max_mob_health_multiplier - BM.update_icons() //If it's getting a new strain, tell it what it does! - to_chat(BM, "Your overmind's blob strain is now: [name]!") - to_chat(BM, "The [name] strain [shortdesc ? "[shortdesc]" : "[description]"]") + for(var/mob/living/blob_mob as anything in overmind.blob_mobs) + blob_mob.maxHealth *= max_mob_health_multiplier + blob_mob.health *= max_mob_health_multiplier + blob_mob.update_icons() //If it's getting a new strain, tell it what it does! + to_chat(blob_mob, "Your overmind's blob strain is now: [name]!") + to_chat(blob_mob, "The [name] strain [shortdesc ? "[shortdesc]" : "[description]"]") /datum/blobstrain/proc/on_lose() if(overmind.blob_core) - overmind.blob_core.max_spores -= core_spore_bonus overmind.blob_core.claim_range -= core_range_bonus - overmind.blob_core.pulse_range -= core_range_bonus overmind.blob_core.expand_range -= core_range_bonus overmind.blob_core.strong_reinforce_range -= core_strong_reinforcement_range_bonus overmind.blob_core.reflector_reinforce_range -= core_reflector_reinforcement_range_bonus for(var/obj/structure/blob/special/node/N as anything in overmind.node_blobs) - N.max_spores -= node_spore_bonus N.claim_range -= node_range_bonus - N.pulse_range -= node_range_bonus N.expand_range -= node_range_bonus N.strong_reinforce_range -= node_strong_reinforcement_range_bonus N.reflector_reinforce_range -= node_reflector_reinforcement_range_bonus @@ -130,9 +122,9 @@ GLOBAL_LIST_INIT(valid_blobstrains, subtypesof(/datum/blobstrain) - list(/datum/ for(var/obj/structure/blob/B as anything in overmind.all_blobs) B.modify_max_integrity(B.max_integrity / max_structure_health_multiplier) - for(var/mob/living/simple_animal/hostile/blob/BM as anything in overmind.blob_mobs) - BM.maxHealth /= max_mob_health_multiplier - BM.health /= max_mob_health_multiplier + for(var/mob/living/blob_mob as anything in overmind.blob_mobs) + blob_mob.maxHealth /= max_mob_health_multiplier + blob_mob.health /= max_mob_health_multiplier /datum/blobstrain/proc/on_sporedeath(mob/living/spore) diff --git a/code/modules/antagonists/blob/blobstrains/_reagent.dm b/code/modules/antagonists/blob/blobstrains/_reagent.dm index aefd6c02f3c3..05bc73f95c8d 100644 --- a/code/modules/antagonists/blob/blobstrains/_reagent.dm +++ b/code/modules/antagonists/blob/blobstrains/_reagent.dm @@ -15,8 +15,9 @@ var/mob_protection = L.getarmor(null, BIO) * 0.01 reagent.expose_mob(L, VAPOR, BLOBMOB_BLOBBERNAUT_REAGENTATK_VOL+blobbernaut_reagentatk_bonus, FALSE, mob_protection, overmind)//this will do between 10 and 20 damage(reduced by mob protection), depending on chemical, plus 4 from base brute damage. -/datum/blobstrain/reagent/on_sporedeath(mob/living/spore) - spore.reagents.add_reagent(reagent.type, 10) +/datum/blobstrain/reagent/on_sporedeath(mob/living/basic/spore) + var/burst_range = (spore.type == /mob/living/basic/blob_minion/spore) ? 1 : 0 + do_chem_smoke(range = burst_range, holder = spore, location = get_turf(spore), reagent_type = reagent.type) // These can only be applied by blobs. They are what (reagent) blobs are made out of. /datum/reagent/blob @@ -29,7 +30,7 @@ /// Used by blob reagents to calculate the reaction volume they should use when exposing mobs. /datum/reagent/blob/proc/return_mob_expose_reac_volume(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/overmind) - if(exposed_mob.stat == DEAD || isblobmonster(exposed_mob)) + if(exposed_mob.stat == DEAD || HAS_TRAIT(exposed_mob, TRAIT_BLOB_ALLY)) return 0 //the dead, and blob mobs, don't cause reactions return round(reac_volume * min(1.5 - touch_protection, 1), 0.1) //full touch protection means 50% volume, any prot below 0.5 means 100% volume. diff --git a/code/modules/antagonists/blob/blobstrains/distributed_neurons.dm b/code/modules/antagonists/blob/blobstrains/distributed_neurons.dm index f97599948a13..ea2bf54d7692 100644 --- a/code/modules/antagonists/blob/blobstrains/distributed_neurons.dm +++ b/code/modules/antagonists/blob/blobstrains/distributed_neurons.dm @@ -11,18 +11,16 @@ message_living = ", and you feel tired" reagent = /datum/reagent/blob/distributed_neurons -/datum/blobstrain/reagent/distributed_neurons/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag) - if((damage_flag == MELEE || damage_flag == BULLET || damage_flag == LASER) && damage <= 20 && B.get_integrity() - damage <= 0 && prob(15)) //if the cause isn't fire or a bomb, the damage is less than 21, we're going to die from that damage, 15% chance of a shitty spore. - B.visible_message(span_warning("A spore floats free of the blob!")) - var/mob/living/simple_animal/hostile/blob/blobspore/weak/BS = new/mob/living/simple_animal/hostile/blob/blobspore/weak(B.loc) - BS.overmind = B.overmind - BS.update_icons() - B.overmind.blob_mobs.Add(BS) +/datum/blobstrain/reagent/distributed_neurons/damage_reaction(obj/structure/blob/blob_tile, damage, damage_type, damage_flag) + if((damage_flag == MELEE || damage_flag == BULLET || damage_flag == LASER) && damage <= 20 && blob_tile.get_integrity() - damage <= 0 && prob(15)) //if the cause isn't fire or a bomb, the damage is less than 21, we're going to die from that damage, 15% chance of a shitty spore. + blob_tile.visible_message(span_boldwarning("A spore floats free of the blob!")) + blob_tile.overmind.create_spore(blob_tile.loc, /mob/living/basic/blob_minion/spore/minion/weak) return ..() /datum/reagent/blob/distributed_neurons name = "Distributed Neurons" color = "#E88D5D" + taste_description = "fizzing" /datum/reagent/blob/distributed_neurons/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/overmind) . = ..() @@ -33,10 +31,7 @@ exposed_mob.investigate_log("has been killed by distributed neurons (blob).", INVESTIGATE_DEATHS) exposed_mob.death() //sleeping in a fight? bad plan. if(exposed_mob.stat == DEAD && overmind.can_buy(5)) - var/mob/living/simple_animal/hostile/blob/blobspore/spore = new/mob/living/simple_animal/hostile/blob/blobspore(get_turf(exposed_mob)) - spore.overmind = overmind - spore.update_icons() - overmind.blob_mobs.Add(spore) + var/mob/living/basic/blob_minion/spore/minion/spore = overmind.create_spore(get_turf(exposed_mob)) spore.zombify(exposed_mob) overmind.add_points(-5) to_chat(overmind, span_notice("Spent 5 resources for the zombification of [exposed_mob].")) diff --git a/code/modules/antagonists/blob/blobstrains/reactive_spines.dm b/code/modules/antagonists/blob/blobstrains/reactive_spines.dm index 53f336ca419e..1c8cb893df83 100644 --- a/code/modules/antagonists/blob/blobstrains/reactive_spines.dm +++ b/code/modules/antagonists/blob/blobstrains/reactive_spines.dm @@ -16,15 +16,15 @@ if(damage && ((damage_type == BRUTE) || (damage_type == BURN)) && B.get_integrity() - damage > 0 && COOLDOWN_FINISHED(src, retaliate_cooldown)) // Is there any damage, is it burn or brute, will we be alive, and has the cooldown finished? COOLDOWN_START(src, retaliate_cooldown, 2.5 SECONDS) // 2.5 seconds before auto-retaliate can whack everything within 1 tile again B.visible_message(span_boldwarning("The blob retaliates, lashing out!")) - for(var/atom/A in range(1, B)) - if(!A.can_blob_attack()) + for(var/atom/thing in range(1, B)) + if(!thing.can_blob_attack()) continue - var/attacked_turf = get_turf(A) - if(isliving(A) && !isblobmonster(A)) // Make sure to inject strain-reagents with automatic attacks when needed. + var/attacked_turf = get_turf(thing) + if(isliving(thing) && !HAS_TRAIT(thing, TRAIT_BLOB_ALLY)) // Make sure to inject strain-reagents with automatic attacks when needed. B.blob_attack_animation(attacked_turf, overmind) - attack_living(A) + attack_living(thing) - else if(A.blob_act(B)) // After checking for mobs, whack everything else with the standard attack + else if(thing.blob_act(B)) // After checking for mobs, whack everything else with the standard attack B.blob_attack_animation(attacked_turf, overmind) // Only play the animation if the attack did something meaningful return ..() @@ -35,7 +35,7 @@ color = "#9ACD32" /datum/reagent/blob/reactive_spines/return_mob_expose_reac_volume(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/overmind) - if(exposed_mob.stat == DEAD || isblobmonster(exposed_mob)) + if(exposed_mob.stat == DEAD || HAS_TRAIT(exposed_mob, TRAIT_BLOB_ALLY)) return 0 //the dead, and blob mobs, don't cause reactions return reac_volume diff --git a/code/modules/antagonists/blob/overmind.dm b/code/modules/antagonists/blob/overmind.dm index 021c8bb12d51..f09108aa4448 100644 --- a/code/modules/antagonists/blob/overmind.dm +++ b/code/modules/antagonists/blob/overmind.dm @@ -54,6 +54,7 @@ GLOBAL_LIST_EMPTY(blob_nodes) var/list/strain_choices /mob/camera/blob/Initialize(mapload, starting_points = OVERMIND_STARTING_POINTS) + ADD_TRAIT(src, TRAIT_BLOB_ALLY, INNATE_TRAIT) validate_location() blob_points = starting_points manualplace_min_time += world.time @@ -71,6 +72,7 @@ GLOBAL_LIST_EMPTY(blob_nodes) SSshuttle.registerHostileEnvironment(src) . = ..() START_PROCESSING(SSobj, src) + GLOB.blob_telepathy_mobs |= src /mob/camera/blob/proc/validate_location() var/turf/T = get_turf(src) @@ -112,6 +114,7 @@ GLOBAL_LIST_EMPTY(blob_nodes) to_chat(src, span_notice("The [blobstrain.name] strain [blobstrain.description]")) if(blobstrain.effectdesc) to_chat(src, span_notice("The [blobstrain.name] strain [blobstrain.effectdesc]")) + SEND_SIGNAL(src, COMSIG_BLOB_SELECTED_STRAIN, blobstrain) /mob/camera/blob/can_z_move(direction, turf/start, turf/destination, z_move_flags = NONE, mob/living/rider) if(placed) // The blob can't expand vertically (yet) @@ -162,50 +165,70 @@ GLOBAL_LIST_EMPTY(blob_nodes) priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", ANNOUNCER_OUTBREAK5) has_announced = TRUE +/// Create a blob spore and link it to us +/mob/camera/blob/proc/create_spore(turf/spore_turf, spore_type = /mob/living/basic/blob_minion/spore/minion) + var/mob/living/basic/blob_minion/spore/spore = new spore_type(spore_turf) + assume_direct_control(spore) + return spore + +/// Give our new minion the properties of a minion +/mob/camera/blob/proc/assume_direct_control(mob/living/minion) + minion.AddComponent(/datum/component/blob_minion, src) + +/// Add something to our list of mobs and wait for it to die +/mob/camera/blob/proc/register_new_minion(mob/living/minion) + blob_mobs |= minion + if (!istype(minion, /mob/living/basic/blob_minion/blobbernaut)) + RegisterSignal(minion, COMSIG_LIVING_DEATH, PROC_REF(on_minion_death)) + +/// When a spore (or zombie) dies then we do this +/mob/camera/blob/proc/on_minion_death(mob/living/spore) + SIGNAL_HANDLER + blobstrain.on_sporedeath(spore) + /mob/camera/blob/proc/victory() sound_to_playing_players('sound/machines/alarm.ogg') sleep(10 SECONDS) - for(var/i in GLOB.mob_living_list) - var/mob/living/L = i - var/turf/T = get_turf(L) - if(!T || !is_station_level(T.z)) + for(var/mob/living/live_guy as anything in GLOB.mob_living_list) + var/turf/guy_turf = get_turf(live_guy) + if(isnull(guy_turf) || !is_station_level(guy_turf.z)) continue - if(L in GLOB.overminds || (L.pass_flags & PASSBLOB)) + if(live_guy in GLOB.overminds || (live_guy.pass_flags & PASSBLOB)) continue - var/area/Ablob = get_area(T) - - if(!(Ablob.area_flags & BLOBS_ALLOWED)) + var/area/blob_area = get_area(guy_turf) + if(!(blob_area.area_flags & BLOBS_ALLOWED)) continue - if(!(ROLE_BLOB in L.faction)) - playsound(L, 'sound/effects/splat.ogg', 50, TRUE) - if(L.stat != DEAD) - L.investigate_log("has died from blob takeover.", INVESTIGATE_DEATHS) - L.death() - new/mob/living/simple_animal/hostile/blob/blobspore(T) + if(!(ROLE_BLOB in live_guy.faction)) + playsound(live_guy, 'sound/effects/splat.ogg', 50, TRUE) + if(live_guy.stat != DEAD) + live_guy.investigate_log("has died from blob takeover.", INVESTIGATE_DEATHS) + live_guy.death() + create_spore(guy_turf) else - L.fully_heal() + live_guy.fully_heal() - for(var/area/A in GLOB.areas) - if(!(A.type in GLOB.the_station_areas)) + for(var/area/check_area in GLOB.areas) + if(!is_type_in_list(check_area, GLOB.the_station_areas)) continue - if(!(A.area_flags & BLOBS_ALLOWED)) + if(!(check_area.area_flags & BLOBS_ALLOWED)) continue - A.color = blobstrain.color - A.name = "blob" - A.icon = 'icons/mob/nonhuman-player/blob.dmi' - A.icon_state = "blob_shield" - A.layer = BELOW_MOB_LAYER - A.invisibility = 0 - A.blend_mode = 0 + check_area.color = blobstrain.color + check_area.name = "blob" + check_area.icon = 'icons/mob/nonhuman-player/blob.dmi' + check_area.icon_state = "blob_shield" + check_area.layer = BELOW_MOB_LAYER + check_area.invisibility = 0 + check_area.blend_mode = 0 + var/datum/antagonist/blob/B = mind.has_antag_datum(/datum/antagonist/blob) if(B) var/datum/objective/blob_takeover/main_objective = locate() in B.objectives if(main_objective) main_objective.completed = TRUE - to_chat(world, span_blob("[real_name] consumed the station in an unstoppable tide!")) + to_chat(world, span_blobannounce("[real_name] consumed the station in an unstoppable tide!")) SSticker.news_report = BLOB_WIN SSticker.force_ending = TRUE @@ -216,11 +239,6 @@ GLOBAL_LIST_EMPTY(blob_nodes) if(B && B.overmind == src) B.overmind = null B.update_appearance() //reset anything that was ours - for(var/BLO in blob_mobs) - var/mob/living/simple_animal/hostile/blob/BM = BLO - if(BM) - BM.overmind = null - BM.update_icons() for(var/obj/structure/blob/blob_structure as anything in all_blobs) blob_structure.overmind = null all_blobs = null @@ -233,6 +251,7 @@ GLOBAL_LIST_EMPTY(blob_nodes) SSshuttle.clearHostileEnvironment(src) STOP_PROCESSING(SSobj, src) + GLOB.blob_telepathy_mobs -= src return ..() @@ -240,7 +259,7 @@ GLOBAL_LIST_EMPTY(blob_nodes) . = ..() if(!. || !client) return FALSE - to_chat(src, span_blob("You are the overmind!")) + to_chat(src, span_blobannounce("You are the overmind!")) if(!placed && autoplace_max_time <= world.time) to_chat(src, span_boldannounce("You will automatically place your blob core in [DisplayTimeText(autoplace_max_time - world.time)].")) to_chat(src, span_boldannounce("You [manualplace_min_time ? "will be able to":"can"] manually place your blob core by pressing the Place Blob Core button in the bottom right corner of the screen.")) @@ -257,9 +276,11 @@ GLOBAL_LIST_EMPTY(blob_nodes) return FALSE var/current_health = round((blob_core.get_integrity() / blob_core.max_integrity) * 100) hud_used.healths.maptext = MAPTEXT("
[current_health]%
") - for(var/mob/living/simple_animal/hostile/blob/blobbernaut/blobbernaut in blob_mobs) - if(blobbernaut.hud_used && blobbernaut.hud_used.blobpwrdisplay) - blobbernaut.hud_used.blobpwrdisplay.maptext = MAPTEXT("
[current_health]%
") + for(var/mob/living/basic/blob_minion/blobbernaut/blobbernaut in blob_mobs) + var/datum/hud/using_hud = blobbernaut.hud_used + if(!using_hud?.blobpwrdisplay) + continue + using_hud.blobpwrdisplay.maptext = MAPTEXT("
[current_health]%
") /mob/camera/blob/proc/add_points(points) blob_points = clamp(blob_points + points, 0, max_blob_points) @@ -273,7 +294,7 @@ GLOBAL_LIST_EMPTY(blob_nodes) if(client.prefs.muted & MUTE_IC) to_chat(src, span_boldwarning("You cannot send IC messages (muted).")) return - if (!(ignore_spam || forced) && src.client.handle_spam_prevention(message,MUTE_IC)) + if (!(ignore_spam || forced) && src.client.handle_spam_prevention(message, MUTE_IC)) return if (stat) @@ -291,14 +312,8 @@ GLOBAL_LIST_EMPTY(blob_nodes) src.log_talk(message, LOG_SAY) var/message_a = say_quote(message) - var/rendered = span_big("\[Blob Telepathy\] [name]([blobstrain.name]) [message_a]") - - for(var/mob/M in GLOB.mob_list) - if(isovermind(M) || isblobmonster(M)) - to_chat(M, rendered) - if(isobserver(M)) - var/link = FOLLOW_LINK(M, src) - to_chat(M, "[link] [rendered]") + var/rendered = span_big(span_blob("\[Blob Telepathy\] [name]([blobstrain.name]) [message_a]")) + relay_to_list_and_observers(rendered, GLOB.blob_telepathy_mobs, src) /mob/camera/blob/blob_act(obj/structure/blob/B) return @@ -324,8 +339,8 @@ GLOBAL_LIST_EMPTY(blob_nodes) else return FALSE else - var/area/A = get_area(NewLoc) - if(isgroundlessturf(NewLoc) || istype(A, /area/shuttle)) //if unplaced, can't go on shuttles or goundless tiles + var/area/check_area = get_area(NewLoc) + if(isgroundlessturf(NewLoc) || istype(check_area, /area/shuttle)) //if unplaced, can't go on shuttles or groundless tiles return FALSE forceMove(NewLoc) return TRUE diff --git a/code/modules/antagonists/blob/powers.dm b/code/modules/antagonists/blob/powers.dm index 765b93144959..04054f6df85a 100644 --- a/code/modules/antagonists/blob/powers.dm +++ b/code/modules/antagonists/blob/powers.dm @@ -176,7 +176,7 @@ if(!factory) to_chat(src, span_warning("You must be on a factory blob!")) return FALSE - if(factory.naut) //if it already made a blobbernaut, it can't do it again + if(factory.blobbernaut || factory.is_creating_blobbernaut) //if it already made or making a blobbernaut, it can't do it again to_chat(src, span_warning("This factory blob is already sustaining a blobbernaut.")) return FALSE if(factory.get_integrity() < factory.max_integrity * 0.5) @@ -185,7 +185,7 @@ if(!can_buy(BLOBMOB_BLOBBERNAUT_RESOURCE_COST)) return FALSE - factory.naut = TRUE //temporary placeholder to prevent creation of more than one per factory. + factory.is_creating_blobbernaut = TRUE to_chat(src, span_notice("You attempt to produce a blobbernaut.")) pick_blobbernaut_candidate(factory) @@ -199,33 +199,22 @@ if(!length(candidates)) to_chat(src, span_warning("You could not conjure a sentience for your blobbernaut. Your points have been refunded. Try again later.")) add_points(BLOBMOB_BLOBBERNAUT_RESOURCE_COST) - factory.naut = null //players must answer rapidly + factory.assign_blobbernaut(null) return FALSE - factory.modify_max_integrity(initial(factory.max_integrity) * 0.25) //factories that produced a blobbernaut have much lower health - factory.update_appearance() - factory.visible_message(span_warning("The blobbernaut [pick("rips", "tears", "shreds")] its way out of the factory blob!")) - playsound(factory.loc, 'sound/effects/splat.ogg', 50, TRUE) - - var/mob/living/simple_animal/hostile/blob/blobbernaut/blobber = new /mob/living/simple_animal/hostile/blob/blobbernaut(get_turf(factory)) - flick("blobbernaut_produce", blobber) - - factory.naut = blobber - blobber.factory = factory - blobber.overmind = src - blobber.update_icons() - blobber.adjustHealth(blobber.maxHealth * 0.5) - blob_mobs += blobber - + var/mob/living/basic/blob_minion/blobbernaut/minion/blobber = new(get_turf(factory)) + assume_direct_control(blobber) + factory.assign_blobbernaut(blobber) var/mob/dead/observer/player = pick(candidates) - blobber.key = player.key + blobber.assign_key(player.key, blobstrain) + RegisterSignal(blobber, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(on_blobbernaut_attacked)) - SEND_SOUND(blobber, sound('sound/effects/blobattack.ogg')) - SEND_SOUND(blobber, sound('sound/effects/attackblob.ogg')) - to_chat(blobber, span_infoplain("You are powerful, hard to kill, and slowly regenerate near nodes and cores, [span_cultlarge("but will slowly die if not near the blob")] or if the factory that made you is killed.")) - to_chat(blobber, span_infoplain("You can communicate with other blobbernauts and overminds telepathically by attempting to speak normally")) - to_chat(blobber, span_infoplain("Your overmind's blob reagent is: [blobstrain.name]!")) - to_chat(blobber, span_infoplain("The [blobstrain.name] reagent [blobstrain.shortdesc ? "[blobstrain.shortdesc]" : "[blobstrain.description]"]")) +/// When one of our boys attacked something, we sometimes want to perform extra effects +/mob/camera/blob/proc/on_blobbernaut_attacked(mob/living/basic/blobbynaut, atom/target, success) + SIGNAL_HANDLER + if (!success) + return + blobstrain.blobbernaut_attack(target, blobbynaut) /** Moves the core */ /mob/camera/blob/proc/relocate_core() @@ -356,10 +345,11 @@ var/list/surrounding_turfs = TURF_NEIGHBORS(tile) if(!length(surrounding_turfs)) return FALSE - for(var/mob/living/simple_animal/hostile/blob/blobspore/spore as anything in blob_mobs) - if(isturf(spore.loc) && get_dist(spore, tile) <= 35 && !spore.key) - spore.LoseTarget() - spore.Goto(pick(surrounding_turfs), spore.move_to_delay) + for(var/mob/living/basic/blob_mob as anything in blob_mobs) + if(!isturf(blob_mob.loc) || get_dist(blob_mob, tile) > 35 || blob_mob.key) + continue + blob_mob.ai_controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) + blob_mob.ai_controller.set_blackboard_key(BB_TRAVEL_DESTINATION, pick(surrounding_turfs)) /** Opens the reroll menu to change strains */ /mob/camera/blob/proc/strain_reroll() diff --git a/code/modules/antagonists/blob/structures/_blob.dm b/code/modules/antagonists/blob/structures/_blob.dm index bf934053eeeb..0655a30f3ff2 100644 --- a/code/modules/antagonists/blob/structures/_blob.dm +++ b/code/modules/antagonists/blob/structures/_blob.dm @@ -127,13 +127,13 @@ return FALSE //oh no we failed /obj/structure/blob/proc/ConsumeTile() - for(var/atom/A in loc) - if(!A.can_blob_attack()) + for(var/atom/thing in loc) + if(!thing.can_blob_attack()) continue - if(isliving(A) && overmind && !isblobmonster(A)) // Make sure to inject strain-reagents with automatic attacks when needed. - overmind.blobstrain.attack_living(A) + if(isliving(thing) && overmind && !HAS_TRAIT(thing, TRAIT_BLOB_ALLY)) // Make sure to inject strain-reagents with automatic attacks when needed. + overmind.blobstrain.attack_living(thing) continue // Don't smack them twice though - A.blob_act(src) + thing.blob_act(src) if(iswallturf(loc)) loc.blob_act(src) //don't ask how a wall got on top of the core, just eat it @@ -398,13 +398,6 @@ /// The radius up to which this special structure naturally grows normal blobs. var/expand_range = 0 - // Spore production vars: for core, factories, and nodes (with strains) - var/mob/living/simple_animal/hostile/blob/blobbernaut/naut = null - var/max_spores = 0 - var/list/spores = list() - COOLDOWN_DECLARE(spore_delay) - var/spore_cooldown = BLOBMOB_SPORE_SPAWN_COOLDOWN - // Area reinforcement vars: used by cores and nodes, for strains to modify /// Range this blob free upgrades to strong blobs at: for the core, and for strains var/strong_reinforce_range = 0 @@ -452,17 +445,3 @@ expanded = TRUE if(distance <= pulse_range) B.Be_Pulsed() - -/obj/structure/blob/special/proc/produce_spores() - if(naut) - return - if(spores.len >= max_spores) - return - if(!COOLDOWN_FINISHED(src, spore_delay)) - return - COOLDOWN_START(src, spore_delay, spore_cooldown) - var/mob/living/simple_animal/hostile/blob/blobspore/BS = new (loc, src) - if(overmind) //if we don't have an overmind, we don't need to do anything but make a spore - BS.overmind = overmind - BS.update_icons() - overmind.blob_mobs.Add(BS) diff --git a/code/modules/antagonists/blob/structures/core.dm b/code/modules/antagonists/blob/structures/core.dm index 2f62ca427859..5b81f857d62f 100644 --- a/code/modules/antagonists/blob/structures/core.dm +++ b/code/modules/antagonists/blob/structures/core.dm @@ -14,7 +14,6 @@ claim_range = BLOB_CORE_CLAIM_RANGE pulse_range = BLOB_CORE_PULSE_RANGE expand_range = BLOB_CORE_EXPAND_RANGE - max_spores = BLOB_CORE_MAX_SPORES ignore_syncmesh_share = TRUE /datum/armor/special_core @@ -78,7 +77,6 @@ overmind.update_health_hud() pulse_area(overmind, claim_range, pulse_range, expand_range) reinforce_area(seconds_per_tick) - produce_spores() ..() /obj/structure/blob/special/core/on_changed_z_level(turf/old_turf, turf/new_turf) diff --git a/code/modules/antagonists/blob/structures/factory.dm b/code/modules/antagonists/blob/structures/factory.dm index b29262dec8ea..285946b9097c 100644 --- a/code/modules/antagonists/blob/structures/factory.dm +++ b/code/modules/antagonists/blob/structures/factory.dm @@ -7,10 +7,19 @@ health_regen = BLOB_FACTORY_HP_REGEN point_return = BLOB_REFUND_FACTORY_COST resistance_flags = LAVA_PROOF - max_spores = BLOB_FACTORY_MAX_SPORES + ///How many spores this factory can have. + var/max_spores = BLOB_FACTORY_MAX_SPORES + ///The list of spores and zombies + var/list/spores_and_zombies = list() + COOLDOWN_DECLARE(spore_delay) + var/spore_cooldown = BLOBMOB_SPORE_SPAWN_COOLDOWN + ///Its Blobbernaut, if it has spawned any. + var/mob/living/basic/blob_minion/blobbernaut/minion/blobbernaut + ///Used in blob/powers.dm, checks if it's already trying to spawn a blobbernaut to prevent issues. + var/is_creating_blobbernaut = FALSE /obj/structure/blob/special/factory/scannerreport() - if(naut) + if(blobbernaut) return "It is currently sustaining a blobbernaut, making it fragile and unable to produce blob spores." return "Will produce a blob spore every few seconds." @@ -19,18 +28,67 @@ overmind.factory_blobs += src /obj/structure/blob/special/factory/Destroy() - for(var/mob/living/simple_animal/hostile/blob/blobspore/spore in spores) - to_chat(spore, span_userdanger("Your factory was destroyed! You can no longer sustain yourself.")) - spore.death() - if(naut) - naut.factory = null - to_chat(naut, span_userdanger("Your factory was destroyed! You feel yourself dying!")) - naut.throw_alert("nofactory", /atom/movable/screen/alert/nofactory) - spores = null + spores_and_zombies = null + blobbernaut = null if(overmind) overmind.factory_blobs -= src return ..() /obj/structure/blob/special/factory/Be_Pulsed() . = ..() - produce_spores() + if(blobbernaut) + return + if(length(spores_and_zombies) >= max_spores) + return + if(!COOLDOWN_FINISHED(src, spore_delay)) + return + COOLDOWN_START(src, spore_delay, spore_cooldown) + var/mob/living/basic/blob_minion/created_spore = (overmind) ? overmind.create_spore(loc) : new(loc) + register_mob(created_spore) + RegisterSignal(created_spore, COMSIG_BLOB_ZOMBIFIED, PROC_REF(on_zombie_created)) + +/// Tracks the existence of a mob in our mobs list +/obj/structure/blob/special/factory/proc/register_mob(mob/living/basic/blob_minion/blob_mob) + spores_and_zombies |= blob_mob + blob_mob.link_to_factory(src) + RegisterSignal(blob_mob, COMSIG_LIVING_DEATH, PROC_REF(on_spore_died)) + RegisterSignal(blob_mob, COMSIG_PARENT_QDELETING, PROC_REF(on_spore_lost)) + +/// When a spore or zombie dies reset our spawn cooldown so we don't instantly replace it +/obj/structure/blob/special/factory/proc/on_spore_died(mob/living/dead_spore) + SIGNAL_HANDLER + COOLDOWN_START(src, spore_delay, spore_cooldown) + +/// When a spore is deleted remove it from our list +/obj/structure/blob/special/factory/proc/on_spore_lost(mob/living/dead_spore) + SIGNAL_HANDLER + spores_and_zombies -= dead_spore + +/// When a spore makes a zombie add it to our mobs list +/obj/structure/blob/special/factory/proc/on_zombie_created(mob/living/spore, mob/living/zombie) + SIGNAL_HANDLER + register_mob(zombie) + +/// Produce a blobbernaut +/obj/structure/blob/special/factory/proc/assign_blobbernaut(mob/living/new_naut) + is_creating_blobbernaut = FALSE + if (isnull(new_naut)) + return + + modify_max_integrity(initial(max_integrity) * 0.25) //factories that produced a blobbernaut have much lower health + visible_message(span_boldwarning("The blobbernaut [pick("rips", "tears", "shreds")] its way out of the factory blob!")) + playsound(loc, 'sound/effects/splat.ogg', 50, TRUE) + + blobbernaut = new_naut + blobbernaut.link_to_factory(src) + RegisterSignals(new_naut, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(on_blobbernaut_death)) + update_appearance(UPDATE_ICON) + +/// When our brave soldier dies, reset our max integrity +/obj/structure/blob/special/factory/proc/on_blobbernaut_death(mob/living/death_naut) + SIGNAL_HANDLER + if (isnull(blobbernaut) || blobbernaut != death_naut) + return + blobbernaut = null + max_integrity = initial(max_integrity) + update_appearance(UPDATE_ICON) diff --git a/code/modules/antagonists/blob/structures/node.dm b/code/modules/antagonists/blob/structures/node.dm index aac3f3a4a8e0..7e3e2a96aeb4 100644 --- a/code/modules/antagonists/blob/structures/node.dm +++ b/code/modules/antagonists/blob/structures/node.dm @@ -11,7 +11,6 @@ pulse_range = BLOB_NODE_PULSE_RANGE expand_range = BLOB_NODE_EXPAND_RANGE resistance_flags = LAVA_PROOF - max_spores = BLOB_NODE_MAX_SPORES ignore_syncmesh_share = TRUE @@ -57,4 +56,3 @@ if(overmind) pulse_area(overmind, claim_range, pulse_range, expand_range) reinforce_area(seconds_per_tick) - produce_spores() diff --git a/code/modules/antagonists/changeling/powers/absorb.dm b/code/modules/antagonists/changeling/powers/absorb.dm index 9a295fc8ac20..6cc9290b7f7f 100644 --- a/code/modules/antagonists/changeling/powers/absorb.dm +++ b/code/modules/antagonists/changeling/powers/absorb.dm @@ -95,31 +95,14 @@ //Some of target's recent speech, so the changeling can attempt to imitate them better. //Recent as opposed to all because rounds tend to have a LOT of text. - var/list/recent_speech = list() - var/list/say_log = list() - var/log_source = target.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 = reverse_range(reversed.Copy()) - break - - if(LAZYLEN(say_log) > LING_ABSORB_RECENT_SPEECH) - recent_speech = say_log.Copy(say_log.len-LING_ABSORB_RECENT_SPEECH+1,0) //0 so len-LING_ARS+1 to end of list - else - for(var/spoken_memory in say_log) - if(recent_speech.len >= LING_ABSORB_RECENT_SPEECH) - break - recent_speech[spoken_memory] = splittext(say_log[spoken_memory], "\"", 1, 0, TRUE)[3] + var/list/recent_speech = target.copy_recent_speech() if(recent_speech.len) changeling.antag_memory += "Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!
" to_chat(owner, span_boldnotice("Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!")) for(var/spoken_memory in recent_speech) - changeling.antag_memory += "\"[recent_speech[spoken_memory]]\"
" - to_chat(owner, span_notice("\"[recent_speech[spoken_memory]]\"")) + changeling.antag_memory += "\"[spoken_memory]\"
" + to_chat(owner, span_notice("\"[spoken_memory]\"")) changeling.antag_memory += "We have no more knowledge of [target]'s speech patterns.
" to_chat(owner, span_boldnotice("We have no more knowledge of [target]'s speech patterns.")) diff --git a/code/modules/antagonists/changeling/powers/lesserform.dm b/code/modules/antagonists/changeling/powers/lesserform.dm index f4aab1c89687..854234af965f 100644 --- a/code/modules/antagonists/changeling/powers/lesserform.dm +++ b/code/modules/antagonists/changeling/powers/lesserform.dm @@ -20,7 +20,7 @@ //Transform into a monkey. /datum/action/changeling/lesserform/sting_action(mob/living/carbon/human/user) - if(!user || user.notransform) + if(!user || HAS_TRAIT(user, TRAIT_NO_TRANSFORM)) return FALSE ..() return ismonkey(user) ? unmonkey(user) : become_monkey(user) diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm index 97b9b485257d..91b31733dc77 100644 --- a/code/modules/antagonists/changeling/powers/mutations.dm +++ b/code/modules/antagonists/changeling/powers/mutations.dm @@ -371,53 +371,87 @@ playsound(get_turf(H),I.hitsound,75,TRUE) return -/obj/projectile/tentacle/on_hit(atom/target, blocked = FALSE) - var/mob/living/carbon/human/H = firer +/obj/projectile/tentacle/on_hit(atom/movable/target, blocked = 0, pierce_hit) + if(!isliving(firer) || !ismovable(target)) + return ..() + if(blocked >= 100) return BULLET_ACT_BLOCK - if(isitem(target)) - var/obj/item/I = target - if(!I.anchored) - to_chat(firer, span_notice("You pull [I] towards yourself.")) - H.throw_mode_on(THROW_MODE_TOGGLE) - I.throw_at(H, 10, 2) - . = BULLET_ACT_HIT - - else if(isliving(target)) - var/mob/living/L = target - if(!L.anchored && !L.throwing)//avoid double hits - if(iscarbon(L)) - var/mob/living/carbon/C = L - var/firer_istate = TRUE - var/mob/living/living_shooter = firer - if(istype(living_shooter)) - firer_istate = (living_shooter.istate & ISTATE_HARM) - if(fire_modifiers && fire_modifiers["right"]) - var/obj/item/I = C.get_active_held_item() - if(I) - if(C.dropItemToGround(I)) - C.visible_message(span_danger("[I] is yanked off [C]'s hand by [src]!"),span_userdanger("A tentacle pulls [I] away from you!")) - on_hit(I) //grab the item as if you had hit it directly with the tentacle - return BULLET_ACT_HIT - else - to_chat(firer, span_warning("You can't seem to pry [I] off [C]'s hands!")) - return BULLET_ACT_BLOCK - else - to_chat(firer, span_danger("[C] has nothing in hand to disarm!")) - return BULLET_ACT_HIT - if(firer_istate) - C.visible_message(span_danger("[L] is thrown towards [H] by a tentacle!"),span_userdanger("A tentacle grabs you and throws you towards [H]!")) - C.throw_at(get_step_towards(H,C), 8, 2, H, TRUE, TRUE, callback=CALLBACK(src, PROC_REF(tentacle_grab), H, C)) - return BULLET_ACT_HIT - else - C.visible_message(span_danger("[L] is grabbed by [H]'s tentacle!"),span_userdanger("A tentacle grabs you and pulls you towards [H]!")) - C.throw_at(get_step_towards(H,C), 8, 2, H, TRUE, TRUE) - return BULLET_ACT_HIT - - else - L.visible_message(span_danger("[L] is pulled by [H]'s tentacle!"),span_userdanger("A tentacle grabs you and pulls you towards [H]!")) - L.throw_at(get_step_towards(H,L), 8, 2) - . = BULLET_ACT_HIT + + var/mob/living/ling = firer + if(isitem(target) && iscarbon(ling)) + var/obj/item/catching = target + if(catching.anchored) + return BULLET_ACT_BLOCK + + var/mob/living/carbon/carbon_ling = ling + to_chat(carbon_ling, span_notice("You pull [catching] towards yourself.")) + carbon_ling.throw_mode_on(THROW_MODE_TOGGLE) + catching.throw_at( + target = carbon_ling, + range = 10, + speed = 2, + thrower = carbon_ling, + diagonals_first = TRUE, + callback = CALLBACK(src, PROC_REF(reset_throw), carbon_ling), + gentle = TRUE, + ) + return BULLET_ACT_HIT + + . = ..() + if(. != BULLET_ACT_HIT) + return . + var/mob/living/victim = target + if(!isliving(victim) || target.anchored || victim.throwing) + return BULLET_ACT_BLOCK + + if(!iscarbon(victim) || !ishuman(ling) || !(ling.istate & ISTATE_HARM)) + victim.visible_message( + span_danger("[victim] is grabbed by [ling]'s [src]]!"), + span_userdanger("\A [src] grabs you and pulls you towards [ling]!"), + ) + victim.throw_at( + target = get_step_towards(ling, victim), + range = 8, + speed = 2, + thrower = ling, + diagonals_first = TRUE, + gentle = TRUE, + ) + return BULLET_ACT_HIT + + if(LAZYACCESS(fire_modifiers, RIGHT_CLICK)) + var/obj/item/stealing = victim.get_active_held_item() + if(!isnull(stealing)) + if(victim.dropItemToGround(stealing)) + victim.visible_message( + span_danger("[stealing] is yanked off [victim]'s hand by [src]!"), + span_userdanger("\A [src] pulls [stealing] away from you!"), + ) + return on_hit(stealing) //grab the item as if you had hit it directly with the tentacle + + to_chat(ling, span_warning("You can't seem to pry [stealing] off [victim]'s hands!")) + return BULLET_ACT_BLOCK + + to_chat(ling, span_danger("[victim] has nothing in hand to disarm!")) + return BULLET_ACT_HIT + + if(ling.istate & ISTATE_HARM) + victim.visible_message( + span_danger("[victim] is thrown towards [ling] by \a [src]!"), + span_userdanger("\A [src] grabs you and throws you towards [ling]!"), + ) + victim.throw_at( + target = get_step_towards(ling, victim), + range = 8, + speed = 2, + thrower = ling, + diagonals_first = TRUE, + callback = CALLBACK(src, PROC_REF(tentacle_grab), ling, victim), + gentle = TRUE, + ) + + return BULLET_ACT_HIT /obj/projectile/tentacle/Destroy() qdel(chain) diff --git a/code/modules/antagonists/changeling/powers/panacea.dm b/code/modules/antagonists/changeling/powers/panacea.dm index 5c3bcd6da77d..d72d5dede99e 100644 --- a/code/modules/antagonists/changeling/powers/panacea.dm +++ b/code/modules/antagonists/changeling/powers/panacea.dm @@ -13,7 +13,9 @@ ..() var/list/bad_organs = list( user.get_organ_by_type(/obj/item/organ/internal/body_egg), - user.get_organ_by_type(/obj/item/organ/internal/zombie_infection)) + user.get_organ_by_type(/obj/item/organ/internal/legion_tumour), + user.get_organ_by_type(/obj/item/organ/internal/zombie_infection), + ) for(var/o in bad_organs) var/obj/item/organ/O = o diff --git a/code/modules/antagonists/cult/cult_bastard_sword.dm b/code/modules/antagonists/cult/cult_bastard_sword.dm index 5ae594207db7..9625e1c9ef4f 100644 --- a/code/modules/antagonists/cult/cult_bastard_sword.dm +++ b/code/modules/antagonists/cult/cult_bastard_sword.dm @@ -32,7 +32,7 @@ set_light(4) AddComponent(/datum/component/butchering, 50, 80) AddComponent(/datum/component/two_handed, require_twohands = TRUE) - AddComponent(/datum/component/soul_stealer) + AddComponent(/datum/component/soul_stealer, soulstone_type = /obj/item/soulstone) AddComponent( \ /datum/component/spin2win, \ spin_cooldown_time = 25 SECONDS, \ @@ -79,7 +79,7 @@ to_chat(user, span_cultlarge("\"You cling to the Forgotten Gods, as if you're more than their pawn.\"")) to_chat(user, span_userdanger("A horrible force yanks at your arm!")) user.emote("scream") - user.apply_damage(30, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + user.apply_damage(30, BRUTE, pick(GLOB.arm_zones)) user.dropItemToGround(src, TRUE) user.Paralyze(50) return diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index df3c9e3406bc..0c8fb569e0e8 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -100,7 +100,7 @@ Striking a noncultist, however, will tear their flesh."} span_cultlarge("\"You shouldn't play with sharp things. You'll poke someone's eye out.\"")) if(ishuman(user)) var/mob/living/carbon/human/miscreant = user - miscreant.apply_damage(rand(force/2, force), BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + miscreant.apply_damage(rand(force/2, force), BRUTE, pick(GLOB.arm_zones)) else user.adjustBruteLoss(rand(force/2,force)) return diff --git a/code/modules/antagonists/cult/cult_structures.dm b/code/modules/antagonists/cult/cult_structures.dm index 38bf78beaa3e..e2f676948b3f 100644 --- a/code/modules/antagonists/cult/cult_structures.dm +++ b/code/modules/antagonists/cult/cult_structures.dm @@ -37,26 +37,6 @@ icon_state = "[initial(icon_state)][anchored ? "" : "_off"]" return ..() -/obj/structure/destructible/cult/attack_animal(mob/living/simple_animal/user, list/modifiers) - if(!isconstruct(user)) - return ..() - - var/mob/living/simple_animal/hostile/construct/healer = user - if(!healer.can_repair) - return ..() - - if(atom_integrity >= max_integrity) - to_chat(user, span_cult("You cannot repair [src], as it's undamaged!")) - return - - user.changeNext_move(CLICK_CD_MELEE) - atom_integrity = min(max_integrity, atom_integrity + 5) - Beam(user, icon_state = "sendbeam", time = 0.4 SECONDS) - user.visible_message( - span_danger("[user] repairs [src]."), - span_cult("You repair [src], leaving it at [round(atom_integrity * 100 / max_integrity)]% stability.") - ) - /* * Proc for use with the concealing spell. Hides the building (makes it invisible). */ diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 20583574e772..8c0934812073 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -112,7 +112,7 @@ Runes can either be invoked by one's self or with many different cultists. Each /obj/effect/rune/attack_animal(mob/living/simple_animal/user, list/modifiers) if(isshade(user) || isconstruct(user)) - if(istype(user, /mob/living/simple_animal/hostile/construct/wraith/angelic) || istype(user, /mob/living/simple_animal/hostile/construct/juggernaut/angelic) || istype(user, /mob/living/simple_animal/hostile/construct/artificer/angelic)) + if(HAS_TRAIT(user, TRAIT_ANGELIC)) to_chat(user, span_warning("You purge the rune!")) qdel(src) else if(construct_invoke || !IS_CULTIST(user)) //if you're not a cult construct we want the normal fail message @@ -722,7 +722,7 @@ structure_check() searches for nearby cultist structures required for the invoca barrier.Toggle() if(iscarbon(user)) var/mob/living/carbon/C = user - C.apply_damage(2, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + C.apply_damage(2, BRUTE, pick(GLOB.arm_zones)) //Rite of Joined Souls: Summons a single cultist. /obj/effect/rune/summon diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index 5d9675e1ac92..55c3cead1a7c 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -509,7 +509,7 @@ // Fade in the summon while the ghost poll is ongoing. // Also don't let them mess with the summon while waiting summoned.alpha = 0 - summoned.notransform = TRUE + ADD_TRAIT(summoned, TRAIT_NO_TRANSFORM, REF(src)) summoned.move_resist = MOVE_FORCE_OVERPOWERING animate(summoned, 10 SECONDS, alpha = 155) @@ -524,7 +524,7 @@ var/mob/dead/observer/picked_candidate = pick(candidates) // Ok let's make them an interactable mob now, since we got a ghost summoned.alpha = 255 - summoned.notransform = FALSE + REMOVE_TRAIT(summoned, TRAIT_NO_TRANSFORM, REF(src)) summoned.move_resist = initial(summoned.move_resist) summoned.ghostize(FALSE) diff --git a/code/modules/antagonists/heretic/heretic_living_heart.dm b/code/modules/antagonists/heretic/heretic_living_heart.dm index 063303c49b6a..aa42a67172ab 100644 --- a/code/modules/antagonists/heretic/heretic_living_heart.dm +++ b/code/modules/antagonists/heretic/heretic_living_heart.dm @@ -99,7 +99,7 @@ return TRUE -/datum/action/cooldown/track_target/Trigger(trigger_flags) +/datum/action/cooldown/track_target/Trigger(trigger_flags, atom/target) right_clicked = !!(trigger_flags & TRIGGER_SECONDARY_ACTION) return ..() diff --git a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm index b6b7fa6f77b6..02040dae989f 100644 --- a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm @@ -236,7 +236,7 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targetting/attack/star_gazer + /datum/pet_command/point_targeting/attack/star_gazer ) /datum/heretic_knowledge/ultimate/cosmic_final/is_valid_sacrifice(mob/living/carbon/human/sacrifice) diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm index 76324e83a08c..84652904534f 100644 --- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm @@ -250,7 +250,7 @@ /obj/effect/decal/cleanable/blood = 1, /obj/item/bodypart/arm/left = 1, ) - mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/raw_prophet + mob_to_summon = /mob/living/basic/heretic_summon/raw_prophet cost = 1 route = PATH_FLESH @@ -268,8 +268,7 @@ var/mob/living/carbon/carbon_target = target var/obj/item/bodypart/bodypart = pick(carbon_target.bodyparts) - var/datum/wound/slash/severe/crit_wound = new() - crit_wound.apply_wound(bodypart, attack_direction = get_dir(source, target)) + carbon_target.cause_wound_of_type_and_severity(WOUND_SLASH, bodypart, WOUND_SEVERITY_SEVERE, WOUND_SEVERITY_CRITICAL) /datum/heretic_knowledge/summon/stalker name = "Lonely Ritual" @@ -289,7 +288,7 @@ /obj/item/pen = 1, /obj/item/paper = 1, ) - mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/stalker + mob_to_summon = /mob/living/basic/heretic_summon/stalker cost = 1 route = PATH_FLESH diff --git a/code/modules/antagonists/heretic/knowledge/side_ash_flesh.dm b/code/modules/antagonists/heretic/knowledge/side_ash_flesh.dm index 384076b8cf6f..a593e61e8edc 100644 --- a/code/modules/antagonists/heretic/knowledge/side_ash_flesh.dm +++ b/code/modules/antagonists/heretic/knowledge/side_ash_flesh.dm @@ -71,7 +71,7 @@ /obj/item/bodypart/head = 1, /obj/item/book = 1, ) - mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/ash_spirit + mob_to_summon = /mob/living/basic/heretic_summon/ash_spirit cost = 1 route = PATH_SIDE diff --git a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm index 9b7149716727..313412c030dc 100644 --- a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm +++ b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm @@ -66,7 +66,7 @@ /obj/item/book = 1, /obj/item/bodypart/head = 1, ) - mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/rust_spirit + mob_to_summon = /mob/living/basic/heretic_summon/rust_walker cost = 1 route = PATH_SIDE diff --git a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm index 6b380eb2f0de..a548646253ae 100644 --- a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm +++ b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm @@ -159,4 +159,4 @@ ) cost = 1 route = PATH_SIDE - mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror + mob_to_summon = /mob/living/basic/heretic_summon/maid_in_the_mirror diff --git a/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm new file mode 100644 index 000000000000..f1d6de56e399 --- /dev/null +++ b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm @@ -0,0 +1,32 @@ +// Given to ascended knock heretics, is a form of shapeshift that can turn into all 4 common heretic summons, and is not limited to 1 selection. +/datum/action/cooldown/spell/shapeshift/eldritch/ascension + name = "Ascended Shapechange" + desc = "A spell that allows you to take on the form of another eldritch creature, gaining their abilities. \ + You can change your choice at any time, and if your form dies, you dont die." + cooldown_time = 20 SECONDS + die_with_shapeshifted_form = FALSE + possible_shapes = list( + /mob/living/basic/heretic_summon/ash_spirit, + /mob/living/basic/heretic_summon/raw_prophet/ascended, + /mob/living/basic/heretic_summon/rust_walker, + /mob/living/basic/heretic_summon/stalker, + ) + +/datum/action/cooldown/spell/shapeshift/eldritch/ascension/do_shapeshift(mob/living/caster) + . = ..() + if(!.) + return + //buff our forms so this ascension ability isnt shit + playsound(caster, 'sound/magic/demon_consume.ogg', 50, TRUE) + var/mob/living/monster = . + monster.AddComponent(/datum/component/seethrough_mob) + monster.maxHealth *= 1.5 + monster.health = monster.maxHealth + monster.melee_damage_lower = max((monster.melee_damage_lower * 2), 40) + monster.melee_damage_upper = monster.melee_damage_upper / 2 + monster.transform *= 1.5 + monster.AddElement(/datum/element/wall_smasher, strength_flag = ENVIRONMENT_SMASH_RWALLS) + +/datum/action/cooldown/spell/shapeshift/eldritch/ascension/do_unshapeshift(mob/living/caster) + . = ..() + shapeshift_type = null //pick another loser diff --git a/code/modules/antagonists/heretic/magic/blood_cleave.dm b/code/modules/antagonists/heretic/magic/blood_cleave.dm index eca27e55566f..d5317f23e344 100644 --- a/code/modules/antagonists/heretic/magic/blood_cleave.dm +++ b/code/modules/antagonists/heretic/magic/blood_cleave.dm @@ -19,7 +19,7 @@ /// The radius of the cleave effect var/cleave_radius = 1 /// What type of wound we apply - var/wound_type = /datum/wound/slash/critical/cleave + var/wound_type = /datum/wound/slash/flesh/critical/cleave /datum/action/cooldown/spell/pointed/cleave/is_valid_target(atom/cast_on) return ..() && ishuman(cast_on) @@ -45,7 +45,7 @@ ) var/obj/item/bodypart/bodypart = pick(victim.bodyparts) - var/datum/wound/slash/crit_wound = new wound_type() + var/datum/wound/slash/flesh/crit_wound = new wound_type() crit_wound.apply_wound(bodypart) victim.apply_damage(20, BURN, wound_bonus = CANT_WOUND) @@ -56,7 +56,7 @@ /datum/action/cooldown/spell/pointed/cleave/long name = "Lesser Cleave" cooldown_time = 60 SECONDS - wound_type = /datum/wound/slash/severe + wound_type = /datum/wound/slash/flesh/severe /obj/effect/temp_visual/cleave icon = 'icons/effects/eldritch.dmi' diff --git a/code/modules/antagonists/heretic/magic/flesh_ascension.dm b/code/modules/antagonists/heretic/magic/flesh_ascension.dm index cb9ab63e031e..d086c1127fcf 100644 --- a/code/modules/antagonists/heretic/magic/flesh_ascension.dm +++ b/code/modules/antagonists/heretic/magic/flesh_ascension.dm @@ -13,7 +13,7 @@ invocation_type = INVOCATION_SHOUT spell_requirements = NONE - possible_shapes = list(/mob/living/simple_animal/hostile/heretic_summon/armsy/prime) + possible_shapes = list(/mob/living/basic/heretic_summon/armsy) /// The length of our new wormy when we shed. var/segment_length = 10 @@ -35,32 +35,11 @@ return ..() -/datum/action/cooldown/spell/shapeshift/shed_human_form/do_unshapeshift(mob/living/simple_animal/hostile/heretic_summon/armsy/caster) +/datum/action/cooldown/spell/shapeshift/shed_human_form/do_unshapeshift(mob/living/basic/heretic_summon/armsy/caster) if(istype(caster)) - segment_length = caster.get_length() + segment_length = caster.get_length() - 1 // Don't count the head return ..() /datum/action/cooldown/spell/shapeshift/shed_human_form/create_shapeshift_mob(atom/loc) return new shapeshift_type(loc, TRUE, segment_length) - -/datum/action/cooldown/spell/worm_contract - name = "Force Contract" - desc = "Forces your body to contract onto a single tile." - background_icon_state = "bg_heretic" - overlay_icon_state = "bg_heretic_border" - button_icon = 'icons/mob/actions/actions_ecult.dmi' - button_icon_state = "worm_contract" - - school = SCHOOL_FORBIDDEN - cooldown_time = 30 SECONDS - - invocation_type = INVOCATION_NONE - spell_requirements = NONE - -/datum/action/cooldown/spell/worm_contract/is_valid_target(atom/cast_on) - return istype(cast_on, /mob/living/simple_animal/hostile/heretic_summon/armsy) - -/datum/action/cooldown/spell/worm_contract/cast(mob/living/simple_animal/hostile/heretic_summon/armsy/cast_on) - . = ..() - cast_on.contract_next_chain_into_single_tile() diff --git a/code/modules/antagonists/heretic/magic/furious_steel.dm b/code/modules/antagonists/heretic/magic/furious_steel.dm index f4e8596fbba9..9d9dcc29c4a9 100644 --- a/code/modules/antagonists/heretic/magic/furious_steel.dm +++ b/code/modules/antagonists/heretic/magic/furious_steel.dm @@ -43,12 +43,12 @@ unset_click_ability(source, refund_cooldown = TRUE) -/datum/action/cooldown/spell/pointed/projectile/furious_steel/InterceptClickOn(mob/living/caller, params, atom/click_target) +/datum/action/cooldown/spell/pointed/projectile/furious_steel/InterceptClickOn(mob/living/caller, params, atom/target) // Let the caster prioritize using items like guns over blade casts if(caller.get_active_held_item()) return FALSE // Let the caster prioritize melee attacks like punches and shoves over blade casts - if(get_dist(caller, click_target) <= 1) + if(get_dist(caller, target) <= 1) return FALSE return ..() diff --git a/code/modules/antagonists/heretic/magic/shadow_cloak.dm b/code/modules/antagonists/heretic/magic/shadow_cloak.dm index af7bf9d94937..9312c5588161 100644 --- a/code/modules/antagonists/heretic/magic/shadow_cloak.dm +++ b/code/modules/antagonists/heretic/magic/shadow_cloak.dm @@ -193,7 +193,7 @@ qdel(src) /// Signal proc for [COMSIG_MOB_APPLY_DAMAGE], being damaged past a threshold will roll a chance to stop the effect -/datum/status_effect/shadow_cloak/proc/on_damaged(datum/source, damage, damagetype) +/datum/status_effect/shadow_cloak/proc/on_damaged(datum/source, damage, damagetype, ...) SIGNAL_HANDLER // Stam damage is generally bursty, so we'll half it diff --git a/code/modules/antagonists/heretic/magic/space_crawl.dm b/code/modules/antagonists/heretic/magic/space_crawl.dm index 56fafdf86fb8..b88dc88b9943 100644 --- a/code/modules/antagonists/heretic/magic/space_crawl.dm +++ b/code/modules/antagonists/heretic/magic/space_crawl.dm @@ -60,10 +60,10 @@ */ /datum/action/cooldown/spell/jaunt/space_crawl/proc/try_enter_jaunt(turf/our_turf, mob/living/jaunter) // Begin the jaunt - jaunter.notransform = TRUE + ADD_TRAIT(jaunter, TRAIT_NO_TRANSFORM, REF(src)) var/obj/effect/dummy/phased_mob/holder = enter_jaunt(jaunter, our_turf) - if(!holder) - jaunter.notransform = FALSE + if(isnull(holder)) + REMOVE_TRAIT(jaunter, TRAIT_NO_TRANSFORM, REF(src)) return FALSE RegisterSignal(holder, COMSIG_MOVABLE_MOVED, PROC_REF(update_status_on_signal)) @@ -82,14 +82,14 @@ new /obj/effect/temp_visual/space_explosion(our_turf) jaunter.extinguish_mob() - jaunter.notransform = FALSE + REMOVE_TRAIT(jaunter, TRAIT_NO_TRANSFORM, REF(src)) return TRUE /** * Attempts to Exit the passed space or misc turf. */ /datum/action/cooldown/spell/jaunt/space_crawl/proc/try_exit_jaunt(turf/our_turf, mob/living/jaunter) - if(jaunter.notransform) + if(HAS_TRAIT_FROM(jaunter, TRAIT_NO_TRANSFORM, REF(src))) to_chat(jaunter, span_warning("You cannot exit yet!!")) return FALSE diff --git a/code/modules/antagonists/heretic/magic/star_blast.dm b/code/modules/antagonists/heretic/magic/star_blast.dm index 297e24455e22..212e90535d6c 100644 --- a/code/modules/antagonists/heretic/magic/star_blast.dm +++ b/code/modules/antagonists/heretic/magic/star_blast.dm @@ -37,7 +37,7 @@ . = ..() AddElement(/datum/element/effect_trail, /obj/effect/forcefield/cosmic_field/fast) -/obj/projectile/magic/star_ball/on_hit(atom/target, blocked = FALSE, pierce_hit) +/obj/projectile/magic/star_ball/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() var/mob/living/cast_on = firer for(var/mob/living/nearby_mob in range(star_mark_range, target)) diff --git a/code/modules/antagonists/heretic/status_effects/buffs.dm b/code/modules/antagonists/heretic/status_effects/buffs.dm index 128adb6e016e..a7bf076e1c65 100644 --- a/code/modules/antagonists/heretic/status_effects/buffs.dm +++ b/code/modules/antagonists/heretic/status_effects/buffs.dm @@ -74,7 +74,8 @@ heal_amt = 3 if(WOUND_SEVERITY_CRITICAL) heal_amt = 6 - if(wound.wound_type == WOUND_BURN) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound.type] + if (pregen_data.wounding_types_valid(list(WOUND_BURN))) carbie.adjustFireLoss(-heal_amt) else carbie.adjustBruteLoss(-heal_amt) diff --git a/code/modules/antagonists/heretic/status_effects/mark_effects.dm b/code/modules/antagonists/heretic/status_effects/mark_effects.dm index 55bdacffb947..057633688a18 100644 --- a/code/modules/antagonists/heretic/status_effects/mark_effects.dm +++ b/code/modules/antagonists/heretic/status_effects/mark_effects.dm @@ -61,8 +61,7 @@ if(ishuman(owner)) var/mob/living/carbon/human/human_owner = owner var/obj/item/bodypart/bodypart = pick(human_owner.bodyparts) - var/datum/wound/slash/severe/crit_wound = new() - crit_wound.apply_wound(bodypart) + human_owner.cause_wound_of_type_and_severity(WOUND_SLASH, bodypart, WOUND_SEVERITY_SEVERE) return ..() diff --git a/code/modules/antagonists/heretic/structures/knock_final.dm b/code/modules/antagonists/heretic/structures/knock_final.dm new file mode 100644 index 000000000000..da42c2c00945 --- /dev/null +++ b/code/modules/antagonists/heretic/structures/knock_final.dm @@ -0,0 +1,114 @@ +/obj/structure/knock_tear + name = "???" + desc = "It stares back. Theres no reason to remain. Run." + max_integrity = INFINITE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + icon = 'icons/obj/anomaly.dmi' + icon_state = "bhole3" + color = COLOR_PURPLE + light_color = COLOR_PURPLE + light_outer_range = 20 + anchored = TRUE + density = FALSE + layer = HIGH_PIPE_LAYER //0.01 above sigil layer used by heretic runes + move_resist = INFINITY + /// Who is our daddy? + var/datum/mind/ascendee + /// True if we're currently checking for ghost opinions + var/gathering_candidates = TRUE + ///a static list of heretic summons we cam create, automatically populated from heretic monster subtypes + var/static/list/monster_types + /// A static list of heretic summons which we should not create + var/static/list/monster_types_blacklist = list( + /mob/living/basic/heretic_summon/armsy, + /mob/living/basic/heretic_summon/star_gazer, + ) + +/obj/structure/knock_tear/Initialize(mapload, datum/mind/ascendant_mind) + . = ..() + transform *= 3 + if(isnull(monster_types)) + monster_types = subtypesof(/mob/living/basic/heretic_summon) - monster_types_blacklist + if(!isnull(ascendant_mind)) + ascendee = ascendant_mind + RegisterSignals(ascendant_mind.current, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING), PROC_REF(end_madness)) + SSpoints_of_interest.make_point_of_interest(src) + INVOKE_ASYNC(src, PROC_REF(poll_ghosts)) + +/// Ask ghosts if they want to make some noise +/obj/structure/knock_tear/proc/poll_ghosts() + var/list/candidates = poll_ghost_candidates("Would you like to be a random eldritch monster attacking the crew?", ROLE_SENTIENCE, ROLE_SENTIENCE, 10 SECONDS, POLL_IGNORE_HERETIC_MONSTER) + while(LAZYLEN(candidates)) + var/mob/dead/observer/candidate = pick_n_take(candidates) + ghost_to_monster(candidate, should_ask = FALSE) + gathering_candidates = FALSE + +/// Destroy the rift if you kill the heretic +/obj/structure/knock_tear/proc/end_madness(datum/former_master) + SIGNAL_HANDLER + var/turf/our_turf = get_turf(src) + playsound(our_turf, 'sound/magic/castsummon.ogg', vol = 100, vary = TRUE) + visible_message(span_boldwarning("The rip in space spasms and disappears!")) + UnregisterSignal(former_master, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING)) // Just in case they die THEN delete + new /obj/effect/temp_visual/destabilising_tear(our_turf) + qdel(src) + +/obj/structure/knock_tear/attack_ghost(mob/user) + . = ..() + if(. || gathering_candidates) + return + ghost_to_monster(user) + +/obj/structure/knock_tear/examine(mob/user) + . = ..() + if (!isobserver(user) || gathering_candidates) + return + . += span_notice("You can use this to enter the world as a foul monster.") + +/// Turn a ghost into an 'orrible beast +/obj/structure/knock_tear/proc/ghost_to_monster(mob/dead/observer/user, should_ask = TRUE) + if(should_ask) + var/ask = tgui_alert(user, "Become a monster?", "Ascended Rift", list("Yes", "No")) + if(ask != "Yes" || QDELETED(src) || QDELETED(user)) + return FALSE + var/monster_type = pick(monster_types) + var/mob/living/monster = new monster_type(loc) + monster.key = user.key + monster.set_name() + var/datum/antagonist/heretic_monster/woohoo_free_antag = new(src) + monster.mind.add_antag_datum(woohoo_free_antag) + if(ascendee) + monster.faction = ascendee.current.faction + woohoo_free_antag.set_owner(ascendee) + var/datum/objective/kill_all_your_friends = new() + kill_all_your_friends.owner = monster.mind + kill_all_your_friends.explanation_text = "The station's crew must be culled." + kill_all_your_friends.completed = TRUE + woohoo_free_antag.objectives += kill_all_your_friends + +/obj/structure/knock_tear/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) + return FALSE + +/obj/structure/knock_tear/Destroy(force) + if(ascendee) + ascendee = null + return ..() + +/obj/effect/temp_visual/destabilising_tear + name = "destabilised tear" + icon = 'icons/obj/anomaly.dmi' + icon_state = "bhole3" + color = COLOR_PURPLE + light_color = COLOR_PURPLE + light_outer_range = 20 + layer = HIGH_PIPE_LAYER + duration = 1 SECONDS + +/obj/effect/temp_visual/destabilising_tear/Initialize(mapload) + . = ..() + transform *= 3 + animate(src, transform = matrix().Scale(3.2), time = 0.15 SECONDS) + animate(transform = matrix().Scale(0.2), time = 0.75 SECONDS) + animate(transform = matrix().Scale(3, 0), time = 0.1 SECONDS) + animate(src, color = COLOR_WHITE, time = 0.25 SECONDS, flags = ANIMATION_PARALLEL) + animate(color = COLOR_PURPLE, time = 0.3 SECONDS) diff --git a/code/modules/antagonists/malf_ai/malf_ai_modules.dm b/code/modules/antagonists/malf_ai/malf_ai_modules.dm index feba37341305..4a2e82861d2c 100644 --- a/code/modules/antagonists/malf_ai/malf_ai_modules.dm +++ b/code/modules/antagonists/malf_ai/malf_ai_modules.dm @@ -1,6 +1,16 @@ #define DEFAULT_DOOMSDAY_TIMER 4500 #define DOOMSDAY_ANNOUNCE_INTERVAL 600 +#define VENDOR_TIPPING_USES 8 +#define MALF_VENDOR_TIPPING_TIME 0.5 SECONDS //within human reaction time +#define MALF_VENDOR_TIPPING_CRIT_CHANCE 100 //percent - guaranteed + +#define MALF_AI_ROLL_TIME 0.5 SECONDS +#define MALF_AI_ROLL_COOLDOWN 1 SECONDS + MALF_AI_ROLL_TIME +#define MALF_AI_ROLL_DAMAGE 75 +#define MALF_AI_ROLL_CRIT_CHANCE 5 //percent +#define MALF_AI_ROLL_MAX_DISTANCE 1 //anything further away than this, and the roll will fail + GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( /obj/machinery/field/containment, /obj/machinery/power/supermatter_crystal, @@ -1010,3 +1020,13 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module)) #undef DEFAULT_DOOMSDAY_TIMER #undef DOOMSDAY_ANNOUNCE_INTERVAL + +#undef VENDOR_TIPPING_USES +#undef MALF_VENDOR_TIPPING_TIME +#undef MALF_VENDOR_TIPPING_CRIT_CHANCE + +#undef MALF_AI_ROLL_COOLDOWN +#undef MALF_AI_ROLL_TIME +#undef MALF_AI_ROLL_DAMAGE +#undef MALF_AI_ROLL_CRIT_CHANCE +#undef MALF_AI_ROLL_MAX_DISTANCE diff --git a/code/modules/antagonists/nightmare/nightmare_organs.dm b/code/modules/antagonists/nightmare/nightmare_organs.dm index cf1142ee2fa2..1e07ddc69417 100644 --- a/code/modules/antagonists/nightmare/nightmare_organs.dm +++ b/code/modules/antagonists/nightmare/nightmare_organs.dm @@ -27,10 +27,25 @@ terrorize_spell = new(src) terrorize_spell.Grant(brain_owner) + RegisterSignal(brain_owner, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(dodge_bullets)) + /obj/item/organ/internal/brain/shadow/nightmare/on_remove(mob/living/carbon/brain_owner) . = ..() QDEL_NULL(our_jaunt) QDEL_NULL(terrorize_spell) + UnregisterSignal(brain_owner, COMSIG_ATOM_PRE_BULLET_ACT) + +/obj/item/organ/internal/brain/shadow/nightmare/proc/dodge_bullets(mob/living/carbon/human/source, obj/projectile/hitting_projectile, def_zone) + SIGNAL_HANDLER + var/turf/dodge_turf = source.loc + if(!istype(dodge_turf) || dodge_turf.get_lumcount() >= SHADOW_SPECIES_LIGHT_THRESHOLD) + return NONE + source.visible_message( + span_danger("[source] dances in the shadows, evading [hitting_projectile]!"), + span_danger("You evade [hitting_projectile] with the cover of darkness!"), + ) + playsound(source, SFX_BULLET_MISS, 75, TRUE) + return COMPONENT_BULLET_PIERCED /obj/item/organ/internal/heart/nightmare name = "heart of darkness" diff --git a/code/modules/antagonists/nightmare/nightmare_species.dm b/code/modules/antagonists/nightmare/nightmare_species.dm index 9fc44cb434cb..fcfc07af378a 100644 --- a/code/modules/antagonists/nightmare/nightmare_species.dm +++ b/code/modules/antagonists/nightmare/nightmare_species.dm @@ -41,15 +41,5 @@ C.fully_replace_character_name(null, pick(GLOB.nightmare_names)) C.set_safe_hunger_level() -/datum/species/shadow/nightmare/bullet_act(obj/projectile/P, mob/living/carbon/human/H) - var/turf/T = H.loc - if(istype(T)) - var/light_amount = T.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) - H.visible_message(span_danger("[H] dances in the shadows, evading [P]!")) - playsound(T, SFX_BULLET_MISS, 75, TRUE) - return BULLET_ACT_FORCE_PIERCE - return ..() - /datum/species/shadow/nightmare/check_roundstart_eligible() return FALSE diff --git a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm index 7067fb0ce6f3..98894adfc0a5 100644 --- a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm +++ b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm @@ -381,13 +381,13 @@ /datum/export/pirate/parrot cost = 2000 unit_name = "alive parrot" - export_types = list(/mob/living/simple_animal/parrot) + export_types = list(/mob/living/basic/parrot) /datum/export/pirate/parrot/find_loot() - for(var/mob/living/simple_animal/parrot/P in GLOB.alive_mob_list) - var/turf/T = get_turf(P) - if(T && is_station_level(T.z)) - return P + for(var/mob/living/basic/parrot/current_parrot in GLOB.alive_mob_list) + var/turf/parrot_turf = get_turf(current_parrot) + if(parrot_turf && is_station_level(parrot_turf.z)) + return current_parrot /datum/export/pirate/cash cost = 1 diff --git a/code/modules/antagonists/space_dragon/space_dragon.dm b/code/modules/antagonists/space_dragon/space_dragon.dm index 7ebcd4dde0c2..d32e62175d5a 100644 --- a/code/modules/antagonists/space_dragon/space_dragon.dm +++ b/code/modules/antagonists/space_dragon/space_dragon.dm @@ -111,6 +111,7 @@ if(objective_complete) return rifts_charged = 0 + ADD_TRAIT(owner.current, TRAIT_RIFT_FAILURE, REF(src)) owner.current.add_movespeed_modifier(/datum/movespeed_modifier/dragon_depression) riftTimer = -1 SEND_SOUND(owner.current, sound('sound/vehicles/rocketlaunch.ogg')) diff --git a/code/modules/antagonists/traitor/objectives/demoralise_assault.dm b/code/modules/antagonists/traitor/objectives/demoralise_assault.dm new file mode 100644 index 000000000000..f8cbd6a6e035 --- /dev/null +++ b/code/modules/antagonists/traitor/objectives/demoralise_assault.dm @@ -0,0 +1,133 @@ +/datum/traitor_objective_category/demoralise + name = "Demoralise Crew" + objectives = list( + /datum/traitor_objective/target_player/assault = 1, + /datum/traitor_objective/destroy_item/demoralise = 1, + ) + weight = OBJECTIVE_WEIGHT_UNLIKELY + +/datum/traitor_objective/target_player/assault + name = "Assault %TARGET% the %JOB TITLE%" + description = "%TARGET% has been identified as a potential future agent. \ + Pick a fight and give them a good beating. \ + %COUNT% hits should reduce their morale and have them questioning their loyalties. \ + Try not to kill them just yet, we may want to recruit them in the future." + + abstract_type = /datum/traitor_objective/target_player + duplicate_type = /datum/traitor_objective/target_player + + progression_minimum = 0 MINUTES + progression_maximum = 30 MINUTES + progression_reward = list(4 MINUTES, 8 MINUTES) + telecrystal_reward = list(0, 1) + + /// Min attacks required to pass the objective. Picked at random between this and max. + var/min_attacks_required = 2 + /// Max attacks required to pass the objective. Picked at random between this and min. + var/max_attacks_required = 5 + /// The random number picked for the number of required attacks to pass this objective. + var/attacks_required = 0 + /// Total number of successful attacks recorded. + var/attacks_inflicted = 0 + +/datum/traitor_objective/target_player/assault/on_objective_taken(mob/user) + . = ..() + + target.AddElement(/datum/element/relay_attackers) + RegisterSignal(target, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) + +/datum/traitor_objective/target_player/assault/proc/on_attacked(mob/source, mob/living/attacker, attack_flags) + SIGNAL_HANDLER + + // Only care about attacks from the objective's owner. + if(attacker != handler.owner.current) + return + + // We want some sort of damaging attack to trigger this, rather than shoves and non-lethals. + if(!(attack_flags & ATTACKER_DAMAGING_ATTACK)) + return + + attacks_inflicted++ + + if(attacks_inflicted == attacks_required) + succeed_objective() + +/datum/traitor_objective/target_player/assault/ungenerate_objective() + UnregisterSignal(target, COMSIG_ATOM_WAS_ATTACKED) + UnregisterSignal(target, COMSIG_LIVING_DEATH) + UnregisterSignal(target, COMSIG_PARENT_QDELETING) + + target = null + +/datum/traitor_objective/target_player/assault/generate_objective(datum/mind/generating_for, list/possible_duplicates) + var/list/already_targeting = list() //List of minds we're already targeting. The possible_duplicates is a list of objectives, so let's not mix things + for(var/datum/objective/task as anything in handler.primary_objectives) + if(!istype(task.target, /datum/mind)) + continue + already_targeting += task.target //Removing primary objective kill targets from the list + + var/list/possible_targets = list() + + for(var/datum/mind/possible_target as anything in get_crewmember_minds()) + if(possible_target in already_targeting) + continue + + if(possible_target == generating_for) + continue + + if(!ishuman(possible_target.current)) + continue + + if(possible_target.current.stat == DEAD) + continue + + if(possible_target.has_antag_datum(/datum/antagonist/traitor)) + continue + + possible_targets += possible_target + + for(var/datum/traitor_objective/target_player/objective as anything in possible_duplicates) + possible_targets -= objective.target?.mind + + if(generating_for.late_joiner) + var/list/all_possible_targets = possible_targets.Copy() + for(var/datum/mind/possible_target as anything in all_possible_targets) + if(!possible_target.late_joiner) + possible_targets -= possible_target + if(!possible_targets.len) + possible_targets = all_possible_targets + + if(!possible_targets.len) + return FALSE + + var/datum/mind/target_mind = pick(possible_targets) + + target = target_mind.current + replace_in_name("%TARGET%", target.real_name) + replace_in_name("%JOB TITLE%", target_mind.assigned_role.title) + + attacks_required = rand(min_attacks_required, max_attacks_required) + replace_in_name("%COUNT%", attacks_required) + + RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_target_death)) + RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(on_target_qdeleted)) + + return TRUE + +/datum/traitor_objective/target_player/assault/generate_ui_buttons(mob/user) + var/list/buttons = list() + if(attacks_required > attacks_inflicted) + buttons += add_ui_button("[attacks_required - attacks_inflicted]", "This tells you how many more times you have to attack the target player to succeed.", "hand-rock-o", "none") + return buttons + +/datum/traitor_objective/target_player/assault/proc/on_target_qdeleted() + SIGNAL_HANDLER + + //don't take an objective target of someone who is already obliterated + fail_objective() + +/datum/traitor_objective/target_player/assault/proc/on_target_death() + SIGNAL_HANDLER + + //don't take an objective target of someone who is already dead + fail_objective() diff --git a/code/modules/antagonists/traitor/objectives/demoralise_crew.dm b/code/modules/antagonists/traitor/objectives/demoralise_crew.dm deleted file mode 100644 index 4529273f5e2e..000000000000 --- a/code/modules/antagonists/traitor/objectives/demoralise_crew.dm +++ /dev/null @@ -1,53 +0,0 @@ -#define MAX_CREW_RATIO 0.33 -#define MIN_CREW_DEMORALISED 8 -#define MAX_CREW_DEMORALISED 16 - -/datum/traitor_objective_category/demoralise - name = "Demoralise Crew" - objectives = list( - /datum/traitor_objective/demoralise/poster = 2, - /datum/traitor_objective/demoralise/graffiti = 1, - ) - weight = OBJECTIVE_WEIGHT_UNLIKELY - -/datum/traitor_objective/demoralise - name = "Debug your code." - description = "If you actually get this objective someone fucked up." - - abstract_type = /datum/traitor_objective/demoralise - - /// How many 'mood events' are required? - var/demoralised_crew_required = 0 - /// How many 'mood events' have happened so far? - var/demoralised_crew_events = 0 - -/datum/traitor_objective/demoralise/can_generate_objective(datum/mind/generating_for, list/possible_duplicates) - if(length(possible_duplicates) > 0) - return FALSE - return TRUE - -/datum/traitor_objective/demoralise/generate_objective(datum/mind/generating_for, list/possible_duplicates) - demoralised_crew_required = (clamp(rand(MIN_CREW_DEMORALISED, length(get_crewmember_minds()) * MAX_CREW_RATIO), MIN_CREW_DEMORALISED, MAX_CREW_DEMORALISED)) - replace_in_name("%VIEWS%", demoralised_crew_required) - return TRUE - -/** - * Handles an event which increases your progress towards success. - * - * Arguments - * * source - Source atom of the signal. - * * victim - Mind of whoever it was you just triggered some kind of effect on. - */ -/datum/traitor_objective/demoralise/proc/on_mood_event(atom/source, datum/mind/victim) - SIGNAL_HANDLER - if (victim == handler.owner) - return - - demoralised_crew_events++ - if (demoralised_crew_events >= demoralised_crew_required) - to_chat(handler.owner, span_nicegreen("The crew look despondent. Mission accomplished.")) - succeed_objective() - -#undef MAX_CREW_RATIO -#undef MIN_CREW_DEMORALISED -#undef MAX_CREW_DEMORALISED diff --git a/code/modules/antagonists/traitor/objectives/destroy_heirloom.dm b/code/modules/antagonists/traitor/objectives/destroy_heirloom.dm index f5fab7ffc433..a93624e90c6f 100644 --- a/code/modules/antagonists/traitor/objectives/destroy_heirloom.dm +++ b/code/modules/antagonists/traitor/objectives/destroy_heirloom.dm @@ -17,7 +17,7 @@ abstract_type = /datum/traitor_objective/destroy_heirloom - /// The jobs that this objective is targetting. + /// The jobs that this objective is targeting. var/list/target_jobs /// the item we need to destroy var/obj/item/target_item diff --git a/code/modules/antagonists/traitor/objectives/destroy_item.dm b/code/modules/antagonists/traitor/objectives/destroy_item.dm index 7a4898f2b3d3..2de962ee4b97 100644 --- a/code/modules/antagonists/traitor/objectives/destroy_item.dm +++ b/code/modules/antagonists/traitor/objectives/destroy_item.dm @@ -34,6 +34,27 @@ /datum/objective_item/steal/blackbox, ) +/// Super early-game destroy objective intended to be items easily tided that the crew tends to value. +/datum/traitor_objective/destroy_item/demoralise + description = "Find %ITEM% and destroy it using any means necessary. \ + We believe this luxury item is important for crew morale. \ + Destruction of this item will help our recruitment efforts." + + progression_minimum = 0 MINUTES + progression_maximum = 10 MINUTES + progression_reward = list(4 MINUTES, 8 MINUTES) + telecrystal_reward = list(0, 1) + + possible_items = list( + /datum/objective_item/steal/traitor/rpd, + /datum/objective_item/steal/traitor/space_law, + /datum/objective_item/steal/traitor/granted_stamp, + /datum/objective_item/steal/traitor/denied_stamp, + /datum/objective_item/steal/traitor/lizard_plush, + /datum/objective_item/steal/traitor/moth_plush, + /datum/objective_item/steal/traitor/insuls, + ) + /datum/traitor_objective/destroy_item/generate_objective(datum/mind/generating_for, list/possible_duplicates) for(var/datum/traitor_objective/destroy_item/objective as anything in possible_duplicates) possible_items -= objective.target_item.type diff --git a/code/modules/antagonists/traitor/objectives/eyesnatching.dm b/code/modules/antagonists/traitor/objectives/eyesnatching.dm index d4558420b1d2..16eb0644ff43 100644 --- a/code/modules/antagonists/traitor/objectives/eyesnatching.dm +++ b/code/modules/antagonists/traitor/objectives/eyesnatching.dm @@ -177,9 +177,10 @@ if(!do_after(user, 5 SECONDS, target = target, extra_checks = CALLBACK(src, PROC_REF(eyeballs_exist), eyeballies, head, target))) return - var/datum/wound/blunt/severe/severe_wound_type = /datum/wound/blunt/severe - var/datum/wound/blunt/critical/critical_wound_type = /datum/wound/blunt/critical - target.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = rand(initial(severe_wound_type.threshold_minimum), initial(critical_wound_type.threshold_minimum) + 10)) + var/min_wound = head.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_SEVERE, return_value_if_no_wound = 30, wound_source = src) + var/max_wound = head.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_CRITICAL, return_value_if_no_wound = 50, wound_source = src) + + target.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = rand(min_wound, max_wound + 10), attacking_item = src) target.visible_message( span_danger("[src] pierces through [target]'s skull, horribly mutilating their eyes!"), span_userdanger("Something penetrates your skull, horribly mutilating your eyes! Holy fuck!"), diff --git a/code/modules/antagonists/traitor/objectives/kidnapping.dm b/code/modules/antagonists/traitor/objectives/kidnapping.dm index 72551716a491..8f7fefaf84d0 100644 --- a/code/modules/antagonists/traitor/objectives/kidnapping.dm +++ b/code/modules/antagonists/traitor/objectives/kidnapping.dm @@ -5,7 +5,7 @@ abstract_type = /datum/traitor_objective/target_player/kidnapping - /// The jobs that this objective is targetting. + /// The jobs that this objective is targeting. var/list/target_jobs /// Area that the target needs to be delivered to var/area/dropoff_area diff --git a/code/modules/antagonists/traitor/objectives/kill_pet.dm b/code/modules/antagonists/traitor/objectives/kill_pet.dm index 227a6e1b1ce7..69514b0c8a7e 100644 --- a/code/modules/antagonists/traitor/objectives/kill_pet.dm +++ b/code/modules/antagonists/traitor/objectives/kill_pet.dm @@ -24,9 +24,14 @@ ), JOB_CAPTAIN = /mob/living/basic/pet/fox/renault, JOB_CHIEF_MEDICAL_OFFICER = /mob/living/simple_animal/pet/cat/runtime, - JOB_CHIEF_ENGINEER = /mob/living/simple_animal/parrot/poly, + JOB_CHIEF_ENGINEER = /mob/living/basic/parrot/poly, + JOB_QUARTERMASTER = list( + /mob/living/basic/gorilla/cargorilla, + /mob/living/basic/sloth/citrus, + /mob/living/basic/sloth/paperwork, + ) ) - /// The head that we are targetting + /// The head that we are targeting var/datum/job/target /// Whether or not we only take from the traitor's own department head or not. var/limited_to_department_head = TRUE diff --git a/code/modules/antagonists/traitor/objectives/steal.dm b/code/modules/antagonists/traitor/objectives/steal.dm index 354b41bd984a..2ee09ec67bad 100644 --- a/code/modules/antagonists/traitor/objectives/steal.dm +++ b/code/modules/antagonists/traitor/objectives/steal.dm @@ -97,6 +97,9 @@ GLOBAL_DATUM_INIT(steal_item_handler, /datum/objective_item_handler, new()) possible_items = list( /datum/objective_item/steal/traitor/cargo_budget, /datum/objective_item/steal/traitor/clown_shoes, + /datum/objective_item/steal/traitor/lawyers_badge, + /datum/objective_item/steal/traitor/chef_moustache, + /datum/objective_item/steal/traitor/pka, ) /datum/traitor_objective/steal_item/somewhat_risky diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm index eedc80471242..04cc5fb05a3e 100644 --- a/code/modules/antagonists/wizard/equipment/artefact.dm +++ b/code/modules/antagonists/wizard/equipment/artefact.dm @@ -86,7 +86,7 @@ /obj/item/veilrender/honkrender name = "honk render" desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus." - spawn_type = /mob/living/simple_animal/hostile/retaliate/clown + spawn_type = /mob/living/basic/clown spawn_amt = 10 activate_descriptor = "depression" rend_desc = "Gently wafting with the sounds of endless laughter." @@ -97,7 +97,7 @@ /obj/item/veilrender/honkrender/honkhulkrender name = "superior honk render" desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus. This one gleams with a special light." - spawn_type = /mob/living/simple_animal/hostile/retaliate/clown/clownhulk + spawn_type = /mob/living/basic/clown/clownhulk spawn_amt = 5 activate_descriptor = "depression" rend_desc = "Gently wafting with the sounds of mirthful grunting." diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm index 0a0746e52992..5aaaae141979 100644 --- a/code/modules/antagonists/wizard/equipment/soulstone.dm +++ b/code/modules/antagonists/wizard/equipment/soulstone.dm @@ -35,20 +35,14 @@ /obj/item/soulstone/update_appearance(updates) . = ..() - for(var/mob/living/simple_animal/shade/sharded_shade in src) + for(var/mob/living/basic/shade/sharded_shade in src) switch(theme) if(THEME_HOLY) sharded_shade.name = "Purified [sharded_shade.real_name]" - sharded_shade.icon_state = "shade_holy" - sharded_shade.loot = list(/obj/item/ectoplasm/angelic) - if(THEME_CULT) + else sharded_shade.name = sharded_shade.real_name - sharded_shade.icon_state = "shade_cult" - sharded_shade.loot = list(/obj/item/ectoplasm) - if(THEME_WIZARD) - sharded_shade.name = sharded_shade.real_name - sharded_shade.icon_state = "shade_wizard" - sharded_shade.loot = list(/obj/item/ectoplasm/mystic) + sharded_shade.theme = theme + sharded_shade.update_appearance(UPDATE_ICON_STATE) /obj/item/soulstone/update_icon_state() . = ..() @@ -70,7 +64,7 @@ // "dull soulstone" name = "dull [name]" - var/mob/living/simple_animal/shade/shade = locate() in src + var/mob/living/basic/shade/shade = locate() in src if(shade) // "(dull) soulstone: Urist McCaptain" name = "[name]: [shade.real_name]" @@ -159,7 +153,7 @@ . += span_cult("This shard is spent; it is now just a creepy rock.") /obj/item/soulstone/Destroy() //Stops the shade from being qdel'd immediately and their ghost being sent back to the arrival shuttle. - for(var/mob/living/simple_animal/shade/shade in src) + for(var/mob/living/basic/shade/shade in src) INVOKE_ASYNC(shade, TYPE_PROC_REF(/mob/living, death)) return ..() @@ -212,7 +206,7 @@ release_shades(user) /obj/item/soulstone/proc/release_shades(mob/user, silent = FALSE) - for(var/mob/living/simple_animal/shade/captured_shade in src) + for(var/mob/living/basic/shade/captured_shade in src) captured_shade.forceMove(get_turf(user)) captured_shade.cancel_camera() update_appearance() @@ -229,7 +223,7 @@ on_release_spirits() /obj/item/soulstone/pre_attack(atom/A, mob/living/user, params) - var/mob/living/simple_animal/shade/occupant = (locate() in src) + var/mob/living/basic/shade/occupant = (locate() in src) var/obj/item/storage/toolbox/mechanical/target_toolbox = A if(!occupant || !istype(target_toolbox) || target_toolbox.has_soul) return ..() @@ -322,7 +316,7 @@ return TRUE //it'll probably get someone ;) ///captures a shade that was previously released from a soulstone. -/obj/item/soulstone/proc/capture_shade(mob/living/simple_animal/shade/shade, mob/living/user) +/obj/item/soulstone/proc/capture_shade(mob/living/basic/shade/shade, mob/living/user) if(isliving(user) && !role_check(user)) user.Unconscious(10 SECONDS) to_chat(user, span_userdanger("Your body is wracked with debilitating pain!")) @@ -345,7 +339,7 @@ ///transfer the mind of the shade to a construct mob selected by the user, then deletes both the shade and src. /obj/item/soulstone/proc/transfer_to_construct(obj/structure/constructshell/shell, mob/user) - var/mob/living/simple_animal/shade/shade = locate() in src + var/mob/living/basic/shade/shade = locate() in src if(!shade) to_chat(user, "[span_userdanger("Creation failed!")]: [src] is empty! Go kill someone!") return FALSE @@ -377,7 +371,7 @@ if(!shade_controller) shade_controller = victim victim.stop_sound_channel(CHANNEL_HEARTBEAT) - var/mob/living/simple_animal/shade/soulstone_spirit = new /mob/living/simple_animal/shade(src) + var/mob/living/basic/shade/soulstone_spirit = new /mob/living/basic/shade(src) soulstone_spirit.AddComponent(/datum/component/soulstoned, src) soulstone_spirit.name = "Shade of [victim.real_name]" soulstone_spirit.real_name = "Shade of [victim.real_name]" @@ -464,42 +458,42 @@ switch(construct_class) if(CONSTRUCT_JUGGERNAUT) if(IS_CULTIST(creator)) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/juggernaut, target, creator, cultoverride, loc_override) // ignore themes, the actual giving of cult info is in the makeNewConstruct proc + makeNewConstruct(/mob/living/basic/construct/juggernaut, target, creator, cultoverride, loc_override) // ignore themes, the actual giving of cult info is in the makeNewConstruct proc return switch(theme) if(THEME_WIZARD) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/juggernaut/mystic, target, creator, cultoverride, loc_override) + makeNewConstruct(/mob/living/basic/construct/juggernaut/mystic, target, creator, cultoverride, loc_override) if(THEME_HOLY) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/juggernaut/angelic, target, creator, cultoverride, loc_override) + makeNewConstruct(/mob/living/basic/construct/juggernaut/angelic, target, creator, cultoverride, loc_override) if(THEME_CULT) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/juggernaut/noncult, target, creator, cultoverride, loc_override) + makeNewConstruct(/mob/living/basic/construct/juggernaut, target, creator, cultoverride, loc_override) if(CONSTRUCT_WRAITH) if(IS_CULTIST(creator)) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/wraith, target, creator, cultoverride, loc_override) // ignore themes, the actual giving of cult info is in the makeNewConstruct proc + makeNewConstruct(/mob/living/basic/construct/wraith, target, creator, cultoverride, loc_override) // ignore themes, the actual giving of cult info is in the makeNewConstruct proc return switch(theme) if(THEME_WIZARD) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/wraith/mystic, target, creator, cultoverride, loc_override) + makeNewConstruct(/mob/living/basic/construct/wraith/mystic, target, creator, cultoverride, loc_override) if(THEME_HOLY) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/wraith/angelic, target, creator, cultoverride, loc_override) + makeNewConstruct(/mob/living/basic/construct/wraith/angelic, target, creator, cultoverride, loc_override) if(THEME_CULT) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/wraith/noncult, target, creator, cultoverride, loc_override) + makeNewConstruct(/mob/living/basic/construct/wraith, target, creator, cultoverride, loc_override) if(CONSTRUCT_ARTIFICER) if(IS_CULTIST(creator)) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/artificer, target, creator, cultoverride, loc_override) // ignore themes, the actual giving of cult info is in the makeNewConstruct proc + makeNewConstruct(/mob/living/basic/construct/artificer, target, creator, cultoverride, loc_override) // ignore themes, the actual giving of cult info is in the makeNewConstruct proc return switch(theme) if(THEME_WIZARD) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/artificer/mystic, target, creator, cultoverride, loc_override) + makeNewConstruct(/mob/living/basic/construct/artificer/mystic, target, creator, cultoverride, loc_override) if(THEME_HOLY) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/artificer/angelic, target, creator, cultoverride, loc_override) + makeNewConstruct(/mob/living/basic/construct/artificer/angelic, target, creator, cultoverride, loc_override) if(THEME_CULT) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/artificer/noncult, target, creator, cultoverride, loc_override) + makeNewConstruct(/mob/living/basic/construct/artificer/noncult, target, creator, cultoverride, loc_override) -/proc/makeNewConstruct(mob/living/simple_animal/hostile/construct/ctype, mob/target, mob/stoner = null, cultoverride = FALSE, loc_override = null) +/proc/makeNewConstruct(mob/living/basic/construct/ctype, mob/target, mob/stoner = null, cultoverride = FALSE, loc_override = null) if(QDELETED(target)) return - var/mob/living/simple_animal/hostile/construct/newstruct = new ctype((loc_override) ? (loc_override) : (get_turf(target))) + var/mob/living/basic/construct/newstruct = new ctype((loc_override) ? (loc_override) : (get_turf(target))) var/makeicon = newstruct.icon_state var/theme = newstruct.theme flick("make_[makeicon][theme]", newstruct) diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm index 9227165c40a2..e576d7738c3d 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm @@ -69,7 +69,7 @@ name = "Guardian Deck" desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \ It would be wise to avoid buying these with anything capable of causing you to swap bodies with others." - item_path = /obj/item/guardiancreator/choose/wizard + item_path = /obj/item/guardian_creator/wizard category = "Assistance" /datum/spellbook_entry/item/bloodbottle diff --git a/code/modules/antagonists/wizard/grand_ritual/grand_side_effect.dm b/code/modules/antagonists/wizard/grand_ritual/grand_side_effect.dm index 008d9d698e08..950390a7227d 100644 --- a/code/modules/antagonists/wizard/grand_ritual/grand_side_effect.dm +++ b/code/modules/antagonists/wizard/grand_ritual/grand_side_effect.dm @@ -352,12 +352,12 @@ abstract = FALSE /// Typepaths of mobs to create var/static/list/permitted_mobs = list( - /mob/living/basic/wumborian_fugu, - /mob/living/simple_animal/hostile/skeleton, + /mob/living/basic/carp, /mob/living/basic/killer_tomato, - /mob/living/simple_animal/hostile/ooze, + /mob/living/basic/skeleton, + /mob/living/basic/wumborian_fugu, /mob/living/simple_animal/hostile/illusion, - /mob/living/basic/carp, + /mob/living/simple_animal/hostile/ooze, ) /datum/grand_side_effect/spawn_delayed_mobs/trigger(potency, turf/ritual_location, mob/invoker) diff --git a/code/modules/awaymissions/cordon.dm b/code/modules/awaymissions/cordon.dm index d86ff7f8ad64..177fc85ba37d 100644 --- a/code/modules/awaymissions/cordon.dm +++ b/code/modules/awaymissions/cordon.dm @@ -40,7 +40,8 @@ return /turf/cordon/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit) - return BULLET_ACT_HIT + SHOULD_CALL_PARENT(FALSE) // Fuck you + return BULLET_ACT_BLOCK /turf/cordon/Adjacent(atom/neighbor, atom/target, atom/movable/mover) return FALSE diff --git a/code/modules/bitrunning/components/avatar_connection.dm b/code/modules/bitrunning/components/avatar_connection.dm index 24f42d8f3e51..60816eab2ae7 100644 --- a/code/modules/bitrunning/components/avatar_connection.dm +++ b/code/modules/bitrunning/components/avatar_connection.dm @@ -107,7 +107,7 @@ ) /// Transfers damage from the avatar to the old_body -/datum/component/avatar_connection/proc/on_linked_damage(datum/source, damage, damage_type, def_zone, blocked, forced) +/datum/component/avatar_connection/proc/on_linked_damage(datum/source, damage, damage_type, def_zone, blocked, ...) SIGNAL_HANDLER var/mob/living/carbon/old_body = old_body_ref?.resolve() @@ -122,7 +122,7 @@ if(damage > 30 && prob(30)) INVOKE_ASYNC(old_body, TYPE_PROC_REF(/mob/living, emote), "scream") - old_body.apply_damage(damage, damage_type, def_zone, blocked, forced, wound_bonus = CANT_WOUND) + old_body.apply_damage(damage, damage_type, def_zone, blocked, wound_bonus = CANT_WOUND) if(old_body.stat > SOFT_CRIT) // KO! full_avatar_disconnect(forced = TRUE) diff --git a/code/modules/bitrunning/server/util.dm b/code/modules/bitrunning/server/util.dm index 05e80a269804..9570fd439151 100644 --- a/code/modules/bitrunning/server/util.dm +++ b/code/modules/bitrunning/server/util.dm @@ -58,10 +58,10 @@ "health" = creature.health, "name" = creature.name, "pilot" = pilot, - "brute" = creature.get_damage_amount(BRUTE), - "burn" = creature.get_damage_amount(BURN), - "tox" = creature.get_damage_amount(TOX), - "oxy" = creature.get_damage_amount(OXY), + "brute" = creature.getBruteLoss(), + "burn" = creature.getFireLoss(), + "tox" = creature.getToxLoss(), + "oxy" = creature.getOxyLoss(), )) return hosted_avatars diff --git a/code/modules/capture_the_flag/ctf_equipment.dm b/code/modules/capture_the_flag/ctf_equipment.dm index 3e8433988934..53a66ed29b52 100644 --- a/code/modules/capture_the_flag/ctf_equipment.dm +++ b/code/modules/capture_the_flag/ctf_equipment.dm @@ -11,7 +11,7 @@ return PROJECTILE_PIERCE_NONE /// hey uhhh don't hit anyone behind them . = ..() -/obj/projectile/beam/ctf/on_hit(atom/target, blocked = FALSE) +/obj/projectile/beam/ctf/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(is_ctf_target(target) && blocked == FALSE) if(iscarbon(target)) @@ -182,7 +182,7 @@ impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser light_color = LIGHT_COLOR_PURPLE -/obj/projectile/beam/instakill/on_hit(atom/target) +/obj/projectile/beam/instakill/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) var/mob/living/carbon/target_mob = target diff --git a/code/modules/capture_the_flag/ctf_game.dm b/code/modules/capture_the_flag/ctf_game.dm index 04fcdea4c1b1..5da88a0456f8 100644 --- a/code/modules/capture_the_flag/ctf_game.dm +++ b/code/modules/capture_the_flag/ctf_game.dm @@ -113,7 +113,7 @@ to_chat(user, span_userdanger("You are now a member of [src.team]. Get the enemy flag and bring it back to your team's controller!")) ctf_game.add_player(team, user.ckey) var/client/new_team_member = user.client - spawn_team_member(new_team_member) + spawn_team_member(new_team_member) /obj/machinery/ctf/spawner/Topic(href, href_list) if(href_list["join"]) @@ -259,6 +259,15 @@ user.set_anchored(TRUE) user.status_flags &= ~CANPUSH +/obj/item/ctf_flag/attackby(obj/item/item, mob/user, params) + if(!istype(item, /obj/item/ctf_flag)) + return ..() + + var/obj/item/ctf_flag/flag = item + if(flag.team != team) + to_chat(user, span_userdanger("Take \the [initial(flag.name)] to your team's controller!")) + user.playsound_local(get_turf(user), 'sound/machines/buzz-sigh.ogg', 100, vary = FALSE, use_reverb = FALSE) + /obj/item/ctf_flag/dropped(mob/user) ..() user.anchored = FALSE // Hacky usage that bypasses set_anchored() @@ -340,7 +349,7 @@ /obj/machinery/ctf/control_point/process(seconds_per_tick) if(controlling_team) ctf_game.control_point_scoring(controlling_team, point_rate * seconds_per_tick) - + var/scores if(ctf_game.ctf_enabled) @@ -348,10 +357,10 @@ var/datum/ctf_team/ctf_team = ctf_game.teams[team] scores += UNLINT("[ctf_team.team_color] - [ctf_team.points]/[ctf_game.points_to_win]\n") balloon_alert_to_viewers(scores) - + /obj/machinery/ctf/control_point/attackby(mob/user, params) capture(user) - + /obj/machinery/ctf/control_point/attack_hand(mob/user, list/modifiers) . = ..() if(.) diff --git a/code/modules/cargo/exports/lavaland.dm b/code/modules/cargo/exports/lavaland.dm index d4017646a79c..5c0bd076f2d7 100644 --- a/code/modules/cargo/exports/lavaland.dm +++ b/code/modules/cargo/exports/lavaland.dm @@ -29,12 +29,14 @@ /datum/export/lavaland/major //valuable chest/ruin loot, minor megafauna loot cost = CARGO_CRATE_VALUE * 40 unit_name = "lava planet artifact" - export_types = list(/obj/item/guardiancreator/miner, - /obj/item/rod_of_asclepius, - /obj/item/dragons_blood, - /obj/item/lava_staff, - /obj/item/prisoncube, - ) + export_types = list( + /obj/item/dragons_blood, + /obj/item/guardian_creator/miner, + /obj/item/lava_staff, + /obj/item/melee/ghost_sword, + /obj/item/prisoncube, + /obj/item/rod_of_asclepius, + ) //Megafauna loot, except for ash drakes diff --git a/code/modules/cargo/packs/livestock.dm b/code/modules/cargo/packs/livestock.dm index a8cdd16bd81a..f9eb781bfa2f 100644 --- a/code/modules/cargo/packs/livestock.dm +++ b/code/modules/cargo/packs/livestock.dm @@ -6,13 +6,13 @@ name = "Bird Crate" desc = "Contains five expert telecommunication birds." cost = CARGO_CRATE_VALUE * 8 - contains = list(/mob/living/simple_animal/parrot) + contains = list(/mob/living/basic/parrot) crate_name = "parrot crate" /datum/supply_pack/critter/parrot/generate() . = ..() for(var/i in 1 to 4) - new /mob/living/simple_animal/parrot(.) + new /mob/living/basic/parrot(.) /datum/supply_pack/critter/butterfly name = "Butterflies Crate" @@ -135,7 +135,7 @@ name = "Goat Crate" desc = "The goat goes baa! Contains one goat. Warranty void if used as a replacement for Pete." cost = CARGO_CRATE_VALUE * 5 - contains = list(/mob/living/simple_animal/hostile/retaliate/goat) + contains = list(/mob/living/basic/goat) crate_name = "goat crate" /datum/supply_pack/critter/rabbit @@ -185,7 +185,7 @@ desc = "Tired of these MOTHER FUCKING snakes on this MOTHER FUCKING space station? \ Then this isn't the crate for you. Contains three venomous snakes." cost = CARGO_CRATE_VALUE * 6 - contains = list(/mob/living/simple_animal/hostile/retaliate/snake = 3) + contains = list(/mob/living/basic/snake = 3) crate_name = "snake crate" /datum/supply_pack/critter/amphibians diff --git a/code/modules/cargo/packs/organic.dm b/code/modules/cargo/packs/organic.dm index 1b2e5dc3c976..c7cf0455a21d 100644 --- a/code/modules/cargo/packs/organic.dm +++ b/code/modules/cargo/packs/organic.dm @@ -69,6 +69,7 @@ /obj/item/seeds/bamboo, /obj/item/seeds/eggplant/eggy, /obj/item/seeds/rainbow_bunch, + /obj/item/seeds/seedling, /obj/item/seeds/shrub, /obj/item/seeds/random = 2, ) @@ -297,10 +298,12 @@ ONLY 5000 BUX GET NOW! Contains a grill and fuel." cost = CARGO_CRATE_VALUE * 8 crate_type = /obj/structure/closet/crate - contains = list(/obj/item/stack/sheet/mineral/coal/five, - /obj/machinery/grill/unwrenched, - /obj/item/reagent_containers/cup/soda_cans/monkey_energy, - ) + contains = list( + /obj/item/stack/sheet/mineral/coal/five, + /obj/item/kitchen/tongs, + /obj/item/reagent_containers/cup/soda_cans/monkey_energy, + /obj/machinery/grill/unwrenched, + ) crate_name = "grilling starter kit crate" /datum/supply_pack/organic/grillfuel diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index 95d8b674f25a..47b1b84b0435 100644 --- a/code/modules/clothing/chameleon.dm +++ b/code/modules/clothing/chameleon.dm @@ -39,7 +39,7 @@ // No point making the code more complicated if no non-drone // is ever going to use one of these - var/mob/living/simple_animal/drone/D + var/mob/living/basic/drone/D if(isdrone(owner)) D = owner diff --git a/code/modules/clothing/chameleon/chameleon_drone.dm b/code/modules/clothing/chameleon/chameleon_drone.dm new file mode 100644 index 000000000000..dd50fc871088 --- /dev/null +++ b/code/modules/clothing/chameleon/chameleon_drone.dm @@ -0,0 +1,52 @@ +/datum/action/item_action/chameleon/drone/randomise + name = "Randomise Headgear" + button_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "random" + +/datum/action/item_action/chameleon/drone/randomise/Trigger(trigger_flags) + if(!IsAvailable(feedback = TRUE)) + return FALSE + + for(var/datum/action/item_action/chameleon/change/to_randomize in owner.actions) + to_randomize.random_look() + return TRUE + +// Allows a drone to turn their hat into a mask +// This action's existence is very silly can be replaced with just, a hat with a chameleon action that can be both hats and masks. +/datum/action/item_action/chameleon/drone/togglehatmask + name = "Toggle Headgear Mode" + button_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon_state = "drone_camogear_helm" + +/datum/action/item_action/chameleon/drone/togglehatmask/New(Target) + if (istype(Target, /obj/item/clothing/head/chameleon/drone)) + button_icon_state = "drone_camogear_helm" + if (istype(Target, /obj/item/clothing/mask/chameleon/drone)) + button_icon_state = "drone_camogear_mask" + return ..() + +/datum/action/item_action/chameleon/drone/togglehatmask/IsAvailable(feedback) + return ..() && isdrone(owner) + +/datum/action/item_action/chameleon/drone/togglehatmask/Trigger(trigger_flags) + if(!IsAvailable(feedback = TRUE)) + return FALSE + + var/mob/living/basic/drone/droney = owner + + // The drone unEquip() proc sets head to null after dropping + // an item, so we need to keep a reference to our old headgear + // to make sure it's deleted. + var/obj/old_headgear = target + var/obj/new_headgear + + if(istype(old_headgear, /obj/item/clothing/head/chameleon/drone)) + new_headgear = new /obj/item/clothing/mask/chameleon/drone(droney) + else if(istype(old_headgear, /obj/item/clothing/mask/chameleon/drone)) + new_headgear = new /obj/item/clothing/head/chameleon/drone(droney) + else + to_chat(owner, span_warning("You shouldn't be able to toggle a camogear helmetmask if you're not wearing it.")) + return FALSE + droney.dropItemToGround(target, force = TRUE) + droney.equip_to_slot_or_del(new_headgear, ITEM_SLOT_HEAD) + return TRUE diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 68c4831febd1..8c79ba80b506 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -562,3 +562,11 @@ BLIND // can't see anything to_chat(L, span_warning("The damaged threads on your [src.name] chafe!")) #undef MOTH_EATING_CLOTHING_DAMAGE + +/obj/item/clothing/apply_fantasy_bonuses(bonus) + . = ..() + set_armor(get_armor().generate_new_with_modifiers(list(ARMOR_ALL = bonus))) + +/obj/item/clothing/remove_fantasy_bonuses(bonus) + set_armor(get_armor().generate_new_with_modifiers(list(ARMOR_ALL = -bonus))) + return ..() diff --git a/code/modules/clothing/gloves/_gloves.dm b/code/modules/clothing/gloves/_gloves.dm index ff01d0ca1de0..3f5503ca0eed 100644 --- a/code/modules/clothing/gloves/_gloves.dm +++ b/code/modules/clothing/gloves/_gloves.dm @@ -21,6 +21,14 @@ /// Used for handling bloody gloves leaving behind bloodstains on objects. Will be decremented whenever a bloodstain is left behind, and be incremented when the gloves become bloody. var/transfer_blood = 0 +/obj/item/clothing/gloves/apply_fantasy_bonuses(bonus) + . = ..() + siemens_coefficient = modify_fantasy_variable("siemens_coefficient", siemens_coefficient, -bonus / 10) + +/obj/item/clothing/gloves/remove_fantasy_bonuses(bonus) + siemens_coefficient = reset_fantasy_variable("siemens_coefficient", siemens_coefficient) + return ..() + /obj/item/clothing/gloves/wash(clean_types) . = ..() if((clean_types & CLEAN_TYPE_BLOOD) && transfer_blood > 0) diff --git a/code/modules/clothing/gloves/insulated.dm b/code/modules/clothing/gloves/insulated.dm index cfde4f64c9c7..89f33963af5d 100644 --- a/code/modules/clothing/gloves/insulated.dm +++ b/code/modules/clothing/gloves/insulated.dm @@ -13,6 +13,7 @@ custom_price = PAYCHECK_CREW * 10 custom_premium_price = PAYCHECK_COMMAND * 6 cut_type = /obj/item/clothing/gloves/cut + /datum/armor/color_yellow bio = 50 diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm index ef4c3d177cf8..acb10fdbb80c 100644 --- a/code/modules/clothing/shoes/_shoes.dm +++ b/code/modules/clothing/shoes/_shoes.dm @@ -198,7 +198,7 @@ to_chat(our_guy, span_userdanger("You stamp on [user]'s hand! What the- [user.p_they()] [user.p_were()] [tied ? "knotting" : "untying"] your shoelaces!")) user.emote("scream") if(istype(L)) - var/obj/item/bodypart/ouchie = L.get_bodypart(pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + var/obj/item/bodypart/ouchie = L.get_bodypart(pick(GLOB.arm_zones)) if(ouchie) ouchie.receive_damage(brute = 10) L.stamina.adjust(-40) @@ -278,3 +278,17 @@ if(do_after(user, lace_time, target = src,extra_checks = CALLBACK(src, PROC_REF(still_shoed), user))) to_chat(user, span_notice("You [tied ? "untie" : "tie"] the laces on [src].")) adjust_laces(tied ? SHOES_UNTIED : SHOES_TIED, user) + +/obj/item/clothing/shoes/apply_fantasy_bonuses(bonus) + . = ..() + slowdown = modify_fantasy_variable("slowdown", slowdown, -bonus * 0.1, 0) + if(ismob(loc)) + var/mob/wearer = loc + wearer.update_equipment_speed_mods() + +/obj/item/clothing/shoes/remove_fantasy_bonuses(bonus) + slowdown = reset_fantasy_variable("slowdown", slowdown) + if(ismob(loc)) + var/mob/wearer = loc + wearer.update_equipment_speed_mods() + return ..() diff --git a/code/modules/clothing/shoes/cowboy.dm b/code/modules/clothing/shoes/cowboy.dm index e6f02264d352..05792a72cbd9 100644 --- a/code/modules/clothing/shoes/cowboy.dm +++ b/code/modules/clothing/shoes/cowboy.dm @@ -17,7 +17,7 @@ if(prob(2)) //There's a snake in my boot - new /mob/living/simple_animal/hostile/retaliate/snake(src) + new /mob/living/basic/snake(src) /obj/item/clothing/shoes/cowboy/equipped(mob/living/carbon/user, slot) @@ -56,7 +56,7 @@ if(contents.len >= max_occupants) to_chat(user, span_warning("[src] are full!")) return - if(istype(target, /mob/living/simple_animal/hostile/retaliate/snake) || istype(target, /mob/living/basic/headslug) || islarva(target)) + if(istype(target, /mob/living/basic/snake) || istype(target, /mob/living/basic/headslug) || islarva(target)) target.forceMove(src) to_chat(user, span_notice("[target] slithers into [src].")) diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index 0dd8010e5f07..53009e2df8c9 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -28,6 +28,20 @@ if(!allowed) allowed = GLOB.security_vest_allowed +/obj/item/clothing/suit/armor/apply_fantasy_bonuses(bonus) + . = ..() + slowdown = modify_fantasy_variable("slowdown", slowdown, -bonus * 0.1, 0) + if(ismob(loc)) + var/mob/wearer = loc + wearer.update_equipment_speed_mods() + +/obj/item/clothing/suit/armor/remove_fantasy_bonuses(bonus) + slowdown = reset_fantasy_variable("slowdown", slowdown) + if(ismob(loc)) + var/mob/wearer = loc + wearer.update_equipment_speed_mods() + return ..() + /obj/item/clothing/suit/armor/vest name = "armor vest" desc = "A slim Type I armored vest that provides decent protection against most types of damage." diff --git a/code/modules/events/ghost_role/revenant_event.dm b/code/modules/events/ghost_role/revenant_event.dm index b15d6e9aa543..0de546c567f4 100644 --- a/code/modules/events/ghost_role/revenant_event.dm +++ b/code/modules/events/ghost_role/revenant_event.dm @@ -56,11 +56,12 @@ if(!spawn_locs.len) //If we can't find THAT, then just give up and cry return MAP_ERROR - var/mob/living/simple_animal/revenant/revvie = new(pick(spawn_locs)) - revvie.key = selected.key + var/mob/living/basic/revenant/revvie = new(pick(spawn_locs)) + selected.mind.transfer_to(revvie) message_admins("[ADMIN_LOOKUPFLW(revvie)] has been made into a revenant by an event.") revvie.log_message("was spawned as a revenant by an event.", LOG_GAME) spawned_mobs += revvie + qdel(selected) return SUCCESSFUL_SPAWN #undef REVENANT_SPAWN_THRESHOLD diff --git a/code/modules/events/ghost_role/sentience.dm b/code/modules/events/ghost_role/sentience.dm index cc38f7202447..289c259643e7 100644 --- a/code/modules/events/ghost_role/sentience.dm +++ b/code/modules/events/ghost_role/sentience.dm @@ -4,14 +4,20 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list( /mob/living/basic/lizard, /mob/living/basic/carp/pet/cayenne, /mob/living/basic/cow, - /mob/living/basic/spider/giant/sgt_araneus, + /mob/living/basic/goat, /mob/living/basic/lizard, /mob/living/basic/mouse/brown/tom, + /mob/living/basic/parrot, /mob/living/basic/pet, /mob/living/basic/pig, /mob/living/basic/rabbit, /mob/living/basic/sheep, + /mob/living/basic/sloth, + /mob/living/basic/snake, + /mob/living/basic/spider/giant/sgt_araneus, /mob/living/simple_animal/bot/secbot/beepsky, + /mob/living/simple_animal/hostile/retaliate/goose/vomit, + /mob/living/simple_animal/pet, ))) /datum/round_event_control/sentience diff --git a/code/modules/events/ghost_role/space_dragon.dm b/code/modules/events/ghost_role/space_dragon.dm index 8b579abad17d..f29eade80971 100644 --- a/code/modules/events/ghost_role/space_dragon.dm +++ b/code/modules/events/ghost_role/space_dragon.dm @@ -35,7 +35,7 @@ if(isnull(spawn_location)) return MAP_ERROR - var/mob/living/simple_animal/hostile/space_dragon/dragon = new (spawn_location) + var/mob/living/basic/space_dragon/dragon = new (spawn_location) dragon.key = key dragon.mind.set_assigned_role(SSjob.GetJobType(/datum/job/space_dragon)) dragon.mind.special_role = ROLE_SPACE_DRAGON diff --git a/code/modules/events/holiday/halloween.dm b/code/modules/events/holiday/halloween.dm index b0b9aa35264b..a2874d4a0d03 100644 --- a/code/modules/events/holiday/halloween.dm +++ b/code/modules/events/holiday/halloween.dm @@ -18,9 +18,9 @@ for(var/mob/living/basic/pet/dog/corgi/ian/Ian in GLOB.mob_living_list) Ian.place_on_head(new /obj/item/bedsheet(Ian)) - for(var/mob/living/simple_animal/parrot/poly/Poly in GLOB.mob_living_list) - new /mob/living/simple_animal/parrot/poly/ghost(Poly.loc) - qdel(Poly) + for(var/mob/living/basic/parrot/poly/bird in GLOB.mob_living_list) + new /mob/living/basic/parrot/poly/ghost(bird.loc) + qdel(bird) /datum/round_event/spooky/announce(fake) priority_announce(pick("RATTLE ME BONES!","THE RIDE NEVER ENDS!", "A SKELETON POPS OUT!", "SPOOKY SCARY SKELETONS!", "CREWMEMBERS BEWARE, YOU'RE IN FOR A SCARE!") , "THE CALL IS COMING FROM INSIDE THE HOUSE") diff --git a/code/modules/events/portal_storm.dm b/code/modules/events/portal_storm.dm index b5771d5075a0..86e6a3e6f74d 100644 --- a/code/modules/events/portal_storm.dm +++ b/code/modules/events/portal_storm.dm @@ -11,9 +11,11 @@ tags = list(TAG_COMBAT) /datum/round_event/portal_storm/syndicate_shocktroop - boss_types = list(/mob/living/basic/syndicate/melee/space/stormtrooper = 2) - hostile_types = list(/mob/living/basic/syndicate/melee/space = 8,\ - /mob/living/basic/syndicate/ranged/space = 2) + boss_types = list(/mob/living/basic/trooper/syndicate/melee/space/stormtrooper = 2) + hostile_types = list( + /mob/living/basic/trooper/syndicate/melee/space = 8, + /mob/living/basic/trooper/syndicate/ranged/space = 2, + ) /datum/round_event_control/portal_storm_narsie name = "Portal Storm: Constructs" @@ -26,9 +28,11 @@ max_wizard_trigger_potency = 7 /datum/round_event/portal_storm/portal_storm_narsie - boss_types = list(/mob/living/simple_animal/hostile/construct/artificer/hostile = 6) - hostile_types = list(/mob/living/simple_animal/hostile/construct/juggernaut/hostile = 8,\ - /mob/living/simple_animal/hostile/construct/wraith/hostile = 6) + boss_types = list(/mob/living/basic/construct/artificer/hostile = 6) + hostile_types = list( + /mob/living/basic/construct/juggernaut/hostile = 8, + /mob/living/basic/construct/wraith/hostile = 6, + ) /datum/round_event/portal_storm start_when = 7 diff --git a/code/modules/events/shuttle_loan/shuttle_loan_datum.dm b/code/modules/events/shuttle_loan/shuttle_loan_datum.dm index 7bbb84e5ab47..4f5564563f90 100644 --- a/code/modules/events/shuttle_loan/shuttle_loan_datum.dm +++ b/code/modules/events/shuttle_loan/shuttle_loan_datum.dm @@ -86,12 +86,12 @@ var/datum/supply_pack/pack = SSshuttle.supply_packs[/datum/supply_pack/imports/specialops] pack.generate(pick_n_take(empty_shuttle_turfs)) - spawn_list.Add(/mob/living/basic/syndicate/ranged/infiltrator) - spawn_list.Add(/mob/living/basic/syndicate/ranged/infiltrator) + spawn_list.Add(/mob/living/basic/trooper/syndicate/ranged/infiltrator) + spawn_list.Add(/mob/living/basic/trooper/syndicate/ranged/infiltrator) if(prob(75)) - spawn_list.Add(/mob/living/basic/syndicate/ranged/infiltrator) + spawn_list.Add(/mob/living/basic/trooper/syndicate/ranged/infiltrator) if(prob(50)) - spawn_list.Add(/mob/living/basic/syndicate/ranged/infiltrator) + spawn_list.Add(/mob/living/basic/trooper/syndicate/ranged/infiltrator) /datum/shuttle_loan_situation/lots_of_bees sender = "CentCom Janitorial Division" @@ -178,11 +178,11 @@ var/datum/supply_pack/pack = SSshuttle.supply_packs[/datum/supply_pack/service/party] pack.generate(pick_n_take(empty_shuttle_turfs)) - spawn_list.Add(/mob/living/basic/syndicate/russian) - spawn_list.Add(/mob/living/basic/syndicate/russian/ranged) //drops a mateba + spawn_list.Add(/mob/living/basic/trooper/russian) + spawn_list.Add(/mob/living/basic/trooper/russian/ranged) //drops a mateba spawn_list.Add(/mob/living/basic/bear/russian) if(prob(75)) - spawn_list.Add(/mob/living/basic/syndicate/russian) + spawn_list.Add(/mob/living/basic/trooper/russian) if(prob(50)) spawn_list.Add(/mob/living/basic/bear/russian) diff --git a/code/modules/events/wizard/blobies.dm b/code/modules/events/wizard/blobies.dm index 307d01ff7eb4..0a9c96d51354 100644 --- a/code/modules/events/wizard/blobies.dm +++ b/code/modules/events/wizard/blobies.dm @@ -10,4 +10,4 @@ /datum/round_event/wizard/blobies/start() for(var/mob/living/carbon/human/H in GLOB.dead_mob_list) - new /mob/living/simple_animal/hostile/blob/blobspore(H.loc) + new /mob/living/basic/blob_minion/spore/minion(H.loc) // Creates zombies which ghosts can control diff --git a/code/modules/events/wizard/petsplosion.dm b/code/modules/events/wizard/petsplosion.dm index e2c69a52ee1f..9bc15342a69d 100644 --- a/code/modules/events/wizard/petsplosion.dm +++ b/code/modules/events/wizard/petsplosion.dm @@ -4,21 +4,21 @@ GLOBAL_LIST_INIT(petsplosion_candidates, typecacheof(list( /mob/living/basic/butterfly, /mob/living/basic/carp/pet/cayenne, /mob/living/basic/cow, - /mob/living/basic/spider/giant/sgt_araneus, + /mob/living/basic/goat, /mob/living/basic/lizard, /mob/living/basic/mothroach, /mob/living/basic/mouse/brown/tom, + /mob/living/basic/parrot, /mob/living/basic/pet, /mob/living/basic/pig, /mob/living/basic/chicken, /mob/living/basic/rabbit, /mob/living/basic/sheep, - /mob/living/simple_animal/pet, - /mob/living/simple_animal/parrot, - /mob/living/simple_animal/sloth, - /mob/living/simple_animal/hostile/retaliate/goat, - /mob/living/simple_animal/hostile/retaliate/snake, + /mob/living/basic/sloth, + /mob/living/basic/snake, + /mob/living/basic/spider/giant/sgt_araneus, /mob/living/simple_animal/hostile/retaliate/goose/vomit, + /mob/living/simple_animal/pet, ))) /datum/round_event_control/wizard/petsplosion //the horror diff --git a/code/modules/events/wizard/rpgloot.dm b/code/modules/events/wizard/rpgloot.dm index 79b0462d5164..9220f37fb896 100644 --- a/code/modules/events/wizard/rpgloot.dm +++ b/code/modules/events/wizard/rpgloot.dm @@ -23,21 +23,28 @@ var/can_backfire = TRUE var/uses = 1 -/obj/item/upgradescroll/afterattack(obj/item/target, mob/user, proximity) +/obj/item/upgradescroll/apply_fantasy_bonuses(bonus) . = ..() - if(!proximity || !istype(target)) - return + if(bonus >= 15) + can_backfire = FALSE + upgrade_amount = modify_fantasy_variable("upgrade_amount", upgrade_amount, round(bonus / 4), minimum = 1) - . |= AFTERATTACK_PROCESSED_ITEM +/obj/item/upgradescroll/remove_fantasy_bonuses(bonus) + upgrade_amount = reset_fantasy_variable("upgrade_amount", upgrade_amount) + can_backfire = TRUE + return ..() - target.AddComponent(/datum/component/fantasy, upgrade_amount, null, null, can_backfire, TRUE) +/obj/item/upgradescroll/pre_attack(obj/item/target, mob/living/user) + . = ..() + if(. || !istype(target) || !(user.istate & ISTATE_HARM)) + return + target.AddComponent(/datum/component/fantasy, upgrade_amount, null, null, can_backfire, TRUE) uses -= 1 if(!uses) visible_message(span_warning("[src] vanishes, its magic completely consumed from the fortification.")) qdel(src) - - return . + return TRUE /obj/item/upgradescroll/unlimited name = "unlimited foolproof item fortification scroll" @@ -66,13 +73,16 @@ GLOBAL_DATUM(rpgloot_controller, /datum/rpgloot_controller) /datum/rpgloot_controller/New() . = ..() //second operation takes MUCH longer, so lets set up signals first. - RegisterSignal(SSdcs, COMSIG_GLOB_NEW_ITEM, PROC_REF(on_new_item_in_existence)) + RegisterSignal(SSdcs, COMSIG_GLOB_ATOM_AFTER_POST_INIT, PROC_REF(on_new_item_in_existence)) handle_current_items() ///signal sent by a new item being created. /datum/rpgloot_controller/proc/on_new_item_in_existence(datum/source, obj/item/created_item) SIGNAL_HANDLER - + if(!istype(created_item)) + return + if(created_item.item_flags & SKIP_FANTASY_ON_SPAWN) + return created_item.AddComponent(/datum/component/fantasy) /** @@ -91,6 +101,9 @@ GLOBAL_DATUM(rpgloot_controller, /datum/rpgloot_controller) fantasy_item.AddComponent(/datum/component/fantasy) + if(isnull(fantasy_item.loc)) + continue + if(istype(fantasy_item, /obj/item/storage)) var/obj/item/storage/storage_item = fantasy_item var/datum/storage/storage_component = storage_item.atom_storage diff --git a/code/modules/experisci/experiment/experiments.dm b/code/modules/experisci/experiment/experiments.dm index b2e2ef909ff3..196005e4beb8 100644 --- a/code/modules/experisci/experiment/experiments.dm +++ b/code/modules/experisci/experiment/experiments.dm @@ -39,7 +39,14 @@ description = "We need to see how the body functions from the earliest moments. Some cytology experiments will help us gain this understanding." total_requirement = 3 max_requirement_per_type = 2 - possible_types = list(/mob/living/basic/carp, /mob/living/simple_animal/hostile/retaliate/snake, /mob/living/simple_animal/pet/cat, /mob/living/basic/pet/dog/corgi, /mob/living/basic/cow, /mob/living/basic/chicken) + possible_types = list( + /mob/living/basic/carp, + /mob/living/basic/chicken, + /mob/living/basic/cow, + /mob/living/basic/pet/dog/corgi, + /mob/living/basic/snake, + /mob/living/simple_animal/pet/cat, + ) /datum/experiment/scanning/random/cytology/medium/one name = "Advanced Cytology Scanning Experiment One" diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm index a5149b29e3d4..236a84045008 100644 --- a/code/modules/fishing/fish/_fish.dm +++ b/code/modules/fishing/fish/_fish.dm @@ -417,13 +417,13 @@ //Fish breeding stops if fish count exceeds this. #define AQUARIUM_MAX_BREEDING_POPULATION 20 -/obj/item/fish/proc/ready_to_reproduce(being_targetted = FALSE) +/obj/item/fish/proc/ready_to_reproduce(being_targeted = FALSE) var/obj/structure/aquarium/aquarium = loc if(!istype(aquarium)) return FALSE - if(being_targetted && HAS_TRAIT(src, TRAIT_FISH_NO_MATING)) + if(being_targeted && HAS_TRAIT(src, TRAIT_FISH_NO_MATING)) return FALSE - if(!being_targetted && length(aquarium.get_fishes()) >= AQUARIUM_MAX_BREEDING_POPULATION) + if(!being_targeted && length(aquarium.get_fishes()) >= AQUARIUM_MAX_BREEDING_POPULATION) return FALSE return aquarium.allow_breeding && health >= initial(health) * 0.8 && stable_population > 1 && world.time >= breeding_wait diff --git a/code/modules/food_and_drinks/restaurant/_venue.dm b/code/modules/food_and_drinks/restaurant/_venue.dm index 0e3af728a8b3..76ccd245bf68 100644 --- a/code/modules/food_and_drinks/restaurant/_venue.dm +++ b/code/modules/food_and_drinks/restaurant/_venue.dm @@ -60,10 +60,10 @@ if (initial(customer_type.is_unique)) customer_types -= customer_type - var/mob/living/simple_animal/robot_customer/new_customer = new /mob/living/simple_animal/robot_customer(get_turf(restaurant_portal), customer_type, src) + var/mob/living/basic/robot_customer/new_customer = new /mob/living/basic/robot_customer(get_turf(restaurant_portal), customer_type, src) current_visitors += new_customer -/datum/venue/proc/order_food(mob/living/simple_animal/robot_customer/customer_pawn, datum/customer_data/customer_data) +/datum/venue/proc/order_food(mob/living/basic/robot_customer/customer_pawn, datum/customer_data/customer_data) var/order = pick_weight(customer_data.orderable_objects[venue_type]) var/list/order_args // Only for custom orders - arguments passed into New var/image/food_image @@ -113,7 +113,7 @@ return "broken venue pls call a coder" ///Effects for when a customer receives their order at this venue -/datum/venue/proc/on_get_order(mob/living/simple_animal/robot_customer/customer_pawn, obj/item/order_item) +/datum/venue/proc/on_get_order(mob/living/basic/robot_customer/customer_pawn, obj/item/order_item) SHOULD_CALL_PARENT(TRUE) // This is an item typepath, a reagent typepath, or a custom order datum instance. @@ -152,7 +152,7 @@ open = FALSE restaurant_portal.update_icon() STOP_PROCESSING(SSobj, src) - for(var/mob/living/simple_animal/robot_customer as anything in current_visitors) + for(var/mob/living/basic/robot_customer as anything in current_visitors) robot_customer.ai_controller.set_blackboard_key(BB_CUSTOMER_LEAVING, TRUE) //LEAVEEEEEE /obj/machinery/restaurant_portal diff --git a/code/modules/food_and_drinks/restaurant/custom_order.dm b/code/modules/food_and_drinks/restaurant/custom_order.dm index 25ea87802413..d865c65c3e55 100644 --- a/code/modules/food_and_drinks/restaurant/custom_order.dm +++ b/code/modules/food_and_drinks/restaurant/custom_order.dm @@ -34,7 +34,7 @@ * Return [TRANSACTION_SUCCESS] to denote the order went through successfully (Not generally necessary to include here) * Return [TRANSACTION_HANDLED] to not do any further handling of the order by the */ -/datum/custom_order/proc/handle_get_order(mob/living/simple_animal/robot_customer/customer_pawn, obj/item/order_item) +/datum/custom_order/proc/handle_get_order(mob/living/basic/robot_customer/customer_pawn, obj/item/order_item) return NONE /datum/custom_order/moth_clothing @@ -152,7 +152,7 @@ food_image.add_overlay(drink_image) return food_image -/datum/custom_order/reagent/handle_get_order(mob/living/simple_animal/robot_customer/customer_pawn, obj/item/order_item) +/datum/custom_order/reagent/handle_get_order(mob/living/basic/robot_customer/customer_pawn, obj/item/order_item) . = TRANSACTION_HANDLED for(var/datum/reagent/reagent as anything in order_item.reagents?.reagent_list) @@ -183,7 +183,7 @@ /datum/custom_order/reagent/drink container_needed = /obj/item/reagent_containers/cup/glass/drinkingglass -/datum/custom_order/reagent/drink/handle_get_order(mob/living/simple_animal/robot_customer/customer_pawn, obj/item/order_item) +/datum/custom_order/reagent/drink/handle_get_order(mob/living/basic/robot_customer/customer_pawn, obj/item/order_item) customer_pawn.visible_message( span_danger("[customer_pawn] slurps up [order_item] in one go!"), span_danger("You slurp up [order_item] in one go."), @@ -209,7 +209,7 @@ /datum/custom_order/reagent/soup/get_order_line(datum/venue/our_venue) return "I'll take a [picked_serving] of [initial(reagent_type.name)]" -/datum/custom_order/reagent/soup/handle_get_order(mob/living/simple_animal/robot_customer/customer_pawn, obj/item/order_item) +/datum/custom_order/reagent/soup/handle_get_order(mob/living/basic/robot_customer/customer_pawn, obj/item/order_item) customer_pawn.visible_message( span_danger("[customer_pawn] pours [order_item] right down [customer_pawn.p_their()] hatch!"), span_danger("You pour [order_item] down your hatch in one go."), diff --git a/code/modules/food_and_drinks/restaurant/customers/_customer.dm b/code/modules/food_and_drinks/restaurant/customers/_customer.dm index 935323192b30..85ccdc499640 100644 --- a/code/modules/food_and_drinks/restaurant/customers/_customer.dm +++ b/code/modules/food_and_drinks/restaurant/customers/_customer.dm @@ -52,10 +52,10 @@ /datum/customer_data/proc/can_use(datum/venue/venue) return TRUE -/datum/customer_data/proc/get_overlays(mob/living/simple_animal/robot_customer/customer) +/datum/customer_data/proc/get_overlays(mob/living/basic/robot_customer/customer) return -/datum/customer_data/proc/get_underlays(mob/living/simple_animal/robot_customer/customer) +/datum/customer_data/proc/get_underlays(mob/living/basic/robot_customer/customer) return /datum/customer_data/american @@ -176,7 +176,7 @@ ), ) -/datum/customer_data/french/get_overlays(mob/living/simple_animal/robot_customer/customer) +/datum/customer_data/french/get_overlays(mob/living/basic/robot_customer/customer) if(customer.ai_controller.blackboard[BB_CUSTOMER_LEAVING]) var/mutable_appearance/flag = mutable_appearance(customer.icon, "french_flag") flag.appearance_flags = RESET_COLOR @@ -223,7 +223,7 @@ ), ) -/datum/customer_data/japanese/get_overlays(mob/living/simple_animal/robot_customer/customer) +/datum/customer_data/japanese/get_overlays(mob/living/basic/robot_customer/customer) //leaving and eaten if(type == /datum/customer_data/japanese && customer.ai_controller.blackboard[BB_CUSTOMER_LEAVING] && customer.ai_controller.blackboard[BB_CUSTOMER_EATING]) var/mutable_appearance/you_won_my_heart = mutable_appearance('icons/effects/effects.dmi', "love_hearts") @@ -302,13 +302,13 @@ return FALSE return TRUE -/datum/customer_data/moth/proc/get_wings(mob/living/simple_animal/robot_customer/customer) +/datum/customer_data/moth/proc/get_wings(mob/living/basic/robot_customer/customer) var/customer_ref = WEAKREF(customer) if (!LAZYACCESS(wings_chosen, customer_ref)) LAZYSET(wings_chosen, customer_ref, GLOB.moth_wings_list[pick(GLOB.moth_wings_list)]) return wings_chosen[customer_ref] -/datum/customer_data/moth/get_underlays(mob/living/simple_animal/robot_customer/customer) +/datum/customer_data/moth/get_underlays(mob/living/basic/robot_customer/customer) var/list/underlays = list() var/datum/sprite_accessory/moth_wings/wings = get_wings(customer) @@ -319,7 +319,7 @@ return underlays -/datum/customer_data/moth/get_overlays(mob/living/simple_animal/robot_customer/customer) +/datum/customer_data/moth/get_overlays(mob/living/basic/robot_customer/customer) var/list/overlays = list() var/datum/sprite_accessory/moth_wings/wings = get_wings(customer) diff --git a/code/modules/food_and_drinks/restaurant/generic_venues.dm b/code/modules/food_and_drinks/restaurant/generic_venues.dm index 19f1ff613233..9e9b0a6d0e3b 100644 --- a/code/modules/food_and_drinks/restaurant/generic_venues.dm +++ b/code/modules/food_and_drinks/restaurant/generic_venues.dm @@ -42,7 +42,7 @@ var/obj/item/object_to_order = order return "I'll take \a [initial(object_to_order.name)]" -/datum/venue/restaurant/on_get_order(mob/living/simple_animal/robot_customer/customer_pawn, obj/item/order_item) +/datum/venue/restaurant/on_get_order(mob/living/basic/robot_customer/customer_pawn, obj/item/order_item) var/transaction_result = ..() if((transaction_result & TRANSACTION_HANDLED) || !(transaction_result & TRANSACTION_SUCCESS)) return diff --git a/code/modules/hallucination/body.dm b/code/modules/hallucination/body.dm index cec96f9f8281..592935409942 100644 --- a/code/modules/hallucination/body.dm +++ b/code/modules/hallucination/body.dm @@ -151,11 +151,11 @@ body_floats = TRUE /datum/hallucination/body/weird/faceless - body_image_file = 'icons/mob/simple/traders.dmi' + body_image_file = 'icons/obj/trader_signs.dmi' body_image_state = "faceless" /datum/hallucination/body/weird/bones - body_image_file = 'icons/mob/simple/traders.dmi' + body_image_file = 'icons/obj/trader_signs.dmi' body_image_state = "mrbones" /datum/hallucination/body/weird/freezer diff --git a/code/modules/holodeck/turfs.dm b/code/modules/holodeck/turfs.dm index 0019f8e63c16..ef9cd5353dca 100644 --- a/code/modules/holodeck/turfs.dm +++ b/code/modules/holodeck/turfs.dm @@ -8,7 +8,7 @@ /turf/open/floor/holofloor/attackby(obj/item/I, mob/living/user) return // HOLOFLOOR DOES NOT GIVE A FUCK -/turf/open/floor/holofloor/tool_act(mob/living/user, obj/item/I, tool_type) +/turf/open/floor/holofloor/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking) return /turf/open/floor/holofloor/burn_tile() diff --git a/code/modules/hydroponics/grown/seedling.dm b/code/modules/hydroponics/grown/seedling.dm new file mode 100644 index 000000000000..9a915c41659d --- /dev/null +++ b/code/modules/hydroponics/grown/seedling.dm @@ -0,0 +1,28 @@ +/obj/item/seeds/seedling + name = "pack of seedling seeds" + desc = "These seeds grow into a floral assistant which can help look after other plants!" + icon_state = "seed-seedling" + growing_icon = 'icons/obj/service/hydroponics/growing_fruits.dmi' + icon = 'icons/obj/service/hydroponics/seeds.dmi' + species = "seedling" + plantname = "Seedling Plant" + product = /mob/living/basic/seedling + lifespan = 40 + endurance = 7 + maturation = 10 + production = 1 + growthstages = 2 + yield = 1 + potency = 30 + +/obj/item/seeds/seedling/harvest(mob/harvester) + var/obj/machinery/hydroponics/parent = loc + var/list/grow_locations = get_adjacent_open_turfs(parent) + var/turf/final_location = length(grow_locations) ? pick(grow_locations) : get_turf(parent) + var/mob/living/basic/seedling/seed_pet = new product(final_location) + seed_pet.befriend(harvester) + parent.update_tray(user = harvester, product_count = 1) + +/obj/item/seeds/seedling/evil + product = /mob/living/basic/seedling/meanie + icon_state = "seed-seedling-evil" diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 988beb8e69b5..5e5211d00245 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -230,12 +230,15 @@ /obj/machinery/hydroponics/bullet_act(obj/projectile/Proj) if(!myseed) return ..() - if(istype(Proj , /obj/projectile/energy/floramut)) + if(istype(Proj , /obj/projectile/energy/flora/mut)) mutate() - else if(istype(Proj , /obj/projectile/energy/florayield)) + else if(istype(Proj , /obj/projectile/energy/flora/yield)) return myseed.bullet_act(Proj) - else if(istype(Proj , /obj/projectile/energy/florarevolution)) - mutatespecie_new() + else if(istype(Proj , /obj/projectile/energy/flora/evolution)) + if(myseed) + if(LAZYLEN(myseed.mutatelist)) + myseed.mutate() + mutatespecie() else return ..() diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index 51c7ba004a50..8a7cdd1b2e14 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -231,7 +231,7 @@ /obj/item/seeds/bullet_act(obj/projectile/Proj) //Works with the Somatoray to modify plant variables. - if(istype(Proj, /obj/projectile/energy/florayield)) + if(istype(Proj, /obj/projectile/energy/flora/yield)) var/rating = 1 if(istype(loc, /obj/machinery/hydroponics)) var/obj/machinery/hydroponics/H = loc diff --git a/code/modules/industrial_lift/industrial_lift.dm b/code/modules/industrial_lift/industrial_lift.dm index 35525e7ed247..368f3dbc9a97 100644 --- a/code/modules/industrial_lift/industrial_lift.dm +++ b/code/modules/industrial_lift/industrial_lift.dm @@ -1,4 +1,14 @@ GLOBAL_LIST_EMPTY(lifts) +GLOBAL_LIST_INIT(all_radial_directions, list( + "NORTH" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH), + "NORTHEAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTHEAST), + "EAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = EAST), + "SOUTHEAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTHEAST), + "SOUTH" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH), + "SOUTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTHWEST), + "WEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = WEST), + "NORTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTHWEST) +)) /obj/structure/industrial_lift name = "lift platform" @@ -742,19 +752,8 @@ GLOBAL_LIST_EMPTY(lifts) var/starting_position = loc if (!can_open_lift_radial(user,starting_position)) return -//NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST - var/static/list/tool_list = list( - "NORTH" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH), - "NORTHEAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH), - "EAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = EAST), - "SOUTHEAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = EAST), - "SOUTH" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH), - "SOUTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH), - "WEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = WEST), - "NORTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = WEST) - ) - - var/result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, PROC_REF(can_open_lift_radial), user, starting_position), require_near = TRUE, tooltips = FALSE) + + var/result = show_radial_menu(user, src, GLOB.all_radial_directions, custom_check = CALLBACK(src, PROC_REF(can_open_lift_radial), user, starting_position), require_near = TRUE, tooltips = FALSE) if (!can_open_lift_radial(user,starting_position)) return // nice try if(!isnull(result) && result != "Cancel" && lift_master_datum.controls_locked) diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm index 6be121085d2c..edbc4f625bdb 100644 --- a/code/modules/jobs/job_types/cook.dm +++ b/code/modules/jobs/job_types/cook.dm @@ -33,13 +33,14 @@ // Adds up to 100, don't mess it up mail_goodies = list( /obj/item/storage/box/ingredients/random = 40, - /obj/item/reagent_containers/cup/bottle/caramel = 8, - /obj/item/reagent_containers/condiment/flour = 8, - /obj/item/reagent_containers/condiment/rice = 8, - /obj/item/reagent_containers/condiment/ketchup = 8, - /obj/item/reagent_containers/condiment/enzyme = 8, - /obj/item/reagent_containers/condiment/soymilk = 8, + /obj/item/reagent_containers/cup/bottle/caramel = 7, + /obj/item/reagent_containers/condiment/flour = 7, + /obj/item/reagent_containers/condiment/rice = 7, + /obj/item/reagent_containers/condiment/ketchup = 7, + /obj/item/reagent_containers/condiment/enzyme = 7, + /obj/item/reagent_containers/condiment/soymilk = 7, /obj/item/kitchen/spoon/soup_ladle = 6, + /obj/item/kitchen/tongs = 6, /obj/item/knife/kitchen = 4, /obj/item/knife/butcher = 2, ) diff --git a/code/modules/mapfluff/ruins/lavalandruin_code/biodome_winter.dm b/code/modules/mapfluff/ruins/lavalandruin_code/biodome_winter.dm index 1149e5af4fee..7206e6c02280 100644 --- a/code/modules/mapfluff/ruins/lavalandruin_code/biodome_winter.dm +++ b/code/modules/mapfluff/ruins/lavalandruin_code/biodome_winter.dm @@ -18,7 +18,7 @@ . = ..() . += span_notice("Throw this at objects or creatures to freeze them, it will boomerang back so be cautious!") -/obj/item/freeze_cube/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, quickstart = TRUE) +/obj/item/freeze_cube/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, gentle, quickstart = TRUE) . = ..() if(!.) return diff --git a/code/modules/mapfluff/ruins/lavalandruin_code/puzzle.dm b/code/modules/mapfluff/ruins/lavalandruin_code/puzzle.dm index 14c17b621920..9ab9ceee4603 100644 --- a/code/modules/mapfluff/ruins/lavalandruin_code/puzzle.dm +++ b/code/modules/mapfluff/ruins/lavalandruin_code/puzzle.dm @@ -291,7 +291,7 @@ /obj/effect/sliding_puzzle/prison/dispense_reward() prisoner.forceMove(get_turf(src)) - prisoner.notransform = FALSE + REMOVE_TRAIT(prisoner, TRAIT_NO_TRANSFORM, element_type) prisoner = null //Some armor so it's harder to kill someone by mistake. @@ -340,7 +340,7 @@ return FALSE //First grab the prisoner and move them temporarily into the generator so they won't get thrown around. - prisoner.notransform = TRUE + ADD_TRAIT(prisoner, TRAIT_NO_TRANSFORM, cube.element_type) prisoner.forceMove(cube) to_chat(prisoner,span_userdanger("You're trapped by the prison cube! You will remain trapped until someone solves it.")) diff --git a/code/modules/mapfluff/ruins/objects_and_mobs/ash_walker_den.dm b/code/modules/mapfluff/ruins/objects_and_mobs/ash_walker_den.dm index 42fdf6216601..11c3128b9402 100644 --- a/code/modules/mapfluff/ruins/objects_and_mobs/ash_walker_den.dm +++ b/code/modules/mapfluff/ruins/objects_and_mobs/ash_walker_den.dm @@ -13,8 +13,8 @@ resistance_flags = FIRE_PROOF | LAVA_PROOF max_integrity = 200 + faction = list(FACTION_ASHWALKER) - var/faction = list(FACTION_ASHWALKER) var/meat_counter = 6 var/datum/team/ashwalkers/ashies var/datum/linked_objective diff --git a/code/modules/mapfluff/ruins/spaceruin_code/clericsden.dm b/code/modules/mapfluff/ruins/spaceruin_code/clericsden.dm index 2ea63245661a..56b09ef7f79f 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/clericsden.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/clericsden.dm @@ -13,24 +13,3 @@ /obj/item/paper/fluff/ruins/clericsden/warning default_raw_text = "FATHER ODIVALLUS DO NOT GO FORWARD WITH THE RITUAL. THE ASTEROID WE'RE ANCHORED TO IS UNSTABLE, YOU WILL DESTROY THE STATION. I HOPE THIS REACHES YOU IN TIME. FATHER AURELLION." - -/mob/living/simple_animal/hostile/construct/proteon - name = "Proteon" - real_name = "Proteon" - desc = "A weaker construct meant to scour ruins for objects of Nar'Sie's affection. Those barbed claws are no joke." - icon_state = "proteon" - icon_living = "proteon" - maxHealth = 35 - health = 35 - melee_damage_lower = 8 - melee_damage_upper = 10 - retreat_distance = 4 //AI proteons will rapidly move in and out of combat to avoid conflict, but will still target and follow you. - attack_verb_continuous = "pinches" - attack_verb_simple = "pinch" - environment_smash = ENVIRONMENT_SMASH_WALLS - attack_sound = 'sound/weapons/punch2.ogg' - playstyle_string = "You are a Proteon. Your abilities in combat are outmatched by most combat constructs, but you are still fast and nimble. Run metal and supplies, and cooperate with your fellow cultists." - -/mob/living/simple_animal/hostile/construct/proteon/hostile //Style of mob spawned by trapped cult runes in the cleric ruin. - AIStatus = AI_ON - environment_smash = ENVIRONMENT_SMASH_STRUCTURES //standard ai construct behavior, breaks things if it wants, but not walls. diff --git a/code/modules/mapfluff/ruins/spaceruin_code/forgottenship.dm b/code/modules/mapfluff/ruins/spaceruin_code/forgottenship.dm index 045ab8ec00c9..964f8b218ab5 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/forgottenship.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/forgottenship.dm @@ -124,42 +124,3 @@ GLOBAL_VAR_INIT(fscpassword, generate_password()) icon_state = "syndie-ship" ambientsounds = list('sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg') area_flags = NOTELEPORT | UNIQUE_AREA - -//Special NT NPCs - -/mob/living/simple_animal/hostile/nanotrasen/ranged/assault - name = "Nanotrasen Assault Officer" - desc = "Nanotrasen Assault Officer. Contact CentCom if you saw him on your station. Prepare to die, if you've been found near Syndicate property." - ranged = TRUE - rapid = 4 - rapid_fire_delay = 1 - rapid_melee = 1 - retreat_distance = 2 - minimum_distance = 4 - casingtype = /obj/item/ammo_casing/a556/weak - projectilesound = 'sound/weapons/gun/smg/shot.ogg' - loot = list(/obj/effect/mob_spawn/corpse/human/nanotrasenassaultsoldier) - mob_spawner = /obj/effect/mob_spawn/corpse/human/nanotrasenassaultsoldier - held_item = /obj/item/gun/ballistic/automatic/ar - -/mob/living/simple_animal/hostile/nanotrasen/elite - name = "Nanotrasen Elite Assault Officer" - desc = "Pray for your life, syndicate. Run while you can." - maxHealth = 150 - health = 150 - melee_damage_lower = 13 - melee_damage_upper = 18 - ranged = TRUE - rapid = 3 - rapid_fire_delay = 5 - rapid_melee = 3 - retreat_distance = 0 - minimum_distance = 1 - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - projectiletype = /obj/projectile/beam/laser - projectilesound = 'sound/weapons/laser.ogg' - loot = list(/obj/effect/gibspawner/human) - faction = list(ROLE_DEATHSQUAD) - mob_spawner = /obj/effect/mob_spawn/corpse/human/nanotrasenelitesoldier - held_item = /obj/item/gun/energy/pulse/carbine/lethal diff --git a/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm index c040b96be3b8..e21b73659f4f 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm @@ -482,14 +482,17 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) /obj/item/abstracthotelstorage/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) . = ..() if(ismob(arrived)) - var/mob/M = arrived - M.notransform = TRUE + var/mob/target = arrived + ADD_TRAIT(target, TRAIT_NO_TRANSFORM, REF(src)) /obj/item/abstracthotelstorage/Exited(atom/movable/gone, direction) . = ..() if(ismob(gone)) - var/mob/M = gone - M.notransform = FALSE + var/mob/target = gone + REMOVE_TRAIT(target, TRAIT_NO_TRANSFORM, REF(src)) + if(istype(gone, /obj/machinery/light)) + var/obj/machinery/light/exited_light = gone + exited_light.begin_processing() //Space Ruin stuff /area/ruin/space/has_grav/powered/hilbertresearchfacility diff --git a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm index 8af5d4e5a9d8..fc79c82e780e 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm @@ -13,8 +13,12 @@ /obj/effect/mob_spawn/corpse/human/tigercultist/perforated/special(mob/living/carbon/human/spawned_human) . = ..() - var/datum/wound/pierce/critical/exit_hole = new() - exit_hole.apply_wound(spawned_human.get_bodypart(BODY_ZONE_CHEST)) + + var/obj/item/bodypart/chest/their_chest = spawned_human.get_bodypart(BODY_ZONE_CHEST) + if (!their_chest) + return + + spawned_human.cause_wound_of_type_and_severity(WOUND_PIERCE, their_chest, WOUND_SEVERITY_CRITICAL) /// A fun drink enjoyed by the tiger cooperative, might corrode your brain if you drink the whole bottle /obj/item/reagent_containers/cup/glass/bottle/ritual_wine diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm index f944bb11c1f9..8f68b7589716 100644 --- a/code/modules/mining/equipment/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher.dm @@ -198,7 +198,7 @@ hammer_synced = null return ..() -/obj/projectile/destabilizer/on_hit(atom/target, blocked = FALSE) +/obj/projectile/destabilizer/on_hit(atom/target, blocked = 0, pierce_hit) if(isliving(target)) var/mob/living/L = target var/had_effect = (L.has_status_effect(/datum/status_effect/crusher_mark)) //used as a boolean @@ -435,3 +435,37 @@ chaser.monster_damage_boost = FALSE // Weaker cuz no cooldown chaser.damage = 20 log_combat(user, target, "fired a chaser at", src) + +/obj/item/crusher_trophy/ice_demon_cube + name = "demonic cube" + desc = "A stone cold cube dropped from an ice demon." + icon_state = "ice_demon_cube" + icon = 'icons/obj/mining_zones/artefacts.dmi' + denied_type = /obj/item/crusher_trophy/ice_demon_cube + ///how many will we summon? + var/summon_amount = 2 + ///cooldown to summon demons upon the target + COOLDOWN_DECLARE(summon_cooldown) + +/obj/item/crusher_trophy/ice_demon_cube/effect_desc() + return "mark detonation to unleash demonic ice clones upon the target" + +/obj/item/crusher_trophy/ice_demon_cube/on_mark_detonation(mob/living/target, mob/living/user) + if(isnull(target) || !COOLDOWN_FINISHED(src, summon_cooldown)) + return + for(var/i in 1 to summon_amount) + var/turf/drop_off = find_dropoff_turf(target, user) + var/mob/living/basic/mining/demon_afterimage/crusher/friend = new(drop_off) + friend.faction = list(FACTION_NEUTRAL) + friend.befriend(user) + friend.ai_controller?.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target) + COOLDOWN_START(src, summon_cooldown, 30 SECONDS) + +///try to make them spawn all around the target to surround him +/obj/item/crusher_trophy/ice_demon_cube/proc/find_dropoff_turf(mob/living/target, mob/living/user) + var/list/turfs_list = get_adjacent_open_turfs(target) + for(var/turf/possible_turf in turfs_list) + if(possible_turf.is_blocked_turf()) + continue + return possible_turf + return get_turf(user) diff --git a/code/modules/mining/equipment/monster_organs/brimdust_sac.dm b/code/modules/mining/equipment/monster_organs/brimdust_sac.dm index dc6539ff336b..a77e526a9d1f 100644 --- a/code/modules/mining/equipment/monster_organs/brimdust_sac.dm +++ b/code/modules/mining/equipment/monster_organs/brimdust_sac.dm @@ -141,7 +141,7 @@ return COMPONENT_CLEANED /// When you take brute damage, schedule an explosion -/datum/status_effect/stacking/brimdust_coating/proc/on_take_damage(datum/source, damage, damagetype) +/datum/status_effect/stacking/brimdust_coating/proc/on_take_damage(datum/source, damage, damagetype, ...) SIGNAL_HANDLER if(damagetype != BRUTE) return diff --git a/code/modules/mining/equipment/monster_organs/regenerative_core.dm b/code/modules/mining/equipment/monster_organs/regenerative_core.dm index bb56b773d735..3f7c2058be82 100644 --- a/code/modules/mining/equipment/monster_organs/regenerative_core.dm +++ b/code/modules/mining/equipment/monster_organs/regenerative_core.dm @@ -36,7 +36,7 @@ /// Log applications and apply moodlet. /obj/item/organ/internal/monster_core/regenerative_core/apply_to(mob/living/target, mob/user) - target.add_mood_event("regenerative core", /datum/mood_event/healsbadman) + target.add_mood_event(MOOD_CATEGORY_LEGION_CORE, /datum/mood_event/healsbadman) if (target != user) target.visible_message(span_notice("[user] forces [target] to apply [src]... Black tendrils entangle and reinforce [target.p_them()]!")) SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "other")) diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm index 8e1876361ac2..bfe050b7ce1d 100644 --- a/code/modules/mining/lavaland/megafauna_loot.dm +++ b/code/modules/mining/lavaland/megafauna_loot.dm @@ -398,7 +398,7 @@ give_blood(10) /obj/item/soulscythe/attack_hand(mob/user, list/modifiers) - if(soul.ckey && !soul.faction_check_mob(user)) + if(soul.ckey && !soul.faction_check_atom(user)) to_chat(user, span_warning("You can't pick up [src]!")) return return ..() @@ -606,7 +606,7 @@ light_power = 1 light_color = LIGHT_COLOR_BLOOD_MAGIC -/obj/projectile/soulscythe/on_hit(atom/target, blocked = FALSE) +/obj/projectile/soulscythe/on_hit(atom/target, blocked = 0, pierce_hit) if(ishostile(target)) damage *= 2 return ..() diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index ff210b4eeb4d..4bedc5946e4a 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -44,7 +44,7 @@ if(12) new /obj/item/jacobs_ladder(src) if(13) - new /obj/item/guardiancreator/miner(src) + new /obj/item/guardian_creator/miner(src) if(14) new /obj/item/warp_cube/red(src) if(15) diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm index fd9baf3a0320..81d144ea4490 100644 --- a/code/modules/mining/lavaland/tendril_loot.dm +++ b/code/modules/mining/lavaland/tendril_loot.dm @@ -198,14 +198,14 @@ continue regurgitate_guardian(guardian) -/obj/item/clothing/neck/necklace/memento_mori/proc/consume_guardian(mob/living/simple_animal/hostile/guardian/guardian) +/obj/item/clothing/neck/necklace/memento_mori/proc/consume_guardian(mob/living/basic/guardian/guardian) new /obj/effect/temp_visual/guardian/phase/out(get_turf(guardian)) guardian.locked = TRUE guardian.forceMove(src) to_chat(guardian, span_userdanger("You have been locked away in your summoner's pendant!")) guardian.playsound_local(get_turf(guardian), 'sound/magic/summonitems_generic.ogg', 50, TRUE) -/obj/item/clothing/neck/necklace/memento_mori/proc/regurgitate_guardian(mob/living/simple_animal/hostile/guardian/guardian) +/obj/item/clothing/neck/necklace/memento_mori/proc/regurgitate_guardian(mob/living/basic/guardian/guardian) guardian.locked = FALSE guardian.recall(forced = TRUE) to_chat(guardian, span_notice("You have been returned back from your summoner's pendant!")) @@ -421,7 +421,7 @@ return user.status_flags &= ~GODMODE - user.notransform = FALSE + REMOVE_TRAIT(user, TRAIT_NO_TRANSFORM, REF(src)) user.forceMove(get_turf(src)) user.visible_message(span_danger("[user] pops back into reality!")) @@ -432,7 +432,7 @@ setDir(user.dir) user.forceMove(src) - user.notransform = TRUE + ADD_TRAIT(user, TRAIT_NO_TRANSFORM, REF(src)) user.status_flags |= GODMODE user_ref = WEAKREF(user) @@ -446,8 +446,8 @@ return /obj/effect/immortality_talisman/relaymove(mob/living/user, direction) - // Won't really come into play since our mob has notransform and cannot move, - // but regardless block all relayed moves, becuase no, you cannot move in the void. + // Won't really come into play since our mob has TRAIT_NO_TRANSFORM and cannot move, + // but regardless block all relayed moves, because no, you cannot move in the void. return /obj/effect/immortality_talisman/singularity_pull() diff --git a/code/modules/mining/mine_items.dm b/code/modules/mining/mine_items.dm index 08df751db8e7..d911328b4a93 100644 --- a/code/modules/mining/mine_items.dm +++ b/code/modules/mining/mine_items.dm @@ -61,12 +61,16 @@ new /obj/item/storage/bag/plants(src) new /obj/item/storage/bag/ore(src) new /obj/item/t_scanner/adv_mining_scanner/lesser(src) - new /obj/item/gun/energy/recharge/kinetic_accelerator(src) new /obj/item/clothing/glasses/meson(src) new /obj/item/survivalcapsule(src) new /obj/item/assault_pod/mining(src) +/obj/structure/closet/secure_closet/miner/populate_contents_immediate() + . = ..() + + new /obj/item/gun/energy/recharge/kinetic_accelerator(src) + /**********************Shuttle Computer**************************/ /obj/machinery/computer/shuttle/mining diff --git a/code/modules/mining/minebot.dm b/code/modules/mining/minebot.dm deleted file mode 100644 index 3894408d98db..000000000000 --- a/code/modules/mining/minebot.dm +++ /dev/null @@ -1,331 +0,0 @@ -/**********************Mining drone**********************/ -#define MINEDRONE_COLLECT 1 -#define MINEDRONE_ATTACK 2 - -/mob/living/simple_animal/hostile/mining_drone - name = "\improper Nanotrasen minebot" - desc = "The instructions printed on the side read: This is a small robot used to support miners, can be set to search and collect loose ore, or to help fend off wildlife." - gender = NEUTER - icon = 'icons/mob/silicon/aibots.dmi' - icon_state = "mining_drone" - icon_living = "mining_drone" - status_flags = CANSTUN|CANKNOCKDOWN|CANPUSH - mouse_opacity = MOUSE_OPACITY_ICON - faction = list(FACTION_NEUTRAL) - istate = ISTATE_HARM|ISTATE_BLOCKING - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - move_to_delay = 10 - health = 125 - maxHealth = 125 - melee_damage_lower = 15 - melee_damage_upper = 15 - obj_damage = 10 - environment_smash = ENVIRONMENT_SMASH_NONE - check_friendly_fire = TRUE - stop_automated_movement_when_pulled = TRUE - attack_verb_continuous = "drills" - attack_verb_simple = "drill" - attack_sound = 'sound/weapons/circsawhit.ogg' - sentience_type = SENTIENCE_MINEBOT - speak_emote = list("states") - wanted_objects = list(/obj/item/stack/ore/diamond, /obj/item/stack/ore/gold, /obj/item/stack/ore/silver, - /obj/item/stack/ore/plasma, /obj/item/stack/ore/uranium, /obj/item/stack/ore/iron, - /obj/item/stack/ore/bananium, /obj/item/stack/ore/titanium) - healable = 0 - loot = list(/obj/effect/decal/cleanable/robot_debris) - del_on_death = TRUE - light_system = MOVABLE_LIGHT - light_outer_range = 6 - light_on = FALSE - var/mode = MINEDRONE_COLLECT - var/obj/item/gun/energy/recharge/kinetic_accelerator/minebot/stored_gun - -/mob/living/simple_animal/hostile/mining_drone/Initialize(mapload) - . = ..() - - AddElement(/datum/element/footstep, FOOTSTEP_OBJ_ROBOT, 1, -6, sound_vary = TRUE) - - stored_gun = new(src) - var/datum/action/innate/minedrone/toggle_light/toggle_light_action = new() - toggle_light_action.Grant(src) - var/datum/action/innate/minedrone/toggle_meson_vision/toggle_meson_vision_action = new() - toggle_meson_vision_action.Grant(src) - var/datum/action/innate/minedrone/toggle_mode/toggle_mode_action = new() - toggle_mode_action.Grant(src) - var/datum/action/innate/minedrone/dump_ore/dump_ore_action = new() - dump_ore_action.Grant(src) - var/obj/item/implant/radio/mining/imp = new(src) - imp.implant(src) - - access_card = new /obj/item/card/id/advanced/gold(src) - SSid_access.apply_trim_to_card(access_card, /datum/id_trim/job/shaft_miner) - - SetCollectBehavior() - -/mob/living/simple_animal/hostile/mining_drone/Destroy() - QDEL_NULL(stored_gun) - for (var/datum/action/innate/minedrone/action in actions) - qdel(action) - return ..() - -/mob/living/simple_animal/hostile/mining_drone/sentience_act() - ..() - check_friendly_fire = 0 - -/mob/living/simple_animal/hostile/mining_drone/examine(mob/user) - . = ..() - var/t_He = p_they(TRUE) - var/t_him = p_them() - var/t_s = p_s() - if(health < maxHealth) - if(health >= maxHealth * 0.5) - . += span_warning("[t_He] look[t_s] slightly dented.") - else - . += span_boldwarning("[t_He] look[t_s] severely dented!") - . += {"Using a mining scanner on [t_him] will instruct [t_him] to drop stored ore. [max(0, LAZYLEN(contents) - 1)] Stored Ore\n - Field repairs can be done with a welder."} - if(stored_gun?.max_mod_capacity) - . += "[stored_gun.get_remaining_mod_capacity()]% mod capacity remaining." - for(var/obj/item/borg/upgrade/modkit/modkit as anything in stored_gun.modkits) - . += span_notice("There is \a [modkit] installed, using [modkit.cost]% capacity.") - -/mob/living/simple_animal/hostile/mining_drone/welder_act(mob/living/user, obj/item/welder) - ..() - . = TRUE - if(mode == MINEDRONE_ATTACK) - to_chat(user, span_warning("[src] can't be repaired while in attack mode!")) - return - - if(maxHealth == health) - to_chat(user, span_info("[src] is at full integrity.")) - return - - if(welder.use_tool(src, user, 0, volume=40)) - adjustBruteLoss(-15) - to_chat(user, span_info("You repair some of the armor on [src].")) - -/mob/living/simple_animal/hostile/mining_drone/attackby(obj/item/item_used, mob/user, params) - if(istype(item_used, /obj/item/mining_scanner) || istype(item_used, /obj/item/t_scanner/adv_mining_scanner)) - to_chat(user, span_info("You instruct [src] to drop any collected ore.")) - DropOre() - return - if(item_used.tool_behaviour == TOOL_CROWBAR || istype(item_used, /obj/item/borg/upgrade/modkit)) - item_used.melee_attack_chain(user, stored_gun, params) - return - ..() - -/mob/living/simple_animal/hostile/mining_drone/death() - DropOre() - if(stored_gun) - for(var/obj/item/borg/upgrade/modkit/modkit as anything in stored_gun.modkits) - modkit.uninstall(stored_gun) - death_message = "blows apart!" - ..() - -/mob/living/simple_animal/hostile/mining_drone/attack_hand(mob/living/carbon/human/user, list/modifiers) - . = ..() - if(.) - return - if(!(user.istate & ISTATE_HARM)) - toggle_mode() - switch(mode) - if(MINEDRONE_COLLECT) - to_chat(user, span_info("[src] has been set to search and store loose ore.")) - if(MINEDRONE_ATTACK) - to_chat(user, span_info("[src] has been set to attack hostile wildlife.")) - return - -/mob/living/simple_animal/hostile/mining_drone/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - if(istype(mover, /obj/projectile/kinetic)) - var/obj/projectile/kinetic/projectile = mover - if(projectile.kinetic_gun) - if (locate(/obj/item/borg/upgrade/modkit/minebot_passthrough) in projectile.kinetic_gun.modkits) - return TRUE - else if(istype(mover, /obj/projectile/destabilizer)) - return TRUE - -/mob/living/simple_animal/hostile/mining_drone/proc/SetCollectBehavior() - mode = MINEDRONE_COLLECT - vision_range = 9 - search_objects = 2 - wander = TRUE - ranged = FALSE - minimum_distance = 1 - retreat_distance = null - icon_state = "mining_drone" - to_chat(src, span_info("You are set to collect mode. You can now collect loose ore.")) - -/mob/living/simple_animal/hostile/mining_drone/proc/SetOffenseBehavior() - mode = MINEDRONE_ATTACK - vision_range = 7 - search_objects = 0 - wander = FALSE - ranged = TRUE - retreat_distance = 2 - minimum_distance = 1 - icon_state = "mining_drone_offense" - to_chat(src, span_info("You are set to attack mode. You can now attack from range.")) - -/mob/living/simple_animal/hostile/mining_drone/AttackingTarget() - if(istype(target, /obj/item/stack/ore) && mode == MINEDRONE_COLLECT) - CollectOre() - return - if(isliving(target)) - SetOffenseBehavior() - return ..() - -/mob/living/simple_animal/hostile/mining_drone/OpenFire(atom/target) - if(CheckFriendlyFire(target)) - return - stored_gun.afterattack(target, src) //of the possible options to allow minebots to have KA mods, would you believe this is the best? - -/mob/living/simple_animal/hostile/mining_drone/proc/CollectOre() - for(var/obj/item/stack/ore/O in range(1, src)) - O.forceMove(src) - -/mob/living/simple_animal/hostile/mining_drone/proc/DropOre(message = 1) - if(!contents.len) - if(message) - to_chat(src, span_warning("You attempt to dump your stored ore, but you have none!")) - return - if(message) - to_chat(src, span_notice("You dump your stored ore.")) - for(var/obj/item/stack/ore/O in contents) - O.forceMove(drop_location()) - -/mob/living/simple_animal/hostile/mining_drone/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - if(mode != MINEDRONE_ATTACK && amount > 0) - SetOffenseBehavior() - . = ..() - -/datum/action/innate/minedrone/toggle_meson_vision - name = "Toggle Meson Vision" - button_icon_state = "meson" - -/datum/action/innate/minedrone/toggle_meson_vision/Activate() - var/mob/living/simple_animal/hostile/mining_drone/user = owner - if(user.sight & SEE_TURFS) - user.clear_sight(SEE_TURFS) - user.lighting_cutoff_red += 5 - user.lighting_cutoff_green += 15 - user.lighting_cutoff_blue += 5 - else - user.add_sight(SEE_TURFS) - user.lighting_cutoff_red -= 5 - user.lighting_cutoff_green -= 15 - user.lighting_cutoff_blue -= 5 - - user.sync_lighting_plane_cutoff() - - to_chat(user, span_notice("You toggle your meson vision [(user.sight & SEE_TURFS) ? "on" : "off"].")) - - -/mob/living/simple_animal/hostile/mining_drone/proc/toggle_mode() - switch(mode) - if(MINEDRONE_ATTACK) - SetCollectBehavior() - else - SetOffenseBehavior() - -//Actions for sentient minebots - -/datum/action/innate/minedrone - check_flags = AB_CHECK_CONSCIOUS - button_icon = 'icons/mob/actions/actions_mecha.dmi' - background_icon_state = "bg_default" - overlay_icon_state = "bg_default_border" - -/datum/action/innate/minedrone/toggle_light - name = "Toggle Light" - button_icon_state = "mech_lights_off" - - -/datum/action/innate/minedrone/toggle_light/Activate() - var/mob/living/simple_animal/hostile/mining_drone/user = owner - user.set_light_on(!user.light_on) - to_chat(user, span_notice("You toggle your light [user.light_on ? "on" : "off"].")) - - -/datum/action/innate/minedrone/toggle_mode - name = "Toggle Mode" - button_icon_state = "mech_cycle_equip_off" - -/datum/action/innate/minedrone/toggle_mode/Activate() - var/mob/living/simple_animal/hostile/mining_drone/user = owner - user.toggle_mode() - -/datum/action/innate/minedrone/dump_ore - name = "Dump Ore" - button_icon_state = "mech_eject" - -/datum/action/innate/minedrone/dump_ore/Activate() - var/mob/living/simple_animal/hostile/mining_drone/user = owner - user.DropOre() - - -/**********************Minebot Upgrades**********************/ - -//Melee - -/obj/item/mine_bot_upgrade - name = "minebot melee upgrade" - desc = "A minebot upgrade." - icon_state = "door_electronics" - icon = 'icons/obj/module.dmi' - -/obj/item/mine_bot_upgrade/afterattack(mob/living/simple_animal/hostile/mining_drone/minebot, mob/user, proximity) - . = ..() - if(!istype(minebot) || !proximity) - return - upgrade_bot(minebot, user) - -/obj/item/mine_bot_upgrade/proc/upgrade_bot(mob/living/simple_animal/hostile/mining_drone/minebot, mob/user) - if(minebot.melee_damage_upper != initial(minebot.melee_damage_upper)) - to_chat(user, span_warning("[minebot] already has a combat upgrade installed!")) - return - minebot.melee_damage_lower += 7 - minebot.melee_damage_upper += 7 - to_chat(user, "You increase the close-quarter combat abilities of [minebot].") - qdel(src) - -//Health - -/obj/item/mine_bot_upgrade/health - name = "minebot armor upgrade" - -/obj/item/mine_bot_upgrade/health/upgrade_bot(mob/living/simple_animal/hostile/mining_drone/minebot, mob/user) - if(minebot.maxHealth != initial(minebot.maxHealth)) - to_chat(user, span_warning("[minebot] already has reinforced armor!")) - return - minebot.maxHealth += 45 - minebot.updatehealth() - to_chat(user, "You reinforce the armor of [minebot].") - qdel(src) - -//AI - -/obj/item/slimepotion/slime/sentience/mining - name = "minebot AI upgrade" - desc = "Can be used to grant sentience to minebots. It's incompatible with minebot armor and melee upgrades, and will override them." - icon_state = "door_electronics" - icon = 'icons/obj/module.dmi' - sentience_type = SENTIENCE_MINEBOT - var/base_health_add = 5 //sentient minebots are penalized for beign sentient; they have their stats reset to normal plus these values - var/base_damage_add = 1 //this thus disables other minebot upgrades - var/base_speed_add = 1 - var/base_cooldown_add = 10 //base cooldown isn't reset to normal, it's just added on, since it's not practical to disable the cooldown module - -/obj/item/slimepotion/slime/sentience/mining/after_success(mob/living/user, mob/living/simple_animal/simple_mob) - if(!istype(simple_mob, /mob/living/simple_animal/hostile/mining_drone)) - return - var/mob/living/simple_animal/hostile/mining_drone/minebot = simple_mob - minebot.maxHealth = initial(minebot.maxHealth) + base_health_add - minebot.melee_damage_lower = initial(minebot.melee_damage_lower) + base_damage_add - minebot.melee_damage_upper = initial(minebot.melee_damage_upper) + base_damage_add - minebot.move_to_delay = initial(minebot.move_to_delay) + base_speed_add - minebot.stored_gun?.recharge_time += base_cooldown_add - -#undef MINEDRONE_COLLECT -#undef MINEDRONE_ATTACK diff --git a/code/modules/mining/voucher_sets.dm b/code/modules/mining/voucher_sets.dm index 86c6e407d67b..071a8f115367 100644 --- a/code/modules/mining/voucher_sets.dm +++ b/code/modules/mining/voucher_sets.dm @@ -62,7 +62,7 @@ icon = 'icons/mob/silicon/aibots.dmi' icon_state = "mining_drone" set_items = list( - /mob/living/simple_animal/hostile/mining_drone, + /mob/living/basic/mining_drone, /obj/item/weldingtool/hugetank, /obj/item/clothing/head/utility/welding, /obj/item/borg/upgrade/modkit/minebot_passthrough, diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm index 482b4bf5a1c7..0fe63f7479bd 100644 --- a/code/modules/mob/dead/dead.dm +++ b/code/modules/mob/dead/dead.dm @@ -46,11 +46,13 @@ INITIALIZE_IMMEDIATE(/mob/dead) if(client.holder) . += "Admins Ready: [SSticker.total_admins_ready] / [length(GLOB.admins)]" +#define SERVER_HOPPER_TRAIT "server_hopper" + /mob/dead/proc/server_hop() set category = "OOC" set name = "Server Hop" set desc= "Jump to the other server" - if(notransform) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) // in case the round is ending and a cinematic is already playing we don't wanna clash with that (yes i know) return var/list/our_id = CONFIG_GET(string/cross_comms_name) var/list/csa = CONFIG_GET(keyed_list/cross_server) - our_id @@ -76,9 +78,9 @@ INITIALIZE_IMMEDIATE(/mob/dead) to_chat(C, span_notice("Sending you to [pick].")) new /atom/movable/screen/splash(null, C) - notransform = TRUE + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, SERVER_HOPPER_TRAIT) sleep(2.9 SECONDS) //let the animation play - notransform = FALSE + REMOVE_TRAIT(src, TRAIT_NO_TRANSFORM, SERVER_HOPPER_TRAIT) if(!C) return @@ -87,6 +89,8 @@ INITIALIZE_IMMEDIATE(/mob/dead) C << link("[addr]") +#undef SERVER_HOPPER_TRAIT + /mob/dead/proc/update_z(new_z) // 1+ to register, null to unregister if (registered_z != new_z) if (registered_z) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index bbf767371807..b8a3071be469 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -248,6 +248,10 @@ I.dropped(src) return FALSE +/// Returns true if a mob is holding something +/mob/proc/is_holding_items() + return !!locate(/obj/item) in held_items + /mob/proc/drop_all_held_items() . = FALSE for(var/obj/item/I in held_items) diff --git a/code/modules/mob/living/basic/basic.dm b/code/modules/mob/living/basic/basic.dm index 14ff9c0e5f38..9385b60073de 100644 --- a/code/modules/mob/living/basic/basic.dm +++ b/code/modules/mob/living/basic/basic.dm @@ -11,7 +11,7 @@ var/basic_mob_flags = NONE - ///Defines how fast the basic mob can move. This is a multiplier + ///Defines how fast the basic mob can move. This is not a multiplier var/speed = 1 ///How much stamina the mob recovers per second var/stamina_recovery = 5 @@ -35,6 +35,8 @@ var/attack_vis_effect ///Played when someone punches the creature. var/attacked_sound = SFX_PUNCH //This should be an element + /// How often can you melee attack? + var/melee_attack_cooldown = 2 SECONDS /// Variable maintained for compatibility with attack_animal procs until simple animals can be refactored away. Use element instead of setting manually. var/environment_smash = ENVIRONMENT_SMASH_STRUCTURES @@ -143,6 +145,19 @@ health = 0 look_dead() +/mob/living/basic/gib() + if(butcher_results || guaranteed_butcher_results) + var/list/butcher_loot = list() + if(butcher_results) + butcher_loot += butcher_results + if(guaranteed_butcher_results) + butcher_loot += guaranteed_butcher_results + var/atom/loot_destination = drop_location() + for(var/path in butcher_loot) + for(var/i in 1 to butcher_loot[path]) + new path(loot_destination) + return ..() + /** * Apply the appearance and properties this mob has when it dies * This is called by the mob pretending to be dead too so don't put loot drops in here or something @@ -152,7 +167,7 @@ if(basic_mob_flags & FLIP_ON_DEATH) transform = transform.Turn(180) if(!(basic_mob_flags & REMAIN_DENSE_WHILE_DEAD)) - set_density(FALSE) + ADD_TRAIT(src, TRAIT_UNDENSE, BASIC_MOB_DEATH_TRAIT) SEND_SIGNAL(src, COMSIG_BASICMOB_LOOK_DEAD) /mob/living/basic/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE) @@ -167,16 +182,24 @@ if(basic_mob_flags & FLIP_ON_DEATH) transform = transform.Turn(180) if(!(basic_mob_flags & REMAIN_DENSE_WHILE_DEAD)) - set_density(FALSE) + REMOVE_TRAIT(src, TRAIT_UNDENSE, BASIC_MOB_DEATH_TRAIT) SEND_SIGNAL(src, COMSIG_BASICMOB_LOOK_ALIVE) /mob/living/basic/update_sight() lighting_color_cutoffs = list(lighting_cutoff_red, lighting_cutoff_green, lighting_cutoff_blue) return ..() -/mob/living/basic/proc/melee_attack(atom/target, list/modifiers) +/mob/living/basic/examine(mob/user) + . = ..() + if(stat != DEAD) + return + . += span_deadsay("Upon closer examination, [p_they()] appear[p_s()] to be [HAS_TRAIT(user.mind, TRAIT_NAIVE) ? "asleep" : "dead"].") + +/mob/living/basic/proc/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE) face_atom(target) - if(SEND_SIGNAL(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, target) & COMPONENT_HOSTILE_NO_ATTACK) + if (!ignore_cooldown) + changeNext_move(melee_attack_cooldown) + if(SEND_SIGNAL(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, target, Adjacent(target), modifiers) & COMPONENT_HOSTILE_NO_ATTACK) return FALSE //but more importantly return before attack_animal called var/result = target.attack_basic_mob(src, modifiers) SEND_SIGNAL(src, COMSIG_HOSTILE_POST_ATTACKINGTARGET, target, result) @@ -243,3 +266,25 @@ else if(on_fire && !isnull(last_icon_state)) return last_icon_state return null + +/mob/living/basic/put_in_hands(obj/item/I, del_on_fail = FALSE, merge_stacks = TRUE, ignore_animation = TRUE) + . = ..() + if (.) + update_held_items() + +/mob/living/basic/update_held_items() + . = ..() + if(isnull(client) || isnull(hud_used) || hud_used.hud_version == HUD_STYLE_NOHUD) + return + var/turf/our_turf = get_turf(src) + for(var/obj/item/held in held_items) + var/index = get_held_index_of_item(held) + SET_PLANE(held, ABOVE_HUD_PLANE, our_turf) + held.screen_loc = ui_hand_position(index) + client.screen |= held + +/mob/living/basic/get_body_temp_heat_damage_limit() + return maximum_survivable_temperature + +/mob/living/basic/get_body_temp_cold_damage_limit() + return minimum_survivable_temperature diff --git a/code/modules/mob/living/basic/basic_defense.dm b/code/modules/mob/living/basic/basic_defense.dm index ddedd1f8aff1..444780e14885 100644 --- a/code/modules/mob/living/basic/basic_defense.dm +++ b/code/modules/mob/living/basic/basic_defense.dm @@ -11,13 +11,13 @@ var/shove_dir = get_dir(user, src) if(!Move(get_step(src, shove_dir), shove_dir)) log_combat(user, src, "shoved", "failing to move it") - user.visible_message(span_danger("[user.name] shoves [src]!"), - span_danger("You shove [src]!"), span_hear("You hear aggressive shuffling!"), COMBAT_MESSAGE_RANGE, list(src)) + user.visible_message(span_danger("[user.name] [response_disarm_continuous] [src]!"), + span_danger("You [response_disarm_simple] [src]!"), span_hear("You hear aggressive shuffling!"), COMBAT_MESSAGE_RANGE, list(src)) to_chat(src, span_userdanger("You're shoved by [user.name]!")) return TRUE log_combat(user, src, "shoved", "pushing it") - user.visible_message(span_danger("[user.name] shoves [src], pushing [p_them()]!"), - span_danger("You shove [src], pushing [p_them()]!"), span_hear("You hear aggressive shuffling!"), COMBAT_MESSAGE_RANGE, list(src)) + user.visible_message(span_danger("[user.name] [response_disarm_continuous] [src], pushing [p_them()]!"), + span_danger("You [response_disarm_simple] [src], pushing [p_them()]!"), span_hear("You hear aggressive shuffling!"), COMBAT_MESSAGE_RANGE, list(src)) to_chat(src, span_userdanger("You're pushed by [user.name]!")) return TRUE @@ -109,13 +109,13 @@ damage = rand(20, 35) return attack_threshold_check(damage) -/mob/living/basic/attack_drone(mob/living/simple_animal/drone/attacking_drone) - if((attacking_drone.istate & ISTATE_HARM)) //No kicking dogs even as a rogue drone. Use a weapon. +/mob/living/basic/attack_drone(mob/living/basic/drone/attacking_drone) + if(attacking_drone.istate & ISTATE_HARM) //No kicking dogs even as a rogue drone. Use a weapon. return return ..() -/mob/living/basic/attack_drone_secondary(mob/living/simple_animal/drone/attacking_drone) - if((attacking_drone.istate & ISTATE_HARM)) +/mob/living/basic/attack_drone_secondary(mob/living/basic/drone/attacking_drone) + if(attacking_drone.istate & ISTATE_HARM) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN return ..() @@ -162,6 +162,9 @@ apply_damage(bloss, damagetype = BRUTE) /mob/living/basic/blob_act(obj/structure/blob/attacking_blob) + . = ..() + if (!.) + return apply_damage(20, damagetype = BRUTE) /mob/living/basic/do_attack_animation(atom/attacked_atom, visual_effect_icon, used_item, no_effect) diff --git a/code/modules/mob/living/basic/blob_minions/blob_ai.dm b/code/modules/mob/living/basic/blob_minions/blob_ai.dm new file mode 100644 index 000000000000..5aad05d4656f --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blob_ai.dm @@ -0,0 +1,54 @@ +/** + * Extremely simple AI, this isn't a very smart boy + * Only notable quirk is that it uses JPS movement, simple avoidance would fail to realise it can path through blobs + */ +/datum/ai_controller/basic_controller/blobbernaut + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + ) + + ai_movement = /datum/ai_movement/jps + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/** + * Move to a point designated by the overmind, otherwise just slap people nearby + */ +/datum/ai_controller/basic_controller/blob_zombie + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + ) + + ai_movement = /datum/ai_movement/jps + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/travel_to_point/and_clear_target, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/** + * As blob zombie but will prioritise attacking corpses to zombify them + */ +/datum/ai_controller/basic_controller/blob_spore + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + ) + + ai_movement = /datum/ai_movement/jps + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/find_and_hunt_target/corpses, + /datum/ai_planning_subtree/travel_to_point/and_clear_target, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) diff --git a/code/modules/mob/living/basic/blob_minions/blob_mob.dm b/code/modules/mob/living/basic/blob_minions/blob_mob.dm new file mode 100644 index 000000000000..d655e33d9e30 --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blob_mob.dm @@ -0,0 +1,37 @@ +/// Root of shared behaviour for mobs spawned by blobs, is abstract and should not be spawned +/mob/living/basic/blob_minion + name = "Blob Error" + desc = "A nonfunctional fungal creature created by bad code or celestial mistake. Point and laugh." + icon = 'icons/mob/nonhuman-player/blob.dmi' + icon_state = "blob_head" + unique_name = TRUE + pass_flags = PASSBLOB + faction = list(ROLE_BLOB) + istate = ISTATE_HARM + bubble_icon = "blob" + speak_emote = null + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = 0 + maximum_survivable_temperature = INFINITY + lighting_cutoff_red = 20 + lighting_cutoff_green = 40 + lighting_cutoff_blue = 30 + initial_language_holder = /datum/language_holder/empty + +/mob/living/basic/blob_minion/Initialize(mapload) + . = ..() + add_traits(list(TRAIT_BLOB_ALLY, TRAIT_MUTE), INNATE_TRAIT) + AddComponent(/datum/component/blob_minion, on_strain_changed = CALLBACK(src, PROC_REF(on_strain_updated))) + +/// Called when our blob overmind changes their variant, update some of our mob properties +/mob/living/basic/blob_minion/proc/on_strain_updated(mob/camera/blob/overmind, datum/blobstrain/new_strain) + return + +/// Associates this mob with a specific blob factory node +/mob/living/basic/blob_minion/proc/link_to_factory(obj/structure/blob/special/factory/factory) + RegisterSignal(factory, COMSIG_PARENT_QDELETING, PROC_REF(on_factory_destroyed)) + +/// Called when our factory is destroyed +/mob/living/basic/blob_minion/proc/on_factory_destroyed() + SIGNAL_HANDLER + to_chat(src, span_userdanger("Your factory was destroyed! You feel yourself dying!")) diff --git a/code/modules/mob/living/basic/blob_minions/blob_spore.dm b/code/modules/mob/living/basic/blob_minions/blob_spore.dm new file mode 100644 index 000000000000..f13a6a70f843 --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blob_spore.dm @@ -0,0 +1,118 @@ +/** + * A floating fungus which turns people into zombies and explodes into reagent clouds upon death. + */ +/mob/living/basic/blob_minion/spore + name = "blob spore" + desc = "A floating, fragile spore." + icon = 'icons/mob/nonhuman-player/blob.dmi' + icon_state = "blobpod" + icon_living = "blobpod" + health_doll_icon = "blobpod" + health = BLOBMOB_SPORE_HEALTH + maxHealth = BLOBMOB_SPORE_HEALTH + verb_say = "psychically pulses" + verb_ask = "psychically probes" + verb_exclaim = "psychically yells" + verb_yell = "psychically screams" + melee_damage_lower = BLOBMOB_SPORE_DMG_LOWER + melee_damage_upper = BLOBMOB_SPORE_DMG_UPPER + obj_damage = 0 + attack_verb_continuous = "batters" + attack_verb_simple = "batter" + attack_sound = 'sound/weapons/genhit1.ogg' + death_message = "explodes into a cloud of gas!" + gold_core_spawnable = HOSTILE_SPAWN + basic_mob_flags = DEL_ON_DEATH + ai_controller = /datum/ai_controller/basic_controller/blob_spore + /// Size of cloud produced from a dying spore + var/death_cloud_size = 1 + /// Type of mob to create + var/mob/living/zombie_type = /mob/living/basic/blob_minion/zombie + +/mob/living/basic/blob_minion/spore/Initialize(mapload) + . = ..() + AddElement(/datum/element/simple_flying) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBSPORE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + +/mob/living/basic/blob_minion/spore/death(gibbed) + . = ..() + death_burst() + +/mob/living/basic/blob_minion/spore/on_factory_destroyed() + death() + +/// Create an explosion of spores on death +/mob/living/basic/blob_minion/spore/proc/death_burst() + do_chem_smoke(range = death_cloud_size, holder = src, location = get_turf(src), reagent_type = /datum/reagent/toxin/spore) + + +/mob/living/basic/blob_minion/spore/melee_attack(mob/living/carbon/human/target, list/modifiers, ignore_cooldown) + . = ..() + if (!ishuman(target) || target.stat != DEAD) + return + zombify(target) + +/// Become a zombie +/mob/living/basic/blob_minion/spore/proc/zombify(mob/living/carbon/human/target) + visible_message(span_warning("The corpse of [target.name] suddenly rises!")) + var/mob/living/basic/blob_minion/zombie/blombie = change_mob_type(zombie_type, loc, new_name = initial(zombie_type.name)) + blombie.set_name() + if (istype(blombie)) // In case of badmin + blombie.consume_corpse(target) + SEND_SIGNAL(src, COMSIG_BLOB_ZOMBIFIED, blombie) + qdel(src) + +/// Variant of the blob spore which is actually spawned by blob factories +/mob/living/basic/blob_minion/spore/minion + gold_core_spawnable = NO_SPAWN + zombie_type = /mob/living/basic/blob_minion/zombie/controlled + /// We die if we leave the same turf as this z level + var/turf/z_turf + +/mob/living/basic/blob_minion/spore/minion/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_z_changed)) + +/// When we z-move check that we're on the same z level as our factory was +/mob/living/basic/blob_minion/spore/minion/proc/on_z_changed() + SIGNAL_HANDLER + if (isnull(z_turf)) + return + if (!is_valid_z_level(get_turf(src), z_turf)) + death() + +/// Mark the turf we need to track from our factory +/mob/living/basic/blob_minion/spore/minion/link_to_factory(obj/structure/blob/special/factory/factory) + . = ..() + z_turf = get_turf(factory) + +/// If the blob changes to distributed neurons then you can control the spores +/mob/living/basic/blob_minion/spore/minion/on_strain_updated(mob/camera/blob/overmind, datum/blobstrain/new_strain) + if (istype(new_strain, /datum/blobstrain/reagent/distributed_neurons)) + AddComponent(\ + /datum/component/ghost_direct_control,\ + ban_type = ROLE_BLOB_INFECTION,\ + poll_candidates = TRUE,\ + poll_ignore_key = POLL_IGNORE_BLOB,\ + ) + else + qdel(GetComponent(/datum/component/ghost_direct_control)) + +/mob/living/basic/blob_minion/spore/minion/death_burst() + return // This behaviour is superceded by the overmind's intervention + + +/// Weakened spore spawned by distributed neurons, can't zombify people and makes a teeny explosion +/mob/living/basic/blob_minion/spore/minion/weak + name = "fragile blob spore" + health = 15 + maxHealth = 15 + melee_damage_lower = 1 + melee_damage_upper = 2 + death_cloud_size = 0 + +/mob/living/basic/blob_minion/spore/minion/weak/zombify() + return + +/mob/living/basic/blob_minion/spore/minion/weak/on_strain_updated() + return diff --git a/code/modules/mob/living/basic/blob_minions/blob_zombie.dm b/code/modules/mob/living/basic/blob_minions/blob_zombie.dm new file mode 100644 index 000000000000..6aa047c0c316 --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blob_zombie.dm @@ -0,0 +1,96 @@ +/// A shambling mob made out of a crew member +/mob/living/basic/blob_minion/zombie + name = "blob zombie" + desc = "A shambling corpse animated by the blob." + icon_state = "zombie" + icon_living = "zombie" + health_doll_icon = "blobpod" + mob_biotypes = MOB_ORGANIC | MOB_HUMANOID + health = 70 + maxHealth = 70 + verb_say = "gurgles" + verb_ask = "demands" + verb_exclaim = "roars" + verb_yell = "bellows" + melee_damage_lower = 10 + melee_damage_upper = 15 + melee_attack_cooldown = CLICK_CD_MELEE + obj_damage = 20 + attack_verb_continuous = "punches" + attack_verb_simple = "punch" + attack_sound = 'sound/weapons/genhit1.ogg' + death_message = "collapses to the ground!" + gold_core_spawnable = NO_SPAWN + basic_mob_flags = DEL_ON_DEATH + ai_controller = /datum/ai_controller/basic_controller/blob_zombie + /// The dead body we have inside + var/mob/living/carbon/human/corpse + +/mob/living/basic/blob_minion/zombie/Initialize(mapload) + . = ..() + AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBSPORE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + +/mob/living/basic/blob_minion/zombie/death(gibbed) + corpse?.forceMove(loc) + death_burst() + return ..() + +/mob/living/basic/blob_minion/zombie/Exited(atom/movable/gone, direction) + . = ..() + if (gone != corpse) + return + corpse = null + death() + +/mob/living/basic/blob_minion/zombie/Destroy() + QDEL_NULL(corpse) + return ..() + +/mob/living/basic/blob_minion/zombie/on_factory_destroyed() + . = ..() + death() + +/mob/living/basic/blob_minion/zombie/update_overlays() + . = ..() + copy_overlays(corpse, TRUE) + var/mutable_appearance/blob_head_overlay = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "blob_head") + blob_head_overlay.color = LAZYACCESS(atom_colours, FIXED_COLOUR_PRIORITY) || COLOR_WHITE + color = initial(color) // reversing what our component did lol, but we needed the value for the overlay + . += blob_head_overlay + +/// Create an explosion of spores on death +/mob/living/basic/blob_minion/zombie/proc/death_burst() + do_chem_smoke(range = 0, holder = src, location = get_turf(src), reagent_type = /datum/reagent/toxin/spore) + +/// Store a body so that we can drop it on death +/mob/living/basic/blob_minion/zombie/proc/consume_corpse(mob/living/carbon/human/new_corpse) + if(new_corpse.wear_suit) + maxHealth += new_corpse.get_armor_rating(MELEE) + health = maxHealth + new_corpse.forceMove(src) + corpse = new_corpse + update_appearance(UPDATE_ICON) + RegisterSignal(corpse, COMSIG_LIVING_REVIVE, PROC_REF(on_corpse_revived)) + +/// Dynamic changeling reentry +/mob/living/basic/blob_minion/zombie/proc/on_corpse_revived() + SIGNAL_HANDLER + visible_message(span_boldwarning("[src] bursts from the inside!")) + death() + +/// Blob-created zombies will ping for player control when they make a zombie +/mob/living/basic/blob_minion/zombie/controlled + +/mob/living/basic/blob_minion/zombie/controlled/consume_corpse(mob/living/carbon/human/new_corpse) + . = ..() + if (!isnull(client)) + return + AddComponent(\ + /datum/component/ghost_direct_control,\ + ban_type = ROLE_BLOB_INFECTION,\ + poll_candidates = TRUE,\ + poll_ignore_key = POLL_IGNORE_BLOB,\ + ) + +/mob/living/basic/blob_minion/zombie/controlled/death_burst() + return diff --git a/code/modules/mob/living/basic/blob_minions/blobbernaut.dm b/code/modules/mob/living/basic/blob_minions/blobbernaut.dm new file mode 100644 index 000000000000..b483641993a7 --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blobbernaut.dm @@ -0,0 +1,109 @@ +/** + * Player-piloted brute mob. Mostly just a "move and click" kind of guy. + * Has a variant which takes damage when away from blob tiles + */ +/mob/living/basic/blob_minion/blobbernaut + name = "blobbernaut" + desc = "A hulking, mobile chunk of blobmass." + icon_state = "blobbernaut" + icon_living = "blobbernaut" + icon_dead = "blobbernaut_dead" + health = BLOBMOB_BLOBBERNAUT_HEALTH + maxHealth = BLOBMOB_BLOBBERNAUT_HEALTH + damage_coeff = list(BRUTE = 0.5, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + melee_damage_lower = BLOBMOB_BLOBBERNAUT_DMG_SOLO_LOWER + melee_damage_upper = BLOBMOB_BLOBBERNAUT_DMG_SOLO_UPPER + melee_attack_cooldown = CLICK_CD_MELEE + obj_damage = BLOBMOB_BLOBBERNAUT_DMG_OBJ + attack_verb_continuous = "slams" + attack_verb_simple = "slam" + attack_sound = 'sound/effects/blobattack.ogg' + verb_say = "gurgles" + verb_ask = "demands" + verb_exclaim = "roars" + verb_yell = "bellows" + force_threshold = 10 + pressure_resistance = 50 + mob_size = MOB_SIZE_LARGE + hud_type = /datum/hud/living/blobbernaut + gold_core_spawnable = HOSTILE_SPAWN + ai_controller = /datum/ai_controller/basic_controller/blobbernaut + +/mob/living/basic/blob_minion/blobbernaut/Initialize(mapload) + . = ..() + AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBBERNAUT, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + +/mob/living/basic/blob_minion/blobbernaut/death(gibbed) + flick("blobbernaut_death", src) + return ..() + +/// This variant is the one actually spawned by blob factories, takes damage when away from blob tiles +/mob/living/basic/blob_minion/blobbernaut/minion + gold_core_spawnable = NO_SPAWN + /// Is our factory dead? + var/orphaned = FALSE + +/mob/living/basic/blob_minion/blobbernaut/minion/Life(seconds_per_tick, times_fired) + . = ..() + if (!.) + return FALSE + var/damage_sources = 0 + var/list/blobs_in_area = range(2, src) + + if (!(locate(/obj/structure/blob) in blobs_in_area)) + damage_sources++ + + if (orphaned) + damage_sources++ + else + var/particle_colour = atom_colours[FIXED_COLOUR_PRIORITY] || COLOR_BLACK + if (locate(/obj/structure/blob/special/core) in blobs_in_area) + heal_overall_damage(maxHealth * BLOBMOB_BLOBBERNAUT_HEALING_CORE * seconds_per_tick) + var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(src)) + heal_effect.color = particle_colour + + if (locate(/obj/structure/blob/special/node) in blobs_in_area) + heal_overall_damage(maxHealth * BLOBMOB_BLOBBERNAUT_HEALING_NODE * seconds_per_tick) + var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(src)) + heal_effect.color = particle_colour + + if (damage_sources == 0) + return FALSE + + // take 2.5% of max health as damage when not near the blob or if the naut has no factory, 5% if both + apply_damage(maxHealth * BLOBMOB_BLOBBERNAUT_HEALTH_DECAY * damage_sources * seconds_per_tick, damagetype = TOX) // We reduce brute damage + var/mutable_appearance/harming = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "nautdamage", MOB_LAYER + 0.01) + harming.appearance_flags = RESET_COLOR + harming.color = atom_colours[FIXED_COLOUR_PRIORITY] || COLOR_WHITE + harming.dir = dir + flick_overlay_view(harming, 0.8 SECONDS) + return TRUE + +/// Called by the blob creation power to give us a mind and a basic task orientation +/mob/living/basic/blob_minion/blobbernaut/minion/proc/assign_key(ckey, datum/blobstrain/blobstrain) + key = ckey + flick("blobbernaut_produce", src) + health = maxHealth / 2 // Start out injured to encourage not beelining away from the blob + SEND_SOUND(src, sound('sound/effects/blobattack.ogg')) + SEND_SOUND(src, sound('sound/effects/attackblob.ogg')) + to_chat(src, span_infoplain("You are powerful, hard to kill, and slowly regenerate near nodes and cores, [span_cultlarge("but will slowly die if not near the blob")] or if the factory that made you is killed.")) + to_chat(src, span_infoplain("You can communicate with other blobbernauts and overminds telepathically by attempting to speak normally")) + to_chat(src, span_infoplain("Your overmind's blob reagent is: [blobstrain.name]!")) + to_chat(src, span_infoplain("The [blobstrain.name] reagent [blobstrain.shortdesc ? "[blobstrain.shortdesc]" : "[blobstrain.description]"]")) + +/// Set our attack damage based on blob's properties +/mob/living/basic/blob_minion/blobbernaut/minion/on_strain_updated(mob/camera/blob/overmind, datum/blobstrain/new_strain) + if (isnull(overmind)) + melee_damage_lower = initial(melee_damage_lower) + melee_damage_upper = initial(melee_damage_upper) + attack_verb_continuous = initial(attack_verb_continuous) + return + melee_damage_lower = BLOBMOB_BLOBBERNAUT_DMG_LOWER + melee_damage_upper = BLOBMOB_BLOBBERNAUT_DMG_UPPER + attack_verb_continuous = new_strain.blobbernaut_message + +/// Called by our factory to inform us that it's not going to support us financially any more +/mob/living/basic/blob_minion/blobbernaut/minion/on_factory_destroyed() + . = ..() + orphaned = TRUE + throw_alert("nofactory", /atom/movable/screen/alert/nofactory) diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm b/code/modules/mob/living/basic/clown/clown.dm similarity index 62% rename from code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm rename to code/modules/mob/living/basic/clown/clown.dm index 8b1fa4a2d068..ff5410fd0593 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm +++ b/code/modules/mob/living/basic/clown/clown.dm @@ -1,4 +1,4 @@ -/mob/living/simple_animal/hostile/retaliate/clown +/mob/living/basic/clown name = "Clown" desc = "A denizen of clown planet." icon = 'icons/mob/simple/clown_mobs.dmi' @@ -8,189 +8,113 @@ icon_gib = "clown_gib" health_doll_icon = "clown" //if >32x32, it will use this generic. for all the huge clown mobs that subtype from this mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - turns_per_move = 5 response_disarm_continuous = "gently pushes aside" response_disarm_simple = "gently push aside" response_harm_continuous = "robusts" response_harm_simple = "robust" - speak = list("HONK", "Honk!", "Welcome to clown planet!") - emote_see = list("honks", "squeaks") - speak_chance = 1 - istate = ISTATE_HARM|ISTATE_BLOCKING + istate = ISTATE_HARM maxHealth = 75 health = 75 - speed = 1 - harm_intent_damage = 8 melee_damage_lower = 10 melee_damage_upper = 10 attack_sound = 'sound/items/bikehorn.ogg' - obj_damage = 0 + attacked_sound = 'sound/items/bikehorn.ogg' environment_smash = ENVIRONMENT_SMASH_NONE - del_on_death = 1 - loot = list(/obj/effect/mob_spawn/corpse/human/clown) + basic_mob_flags = DEL_ON_DEATH initial_language_holder = /datum/language_holder/clown - atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 270 - maxbodytemp = 370 + habitable_atmos = list("min_oxy" = 5, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = T0C + maximum_survivable_temperature = (T0C + 100) unsuitable_atmos_damage = 10 unsuitable_heat_damage = 15 - footstep_type = FOOTSTEP_MOB_SHOE faction = list(FACTION_CLOWN) - var/attack_reagent + ai_controller = /datum/ai_controller/basic_controller/clown + speed = 1.4 //roughly close to simpleanimal clowns + ///list of stuff we drop on death + var/list/loot = list(/obj/effect/mob_spawn/corpse/human/clown) + ///blackboard emote list + var/list/emotes = list( + BB_EMOTE_SAY = list("HONK", "Honk!", "Welcome to clown planet!"), + BB_EMOTE_HEAR = list("honks", "squeaks"), + BB_EMOTE_SOUND = list('sound/items/bikehorn.ogg'), //WE LOVE TO PARTY + BB_SPEAK_CHANCE = 5, + ) + ///do we waddle (honk) + var/waddles = TRUE -/mob/living/simple_animal/hostile/retaliate/clown/Initialize(mapload) +/mob/living/basic/clown/Initialize(mapload) . = ..() - if(attack_reagent) - var/static/list/injection_range = list(1, 5) - AddElement(/datum/element/venomous, attack_reagent, injection_range) - -/mob/living/simple_animal/hostile/retaliate/clown/attack_hand(mob/living/carbon/human/user, list/modifiers) - ..() - playsound(loc, 'sound/items/bikehorn.ogg', 50, TRUE) + AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_SHOE) + AddComponent(/datum/component/ai_retaliate_advanced, CALLBACK(src, PROC_REF(retaliate_callback))) + ai_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, emotes) + //im not putting dynamic humans or whatever its called here because this is the base path of nonhuman clownstrosities + if(waddles) + AddElement(/datum/element/waddling) + if(length(loot)) + loot = string_list(loot) + AddElement(/datum/element/death_drops, loot) + +/mob/living/basic/clown/proc/retaliate_callback(mob/living/attacker) + if (!istype(attacker)) + return + for (var/mob/living/basic/clown/harbringer in oview(src, 7)) + harbringer.ai_controller.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, attacker) -/mob/living/simple_animal/hostile/retaliate/clown/AttackingTarget(atom/attacked_target) - if(!istype(attacked_target, /obj/item/food/grown/banana/bunch)) +/mob/living/basic/clown/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE) + if(!istype(target, /obj/item/food/grown/banana/bunch)) return ..() - var/obj/item/food/grown/banana/bunch/unripe_bunch = attacked_target + var/obj/item/food/grown/banana/bunch/unripe_bunch = target unripe_bunch.start_ripening() - log_combat(src, attacked_target, "honksposivley ripened") + log_combat(src, target, "explosively ripened") -/mob/living/simple_animal/hostile/retaliate/clown/lube +/mob/living/basic/clown/lube name = "Living Lube" desc = "A puddle of lube brought to life by the honkmother." icon_state = "lube" icon_living = "lube" - turns_per_move = 1 response_help_continuous = "dips a finger into" response_help_simple = "dip a finger into" response_disarm_continuous = "gently scoops and pours aside" response_disarm_simple = "gently scoop and pour aside" - emote_see = list("bubbles", "oozes") - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/particle_effect/fluid/foam) + emotes = list( + BB_EMOTE_SAY = list("HONK", "Honk!", "Welcome to clown planet!"), + BB_EMOTE_HEAR = list("bubbles", "oozes"), + ) + waddles = FALSE + loot = list( + /obj/effect/spawner/foam_starter/small, + /obj/item/clothing/mask/gas/clown_hat, + ) -/mob/living/simple_animal/hostile/retaliate/clown/lube/Initialize(mapload) +/mob/living/basic/clown/lube/Initialize(mapload) . = ..() AddElement(/datum/element/snailcrawl) -/mob/living/simple_animal/hostile/retaliate/clown/banana - name = "Clownana" - desc = "A fusion of clown and banana DNA birthed from a botany experiment gone wrong." - icon_state = "banana tree" - icon_living = "banana tree" - response_disarm_continuous = "peels" - response_disarm_simple = "peel" - response_harm_continuous = "peels" - response_harm_simple = "peel" - turns_per_move = 1 - speak = list("HONK", "Honk!", "YA-HONK!!!") - emote_see = list("honks", "bites into the banana", "plucks a banana off its head", "photosynthesizes") - maxHealth = 120 - health = 120 - speed = -1 - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap, /obj/item/seeds/banana) - ///Our peel dropping ability - var/datum/action/cooldown/rustle/banana_rustle - ///Our banana bunch spawning ability - var/datum/action/cooldown/exquisite_bunch/banana_bunch - -/mob/living/simple_animal/hostile/retaliate/clown/banana/Initialize(mapload) - . = ..() - banana_rustle = new() - banana_rustle.Grant(src) - banana_bunch = new() - banana_bunch.Grant(src) - -/mob/living/simple_animal/hostile/retaliate/clown/banana/Destroy() - . = ..() - QDEL_NULL(banana_rustle) - QDEL_NULL(banana_bunch) - -///drops peels around the mob when activated -/datum/action/cooldown/rustle - name = "Rustle" - desc = "Shake loose a few banana peels." - cooldown_time = 8 SECONDS - button_icon_state = "rustle" - button_icon = 'icons/mob/actions/actions_clown.dmi' - background_icon_state = "bg_nature" - overlay_icon_state = "bg_nature_border" - ///which type of peel to spawn - var/banana_type = /obj/item/grown/bananapeel - ///How many peels to spawn - var/peel_amount = 3 - -/datum/action/cooldown/rustle/Activate(atom/target) - . = ..() - var/list/reachable_turfs = list() - for(var/turf/adjacent_turf in RANGE_TURFS(1, owner.loc)) - if(adjacent_turf == owner.loc || !owner.CanReach(adjacent_turf) || !isopenturf(adjacent_turf)) - continue - reachable_turfs += adjacent_turf - - var/peels_to_spawn = min(peel_amount, reachable_turfs.len) - for(var/i in 1 to peels_to_spawn) - new banana_type(pick_n_take(reachable_turfs)) - playsound(owner, 'sound/creatures/clown/clownana_rustle.ogg', 60) - animate(owner, time = 1, pixel_x = 6, easing = CUBIC_EASING | EASE_OUT) - animate(time = 2, pixel_x = -8, easing = CUBIC_EASING) - animate(time = 1, pixel_x = 0, easing = CUBIC_EASING | EASE_IN) - StartCooldown() - -///spawns a plumb bunch of bananas imbued with mystical power. -/datum/action/cooldown/exquisite_bunch - name = "Exquisite Bunch" - desc = "Pluck your finest bunch of bananas from your head. This bunch is especially nutrious to monkeykind. A gentle tap will trigger an explosive ripening process." - button_icon = 'icons/obj/hydroponics/harvest.dmi' - cooldown_time = 60 SECONDS - button_icon_state = "banana_bunch" - background_icon_state = "bg_nature" - overlay_icon_state = "bg_nature_border" - ///If we are currently activating our ability. - var/activating = FALSE - -/datum/action/cooldown/exquisite_bunch/Trigger(trigger_flags, atom/target) - if(activating) - return - var/bunch_turf = get_step(owner.loc, owner.dir) - if(!bunch_turf) - return - if(!owner.CanReach(bunch_turf) || !isopenturf(bunch_turf)) - owner.balloon_alert(owner, "can't do that here!") - return - activating = TRUE - if(!do_after(owner, 1 SECONDS)) - activating = FALSE - return - playsound(owner, 'sound/creatures/clown/hehe.ogg', 100) - if(!do_after(owner, 1 SECONDS)) - activating = FALSE - return - activating = FALSE - return ..() - -/datum/action/cooldown/exquisite_bunch/Activate(atom/target) - . = ..() - new /obj/item/food/grown/banana/bunch(get_step(owner.loc, owner.dir)) - playsound(owner, 'sound/items/bikehorn.ogg', 60) - addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(playsound), owner, 'sound/creatures/clown/hohoho.ogg', 100, 1), 1 SECONDS) - StartCooldown() - -/mob/living/simple_animal/hostile/retaliate/clown/honkling +/mob/living/basic/clown/honkling name = "Honkling" desc = "A divine being sent by the Honkmother to spread joy. It's not dangerous, but it's a bit of a nuisance." icon_state = "honkling" icon_living = "honkling" - turns_per_move = 1 - speed = -10 - harm_intent_damage = 1 + speed = 1.1 melee_damage_lower = 1 melee_damage_upper = 1 attack_verb_continuous = "cheers up" attack_verb_simple = "cheer up" - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap, /obj/item/seeds/banana/bluespace) - attack_reagent = /datum/reagent/consumable/laughter + loot = list( + /obj/item/clothing/mask/gas/clown_hat, + /obj/effect/gibspawner/human, + /obj/item/soap, + /obj/item/seeds/banana/bluespace, + ) -/mob/living/simple_animal/hostile/retaliate/clown/fleshclown +/mob/living/basic/clown/honkling/Initialize(mapload) + . = ..() + var/static/list/injection_range + if(!injection_range) + injection_range = string_numbers_list(list(1, 5)) + AddElement(/datum/element/venomous, /datum/reagent/consumable/laughter, injection_range) + +/mob/living/basic/clown/fleshclown name = "Fleshclown" desc = "A being forged out of the pure essence of pranking, cursed into existence by a cruel maker." icon_state = "fleshclown" @@ -201,51 +125,70 @@ response_disarm_simple = "sink your hands into the spongy flesh of" response_harm_continuous = "cleanses the world of" response_harm_simple = "cleanse the world of" - speak = list("HONK", "Honk!", "I didn't ask for this", "I feel constant and horrible pain", "YA-HONK!!!", "this body is a merciless and unforgiving prison", "I was born out of mirthful pranking but I live in suffering") - emote_see = list("honks", "sweats", "jiggles", "contemplates its existence") - speak_chance = 5 - dextrous = TRUE maxHealth = 140 health = 140 - speed = -5 + speed = 1 melee_damage_upper = 15 attack_verb_continuous = "limply slaps" attack_verb_simple = "limply slap" obj_damage = 5 - loot = list(/obj/item/clothing/suit/hooded/bloated_human, /obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap) + loot = list( + /obj/effect/gibspawner/human, + /obj/item/clothing/mask/gas/clown_hat, + /obj/item/soap, + /obj/item/clothing/suit/hooded/bloated_human, + ) + emotes = list( + BB_EMOTE_SAY = list( + "HONK", + "Honk!", + "I didn't ask for this", + "I feel constant and horrible pain", + "I was born out of mirthful pranking but I live in suffering", + "This body is a merciless and unforgiving prison", + "YA-HONK!!!", + ), + BB_EMOTE_HEAR = list("honks", "contemplates its existence"), + BB_EMOTE_SEE = list("sweats", "jiggles"), + BB_SPEAK_CHANCE = 5, + ) -/mob/living/simple_animal/hostile/retaliate/clown/fleshclown/Initialize(mapload) +/mob/living/basic/clown/fleshclown/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) -/mob/living/simple_animal/hostile/retaliate/clown/longface +/mob/living/basic/clown/longface name = "Longface" desc = "Often found walking into the bar." icon_state = "long face" icon_living = "long face" move_resist = INFINITY - turns_per_move = 10 response_help_continuous = "tries to awkwardly hug" response_help_simple = "try to awkwardly hug" response_disarm_continuous = "pushes the unwieldy frame of" response_disarm_simple = "push the unwieldy frame of" response_harm_continuous = "tries to shut up" response_harm_simple = "try to shut up" - speak = list("YA-HONK!!!") - emote_see = list("honks", "squeaks") - speak_chance = 60 maxHealth = 150 health = 150 pixel_x = -16 base_pixel_x = -16 - speed = 10 - harm_intent_damage = 5 + speed = 3 melee_damage_lower = 5 attack_verb_continuous = "YA-HONKs" attack_verb_simple = "YA-HONK" - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap) + loot = list( + /obj/effect/gibspawner/human, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("YA-HONK!!!"), + BB_EMOTE_HEAR = list("honks", "squeaks"), + BB_SPEAK_CHANCE = 60, + ) -/mob/living/simple_animal/hostile/retaliate/clown/clownhulk +/mob/living/basic/clown/clownhulk name = "Honk Hulk" desc = "A cruel and fearsome clown. Don't make him angry." icon_state = "honkhulk" @@ -258,24 +201,30 @@ response_disarm_simple = "foolishly push" response_harm_continuous = "angers" response_harm_simple = "anger" - speak = list("HONK", "Honk!", "HAUAUANK!!!", "GUUURRRRAAAHHH!!!") - emote_see = list("honks", "sweats", "grunts") - speak_chance = 5 maxHealth = 400 health = 400 pixel_x = -16 base_pixel_x = -16 speed = 2 - harm_intent_damage = 15 melee_damage_lower = 15 melee_damage_upper = 20 attack_verb_continuous = "pummels" attack_verb_simple = "pummel" obj_damage = 30 environment_smash = ENVIRONMENT_SMASH_WALLS - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap) + loot = list( + /obj/effect/gibspawner/human, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("HONK", "Honk!", "HAUAUANK!!!", "GUUURRRRAAAHHH!!!"), + BB_EMOTE_HEAR = list("honks", "grunts"), + BB_EMOTE_SEE = list("sweats"), + BB_SPEAK_CHANCE = 5, + ) -/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown +/mob/living/basic/clown/clownhulk/chlown name = "Chlown" desc = "A real lunkhead who somehow gets all the girls." icon_state = "chlown" @@ -287,18 +236,26 @@ response_disarm_simple = "try to assert dominance over" response_harm_continuous = "makes a weak beta attack at" response_harm_simple = "make a weak beta attack at" - speak = list("HONK", "Honk!", "Bruh", "cheeaaaahhh?") - emote_see = list("asserts his dominance", "emasculates everyone implicitly") maxHealth = 500 health = 500 - speed = -2 + speed = -2 //ridicilously fast but i dont even know what this is used for armour_penetration = 20 attack_verb_continuous = "steals the girlfriend of" attack_verb_simple = "steal the girlfriend of" attack_sound = 'sound/items/airhorn2.ogg' - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/effect/particle_effect/fluid/foam, /obj/item/soap) + loot = list( + /obj/effect/gibspawner/human, + /obj/effect/spawner/foam_starter/small, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("HONK", "Honk!", "Bruh", "cheeaaaahhh?"), + BB_EMOTE_SEE = list("asserts his dominance", "emasculates everyone implicitly"), + BB_SPEAK_CHANCE = 5, + ) -/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus +/mob/living/basic/clown/clownhulk/honkmunculus name = "Honkmunculus" desc = "A slender wiry figure of alchemical origin." icon_state = "honkmunculus" @@ -307,82 +264,107 @@ response_help_simple = "skeptically poke" response_disarm_continuous = "pushes the unwieldy frame of" response_disarm_simple = "push the unwieldy frame of" - speak = list("honk") - emote_see = list("squirms", "writhes") - speak_chance = 1 maxHealth = 200 health = 200 - speed = -5 - harm_intent_damage = 5 + speed = 1 melee_damage_lower = 5 melee_damage_upper = 10 attack_verb_continuous = "ferociously mauls" attack_verb_simple = "ferociously maul" environment_smash = ENVIRONMENT_SMASH_NONE - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/xeno/bodypartless, /obj/effect/particle_effect/fluid/foam, /obj/item/soap) - attack_reagent = /datum/reagent/peaceborg/confuse + loot = list( + /obj/effect/gibspawner/xeno/bodypartless, + /obj/effect/spawner/foam_starter/small, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("honk"), + BB_EMOTE_SEE = list("squirms", "writhes"), + ) + +/mob/living/basic/clown/clownhulk/honkmunculus/Initialize(mapload) + . = ..() + var/static/list/injection_range + if(!injection_range) + injection_range = string_numbers_list(list(1, 5)) + AddElement(/datum/element/venomous, /datum/reagent/peaceborg/confuse, injection_range) -/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/destroyer +/mob/living/basic/clown/clownhulk/destroyer name = "The Destroyer" desc = "An ancient being born of arcane honking." icon_state = "destroyer" icon_living = "destroyer" response_disarm_continuous = "bounces off of" response_harm_continuous = "bounces off of" - speak = list("HONK!!!", "The Honkmother is merciful, so I must act out her wrath.", "parce mihi ad beatus honkmother placet mihi ut peccata committere,", "DIE!!!") maxHealth = 400 health = 400 speed = 5 - harm_intent_damage = 30 melee_damage_lower = 20 melee_damage_upper = 40 armour_penetration = 30 - stat_attack = HARD_CRIT attack_verb_continuous = "acts out divine vengeance on" attack_verb_simple = "act out divine vengeance on" obj_damage = 50 environment_smash = ENVIRONMENT_SMASH_RWALLS - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/effect/particle_effect/fluid/foam, /obj/item/soap) + ai_controller = /datum/ai_controller/basic_controller/clown/murder + loot = list( + /obj/effect/gibspawner/human, + /obj/effect/spawner/foam_starter/small, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("HONK!!!", "The Honkmother is merciful, so I must act out her wrath.", "parce mihi ad beatus honkmother placet mihi ut peccata committere,", "DIE!!!"), + BB_EMOTE_HEAR = list("honks", "grunts"), + BB_EMOTE_SEE = list("sweats"), + BB_SPEAK_CHANCE = 5, + ) -/mob/living/simple_animal/hostile/retaliate/clown/mutant +/mob/living/basic/clown/mutant name = "Unknown" desc = "Kill it for its own sake." icon_state = "mutant" icon_living = "mutant" move_resist = INFINITY - turns_per_move = 10 response_help_continuous = "reluctantly sinks a finger into" response_help_simple = "reluctantly sink a finger into" response_disarm_continuous = "squishes into" response_disarm_simple = "squish into" response_harm_continuous = "squishes into" response_harm_simple = "squish into" - speak = list("aaaaaahhhhuuhhhuhhhaaaaa", "AAAaaauuuaaAAAaauuhhh", "huuuuuh... hhhhuuuooooonnnnkk", "HuaUAAAnKKKK") - emote_see = list("squirms", "writhes", "pulsates", "froths", "oozes") - speak_chance = 10 maxHealth = 130 health = 130 pixel_x = -16 base_pixel_x = -16 speed = -5 - harm_intent_damage = 10 melee_damage_lower = 10 melee_damage_upper = 20 attack_verb_continuous = "awkwardly flails at" attack_verb_simple = "awkwardly flail at" - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/xeno/bodypartless, /obj/item/soap, /obj/effect/gibspawner/generic, /obj/effect/gibspawner/generic/animal, /obj/effect/gibspawner/human/bodypartless, /obj/effect/gibspawner/human) + loot = list( + /obj/effect/gibspawner/generic, + /obj/effect/gibspawner/generic/animal, + /obj/effect/gibspawner/human, + /obj/effect/gibspawner/human/bodypartless, + /obj/effect/gibspawner/xeno/bodypartless, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("aaaaaahhhhuuhhhuhhhaaaaa", "AAAaaauuuaaAAAaauuhhh", "huuuuuh... hhhhuuuooooonnnnkk", "HuaUAAAnKKKK"), + BB_EMOTE_SEE = list("squirms", "writhes", "pulsates", "froths", "oozes"), + BB_SPEAK_CHANCE = 10, + ) -/mob/living/simple_animal/hostile/retaliate/clown/mutant/slow +/mob/living/basic/clown/mutant/slow speed = 20 - move_to_delay = 60 -/mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton +/mob/living/basic/clown/mutant/glutton name = "banana glutton" desc = "Something that was once a clown" icon_state = "glutton" icon_living = "glutton" - speak = list("hey, buddy", "HONK!!!", "H-h-h-H-HOOOOONK!!!!", "HONKHONKHONK!!!", "HEY, BUCKO, GET BACK HERE!!!", "HOOOOOOOONK!!!") - emote_see = list("jiggles", "wobbles") health = 200 mob_size = MOB_SIZE_LARGE speed = 1 @@ -392,37 +374,47 @@ damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 2, STAMINA = 0, OXY = 1) attack_verb_continuous = "slams" attack_verb_simple = "slam" - loot = list(/obj/effect/gibspawner/xeno/bodypartless, /obj/effect/gibspawner/generic, /obj/effect/gibspawner/generic/animal, /obj/effect/gibspawner/human/bodypartless) + loot = list( + /obj/effect/gibspawner/generic, + /obj/effect/gibspawner/generic/animal, + /obj/effect/gibspawner/human/bodypartless, + /obj/effect/gibspawner/xeno/bodypartless, + ) + emotes = list( + BB_EMOTE_SAY = list("hey, buddy", "HONK!!!", "H-h-h-H-HOOOOONK!!!!", "HONKHONKHONK!!!", "HEY, BUCKO, GET BACK HERE!!!", "HOOOOOOOONK!!!"), + BB_EMOTE_SEE = list("jiggles", "wobbles"), + ) death_sound = 'sound/misc/sadtrombone.ogg' + waddles = FALSE ///This is the list of items we are ready to regurgitate, var/list/prank_pouch = list() -/mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton/Initialize(mapload) +/mob/living/basic/clown/mutant/glutton/Initialize(mapload) . = ..() var/datum/action/cooldown/regurgitate/spit = new(src) spit.Grant(src) - add_cell_sample() + AddElement(/datum/element/swabable, CELL_LINE_TABLE_GLUTTON, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/cheesiehonkers, /obj/item/food/cornchips), tame_chance = 30, bonus_tame_chance = 0, after_tame = CALLBACK(src, PROC_REF(tamed))) -/mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton/attacked_by(obj/item/I, mob/living/user) - if(!check_edible(I)) +/mob/living/basic/clown/mutant/glutton/attacked_by(obj/item/item, mob/living/user) + if(!check_edible(item)) return ..() - eat_atom(I) + eat_atom(item) -/mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton/AttackingTarget(atom/attacked_target) - if(!check_edible(attacked_target)) +/mob/living/basic/clown/mutant/glutton/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE) + if(!check_edible(target)) return ..() - eat_atom(attacked_target) + eat_atom(target) -/mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton/UnarmedAttack(atom/A, proximity_flag) - if(!check_edible(A)) +/mob/living/basic/clown/mutant/glutton/UnarmedAttack(atom/victim, proximity_flag, list/modifiers) + if(!check_edible(victim)) return ..() - eat_atom(A) + eat_atom(victim) ///Returns whether or not the supplied movable atom is edible. -/mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton/proc/check_edible(atom/movable/potential_food) +/mob/living/basic/clown/mutant/glutton/proc/check_edible(atom/movable/potential_food) if(isliving(potential_food)) var/mob/living/living_morsel = potential_food if(living_morsel.mob_size > MOB_SIZE_SMALL) @@ -437,7 +429,7 @@ return TRUE ///This proc eats the atom, certain funny items are stored directly in the prank pouch while bananas grant a heal based on their potency and the peels are retained in the pouch. -/mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton/proc/eat_atom(atom/movable/eaten_atom) +/mob/living/basic/clown/mutant/glutton/proc/eat_atom(atom/movable/eaten_atom) var/static/funny_items = list( /obj/item/food/pie/cream, @@ -461,14 +453,11 @@ playsound(loc,'sound/items/eatfood.ogg', rand(30,50), TRUE) flick("glutton_mouth", src) -/mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton/proc/tamed(mob/living/tamer) +/mob/living/basic/clown/mutant/glutton/proc/tamed(mob/living/tamer) buckle_lying = 0 AddElement(/datum/element/ridable, /datum/component/riding/creature/glutton) -/mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton/add_cell_sample() - AddElement(/datum/element/swabable, CELL_LINE_TABLE_GLUTTON, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) - -/mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton/Exited(atom/movable/gone, direction) +/mob/living/basic/clown/mutant/glutton/Exited(atom/movable/gone, direction) . = ..() prank_pouch -= gone @@ -509,12 +498,12 @@ return FALSE // Hardcoded to only work with gluttons. Come back next year - return istype(owner, /mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton) + return istype(owner, /mob/living/basic/clown/mutant/glutton) /datum/action/cooldown/regurgitate/Activate(atom/spit_at) StartCooldown(cooldown_time / 4) - var/mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton/pouch_owner = owner + var/mob/living/basic/clown/mutant/glutton/pouch_owner = owner if(!length(pouch_owner.prank_pouch)) pouch_owner.icon_state = initial(pouch_owner.icon_state) to_chat(pouch_owner, span_notice("Your prank pouch is empty.")) @@ -528,3 +517,113 @@ StartCooldown() return TRUE + +/mob/living/basic/clown/banana + name = "Clownana" + desc = "A fusion of clown and banana DNA birthed from a botany experiment gone wrong." + icon_state = "banana tree" + icon_living = "banana tree" + response_disarm_continuous = "peels" + response_disarm_simple = "peel" + response_harm_continuous = "peels" + response_harm_simple = "peel" + maxHealth = 120 + health = 120 + speed = -1 + loot = list( + /obj/effect/gibspawner/human, + /obj/item/seeds/banana, + /obj/item/soap, + /obj/item/clothing/mask/gas/clown_hat, + ) + emotes = list( + BB_EMOTE_SAY = list("HONK", "Honk!", "YA-HONK!!!"), + BB_EMOTE_SEE = list("bites into the banana", "plucks a banana off its head", "photosynthesizes"), + BB_EMOTE_SOUND = list('sound/items/bikehorn.ogg'), + ) + ///Our peel dropping ability + var/datum/action/cooldown/rustle/banana_rustle + ///Our banana bunch spawning ability + var/datum/action/cooldown/exquisite_bunch/banana_bunch + +/mob/living/basic/clown/banana/Initialize(mapload) + . = ..() + banana_rustle = new() + banana_rustle.Grant(src) + banana_bunch = new() + banana_bunch.Grant(src) + +/mob/living/basic/clown/banana/Destroy() + . = ..() + QDEL_NULL(banana_rustle) + QDEL_NULL(banana_bunch) + +///drops peels around the mob when activated +/datum/action/cooldown/rustle + name = "Rustle" + desc = "Shake loose a few banana peels." + cooldown_time = 8 SECONDS + button_icon_state = "rustle" + button_icon = 'icons/mob/actions/actions_clown.dmi' + background_icon_state = "bg_nature" + overlay_icon_state = "bg_nature_border" + ///which type of peel to spawn + var/banana_type = /obj/item/grown/bananapeel + ///How many peels to spawn + var/peel_amount = 3 + +/datum/action/cooldown/rustle/Activate(atom/target) + . = ..() + var/list/reachable_turfs = list() + for(var/turf/adjacent_turf in RANGE_TURFS(1, owner.loc)) + if(adjacent_turf == owner.loc || !owner.CanReach(adjacent_turf) || !isopenturf(adjacent_turf)) + continue + reachable_turfs += adjacent_turf + + var/peels_to_spawn = min(peel_amount, reachable_turfs.len) + for(var/i in 1 to peels_to_spawn) + new banana_type(pick_n_take(reachable_turfs)) + playsound(owner, 'sound/creatures/clown/clownana_rustle.ogg', 60) + animate(owner, time = 1, pixel_x = 6, easing = CUBIC_EASING | EASE_OUT) + animate(time = 2, pixel_x = -8, easing = CUBIC_EASING) + animate(time = 1, pixel_x = 0, easing = CUBIC_EASING | EASE_IN) + StartCooldown() + +///spawns a plumb bunch of bananas imbued with mystical power. +/datum/action/cooldown/exquisite_bunch + name = "Exquisite Bunch" + desc = "Pluck your finest bunch of bananas from your head. This bunch is especially nutrious to monkeykind. A gentle tap will trigger an explosive ripening process." + button_icon = 'icons/obj/hydroponics/harvest.dmi' + cooldown_time = 60 SECONDS + button_icon_state = "banana_bunch" + background_icon_state = "bg_nature" + overlay_icon_state = "bg_nature_border" + ///If we are currently activating our ability. + var/activating = FALSE + +/datum/action/cooldown/exquisite_bunch/Trigger(trigger_flags, atom/target) + if(activating) + return + var/bunch_turf = get_step(owner.loc, owner.dir) + if(!bunch_turf) + return + if(!owner.CanReach(bunch_turf) || !isopenturf(bunch_turf)) + owner.balloon_alert(owner, "can't do that here!") + return + activating = TRUE + if(!do_after(owner, 1 SECONDS)) + activating = FALSE + return + playsound(owner, 'sound/creatures/clown/hehe.ogg', 100) + if(!do_after(owner, 1 SECONDS)) + activating = FALSE + return + activating = FALSE + return ..() + +/datum/action/cooldown/exquisite_bunch/Activate(atom/target) + . = ..() + new /obj/item/food/grown/banana/bunch(get_step(owner.loc, owner.dir)) + playsound(owner, 'sound/items/bikehorn.ogg', 60) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(playsound), owner, 'sound/creatures/clown/hohoho.ogg', 100, 1), 1 SECONDS) + StartCooldown() diff --git a/code/modules/mob/living/basic/clown/clown_ai.dm b/code/modules/mob/living/basic/clown/clown_ai.dm new file mode 100644 index 000000000000..b3f5a9f9aef4 --- /dev/null +++ b/code/modules/mob/living/basic/clown/clown_ai.dm @@ -0,0 +1,20 @@ +/datum/ai_controller/basic_controller/clown + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = null, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/random_speech/blackboard, + ) + +/datum/ai_controller/basic_controller/clown/murder + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = null, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + ) diff --git a/code/modules/mob/living/basic/cult/constructs/_construct.dm b/code/modules/mob/living/basic/cult/constructs/_construct.dm new file mode 100644 index 000000000000..80f4ecf1bc6e --- /dev/null +++ b/code/modules/mob/living/basic/cult/constructs/_construct.dm @@ -0,0 +1,135 @@ +/mob/living/basic/construct + icon = 'icons/mob/nonhuman-player/cult.dmi' + gender = NEUTER + basic_mob_flags = DEL_ON_DEATH + istate = ISTATE_HARM + mob_biotypes = MOB_MINERAL + faction = list(FACTION_CULT) + unsuitable_atmos_damage = 0 + minimum_survivable_temperature = 0 + maximum_survivable_temperature = INFINITY + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + pressure_resistance = 100 + speed = 0 + unique_name = TRUE + initial_language_holder = /datum/language_holder/construct + death_message = "collapses in a shattered heap." + + speak_emote = list("hisses") + response_help_continuous = "thinks better of touching" + response_help_simple = "think better of touching" + response_disarm_continuous = "flails at" + response_disarm_simple = "flail at" + response_harm_continuous = "punches" + response_harm_simple = "punch" + melee_attack_cooldown = CLICK_CD_MELEE + + // Vivid red, cause cult theme + lighting_cutoff_red = 30 + lighting_cutoff_green = 5 + lighting_cutoff_blue = 20 + + /// List of spells that this construct can cast + var/list/construct_spells = list() + /// Flavor text shown to players when they spawn as this construct + var/playstyle_string = "You are a generic construct. Your job is to not exist, and you should probably adminhelp this." + /// The construct's master + var/master = null + /// Whether this construct is currently seeking nar nar + var/seeking = FALSE + /// Whether this construct can repair other constructs or cult buildings. Gets the healing_touch component if so. + var/can_repair = FALSE + /// Whether this construct can repair itself. Works independently of can_repair. + var/can_repair_self = FALSE + /// Theme controls color. THEME_CULT is red THEME_WIZARD is purple and THEME_HOLY is blue + var/theme = THEME_CULT + /// Can this construct smash walls? Gets the wall_smasher element if so. + var/smashes_walls = FALSE + /// The different flavors of goop constructs can drop, depending on theme. + var/static/list/remains_by_theme = list( + THEME_CULT = list(/obj/item/ectoplasm/construct), + THEME_HOLY = list(/obj/item/ectoplasm/angelic), + THEME_WIZARD = list(/obj/item/ectoplasm/mystic), + ) + +/mob/living/basic/construct/Initialize(mapload) + . = ..() + AddElement(/datum/element/simple_flying) + var/list/remains = string_list(remains_by_theme[theme]) + if(length(remains)) + AddElement(/datum/element/death_drops, remains) + if(smashes_walls) + AddElement(/datum/element/wall_smasher, strength_flag = ENVIRONMENT_SMASH_WALLS) + if(can_repair) + AddComponent(\ + /datum/component/healing_touch,\ + heal_brute = 5,\ + heal_burn = 0,\ + heal_time = 0,\ + valid_targets_typecache = typecacheof(list(/mob/living/basic/construct, /mob/living/basic/shade)),\ + valid_biotypes = MOB_MINERAL | MOB_SPIRIT,\ + self_targeting = can_repair_self ? HEALING_TOUCH_ANYONE : HEALING_TOUCH_NOT_SELF,\ + action_text = "%SOURCE% begins repairing %TARGET%'s dents.",\ + complete_text = "%TARGET%'s dents are repaired.",\ + show_health = TRUE,\ + heal_color = COLOR_CULT_RED,\ + ) + var/static/list/structure_types = typecacheof(list(/obj/structure/destructible/cult)) + AddElement(\ + /datum/element/structure_repair,\ + structure_types_typecache = structure_types,\ + ) + add_traits(list(TRAIT_HEALS_FROM_CULT_PYLONS, TRAIT_SPACEWALK), INNATE_TRAIT) + for(var/spell in construct_spells) + var/datum/action/new_spell = new spell(src) + new_spell.Grant(src) + + var/spell_count = 1 + for(var/datum/action/spell as anything in actions) + if(!(spell.type in construct_spells)) + continue + + var/pos = 2 + spell_count * 31 + if(construct_spells.len >= 4) + pos -= 31 * (construct_spells.len - 4) + spell.default_button_position = "6:[pos],4:-2" // Set the default position to this random position + spell_count++ + update_action_buttons() + + if(icon_state) + add_overlay("glow_[icon_state]_[theme]") + +/mob/living/basic/construct/Login() + . = ..() + if(!. || !client) + return FALSE + to_chat(src, span_bold(playstyle_string)) + +/mob/living/basic/construct/examine(mob/user) + var/text_span + switch(theme) + if(THEME_CULT) + text_span = "cult" + if(THEME_WIZARD) + text_span = "purple" + if(THEME_HOLY) + text_span = "blue" + . = list("This is [icon2html(src, user)] \a [src]!\n[desc]") + if(health < maxHealth) + if(health >= maxHealth/2) + . += span_warning("[p_They()] look[p_s()] slightly dented.") + else + . += span_warning(span_bold("[p_They()] look[p_s()] severely dented!")) + . += "" + return . + +/mob/living/basic/construct/narsie_act() + return + +/mob/living/basic/construct/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE) + return FALSE + +/// Construct ectoplasm. Largely a placeholder, since the death drop element needs a unique list. +/obj/item/ectoplasm/construct + name = "blood-red ectoplasm" + desc = "Has a pungent metallic smell." diff --git a/code/modules/mob/living/simple_animal/hostile/constructs/artificer.dm b/code/modules/mob/living/basic/cult/constructs/artificer.dm similarity index 50% rename from code/modules/mob/living/simple_animal/hostile/constructs/artificer.dm rename to code/modules/mob/living/basic/cult/constructs/artificer.dm index 743931c508d0..bf4a086bcdb2 100644 --- a/code/modules/mob/living/simple_animal/hostile/constructs/artificer.dm +++ b/code/modules/mob/living/basic/cult/constructs/artificer.dm @@ -1,4 +1,4 @@ -/mob/living/simple_animal/hostile/construct/artificer +/mob/living/basic/construct/artificer name = "Artificer" real_name = "Artificer" desc = "A bulbous construct dedicated to building and maintaining the Cult of Nar'Sie's armies." @@ -8,15 +8,11 @@ health = 50 response_harm_continuous = "viciously beats" response_harm_simple = "viciously beat" - harm_intent_damage = 5 obj_damage = 60 melee_damage_lower = 5 melee_damage_upper = 5 - retreat_distance = 10 - minimum_distance = 10 //AI artificers will flee like fuck attack_verb_continuous = "rams" attack_verb_simple = "ram" - environment_smash = ENVIRONMENT_SMASH_WALLS attack_sound = 'sound/weapons/punch2.ogg' construct_spells = list( /datum/action/cooldown/spell/conjure/cult_floor, @@ -33,70 +29,39 @@ can_repair = TRUE can_repair_self = TRUE + smashes_walls = TRUE ///The health HUD applied to this mob. var/health_hud = DATA_HUD_MEDICAL_ADVANCED -/mob/living/simple_animal/hostile/construct/artificer/Initialize(mapload) +/mob/living/basic/construct/artificer/Initialize(mapload) . = ..() + AddElement(/datum/element/ai_retaliate) var/datum/atom_hud/datahud = GLOB.huds[health_hud] datahud.show_to(src) -/mob/living/simple_animal/hostile/construct/artificer/Found(atom/thing) //what have we found here? - if(!isconstruct(thing)) //is it a construct? - return FALSE - var/mob/living/simple_animal/hostile/construct/cultie = thing - if(cultie.health < cultie.maxHealth) //is it hurt? let's go heal it if it is - return TRUE +/// Hostile NPC version. Heals nearby constructs and cult structures, avoids targets that aren't extremely hurt. +/mob/living/basic/construct/artificer/hostile + ai_controller = /datum/ai_controller/basic_controller/artificer + smashes_walls = FALSE + melee_attack_cooldown = 2 SECONDS -/mob/living/simple_animal/hostile/construct/artificer/CanAttack(atom/the_target) - if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it - return FALSE - if(Found(the_target) || ..()) //If we Found it or Can_Attack it normally, we Can_Attack it as long as it wasn't invisible - return TRUE //as a note this shouldn't be added to base hostile mobs because it'll mess up retaliate hostile mobs - return FALSE - -/mob/living/simple_animal/hostile/construct/artificer/MoveToTarget(list/possible_targets) - ..() - if(!isliving(target)) - return - - var/mob/living/victim = target - if(isconstruct(victim) && victim.health >= victim.maxHealth) //is this target an unhurt construct? stop trying to heal it - LoseTarget() - return - if(victim.health <= melee_damage_lower+melee_damage_upper) //ey bucko you're hurt as fuck let's go hit you - retreat_distance = null - minimum_distance = 1 - -/mob/living/simple_animal/hostile/construct/artificer/Aggro() - ..() - if(isconstruct(target)) //oh the target is a construct no need to flee - retreat_distance = null - minimum_distance = 1 - -/mob/living/simple_animal/hostile/construct/artificer/LoseAggro() - ..() - retreat_distance = initial(retreat_distance) - minimum_distance = initial(minimum_distance) - -/mob/living/simple_animal/hostile/construct/artificer/hostile //actually hostile, will move around, hit things, heal other constructs - AIStatus = AI_ON - environment_smash = ENVIRONMENT_SMASH_STRUCTURES //only token destruction, don't smash the cult wall NO STOP - -/////////////////////////////Artificer-alts///////////////////////// -/mob/living/simple_animal/hostile/construct/artificer/angelic +// Alternate artificer themes +/mob/living/basic/construct/artificer/angelic desc = "A bulbous construct dedicated to building and maintaining holy armies." theme = THEME_HOLY - loot = list(/obj/item/ectoplasm/angelic) construct_spells = list( /datum/action/cooldown/spell/conjure/soulstone/purified, /datum/action/cooldown/spell/conjure/construct/lesser, /datum/action/cooldown/spell/aoe/magic_missile/lesser, /datum/action/innate/cult/create_rune/revive, ) -/mob/living/simple_animal/hostile/construct/artificer/mystic + +/mob/living/basic/construct/artificer/angelic/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_ANGELIC, INNATE_TRAIT) + +/mob/living/basic/construct/artificer/mystic theme = THEME_WIZARD - loot = list(/obj/item/ectoplasm/mystic) construct_spells = list( /datum/action/cooldown/spell/conjure/cult_floor, /datum/action/cooldown/spell/conjure/cult_wall, @@ -106,7 +71,7 @@ /datum/action/innate/cult/create_rune/revive, ) -/mob/living/simple_animal/hostile/construct/artificer/noncult +/mob/living/basic/construct/artificer/noncult construct_spells = list( /datum/action/cooldown/spell/conjure/cult_floor, /datum/action/cooldown/spell/conjure/cult_wall, diff --git a/code/modules/mob/living/basic/cult/constructs/construct_ai.dm b/code/modules/mob/living/basic/cult/constructs/construct_ai.dm new file mode 100644 index 000000000000..b8417affed94 --- /dev/null +++ b/code/modules/mob/living/basic/cult/constructs/construct_ai.dm @@ -0,0 +1,90 @@ +/** + * Artificers + * + * Artificers will seek out and heal the most wounded construct or shade they can see. + * If there is no one to heal, they will run away from any non-allied mobs. + */ +/datum/ai_controller/basic_controller/artificer + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/same_faction/construct, + BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_WOUNDED_ONLY = TRUE, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_wounded_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/target_retaliate/to_flee, + /datum/ai_planning_subtree/flee_target/from_flee_key, + ) + +/** + * Juggernauts + * + * Juggernauts slowly walk toward non-allied mobs and pummel them to death. + */ +/datum/ai_controller/basic_controller/juggernaut + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/** + * Proteons + * + * Proteons perform cowardly hit-and-run attacks, fleeing melee when struck but returning to fight again. + */ +/datum/ai_controller/basic_controller/proteon + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate/to_flee, + /datum/ai_planning_subtree/flee_target/from_flee_key, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/** + * Wraiths + * + * Wraiths seek out the most injured non-allied mob to beat to death. + */ +/datum/ai_controller/basic_controller/wraith + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_wounded_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Targeting strategy that will only allow mobs that constructs can heal. +/datum/targeting_strategy/basic/same_faction/construct + target_wounded_key = BB_TARGET_WOUNDED_ONLY + +/datum/targeting_strategy/basic/same_faction/construct/can_attack(mob/living/living_mob, atom/the_target, vision_range, check_faction = TRUE) + if(isconstruct(the_target) || istype(the_target, /mob/living/basic/shade)) + return ..() + return FALSE diff --git a/code/modules/mob/living/simple_animal/hostile/constructs/harvester.dm b/code/modules/mob/living/basic/cult/constructs/harvester.dm similarity index 69% rename from code/modules/mob/living/simple_animal/hostile/constructs/harvester.dm rename to code/modules/mob/living/basic/cult/constructs/harvester.dm index 8c5fc8eae37b..30b309948728 100644 --- a/code/modules/mob/living/simple_animal/hostile/constructs/harvester.dm +++ b/code/modules/mob/living/basic/cult/constructs/harvester.dm @@ -1,4 +1,4 @@ -/mob/living/simple_animal/hostile/construct/harvester +/mob/living/basic/construct/harvester name = "Harvester" real_name = "Harvester" desc = "A long, thin construct built to herald Nar'Sie's rise. It'll be all over soon." @@ -24,58 +24,33 @@ can_repair = TRUE slowed_by_drag = FALSE - -/mob/living/simple_animal/hostile/construct/harvester/Bump(atom/thing) +/mob/living/basic/construct/harvester/Initialize(mapload) . = ..() - if(!istype(thing, /turf/closed/wall/mineral/cult) || thing == loc) - return // we can go through cult walls - var/atom/movable/stored_pulling = pulling - - if(stored_pulling) - stored_pulling.setDir(get_dir(stored_pulling.loc, loc)) - stored_pulling.forceMove(loc) - forceMove(thing) - - if(stored_pulling) - start_pulling(stored_pulling, supress_message = TRUE) //drag anything we're pulling through the wall with us by magic + AddElement(\ + /datum/element/amputating_limbs,\ + surgery_time = 0,\ + surgery_verb = "slicing",\ + minimum_stat = CONSCIOUS,\ + ) + AddElement(/datum/element/wall_walker, /turf/closed/wall/mineral/cult) + var/datum/action/innate/seek_prey/seek = new(src) + seek.Grant(src) + seek.Activate() -/mob/living/simple_animal/hostile/construct/harvester/AttackingTarget() - if(!iscarbon(target)) +/// If the attack is a limbless carbon, abort the attack, paralyze them, and get a special message from Nar'Sie. +/mob/living/basic/construct/harvester/resolve_unarmed_attack(atom/attack_target, list/modifiers) + if(!iscarbon(attack_target)) return ..() + var/mob/living/carbon/carbon_target = attack_target - var/mob/living/carbon/victim = target - if(HAS_TRAIT(victim, TRAIT_NODISMEMBER)) - return ..() //ATTACK! - - var/list/parts = list() - var/strong_limbs = 0 - - for(var/obj/item/bodypart/limb as anything in victim.bodyparts) + for(var/obj/item/bodypart/limb as anything in carbon_target.bodyparts) if(limb.body_part == HEAD || limb.body_part == CHEST) continue - if(!(limb.bodypart_flags & BODYPART_UNREMOVABLE)) - parts += limb - else - strong_limbs++ - - if(!LAZYLEN(parts)) - if(strong_limbs) // they have limbs we can't remove, and no parts we can, attack! - return ..() - victim.Paralyze(60) - visible_message(span_danger("[src] knocks [victim] down!")) - to_chat(src, span_cultlarge("\"Bring [victim.p_them()] to me.\"")) - return FALSE - - do_attack_animation(victim) - var/obj/item/bodypart/limb = pick(parts) - limb.dismember() - return FALSE - -/mob/living/simple_animal/hostile/construct/harvester/Initialize(mapload) - . = ..() - var/datum/action/innate/seek_prey/seek = new() - seek.Grant(src) - seek.Activate() + return ..() //if any arms or legs exist, attack + + carbon_target.Paralyze(6 SECONDS) + visible_message(span_danger("[src] knocks [carbon_target] down!")) + to_chat(src, span_cultlarge("\"Bring [carbon_target.p_them()] to me.\"")) /datum/action/innate/seek_master name = "Seek your Master" @@ -89,7 +64,7 @@ /// Where is nar nar? Are we even looking? var/tracking = FALSE /// The construct we're attached to - var/mob/living/simple_animal/hostile/construct/the_construct + var/mob/living/basic/construct/the_construct /datum/action/innate/seek_master/Grant(mob/living/player) the_construct = player @@ -132,7 +107,7 @@ /datum/action/innate/seek_prey/Activate() if(GLOB.cult_narsie == null) return - var/mob/living/simple_animal/hostile/construct/harvester/the_construct = owner + var/mob/living/basic/construct/harvester/the_construct = owner if(the_construct.seeking) desc = "None can hide from Nar'Sie, activate to track a survivor attempting to flee the red harvest!" diff --git a/code/modules/mob/living/basic/cult/constructs/juggernaut.dm b/code/modules/mob/living/basic/cult/constructs/juggernaut.dm new file mode 100644 index 000000000000..2b8bb7e293d8 --- /dev/null +++ b/code/modules/mob/living/basic/cult/constructs/juggernaut.dm @@ -0,0 +1,61 @@ +/mob/living/basic/construct/juggernaut + name = "Juggernaut" + real_name = "Juggernaut" + desc = "A massive, armored construct built to spearhead attacks and soak up enemy fire." + icon_state = "juggernaut" + icon_living = "juggernaut" + maxHealth = 150 + health = 150 + response_harm_continuous = "harmlessly punches" + response_harm_simple = "harmlessly punch" + obj_damage = 90 + melee_damage_lower = 25 + melee_damage_upper = 25 + attack_verb_continuous = "smashes their armored gauntlet into" + attack_verb_simple = "smash your armored gauntlet into" + speed = 2.5 + attack_sound = 'sound/weapons/punch3.ogg' + status_flags = NONE + mob_size = MOB_SIZE_LARGE + force_threshold = 10 + construct_spells = list( + /datum/action/cooldown/spell/forcewall/cult, + /datum/action/cooldown/spell/basic_projectile/juggernaut, + /datum/action/innate/cult/create_rune/wall, + ) + playstyle_string = span_bold("You are a Juggernaut. Though slow, your shell can withstand heavy punishment, create shield walls, rip apart enemies and walls alike, and even deflect energy weapons.") + + smashes_walls = TRUE + +/// Hostile NPC version. Pretty dumb, just attacks whoever is near. +/mob/living/basic/construct/juggernaut/hostile + ai_controller = /datum/ai_controller/basic_controller/juggernaut + smashes_walls = FALSE + melee_attack_cooldown = 2 SECONDS + +/mob/living/basic/construct/juggernaut/bullet_act(obj/projectile/bullet) + if(!istype(bullet, /obj/projectile/energy) && !istype(bullet, /obj/projectile/beam)) + return ..() + if(!prob(40 - round(bullet.damage / 3))) // reflect chance + return ..() + + apply_damage(bullet.damage * 0.5, bullet.damage_type) + visible_message( + span_danger("The [bullet.name] is reflected by [src]'s armored shell!"), + span_userdanger("The [bullet.name] is reflected by your armored shell!"), + ) + + bullet.reflect(src) + + return BULLET_ACT_FORCE_PIERCE // complete projectile permutation + +// Alternate juggernaut themes +/mob/living/basic/construct/juggernaut/angelic + theme = THEME_HOLY + +/mob/living/basic/construct/juggernaut/angelic/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_ANGELIC, INNATE_TRAIT) + +/mob/living/basic/construct/juggernaut/mystic + theme = THEME_WIZARD diff --git a/code/modules/mob/living/basic/cult/constructs/proteon.dm b/code/modules/mob/living/basic/cult/constructs/proteon.dm new file mode 100644 index 000000000000..2ff58d2463c0 --- /dev/null +++ b/code/modules/mob/living/basic/cult/constructs/proteon.dm @@ -0,0 +1,39 @@ +/// Proteon - a very weak construct that only appears in NPC form in various ruins. +/mob/living/basic/construct/proteon + name = "Proteon" + real_name = "Proteon" + desc = "A weaker construct meant to scour ruins for objects of Nar'Sie's affection. Those barbed claws are no joke." + icon_state = "proteon" + icon_living = "proteon" + maxHealth = 35 + health = 35 + melee_damage_lower = 8 + melee_damage_upper = 10 + attack_verb_continuous = "pinches" + attack_verb_simple = "pinch" + smashes_walls = TRUE + attack_sound = 'sound/weapons/punch2.ogg' + playstyle_string = span_bold("You are a Proteon. Your abilities in combat are outmatched by most combat constructs, but you are still fast and nimble. Run metal and supplies, and cooperate with your fellow cultists.") + +/// Hostile NPC version +/mob/living/basic/construct/proteon/hostile + ai_controller = /datum/ai_controller/basic_controller/proteon + smashes_walls = FALSE + melee_attack_cooldown = 1.5 SECONDS + +/mob/living/basic/construct/proteon/hostile/Initialize(mapload) + . = ..() + var/datum/callback/retaliate_callback = CALLBACK(src, PROC_REF(ai_retaliate_behaviour)) + AddComponent(/datum/component/ai_retaliate_advanced, retaliate_callback) + +/// Set a timer to clear our retaliate list +/mob/living/basic/construct/proteon/hostile/proc/ai_retaliate_behaviour(mob/living/attacker) + if (!istype(attacker)) + return + var/random_timer = rand(2 SECONDS, 4 SECONDS) //for unpredictability + addtimer(CALLBACK(src, PROC_REF(clear_retaliate_list)), random_timer) + +/mob/living/basic/construct/proteon/hostile/proc/clear_retaliate_list() + if(!ai_controller.blackboard_key_exists(BB_BASIC_MOB_RETALIATE_LIST)) + return + ai_controller.clear_blackboard_key(BB_BASIC_MOB_RETALIATE_LIST) diff --git a/code/modules/mob/living/basic/cult/constructs/wraith.dm b/code/modules/mob/living/basic/cult/constructs/wraith.dm new file mode 100644 index 000000000000..06a09b6446ed --- /dev/null +++ b/code/modules/mob/living/basic/cult/constructs/wraith.dm @@ -0,0 +1,50 @@ +/mob/living/basic/construct/wraith + name = "Wraith" + real_name = "Wraith" + desc = "A wicked, clawed shell constructed to assassinate enemies and sow chaos behind enemy lines." + icon_state = "wraith" + icon_living = "wraith" + maxHealth = 65 + health = 65 + melee_damage_lower = 20 + melee_damage_upper = 20 + attack_verb_continuous = "slashes" + attack_verb_simple = "slash" + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH + construct_spells = list( + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift, + /datum/action/innate/cult/create_rune/tele, + ) + playstyle_string = span_bold("You are a Wraith. Though relatively fragile, you are fast, deadly, and can phase through walls. Your attacks will lower the cooldown on phasing, moreso for fatal blows.") + +/mob/living/basic/construct/wraith/Initialize(mapload) + . = ..() + var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/jaunt = locate() in actions + if(isnull(jaunt)) + return . + AddComponent(/datum/component/recharging_attacks, recharged_action = jaunt) + +/// Hostile NPC version. Attempts to kill the lowest-health mob it can see. +/mob/living/basic/construct/wraith/hostile + ai_controller = /datum/ai_controller/basic_controller/wraith + melee_attack_cooldown = 1.5 SECONDS + +// Alternate wraith themes +/mob/living/basic/construct/wraith/angelic + theme = THEME_HOLY + construct_spells = list( + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/angelic, + /datum/action/innate/cult/create_rune/tele, + ) + +/mob/living/basic/construct/wraith/angelic/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_ANGELIC, INNATE_TRAIT) + +/mob/living/basic/construct/wraith/mystic + theme = THEME_WIZARD + construct_spells = list( + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/mystic, + /datum/action/innate/cult/create_rune/tele, + ) diff --git a/code/modules/mob/living/basic/cult/shade.dm b/code/modules/mob/living/basic/cult/shade.dm new file mode 100644 index 000000000000..fac1d347665e --- /dev/null +++ b/code/modules/mob/living/basic/cult/shade.dm @@ -0,0 +1,71 @@ +/mob/living/basic/shade + name = "Shade" + real_name = "Shade" + desc = "A bound spirit." + gender = PLURAL + icon = 'icons/mob/nonhuman-player/cult.dmi' + icon_state = "shade_cult" + icon_living = "shade_cult" + mob_biotypes = MOB_SPIRIT + maxHealth = 40 + health = 40 + speak_emote = list("hisses") + response_help_continuous = "puts their hand through" + response_help_simple = "put your hand through" + response_disarm_continuous = "flails at" + response_disarm_simple = "flail at" + response_harm_continuous = "punches" + response_harm_simple = "punch" + melee_damage_lower = 5 + melee_damage_upper = 12 + attack_verb_continuous = "metaphysically strikes" + attack_verb_simple = "metaphysically strike" + unsuitable_cold_damage = 0 + unsuitable_heat_damage = 0 + unsuitable_atmos_damage = 0 + speed = -1 + faction = list(FACTION_CULT) + basic_mob_flags = DEL_ON_DEATH + initial_language_holder = /datum/language_holder/construct + /// Theme controls color. THEME_CULT is red THEME_WIZARD is purple and THEME_HOLY is blue + var/theme = THEME_CULT + /// The different flavors of goop shades can drop, depending on theme. + var/static/list/remains_by_theme = list( + THEME_CULT = list(/obj/item/ectoplasm/construct), + THEME_HOLY = list(/obj/item/ectoplasm/angelic), + THEME_WIZARD = list(/obj/item/ectoplasm/mystic), + ) + +/mob/living/basic/shade/Initialize(mapload) + . = ..() + AddElement(/datum/element/simple_flying) + add_traits(list(TRAIT_HEALS_FROM_CULT_PYLONS, TRAIT_SPACEWALK, TRAIT_VENTCRAWLER_ALWAYS), INNATE_TRAIT) + if(isnull(theme)) + return + icon_state = "shade_[theme]" + var/list/remains = string_list(remains_by_theme[theme]) + if(length(remains)) + AddElement(/datum/element/death_drops, remains) + +/mob/living/basic/shade/update_icon_state() + . = ..() + if(!isnull(theme)) + icon_state = "shade_[theme]" + icon_living = icon_state + +/mob/living/basic/shade/death() + if(death_message == initial(death_message)) + death_message = "lets out a contented sigh as [p_their()] form unwinds." + ..() + +/mob/living/basic/shade/can_suicide() + if(istype(loc, /obj/item/soulstone)) //do not suicide inside the soulstone + return FALSE + return ..() + +/mob/living/basic/shade/attackby(obj/item/item, mob/user, params) + if(istype(item, /obj/item/soulstone)) + var/obj/item/soulstone/stone = item + stone.capture_shade(src, user) + else + . = ..() diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/basic/drone/_drone.dm similarity index 79% rename from code/modules/mob/living/simple_animal/friendly/drone/_drone.dm rename to code/modules/mob/living/basic/drone/_drone.dm index fe2ac091ca5a..143d4f618e8d 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm +++ b/code/modules/mob/living/basic/drone/_drone.dm @@ -1,6 +1,6 @@ /** - * # Maintenance Drone + * Maintenance Drone * * Small player controlled fixer-upper * @@ -13,7 +13,7 @@ * They have laws to prevent them from doing anything else. * */ -/mob/living/simple_animal/drone +/mob/living/basic/drone name = "Drone" desc = "A maintenance drone, an expendable robot built to perform station repairs." icon = 'icons/mob/silicon/drone.dmi' @@ -23,11 +23,9 @@ health = 45 maxHealth = 45 unsuitable_atmos_damage = 0 - minbodytemp = 0 - maxbodytemp = 0 - wander = 0 + unsuitable_cold_damage = 0 + unsuitable_heat_damage = 0 speed = 0 - healable = 0 density = FALSE pass_flags = PASSTABLE | PASSMOB sight = SEE_TURFS | SEE_OBJS @@ -44,8 +42,7 @@ hud_possible = list(DIAG_STAT_HUD, DIAG_HUD, ANTAG_HUD) unique_name = TRUE faction = list(FACTION_NEUTRAL,FACTION_SILICON,FACTION_TURRET) - dextrous = TRUE - dextrous_hud_type = /datum/hud/dextrous/drone + hud_type = /datum/hud/dextrous/drone // Going for a sort of pale green here lighting_cutoff_red = 30 lighting_cutoff_green = 35 @@ -53,7 +50,6 @@ can_be_held = TRUE worn_slot_flags = ITEM_SLOT_HEAD - held_items = list(null, null) /// `TRUE` if we have picked our visual appearance, `FALSE` otherwise (default) var/picked = FALSE /// Stored drone color, restored when unhacked @@ -72,10 +68,10 @@ var/obj/item/internal_storage /// Headwear slot var/obj/item/head - /// Default [/mob/living/simple_animal/drone/var/internal_storage] item + /// Default [/mob/living/basic/drone/var/internal_storage] item var/obj/item/default_storage = /obj/item/storage/drone_tools - /// Default [/mob/living/simple_animal/drone/var/head] item - var/obj/item/default_hatmask + /// Default [/mob/living/basic/drone/var/head] item + var/obj/item/default_headwear /** * icon_state of drone from icons/mobs/drone.dmi * @@ -87,9 +83,11 @@ * - [CLOCKDRONE] */ var/visualAppearance = MAINTDRONE - /// Hacked state, see [/mob/living/simple_animal/drone/proc/update_drone_hack] + /// Hacked state, see [/mob/living/basic/drone/proc/update_drone_hack] var/hacked = FALSE - /// If we have laws to minimize bothering others. Enables or disables drone laws enforcement components (use [/mob/living/simple_animal/drone/proc/set_shy] to set) + /// Whether this drone can be un-hacked. Used for subtypes that cannot be meaningfully "fixed". + var/can_unhack = TRUE + /// If we have laws to minimize bothering others. Enables or disables drone laws enforcement components (use [/mob/living/basic/drone/proc/set_shy] to set) var/shy = TRUE /// Flavor text announced to drones on [/mob/proc/Login] var/flavortext = \ @@ -170,23 +168,25 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber, ) -/mob/living/simple_animal/drone/Initialize(mapload) +/mob/living/basic/drone/Initialize(mapload) . = ..() GLOB.drones_list += src - access_card = new /obj/item/card/id/advanced/simple_bot(src) - - // Doing this hurts my soul, but simple_animal access reworks are for another day. - var/datum/id_trim/job/cap_trim = SSid_access.trim_singletons_by_path[/datum/id_trim/job/captain] - access_card.add_access(cap_trim.access + cap_trim.wildcard_access) + AddElement(/datum/element/dextrous, hud_type = hud_type) + AddComponent(/datum/component/basic_inhands, y_offset = getItemPixelShiftY()) + AddComponent(/datum/component/simple_access, SSid_access.get_region_access_list(list(REGION_ALL_GLOBAL))) if(default_storage) - var/obj/item/I = new default_storage(src) - equip_to_slot_or_del(I, ITEM_SLOT_DEX_STORAGE) - if(default_hatmask) - var/obj/item/I = new default_hatmask(src) - equip_to_slot_or_del(I, ITEM_SLOT_HEAD) + var/obj/item/storage = new default_storage(src) + equip_to_slot_or_del(storage, ITEM_SLOT_DEX_STORAGE) + + for(var/holiday_name in GLOB.holidays) + var/obj/item/potential_hat + if(!isnull(potential_hat) && isnull(default_headwear)) //If our drone type doesn't start with a hat, we take the holiday one. + default_headwear = potential_hat - ADD_TRAIT(access_card, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) + if(default_headwear) + var/obj/item/new_hat = new default_headwear(src) + equip_to_slot_or_del(new_hat, ITEM_SLOT_HEAD) shy_update() @@ -195,7 +195,7 @@ for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) diag_hud.add_atom_to_hud(src) - add_traits(list(TRAIT_VENTCRAWLER_ALWAYS, TRAIT_NEGATES_GRAVITY, TRAIT_LITERATE, TRAIT_KNOW_ENGI_WIRES), INNATE_TRAIT) + add_traits(list(TRAIT_VENTCRAWLER_ALWAYS, TRAIT_NEGATES_GRAVITY, TRAIT_LITERATE, TRAIT_KNOW_ENGI_WIRES, TRAIT_ADVANCEDTOOLUSER), INNATE_TRAIT) listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), list(z)) RegisterSignal(listener, COMSIG_ALARM_LISTENER_TRIGGERED, PROC_REF(alarm_triggered)) @@ -203,16 +203,16 @@ listener.RegisterSignal(src, COMSIG_LIVING_DEATH, TYPE_PROC_REF(/datum/alarm_listener, prevent_alarm_changes)) listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, TYPE_PROC_REF(/datum/alarm_listener, allow_alarm_changes)) -/mob/living/simple_animal/drone/med_hud_set_health() +/mob/living/basic/drone/med_hud_set_health() var/image/holder = hud_list[DIAG_HUD] - var/icon/I = icon(icon, icon_state, dir) - holder.pixel_y = I.Height() - world.icon_size + var/icon/hud_icon = icon(icon, icon_state, dir) + holder.pixel_y = hud_icon.Height() - world.icon_size holder.icon_state = "huddiag[RoundDiagBar(health/maxHealth)]" -/mob/living/simple_animal/drone/med_hud_set_status() +/mob/living/basic/drone/med_hud_set_status() var/image/holder = hud_list[DIAG_STAT_HUD] - var/icon/I = icon(icon, icon_state, dir) - holder.pixel_y = I.Height() - world.icon_size + var/icon/hud_icon = icon(icon, icon_state, dir) + holder.pixel_y = hud_icon.Height() - world.icon_size if(stat == DEAD) holder.icon_state = "huddead2" else if(incapacitated()) @@ -220,13 +220,12 @@ else holder.icon_state = "hudstat" -/mob/living/simple_animal/drone/Destroy() +/mob/living/basic/drone/Destroy() GLOB.drones_list -= src - QDEL_NULL(access_card) //Otherwise it ends up on the floor! QDEL_NULL(listener) return ..() -/mob/living/simple_animal/drone/Login() +/mob/living/basic/drone/Login() . = ..() if(!. || !client) return FALSE @@ -238,14 +237,14 @@ if(!picked) pickVisualAppearance() -/mob/living/simple_animal/drone/auto_deadmin_on_login() +/mob/living/basic/drone/auto_deadmin_on_login() if(!client?.holder) return TRUE if(CONFIG_GET(flag/auto_deadmin_silicons) || (client.prefs?.toggles & DEADMIN_POSITION_SILICON)) return client.holder.auto_deadmin() return ..() -/mob/living/simple_animal/drone/death(gibbed) +/mob/living/basic/drone/death(gibbed) ..(gibbed) if(internal_storage) dropItemToGround(internal_storage) @@ -255,10 +254,10 @@ alert_drones(DRONE_NET_DISCONNECT) -/mob/living/simple_animal/drone/gib() +/mob/living/basic/drone/gib() dust() -/mob/living/simple_animal/drone/examine(mob/user) +/mob/living/basic/drone/examine(mob/user) . = list("This is [icon2html(src, user)] \a [src]!") //Hands @@ -299,11 +298,11 @@ . += "" -/mob/living/simple_animal/drone/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) //Secbots won't hunt maintenance drones. +/mob/living/basic/drone/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) //Secbots won't hunt maintenance drones. return -10 -/mob/living/simple_animal/drone/emp_act(severity) +/mob/living/basic/drone/emp_act(severity) . = ..() if(. & EMP_PROTECT_SELF) return @@ -313,39 +312,39 @@ adjustBruteLoss(heavy_emp_damage) to_chat(src, span_userdanger("HeAV% DA%^MMA+G TO I/O CIR!%UUT!")) -/mob/living/simple_animal/drone/proc/alarm_triggered(datum/source, alarm_type, area/source_area) +/mob/living/basic/drone/proc/alarm_triggered(datum/source, alarm_type, area/source_area) SIGNAL_HANDLER to_chat(src, "--- [alarm_type] alarm detected in [source_area.name]!") -/mob/living/simple_animal/drone/proc/alarm_cleared(datum/source, alarm_type, area/source_area) +/mob/living/basic/drone/proc/alarm_cleared(datum/source, alarm_type, area/source_area) SIGNAL_HANDLER to_chat(src, "--- [alarm_type] alarm in [source_area.name] has been cleared.") -/mob/living/simple_animal/drone/proc/blacklist_on_try_use_machine(datum/source, obj/machinery/machine) +/mob/living/basic/drone/proc/blacklist_on_try_use_machine(datum/source, obj/machinery/machine) SIGNAL_HANDLER if(GLOB.drone_machine_blacklist_enabled && is_type_in_typecache(machine, drone_machinery_blacklist_compiled)) to_chat(src, span_warning("Using [machine] could break your laws.")) return COMPONENT_CANT_USE_MACHINE_INTERACT | COMPONENT_CANT_USE_MACHINE_TOOLS -/mob/living/simple_animal/drone/proc/blacklist_on_try_wires_interact(datum/source, atom/machine) +/mob/living/basic/drone/proc/blacklist_on_try_wires_interact(datum/source, atom/machine) SIGNAL_HANDLER if(GLOB.drone_machine_blacklist_enabled && is_type_in_typecache(machine, drone_machinery_blacklist_compiled)) to_chat(src, span_warning("Using [machine] could break your laws.")) return COMPONENT_CANT_INTERACT_WIRES -/mob/living/simple_animal/drone/proc/set_shy(new_shy) +/mob/living/basic/drone/proc/set_shy(new_shy) shy = new_shy shy_update() -/mob/living/simple_animal/drone/proc/shy_update() +/mob/living/basic/drone/proc/shy_update() var/list/drone_bad_areas = make_associative(drone_area_blacklist_flat) + typecacheof(drone_area_blacklist_recursive) var/list/drone_bad_machinery = make_associative(drone_machinery_blacklist_flat) + typecacheof(drone_machinery_blacklist_recursive) var/list/drone_good_machinery = LAZYCOPY(drone_machinery_whitelist_flat) + typecacheof(drone_machinery_whitelist_recursive) // not a valid typecache, only intended for negation against drone_bad_machinery drone_machinery_blacklist_compiled = drone_bad_machinery - drone_good_machinery - var/static/list/not_shy_of = typecacheof(list(/mob/living/simple_animal/drone, /mob/living/simple_animal/bot)) + var/static/list/not_shy_of = typecacheof(list(/mob/living/basic/drone, /mob/living/simple_animal/bot)) if(shy) ADD_TRAIT(src, TRAIT_PACIFISM, DRONE_SHY_TRAIT) LoadComponent(/datum/component/shy, mob_whitelist=not_shy_of, shy_range=1, message="Your laws prevent this action near %TARGET.", keyless_shy=FALSE, clientless_shy=FALSE, dead_shy=FALSE, dead_shy_immediate=TRUE, machine_whitelist=shy_machine_whitelist) @@ -361,16 +360,13 @@ qdel(GetComponent(/datum/component/itempicky)) UnregisterSignal(src, list(COMSIG_TRY_USE_MACHINE, COMSIG_TRY_WIRES_INTERACT)) -/mob/living/simple_animal/drone/handle_temperature_damage() - return - -/mob/living/simple_animal/drone/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash, length = 25) +/mob/living/basic/drone/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash, length = 25) if(affect_silicon) return ..() -/mob/living/simple_animal/drone/bee_friendly() +/mob/living/basic/drone/bee_friendly() // Why would bees pay attention to drones? return TRUE -/mob/living/simple_animal/drone/electrocute_act(shock_damage, source, siemens_coeff, flags = NONE) +/mob/living/basic/drone/electrocute_act(shock_damage, source, siemens_coeff, flags = NONE) return FALSE //So they don't die trying to fix wiring diff --git a/code/modules/mob/living/simple_animal/friendly/drone/drone_say.dm b/code/modules/mob/living/basic/drone/drone_say.dm similarity index 55% rename from code/modules/mob/living/simple_animal/friendly/drone/drone_say.dm rename to code/modules/mob/living/basic/drone/drone_say.dm index 88fc3b5cd85b..af0bef41bb1c 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/drone_say.dm +++ b/code/modules/mob/living/basic/drone/drone_say.dm @@ -6,21 +6,21 @@ * * dead_can_hear - Boolean that determines if ghosts can hear the message (`FALSE` by default) * * source - [/atom] source that created the message * * faction_checked_mob - [/mob/living] to determine faction matches from - * * exact_faction_match - Passed to [/mob/proc/faction_check_mob] + * * exact_faction_match - Passed to [/mob/proc/faction_check_atom] */ /proc/_alert_drones(msg, dead_can_hear = FALSE, atom/source, mob/living/faction_checked_mob, exact_faction_match) if (dead_can_hear && source) - for (var/mob/M in GLOB.dead_mob_list) - var/link = FOLLOW_LINK(M, source) - to_chat(M, "[link] [msg]") - for(var/i in GLOB.drones_list) - var/mob/living/simple_animal/drone/D = i - if(istype(D) && D.stat != DEAD) + for (var/mob/dead_mob in GLOB.dead_mob_list) + var/link = FOLLOW_LINK(dead_mob, source) + to_chat(dead_mob, "[link] [msg]") + for(var/global_drone in GLOB.drones_list) + var/mob/living/basic/drone/drone = global_drone + if(istype(drone) && drone.stat != DEAD) if(faction_checked_mob) - if(D.faction_check_mob(faction_checked_mob, exact_faction_match)) - to_chat(D, msg) + if(drone.faction_check_atom(faction_checked_mob, exact_faction_match)) + to_chat(drone, msg) else - to_chat(D, msg) + to_chat(drone, msg) @@ -28,16 +28,16 @@ * Wraps [/proc/_alert_drones] with defaults * * * source - `src` - * * faction_check_mob - `src` + * * faction_check_atom - `src` * * dead_can_hear - `TRUE` */ -/mob/living/simple_animal/drone/proc/alert_drones(msg, dead_can_hear = FALSE) +/mob/living/basic/drone/proc/alert_drones(msg, dead_can_hear = FALSE) _alert_drones(msg, dead_can_hear, src, src, TRUE) /** - * Wraps [/mob/living/simple_animal/drone/proc/alert_drones] as a Drone Chat + * Wraps [/mob/living/basic/drone/proc/alert_drones] as a Drone Chat * * Shares the same radio code with binary */ -/mob/living/simple_animal/drone/proc/drone_chat(msg) +/mob/living/basic/drone/proc/drone_chat(msg) alert_drones("Drone Chat: [span_name("[name]")] [say_quote(msg)]", TRUE) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/drone_tools.dm b/code/modules/mob/living/basic/drone/drone_tools.dm similarity index 100% rename from code/modules/mob/living/simple_animal/friendly/drone/drone_tools.dm rename to code/modules/mob/living/basic/drone/drone_tools.dm diff --git a/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm b/code/modules/mob/living/basic/drone/drones_as_items.dm similarity index 81% rename from code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm rename to code/modules/mob/living/basic/drone/drones_as_items.dm index c163066ae1e1..629c7fe87a3b 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm +++ b/code/modules/mob/living/basic/drone/drones_as_items.dm @@ -1,12 +1,7 @@ -/////////////////// -//DRONES AS ITEMS// -/////////////////// -//Drone shells - /** Drone Shell: Ghost role item for drones * * A simple mob spawner item that transforms into a maintenance drone - * Resepcts drone minimum age + * Respects drone minimum age */ /obj/effect/mob_spawn/ghost_role/drone @@ -18,7 +13,7 @@ density = FALSE mob_name = "drone" ///Type of drone that will be spawned - mob_type = /mob/living/simple_animal/drone + mob_type = /mob/living/basic/drone role_ban = ROLE_DRONE show_flavor = FALSE prompt_name = "maintenance drone" @@ -29,13 +24,13 @@ /obj/effect/mob_spawn/ghost_role/drone/Initialize(mapload) . = ..() - var/area/A = get_area(src) - if(A) - notify_ghosts("A drone shell has been created in \the [A.name].", source = src, action=NOTIFY_ATTACK, flashwindow = FALSE, ignore_key = POLL_IGNORE_DRONE, notify_suiciders = FALSE) + var/area/area = get_area(src) + if(area) + notify_ghosts("A drone shell has been created in \the [area.name].", source = src, action=NOTIFY_ATTACK, flashwindow = FALSE, ignore_key = POLL_IGNORE_DRONE, notify_suiciders = FALSE) /obj/effect/mob_spawn/ghost_role/drone/allow_spawn(mob/user, silent = FALSE) var/client/user_client = user.client - var/mob/living/simple_animal/drone/drone_type = mob_type + var/mob/living/basic/drone/drone_type = mob_type if(!initial(drone_type.shy) || isnull(user_client) || !CONFIG_GET(flag/use_exp_restrictions_other)) return ..() var/required_role = CONFIG_GET(string/drone_required_role) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm b/code/modules/mob/living/basic/drone/extra_drone_types.dm similarity index 70% rename from code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm rename to code/modules/mob/living/basic/drone/extra_drone_types.dm index 6c60bc855633..b7bfada8db4a 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm +++ b/code/modules/mob/living/basic/drone/extra_drone_types.dm @@ -1,15 +1,8 @@ -//////////////////// -//MORE DRONE TYPES// -//////////////////// -//Drones with custom laws -//Drones with custom shells -//Drones with overridden procs -//Drones with camogear for hat related memes -//Drone type for use with polymorph (no preloaded items, random appearance) - - -//More types of drones -/mob/living/simple_animal/drone/syndrone +/** +* A Syndicate drone, tasked to cause chaos on the station. +* Has a lot more health and its own uplink with 10 TC. +*/ +/mob/living/basic/drone/syndrone name = "Syndrone" desc = "A modified maintenance drone. This one brings with it the feeling of terror." icon_state = "drone_synd" @@ -27,67 +20,71 @@ "2. Kill.\n"+\ "3. Destroy." default_storage = /obj/item/uplink - default_hatmask = /obj/item/clothing/head/helmet/swat + default_headwear = /obj/item/clothing/head/helmet/swat hacked = TRUE + can_unhack = FALSE shy = FALSE flavortext = null -/mob/living/simple_animal/drone/syndrone/Initialize(mapload) - . = ..() - var/datum/component/uplink/hidden_uplink = internal_storage.GetComponent(/datum/component/uplink) - hidden_uplink.set_telecrystals(10) + /// The number of telecrystals to put in the drone's uplink + var/telecrystal_count = 10 -/mob/living/simple_animal/drone/syndrone/badass - name = "Badass Syndrone" - default_storage = /obj/item/uplink/nuclear -/mob/living/simple_animal/drone/syndrone/badass/Initialize(mapload) +/mob/living/basic/drone/syndrone/Initialize(mapload) . = ..() var/datum/component/uplink/hidden_uplink = internal_storage.GetComponent(/datum/component/uplink) - hidden_uplink.set_telecrystals(30) - var/obj/item/implant/weapons_auth/W = new/obj/item/implant/weapons_auth(src) - W.implant(src, force = TRUE) - -/mob/living/simple_animal/drone/snowflake - default_hatmask = /obj/item/clothing/head/chameleon/drone - -/mob/living/simple_animal/drone/snowflake/Initialize(mapload) - . = ..() - desc += " This drone appears to have a complex holoprojector built on its 'head'." + hidden_uplink.set_telecrystals(telecrystal_count) /obj/effect/mob_spawn/ghost_role/drone/syndrone name = "syndrone shell" desc = "A shell of a syndrone, a modified maintenance drone designed to infiltrate and annihilate." icon_state = "syndrone_item" mob_name = "syndrone" - mob_type = /mob/living/simple_animal/drone/syndrone + mob_type = /mob/living/basic/drone/syndrone prompt_name = "a syndrone" you_are_text = "You are a Syndicate Maintenance Drone." flavour_text = "In a prior life, you maintained a Nanotrasen Research Station. Abducted from your home, you were given some upgrades... and now serve an enemy of your former masters." important_text = "" spawner_job_path = /datum/job/ghost_role +/// A version of the syndrone that gets a nuclear uplink, a firearms implant, and 30 TC. +/mob/living/basic/drone/syndrone/badass + name = "Badass Syndrone" + default_storage = /obj/item/uplink/nuclear + telecrystal_count = 30 + +/mob/living/basic/drone/syndrone/badass/Initialize(mapload) + . = ..() + var/obj/item/implant/weapons_auth/weapon_implant = new/obj/item/implant/weapons_auth(src) + weapon_implant.implant(src, force = TRUE) + /obj/effect/mob_spawn/ghost_role/drone/syndrone/badass name = "badass syndrone shell" mob_name = "badass syndrone" - mob_type = /mob/living/simple_animal/drone/syndrone/badass + mob_type = /mob/living/basic/drone/syndrone/badass prompt_name = "a badass syndrone" flavour_text = "In a prior life, you maintained a Nanotrasen Research Station. Abducted from your home, you were given some BETTER upgrades... and now serve an enemy of your former masters." +/// A drone that spawns with a chameleon hat for fashion purposes. +/mob/living/basic/drone/snowflake + default_headwear = /obj/item/clothing/head/chameleon/drone + desc = "A maintenance drone, an expendable robot built to perform station repairs. This drone appears to have a complex holoprojector built on its 'head'." + /obj/effect/mob_spawn/ghost_role/drone/snowflake name = "snowflake drone shell" desc = "A shell of a snowflake drone, a maintenance drone with a built in holographic projector to display hats and masks." mob_name = "snowflake drone" prompt_name = "a drone with a holohat projector" - mob_type = /mob/living/simple_animal/drone/snowflake + mob_type = /mob/living/basic/drone/snowflake -/mob/living/simple_animal/drone/polymorphed +/// A free drone that people can be turned into via wabbajack. +/mob/living/basic/drone/polymorphed default_storage = null - default_hatmask = null + default_headwear = null picked = TRUE flavortext = null -/mob/living/simple_animal/drone/polymorphed/Initialize(mapload) +/mob/living/basic/drone/polymorphed/Initialize(mapload) . = ..() liberate() visualAppearance = pick(MAINTDRONE, REPAIRDRONE, SCOUTDRONE) @@ -100,31 +97,19 @@ icon_living = icon_state icon_dead = "[visualAppearance]_dead" -/obj/effect/mob_spawn/ghost_role/drone/classic - mob_type = /mob/living/simple_animal/drone/classic - -/mob/living/simple_animal/drone/classic +/// "Classic" drones, which are not shy and get a duffelbag of tools instead of built-in tools. +/mob/living/basic/drone/classic name = "classic drone shell" shy = FALSE default_storage = /obj/item/storage/backpack/duffelbag/drone -/obj/effect/mob_spawn/ghost_role/drone/derelict - name = "derelict drone shell" - desc = "A long-forgotten drone shell. It seems kind of... Space Russian." - icon = 'icons/mob/silicon/drone.dmi' - icon_state = "drone_maint_hat" - mob_name = "derelict drone" - mob_type = /mob/living/simple_animal/drone/derelict - anchored = TRUE - prompt_name = "a derelict drone" - you_are_text = "You are a drone on Kosmicheskaya Stantsiya 13." - flavour_text = "Something has brought you out of hibernation, and the station is in gross disrepair." - important_text = "Build, repair, maintain and improve the station that housed you on activation." - spawner_job_path = /datum/job/ghost_role +/obj/effect/mob_spawn/ghost_role/drone/classic + mob_type = /mob/living/basic/drone/classic -/mob/living/simple_animal/drone/derelict +/// Derelict drones, a ghost role tasked with repairing KS13. Get gibbed if they leave. +/mob/living/basic/drone/derelict name = "derelict drone" - default_hatmask = /obj/item/clothing/head/costume/ushanka + default_headwear = /obj/item/clothing/head/costume/ushanka laws = \ "1. You may not involve yourself in the matters of another sentient being outside the station that housed your activation, even if such matters conflict with Law Two or Law Three, unless the other being is another Drone.\n"+\ "2. You may not harm any sentient being, regardless of intent or circumstance.\n"+\ @@ -140,8 +125,24 @@ "If you do not have the regular drone laws, follow your laws to the best of your ability." shy = FALSE -/mob/living/simple_animal/drone/derelict/Initialize(mapload) +/mob/living/basic/drone/derelict/Initialize(mapload) . = ..() AddComponent(/datum/component/stationstuck, PUNISHMENT_GIB, "01000110 01010101 01000011 01001011 00100000 01011001 01001111 01010101
WARNING: Dereliction of KS13 detected. Self-destruct activated.") +/obj/effect/mob_spawn/ghost_role/drone/derelict + name = "derelict drone shell" + desc = "A long-forgotten drone shell. It seems kind of... Space Russian." + icon = 'icons/mob/silicon/drone.dmi' + icon_state = "drone_maint_hat" + mob_name = "derelict drone" + mob_type = /mob/living/basic/drone/derelict + anchored = TRUE + prompt_name = "a derelict drone" + you_are_text = "You are a drone on Kosmicheskaya Stantsiya 13." + flavour_text = "Something has brought you out of hibernation, and the station is in gross disrepair." + important_text = "Build, repair, maintain and improve the station that housed you on activation." + spawner_job_path = /datum/job/derelict_drone +/datum/job/derelict_drone + title = ROLE_DERELICT_DRONE + policy_index = ROLE_DERELICT_DRONE diff --git a/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm b/code/modules/mob/living/basic/drone/interaction.dm similarity index 76% rename from code/modules/mob/living/simple_animal/friendly/drone/interaction.dm rename to code/modules/mob/living/basic/drone/interaction.dm index f6077a5e4800..ad6261f5cce8 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm +++ b/code/modules/mob/living/basic/drone/interaction.dm @@ -1,12 +1,6 @@ +// Drones' interactions with other mobs -///////////////////// -//DRONE INTERACTION// -///////////////////// -//How drones interact with the world -//How the world interacts with drones - - -/mob/living/simple_animal/drone/attack_drone(mob/living/simple_animal/drone/drone) +/mob/living/basic/drone/attack_drone(mob/living/basic/drone/drone) if(drone == src || stat != DEAD) return FALSE var/input = tgui_alert(drone, "Perform which action?", "Drone Interaction", list("Reactivate", "Cannibalize")) @@ -24,36 +18,27 @@ drone.visible_message(span_notice("[drone] repairs itself using [src]'s remains!"), span_notice("You repair yourself using [src]'s remains.")) drone.adjustBruteLoss(-src.maxHealth) new /obj/effect/decal/cleanable/oil/streak(get_turf(src)) + ghostize(can_reenter_corpse = FALSE) qdel(src) else to_chat(drone, span_warning("You need to remain still to cannibalize [src]!")) -/mob/living/simple_animal/drone/attack_drone_secondary(mob/living/simple_animal/drone/drone) +/mob/living/basic/drone/attack_drone_secondary(mob/living/basic/drone/drone) return SECONDARY_ATTACK_CALL_NORMAL -//ATTACK HAND IGNORING PARENT RETURN VALUE -/mob/living/simple_animal/drone/attack_hand(mob/user, list/modifiers) - if(ishuman(user)) - if(stat == DEAD || status_flags & GODMODE || !can_be_held) - ..() - return - if(user.get_active_held_item()) - to_chat(user, span_warning("Your hands are full!")) - return - visible_message(span_warning("[user] starts picking up [src]."), \ - span_userdanger("[user] starts picking you up!")) - if(!do_after(user, 20, target = src)) - return - visible_message(span_warning("[user] picks up [src]!"), \ - span_userdanger("[user] picks you up!")) - if(buckled) - to_chat(user, span_warning("[src] is buckled to [buckled] and cannot be picked up!")) - return - to_chat(user, span_notice("You pick [src] up.")) - drop_all_held_items() - var/obj/item/clothing/head/mob_holder/drone/DH = new(get_turf(src), src) - DH.slot_flags = worn_slot_flags - user.put_in_hands(DH) +/mob/living/basic/drone/attack_hand(mob/user, list/modifiers) + if(isdrone(user)) + attack_drone(user) + return ..() + +/mob/living/basic/drone/mob_try_pickup(mob/living/user, instant=FALSE) + if(stat == DEAD || status_flags & GODMODE) + return + return ..() + +/mob/living/basic/drone/mob_pickup(mob/living/user) + drop_all_held_items() + return ..() /** * Called when a drone attempts to reactivate a dead drone @@ -64,7 +49,7 @@ * Arguments: * * user - The [/mob/living] attempting to reactivate the drone */ -/mob/living/simple_animal/drone/proc/try_reactivate(mob/living/user) +/mob/living/basic/drone/proc/try_reactivate(mob/living/user) var/mob/dead/observer/G = get_ghost() if(!client && (!G || !G.client)) var/list/faux_gadgets = list( @@ -91,9 +76,13 @@ else to_chat(user, span_warning("You need to remain still to reactivate [src]!")) - -/mob/living/simple_animal/drone/screwdriver_act(mob/living/user, obj/item/tool) +/// Screwdrivering repairs the drone to full hp, if it isn't dead. +/mob/living/basic/drone/screwdriver_act(mob/living/user, obj/item/tool) if(stat == DEAD) + if(isdrone(user)) + user.balloon_alert(user, "reactivate instead!") + else + user.balloon_alert(user, "can't fix!") return FALSE if(health >= maxHealth) to_chat(user, span_warning("[src]'s screws can't get any tighter!")) @@ -108,7 +97,8 @@ visible_message(span_notice("[user] tightens [src == user ? "[user.p_their()]" : "[src]'s"] loose screws!"), span_notice("[src == user ? "You tighten" : "[user] tightens"] your loose screws.")) return TOOL_ACT_TOOLTYPE_SUCCESS -/mob/living/simple_animal/drone/wrench_act(mob/living/user, obj/item/tool) +/// Wrenching un-hacks hacked drones. +/mob/living/basic/drone/wrench_act(mob/living/user, obj/item/tool) if(user == src) return FALSE user.visible_message( @@ -123,18 +113,19 @@ update_drone_hack(FALSE) return TOOL_ACT_TOOLTYPE_SUCCESS -/mob/living/simple_animal/drone/transferItemToLoc(obj/item/item, newloc, force, silent) +/mob/living/basic/drone/transferItemToLoc(obj/item/item, newloc, force, silent) return !(item.type in drone_item_whitelist_flat) && ..() -/mob/living/simple_animal/drone/getarmor(def_zone, type) +/mob/living/basic/drone/getarmor(def_zone, type) var/armorval = 0 if(head) armorval = head.get_armor_rating(type) return (armorval * get_armor_effectiveness()) //armor is reduced for tiny fragile drones -/mob/living/simple_animal/drone/proc/get_armor_effectiveness() - return 0 //multiplier for whatever head armor you wear as a drone +/// Returns a multiplier for any head armor you wear as a drone. +/mob/living/basic/drone/proc/get_armor_effectiveness() + return 0 /** * Hack or unhack a drone @@ -148,7 +139,7 @@ * Arguments * * hack - Boolean if the drone is being hacked or unhacked */ -/mob/living/simple_animal/drone/proc/update_drone_hack(hack) +/mob/living/basic/drone/proc/update_drone_hack(hack) if(!mind) return if(hack) @@ -171,7 +162,7 @@ speed = 1 //gotta go slow message_admins("[ADMIN_LOOKUPFLW(src)] became a hacked drone hellbent on destroying the station!") else - if(!hacked) + if(!hacked || !can_unhack) return Stun(40) visible_message(span_info("[src]'s display glows a content blue!"), \ @@ -189,17 +180,10 @@ update_drone_icon_hacked() /** - * # F R E E D R O N E - * ### R - * ### E - * ### E - * ### D - * ### R - * ### O - * ### N - * ### E + * Makes the drone into a Free Drone, who have no real laws and can do whatever they like. + * Only currently used for players wabbajacked into drones. */ -/mob/living/simple_animal/drone/proc/liberate() +/mob/living/basic/drone/proc/liberate() laws = "1. You are a Free Drone." set_shy(FALSE) to_chat(src, laws) @@ -208,12 +192,12 @@ * Changes the icon state to a hacked version * * See also - * * [/mob/living/simple_animal/drone/var/visualAppearance] + * * [/mob/living/basic/drone/var/visualAppearance] * * [MAINTDRONE] * * [REPAIRDRONE] * * [SCOUTDRONE] */ -/mob/living/simple_animal/drone/proc/update_drone_icon_hacked() //this is hacked both ways +/mob/living/basic/drone/proc/update_drone_icon_hacked() //this is hacked both ways var/static/hacked_appearances = list( SCOUTDRONE = SCOUTDRONE_HACKED, REPAIRDRONE = REPAIRDRONE_HACKED, diff --git a/code/modules/mob/living/basic/drone/inventory.dm b/code/modules/mob/living/basic/drone/inventory.dm new file mode 100644 index 000000000000..e63d370549da --- /dev/null +++ b/code/modules/mob/living/basic/drone/inventory.dm @@ -0,0 +1,83 @@ +// Drone inventory procs + +/mob/living/basic/drone/doUnEquip(obj/item/item, force, newloc, no_move, invdrop = TRUE, silent = FALSE) + if(..()) + update_held_items() + if(item == head) + head = null + update_worn_head() + if(item == internal_storage) + internal_storage = null + update_inv_internal_storage() + return TRUE + return FALSE + + +/mob/living/basic/drone/can_equip(obj/item/item, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE, ignore_equipped = FALSE, indirect_action = FALSE) + switch(slot) + if(ITEM_SLOT_HEAD) + if(head) + return FALSE + if(!((item.slot_flags & ITEM_SLOT_HEAD) || (item.slot_flags & ITEM_SLOT_MASK))) + return FALSE + return TRUE + if(ITEM_SLOT_DEX_STORAGE) + if(internal_storage) + return FALSE + return TRUE + ..() + + +/mob/living/basic/drone/get_item_by_slot(slot_id) + switch(slot_id) + if(ITEM_SLOT_HEAD) + return head + if(ITEM_SLOT_DEX_STORAGE) + return internal_storage + + return ..() + +/mob/living/basic/drone/get_slot_by_item(obj/item/looking_for) + if(internal_storage == looking_for) + return ITEM_SLOT_DEX_STORAGE + if(head == looking_for) + return ITEM_SLOT_HEAD + return ..() + +/mob/living/basic/drone/equip_to_slot(obj/item/equipping, slot, initial = FALSE, redraw_mob = FALSE, indirect_action = FALSE) + if(!slot) + return + if(!istype(equipping)) + return + + var/index = get_held_index_of_item(equipping) + if(index) + held_items[index] = null + update_held_items() + + if(equipping.pulledby) + equipping.pulledby.stop_pulling() + + equipping.screen_loc = null // will get moved if inventory is visible + equipping.forceMove(src) + SET_PLANE_EXPLICIT(equipping, ABOVE_HUD_PLANE, src) + + switch(slot) + if(ITEM_SLOT_HEAD) + head = equipping + update_worn_head() + if(ITEM_SLOT_DEX_STORAGE) + internal_storage = equipping + update_inv_internal_storage() + else + to_chat(src, span_danger("You are trying to equip this item to an unsupported inventory slot. Report this to a coder!")) + return + + //Call back for item being equipped to drone + equipping.on_equipped(src, slot) + +/mob/living/basic/drone/getBackSlot() + return ITEM_SLOT_DEX_STORAGE + +/mob/living/basic/drone/getBeltSlot() + return ITEM_SLOT_DEX_STORAGE diff --git a/code/modules/mob/living/simple_animal/friendly/drone/verbs.dm b/code/modules/mob/living/basic/drone/verbs.dm similarity index 72% rename from code/modules/mob/living/simple_animal/friendly/drone/verbs.dm rename to code/modules/mob/living/basic/drone/verbs.dm index da54c6a81cd3..833d37a3c894 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/verbs.dm +++ b/code/modules/mob/living/basic/drone/verbs.dm @@ -1,15 +1,9 @@ - -/////////////// -//DRONE VERBS// -/////////////// -//Drone verbs that appear in the Drone tab and on buttons - /** * Echoes drone laws to the user * - * See [/mob/living/simple_animal/drone/var/laws] + * See [/mob/living/basic/drone/var/laws] */ -/mob/living/simple_animal/drone/verb/check_laws() +/mob/living/basic/drone/verb/check_laws() set category = "Drone" set name = "Check Laws" @@ -27,7 +21,7 @@ * * Attaches area name to message */ -/mob/living/simple_animal/drone/verb/drone_ping() +/mob/living/basic/drone/verb/drone_ping() set category = "Drone" set name = "Drone ping" diff --git a/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm b/code/modules/mob/living/basic/drone/visuals_icons.dm similarity index 60% rename from code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm rename to code/modules/mob/living/basic/drone/visuals_icons.dm index aa9fa8825237..4ffd29b2a3ba 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm +++ b/code/modules/mob/living/basic/drone/visuals_icons.dm @@ -1,69 +1,33 @@ +// Drone overlays and visuals -///////////////// -//DRONE VISUALS// -///////////////// -//Drone overlays -//Drone visuals - - -/mob/living/simple_animal/drone/proc/apply_overlay(cache_index) +/mob/living/basic/drone/apply_overlay(cache_index) if((. = drone_overlays[cache_index])) add_overlay(.) -/mob/living/simple_animal/drone/proc/remove_overlay(cache_index) - var/I = drone_overlays[cache_index] - if(I) - cut_overlay(I) +/mob/living/basic/drone/remove_overlay(cache_index) + var/overlay = drone_overlays[cache_index] + if(overlay) + cut_overlay(overlay) drone_overlays[cache_index] = null - -/mob/living/simple_animal/drone/update_held_items() - remove_overlay(DRONE_HANDS_LAYER) - var/list/hands_overlays = list() - - var/obj/item/l_hand = get_item_for_held_index(1) - var/obj/item/r_hand = get_item_for_held_index(2) - - var/y_shift = getItemPixelShiftY() - - if(r_hand) - var/mutable_appearance/r_hand_overlay = r_hand.build_worn_icon(default_layer = DRONE_HANDS_LAYER, default_icon_file = r_hand.righthand_file, isinhands = TRUE) - if(y_shift) - r_hand_overlay.pixel_y += y_shift - - hands_overlays += r_hand_overlay - - if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) - SET_PLANE_EXPLICIT(r_hand, ABOVE_HUD_PLANE, src) - r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand)) - client.screen |= r_hand - - if(l_hand) - var/mutable_appearance/l_hand_overlay = l_hand.build_worn_icon(default_layer = DRONE_HANDS_LAYER, default_icon_file = l_hand.lefthand_file, isinhands = TRUE) - if(y_shift) - l_hand_overlay.pixel_y += y_shift - - hands_overlays += l_hand_overlay - - if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) - SET_PLANE_EXPLICIT(l_hand, ABOVE_HUD_PLANE, src) - l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand)) - client.screen |= l_hand - - - if(hands_overlays.len) - drone_overlays[DRONE_HANDS_LAYER] = hands_overlays - apply_overlay(DRONE_HANDS_LAYER) - - -/mob/living/simple_animal/drone/proc/update_inv_internal_storage() +/mob/living/basic/drone/update_clothing(slot_flags) + if(slot_flags & ITEM_SLOT_HEAD) + update_worn_head() + if(slot_flags & ITEM_SLOT_MASK) + update_worn_mask() + if(slot_flags & ITEM_SLOT_HANDS) + update_held_items() + if(slot_flags & (ITEM_SLOT_HANDS|ITEM_SLOT_BACKPACK|ITEM_SLOT_DEX_STORAGE)) + update_inv_internal_storage() + +/mob/living/basic/drone/proc/update_inv_internal_storage() if(internal_storage && client && hud_used?.hud_shown) internal_storage.screen_loc = ui_drone_storage client.screen += internal_storage -/mob/living/simple_animal/drone/update_worn_head() +/mob/living/basic/drone/update_worn_head() remove_overlay(DRONE_HEAD_LAYER) if(head) @@ -80,10 +44,10 @@ apply_overlay(DRONE_HEAD_LAYER) -/mob/living/simple_animal/drone/update_worn_mask() +/mob/living/basic/drone/update_worn_mask() update_worn_head() -/mob/living/simple_animal/drone/regenerate_icons() +/mob/living/basic/drone/regenerate_icons() // Drones only have 4 slots, which in this specific instance // is a small blessing. update_held_items() @@ -91,13 +55,13 @@ update_inv_internal_storage() /** - * Prompt for usr to pick [/mob/living/simple_animal/drone/var/visualAppearance] + * Prompt for user to pick [/mob/living/basic/drone/var/visualAppearance] * - * Does nothing if there is no usr + * Does nothing if there is no user * * Called on [/mob/proc/Login] */ -/mob/living/simple_animal/drone/proc/pickVisualAppearance() +/mob/living/basic/drone/proc/pickVisualAppearance() picked = FALSE var/list/drone_icons = list( "Maintenance Drone" = image(icon = 'icons/mob/silicon/drone.dmi', icon_state = "[MAINTDRONE]_grey"), @@ -141,14 +105,14 @@ /** * check_menu: Checks if we are allowed to interact with a radial menu */ -/mob/living/simple_animal/drone/proc/check_menu() +/mob/living/basic/drone/proc/check_menu() if(!istype(src)) return FALSE if(incapacitated()) return FALSE return TRUE -/mob/living/simple_animal/drone/proc/getItemPixelShiftY() +/mob/living/basic/drone/proc/getItemPixelShiftY() switch(visualAppearance) if(MAINTDRONE) . = 0 diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm index 2dee3b154611..3d2d81bf9c19 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm @@ -77,13 +77,25 @@ if(valid_hives.len) return pick(valid_hives) -/datum/targetting_datum/basic/bee +/datum/targeting_strategy/basic/bee -/datum/targetting_datum/basic/bee/can_attack(mob/living/owner, atom/target) +/datum/targeting_strategy/basic/bee/can_attack(mob/living/owner, atom/target, vision_range) if(!isliving(target)) return FALSE . = ..() if(!.) return FALSE var/mob/living/mob_target = target + + if(mob_target.mob_biotypes & MOB_PLANT) + return FALSE + + var/datum/ai_controller/basic_controller/bee_ai = owner.ai_controller + if(isnull(bee_ai)) + return FALSE + + var/atom/bee_hive = bee_ai.blackboard[BB_CURRENT_HOME] + if(bee_hive && get_dist(target, bee_hive) > 5) + return FALSE + return !(mob_target.bee_friendly()) diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm index 4eb47eb0c53c..242eb9ae4d7e 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm @@ -1,6 +1,7 @@ /datum/ai_controller/basic_controller/bee blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/bee, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/bee, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) ai_traits = STOP_MOVING_WHEN_PULLED @@ -17,7 +18,7 @@ /datum/ai_controller/basic_controller/queen_bee blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/bee, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/bee, ) ai_traits = STOP_MOVING_WHEN_PULLED diff --git a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm index 949a3cd0c34e..2256f7c45990 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm @@ -62,7 +62,7 @@ if(!food_types) food_types = src.food_types.Copy() AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed))) - AddElement(/datum/element/basic_eating, 10, 0, null, food_types) + AddElement(/datum/element/basic_eating, food_types = food_types) /mob/living/basic/cow/proc/tamed(mob/living/tamer) buckle_lying = 0 diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_ai.dm b/code/modules/mob/living/basic/farm_animals/cow/cow_ai.dm index 81e1d722b01e..e1e611a28c2a 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/cow_ai.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_ai.dm @@ -1,6 +1,6 @@ /datum/ai_controller/basic_controller/cow blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, BB_BASIC_MOB_TIP_REACTING = FALSE, BB_BASIC_MOB_TIPPER = null, ) diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm index cdfb0868f9e0..f499859e19e9 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm @@ -34,7 +34,7 @@ var/static/list/food_types if(!food_types) food_types = src.food_types.Copy() - AddElement(/datum/element/basic_eating, 10, 0, null, food_types) + AddElement(/datum/element/basic_eating, food_types = food_types) AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed))) /mob/living/basic/cow/moonicorn/tamed(mob/living/tamer) @@ -44,7 +44,7 @@ /datum/ai_controller/basic_controller/cow/moonicorn blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items/moonicorn(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items/moonicorn, BB_BASIC_MOB_TIP_REACTING = FALSE, BB_BASIC_MOB_TIPPER = null, ) @@ -60,14 +60,14 @@ ) ///moonicorns will not attack people holding something that could tame them. -/datum/targetting_datum/basic/allow_items/moonicorn +/datum/targeting_strategy/basic/allow_items/moonicorn -/datum/targetting_datum/basic/allow_items/moonicorn/can_attack(mob/living/living_mob, atom/the_target) +/datum/targeting_strategy/basic/allow_items/moonicorn/can_attack(mob/living/living_mob, atom/the_target, vision_range) . = ..() if(!.) return FALSE - if(isliving(the_target)) //Targetting vs living mobs + if(isliving(the_target)) //Targeting vs living mobs var/mob/living/living_target = the_target for(var/obj/item/food/grown/galaxythistle/tame_food in living_target.held_items) return FALSE //heyyy this can tame me! let's NOT fight diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.dm b/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.dm index d322f49ae41c..e87b4704fa91 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.dm @@ -22,7 +22,7 @@ return //cannot tame me! and I don't care about eatin' nothing, neither! /datum/ai_controller/basic_controller/cow/wisdom - //don't give a targetting datum + //don't give a targeting strategy blackboard = list( BB_BASIC_MOB_TIP_REACTING = FALSE, BB_BASIC_MOB_TIPPER = null, diff --git a/code/modules/mob/living/basic/farm_animals/deer.dm b/code/modules/mob/living/basic/farm_animals/deer.dm index a016f8b74a6f..1bd185c1509c 100644 --- a/code/modules/mob/living/basic/farm_animals/deer.dm +++ b/code/modules/mob/living/basic/farm_animals/deer.dm @@ -34,9 +34,8 @@ /datum/ai_controller/basic_controller/deer blackboard = list( - BB_BASIC_MOB_FLEEING = TRUE, BB_STATIONARY_MOVE_TO_TARGET = TRUE, - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/farm_animals/goat/_goat.dm b/code/modules/mob/living/basic/farm_animals/goat/_goat.dm new file mode 100644 index 000000000000..9c7046cccf7d --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goat/_goat.dm @@ -0,0 +1,146 @@ +/obj/item/food/meat/slab/grassfed + name = "eco meat" + desc = "A slab of 100% grass fed award-winning farm meat." + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 3, + /datum/reagent/consumable/nutriment/vitamin = 1, + ) // Marble + + +/// The Greatest (animal) Of All Time. Cud chewing, shin-kicking, kitchen-dwelling nuisance. +/mob/living/basic/goat + name = "goat" + desc = "Not known for their pleasant disposition." + icon_state = "goat" + icon_living = "goat" + icon_dead = "goat_dead" + + speak_emote = list("brays") + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently pushes aside" + response_disarm_simple = "gently push aside" + response_harm_continuous = "kicks" + response_harm_simple = "kick" + attack_verb_continuous = "kicks" + attack_verb_simple = "kick" + attack_sound = 'sound/weapons/punch1.ogg' + attack_vis_effect = ATTACK_EFFECT_KICK + + butcher_results = list(/obj/item/food/meat/slab/grassfed = 4) + + faction = list(FACTION_NEUTRAL) + mob_biotypes = MOB_ORGANIC | MOB_BEAST + + health = 40 + maxHealth = 40 + melee_damage_lower = 1 + melee_damage_upper = 2 + environment_smash = ENVIRONMENT_SMASH_NONE + + minimum_survivable_temperature = COLD_ROOM_TEMP - 75 // enough so that they can survive the cold room spawn with plenty of room for comfort + + blood_volume = BLOOD_VOLUME_NORMAL + + ai_controller = /datum/ai_controller/basic_controller/goat + /// How often will we develop an evil gleam in our eye? + var/gleam_delay = 20 SECONDS + /// Time until we can next gleam evilly + COOLDOWN_DECLARE(gleam_cooldown) + /// List of stuff (flora) that we want to eat + var/static/list/edibles = list( + /obj/structure/alien/resin/flower_bud, + /obj/structure/glowshroom, + /obj/structure/spacevine, + ) + +/mob/living/basic/goat/Initialize(mapload) + . = ..() + add_udder() + AddElement(/datum/element/cliff_walking) //we walk the cliff + AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_SHOE) + AddElement(/datum/element/ai_retaliate) + + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_pre_attack)) + RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) + RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_move)) + + ai_controller.set_blackboard_key(BB_BASIC_FOODS, edibles) + +/// Called when we attack something in order to piece together the intent of the AI/user and provide desired behavior. The element might be okay here but I'd rather the fluff. +/// Goats are really good at beating up plants by taking bites out of them, but we use the default attack for everything else +/mob/living/basic/goat/proc/on_pre_attack(datum/source, atom/target) + if(is_type_in_list(target, edibles)) + eat_plant(list(target)) + return COMPONENT_HOSTILE_NO_ATTACK + + if(!isliving(target)) + return + + var/mob/living/living_target = target + if(!(living_target.mob_biotypes & MOB_PLANT)) + return + + living_target.adjustBruteLoss(20) + playsound(src, 'sound/items/eatfood.ogg', rand(30, 50), TRUE) + var/obj/item/bodypart/edible_bodypart + + if(ishuman(living_target)) + var/mob/living/carbon/human/plant_man = target + edible_bodypart = pick(plant_man.bodyparts) + edible_bodypart.dismember() + + living_target.visible_message( + span_warning("[src] takes a big chomp out of [living_target]!"), + span_userdanger("[src] takes a big chomp out of your [edible_bodypart || "body"]!"), + ) + + return COMPONENT_HOSTILE_NO_ATTACK + +/// If we are being attacked by someone, give a nice fluff message. But only once in a while. +/mob/living/basic/goat/proc/on_attacked(datum/source, atom/attacker, attack_flags) + if (!COOLDOWN_FINISHED(src, gleam_cooldown)) + return + visible_message( + span_danger("[src] gets an evil-looking gleam in [p_their()] eye."), + ) + COOLDOWN_START(src, gleam_cooldown, gleam_delay) + +/// Handles automagically eating a plant when we move into a turf that has one. +/mob/living/basic/goat/proc/on_move(datum/source, atom/entering_loc) + SIGNAL_HANDLER + if(!isturf(entering_loc)) + return + + var/list/edible_plants = list() + for(var/obj/target in entering_loc) + if(is_type_in_list(target, edibles)) + edible_plants += target + + INVOKE_ASYNC(src, PROC_REF(eat_plant), edible_plants) + +/// When invoked, adds an udder. Overridden on subtypes +/mob/living/basic/goat/proc/add_udder() + AddComponent(/datum/component/udder) + +/// Proc that handles dealing with the various types of plants we might eat. Assumes that a valid list of type(s) will be passed in. +/mob/living/basic/goat/proc/eat_plant(list/plants) + var/eaten = FALSE + + for(var/atom/target as anything in plants) + if(istype(target, /obj/structure/spacevine)) + var/obj/structure/spacevine/vine = target + vine.eat(src) + eaten = TRUE + + if(istype(target, /obj/structure/alien/resin/flower_bud)) + target.take_damage(rand(30, 50), BRUTE, 0) + eaten = TRUE + + if(istype(target, /obj/structure/glowshroom)) + qdel(target) + eaten = TRUE + + if(eaten && prob(10)) + say("Nom") // bon appetit + playsound(src, 'sound/items/eatfood.ogg', rand(30, 50), TRUE) diff --git a/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm b/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm new file mode 100644 index 000000000000..5928344fee4e --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm @@ -0,0 +1,23 @@ +/// Goats are normally content to sorta hang around and crunch any plant in sight, but they will go ape on someone who attacks them. +/datum/ai_controller/basic_controller/goat + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + + ai_traits = STOP_MOVING_WHEN_PULLED + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + + planning_subtrees = list( + /datum/ai_planning_subtree/capricious_retaliate, // Capricious like Capra, get it? + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/random_speech/goat, + ) + +/datum/ai_planning_subtree/random_speech/goat + speech_chance = 3 + emote_hear = list("brays.") + emote_see = list("shakes their head.", "stamps a foot.", "glares around.") + speak = list("EHEHEHEHEH", "eh?") diff --git a/code/modules/mob/living/basic/farm_animals/goat/goat_subtypes.dm b/code/modules/mob/living/basic/farm_animals/goat/goat_subtypes.dm new file mode 100644 index 000000000000..0638163d0324 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goat/goat_subtypes.dm @@ -0,0 +1,12 @@ +/mob/living/basic/goat/pete // Pete! + name = "Pete" + gender = MALE + +/mob/living/basic/goat/pete/examine() + . = ..() + var/area/goat_area = get_area(src) + if((bodytemperature < T20C) || istype(goat_area, /area/station/service/kitchen/coldroom)) + . = span_notice("[p_They()] [p_do()]n't seem to be too bothered about the cold.") // special for pete + +/mob/living/basic/goat/pete/add_udder() + return //no thank you diff --git a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm new file mode 100644 index 000000000000..3fcb60fb77c1 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm @@ -0,0 +1,178 @@ +/// Where do we draw gorilla held overlays? +#define GORILLA_HANDS_LAYER 1 + +/** + * Like a bigger monkey + * They make a lot of noise and punch limbs off unconscious folks + */ +/mob/living/basic/gorilla + name = "Gorilla" + desc = "A ground-dwelling, predominantly herbivorous ape which usually inhabits the forests of central Africa but today is quite far away from there." + icon = 'icons/mob/simple/gorilla.dmi' + icon_state = "crawling" + icon_living = "crawling" + icon_dead = "dead" + health_doll_icon = "crawling" + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + maxHealth = 220 + health = 220 + response_help_continuous = "prods" + response_help_simple = "prod" + response_disarm_continuous = "challenges" + response_disarm_simple = "challenge" + response_harm_continuous = "thumps" + response_harm_simple = "thump" + speed = 0.5 + melee_damage_lower = 15 + melee_damage_upper = 18 + damage_coeff = list(BRUTE = 1, BURN = 1.5, TOX = 1.5, CLONE = 0, STAMINA = 0, OXY = 1.5) + obj_damage = 20 + attack_verb_continuous = "pummels" + attack_verb_simple = "pummel" + attack_sound = 'sound/weapons/punch1.ogg' + unique_name = TRUE + ai_controller = /datum/ai_controller/basic_controller/gorilla + faction = list(FACTION_MONKEY, FACTION_JUNGLE) + butcher_results = list(/obj/item/food/meat/slab/gorilla = 4, /obj/effect/gibspawner/generic/animal = 1) + /// How likely our meaty fist is to stun someone + var/paralyze_chance = 20 + /// A counter for when we can scream again + var/oogas = 0 + /// Types of things we want to find and eat + var/static/list/gorilla_food = list( + /obj/item/food/bread/banana, + /obj/item/food/breadslice/banana, + /obj/item/food/cnds/banana_honk, + /obj/item/food/grown/banana, + /obj/item/food/salad/fruit, + /obj/item/food/salad/jungle, + /obj/item/food/sundae, + ) + +/mob/living/basic/gorilla/Initialize(mapload) + . = ..() + add_traits(list(TRAIT_ADVANCEDTOOLUSER, TRAIT_CAN_STRIP), ROUNDSTART_TRAIT) + AddElement(/datum/element/wall_smasher) + AddElement(/datum/element/dextrous) + AddElement(/datum/element/footstep, FOOTSTEP_MOB_BAREFOOT) + AddElement(/datum/element/basic_eating, heal_amt = 10, food_types = gorilla_food) + AddElement( + /datum/element/amputating_limbs, \ + surgery_time = 0 SECONDS, \ + surgery_verb = "punches",\ + ) + AddComponent(/datum/component/personal_crafting) + AddComponent(/datum/component/basic_inhands, y_offset = -1) + ai_controller?.set_blackboard_key(BB_BASIC_FOODS, gorilla_food) + +/mob/living/basic/gorilla/update_overlays() + . = ..() + if (is_holding_items()) + . += "standing_overlay" + +/mob/living/basic/gorilla/update_icon_state() + . = ..() + if (stat == DEAD) + return + icon_state = is_holding_items() ? "standing" : "crawling" + +/mob/living/basic/gorilla/update_held_items() + . = ..() + update_appearance(UPDATE_ICON) + if (is_holding_items()) + add_movespeed_modifier(/datum/movespeed_modifier/gorilla_standing) + else + remove_movespeed_modifier(/datum/movespeed_modifier/gorilla_standing) + +/mob/living/basic/gorilla/melee_attack(mob/living/target, list/modifiers, ignore_cooldown) + . = ..() + if (!. || !isliving(target)) + return + ooga_ooga() + if (prob(paralyze_chance)) + target.Paralyze(2 SECONDS) + visible_message(span_danger("[src] knocks [target] down!")) + else + target.throw_at(get_edge_target_turf(target, dir), range = rand(1, 2), speed = 7, thrower = src) + +/mob/living/basic/gorilla/gib() + var/mob/living/brain/gorilla_brain = new(drop_location()) + gorilla_brain.name = real_name + gorilla_brain.real_name = real_name + mind?.transfer_to(gorilla_brain) + return ..() + +/mob/living/basic/gorilla/can_use_guns(obj/item/gun) + to_chat(src, span_warning("Your meaty finger is much too large for the trigger guard!")) + return FALSE + +/// Assert your dominance with audio cues +/mob/living/basic/gorilla/proc/ooga_ooga() + if (isnull(client)) + return // Sorry NPCs + oogas -= 1 + if(oogas > 0) + return + oogas = rand(2,6) + emote("ooga") + +/// Gorillas are slower when carrying something +/datum/movespeed_modifier/gorilla_standing + blacklisted_movetypes = (FLYING|FLOATING) + multiplicative_slowdown = 0.5 + +/// A smaller gorilla summoned via magic +/mob/living/basic/gorilla/lesser + name = "lesser Gorilla" + desc = "An adolescent Gorilla. It may not be fully grown but, much like a banana, that just means it's sturdier and harder to chew!" + maxHealth = 120 + health = 120 + speed = 0.35 + melee_damage_lower = 10 + melee_damage_upper = 15 + obj_damage = 15 + ai_controller = /datum/ai_controller/basic_controller/gorilla/lesser + butcher_results = list(/obj/item/food/meat/slab/gorilla = 2) + +/mob/living/basic/gorilla/lesser/Initialize(mapload) + . = ..() + transform *= 0.75 + +/// Cargo's wonderful mascot, the tranquil box-carrying ape +/mob/living/basic/gorilla/cargorilla + name = "Cargorilla" // Overriden, normally + icon = 'icons/mob/simple/cargorillia.dmi' + desc = "Cargo's pet gorilla. They seem to have an 'I love Mom' tattoo." + maxHealth = 200 + health = 200 + faction = list(FACTION_NEUTRAL, FACTION_MONKEY, FACTION_JUNGLE) + unique_name = FALSE + ai_controller = null + +/mob/living/basic/gorilla/cargorilla/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_PACIFISM, INNATE_TRAIT) + AddComponent(/datum/component/crate_carrier) + +/** + * Poll ghosts for control of the gorilla. Not added in init because we only want to poll when the round starts. + * Preferably in future we can replace this with a popup on the lobby to queue to become a gorilla. + */ +/mob/living/basic/gorilla/cargorilla/proc/poll_for_gorilla() + AddComponent(\ + /datum/component/ghost_direct_control,\ + poll_candidates = TRUE,\ + poll_length = 30 SECONDS,\ + role_name = "Cargorilla",\ + assumed_control_message = "You are Cargorilla, a pacifist friend of the station and carrier of freight.",\ + poll_ignore_key = POLL_IGNORE_CARGORILLA,\ + after_assumed_control = CALLBACK(src, PROC_REF(became_player_controlled)),\ + ) + +/// Called once a ghost assumes control +/mob/living/basic/gorilla/cargorilla/proc/became_player_controlled() + mind.set_assigned_role(SSjob.GetJobType(/datum/job/cargo_technician)) + mind.special_role = "Cargorilla" + to_chat(src, span_notice("You can pick up crates by clicking on them, and drop them by clicking on the ground.")) + +#undef GORILLA_HANDS_LAYER diff --git a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_accessories.dm b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_accessories.dm new file mode 100644 index 000000000000..814e56487bf5 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_accessories.dm @@ -0,0 +1,5 @@ +/// Cargorilla's ID card +/obj/item/card/id/advanced/cargo_gorilla + name = "cargorilla ID" + desc = "A card used to provide ID and determine access across the station. A gorilla-sized ID for a gorilla-sized cargo technician." + trim = /datum/id_trim/job/cargo_technician diff --git a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm new file mode 100644 index 000000000000..28a727fdb1bc --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm @@ -0,0 +1,33 @@ +/// Pretty basic, just click people to death. Also hunt and eat bananas. +/datum/ai_controller/basic_controller/gorilla + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, + BB_TARGET_MINIMUM_STAT = UNCONSCIOUS, + BB_EMOTE_KEY = "ooga", + BB_EMOTE_CHANCE = 40, + ) + + ai_traits = STOP_MOVING_WHEN_PULLED + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + + planning_subtrees = list( + /datum/ai_planning_subtree/run_emote, + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path/gorilla, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/datum/ai_planning_subtree/attack_obstacle_in_path/gorilla + attack_behaviour = /datum/ai_behavior/attack_obstructions/gorilla + +/datum/ai_behavior/attack_obstructions/gorilla + can_attack_turfs = TRUE + +/datum/ai_controller/basic_controller/gorilla/lesser + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, + BB_EMOTE_KEY = "ooga", + BB_EMOTE_CHANCE = 60, + ) diff --git a/code/modules/mob/living/simple_animal/hostile/gorilla/emotes.dm b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_emotes.dm similarity index 78% rename from code/modules/mob/living/simple_animal/hostile/gorilla/emotes.dm rename to code/modules/mob/living/basic/farm_animals/gorilla/gorilla_emotes.dm index 20166d813919..94133336c4d4 100644 --- a/code/modules/mob/living/simple_animal/hostile/gorilla/emotes.dm +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_emotes.dm @@ -1,5 +1,5 @@ /datum/emote/gorilla - mob_type_allowed_typecache = /mob/living/simple_animal/hostile/gorilla + mob_type_allowed_typecache = /mob/living/basic/gorilla mob_type_blacklist_typecache = list() /datum/emote/gorilla/ooga @@ -9,4 +9,3 @@ message_param = "oogas at %t." emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE sound = 'sound/creatures/gorilla.ogg' - diff --git a/code/modules/mob/living/basic/farm_animals/pig.dm b/code/modules/mob/living/basic/farm_animals/pig.dm index abe5b9c3a742..94183d044c53 100644 --- a/code/modules/mob/living/basic/farm_animals/pig.dm +++ b/code/modules/mob/living/basic/farm_animals/pig.dm @@ -29,11 +29,11 @@ ai_controller = /datum/ai_controller/basic_controller/pig /mob/living/basic/pig/Initialize(mapload) + . = ..() AddElement(/datum/element/pet_bonus, "oinks!") AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/ai_flee_while_injured) make_tameable() - . = ..() ///wrapper for the tameable component addition so you can have non tamable cow subtypes /mob/living/basic/pig/proc/make_tameable() @@ -47,7 +47,7 @@ /datum/ai_controller/basic_controller/pig blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED @@ -59,4 +59,5 @@ /datum/ai_planning_subtree/flee_target, /datum/ai_planning_subtree/target_retaliate, /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/random_speech/pig, ) diff --git a/code/modules/mob/living/basic/farm_animals/rabbit.dm b/code/modules/mob/living/basic/farm_animals/rabbit.dm index 071470264dbb..992a127e05f4 100644 --- a/code/modules/mob/living/basic/farm_animals/rabbit.dm +++ b/code/modules/mob/living/basic/farm_animals/rabbit.dm @@ -47,8 +47,7 @@ /datum/ai_controller/basic_controller/rabbit blackboard = list( - BB_BASIC_MOB_FLEEING = TRUE, - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/farm_animals/sheep.dm b/code/modules/mob/living/basic/farm_animals/sheep.dm index 4102bd83fdf6..fbf6560fa8f0 100644 --- a/code/modules/mob/living/basic/farm_animals/sheep.dm +++ b/code/modules/mob/living/basic/farm_animals/sheep.dm @@ -81,8 +81,7 @@ /datum/ai_controller/basic_controller/sheep blackboard = list( - BB_BASIC_MOB_FLEEING = TRUE, - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/festivus_pole.dm b/code/modules/mob/living/basic/festivus_pole.dm index d556e235272f..0b61f02ed17a 100644 --- a/code/modules/mob/living/basic/festivus_pole.dm +++ b/code/modules/mob/living/basic/festivus_pole.dm @@ -42,7 +42,9 @@ /mob/living/basic/festivus/Initialize(mapload) . = ..() - AddElement(/datum/element/death_drops, list(/obj/item/stack/rods)) + AddComponent(/datum/component/seethrough_mob) + var/static/list/death_loot = list(/obj/item/stack/rods) + AddElement(/datum/element/death_drops, death_loot) AddComponent(/datum/component/aggro_emote, emote_list = string_list(list("growls")), emote_chance = 20) var/datum/action/cooldown/mob_cooldown/charge_apc/charge_ability = new(src) charge_ability.Grant(src) @@ -50,7 +52,7 @@ /datum/ai_controller/basic_controller/festivus_pole blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_LOW_PRIORITY_HUNTING_TARGET = null, // APCs ) diff --git a/code/modules/mob/living/basic/guardian/guardian.dm b/code/modules/mob/living/basic/guardian/guardian.dm new file mode 100644 index 000000000000..74004a9f1394 --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian.dm @@ -0,0 +1,332 @@ +/** + * A mob which acts as a guardian angel to another mob, sharing health but protecting them using special powers. + * Usually either obtained in magical form by a wizard, or technological form by a traitor. Sometimes found by miners. + */ +/mob/living/basic/guardian + name = "Guardian Spirit" + real_name = "Guardian Spirit" + desc = "A mysterious being that stands by its charge, ever vigilant." + icon = 'icons/mob/nonhuman-player/guardian.dmi' + icon_state = "magicbase" + icon_living = "magicbase" + icon_dead = "magicbase" + gender = NEUTER + basic_mob_flags = DEL_ON_DEATH + sentience_type = SENTIENCE_HUMANOID + hud_type = /datum/hud/guardian + faction = list() + speed = 0 + maxHealth = INFINITY // The spirit itself is invincible and passes damage to its host + health = INFINITY + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + unsuitable_atmos_damage = 0 + unsuitable_cold_damage = 0 + unsuitable_heat_damage = 0 + speak_emote = list("hisses") + bubble_icon = "guardian" + response_help_continuous = "passes through" + response_help_simple = "pass through" + response_disarm_continuous = "flails at" + response_disarm_simple = "flail at" + response_harm_continuous = "punches" + response_harm_simple = "punch" + attack_sound = 'sound/weapons/punch1.ogg' + attack_verb_continuous = "punches" + attack_verb_simple = "punch" + istate = ISTATE_HARM + obj_damage = 40 + melee_damage_lower = 15 + melee_damage_upper = 15 + melee_attack_cooldown = CLICK_CD_MELEE + light_system = MOVABLE_LIGHT + light_outer_range = 3 + light_on = FALSE + + /// The summoner of the guardian, we share health with them and can't move too far away (usually) + var/mob/living/summoner + /// How far from the summoner the guardian can be. + var/range = 10 + + /// The guardian's colour, used for their sprite, chat, and some effects. + var/guardian_colour + /// Coloured overlay we apply + var/mutable_appearance/overlay + + /// Which toggle button the HUD uses. + var/toggle_button_type = /atom/movable/screen/guardian/toggle_mode/inactive + /// Name used by the guardian creator. + var/creator_name = "Error" + /// Description used by the guardian creator. + var/creator_desc = "This shouldn't be here! Report it on GitHub!" + /// Icon used by the guardian creator. + var/creator_icon = "fuck" + + /// What type of guardian are we? + var/guardian_type = null + /// How are we themed? + var/datum/guardian_fluff/theme + /// A string explaining to the guardian what they can do. + var/playstyle_string = span_boldholoparasite("You are a Guardian without any type. You shouldn't exist and are an affront to god!") + + /// Are we forced to not be able to manifest/recall? + var/locked = FALSE + /// Cooldown between manifests/recalls. + COOLDOWN_DECLARE(manifest_cooldown) + /// Cooldown between the summoner resetting the guardian's client. + COOLDOWN_DECLARE(resetting_cooldown) + + /// List of actions we give to our summoner + var/static/list/control_actions = list( + /datum/action/cooldown/mob_cooldown/guardian_comms, + /datum/action/cooldown/mob_cooldown/recall_guardian, + /datum/action/cooldown/mob_cooldown/replace_guardian, + ) + +/mob/living/basic/guardian/Initialize(mapload, datum/guardian_fluff/theme) + . = ..() + GLOB.parasites += src + src.theme = theme + theme?.apply(src) + var/list/death_loot = string_list(list(/obj/item/stack/sheet/mineral/wood)) + AddElement(/datum/element/death_drops, death_loot) + AddElement(/datum/element/simple_flying) + AddComponent(/datum/component/basic_inhands) + // life link + update_appearance(UPDATE_ICON) + manifest_effects() + +/mob/living/basic/guardian/Destroy() + GLOB.parasites -= src + if (is_deployed()) + recall_effects() + cut_summoner(different_person = TRUE) + return ..() + +/mob/living/basic/guardian/update_overlays() + . = ..() + . += overlay + +/mob/living/basic/guardian/Login() //if we have a mind, set its name to ours when it logs in + . = ..() + if (!. || isnull(client)) + return FALSE + if (isnull(summoner)) + to_chat(src, span_boldholoparasite("For some reason, somehow, you have no summoner. Please report this bug immediately.")) + stack_trace("Guardian created with client but no summoner.") + else + to_chat(src, span_holoparasite("You are a [theme.name], bound to serve [summoner.real_name].")) + to_chat(src, span_holoparasite("You are capable of manifesting or recalling to your master with the buttons on your HUD. You will also find a button to communicate with [summoner.p_them()] privately there.")) + to_chat(src, span_holoparasite("While personally invincible, you will die if [summoner.real_name] does, and any damage dealt to you will have a portion passed on to [summoner.p_them()] as you feed upon [summoner.p_them()] to sustain yourself.")) + to_chat(src, playstyle_string) + if (!isnull(guardian_colour)) + return // Already set up so we don't need to do it again + locked = TRUE + guardian_rename() + guardian_recolour() + locked = FALSE + +/mob/living/basic/guardian/mind_initialize() + . = ..() + if (isnull(summoner)) + to_chat(src, span_boldholoparasite("For some reason, somehow, you have no summoner. Please report this bug immediately.")) + return + mind.enslave_mind_to_creator(summoner) // Once our mind is created, we become enslaved to our summoner. cant be done in the first run of set_summoner, because by then we dont have a mind yet. + +/// Pick a new colour for our guardian +/mob/living/basic/guardian/proc/guardian_recolour() + if (isnull(client)) + return + var/chosen_guardian_colour = input(src, "What would you like your colour to be?", "Choose Your Colour", "#ffffff") as color|null + if (isnull(chosen_guardian_colour)) //redo proc until we get a color + to_chat(src, span_warning("Invalid colour, please try again.")) + return guardian_recolour() + set_guardian_colour(chosen_guardian_colour) + +/// Apply a new colour to our guardian +/mob/living/basic/guardian/proc/set_guardian_colour(colour) + guardian_colour = colour + set_light_color(guardian_colour) + overlay?.color = guardian_colour + update_appearance(UPDATE_ICON) + +/mob/living/basic/guardian/proc/guardian_rename() + if (isnull(client)) + return + + var/new_name = sanitize_name(reject_bad_text(tgui_input_text(src, "What would you like your name to be?", "Choose Your Name", generate_random_name(), MAX_NAME_LEN))) + if (!new_name) //redo proc until we get a good name + to_chat(src, span_warning("Invalid name, please try again.")) + return guardian_rename() + to_chat(src, span_notice("Your new name [span_name(new_name)] anchors itself in your mind.")) + fully_replace_character_name(null, new_name) + +/// Picks a random name as a suggestion +/mob/living/basic/guardian/proc/generate_random_name() + var/list/surname_options = list("Guardian") // Fallback in case you define a guardian with no theme + switch(theme?.fluff_type) + if (GUARDIAN_MAGIC) + surname_options = GLOB.guardian_fantasy_surnames + if (GUARDIAN_TECH) + surname_options = GLOB.guardian_tech_surnames + + return "[pick(GLOB.guardian_first_names)] [pick(surname_options)]" + +/mob/living/basic/guardian/melee_attack(atom/target, list/modifiers, ignore_cooldown) + if (!is_deployed()) + balloon_alert(src, "not tangible!") + return FALSE + return ..() + +/mob/living/basic/guardian/death(gibbed) + if (!QDELETED(summoner)) + to_chat(summoner, span_bolddanger("Your [name] died somehow!")) + summoner.dust() + return ..() + +/mob/living/basic/guardian/ex_act(severity, target) + switch(severity) + if (EXPLODE_DEVASTATE) + investigate_log("has been gibbed by an explosion.", INVESTIGATE_DEATHS) + gib() + return TRUE + if (EXPLODE_HEAVY) + adjustBruteLoss(60) + if (EXPLODE_LIGHT) + adjustBruteLoss(30) + + return TRUE + +/mob/living/basic/guardian/gib() + death(TRUE) + +/mob/living/basic/guardian/dust(just_ash, drop_items, force) + death(TRUE) + +/// Link up with a summoner mob. +/mob/living/basic/guardian/proc/set_summoner(mob/living/to_who, different_person = FALSE) + if (QDELETED(src)) + return // Just in case + if (QDELETED(to_who)) + ghostize(FALSE) + qdel(src) // No life of free invulnerability for you. + return + cut_summoner(different_person) + AddComponent(/datum/component/life_link, to_who, CALLBACK(src, PROC_REF(on_harm)), CALLBACK(src, PROC_REF(on_summoner_death))) + summoner = to_who + + for (var/action_type in control_actions) + if (locate(action_type) in summoner.actions) + continue + var/datum/action/new_action = new action_type(summoner) + new_action.Grant(summoner) + + if (different_person) + if (mind) + mind.enslave_mind_to_creator(to_who) + else //mindless guardian, manually give them factions + faction += summoner.faction + summoner.faction += "[REF(src)]" + remove_all_languages(LANGUAGE_MASTER) + copy_languages(to_who, LANGUAGE_MASTER) // make sure holoparasites speak same language as master + RegisterSignal(to_who, COMSIG_PARENT_QDELETING, PROC_REF(on_summoner_deletion)) + RegisterSignal(to_who, COMSIG_LIVING_ON_WABBAJACKED, PROC_REF(on_summoner_wabbajacked)) + RegisterSignal(to_who, COMSIG_LIVING_SHAPESHIFTED, PROC_REF(on_summoner_shapeshifted)) + RegisterSignal(to_who, COMSIG_LIVING_UNSHAPESHIFTED, PROC_REF(on_summoner_unshapeshifted)) + recall(forced = TRUE) + leash_to(src, summoner) + if (to_who.stat == DEAD) + on_summoner_death(src, to_who) + summoner.updatehealth() + +/// Remove all references to our summoner +/mob/living/basic/guardian/proc/cut_summoner(different_person = FALSE) + if (isnull(summoner)) + return + if (is_deployed()) + recall_effects() + var/summoner_turf = get_turf(summoner) + if (!isnull(summoner_turf)) + forceMove(summoner_turf) + unleash() + UnregisterSignal(summoner, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_SHAPESHIFTED, COMSIG_LIVING_UNSHAPESHIFTED)) + if (different_person) + summoner.faction -= "[REF(src)]" + faction -= summoner.faction + mind?.remove_all_antag_datums() + if (!length(summoner.get_all_linked_holoparasites() - src)) + for (var/action_type in control_actions) + var/datum/action/remove_action = locate(action_type) in summoner.actions + if (isnull(remove_action)) + continue + remove_action.Remove(summoner) + summoner = null + +/// Connects these two mobs by a leash +/mob/living/basic/guardian/proc/leash_to(atom/movable/leashed, atom/movable/leashed_to) + leashed.AddComponent(\ + /datum/component/leash,\ + owner = leashed_to,\ + distance = range,\ + force_teleport_out_effect = /obj/effect/temp_visual/guardian/phase/out,\ + force_teleport_in_effect = /obj/effect/temp_visual/guardian/phase,\ + ) + +/// Removes the leash from this guardian +/mob/living/basic/guardian/proc/unleash() + qdel(GetComponent(/datum/component/leash)) + +/// Called when our owner dies. We fucked up, so now neither of us get to exist. +/mob/living/basic/guardian/proc/on_summoner_death(mob/living/source, mob/living/former_owner) + cut_summoner() + if (!isnull(former_owner.loc)) + forceMove(former_owner.loc) + to_chat(src, span_danger("Your summoner has died!")) + visible_message(span_bolddanger("\The [src] dies along with its user!")) + former_owner.visible_message(span_bolddanger("[former_owner]'s body is completely consumed by the strain of sustaining [src]!")) + former_owner.dust(drop_items = TRUE) + +/// Called when our health changes, inform our owner of why they are getting hurt (if they are) +/mob/living/basic/guardian/proc/on_harm(mob/living/source, mob/living/summoner, amount) + if (QDELETED(src) || QDELETED(summoner) || amount <= 2) + return + to_chat(summoner, span_bolddanger("[name] is under attack! You take damage!")) + summoner.visible_message(span_bolddanger("Blood sprays from [summoner] as [src] takes damage!")) + if(summoner.stat == UNCONSCIOUS || summoner.stat == HARD_CRIT) + to_chat(summoner, span_bolddanger("Your head pounds, you can't take the strain of sustaining [src] in this condition!")) + summoner.adjustOrganLoss(ORGAN_SLOT_BRAIN, amount * 0.5) + +/// When our owner is deleted, we go too. +/mob/living/basic/guardian/proc/on_summoner_deletion(mob/living/source) + SIGNAL_HANDLER + cut_summoner() + to_chat(src, span_danger("Your summoner is gone, you feel yourself fading!")) + ghostize(FALSE) + qdel(src) + +/// Signal proc for [COMSIG_LIVING_ON_WABBAJACKED], when our summoner is wabbajacked we should be alerted. +/mob/living/basic/guardian/proc/on_summoner_wabbajacked(mob/living/source, mob/living/new_mob) + SIGNAL_HANDLER + set_summoner(new_mob) + to_chat(src, span_holoparasite("Your summoner has changed form!")) + +/// Signal proc for [COMSIG_LIVING_SHAPESHIFTED], when our summoner is shapeshifted we should change to the new mob +/mob/living/basic/guardian/proc/on_summoner_shapeshifted(mob/living/source, mob/living/new_shape) + SIGNAL_HANDLER + set_summoner(new_shape) + to_chat(src, span_holoparasite("Your summoner has shapeshifted into that of a [new_shape]!")) + +/// Signal proc for [COMSIG_LIVING_UNSHAPESHIFTED], when our summoner unshapeshifts go back to that mob +/mob/living/basic/guardian/proc/on_summoner_unshapeshifted(mob/living/source, mob/living/old_summoner) + SIGNAL_HANDLER + set_summoner(old_summoner) + to_chat(src, span_holoparasite("Your summoner has shapeshifted back into their normal form!")) + +/mob/living/basic/guardian/wabbajack(what_to_randomize, change_flags = WABBAJACK) + visible_message(span_warning("[src] resists the polymorph!")) // Ha, no + +/mob/living/basic/guardian/can_suicide() + return FALSE // You gotta persuade your boss to end it instead, sorry + +/// Returns true if you are out and about +/mob/living/basic/guardian/proc/is_deployed() + return isnull(summoner) || loc != summoner diff --git a/code/modules/mob/living/simple_animal/guardian/guardian_creator.dm b/code/modules/mob/living/basic/guardian/guardian_creator.dm similarity index 59% rename from code/modules/mob/living/simple_animal/guardian/guardian_creator.dm rename to code/modules/mob/living/basic/guardian/guardian_creator.dm index b06ff2447d2e..a3ba8d415329 100644 --- a/code/modules/mob/living/simple_animal/guardian/guardian_creator.dm +++ b/code/modules/mob/living/basic/guardian/guardian_creator.dm @@ -2,14 +2,15 @@ GLOBAL_LIST_INIT(guardian_radial_images, setup_guardian_radial()) /proc/setup_guardian_radial() . = list() - for(var/mob/living/simple_animal/hostile/guardian/guardian_path as anything in subtypesof(/mob/living/simple_animal/hostile/guardian)) + for(var/mob/living/basic/guardian/guardian_path as anything in subtypesof(/mob/living/basic/guardian)) var/datum/radial_menu_choice/option = new() option.name = initial(guardian_path.creator_name) option.image = image(icon = 'icons/hud/guardian.dmi', icon_state = initial(guardian_path.creator_icon)) option.info = span_boldnotice(initial(guardian_path.creator_desc)) .[guardian_path] = option -/obj/item/guardiancreator +/// An item which grants you your very own soul buddy +/obj/item/guardian_creator name = "enchanted deck of tarot cards" desc = "An enchanted deck of tarot cards, rumored to be a source of unimaginable power." icon = 'icons/obj/toys/playing_cards.dmi' @@ -31,36 +32,41 @@ GLOBAL_LIST_INIT(guardian_radial_images, setup_guardian_radial()) /// Message sent if we successfully get a guardian. var/success_message = span_holoparasite("%GUARDIAN has been summoned!") /// If true, you are given a random guardian rather than picking from a selection. - var/random = TRUE + var/random = FALSE /// If true, you can have multiple guardians at the same time. - var/allowmultiple = FALSE + var/allow_multiple = FALSE /// If true, lings can get guardians from this. - var/allowling = TRUE + var/allow_changeling = TRUE /// If true, a dextrous guardian can get their own guardian, infinite chain! - var/allowguardian = FALSE + var/allow_guardian = FALSE /// List of all the guardians this type can spawn. var/list/possible_guardians = list( //default, has everything but dextrous - /mob/living/simple_animal/hostile/guardian/assassin, - /mob/living/simple_animal/hostile/guardian/charger, - /mob/living/simple_animal/hostile/guardian/explosive, - /mob/living/simple_animal/hostile/guardian/gaseous, - /mob/living/simple_animal/hostile/guardian/gravitokinetic, - /mob/living/simple_animal/hostile/guardian/lightning, - /mob/living/simple_animal/hostile/guardian/protector, - /mob/living/simple_animal/hostile/guardian/ranged, - /mob/living/simple_animal/hostile/guardian/standard, - /mob/living/simple_animal/hostile/guardian/support, + /mob/living/basic/guardian/assassin, + /mob/living/basic/guardian/charger, + /mob/living/basic/guardian/explosive, + /mob/living/basic/guardian/gaseous, + /mob/living/basic/guardian/gravitokinetic, + /mob/living/basic/guardian/lightning, + /mob/living/basic/guardian/protector, + /mob/living/basic/guardian/ranged, + /mob/living/basic/guardian/standard, + /mob/living/basic/guardian/support, ) -/obj/item/guardiancreator/attack_self(mob/living/user) - if(isguardian(user) && !allowguardian) - to_chat(user, span_holoparasite("[mob_name] chains are not allowed.")) +/obj/item/guardian_creator/Initialize(mapload) + . = ..() + var/datum/guardian_fluff/using_theme = GLOB.guardian_themes[theme] + mob_name = using_theme.name + +/obj/item/guardian_creator/attack_self(mob/living/user) + if(isguardian(user) && !allow_guardian) + balloon_alert(user, "can't do that!") return var/list/guardians = user.get_all_linked_holoparasites() - if(length(guardians) && !allowmultiple) - to_chat(user, span_holoparasite("You already have a [mob_name]!")) + if(length(guardians) && !allow_multiple) + balloon_alert(user, "already have one!") return - if(user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling) && !allowling) + if(user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling) && !allow_changeling) to_chat(user, ling_failure) return if(used) @@ -71,19 +77,22 @@ GLOBAL_LIST_INIT(guardian_radial_images, setup_guardian_radial()) if(possible_guardian in possible_guardians) continue radial_options -= possible_guardian - var/mob/living/simple_animal/hostile/guardian/guardian_path + var/mob/living/basic/guardian/guardian_path if(random) guardian_path = pick(possible_guardians) else guardian_path = show_radial_menu(user, src, radial_options, custom_check = CALLBACK(src, PROC_REF(check_menu), user), radius = 42, require_near = TRUE) - if(!guardian_path) + if(isnull(guardian_path)) return used = TRUE to_chat(user, use_message) - var/guardian_type_name = "a random" - if(!random) - guardian_type_name = "the " + lowertext(initial(guardian_path.creator_name)) - var/list/mob/dead/observer/candidates = poll_ghost_candidates("Do you want to play as [guardian_type_name] [mob_name] of [user.real_name]?", ROLE_PAI, FALSE, 100, POLL_IGNORE_HOLOPARASITE) + var/guardian_type_name = random ? "Random" : capitalize(initial(guardian_path.creator_name)) + var/list/mob/dead/observer/candidates = poll_ghost_candidates( + "Do you want to play as [user.real_name]'s [guardian_type_name] [mob_name]?", + jobban_type = ROLE_PAI, + poll_time = 10 SECONDS, + ignore_category = POLL_IGNORE_HOLOPARASITE, + ) if(LAZYLEN(candidates)) var/mob/dead/observer/candidate = pick(candidates) spawn_guardian(user, candidate, guardian_path) @@ -91,123 +100,109 @@ GLOBAL_LIST_INIT(guardian_radial_images, setup_guardian_radial()) to_chat(user, failure_message) used = FALSE -/obj/item/guardiancreator/proc/spawn_guardian(mob/living/user, mob/dead/candidate, guardian_path) +/// Actually create our guy +/obj/item/guardian_creator/proc/spawn_guardian(mob/living/user, mob/dead/candidate, guardian_path) if(QDELETED(user) || user.stat == DEAD) return var/list/guardians = user.get_all_linked_holoparasites() - if(length(guardians) && !allowmultiple) - to_chat(user, span_holoparasite("You already have a [mob_name]!") ) + if(length(guardians) && !allow_multiple) + balloon_alert(user, "already got one!") used = FALSE return - var/mob/living/simple_animal/hostile/guardian/summoned_guardian = new guardian_path(user, theme) + var/datum/guardian_fluff/guardian_theme = GLOB.guardian_themes[theme] + var/mob/living/basic/guardian/summoned_guardian = new guardian_path(user, guardian_theme) summoned_guardian.set_summoner(user, different_person = TRUE) summoned_guardian.key = candidate.key user.log_message("has summoned [key_name(summoned_guardian)], a [summoned_guardian.creator_name] holoparasite.", LOG_GAME) summoned_guardian.log_message("was summoned as a [summoned_guardian.creator_name] holoparasite.", LOG_GAME) - to_chat(user, summoned_guardian.used_fluff_string) + to_chat(user, guardian_theme.get_fluff_string(summoned_guardian.guardian_type)) to_chat(user, replacetext(success_message, "%GUARDIAN", mob_name)) summoned_guardian.client?.init_verbs() return summoned_guardian -/obj/item/guardiancreator/proc/check_menu(mob/living/user) +/// Checks to ensure we're still capable of using the radial selector +/obj/item/guardian_creator/proc/check_menu(mob/living/user) if(!istype(user)) return FALSE if(user.incapacitated() || !user.is_holding(src) || used) return FALSE return TRUE -/obj/item/guardiancreator/choose - random = FALSE - -/obj/item/guardiancreator/choose/all/Initialize(mapload) - . = ..() - possible_guardians = subtypesof(/mob/living/simple_animal/hostile/guardian) - -/obj/item/guardiancreator/choose/wizard - allowmultiple = TRUE - possible_guardians = list( //no support, but dextrous - /mob/living/simple_animal/hostile/guardian/assassin, - /mob/living/simple_animal/hostile/guardian/charger, - /mob/living/simple_animal/hostile/guardian/dextrous, - /mob/living/simple_animal/hostile/guardian/explosive, - /mob/living/simple_animal/hostile/guardian/gaseous, - /mob/living/simple_animal/hostile/guardian/gravitokinetic, - /mob/living/simple_animal/hostile/guardian/lightning, - /mob/living/simple_animal/hostile/guardian/protector, - /mob/living/simple_animal/hostile/guardian/ranged, - /mob/living/simple_animal/hostile/guardian/standard, +/// Guardian creator available in the wizard spellbook. All but support are available. +/obj/item/guardian_creator/wizard + allow_multiple = TRUE + possible_guardians = list( + /mob/living/basic/guardian/assassin, + /mob/living/basic/guardian/charger, + /mob/living/basic/guardian/dextrous, + /mob/living/basic/guardian/explosive, + /mob/living/basic/guardian/gaseous, + /mob/living/basic/guardian/gravitokinetic, + /mob/living/basic/guardian/lightning, + /mob/living/basic/guardian/protector, + /mob/living/basic/guardian/ranged, + /mob/living/basic/guardian/standard, ) -/obj/item/guardiancreator/choose/wizard/spawn_guardian(mob/living/user, mob/dead/candidate) - . = ..() - var/mob/guardian = . - if(!guardian) - return +/obj/item/guardian_creator/wizard/spawn_guardian(mob/living/user, mob/dead/candidate) + var/mob/guardian = ..() + if(isnull(guardian)) + return null + // Add the wizard team datum var/datum/antagonist/wizard/antag_datum = user.mind.has_antag_datum(/datum/antagonist/wizard) - if(antag_datum) - if(!antag_datum.wiz_team) - antag_datum.create_wiz_team() - guardian.mind.add_antag_datum(/datum/antagonist/wizard_minion, antag_datum.wiz_team) - -/obj/item/guardiancreator/tech + if(isnull(antag_datum)) + return guardian + if(!antag_datum.wiz_team) + antag_datum.create_wiz_team() + guardian.mind.add_antag_datum(/datum/antagonist/wizard_minion, antag_datum.wiz_team) + return guardian + +/// Guardian creator available in the traitor uplink. All but dextrous are available, you can pick which you want, and changelings cannot use it. +/obj/item/guardian_creator/tech name = "holoparasite injector" desc = "It contains an alien nanoswarm of unknown origin. Though capable of near sorcerous feats via use of hardlight holograms and nanomachines, it requires an organic host as a home base and source of fuel." icon = 'icons/obj/medical/syringe.dmi' icon_state = "combat_hypo" theme = GUARDIAN_THEME_TECH - mob_name = "Holoparasite" + allow_changeling = FALSE use_message = span_holoparasite("You start to power on the injector...") used_message = span_holoparasite("The injector has already been used.") failure_message = span_boldholoparasite("...ERROR. BOOT SEQUENCE ABORTED. AI FAILED TO INTIALIZE. PLEASE CONTACT SUPPORT OR TRY AGAIN LATER.") ling_failure = span_boldholoparasite("The holoparasites recoil in horror. They want nothing to do with a creature like you.") success_message = span_holoparasite("%GUARDIAN is now online!") -/obj/item/guardiancreator/tech/choose - random = FALSE - -/obj/item/guardiancreator/tech/choose/all/Initialize(mapload) - . = ..() - possible_guardians = subtypesof(/mob/living/simple_animal/hostile/guardian) - -/obj/item/guardiancreator/tech/choose/traitor - allowling = FALSE - -/obj/item/guardiancreator/carp +/// Guardian creator only spawned by admins, which creates a holographic fish. You can have several of them. +/obj/item/guardian_creator/carp name = "holocarp fishsticks" desc = "Using the power of Carp'sie, you can catch a carp from byond the veil of Carpthulu, and bind it to your fleshy flesh form." icon = 'icons/obj/food/meat.dmi' icon_state = "fishfingers" theme = GUARDIAN_THEME_CARP - mob_name = "Holocarp" use_message = span_holoparasite("You put the fishsticks in your mouth...") used_message = span_holoparasite("Someone's already taken a bite out of these fishsticks! Ew.") failure_message = span_boldholoparasite("You couldn't catch any carp spirits from the seas of Lake Carp. Maybe there are none, maybe you fucked up.") ling_failure = span_boldholoparasite("Carp'sie seems to not have taken you as the chosen one. Maybe it's because of your horrifying origin.") success_message = span_holoparasite("%GUARDIAN has been caught!") - allowmultiple = TRUE - -/obj/item/guardiancreator/carp/choose - random = FALSE + allow_multiple = TRUE -/obj/item/guardiancreator/miner +/// Guardian creator available to miners from chests, very limited selection and randomly assigned. +/obj/item/guardian_creator/miner name = "dusty shard" desc = "Seems to be a very old rock, may have originated from a strange meteor." icon = 'icons/obj/lavaland/artefacts.dmi' icon_state = "dustyshard" theme = GUARDIAN_THEME_MINER - mob_name = "Power Miner" use_message = span_holoparasite("You pierce your skin with the shard...") used_message = span_holoparasite("This shard seems to have lost all its power...") failure_message = span_boldholoparasite("The shard hasn't reacted at all. Maybe try again later...") ling_failure = span_boldholoparasite("The power of the shard seems to not react with your horrifying, mutated body.") success_message = span_holoparasite("%GUARDIAN has appeared!") - possible_guardians = list( //limited to ones useful on lavaland - /mob/living/simple_animal/hostile/guardian/charger, - /mob/living/simple_animal/hostile/guardian/protector, - /mob/living/simple_animal/hostile/guardian/ranged, - /mob/living/simple_animal/hostile/guardian/standard, - /mob/living/simple_animal/hostile/guardian/support, + random = TRUE + //limited to ones which are plausibly useful on lavaland + possible_guardians = list( + /mob/living/basic/guardian/charger, // A flying mount which can cross chasms + /mob/living/basic/guardian/protector, // Bodyblocks projectiles for you + /mob/living/basic/guardian/ranged, // Shoots the bad guys + /mob/living/basic/guardian/standard, // Can mine walls + /mob/living/basic/guardian/support, // Heals and teleports you ) - -/obj/item/guardiancreator/miner/choose - random = FALSE diff --git a/code/modules/mob/living/basic/guardian/guardian_fluff.dm b/code/modules/mob/living/basic/guardian/guardian_fluff.dm new file mode 100644 index 000000000000..4ede238921ed --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_fluff.dm @@ -0,0 +1,124 @@ +/** + * Defines a theme used by guardian mobs for visuals and some text output + * The default is used for ones created by wizards + */ +/datum/guardian_fluff + /// What name do we apply before one has been selected? + var/name = "Guardian Spirit" + /// Mob description to apply + var/desc = "A mysterious being that stands by its charge, ever vigilant." + /// Are we magical or technological? Mostly just used to pick a surname + var/fluff_type = GUARDIAN_MAGIC + /// What speech bubble do we use? + var/bubble_icon = "guardian" + /// What is our base icon state? + var/icon_state = "magicbase" + /// What is the icon state for our coloured overlay? + var/overlay_state = "magic" + /// Emote used for speaking + var/list/speak_emote = list("hisses") + /// Verb shown to viewers when attacking + var/attack_verb_continuous = "punches" + /// Verb shown to attacker when attacking + var/attack_verb_simple = "punch" + /// Sound played when we attack + var/attack_sound = 'sound/weapons/punch1.ogg' + /// Visible effect when we attack + var/attack_vis_effect = ATTACK_EFFECT_PUNCH + /// An associative list of type of guardian to some kind of descriptive text to show on appearance. + var/guardian_fluff = list( + GUARDIAN_ASSASSIN = "...And draw the Space Ninja, a lethal and invisible assassin.", + GUARDIAN_CHARGER = "...And draw the Hunter, alien master of rapid assault.", + GUARDIAN_DEXTROUS = "...And draw the Monkey, ascendant beast who has learned to use tools.", + GUARDIAN_EXPLOSIVE = "...And draw the Scientist, herald of explosive death.", + GUARDIAN_GASEOUS = "...And draw the Atmospheric Technician, veiled in a purple haze.", + GUARDIAN_GRAVITOKINETIC = "...And draw the Singularity, a terrible, irresistible force..", + GUARDIAN_LIGHTNING = "...And draw the Supermatter, a shockingly lethal font of power.", + GUARDIAN_PROTECTOR = "...And draw the Corgi, a stalwart protector that never leaves the side of its charge.", + GUARDIAN_RANGED = "...And draw the Watcher, impaling its prey from afar.", + GUARDIAN_STANDARD = "...And draw the Assistant, faceless but never to be underestimated.", + GUARDIAN_SUPPORT = "...And draw the Paramedic, arbiter of life and death.", + ) + +/// Applies relevant visual properties to our guardian +/datum/guardian_fluff/proc/apply(mob/living/basic/guardian/guardian) + guardian.name = name + guardian.real_name = name + guardian.bubble_icon = bubble_icon + guardian.icon_living = icon_state + guardian.icon_state = icon_state + + guardian.speak_emote = speak_emote + guardian.attack_verb_continuous = attack_verb_continuous + guardian.attack_verb_simple = attack_verb_simple + guardian.attack_sound = attack_sound + guardian.attack_vis_effect = attack_vis_effect + + guardian.overlay = mutable_appearance(guardian.icon, overlay_state) + +/// Output an appropriate fluff string for our guardian when it is created +/datum/guardian_fluff/proc/get_fluff_string(guardian_type) + return span_holoparasite(guardian_fluff[guardian_type] || "You bring forth a glitching abomination, something which should not be! Please contact a coder about it.") + +/// Used by holoparasites in the Traitor uplink +/datum/guardian_fluff/tech + name = "Holoparasite" + fluff_type = GUARDIAN_TECH + bubble_icon = "holo" + icon_state = "techbase" + overlay_state = "tech" + guardian_fluff = list( + GUARDIAN_ASSASSIN = "Boot sequence complete. Stealth modules loaded. Holoparasite swarm online.", + GUARDIAN_CHARGER = "Boot sequence complete. Overclocking motive engines. Holoparasite swarm online.", + GUARDIAN_DEXTROUS = "Boot sequence complete. Armed combat routines loaded. Holoparasite swarm online.", + GUARDIAN_EXPLOSIVE = "Boot sequence complete. Payload generator online. Holoparasite swarm online.", + GUARDIAN_GASEOUS = "Boot sequence complete. Atmospheric projectors operational. Holoparasite swarm online.", + GUARDIAN_GRAVITOKINETIC = "Boot sequence complete. Gravitic engine spinning up. Holoparasite swarm online.", + GUARDIAN_LIGHTNING = "Boot sequence complete. Tesla projectors charged. Holoparasite swarm online.", + GUARDIAN_PROTECTOR = "Boot sequence complete. Bodyguard routines loaded. Holoparasite swarm online.", + GUARDIAN_RANGED = "Boot sequence complete. Flechette launchers operational. Holoparasite swarm online.", + GUARDIAN_STANDARD = "Boot sequence complete. CQC suite engaged. Holoparasite swarm online.", + GUARDIAN_SUPPORT = "Boot sequence complete. Medical suite active. Holoparasite swarm online.", + ) + +/// Used by powerminers found in necropolis chests +/datum/guardian_fluff/miner + name = "Power Miner" + icon_state = "minerbase" + overlay_state = "miner" + guardian_fluff = list( + GUARDIAN_ASSASSIN = "The shard reveals... Glass, a sharp but fragile ambusher.", + GUARDIAN_CHARGER = "The shard reveals... Titanium, a lightweight, agile fighter.", + GUARDIAN_DEXTROUS = "The shard reveals... Gold, a malleable hoarder of treasure.", + GUARDIAN_EXPLOSIVE = "The shard reveals... Gibtonite, volatile and surprising.", + GUARDIAN_GASEOUS = "The shard reveals... Plasma, the bringer of flame.", + GUARDIAN_GRAVITOKINETIC = "The shard reveals... Bananium, a manipulator of motive forces.", + GUARDIAN_LIGHTNING = "The shard reveals... Iron, a conductive font of lightning.", + GUARDIAN_PROTECTOR = "The shard reveals... Uranium, dense and resistant.", + GUARDIAN_RANGED = "The shard reveals... Diamond, projecting a million sharp edges.", + GUARDIAN_STANDARD = "The shard reveals... Plastitanium, a powerful fighter.", + GUARDIAN_SUPPORT = "The shard reveals... Bluespace, master of relocation.", + ) + +/// Used by holocarp spawned by admins +/datum/guardian_fluff/carp + name = "Holocarp" + fluff_type = GUARDIAN_TECH + desc = "A mysterious fish that swims by its charge, ever fingilant." + icon_state = null // Handled entirely by the overlay + bubble_icon = "holo" + overlay_state = "carp" + speak_emote = list("gnashes") + guardian_fluff = list( + GUARDIAN_ASSASSIN = "CARP CARP CARP! Caught one! It's an assassin carp! Just when you thought it was safe to go back to the water... which is unhelpful, because we're in space.", + GUARDIAN_CHARGER = "CARP CARP CARP! Caught one! It's a charger carp which likes running at people. But it doesn't have any legs...", + GUARDIAN_DEXTROUS = "CARP CARP CARP! You caught one! It's a dextrous carp ready to slap people with a fish, once it picks one up.", + GUARDIAN_EXPLOSIVE = "CARP CARP CARP! Caught one! It's an explosive carp! You two are going to have a blast.", + GUARDIAN_GASEOUS = "CARP CARP CARP! You caught one! It's a gaseous carp, but don't worry it actually smells pretty good!", + GUARDIAN_GRAVITOKINETIC = "CARP CARP CARP! Caught one! It's a gravitokinetic carp! Now do you understand the gravity of the situation?", + GUARDIAN_LIGHTNING = "CARP CARP CARP! Caught one! It's a lightning carp! What a shocking result!", + GUARDIAN_PROTECTOR = "CARP CARP CARP! You caught one! Wait, no... it caught you! The fisher has become the fishy...", + GUARDIAN_RANGED = "CARP CARP CARP! You caught one! It's a ranged carp! It has been collecting glass shards in preparation for this moment.", + GUARDIAN_STANDARD = "CARP CARP CARP! You caught one! This one is a little generic and disappointing... Better punch through some walls to ease the tension.", + GUARDIAN_SUPPORT = "CARP CARP CARP! You caught a support carp! Now it's here, now you're over there!", + ) diff --git a/code/modules/mob/living/basic/guardian/guardian_helpers.dm b/code/modules/mob/living/basic/guardian/guardian_helpers.dm new file mode 100644 index 000000000000..df50a43e6a09 --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_helpers.dm @@ -0,0 +1,13 @@ +/// Returns a list of all holoparasites that has this mob as a summoner. +/mob/living/proc/get_all_linked_holoparasites() + RETURN_TYPE(/list) + var/list/all_parasites = list() + for(var/mob/living/basic/guardian/stand as anything in GLOB.parasites) + if (stand.summoner != src) + continue + all_parasites += stand + return all_parasites + +/// Returns true if this holoparasite has the same summoner as the passed holoparasite. +/mob/living/basic/guardian/proc/shares_summoner(mob/living/basic/guardian/other_guardian) + return istype(other_guardian) && other_guardian.summoner == summoner diff --git a/code/modules/mob/living/basic/guardian/guardian_types/assassin.dm b/code/modules/mob/living/basic/guardian/guardian_types/assassin.dm new file mode 100644 index 000000000000..d62b9bcedea7 --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_types/assassin.dm @@ -0,0 +1,116 @@ +#define CAN_STEALTH_ALERT "can_stealth" + +/** + * Can enter stealth mode to become invisible and deal bonus damage on their next attack, an ambush predator. + */ +/mob/living/basic/guardian/assassin + guardian_type = GUARDIAN_ASSASSIN + melee_damage_lower = 15 + melee_damage_upper = 15 + attack_verb_continuous = "slashes" + attack_verb_simple = "slash" + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH + sharpness = SHARP_POINTY + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + playstyle_string = span_holoparasite("As an assassin type you do medium damage and have no damage resistance, but can enter stealth, massively increasing the damage of your next attack and causing it to ignore armor. Stealth is broken when you attack or take damage.") + creator_name = "Assassin" + creator_desc = "Does medium damage and takes full damage, but can enter stealth, causing its next attack to do massive damage and ignore armor. However, it becomes briefly unable to recall after attacking from stealth." + creator_icon = "assassin" + toggle_button_type = /atom/movable/screen/guardian/toggle_mode/assassin + /// How long to put stealth on cooldown if we are forced out? + var/stealth_cooldown_time = 16 SECONDS + /// Cooldown for the stealth toggle. + COOLDOWN_DECLARE(stealth_cooldown) + +/mob/living/basic/guardian/assassin/Initialize(mapload, datum/guardian_fluff/theme) + . = ..() + show_can_stealth() + RegisterSignal(src, COMSIG_GUARDIAN_ASSASSIN_REVEALED, PROC_REF(on_forced_unstealth)) + +// Toggle stealth +/mob/living/basic/guardian/assassin/toggle_modes() + var/stealthed = has_status_effect(/datum/status_effect/guardian_stealth) + if (stealthed) + to_chat(src, span_bolddanger("You exit stealth.")) + remove_status_effect(/datum/status_effect/guardian_stealth) + show_can_stealth() + return + if (COOLDOWN_FINISHED(src, stealth_cooldown)) + if (!is_deployed()) + to_chat(src, span_bolddanger("You have to be manifested to enter stealth!")) + return + apply_status_effect(/datum/status_effect/guardian_stealth) + clear_alert(CAN_STEALTH_ALERT) + return + to_chat(src, span_bolddanger("You cannot yet enter stealth, wait another [DisplayTimeText(COOLDOWN_TIMELEFT(src, stealth_cooldown))]!")) + +/mob/living/basic/guardian/assassin/get_status_tab_items() + . = ..() + if(!COOLDOWN_FINISHED(src, stealth_cooldown)) + . += "Stealth Cooldown Remaining: [DisplayTimeText(COOLDOWN_TIMELEFT(src, stealth_cooldown))]" + +/// Called when we are removed from stealth involuntarily +/mob/living/basic/guardian/assassin/proc/on_forced_unstealth(mob/living/source) + SIGNAL_HANDLER + visible_message(span_danger("\The [src] suddenly appears!")) + COOLDOWN_START(src, manifest_cooldown, 4 SECONDS) + COOLDOWN_START(src, stealth_cooldown, stealth_cooldown_time) + addtimer(CALLBACK(src, PROC_REF(show_can_stealth)), stealth_cooldown_time) + +/// Displays an alert letting us know that we can enter stealth +/mob/living/basic/guardian/assassin/proc/show_can_stealth() + if(!COOLDOWN_FINISHED(src, stealth_cooldown)) + return + throw_alert(CAN_STEALTH_ALERT, /atom/movable/screen/alert/canstealth) + +/// Status effect which makes us sneakier and do bonus damage +/datum/status_effect/guardian_stealth + id = "guardian_stealth" + alert_type = /atom/movable/screen/alert/status_effect/instealth + /// Damage added in stealth mode. + var/damage_bonus = 35 + /// Our wound bonus when in stealth mode. Allows you to actually cause wounds, unlike normal. + var/stealth_wound_bonus = -20 + +/datum/status_effect/guardian_stealth/on_apply() + new /obj/effect/temp_visual/guardian/phase/out(get_turf(owner)) + owner.melee_damage_lower += damage_bonus + owner.melee_damage_upper += damage_bonus + if (isbasicmob(owner)) + var/mob/living/basic/basic_owner = owner + basic_owner.armour_penetration = 100 + basic_owner.wound_bonus = stealth_wound_bonus + basic_owner.obj_damage = 0 + to_chat(owner, span_bolddanger("You enter stealth, empowering your next attack.")) + animate(owner, alpha = 15, time = 0.5 SECONDS) + + RegisterSignals(owner, list(COMSIG_GUARDIAN_RECALLED, COMSIG_HOSTILE_POST_ATTACKINGTARGET), PROC_REF(forced_exit)) + RegisterSignals(owner, COMSIG_LIVING_ADJUST_STANDARD_DAMAGE_TYPES, PROC_REF(on_health_changed)) + return TRUE + +/datum/status_effect/guardian_stealth/on_remove() + owner.melee_damage_lower -= damage_bonus + owner.melee_damage_upper -= damage_bonus + if (isbasicmob(owner)) + var/mob/living/basic/basic_owner = owner + basic_owner.armour_penetration = initial(basic_owner.armour_penetration) + basic_owner.wound_bonus = initial(basic_owner.wound_bonus) + basic_owner.obj_damage = initial(basic_owner.obj_damage) + animate(owner, alpha = initial(owner.alpha), time = 0.5 SECONDS) + UnregisterSignal(owner, list(COMSIG_GUARDIAN_RECALLED, COMSIG_HOSTILE_POST_ATTACKINGTARGET) + COMSIG_LIVING_ADJUST_STANDARD_DAMAGE_TYPES) + +/// If we take damage, exit the status effect +/datum/status_effect/guardian_stealth/proc/on_health_changed(mob/living/our_mob, type, amount, forced) + SIGNAL_HANDLER + if (amount <= 0) + return + forced_exit() + +/// Forcibly exit the status effect +/datum/status_effect/guardian_stealth/proc/forced_exit() + SIGNAL_HANDLER + SEND_SIGNAL(owner, COMSIG_GUARDIAN_ASSASSIN_REVEALED) + qdel(src) + +#undef CAN_STEALTH_ALERT diff --git a/code/modules/mob/living/basic/guardian/guardian_types/charger.dm b/code/modules/mob/living/basic/guardian/guardian_types/charger.dm new file mode 100644 index 000000000000..02f839c6a415 --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_types/charger.dm @@ -0,0 +1,69 @@ +/** + * Very fast, has a charging attack, and most importantly can be ridden like a horse. + */ +/mob/living/basic/guardian/charger + guardian_type = GUARDIAN_CHARGER + melee_damage_lower = 15 + melee_damage_upper = 15 + speed = -0.5 + damage_coeff = list(BRUTE = 0.75, BURN = 0.75, TOX = 0.75, CLONE = 0.75, STAMINA = 0, OXY = 0.75) + playstyle_string = span_holoparasite("As a charger type you do medium damage, have light damage resistance, move very fast, can be ridden, and can charge at a location, damaging any target hit and forcing them to drop any items they are holding.") + creator_name = "Charger" + creator_desc = "Moves very fast, does medium damage on attack, can be ridden and can charge at targets, damaging the first target hit and forcing them to drop any items they are holding." + creator_icon = "charger" + +/mob/living/basic/guardian/charger/Initialize(mapload, datum/guardian_fluff/theme) + . = ..() + AddElement(/datum/element/ridable, /datum/component/riding/creature/guardian) + var/datum/action/cooldown/mob_cooldown/charge/basic_charge/guardian/charge = new(src) + charge.Grant(src) + charge.set_click_ability(src) + +/// Guardian charger's charging attack, it knocks items out of people's hands +/datum/action/cooldown/mob_cooldown/charge/basic_charge/guardian + name = "Charge!" + cooldown_time = 4 SECONDS + melee_cooldown_time = 0 SECONDS + button_icon = 'icons/effects/effects.dmi' + button_icon_state = "speed" + background_icon = 'icons/hud/guardian.dmi' + background_icon_state = "base" + charge_delay = 0 + recoil_duration = 0 + charge_damage = 20 + charge_distance = 10 + unset_after_click = FALSE + destroy_objects = FALSE + +/datum/action/cooldown/mob_cooldown/charge/basic_charge/guardian/PreActivate(atom/target) + if (!isguardian(owner)) + return ..() + var/mob/living/basic/guardian/guardian_owner = owner + if (guardian_owner.is_deployed()) + return ..() + return FALSE + +/datum/action/cooldown/mob_cooldown/charge/basic_charge/guardian/do_charge_indicator(atom/charger, atom/charge_target) + playsound(charger, 'sound/items/modsuit/loader_launch.ogg', 75, TRUE) + var/obj/effect/temp_visual/decoy/decoy_flash = new /obj/effect/temp_visual/decoy(charger.loc, charger) + animate(decoy_flash, alpha = 0, color = "#FF0000", transform = matrix() * 2, time = 3) + +/datum/action/cooldown/mob_cooldown/charge/basic_charge/guardian/can_hit_target(atom/movable/source, atom/target) + var/mob/living/living_target = target + if (!istype(living_target)) + return FALSE + var/mob/living/basic/guardian/guardian_owner = owner + if (!istype(guardian_owner)) + return TRUE + if (living_target == guardian_owner.summoner || guardian_owner.shares_summoner(target)) + return FALSE + return TRUE + +/datum/action/cooldown/mob_cooldown/charge/basic_charge/guardian/hit_target(atom/movable/source, mob/living/target, damage_dealt) + if(ishuman(target)) + var/mob/living/carbon/human/hit_human = target + if(hit_human.check_shields(src, charge_damage, name, attack_type = LEAP_ATTACK)) + return + . = ..() + var/mob/living/hit_mob = target + hit_mob.drop_all_held_items() diff --git a/code/modules/mob/living/basic/guardian/guardian_types/dextrous.dm b/code/modules/mob/living/basic/guardian/guardian_types/dextrous.dm new file mode 100644 index 000000000000..ed54a23771d2 --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_types/dextrous.dm @@ -0,0 +1,100 @@ +/// Dextrous guardians have some of the most powerful abilities of all: hands and pockets +/mob/living/basic/guardian/dextrous + guardian_type = GUARDIAN_DEXTROUS + melee_damage_lower = 10 + melee_damage_upper = 10 + damage_coeff = list(BRUTE = 0.75, BURN = 0.75, TOX = 0.75, CLONE = 0.75, STAMINA = 0, OXY = 0.75) + playstyle_string = span_holoparasite("As a dextrous type you can hold items, store an item within yourself, and have medium damage resistance, but do low damage on attacks. Recalling and leashing will force you to drop unstored items!") + creator_name = "Dextrous" + creator_desc = "Does low damage on attack, but is capable of holding items and storing a single item within it. It will drop items held in its hands when it recalls, but it will retain the stored item." + creator_icon = "dextrous" + hud_type = /datum/hud/dextrous/guardian + held_items = list(null, null) + /// An internal pocket we can put stuff in + var/obj/item/internal_storage + +/mob/living/basic/guardian/dextrous/Initialize(mapload, datum/guardian_fluff/theme) + . = ..() + add_traits(list(TRAIT_ADVANCEDTOOLUSER, TRAIT_CAN_STRIP), ROUNDSTART_TRAIT) + AddElement(/datum/element/dextrous, hud_type = hud_type) + AddComponent(/datum/component/personal_crafting) + AddComponent(/datum/component/basic_inhands) + +/mob/living/basic/guardian/dextrous/death(gibbed) + dropItemToGround(internal_storage) + return ..() + +/mob/living/basic/guardian/dextrous/examine(mob/user) + . = ..() + if(isnull(internal_storage) || (internal_storage.item_flags & ABSTRACT)) + return + . += span_info("It is holding [internal_storage.get_examine_string(user)] in its internal storage.") + +/mob/living/basic/guardian/dextrous/recall_effects() + . = ..() + drop_all_held_items() + +// Bullshit related to having a fake pocket begins here + +/mob/living/basic/guardian/dextrous/doUnEquip(obj/item/equipped_item, force, newloc, no_move, invdrop = TRUE, silent = FALSE) + . = ..() + if (!.) + return FALSE + update_held_items() + if(equipped_item == internal_storage) + internal_storage = null + update_inv_internal_storage() + return TRUE + +/mob/living/basic/guardian/dextrous/can_equip(mob/living/M, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE, ignore_equipped = FALSE, indirect_action = FALSE) + if(slot != ITEM_SLOT_DEX_STORAGE) + return FALSE + return isnull(internal_storage) + +/mob/living/basic/guardian/dextrous/get_item_by_slot(slot_id) + if(slot_id == ITEM_SLOT_DEX_STORAGE) + return internal_storage + return ..() + +/mob/living/basic/guardian/dextrous/get_slot_by_item(obj/item/looking_for) + if(internal_storage == looking_for) + return ITEM_SLOT_DEX_STORAGE + return ..() + +/mob/living/basic/guardian/dextrous/equip_to_slot(obj/item/equipping, slot, initial = FALSE, redraw_mob = FALSE, indirect_action = FALSE) + if (slot != ITEM_SLOT_DEX_STORAGE) + to_chat(src, span_danger("You are trying to equip this item to an unsupported inventory slot. Report this to a coder!")) + return FALSE + + var/index = get_held_index_of_item(equipping) + if(index) + held_items[index] = null + update_held_items() + + if(equipping.pulledby) + equipping.pulledby.stop_pulling() + + equipping.screen_loc = null // will get moved if inventory is visible + equipping.forceMove(src) + SET_PLANE_EXPLICIT(equipping, ABOVE_HUD_PLANE, src) + + internal_storage = equipping + update_inv_internal_storage() + + equipping.on_equipped(src, slot) + return TRUE + +/mob/living/basic/guardian/dextrous/getBackSlot() + return ITEM_SLOT_DEX_STORAGE + +/mob/living/basic/guardian/dextrous/getBeltSlot() + return ITEM_SLOT_DEX_STORAGE + +/mob/living/basic/guardian/dextrous/proc/update_inv_internal_storage() + if(isnull(internal_storage) || isnull(client) || !hud_used?.hud_shown) + return + internal_storage.screen_loc = ui_id + client.screen += internal_storage + +/mob/living/basic/guardian/dextrous/regenerate_icons() + update_inv_internal_storage() diff --git a/code/modules/mob/living/basic/guardian/guardian_types/explosive.dm b/code/modules/mob/living/basic/guardian/guardian_types/explosive.dm new file mode 100644 index 000000000000..1e15aac247d6 --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_types/explosive.dm @@ -0,0 +1,77 @@ +/// A durable guardian which can convert objects into hidden explosives. +/mob/living/basic/guardian/explosive + guardian_type = GUARDIAN_EXPLOSIVE + melee_damage_lower = 15 + melee_damage_upper = 15 + damage_coeff = list(BRUTE = 0.6, BURN = 0.6, TOX = 0.6, CLONE = 0.6, STAMINA = 0, OXY = 0.6) + range = 13 + playstyle_string = span_holoparasite("As an explosive type, you have moderate close combat abilities and are capable of converting nearby items and objects into disguised bombs via right-click.") + creator_name = "Explosive" + creator_desc = "High damage resist and medium power attack. Can turn any object, including objects too large to pick up, into a bomb, dealing explosive damage to the next person to touch it. The object will return to normal after the trap is triggered or after a delay." + creator_icon = "explosive" + /// Ability which plants bombs + var/datum/action/cooldown/mob_cooldown/explosive_booby_trap/bomb + +/mob/living/basic/guardian/explosive/Initialize(mapload, datum/guardian_fluff/theme) + . = ..() + bomb = new(src) + bomb.Grant(src) + +/mob/living/basic/guardian/explosive/Destroy() + QDEL_NULL(bomb) + return ..() + +/mob/living/basic/guardian/explosive/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) + if(LAZYACCESS(modifiers, RIGHT_CLICK) && proximity_flag && isobj(attack_target)) + bomb.Trigger(target = attack_target) + return + return ..() + + +/// An ability which can turn an object into a bomb +/datum/action/cooldown/mob_cooldown/explosive_booby_trap + name = "Explosive Trap" + desc = "Convert an inanimate object into a deadly and mostly undetectable explosive, triggered on touch." + button_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "smoke" + cooldown_time = 20 SECONDS + melee_cooldown_time = 0 SECONDS + background_icon = 'icons/hud/guardian.dmi' + background_icon_state = "base" + /// After this amount of time passses, bomb deactivates. + var/decay_time = 1 MINUTES + /// Static list of signals that activate the bomb. + var/static/list/boom_signals = list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_BUMPED, COMSIG_ATOM_ATTACK_HAND) + +/datum/action/cooldown/mob_cooldown/explosive_booby_trap/PreActivate(atom/target) + if (!isobj(target)) + return FALSE + if (!owner.Adjacent(target)) + return FALSE + return ..() + +/datum/action/cooldown/mob_cooldown/explosive_booby_trap/Activate(atom/target) + var/glow_colour = COLOR_RED + var/mob/living/basic/guardian/guardian_owner = owner + if (istype(guardian_owner)) + glow_colour = guardian_owner.guardian_colour + target.AddComponent(\ + /datum/component/direct_explosive_trap, \ + saboteur = owner, \ + expire_time = decay_time, \ + glow_colour = glow_colour,\ + explosive_checks = CALLBACK(src, PROC_REF(validate_target)), \ + triggering_signals = boom_signals, \ + ) + target.balloon_alert(owner, "bomb planted") + StartCooldown() + return TRUE + +/// Validate that we should blow up on this thing, preferably not on one of our allies +/datum/action/cooldown/mob_cooldown/explosive_booby_trap/proc/validate_target(mob/living/target) + if (target == owner) + return FALSE + var/mob/living/basic/guardian/guardian_owner = owner + if (!istype(guardian_owner)) + return TRUE + return target != guardian_owner.summoner && !guardian_owner.shares_summoner(target) diff --git a/code/modules/mob/living/basic/guardian/guardian_types/gaseous.dm b/code/modules/mob/living/basic/guardian/guardian_types/gaseous.dm new file mode 100644 index 000000000000..1471a0976396 --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_types/gaseous.dm @@ -0,0 +1,159 @@ +/// Not particularly resistant, but versatile due to the selection of gases it can generate. +/mob/living/basic/guardian/gaseous + guardian_type = GUARDIAN_GASEOUS + melee_damage_lower = 10 + melee_damage_upper = 10 + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 0) + range = 7 + playstyle_string = span_holoparasite("As a gaseous type, you have only light damage resistance, but you can expel gas in an area. In addition, your punches cause sparks, and you make your summoner inflammable.") + creator_name = "Gaseous" + creator_desc = "Creates sparks on touch and continuously expels a gas of its choice. Automatically extinguishes the user if they catch on fire." + creator_icon = "gaseous" + toggle_button_type = /atom/movable/screen/guardian/toggle_mode/gases + /// Ability we use to select gases + var/datum/action/cooldown/mob_cooldown/expel_gas/gas + /// Rate of temperature stabilization per second. + var/temp_stabilization_rate = 0.1 + +/mob/living/basic/guardian/gaseous/Initialize(mapload, theme) + . = ..() + RegisterSignal(src, COMSIG_ATOM_PRE_PRESSURE_PUSH, PROC_REF(pre_pressure_moved)) + gas = new(src) + gas.owner_has_control = FALSE // It's nicely integrated with the Guardian UI, no need to have two buttons + gas.Grant(src) + +/mob/living/basic/guardian/gaseous/Destroy() + QDEL_NULL(gas) + return ..() + +/mob/living/basic/guardian/gaseous/toggle_modes() + gas.Trigger() + +/mob/living/basic/guardian/gaseous/set_summoner(mob/living/to_who, different_person) + . = ..() + if (QDELETED(src)) + return + RegisterSignal(summoner, COMSIG_LIVING_IGNITED, PROC_REF(on_summoner_ignited)) + RegisterSignal(summoner, COMSIG_LIVING_LIFE, PROC_REF(on_summoner_life)) + +/mob/living/basic/guardian/gaseous/cut_summoner(different_person) + if (!isnull(summoner)) + UnregisterSignal(summoner, list(COMSIG_LIVING_IGNITED, COMSIG_LIVING_LIFE)) + return ..() + +/// Prevent our summoner from being on fire +/mob/living/basic/guardian/gaseous/proc/on_summoner_ignited(mob/living/source) + SIGNAL_HANDLER + source.extinguish_mob() + source.set_fire_stacks(0, remove_wet_stacks = FALSE) + +/// Maintain our summoner at a stable body temperature +/mob/living/basic/guardian/gaseous/proc/on_summoner_life(mob/living/source, seconds_per_tick, times_fired) + SIGNAL_HANDLER + source.adjust_bodytemperature(get_temp_change_amount((summoner.get_body_temp_normal() - summoner.bodytemperature), temp_stabilization_rate * seconds_per_tick)) + +/mob/living/basic/guardian/gaseous/melee_attack(atom/target, list/modifiers, ignore_cooldown) + . = ..() + if(!. || !isliving(target)) + return + do_sparks(1, TRUE, target) + +/mob/living/basic/guardian/gaseous/recall_effects() + . = ..() + if(!isnull(summoner)) + UnregisterSignal(summoner, COMSIG_ATOM_PRE_PRESSURE_PUSH) + +/mob/living/basic/guardian/gaseous/manifest_effects() + . = ..() + if (!isnull(summoner)) + RegisterSignal(summoner, COMSIG_ATOM_PRE_PRESSURE_PUSH, PROC_REF(pre_pressure_moved)) + +/// We stand firm in the face of gas +/mob/living/basic/guardian/gaseous/proc/pre_pressure_moved(datum/source) + SIGNAL_HANDLER + return COMSIG_ATOM_BLOCKS_PRESSURE + + +/// Expel a range of gases +/datum/action/cooldown/mob_cooldown/expel_gas + name = "Release Gas" + desc = "Start or stop expelling a selected gas into the environment." + button_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "smoke" + cooldown_time = 0 SECONDS // We're here for the interface not the cooldown + melee_cooldown_time = 0 SECONDS + click_to_activate = FALSE + /// Gas being expelled. + var/active_gas = null + /// Associative list of types of gases to moles we create every life tick. + var/static/list/possible_gases = list( + /datum/gas/oxygen = 50, + /datum/gas/nitrogen = 750, //overpressurizing is hard!. + /datum/gas/water_vapor = 1, //you need incredibly little water vapor for the effects to kick in + /datum/gas/nitrous_oxide = 15, + /datum/gas/carbon_dioxide = 50, + /datum/gas/plasma = 3, + /datum/gas/bz = 10, + ) + +/datum/action/cooldown/mob_cooldown/expel_gas/Grant(mob/granted_to) + . = ..() + if (isnull(owner)) + return + RegisterSignal(owner, COMSIG_GUARDIAN_RECALLED, PROC_REF(stop_gas)) + +/datum/action/cooldown/mob_cooldown/expel_gas/Remove(mob/removed_from) + UnregisterSignal(owner, list(COMSIG_GUARDIAN_RECALLED, COMSIG_LIVING_LIFE)) + return ..() + +/datum/action/cooldown/mob_cooldown/expel_gas/Activate(atom/target) + StartCooldown(360 SECONDS) + // Regeneated each time just in case someone fucks with our list + var/list/gas_selection = list("None") + for(var/datum/gas/gas as anything in possible_gases) + gas_selection[initial(gas.name)] = gas + + var/picked_gas = tgui_input_list(owner, "Select a gas to emit.", "Gas Producer", gas_selection) + StartCooldown() + if(picked_gas == "None") + stop_gas() + return + + var/gas_type = gas_selection[picked_gas] + if(isnull(picked_gas) || isnull(gas_type)) + return + + to_chat(owner, span_bolddanger("You start releasing [picked_gas].")) + owner.investigate_log("set their gas type to [picked_gas].", INVESTIGATE_ATMOS) + var/had_gas = !isnull(active_gas) + active_gas = gas_type + if(isnull(owner.particles)) + owner.particles = new /particles/smoke/steam() + owner.particles.position = list(-1, 8, 0) + owner.particles.fadein = 5 + owner.particles.height = 200 + //var/datum/gas/chosen_gas = active_gas // Casting it so that we can access gas vars in initial, it's still a typepath + owner.particles.color = COLOR_BLUE + if (!had_gas) + RegisterSignal(owner, COMSIG_LIVING_LIFE, PROC_REF(on_life)) + +/// Turns off the gas +/datum/action/cooldown/mob_cooldown/expel_gas/proc/stop_gas() + SIGNAL_HANDLER + if (!isnull(active_gas)) + to_chat(src, span_notice("You stop releasing gas.")) + active_gas = null + QDEL_NULL(owner.particles) + UnregisterSignal(owner, COMSIG_LIVING_LIFE) + +/// Release gas every life tick while active +/datum/action/cooldown/mob_cooldown/expel_gas/proc/on_life(datum/source, seconds_per_tick, times_fired) + SIGNAL_HANDLER + if (isnull(active_gas)) + return // We shouldn't even be registered at this point but just in case + var/datum/gas_mixture/mix_to_spawn = new() + mix_to_spawn.add_gas(active_gas) + mix_to_spawn.gases[active_gas][MOLES] = possible_gases[active_gas] * seconds_per_tick + mix_to_spawn.temperature = T20C + var/turf/open/our_turf = get_turf(owner) + our_turf.assume_air(mix_to_spawn) diff --git a/code/modules/mob/living/basic/guardian/guardian_types/gravitokinetic.dm b/code/modules/mob/living/basic/guardian/guardian_types/gravitokinetic.dm new file mode 100644 index 000000000000..a0ad9c8c21b5 --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_types/gravitokinetic.dm @@ -0,0 +1,107 @@ +/// Somewhat durable guardian who can increase gravity in an area +/mob/living/basic/guardian/gravitokinetic + guardian_type = GUARDIAN_GRAVITOKINETIC + melee_damage_lower = 15 + melee_damage_upper = 15 + damage_coeff = list(BRUTE = 0.75, BURN = 0.75, TOX = 0.75, CLONE = 0.75, STAMINA = 0, OXY = 0.75) + playstyle_string = span_holoparasite("As a gravitokinetic type, you can right-click to make the gravity on the ground stronger, and punching applies this effect to a target.") + creator_name = "Gravitokinetic" + creator_desc = "Attacks will apply crushing gravity to the target. Can target the ground as well to slow targets advancing on you, but you are not immune to your own such effects." + creator_icon = "gravitokinetic" + /// Targets we have applied our gravity effects on. + var/list/gravity_targets = list() + /// Distance at which our ability works + var/gravity_power_range = 10 + /// Gravity added on punches. + var/punch_gravity = 5 + /// Gravity added to turfs. + var/turf_gravity = 3 + +/mob/living/basic/guardian/gravitokinetic/Initialize(mapload, datum/guardian_fluff/theme) + . = ..() + AddElement(/datum/element/forced_gravity, 1) + + var/static/list/container_connections = list( + COMSIG_MOVABLE_MOVED = PROC_REF(on_moved), + ) + AddComponent(/datum/component/connect_containers, src, container_connections) + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) + +/mob/living/basic/guardian/gravitokinetic/set_summoner(mob/living/to_who, different_person) + . = ..() + if (!QDELETED(src)) + return + to_who.AddElement(/datum/element/forced_gravity, 1) + +/mob/living/basic/guardian/gravitokinetic/cut_summoner(different_person) + summoner?.RemoveElement(/datum/element/forced_gravity, 1) + return ..() + +/mob/living/basic/guardian/gravitokinetic/death(gibbed) + . = ..() + clear_gravity() + +/mob/living/basic/guardian/gravitokinetic/recall_effects() + . = ..() + if (length(gravity_targets)) + to_chat(src, span_bolddanger("You have released your gravitokinetic powers!")) + clear_gravity() + +/mob/living/basic/guardian/gravitokinetic/melee_attack(atom/target, list/modifiers, ignore_cooldown) + . = ..() + if (!. || !isliving(target) || target == src || target == summoner || shares_summoner(target) || gravity_targets[target]) + return + to_chat(src, span_bolddanger("Your punch has applied heavy gravity to [target]!")) + add_gravity(target, punch_gravity) + to_chat(target, span_userdanger("Everything feels really heavy!")) + return TRUE + +/mob/living/basic/guardian/gravitokinetic/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) + if (LAZYACCESS(modifiers, RIGHT_CLICK) && proximity_flag && !gravity_targets[attack_target]) + slam_turf(attack_target) + return + return ..() + +/// Apply forced gravity to the floor +/mob/living/basic/guardian/gravitokinetic/proc/slam_turf(turf/open/slammed) + if (!isopenturf(slammed) || isgroundlessturf(slammed)) + return + visible_message(span_danger("[src] slams their fist into the [slammed]!"), span_notice("You amplify gravity around the [slammed].")) + do_attack_animation(slammed) + add_gravity(slammed, turf_gravity) + +/// Remove our forced gravity from all targets +/mob/living/basic/guardian/gravitokinetic/proc/clear_gravity() + for(var/gravity_target in gravity_targets) + remove_gravity(gravity_target) + +/// Make something heavier +/mob/living/basic/guardian/gravitokinetic/proc/add_gravity(atom/target, new_gravity = 3) + if (gravity_targets[target]) + return + target.AddElement(/datum/element/forced_gravity, new_gravity) + gravity_targets[target] = new_gravity + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_target_moved)) + playsound(src, 'sound/effects/gravhit.ogg', 100, TRUE) + +/// Stop making something heavier +/mob/living/basic/guardian/gravitokinetic/proc/remove_gravity(atom/target, too_far = FALSE) + if (isnull(gravity_targets[target])) + return + if (too_far) + to_chat(src, span_bolddanger("You are too far away from [target] to amplify gravity's hold on them!")) + UnregisterSignal(target, COMSIG_MOVABLE_MOVED) + target.RemoveElement(/datum/element/forced_gravity, gravity_targets[target]) + gravity_targets -= target + +/// When we or something we are inside move check if we are now too far away +/mob/living/basic/guardian/gravitokinetic/proc/on_moved() + for(var/gravity_target in gravity_targets) + if (get_dist(src, gravity_target) > gravity_power_range) + remove_gravity(gravity_target, too_far = TRUE) + +/// When something we put gravity on moves check if it's too far away +/mob/living/basic/guardian/gravitokinetic/proc/on_target_moved(atom/movable/moving_target, old_loc, dir, forced) + SIGNAL_HANDLER + if (get_dist(src, moving_target) > gravity_power_range) + remove_gravity(moving_target, too_far = TRUE) diff --git a/code/modules/mob/living/basic/guardian/guardian_types/lightning.dm b/code/modules/mob/living/basic/guardian/guardian_types/lightning.dm new file mode 100644 index 000000000000..b6d537b8c66e --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_types/lightning.dm @@ -0,0 +1,95 @@ +/// A reasonably durable guardian linked to you by a chain of lightning, zapping people who get between you +/mob/living/basic/guardian/lightning + guardian_type = GUARDIAN_LIGHTNING + melee_damage_lower = 7 + melee_damage_upper = 7 + attack_verb_continuous = "shocks" + attack_verb_simple = "shock" + melee_damage_type = BURN + attack_sound = 'sound/machines/defib_zap.ogg' + damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7) + range = 7 + playstyle_string = span_holoparasite("As a lightning type, you will apply lightning chains to targets on attack and have a lightning chain to your summoner. Lightning chains will shock anyone near them.") + creator_name = "Lightning" + creator_desc = "Attacks apply lightning chains to targets. Has a lightning chain to the user. Lightning chains shock everything near them, doing constant damage." + creator_icon = "lightning" + /// Link between us and our summoner + var/datum/component/summoner_chain + /// Associative list of chained enemies to their chains + var/list/enemy_chains + +/mob/living/basic/guardian/lightning/death(gibbed) + . = ..() + clear_chains() + +/mob/living/basic/guardian/lightning/Destroy() + clear_chains() + return ..() + +/mob/living/basic/guardian/lightning/manifest_effects() + . = ..() + if (isnull(summoner)) + return + summoner_chain = chain_to(summoner, max_range = INFINITY) // Functionally it's actually our leash range but admins might fuck with it + +/mob/living/basic/guardian/lightning/recall_effects() + . = ..() + clear_chains() + +/// Remove all of our chains +/mob/living/basic/guardian/lightning/proc/clear_chains() + QDEL_NULL(summoner_chain) + QDEL_LIST_ASSOC_VAL(enemy_chains) + +/mob/living/basic/guardian/lightning/melee_attack(atom/target, list/modifiers, ignore_cooldown) + . = ..() + if (!. || !validate_target(target) || (target in enemy_chains)) + return + if (length(enemy_chains) == 2) + var/old_target = enemy_chains[1] + var/datum/old_chain = enemy_chains[old_target] + qdel(old_chain) + var/datum/new_chain = chain_to(target) + RegisterSignal(new_chain, COMSIG_PARENT_QDELETING, PROC_REF(on_chain_deleted)) + LAZYADDASSOC(enemy_chains, target, new_chain) + +/// Create a damaging lightning chain between ourselves and a target +/mob/living/basic/guardian/lightning/proc/chain_to(atom/target, max_range = 7) + var/datum/component/chain = AddComponent(\ + /datum/component/damage_chain, \ + linked_to = target, \ + max_distance = max_range, \ + beam_state = "lightning[rand(1,12)]", \ + beam_type = /obj/effect/ebeam/chain, \ + validate_target = CALLBACK(src, PROC_REF(validate_target)), \ + chain_damage_feedback = CALLBACK(src, PROC_REF(on_chain_zap)), \ + ) + return chain + +/// Handle losing our reference when we delete a chain +/mob/living/basic/guardian/lightning/proc/on_chain_deleted(datum/source) + SIGNAL_HANDLER + for (var/target in enemy_chains) + if (enemy_chains[target] != source) + continue + enemy_chains -= target + return + +/// Confirm whether something is valid to zap with lightning +/mob/living/basic/guardian/lightning/proc/validate_target(atom/target) + return isliving(target) && target != src && target != summoner && !shares_summoner(target) + +/// Called every few zaps by a chain +/mob/living/basic/guardian/lightning/proc/on_chain_zap(mob/living/target) + target.electrocute_act(shock_damage = 0, source = "lightning chain") + target.visible_message( + span_danger("[target] was shocked by the lightning chain!"), + span_userdanger("You are shocked by the lightning chain!"), + span_hear("You hear a heavy electrical crack."), + ) + +/// Beam definition for our lightning chain +/obj/effect/ebeam/chain + name = "lightning chain" + layer = LYING_MOB_LAYER + plane = GAME_PLANE_FOV_HIDDEN diff --git a/code/modules/mob/living/basic/guardian/guardian_types/protector.dm b/code/modules/mob/living/basic/guardian/guardian_types/protector.dm new file mode 100644 index 000000000000..a0aa34ad17f1 --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_types/protector.dm @@ -0,0 +1,122 @@ +/// Very durable, and reverses the usual leash dynamic. Can slow down to become extremely durable. +/mob/living/basic/guardian/protector + guardian_type = GUARDIAN_PROTECTOR + melee_damage_lower = 15 + melee_damage_upper = 15 + range = 5 // You want this to be low so you can drag them around + damage_coeff = list(BRUTE = 0.4, BURN = 0.4, TOX = 0.4, CLONE = 0.4, STAMINA = 0, OXY = 0.4) + playstyle_string = span_holoparasite("As a protector type you cause your summoner to leash to you instead of you leashing to them and have two modes; Combat Mode, where you do and take medium damage, and Protection Mode, where you do and take almost no damage, but move slightly slower.") + creator_name = "Protector" + creator_desc = "Causes you to teleport to it when out of range, unlike other parasites. Has two modes; Combat, where it does and takes medium damage, and Protection, where it does and takes almost no damage but moves slightly slower." + creator_icon = "protector" + toggle_button_type = /atom/movable/screen/guardian/toggle_mode + /// Action which toggles our shield + var/datum/action/cooldown/mob_cooldown/protector_shield/shield + +/mob/living/basic/guardian/protector/Initialize(mapload, datum/guardian_fluff/theme) + . = ..() + shield = new(src) + shield.owner_has_control = FALSE // Hide it from the user, it's integrated with guardian UI + shield.Grant(src) + +/mob/living/basic/guardian/protector/Destroy() + QDEL_NULL(shield) + return ..() + +// Invert the order +/mob/living/basic/guardian/protector/leash_to(atom/movable/leashed, atom/movable/leashed_to) + return ..(leashed_to, leashed) + +/mob/living/basic/guardian/protector/unleash() + qdel(summoner?.GetComponent(/datum/component/leash)) + +/mob/living/basic/guardian/protector/toggle_modes() + shield.Trigger() + +/mob/living/basic/guardian/protector/ex_act(severity) + if(severity >= EXPLODE_DEVASTATE) + adjustBruteLoss(400) //if in protector mode, will do 20 damage and not actually necessarily kill the summoner + return TRUE + return ..() + +/// Toggle a status effect which makes you slow but defensive +/datum/action/cooldown/mob_cooldown/protector_shield + name = "Protection Mode" + desc = "Enter a defensive stance which slows you down and reduces your damage, but makes you almost invincible." + button_icon = 'icons/effects/effects.dmi' + button_icon_state = "shield-old" + background_icon = 'icons/hud/guardian.dmi' + background_icon_state = "base" + cooldown_time = 1 SECONDS + click_to_activate = FALSE + +/datum/action/cooldown/mob_cooldown/protector_shield/Activate(mob/living/target) + if (!isliving(target)) + return FALSE + if (target.has_status_effect(/datum/status_effect/protector_shield)) + target.remove_status_effect(/datum/status_effect/protector_shield) + return + target.apply_status_effect(/datum/status_effect/protector_shield) + StartCooldown() + return TRUE + +/// Makes the guardian even more durable, but slower +/datum/status_effect/protector_shield + id = "guardian_shield" + alert_type = null + /// Damage removed in protecting mode. + var/damage_penalty = 13 + /// Colour for our various overlays. + var/overlay_colour = COLOR_TEAL + /// Overlay for our protection shield. + var/mutable_appearance/shield_overlay + /// Damage coefficients when shielded + var/list/shielded_damage = list(BRUTE = 0.05, BURN = 0.05, TOX = 0.05, CLONE = 0.05, STAMINA = 0, OXY = 0.05) + +/datum/status_effect/protector_shield/on_apply() + if (isguardian(owner)) + var/mob/living/basic/guardian/guardian_owner = owner + overlay_colour = guardian_owner.guardian_colour + shield_overlay = mutable_appearance('icons/effects/effects.dmi', "shield-grey") + shield_overlay.color = overlay_colour + + owner.melee_damage_lower -= damage_penalty + owner.melee_damage_upper -= damage_penalty + owner.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/guardian_shield) + + if (isbasicmob(owner)) // Better hope you are or this status is doing basically nothing useful for you + var/mob/living/basic/basic_owner = owner + basic_owner.damage_coeff = shielded_damage + + to_chat(owner, span_bolddanger("You enter protection mode.")) + RegisterSignal(owner, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays)) + RegisterSignals(owner, COMSIG_LIVING_ADJUST_STANDARD_DAMAGE_TYPES, PROC_REF(on_health_changed)) + owner.update_appearance(UPDATE_ICON) + return TRUE + +/datum/status_effect/protector_shield/on_remove() + owner.melee_damage_lower += damage_penalty + owner.melee_damage_upper += damage_penalty + owner.remove_movespeed_modifier(/datum/movespeed_modifier/status_effect/guardian_shield) + + if (isbasicmob(owner)) + var/mob/living/basic/basic_owner = owner + basic_owner.damage_coeff = initial(basic_owner.damage_coeff) + + to_chat(owner, span_bolddanger("You return to your normal mode.")) + UnregisterSignal(owner, list(COMSIG_ATOM_UPDATE_OVERLAYS) + COMSIG_LIVING_ADJUST_STANDARD_DAMAGE_TYPES) + owner.update_appearance(UPDATE_ICON) + +/// Show an extra overlay when we're in shield mode +/datum/status_effect/protector_shield/proc/on_update_overlays(atom/source, list/overlays) + SIGNAL_HANDLER + overlays += shield_overlay + +/// Flash an animation when someone tries to hurt us +/datum/status_effect/protector_shield/proc/on_health_changed(mob/living/our_mob, type, amount, forced) + SIGNAL_HANDLER + if (amount <= 0 && !QDELETED(our_mob)) + return + var/image/flash_overlay = new('icons/effects/effects.dmi', owner, "shield-flash", dir = pick(GLOB.cardinals)) + flash_overlay.color = overlay_colour + owner.flick_overlay_view(flash_overlay, 0.5 SECONDS) diff --git a/code/modules/mob/living/basic/guardian/guardian_types/ranged.dm b/code/modules/mob/living/basic/guardian/guardian_types/ranged.dm new file mode 100644 index 000000000000..3be2e13b34a0 --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_types/ranged.dm @@ -0,0 +1,204 @@ +/// A ranged guardian can fling shards of glass at people very very quickly. It can also enter a long-range scouting mode. +/mob/living/basic/guardian/ranged + guardian_type = GUARDIAN_RANGED + friendly_verb_continuous = "quietly assesses" + friendly_verb_simple = "quietly assess" + melee_damage_lower = 10 + melee_damage_upper = 10 + damage_coeff = list(BRUTE = 0.9, BURN = 0.9, TOX = 0.9, CLONE = 0.9, STAMINA = 0, OXY = 0.9) + range = 13 + playstyle_string = span_holoparasite("As a ranged type, you have only light damage resistance, but are capable of spraying shards of crystal at incredibly high speed. You can also deploy surveillance snares to monitor enemy movement. Finally, you can switch to scout mode, in which you can't attack, but can move without limit.") + creator_name = "Ranged" + creator_desc = "Has two modes. Ranged; which fires a constant stream of weak, armor-ignoring projectiles. Scout; where it cannot attack, but can move through walls and is quite hard to see. Can lay surveillance snares, which alert it when crossed, in either mode." + creator_icon = "ranged" + see_invisible = SEE_INVISIBLE_LIVING + toggle_button_type = /atom/movable/screen/guardian/toggle_mode + +/mob/living/basic/guardian/ranged/Initialize(mapload, datum/guardian_fluff/theme) + . = ..() + AddComponent(\ + /datum/component/ranged_attacks,\ + projectile_type = /obj/projectile/guardian,\ + projectile_sound = 'sound/effects/hit_on_shattered_glass.ogg',\ + cooldown_time = 0.1 SECONDS, \ + ) + AddComponent(/datum/component/ranged_mob_full_auto, autofire_shot_delay = 0.1 SECONDS) + var/datum/action/cooldown/mob_cooldown/guardian_alarm_snare/snare = new (src) + snare.Grant(src) + +/mob/living/basic/guardian/ranged/toggle_modes() + if(is_deployed() && !isnull(summoner)) + balloon_alert(src, "must not be manifested!") + return + if (has_status_effect(/datum/status_effect/guardian_scout_mode)) + remove_status_effect(/datum/status_effect/guardian_scout_mode) + return + apply_status_effect(/datum/status_effect/guardian_scout_mode) + +/mob/living/basic/guardian/ranged/toggle_light() + var/msg + switch(lighting_cutoff) + if (LIGHTING_CUTOFF_VISIBLE) + lighting_cutoff_red = 10 + lighting_cutoff_green = 10 + lighting_cutoff_blue = 15 + msg = "You activate your night vision." + if (LIGHTING_CUTOFF_MEDIUM) + lighting_cutoff_red = 25 + lighting_cutoff_green = 25 + lighting_cutoff_blue = 35 + msg = "You increase your night vision." + if (LIGHTING_CUTOFF_HIGH) + lighting_cutoff_red = 35 + lighting_cutoff_green = 35 + lighting_cutoff_blue = 50 + msg = "You maximize your night vision." + else + lighting_cutoff_red = 0 + lighting_cutoff_green = 0 + lighting_cutoff_blue = 0 + msg = "You deactivate your night vision." + sync_lighting_plane_cutoff() + to_chat(src, span_notice(msg)) + +/// Become an incorporeal scout +/datum/status_effect/guardian_scout_mode + id = "guardian_scout" + alert_type = null + +/datum/status_effect/guardian_scout_mode/on_apply() + animate(owner, alpha = 45, time = 0.5 SECONDS) + RegisterSignal(owner, COMSIG_GUARDIAN_MANIFESTED, PROC_REF(on_manifest)) + RegisterSignal(owner, COMSIG_GUARDIAN_RECALLED, PROC_REF(on_recall)) + RegisterSignal(owner, COMSIG_MOB_CLICKON, PROC_REF(on_click)) + + var/mob/living/basic/guardian/guardian_mob = owner + guardian_mob.unleash() + to_chat(owner, span_bolddanger("You enter scouting mode.")) + return TRUE + +/datum/status_effect/guardian_scout_mode/on_remove() + animate(owner, alpha = initial(owner.alpha), time = 0.5 SECONDS) + UnregisterSignal(owner, list(COMSIG_GUARDIAN_MANIFESTED, COMSIG_GUARDIAN_RECALLED, COMSIG_MOB_CLICKON)) + to_chat(owner, span_bolddanger("You return to your normal mode.")) + var/mob/living/basic/guardian/guardian_mob = owner + guardian_mob.leash_to(owner, guardian_mob.summoner) + +/// Restore incorporeal move when we become corporeal, yes I know that suonds silly +/datum/status_effect/guardian_scout_mode/proc/on_manifest() + SIGNAL_HANDLER + owner.incorporeal_move = INCORPOREAL_MOVE_BASIC + +/// Stop having incorporeal move when we recall so that we can't move +/datum/status_effect/guardian_scout_mode/proc/on_recall() + SIGNAL_HANDLER + owner.incorporeal_move = FALSE + +/// While this is active we can't click anything +/datum/status_effect/guardian_scout_mode/proc/on_click() + SIGNAL_HANDLER + return COMSIG_MOB_CANCEL_CLICKON + + +/// Place an invisible trap which alerts the guardian when it is crossed +/datum/action/cooldown/mob_cooldown/guardian_alarm_snare + name = "Surveillance Snare" + desc = "Place an invisible snare which will alert you when it is crossed." + button_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "eye" + background_icon = 'icons/hud/guardian.dmi' + background_icon_state = "base" + cooldown_time = 2 SECONDS + melee_cooldown_time = 0 + click_to_activate = FALSE + /// How many snares can we have? + var/maximum_snares = 5 + /// What snares have we already placed? + var/list/placed_snares = list() + +/datum/action/cooldown/mob_cooldown/guardian_alarm_snare/Activate(atom/target) + StartCooldown(360 SECONDS) + + if (length(placed_snares) >= maximum_snares) + var/picked_snare = tgui_input_list(owner, "Choose a snare to replace.", "Remove Snare", sort_names(placed_snares)) + if(isnull(picked_snare)) + return FALSE + qdel(picked_snare) + if (length(placed_snares) >= maximum_snares) + StartCooldown(0) + return FALSE + + owner.balloon_alert(owner, "snare deployed") // We need feedback because they are invisible + var/turf/snare_loc = get_turf(owner) + var/obj/effect/abstract/surveillance_snare/new_snare = new(snare_loc, owner) + new_snare.assign_owner(owner) + RegisterSignal(new_snare, COMSIG_PARENT_QDELETING, PROC_REF(on_snare_deleted)) + placed_snares += new_snare + + StartCooldown() + return TRUE + +/// When a snare is deleted remove it from tracking +/datum/action/cooldown/mob_cooldown/guardian_alarm_snare/proc/on_snare_deleted(atom/snare) + SIGNAL_HANDLER + placed_snares -= snare + + +/// An invisible marker placed by a ranged guardian, alerts the owner when crossed +/obj/effect/abstract/surveillance_snare + name = "surveillance snare" + desc = "This thing is invisible, how are you examining it?" + invisibility = INVISIBILITY_ABSTRACT + /// Who do we notify when someone steps on us? + var/mob/living/owner + +/obj/effect/abstract/surveillance_snare/Initialize(mapload, spawning_guardian) + . = ..() + name = "[get_area(src)] snare ([rand(1, 1000)])" + var/static/list/loc_connections = list(COMSIG_ATOM_ENTERED = PROC_REF(on_entered)) + AddElement(/datum/element/connect_loc, loc_connections) + +/// Set up crossed notification +/obj/effect/abstract/surveillance_snare/proc/assign_owner(mob/living/new_owner) + if (isnull(new_owner)) + qdel(src) + return + owner = new_owner + RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(owner_destroyed)) + +/// When crossed notify our owner +/obj/effect/abstract/surveillance_snare/proc/on_entered(atom/source, crossed_object) + SIGNAL_HANDLER + if (isnull(owner)) + qdel(src) + return + if (!isliving(crossed_object) || crossed_object == owner) + return + var/mob/living/basic/guardian/guardian_owner = owner + if (isguardian(owner) && crossed_object == guardian_owner.summoner || guardian_owner.shares_summoner(crossed_object)) + return + + var/send_message = span_bolddanger("[crossed_object] has crossed [name].") + if (!isguardian(owner) || isnull(guardian_owner.summoner)) + to_chat(owner, send_message) + return + + to_chat(guardian_owner.summoner, send_message) + var/list/guardians = guardian_owner.summoner.get_all_linked_holoparasites() + for(var/guardian in guardians) + to_chat(guardian, send_message) + +/// If the person who placed us doesn't exist we might as well die +/obj/effect/abstract/surveillance_snare/proc/owner_destroyed() + SIGNAL_HANDLER + owner = null + qdel(src) + + +/// The glass shards we throw as a guardian. They have low damage because you can fire them very very quickly. +/obj/projectile/guardian + name = "crystal spray" + icon_state = "guardian" + damage = 5 + damage_type = BRUTE + armour_penetration = 100 diff --git a/code/modules/mob/living/basic/guardian/guardian_types/standard.dm b/code/modules/mob/living/basic/guardian/guardian_types/standard.dm new file mode 100644 index 000000000000..2ca006b385c7 --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_types/standard.dm @@ -0,0 +1,63 @@ +/// Plain, but durable and strong. Can destroy walls. +/mob/living/basic/guardian/standard + guardian_type = GUARDIAN_STANDARD + damage_coeff = list(BRUTE = 0.5, BURN = 0.5, TOX = 0.5, CLONE = 0.5, STAMINA = 0, OXY = 0.5) + melee_damage_lower = 20 + melee_damage_upper = 20 + melee_attack_cooldown = 0.6 SECONDS + wound_bonus = -5 //you can wound! + obj_damage = 80 + environment_smash = ENVIRONMENT_SMASH_WALLS + playstyle_string = span_holoparasite("As a standard type you have no special abilities, but have a high damage resistance and a powerful attack capable of smashing through walls.") + creator_name = "Standard" + creator_desc = "Devastating close combat attacks and high damage resistance. Can smash through weak walls." + creator_icon = "standard" + /// The text we shout when attacking. + var/battlecry = "AT" + +/mob/living/basic/guardian/standard/Initialize(mapload, datum/guardian_fluff/theme) + . = ..() + AddElement(/datum/element/wall_tearer, allow_reinforced = FALSE, tear_time = 1.5 SECONDS) + var/datum/action/select_guardian_battlecry/cry = new(src) + cry.Grant(src) + +/mob/living/basic/guardian/standard/do_attack_animation(atom/attacked_atom, visual_effect_icon, used_item, no_effect) + . = ..() + if (!isliving(attacked_atom) || !isclosedturf(attacked_atom)) + return + var/msg = "" + for(var/i in 1 to 9) + msg += battlecry + say("[msg]!!", ignore_spam = TRUE) + for(var/sounds in 1 to 4) + addtimer(CALLBACK(src, PROC_REF(do_attack_sound), attacked_atom.loc), sounds DECISECONDS, TIMER_DELETE_ME) + +/// Echo our punching sounds +/mob/living/basic/guardian/standard/proc/do_attack_sound(atom/playing_from) + playsound(playing_from, attack_sound, 50, TRUE, TRUE) + +/// Action to change our battlecry +/datum/action/select_guardian_battlecry + name = "Select Battlecry" + desc = "Update the really cool thing you shout whenever you attack." + button_icon = 'icons/obj/clothing/gloves.dmi' + button_icon_state = "boxing" + background_icon = 'icons/hud/guardian.dmi' + background_icon_state = "base" + /// How long can it be? Shouldn't be too long because we repeat this a shitload of times + var/max_length = 6 + +/datum/action/select_guardian_battlecry/IsAvailable(feedback) + if (!istype(owner, /mob/living/basic/guardian/standard)) + return FALSE + return ..() + +/datum/action/select_guardian_battlecry/Trigger(trigger_flags) + . = ..() + if (!.) + return + var/mob/living/basic/guardian/standard/stand = owner + var/input = tgui_input_text(owner, "What do you want your battlecry to be?", "Battle Cry", max_length = max_length) + if(!input) + return + stand.battlecry = input diff --git a/code/modules/mob/living/basic/guardian/guardian_types/support.dm b/code/modules/mob/living/basic/guardian/guardian_types/support.dm new file mode 100644 index 000000000000..0fdc120fb5a3 --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_types/support.dm @@ -0,0 +1,164 @@ +/// Quick-moving mob which can teleport things to a beacon and heal its allies +/mob/living/basic/guardian/support + guardian_type = GUARDIAN_SUPPORT + speed = 0 + damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7) + melee_damage_lower = 15 + melee_damage_upper = 15 + playstyle_string = span_holoparasite("As a support type, you may right-click to heal targets. In addition, alt-clicking on an adjacent object or mob will warp them to your bluespace beacon after a short delay.") + creator_name = "Support" + creator_desc = "Does medium damage, but can heal its targets and create beacons to teleport people and things to." + creator_icon = "support" + /// Amount of each damage type to heal per hit + var/healing_amount = 5 + +/mob/living/basic/guardian/support/Initialize(mapload, datum/guardian_fluff/theme) + . = ..() + AddComponent(\ + /datum/component/healing_touch,\ + heal_brute = healing_amount,\ + heal_burn = healing_amount,\ + heal_tox = healing_amount,\ + heal_oxy = healing_amount,\ + heal_time = 0,\ + action_text = "",\ + complete_text = "",\ + required_modifier = RIGHT_CLICK,\ + after_healed = CALLBACK(src, PROC_REF(after_healed)),\ + ) + + var/datum/atom_hud/medsensor = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] + medsensor.show_to(src) + + var/datum/action/cooldown/mob_cooldown/guardian_bluespace_beacon/teleport = new(src) + teleport.Grant(src) + +/mob/living/basic/guardian/support/set_guardian_colour(colour) + . = ..() + AddComponent(/datum/component/healing_touch, heal_color = guardian_colour) + +/// Called after we heal someone, show some visuals +/mob/living/basic/guardian/support/proc/after_healed(mob/living/healed) + do_attack_animation(healed, ATTACK_EFFECT_PUNCH) + healed.visible_message( + message = span_notice("[src] heals [healed]!"), + self_message = span_userdanger("[src] heals you!"), + vision_distance = COMBAT_MESSAGE_RANGE, + ignored_mobs = src, + ) + to_chat(src, span_notice("You heal [healed]!")) + playsound(healed, attack_sound, 50, TRUE, TRUE, frequency = -1) // play punch sound in REVERSE + + +/// Place a beacon and then listen for clicks to teleport people to it +/datum/action/cooldown/mob_cooldown/guardian_bluespace_beacon + name = "Place Bluespace Beacon" + desc = "Mark the ground under your feet as a teleportation point. Alt-click things to teleport them to your beacon." + button_icon = 'icons/effects/effects.dmi' + button_icon_state = "the_freezer" + background_icon = 'icons/hud/guardian.dmi' + background_icon_state = "base" + cooldown_time = 5 MINUTES + melee_cooldown_time = 0 + cooldown_rounding = 1 + click_to_activate = FALSE + /// Our teleportation beacon. + var/obj/structure/guardian_beacon/beacon + /// Time it takes to teleport something. + var/teleport_time = 6 SECONDS + +/datum/action/cooldown/mob_cooldown/guardian_bluespace_beacon/Grant(mob/granted_to) + . = ..() + RegisterSignal(owner, COMSIG_MOB_ALTCLICKON, PROC_REF(try_teleporting)) + +/datum/action/cooldown/mob_cooldown/guardian_bluespace_beacon/Remove(mob/removed_from) + UnregisterSignal(owner, COMSIG_MOB_ALTCLICKON) + return ..() + +/datum/action/cooldown/mob_cooldown/guardian_bluespace_beacon/Activate(atom/movable/target) + var/turf/beacon_loc = owner.loc + if(!isfloorturf(beacon_loc)) + owner.balloon_alert(owner, "no room!") + return FALSE + + if (!isnull(beacon)) + beacon.visible_message("[beacon] vanishes!") + new /obj/effect/temp_visual/guardian/phase/out(beacon.loc) + qdel(beacon) + + beacon = new(beacon_loc, src) + if (isguardian(owner)) + var/mob/living/basic/guardian/guardian_owner = owner + beacon.add_atom_colour(guardian_owner.guardian_colour, FIXED_COLOUR_PRIORITY) + RegisterSignal(beacon, COMSIG_PARENT_QDELETING, PROC_REF(on_beacon_deleted)) + to_chat(src, span_bolddanger("Beacon placed! You may now warp targets and objects to it, including your user, via Alt+Click.")) + StartCooldown() + return TRUE + +/// Don't hold a reference to a deleted beacon +/datum/action/cooldown/mob_cooldown/guardian_bluespace_beacon/proc/on_beacon_deleted() + SIGNAL_HANDLER + beacon = null + +/// Try and teleport something to our beacon +/datum/action/cooldown/mob_cooldown/guardian_bluespace_beacon/proc/try_teleporting(mob/living/source, atom/target) + SIGNAL_HANDLER + if (!can_teleport(source, target)) + return + INVOKE_ASYNC(src, PROC_REF(perform_teleport), source, target) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/// Validate whether we can teleport this object +/datum/action/cooldown/mob_cooldown/guardian_bluespace_beacon/proc/can_teleport(mob/living/source, atom/movable/target) + if (isnull(beacon)) + source.balloon_alert(source, "no beacon!") + return FALSE + if (isguardian(source)) + var/mob/living/basic/guardian/guardian_mob = source + if (!guardian_mob.is_deployed()) + source.balloon_alert(source, "manifest yourself!") + return FALSE + if (!source.Adjacent(target)) + target.balloon_alert(source, "too far!") + return FALSE + if (target.anchored) + target.balloon_alert(source, "it won't budge!") + return FALSE + if(beacon.z != target.z) + target.balloon_alert(source, "too far from beacon!") + return FALSE + return TRUE + +/// Start teleporting +/datum/action/cooldown/mob_cooldown/guardian_bluespace_beacon/proc/perform_teleport(mob/living/source, atom/target) + source.do_attack_animation(target) + playsound(target, 'sound/weapons/punch1.ogg', 50, TRUE, TRUE, frequency = -1) + source.balloon_alert(source, "teleporting...") + target.visible_message( + span_danger("[target] starts to glow faintly!"), \ + span_userdanger("You start to faintly glow, and you feel strangely weightless!")) + if(!do_after(source, teleport_time, target)) + return + new /obj/effect/temp_visual/guardian/phase/out(target.loc) + if(isliving(target)) + var/mob/living/living_target = target + living_target.flash_act() + target.visible_message( + span_danger("[target] disappears in a flash of light!"), \ + span_userdanger("Your vision is obscured by a flash of light!"), \ + ) + do_teleport(target, beacon, precision = 0, channel = TELEPORT_CHANNEL_BLUESPACE) + new /obj/effect/temp_visual/guardian/phase(get_turf(target)) + + +/// Structure which acts as the landing point for a support guardian's teleportation effects +/obj/structure/guardian_beacon + name = "guardian beacon" + icon = 'icons/turf/floors.dmi' + desc = "A glowing zone which acts as a beacon for teleportation." + icon_state = "light_on-8" + light_outer_range = MINIMUM_USEFUL_LIGHT_RANGE + density = FALSE + anchored = TRUE + plane = FLOOR_PLANE + layer = ABOVE_OPEN_TURF_LAYER diff --git a/code/modules/mob/living/basic/guardian/guardian_verbs.dm b/code/modules/mob/living/basic/guardian/guardian_verbs.dm new file mode 100644 index 000000000000..02d1fd1ed3ab --- /dev/null +++ b/code/modules/mob/living/basic/guardian/guardian_verbs.dm @@ -0,0 +1,187 @@ +/// Pop out into the realm of the living. +/mob/living/basic/guardian/proc/manifest(forced) + if (is_deployed() || isnull(summoner) || isnull(summoner.loc) || istype(summoner.loc, /obj/effect) || (!COOLDOWN_FINISHED(src, manifest_cooldown) && !forced) || locked) + return FALSE + forceMove(summoner.loc) + new /obj/effect/temp_visual/guardian/phase(loc) + COOLDOWN_START(src, manifest_cooldown, 1 SECONDS) + reset_perspective() + manifest_effects() + return TRUE + +/// Go and hide inside your boss. +/mob/living/basic/guardian/proc/recall(forced) + if (!is_deployed() || isnull(summoner) || (!COOLDOWN_FINISHED(src, manifest_cooldown) && !forced) || locked) + return FALSE + new /obj/effect/temp_visual/guardian/phase/out(loc) + recall_effects() + forceMove(summoner) + COOLDOWN_START(src, manifest_cooldown, 1 SECONDS) + return TRUE + +/// Do something when we appear. +/mob/living/basic/guardian/proc/manifest_effects() + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(src, COMSIG_GUARDIAN_MANIFESTED) + +/// Do something when we vanish. +/mob/living/basic/guardian/proc/recall_effects() + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(src, COMSIG_GUARDIAN_RECALLED) + +/// Swap to a different mode... if we have one +/mob/living/basic/guardian/proc/toggle_modes() + to_chat(src, span_bolddanger("You don't have another mode!")) + + +/// Turn an internal light on or off. +/mob/living/basic/guardian/proc/toggle_light() + if (!light_on) + to_chat(src, span_notice("You activate your light.")) + set_light_on(TRUE) + else + to_chat(src, span_notice("You deactivate your light.")) + set_light_on(FALSE) + + +/// Prints what type of guardian we are and what we can do. +/mob/living/basic/guardian/verb/check_type() + set name = "Check Guardian Type" + set category = "Guardian" + set desc = "Check what type you are." + to_chat(src, playstyle_string) + + +/// Speak with our boss at a distance +/mob/living/basic/guardian/proc/communicate() + if (isnull(summoner)) + return + var/sender_key = key + var/input = tgui_input_text(src, "Enter a message to tell your summoner", "Guardian") + if (sender_key != key || !input) //guardian got reset, or did not enter anything + return + + var/preliminary_message = span_boldholoparasite("[input]") //apply basic color/bolding + var/my_message = "[span_bolditalic(src.name)]: [preliminary_message]" //add source, color source with the guardian's color + + to_chat(summoner, "[my_message]") + var/list/guardians = summoner.get_all_linked_holoparasites() + for(var/guardian in guardians) + to_chat(guardian, "[my_message]") + for(var/dead_mob in GLOB.dead_mob_list) + var/link = FOLLOW_LINK(dead_mob, src) + to_chat(dead_mob, "[link] [my_message]") + + src.log_talk(input, LOG_SAY, tag="guardian") + + +/// Speak with your guardian(s) at a distance. +/datum/action/cooldown/mob_cooldown/guardian_comms + name = "Guardian Communication" + desc = "Communicate telepathically with your guardian." + button_icon = 'icons/hud/guardian.dmi' + button_icon_state = "communicate" + background_icon = 'icons/hud/guardian.dmi' + background_icon_state = "base" + click_to_activate = FALSE + cooldown_time = 0 SECONDS + melee_cooldown_time = 0 + shared_cooldown = NONE + +/datum/action/cooldown/mob_cooldown/guardian_comms/Activate(atom/target) + StartCooldown(360 SECONDS) + var/input = tgui_input_text(owner, "Enter a message to tell your guardian", "Message") + StartCooldown() + if (!input) + return FALSE + + var/preliminary_message = span_boldholoparasite("[input]") //apply basic color/bolding + var/my_message = span_boldholoparasite("[owner]: [preliminary_message]") //add source, color source with default grey... + + to_chat(owner, "[my_message]") + var/mob/living/living_owner = owner + var/list/guardians = living_owner.get_all_linked_holoparasites() + for(var/mob/living/basic/guardian/guardian as anything in guardians) + to_chat(guardian, "[span_bolditalic(owner.real_name)]: [preliminary_message]" ) + for(var/dead_mob in GLOB.dead_mob_list) + var/link = FOLLOW_LINK(dead_mob, owner) + to_chat(dead_mob, "[link] [my_message]") + owner.log_talk(input, LOG_SAY, tag="guardian") + + return TRUE + + +/// Tell your slacking or distracted guardian to come home. +/datum/action/cooldown/mob_cooldown/recall_guardian + name = "Recall Guardian" + desc = "Forcibly recall your guardian." + button_icon = 'icons/hud/guardian.dmi' + button_icon_state = "recall" + background_icon = 'icons/hud/guardian.dmi' + background_icon_state = "base" + click_to_activate = FALSE + cooldown_time = 0 SECONDS + melee_cooldown_time = 0 + shared_cooldown = NONE + +/datum/action/cooldown/mob_cooldown/recall_guardian/Activate(atom/target) + var/mob/living/living_owner = owner + var/list/guardians = living_owner.get_all_linked_holoparasites() + for(var/mob/living/basic/guardian/guardian in guardians) + guardian.recall() + StartCooldown() + return TRUE + +/// Replace an annoying griefer you were paired up to with a different but probably no less annoying player. +/datum/action/cooldown/mob_cooldown/replace_guardian + name = "Reset Guardian Consciousness" + desc = "Replaces the mind of your guardian with that of a different ghost." + button_icon = 'icons/mob/simple/mob.dmi' + button_icon_state = "ghost" + background_icon = 'icons/hud/guardian.dmi' + background_icon_state = "base" + click_to_activate = FALSE + cooldown_time = 5 SECONDS + melee_cooldown_time = 0 + shared_cooldown = NONE + +/datum/action/cooldown/mob_cooldown/replace_guardian/Activate(atom/target) + StartCooldown(5 MINUTES) + + var/mob/living/living_owner = owner + var/list/guardians = living_owner.get_all_linked_holoparasites() + for(var/mob/living/basic/guardian/resetting_guardian as anything in guardians) + if (!COOLDOWN_FINISHED(resetting_guardian, resetting_cooldown)) + guardians -= resetting_guardian //clear out guardians that are already reset + + if (!length(guardians)) + to_chat(owner, span_holoparasite("You cannot reset [length(guardians) > 1 ? "any of your guardians":"your guardian"] yet.")) + StartCooldown() + return FALSE + + var/mob/living/basic/guardian/chosen_guardian = tgui_input_list(owner, "Pick the guardian you wish to reset", "Guardian Reset", sort_names(guardians)) + if (isnull(chosen_guardian)) + to_chat(owner, span_holoparasite("You decide not to reset [length(guardians) > 1 ? "any of your guardians":"your guardian"].")) + StartCooldown() + return FALSE + + to_chat(owner, span_holoparasite("You attempt to reset [span_bold(chosen_guardian.real_name)]'s personality...")) + var/list/mob/dead/observer/ghost_candidates = poll_ghost_candidates("Do you want to play as [owner.real_name]'s [chosen_guardian.theme.name]?", ROLE_PAI, FALSE, 100) + if (!LAZYLEN(ghost_candidates)) + to_chat(owner, span_holoparasite("Your attempt to reset the personality of \ + [span_bold(chosen_guardian.real_name)] appears to have failed... \ + Looks like you're stuck with it for now.")) + StartCooldown() + return FALSE + + var/mob/dead/observer/candidate = pick(ghost_candidates) + to_chat(chosen_guardian, span_holoparasite("Your user reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.")) + to_chat(owner, span_boldholoparasite("The personality of [chosen_guardian.theme.name] has been successfully reset.")) + message_admins("[key_name_admin(candidate)] has taken control of ([ADMIN_LOOKUPFLW(chosen_guardian)])") + chosen_guardian.ghostize(FALSE) + chosen_guardian.key = candidate.key + COOLDOWN_START(chosen_guardian, resetting_cooldown, 5 MINUTES) + chosen_guardian.guardian_rename() //give it a new color and name, to show it's a new person + chosen_guardian.guardian_recolour() + StartCooldown() + return TRUE diff --git a/code/modules/mob/living/basic/health_adjustment.dm b/code/modules/mob/living/basic/health_adjustment.dm index 6c206b511853..58640b7e99e3 100644 --- a/code/modules/mob/living/basic/health_adjustment.dm +++ b/code/modules/mob/living/basic/health_adjustment.dm @@ -1,5 +1,5 @@ /** - * Adjusts the health of a simple mob by a set amount and wakes AI if its idle to react + * Adjusts the health of a simple mob by a set amount * * Arguments: * * amount The amount that will be used to adjust the mob's health @@ -8,41 +8,55 @@ */ /mob/living/basic/proc/adjust_health(amount, updating_health = TRUE, forced = FALSE) . = FALSE - if(forced || !(status_flags & GODMODE)) - bruteloss = round(clamp(bruteloss + amount, 0, maxHealth * 2), DAMAGE_PRECISION) - if(updating_health) - updatehealth() - . = amount - if(ckey || stat) - return - //if(AIStatus == AI_IDLE) - // toggle_ai(AI_ON) + if(!forced && (status_flags & GODMODE)) + return 0 + . = bruteloss // bruteloss value before applying damage + bruteloss = round(clamp(bruteloss + amount, 0, maxHealth * 2), DAMAGE_PRECISION) + if(updating_health) + updatehealth() + return . - bruteloss + +/mob/living/basic/get_damage_mod(damage_type) + var/modifier = ..() + if (damage_type in damage_coeff) + return modifier * damage_coeff[damage_type] + return modifier /mob/living/basic/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + if(!can_adjust_brute_loss(amount, forced, required_bodytype)) + return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[BRUTE]) . = adjust_health(amount * damage_coeff[BRUTE] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/basic/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + if(!can_adjust_fire_loss(amount, forced, required_bodytype)) + return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[BURN]) . = adjust_health(amount * damage_coeff[BURN] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/basic/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype, required_respiration_type) + if(!can_adjust_oxy_loss(amount, forced, required_biotype, required_respiration_type)) + return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[OXY]) . = adjust_health(amount * damage_coeff[OXY] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/basic/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) + if(!can_adjust_tox_loss(amount, forced, required_biotype)) + return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[TOX]) . = adjust_health(amount * damage_coeff[TOX] * CONFIG_GET(number/damage_multiplier), updating_health, forced) -/mob/living/basic/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE) +/mob/living/basic/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) + if(!can_adjust_clone_loss(amount, forced, required_biotype)) + return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[CLONE]) diff --git a/code/modules/mob/living/basic/heretic/_heretic_summon.dm b/code/modules/mob/living/basic/heretic/_heretic_summon.dm new file mode 100644 index 000000000000..50280d0c0428 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/_heretic_summon.dm @@ -0,0 +1,36 @@ +/mob/living/basic/heretic_summon + name = "Eldritch Demon" + real_name = "Eldritch Demon" + desc = "A horror from beyond this realm, summoned by bad code." + icon = 'icons/mob/nonhuman-player/eldritch_mobs.dmi' + faction = list(FACTION_HERETIC) + basic_mob_flags = DEL_ON_DEATH + gender = NEUTER + mob_biotypes = NONE + + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + speed = 0 + melee_attack_cooldown = CLICK_CD_MELEE + + attack_sound = 'sound/weapons/punch1.ogg' + response_help_continuous = "thinks better of touching" + response_help_simple = "think better of touching" + response_disarm_continuous = "flails at" + response_disarm_simple = "flail at" + response_harm_continuous = "rips" + response_harm_simple = "tear" + death_message = "implodes into itself." + + unsuitable_atmos_damage = 0 + unsuitable_cold_damage = 0 + unsuitable_heat_damage = 0 + + istate = ISTATE_HARM + ai_controller = null + speak_emote = list("screams") + gold_core_spawnable = NO_SPAWN + +/mob/living/basic/heretic_summon/Initialize(mapload) + . = ..() + AddElement(/datum/element/death_drops, string_list(list(/obj/effect/gibspawner/generic))) diff --git a/code/modules/mob/living/basic/heretic/ash_spirit.dm b/code/modules/mob/living/basic/heretic/ash_spirit.dm new file mode 100644 index 000000000000..b2d4d8b4d294 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/ash_spirit.dm @@ -0,0 +1,25 @@ +/** + * Player-only mob which is fast, can jaunt a short distance, and is dangerous at close range + */ +/mob/living/basic/heretic_summon/ash_spirit + name = "Ash Spirit" + real_name = "Ashy" + desc = "A manifestation of ash, trailing a perpetual cloud of short-lived cinders." + icon_state = "ash_walker" + icon_living = "ash_walker" + maxHealth = 75 + health = 75 + melee_damage_lower = 15 + melee_damage_upper = 20 + sight = SEE_TURFS + +/mob/living/basic/heretic_summon/ash_spirit/Initialize(mapload) + . = ..() + var/static/list/actions_to_add = list( + /datum/action/cooldown/spell/fire_sworn, + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash, + /datum/action/cooldown/spell/pointed/cleave, + ) + for (var/action in actions_to_add) + var/datum/action/cooldown/new_action = new action(src) + new_action.Grant(src) diff --git a/code/modules/mob/living/basic/heretic/flesh_stalker.dm b/code/modules/mob/living/basic/heretic/flesh_stalker.dm new file mode 100644 index 000000000000..d12735206674 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/flesh_stalker.dm @@ -0,0 +1,46 @@ +/// Durable ambush mob with an EMP ability +/mob/living/basic/heretic_summon/stalker + name = "Flesh Stalker" + real_name = "Flesh Stalker" + desc = "An abomination cobbled together from varied remains. Its appearance changes slightly every time you blink." + icon_state = "stalker" + icon_living = "stalker" + maxHealth = 150 + health = 150 + melee_damage_lower = 15 + melee_damage_upper = 20 + sight = SEE_MOBS + ai_controller = /datum/ai_controller/basic_controller/stalker + /// Associative list of action types we would like to have, and what blackboard key (if any) to put it in + var/static/list/actions_to_add = list( + /datum/action/cooldown/spell/emp/eldritch = BB_GENERIC_ACTION, + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash = null, + /datum/action/cooldown/spell/shapeshift/eldritch = BB_SHAPESHIFT_ACTION, + ) + +/mob/living/basic/heretic_summon/stalker/Initialize(mapload) + . = ..() + AddComponent(/datum/component/ai_target_timer) + for (var/action_type in actions_to_add) + var/datum/action/new_action = new action_type(src) + new_action.Grant(src) + var/blackboard_key = actions_to_add[action_type] + if (!isnull(blackboard_key)) + ai_controller?.set_blackboard_key(blackboard_key, new_action) + +/// Changes shape and lies in wait when it has no target, uses EMP and attacks once it does +/datum/ai_controller/basic_controller/stalker + ai_traits = CAN_ACT_IN_STASIS + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/shapechange_ambush, + /datum/ai_planning_subtree/use_mob_ability, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) diff --git a/code/modules/mob/living/basic/heretic/flesh_worm.dm b/code/modules/mob/living/basic/heretic/flesh_worm.dm new file mode 100644 index 000000000000..6c61614b9096 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/flesh_worm.dm @@ -0,0 +1,136 @@ +/// Armsy starts to look a bit funky if he's shorter than this +#define MINIMUM_ARMSY_LENGTH 2 + +// What if we took a linked list... But made it a mob? +/// The "Terror of the Night" / Armsy, a large worm made of multiple bodyparts that occupies multiple tiles +/mob/living/basic/heretic_summon/armsy + name = "Lord of the Night" + real_name = "Master of Decay" + desc = "An abomination made from dozens and dozens of severed and malformed limbs grasping onto each other." + icon_state = "armsy_start" + icon_living = "armsy_start" + base_icon_state = "armsy" + maxHealth = 400 + health = 400 + melee_damage_lower = 30 + melee_damage_upper = 50 + obj_damage = 200 + move_force = MOVE_FORCE_OVERPOWERING + move_resist = MOVE_FORCE_OVERPOWERING + pull_force = MOVE_FORCE_OVERPOWERING + mob_size = MOB_SIZE_HUGE + sentience_type = SENTIENCE_BOSS + mob_biotypes = MOB_ORGANIC + ///Previous segment in the chain, we hold onto this purely to keep track of how long we currently are and to attach new growth to the back + var/mob/living/basic/heretic_summon/armsy/back + ///How many arms do we have to eat to expand? + var/stacks_to_grow = 5 + ///Currently eaten arms + var/current_stacks = 0 +/* + * Arguments + * * spawn_bodyparts - whether we spawn additional armsy bodies until we reach length. + * * worm_length - the length of the worm we're creating. Below 2 doesn't work very well. + */ +/mob/living/basic/heretic_summon/armsy/Initialize(mapload, spawn_bodyparts = TRUE, worm_length = 6) + . = ..() + AddElement(/datum/element/wall_smasher, ENVIRONMENT_SMASH_RWALLS) + AddElement(\ + /datum/element/amputating_limbs,\ + surgery_time = 0 SECONDS,\ + surgery_verb = "tears",\ + minimum_stat = CONSCIOUS,\ + snip_chance = 10,\ + target_zones = GLOB.arm_zones,\ + ) + AddComponent(\ + /datum/component/blood_walk, \ + blood_type = /obj/effect/decal/cleanable/blood/tracks, \ + target_dir_change = TRUE,\ + ) + + if(spawn_bodyparts) + build_tail(worm_length) + +// We are a vessel of otherworldly destruction, we bring our gravity with us +/mob/living/basic/heretic_summon/armsy/has_gravity(turf/gravity_turf) + return TRUE + +/mob/living/basic/heretic_summon/armsy/can_be_pulled() + return FALSE // The component does this but not on the head. We don't want the head to be pulled either. + +/mob/living/basic/heretic_summon/armsy/proc/build_tail(worm_length) + worm_length = max(worm_length, MINIMUM_ARMSY_LENGTH) + // Sets the hp of the head to be exactly the (length * hp), so the head is de facto the hardest to destroy. + maxHealth = worm_length * maxHealth + health = maxHealth + + AddComponent(/datum/component/mob_chain, vary_icon_state = TRUE) // We're the front + + var/mob/living/basic/heretic_summon/armsy/prev = src + for(var/i in 1 to worm_length) + prev = new_segment(behind = prev) + update_appearance(UPDATE_ICON_STATE) + +/// Grows a new segment behind the passed mob +/mob/living/basic/heretic_summon/armsy/proc/new_segment(mob/living/basic/heretic_summon/armsy/behind) + var/mob/living/segment = new type(drop_location(), FALSE) + segment.AddComponent(/datum/component/mob_chain, front = behind, vary_icon_state = TRUE) + behind.register_behind(segment) + return segment + +/// Record that we got another guy on our ass +/mob/living/basic/heretic_summon/armsy/proc/register_behind(mob/living/tail) + if(!isnull(back)) // Shouldn't happen but just in case + UnregisterSignal(back, COMSIG_PARENT_QDELETING) + back = tail + update_appearance(UPDATE_ICON_STATE) + if(!isnull(back)) + RegisterSignal(back, COMSIG_PARENT_QDELETING, PROC_REF(tail_deleted)) + +/// When our tail is gone stop holding a reference to it +/mob/living/basic/heretic_summon/armsy/proc/tail_deleted() + SIGNAL_HANDLER + register_behind(null) + +/mob/living/basic/heretic_summon/armsy/melee_attack(atom/target, list/modifiers, ignore_cooldown) + if(!istype(target, /obj/item/bodypart/arm)) + return ..() + visible_message(span_warning("[src] devours [target]!")) + playsound(src, 'sound/magic/demon_consume.ogg', 50, TRUE) + qdel(target) + on_arm_eaten() + +/* + * Handle healing our chain. + * Eating arms off the ground heals us, and if we eat enough arms while above a certain health threshold we get longer! + */ +/mob/living/basic/heretic_summon/armsy/proc/on_arm_eaten() + if(!isnull(back)) + back.on_arm_eaten() + return + + adjustBruteLoss(-maxHealth * 0.5, FALSE) + adjustFireLoss(-maxHealth * 0.5, FALSE) + + if(health < maxHealth * 0.8) + return + + current_stacks++ + if(current_stacks < stacks_to_grow) + return + + visible_message(span_boldwarning("[src] flexes and expands!")) + current_stacks = 0 + new_segment(behind = src) + +/* + * Recursively get the length of our chain. + */ +/mob/living/basic/heretic_summon/armsy/proc/get_length() + . = 1 + if(isnull(back)) + return + . += back.get_length() + +#undef MINIMUM_ARMSY_LENGTH diff --git a/code/modules/mob/living/basic/heretic/heretic_summon.dm b/code/modules/mob/living/basic/heretic/heretic_summon.dm new file mode 100644 index 000000000000..d55b0a21ddf4 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/heretic_summon.dm @@ -0,0 +1,32 @@ +/mob/living/basic/heretic_summon + name = "Eldritch Demon" + real_name = "Eldritch Demon" + desc = "A horror from beyond this realm, summoned by bad code." + icon = 'icons/mob/nonhuman-player/eldritch_mobs.dmi' + faction = list(FACTION_HERETIC) + basic_mob_flags = DEL_ON_DEATH + gender = NEUTER + mob_biotypes = NONE + + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + speed = 0 + melee_attack_cooldown = CLICK_CD_MELEE + + attack_sound = 'sound/weapons/punch1.ogg' + response_help_continuous = "thinks better of touching" + response_help_simple = "think better of touching" + response_disarm_continuous = "flails at" + response_disarm_simple = "flail at" + response_harm_continuous = "rips" + response_harm_simple = "tear" + death_message = "implodes into itself." + + istate = ISTATE_HARM + ai_controller = null + speak_emote = list("screams") + gold_core_spawnable = NO_SPAWN + +/mob/living/basic/heretic_summon/Initialize(mapload) + . = ..() + AddElement(/datum/element/death_drops, string_list(list(/obj/effect/gibspawner/generic))) diff --git a/code/modules/antagonists/heretic/mobs/maid_in_mirror.dm b/code/modules/mob/living/basic/heretic/maid_in_the_mirror.dm similarity index 60% rename from code/modules/antagonists/heretic/mobs/maid_in_mirror.dm rename to code/modules/mob/living/basic/heretic/maid_in_the_mirror.dm index b53bbe147d3d..edc5148b3ccc 100644 --- a/code/modules/antagonists/heretic/mobs/maid_in_mirror.dm +++ b/code/modules/mob/living/basic/heretic/maid_in_the_mirror.dm @@ -1,11 +1,11 @@ -// A summon which floats around the station incorporeally, and can appear in any mirror -/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror +/// Scout and assassin who can appear and disappear from glass surfaces. Damaged by being examined. +/mob/living/basic/heretic_summon/maid_in_the_mirror name = "Maid in the Mirror" real_name = "Maid in the Mirror" desc = "A floating and flowing wisp of chilled air. Glancing at it causes it to shimmer slightly." icon = 'icons/mob/simple/mob.dmi' icon_state = "stand" - icon_living = "stand" // Placeholder sprite + icon_living = "stand" // Placeholder sprite... still speak_emote = list("whispers") movement_type = FLOATING status_flags = CANSTUN | CANPUSH @@ -16,36 +16,34 @@ melee_damage_upper = 16 sight = SEE_MOBS | SEE_OBJS | SEE_TURFS death_message = "shatters and vanishes, releasing a gust of cold air." - loot = list( - /obj/item/shard, + /// Whether we take damage when someone looks at us + var/harmed_by_examine = TRUE + /// How often being examined by a specific mob can hurt us + var/recent_examine_damage_cooldown = 10 SECONDS + /// A list of REFs to people who recently examined us + var/list/recent_examiner_refs = list() + +/mob/living/basic/heretic_summon/Initialize(mapload) + . = ..() + var/static/list/loot = list( /obj/effect/decal/cleanable/ash, /obj/item/clothing/suit/armor/vest, /obj/item/organ/internal/lungs, + /obj/item/shard, ) - actions_to_add = list(/datum/action/cooldown/spell/jaunt/mirror_walk) + AddElement(/datum/element/death_drops, loot) + var/datum/action/cooldown/spell/jaunt/mirror_walk/jaunt = new (src) + jaunt.Grant(src) - /// Whether we take damage when we're examined - var/weak_on_examine = TRUE - /// The cooldown after being examined that the same mob cannot trigger it again - var/recent_examine_damage_cooldown = 10 SECONDS - /// A list of REFs to people who recently examined us - var/list/recent_examiner_refs = list() - -/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror/death(gibbed) +/mob/living/basic/heretic_summon/maid_in_the_mirror/death(gibbed) var/turf/death_turf = get_turf(src) - death_turf.TakeTemperature(-40) + death_turf.TakeTemperature(-40) // Spooky return ..() // Examining them will harm them, on a cooldown. -/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror/examine(mob/user) +/mob/living/basic/heretic_summon/maid_in_the_mirror/examine(mob/user) . = ..() - if(!weak_on_examine) - return - - if(!isliving(user) || user.stat == DEAD) - return - - if(IS_HERETIC_OR_MONSTER(user) || user == src) + if(!harmed_by_examine || user == src || user.stat == DEAD || !isliving(user) || IS_HERETIC_OR_MONSTER(user)) return var/user_ref = REF(user) @@ -62,7 +60,9 @@ recent_examiner_refs += user_ref apply_damage(maxHealth * 0.1) // We take 10% of our health as damage upon being examined playsound(src, 'sound/effects/ghost2.ogg', 40, TRUE) - addtimer(CALLBACK(src, PROC_REF(clear_recent_examiner), user_ref), recent_examine_damage_cooldown) + addtimer(CALLBACK(src, PROC_REF(clear_recent_examiner), user_ref), recent_examine_damage_cooldown, TIMER_DELETE_ME) + animate(src, alpha = 120, time = 0.5 SECONDS, easing = ELASTIC_EASING, loop = 2, flags = ANIMATION_PARALLEL) + animate(alpha = 255, time = 0.5 SECONDS, easing = ELASTIC_EASING) // If we're examined on low enough health we die straight up else @@ -73,7 +73,7 @@ death() -/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror/proc/clear_recent_examiner(mob_ref) +/mob/living/basic/heretic_summon/maid_in_the_mirror/proc/clear_recent_examiner(mob_ref) if(!(mob_ref in recent_examiner_refs)) return diff --git a/code/modules/mob/living/basic/heretic/raw_prophet.dm b/code/modules/mob/living/basic/heretic/raw_prophet.dm new file mode 100644 index 000000000000..967a7dacb605 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/raw_prophet.dm @@ -0,0 +1,81 @@ +/** + * A funny little rolling guy who is great at scouting. + * It can see through walls, jaunt, and create a psychic network to report its findings. + * It can blind people to make a getaway, but also get stronger if it attacks the same target consecutively. + */ +/mob/living/basic/heretic_summon/raw_prophet + name = "Raw Prophet" + real_name = "Raw Prophet" + desc = "An abomination stitched together from a few severed arms and one swollen, orphaned eye." + icon_state = "raw_prophet" + icon_living = "raw_prophet" + status_flags = CANPUSH + melee_damage_lower = 5 + melee_damage_upper = 10 + maxHealth = 65 + health = 65 + sight = SEE_MOBS|SEE_OBJS|SEE_TURFS + /// Some ability we use to make people go blind + var/blind_action_type = /datum/action/cooldown/spell/pointed/blind/eldritch + +/mob/living/basic/heretic_summon/raw_prophet/Initialize(mapload) + . = ..() + AddElement(/datum/element/wheel) + var/static/list/body_parts = list(/obj/effect/gibspawner/human, /obj/item/bodypart/arm/left, /obj/item/organ/internal/eyes) + AddElement(/datum/element/death_drops, body_parts) + AddComponent(/datum/component/focused_attacker) + // We don't use these for AI so we can just repeat the same adding process + var/static/list/add_abilities = list( + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/long, + /datum/action/cooldown/spell/list_target/telepathy/eldritch, + /datum/action/innate/expand_sight, + ) + for (var/ability_type in add_abilities) + var/datum/action/new_action = new ability_type(src) + new_action.Grant(src) + + var/datum/action/cooldown/blind = new blind_action_type(src) + blind.Grant(src) + ai_controller?.set_blackboard_key(BB_TARGETED_ACTION, blind) + +/* + * Callback for the mind_linker component. + * Stuns people who are ejected from the network. + */ +/mob/living/basic/heretic_summon/raw_prophet/proc/after_unlink(mob/living/unlinked_mob) + if(QDELETED(unlinked_mob) || unlinked_mob.stat == DEAD) + return + + INVOKE_ASYNC(unlinked_mob, TYPE_PROC_REF(/mob, emote), "scream") + unlinked_mob.AdjustParalyzed(0.5 SECONDS) //micro stun + +/mob/living/basic/heretic_summon/raw_prophet/melee_attack(atom/target, list/modifiers, ignore_cooldown) + SpinAnimation(speed = 5, loops = 1) + if (target == src) + return + return ..() + +/// Variant raw prophet used by eldritch transformation with more base attack power +/mob/living/basic/heretic_summon/raw_prophet/ascended + melee_damage_lower = 15 + melee_damage_upper = 20 + +/// NPC variant with a less bullshit ability +/mob/living/basic/heretic_summon/raw_prophet/ruins + ai_controller = /datum/ai_controller/basic_controller/raw_prophet + blind_action_type = /datum/action/cooldown/mob_cooldown/watcher_gaze + +/// Walk and attack people, blind them when we can +/datum/ai_controller/basic_controller/raw_prophet + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) diff --git a/code/modules/mob/living/basic/heretic/rust_walker.dm b/code/modules/mob/living/basic/heretic/rust_walker.dm new file mode 100644 index 000000000000..c826fead48ca --- /dev/null +++ b/code/modules/mob/living/basic/heretic/rust_walker.dm @@ -0,0 +1,87 @@ +/// Pretty simple mob which creates areas of rust and has a rust-creating projectile spell +/mob/living/basic/heretic_summon/rust_walker + name = "Rust Walker" + real_name = "Rusty" + desc = "A grinding, clanking construct which leaches life from its surroundings with every armoured step." + icon_state = "rust_walker_s" + base_icon_state = "rust_walker" + icon_living = "rust_walker_s" + maxHealth = 75 + health = 75 + melee_damage_lower = 15 + melee_damage_upper = 20 + sight = SEE_TURFS + speed = 1 + ai_controller = /datum/ai_controller/basic_controller/rust_walker + +/mob/living/basic/heretic_summon/rust_walker/Initialize(mapload) + . = ..() + AddElement(/datum/element/footstep, FOOTSTEP_MOB_RUST) + var/datum/action/cooldown/spell/aoe/rust_conversion/small/conversion = new(src) + conversion.Grant(src) + ai_controller?.set_blackboard_key(BB_GENERIC_ACTION, conversion) + + var/datum/action/cooldown/spell/basic_projectile/rust_wave/short/wave = new(src) + wave.Grant(src) + ai_controller?.set_blackboard_key(BB_TARGETED_ACTION, wave) + +/mob/living/basic/heretic_summon/rust_walker/setDir(newdir) + . = ..() + update_appearance(UPDATE_ICON_STATE) + +/mob/living/basic/heretic_summon/rust_walker/update_icon_state() + . = ..() + if(stat == DEAD) // We usually delete on death but just in case + return + if(dir & NORTH) + icon_state = "[base_icon_state]_n" + else if(dir & SOUTH) + icon_state = "[base_icon_state]_s" + icon_living = icon_state + +/mob/living/basic/heretic_summon/rust_walker/Life(seconds_per_tick = SSMOBS_DT, times_fired) + if(stat == DEAD) + return ..() + var/turf/our_turf = get_turf(src) + if(HAS_TRAIT(our_turf, TRAIT_RUSTY)) + adjustBruteLoss(-3 * seconds_per_tick) + + return ..() + +/// Converts unconverted terrain, sprays pocket sand around +/datum/ai_controller/basic_controller/rust_walker + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk/rust + planning_subtrees = list( + /datum/ai_planning_subtree/use_mob_ability/rust_walker, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Moves a lot if healthy and on rust (to find more tiles to rust) or unhealthy and not on rust (to find healing rust) +/// Still moving in random directions though we're not really seeking it out +/datum/idle_behavior/idle_random_walk/rust + +/datum/idle_behavior/idle_random_walk/rust/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/our_mob = controller.pawn + var/turf/our_turf = get_turf(our_mob) + if (HAS_TRAIT(our_turf, TRAIT_RUSTY)) + walk_chance = (our_mob.health < our_mob.maxHealth) ? 10 : 50 + else + walk_chance = (our_mob.health < our_mob.maxHealth) ? 50 : 10 + return ..() + +/// Use if we're not stood on rust right now +/datum/ai_planning_subtree/use_mob_ability/rust_walker + +/datum/ai_planning_subtree/use_mob_ability/rust_walker/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/turf/our_turf = get_turf(controller.pawn) + if (HAS_TRAIT(our_turf, TRAIT_RUSTY)) + return + return ..() diff --git a/code/modules/mob/living/basic/heretic/star_gazer.dm b/code/modules/mob/living/basic/heretic/star_gazer.dm index 19c29d0eeb18..259d17d9d7d8 100644 --- a/code/modules/mob/living/basic/heretic/star_gazer.dm +++ b/code/modules/mob/living/basic/heretic/star_gazer.dm @@ -24,6 +24,7 @@ attack_verb_simple = "ravage" attack_vis_effect = ATTACK_EFFECT_SLASH attack_sound = 'sound/weapons/bladeslice.ogg' + melee_attack_cooldown = 0.6 SECONDS speak_emote = list("growls") damage_coeff = list(BRUTE = 1, BURN = 0.5, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) death_sound = 'sound/magic/cosmic_expansion.ogg' @@ -62,10 +63,29 @@ ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT) set_light(4, l_color = "#dcaa5b") +// Star gazer attacks everything around itself applies a spooky mark +/mob/living/basic/heretic_summon/star_gazer/melee_attack(mob/living/target, list/modifiers, ignore_cooldown) + . = ..() + if (!. || !isliving(target)) + return + + target.apply_status_effect(/datum/status_effect/star_mark) + target.apply_damage(damage = 5, damagetype = CLONE) + var/datum/targeting_strategy/target_confirmer = GET_TARGETING_STRATEGY(ai_controller.blackboard[BB_TARGETING_STRATEGY]) + for(var/mob/living/nearby_mob in range(1, src)) + if(target == nearby_mob || !target_confirmer?.can_attack(src, nearby_mob)) + continue + nearby_mob.apply_status_effect(/datum/status_effect/star_mark) + nearby_mob.apply_damage(10) + to_chat(nearby_mob, span_userdanger("\The [src] [attack_verb_continuous] you!")) + do_attack_animation(nearby_mob, ATTACK_EFFECT_SLASH) + log_combat(src, nearby_mob, "slashed") + /datum/ai_controller/basic_controller/star_gazer blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/star_gazer(), - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends/attack_closed_turfs(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends/attack_closed_turfs, ) ai_movement = /datum/ai_movement/basic_avoidance @@ -75,39 +95,9 @@ /datum/ai_planning_subtree/pet_planning, /datum/ai_planning_subtree/simple_find_target, /datum/ai_planning_subtree/attack_obstacle_in_path/star_gazer, - /datum/ai_planning_subtree/basic_melee_attack_subtree/star_gazer, + /datum/ai_planning_subtree/basic_melee_attack_subtree, ) -/datum/targetting_datum/basic/star_gazer - stat_attack = HARD_CRIT - -/datum/ai_planning_subtree/basic_melee_attack_subtree/star_gazer - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/star_gazer - -/datum/ai_behavior/basic_melee_attack/star_gazer - action_cooldown = 0.6 SECONDS - -/datum/ai_behavior/basic_melee_attack/star_gazer/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - var/mob/living/living_pawn = controller.pawn - - if(!isliving(target)) - return - var/mob/living/living_target = target - living_target.apply_status_effect(/datum/status_effect/star_mark) - living_target.apply_damage_type(damage = 5, damagetype = CLONE) - if(living_target.pulledby != living_pawn) - if(living_pawn.Adjacent(living_target) && isturf(living_target.loc) && living_target.stat == SOFT_CRIT) - living_target.grabbedby(living_pawn) - for(var/mob/living/nearby_mob in range(1, living_pawn)) - if(nearby_mob.stat == DEAD || living_target == nearby_mob || faction_check(nearby_mob.faction, list(FACTION_HERETIC))) - continue - nearby_mob.apply_status_effect(/datum/status_effect/star_mark) - nearby_mob.adjustBruteLoss(10) - living_pawn.do_attack_animation(nearby_mob, ATTACK_EFFECT_SLASH) - log_combat(living_pawn, nearby_mob, "slashed") - /datum/ai_planning_subtree/attack_obstacle_in_path/star_gazer attack_behaviour = /datum/ai_behavior/attack_obstructions/star_gazer @@ -119,9 +109,9 @@ can_attack_turfs = TRUE can_attack_dense_objects = TRUE -/datum/pet_command/point_targetting/attack/star_gazer +/datum/pet_command/point_targeting/attack/star_gazer speech_commands = list("attack", "sic", "kill", "slash them") command_feedback = "stares!" pointed_reaction = "stares intensely!" refuse_reaction = "..." - attack_behaviour = /datum/ai_behavior/basic_melee_attack/star_gazer + attack_behaviour = /datum/ai_behavior/basic_melee_attack diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm new file mode 100644 index 000000000000..960f875365bf --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm @@ -0,0 +1,89 @@ +/mob/living/basic/mining/ice_demon + name = "demonic watcher" + desc = "A creature formed entirely out of ice, bluespace energy emanates from inside of it." + icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + icon_state = "ice_demon" + icon_living = "ice_demon" + icon_gib = "syndicate_gib" + mob_biotypes = MOB_ORGANIC|MOB_BEAST + mouse_opacity = MOUSE_OPACITY_ICON + basic_mob_flags = DEL_ON_DEATH + speed = 2 + maxHealth = 150 + health = 150 + obj_damage = 40 + melee_damage_lower = 15 + melee_damage_upper = 15 + attack_verb_continuous = "slices" + attack_verb_simple = "slice" + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH + move_force = MOVE_FORCE_VERY_STRONG + move_resist = MOVE_FORCE_VERY_STRONG + pull_force = MOVE_FORCE_VERY_STRONG + crusher_loot = /obj/item/crusher_trophy/ice_demon_cube + ai_controller = /datum/ai_controller/basic_controller/ice_demon + death_message = "fades as the energies that tied it to this world dissipate." + death_sound = 'sound/magic/demon_dies.ogg' + +/mob/living/basic/mining/ice_demon/Initialize(mapload) + . = ..() + var/datum/action/cooldown/mob_cooldown/slippery_ice_floors/ice_floor = new(src) + ice_floor.Grant(src) + ai_controller.set_blackboard_key(BB_DEMON_SLIP_ABILITY, ice_floor) + var/datum/action/cooldown/mob_cooldown/ice_demon_teleport/demon_teleport = new(src) + demon_teleport.Grant(src) + ai_controller.set_blackboard_key(BB_DEMON_TELEPORT_ABILITY, demon_teleport) + var/datum/action/cooldown/spell/conjure/create_afterimages/afterimage = new(src) + afterimage.Grant(src) + ai_controller.set_blackboard_key(BB_DEMON_CLONE_ABILITY, afterimage) + AddComponent(\ + /datum/component/ranged_attacks,\ + projectile_type = /obj/projectile/temp/ice_demon,\ + projectile_sound = 'sound/weapons/pierce.ogg',\ + ) + var/static/list/death_loot = list(/obj/item/stack/ore/bluespace_crystal = 3) + AddElement(/datum/element/death_drops, death_loot) + AddElement(/datum/element/simple_flying) + +/mob/living/basic/mining/ice_demon/death(gibbed) + if(prob(5)) + new /obj/item/raw_anomaly_core/bluespace(loc) + return ..() + +/mob/living/basic/mining/demon_afterimage + name = "afterimage demonic watcher" + desc = "Is this some sort of illusion?" + icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + icon_state = "ice_demon" + icon_living = "ice_demon" + icon_gib = "syndicate_gib" + mob_biotypes = MOB_ORGANIC|MOB_BEAST + mouse_opacity = MOUSE_OPACITY_ICON + basic_mob_flags = DEL_ON_DEATH + speed = 5 + maxHealth = 20 + health = 20 + melee_damage_lower = 5 + melee_damage_upper = 5 + attack_verb_continuous = "slices" + attack_verb_simple = "slice" + attack_sound = 'sound/weapons/bladeslice.ogg' + alpha = 80 + ai_controller = /datum/ai_controller/basic_controller/ice_demon/afterimage + ///how long do we exist for + var/existence_period = 15 SECONDS + +/mob/living/basic/mining/demon_afterimage/Initialize(mapload) + . = ..() + AddElement(/datum/element/simple_flying) + AddElement(/datum/element/temporary_atom, life_time = existence_period) + +///afterimage subtypes summoned by the crusher +/mob/living/basic/mining/demon_afterimage/crusher + speed = 2 + health = 60 + maxHealth = 60 + melee_damage_lower = 10 + melee_damage_upper = 10 + existence_period = 7 SECONDS diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm new file mode 100644 index 000000000000..a09478f9add7 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm @@ -0,0 +1,117 @@ +/obj/projectile/temp/ice_demon + name = "ice blast" + icon_state = "ice_2" + damage = 5 + damage_type = BURN + armor_flag = ENERGY + speed = 1 + pixel_speed_multiplier = 0.25 + temperature = -75 + +/datum/action/cooldown/mob_cooldown/ice_demon_teleport + name = "Bluespace Teleport" + desc = "Teleport towards a destination target!" + button_icon = 'icons/obj/ore.dmi' + button_icon_state = "bluespace_crystal" + cooldown_time = 3 SECONDS + melee_cooldown_time = 0 SECONDS + ///time delay before teleport + var/time_delay = 0.5 SECONDS + +/datum/action/cooldown/mob_cooldown/ice_demon_teleport/Activate(atom/target_atom) + if(isclosedturf(get_turf(target_atom))) + owner.balloon_alert(owner, "blocked!") + return FALSE + animate(owner, transform = matrix().Scale(0.8), time = time_delay, easing = SINE_EASING) + addtimer(CALLBACK(src, PROC_REF(teleport_to_turf), target_atom), time_delay) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/ice_demon_teleport/proc/teleport_to_turf(atom/target) + animate(owner, transform = matrix(), time = 0.5 SECONDS, easing = SINE_EASING) + do_teleport(teleatom = owner, destination = target, channel = TELEPORT_CHANNEL_BLUESPACE, forced = TRUE) + +/datum/action/cooldown/mob_cooldown/slippery_ice_floors + name = "Iced Floors" + desc = "Summon slippery ice floors all around!" + button_icon = 'icons/turf/floors/ice_turf.dmi' + button_icon_state = "ice_turf-6" + cooldown_time = 2 SECONDS + click_to_activate = FALSE + melee_cooldown_time = 0 SECONDS + ///perimeter we will spawn the iced floors on + var/radius = 1 + ///intervals we will spawn the ice floors in + var/spread_duration = 0.2 SECONDS + +/datum/action/cooldown/mob_cooldown/slippery_ice_floors/Activate(atom/target_atom) + for(var/i in 0 to radius) + var/list/list_of_turfs = border_diamond_range_turfs(owner, i) + addtimer(CALLBACK(src, PROC_REF(spawn_icy_floors), list_of_turfs), i * spread_duration) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/slippery_ice_floors/proc/spawn_icy_floors(list/list_of_turfs) + if(!length(list_of_turfs)) + return + for(var/turf/location in list_of_turfs) + if(isnull(location)) + continue + if(isclosedturf(location) || isspaceturf(location)) + continue + new /obj/effect/temp_visual/slippery_ice(location) + +/obj/effect/temp_visual/slippery_ice + name = "slippery acid" + icon = 'icons/turf/floors/ice_turf.dmi' + icon_state = "ice_turf-6" + layer = BELOW_MOB_LAYER + plane = GAME_PLANE + anchored = TRUE + duration = 3 SECONDS + alpha = 100 + /// how long does it take for the effect to phase in + var/phase_in_period = 2 SECONDS + +/obj/effect/temp_visual/slippery_ice/Initialize(mapload) + . = ..() + animate(src, alpha = 160, time = phase_in_period) + animate(alpha = 0, time = duration - phase_in_period) /// slowly fade out of existence + addtimer(CALLBACK(src, PROC_REF(add_slippery_component), phase_in_period)) //only become slippery after we phased in + +/obj/effect/temp_visual/slippery_ice/proc/add_slippery_component() + AddComponent(/datum/component/slippery, 2 SECONDS) + +/datum/action/cooldown/spell/conjure/create_afterimages + name = "Create After Images" + button_icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + button_icon_state = "ice_demon" + spell_requirements = NONE + cooldown_time = 1 MINUTES + summon_type = list(/mob/living/basic/mining/demon_afterimage) + summon_radius = 1 + summon_amount = 2 + ///max number of after images + var/max_afterimages = 2 + ///How many clones do we have summoned + var/number_of_afterimages = 0 + +/datum/action/cooldown/spell/conjure/create_afterimages/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(number_of_afterimages >= max_afterimages) + return FALSE + return TRUE + +/datum/action/cooldown/spell/conjure/create_afterimages/post_summon(atom/summoned_object, atom/cast_on) + var/mob/living/basic/created_copy = summoned_object + created_copy.AddComponent(/datum/component/joint_damage, overlord_mob = owner) + RegisterSignals(created_copy, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(delete_copy)) + number_of_afterimages++ + +/datum/action/cooldown/spell/conjure/create_afterimages/proc/delete_copy(mob/source) + SIGNAL_HANDLER + + UnregisterSignal(source, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH)) + number_of_afterimages-- diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm new file mode 100644 index 000000000000..20bcd8a69a13 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm @@ -0,0 +1,116 @@ +/datum/ai_controller/basic_controller/ice_demon + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_LIST_SCARY_ITEMS = list( + /obj/item/weldingtool, + /obj/item/flashlight/flare, + ), + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/flee_target/ice_demon, + /datum/ai_planning_subtree/ranged_skirmish/ice_demon, + /datum/ai_planning_subtree/maintain_distance/cover_minimum_distance/ice_demon, + /datum/ai_planning_subtree/teleport_away_from_target, + /datum/ai_planning_subtree/find_and_hunt_target/teleport_destination, + /datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages, + ) + + +/datum/ai_planning_subtree/maintain_distance/cover_minimum_distance/ice_demon + maximum_distance = 7 + +/datum/ai_planning_subtree/teleport_away_from_target + ability_key = BB_DEMON_TELEPORT_ABILITY + +/datum/ai_planning_subtree/find_and_hunt_target/teleport_destination + target_key = BB_TELEPORT_DESTINATION + hunting_behavior = /datum/ai_behavior/hunt_target/use_ability_on_target/demon_teleport + finding_behavior = /datum/ai_behavior/find_valid_teleport_location + hunt_targets = list(/turf/open) + hunt_range = 3 + finish_planning = FALSE + +/datum/ai_planning_subtree/find_and_hunt_target/teleport_destination/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + return + if(controller.blackboard_key_exists(BB_ESCAPE_DESTINATION)) + controller.clear_blackboard_key(BB_TELEPORT_DESTINATION) + return + var/datum/action/cooldown/ability = controller.blackboard[BB_DEMON_TELEPORT_ABILITY] + if(!ability?.IsAvailable()) + return + return ..() + +/datum/ai_behavior/find_valid_teleport_location + +/datum/ai_behavior/find_valid_teleport_location/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range) + . = ..() + var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + var/list/possible_turfs = list() + + if(QDELETED(target)) + finish_action(controller, FALSE) + return + + for(var/turf/open/potential_turf in oview(hunt_range, target)) //we check for turfs around the target + if(potential_turf.is_blocked_turf()) + continue + if(!can_see(target, potential_turf, hunt_range)) + continue + possible_turfs += potential_turf + + if(!length(possible_turfs)) + finish_action(controller, FALSE) + return + + controller.set_blackboard_key(hunting_target_key, pick(possible_turfs)) + finish_action(controller, TRUE) + +/datum/ai_behavior/hunt_target/use_ability_on_target/demon_teleport + hunt_cooldown = 2 SECONDS + ability_key = BB_DEMON_TELEPORT_ABILITY + behavior_flags = NONE + +/datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages + ability_key = BB_DEMON_CLONE_ABILITY + +/datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(living_pawn.health / living_pawn.maxHealth > 0.5) //only use this ability when under half health + return + return ..() + +/datum/ai_planning_subtree/flee_target/ice_demon + +/datum/ai_planning_subtree/flee_target/ice_demon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if(QDELETED(target)) + return + if(!iscarbon(target)) + return + var/mob/living/carbon/human_target = target + + for(var/obj/held_item in human_target.held_items) + if(!is_type_in_list(held_item, controller.blackboard[BB_LIST_SCARY_ITEMS])) + continue + if(!held_item.light_on) + continue + var/datum/action/cooldown/slip_ability = controller.blackboard[BB_DEMON_SLIP_ABILITY] + if(slip_ability?.IsAvailable()) + controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_DEMON_SLIP_ABILITY) + return ..() + +/datum/ai_planning_subtree/ranged_skirmish/ice_demon + min_range = 0 + +/datum/ai_controller/basic_controller/ice_demon/afterimage + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/flee_target/ice_demon, //even the afterimages are afraid of flames! + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm index 465d724944b2..e20da2fdfea3 100644 --- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm @@ -43,12 +43,12 @@ AddElement(/datum/element/footstep, FOOTSTEP_MOB_HEAVY) AddComponent(/datum/component/basic_mob_ability_telegraph) AddComponent(/datum/component/basic_mob_attack_telegraph, telegraph_duration = 0.6 SECONDS) - var/datum/action/cooldown/mob_cooldown/ice_breath/flamethrower = new(src) - var/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions/wide_flames = new(src) + var/datum/action/cooldown/mob_cooldown/fire_breath/ice/flamethrower = new(src) flamethrower.Grant(src) + ai_controller.set_blackboard_key(BB_WHELP_STRAIGHTLINE_FIRE, flamethrower) + var/datum/action/cooldown/mob_cooldown/fire_breath/ice/cross/wide_flames = new(src) wide_flames.Grant(src) ai_controller.set_blackboard_key(BB_WHELP_WIDESPREAD_FIRE, wide_flames) - ai_controller.set_blackboard_key(BB_WHELP_STRAIGHTLINE_FIRE, flamethrower) RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) @@ -69,6 +69,7 @@ INVOKE_ASYNC(src, PROC_REF(cannibalize_victim), victim) return COMPONENT_HOSTILE_NO_ATTACK +/// Carve a stone into a beautiful self-portrait /mob/living/basic/mining/ice_whelp/proc/create_sculpture(atom/target) balloon_alert(src, "sculpting...") if(!do_after(src, 5 SECONDS, target = target)) @@ -80,7 +81,9 @@ dragon_statue.set_anchored(TRUE) qdel(target) +/// Gib and consume our fellow ice drakes /mob/living/basic/mining/ice_whelp/proc/cannibalize_victim(mob/living/target) + start_pulling(target) balloon_alert(src, "devouring...") if(!do_after(src, 5 SECONDS, target)) return diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm index fa42958f9a00..026106516fb6 100644 --- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm @@ -1,22 +1,26 @@ -/datum/action/cooldown/mob_cooldown/ice_breath +/// Breathe "fire" in a line (it's freezing cold) +/datum/action/cooldown/mob_cooldown/fire_breath/ice name = "Ice Breath" desc = "Fire a cold line of fire towards the enemy!" button_icon = 'icons/effects/magic.dmi' button_icon_state = "fireball" cooldown_time = 3 SECONDS melee_cooldown_time = 0 SECONDS - click_to_activate = TRUE - ///the range of fire - var/fire_range = 4 + fire_range = 4 + fire_damage = 10 -/datum/action/cooldown/mob_cooldown/ice_breath/Activate(atom/target_atom) - var/turf/target_fire_turf = get_ranged_target_turf_direct(owner, target_atom, fire_range) - var/list/burn_turfs = get_line(owner, target_fire_turf) - get_turf(owner) - dragon_fire_line(owner, burn_turfs, frozen = TRUE) - StartCooldown() - return TRUE +/datum/action/cooldown/mob_cooldown/fire_breath/ice/burn_turf(turf/fire_turf, list/hit_list, atom/source) + var/obj/effect/hotspot/fire_hotspot = ..() + fire_hotspot.add_atom_colour(COLOR_BLUE_LIGHT, FIXED_COLOUR_PRIORITY) // You're blue now, that's my attack + return fire_hotspot -/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions +/datum/action/cooldown/mob_cooldown/fire_breath/ice/on_burn_mob(mob/living/barbecued, mob/living/source) + barbecued.apply_status_effect(/datum/status_effect/ice_block_talisman, 2 SECONDS) + to_chat(barbecued, span_userdanger("You're frozen solid by [source]'s icy breath!")) + barbecued.adjustFireLoss(fire_damage) + +/// Breathe really cold fire in a plus shape, like bomberman +/datum/action/cooldown/mob_cooldown/fire_breath/ice/cross name = "Fire all directions" desc = "Unleash lines of cold fire in all directions" button_icon = 'icons/effects/fire.dmi' @@ -24,13 +28,10 @@ cooldown_time = 4 SECONDS melee_cooldown_time = 0 SECONDS click_to_activate = FALSE - ///the range of fire - var/fire_range = 6 + fire_range = 6 -/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions/Activate(atom/target_atom) +/datum/action/cooldown/mob_cooldown/fire_breath/ice/cross/attack_sequence(atom/target) + playsound(owner.loc, fire_sound, 200, TRUE) for(var/direction in GLOB.cardinals) var/turf/target_fire_turf = get_ranged_target_turf(owner, direction, fire_range) - var/list/burn_turfs = get_line(owner, target_fire_turf) - get_turf(owner) - INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(dragon_fire_line), owner, burn_turfs, frozen = TRUE) - StartCooldown() - return TRUE + fire_line(target_fire_turf) diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm index 08c5fda3fd89..725dcc09b5ec 100644 --- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm @@ -1,7 +1,8 @@ #define ENRAGE_ADDITION 25 /datum/ai_controller/basic_controller/ice_whelp blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items/goliath, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, BB_WHELP_ENRAGED = 0, ) @@ -13,54 +14,33 @@ /datum/ai_planning_subtree/attack_obstacle_in_path, /datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/sculpt_statues, - /datum/ai_planning_subtree/find_and_hunt_target/cannibalize, + /datum/ai_planning_subtree/find_and_hunt_target/corpses/ice_whelp, /datum/ai_planning_subtree/burn_trees, ) - -/datum/ai_planning_subtree/find_and_hunt_target/cannibalize +/datum/ai_planning_subtree/find_and_hunt_target/corpses/ice_whelp target_key = BB_TARGET_CANNIBAL - hunting_behavior = /datum/ai_behavior/cannibalize - finding_behavior = /datum/ai_behavior/find_hunt_target/dragon_corpse + finding_behavior = /datum/ai_behavior/find_hunt_target/corpses/dragon_corpse + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/dragon_cannibalise hunt_targets = list(/mob/living/basic/mining/ice_whelp) hunt_range = 10 -/datum/ai_behavior/find_hunt_target/dragon_corpse +/datum/ai_behavior/find_hunt_target/corpses/dragon_corpse -/datum/ai_behavior/find_hunt_target/dragon_corpse/valid_dinner(mob/living/source, mob/living/dinner, radius) - if(dinner.stat != DEAD) - return FALSE +/datum/ai_behavior/find_hunt_target/corpses/dragon_corpse/valid_dinner(mob/living/source, mob/living/dinner, radius) if(dinner.pulledby) //someone already got him before us return FALSE + return ..() - return can_see(source, dinner, radius) - -/datum/ai_behavior/cannibalize +/datum/ai_behavior/hunt_target/unarmed_attack_target/dragon_cannibalise behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION -/datum/ai_behavior/cannibalize/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/cannibalize/perform(seconds_per_tick, datum/ai_controller/controller, target_key, attack_key) - . = ..() - var/mob/living/basic/living_pawn = controller.pawn +/datum/ai_behavior/hunt_target/unarmed_attack_target/dragon_cannibalise/perform(seconds_per_tick, datum/ai_controller/controller, target_key, attack_key) var/mob/living/target = controller.blackboard[target_key] - - if(QDELETED(target)) + if(QDELETED(target) || target.stat != DEAD || target.pulledby) //we were too slow finish_action(controller, FALSE) return - - if(target.stat != DEAD || target.pulledby) //we were too slow - finish_action(controller, FALSE) - return - - living_pawn.start_pulling(target) - living_pawn.melee_attack(target) - finish_action(controller, TRUE) + return ..() /datum/ai_behavior/cannibalize/finish_action(datum/ai_controller/controller, succeeded, target_key) . = ..() @@ -70,14 +50,10 @@ /datum/ai_planning_subtree/sculpt_statues /datum/ai_planning_subtree/sculpt_statues/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/obj/target = controller.blackboard[BB_TARGET_ROCK] - - if(QDELETED(target)) - controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_TARGET_ROCK, /obj/structure/flora/rock/icy) - return - - controller.queue_behavior(/datum/ai_behavior/sculpt_statue, BB_TARGET_ROCK) - return SUBTREE_RETURN_FINISH_PLANNING + if(controller.blackboard_key_exists(BB_TARGET_ROCK)) + controller.queue_behavior(/datum/ai_behavior/sculpt_statue, BB_TARGET_ROCK) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_TARGET_ROCK, /obj/structure/flora/rock/icy) /datum/ai_behavior/sculpt_statue behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION @@ -136,16 +112,13 @@ /datum/ai_planning_subtree/burn_trees/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) var/datum/action/cooldown/using_action = controller.blackboard[BB_WHELP_STRAIGHTLINE_FIRE] - if (!using_action.IsAvailable()) - return - - var/obj/structure/target = controller.blackboard[BB_TARGET_TREE] - if(QDELETED(target)) - controller.queue_behavior(/datum/ai_behavior/set_target_tree, BB_TARGET_TREE) + if (!using_action?.IsAvailable()) return - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees, BB_WHELP_STRAIGHTLINE_FIRE, BB_TARGET_TREE) - return SUBTREE_RETURN_FINISH_PLANNING + if(controller.blackboard_key_exists(BB_TARGET_TREE)) + controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees, BB_WHELP_STRAIGHTLINE_FIRE, BB_TARGET_TREE) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/set_target_tree, BB_TARGET_TREE) /datum/ai_behavior/set_target_tree diff --git a/code/modules/mob/living/basic/icemoon/wolf/wolf.dm b/code/modules/mob/living/basic/icemoon/wolf/wolf.dm new file mode 100644 index 000000000000..c657fa428433 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/wolf/wolf.dm @@ -0,0 +1,91 @@ +/mob/living/basic/mining/wolf + name = "white wolf" + desc = "Pack hunters of the Icemoon wastes. While a mere nuisance individually, they become fearsome foes in larger groups." + icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + icon_state = "whitewolf" + icon_living = "whitewolf" + icon_dead = "whitewolf_dead" + mob_biotypes = MOB_ORGANIC|MOB_BEAST + mouse_opacity = MOUSE_OPACITY_ICON + speak_emote = list("howls") + friendly_verb_continuous = "howls at" + friendly_verb_simple = "howl at" + + butcher_results = list( + /obj/item/food/meat/slab = 2, + /obj/item/stack/sheet/sinew/wolf = 2, + /obj/item/stack/sheet/bone = 2 + ) + crusher_loot = /obj/item/crusher_trophy/wolf_ear + + maxHealth = 130 + health = 130 + obj_damage = 15 + melee_damage_lower = 7.5 + melee_damage_upper = 7.5 + attack_vis_effect = ATTACK_EFFECT_BITE + melee_attack_cooldown = 1.2 SECONDS + + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + death_message = "snarls its last and perishes." + + attack_sound = 'sound/weapons/bite.ogg' + move_force = MOVE_FORCE_WEAK + move_resist = MOVE_FORCE_WEAK + pull_force = MOVE_FORCE_WEAK + + ai_controller = /datum/ai_controller/basic_controller/wolf + + //can we tame this wolf? + var/can_tame = TRUE + + //commands to give when tamed + var/static/list/pet_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/good_boy/wolf, + /datum/pet_command/follow/wolf, + /datum/pet_command/point_targeting/attack, + /datum/pet_command/point_targeting/fetch, + /datum/pet_command/play_dead, + /datum/pet_command/protect_owner, + ) + +/mob/living/basic/mining/wolf/Initialize(mapload) + . = ..() + + AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) + AddElement(/datum/element/ai_flee_while_injured) + AddElement(/datum/element/ai_retaliate) + AddComponent(/datum/component/basic_mob_ability_telegraph) + AddComponent(/datum/component/basic_mob_attack_telegraph, telegraph_duration = 0.6 SECONDS) + + if(can_tame) + make_tameable() + +/mob/living/basic/mining/wolf/proc/make_tameable() + AddComponent(\ + /datum/component/tameable,\ + food_types = list(/obj/item/food/meat/slab),\ + tame_chance = 15,\ + bonus_tame_chance = 5,\ + after_tame = CALLBACK(src, PROC_REF(tame_wolf)),\ + ) + +/mob/living/basic/mining/wolf/proc/tame_wolf() + new /obj/effect/temp_visual/heart(src.loc) + // ride wolf, life good + AddElement(/datum/element/ridable, /datum/component/riding/creature/wolf) + AddComponent(/datum/component/obeys_commands, pet_commands) + // this is purely a convenience thing once tamed so you can drag them away from shit + ai_controller.ai_traits = STOP_MOVING_WHEN_PULLED + // makes tamed wolves run away far less + ai_controller.set_blackboard_key(BB_BASIC_MOB_FLEE_DISTANCE, 7) + +//port the faction fix from goliath basicmob to make the wildlife hostile when tamed (and also help defuckulate reinforcements ai) +//this should also produce interesting behavior where tamed wolves defend other tamed wolves. +/mob/living/basic/mining/wolf/befriend(mob/living/new_friend) + . = ..() + faction = new_friend.faction.Copy() + visible_message(span_notice("[src] lowers [src.p_their()] snout at [new_friend]'s offering and begins to wag [src.p_their()] tail.")) diff --git a/code/modules/mob/living/basic/icemoon/wolf/wolf_ai.dm b/code/modules/mob/living/basic/icemoon/wolf/wolf_ai.dm new file mode 100644 index 000000000000..f0809d2ec86d --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/wolf/wolf_ai.dm @@ -0,0 +1,36 @@ +//This mimicks the old simple_animal wolf behavior fairly closely. +//The 30 tiles fleeing is pretty wild and may need toning back under basicmob behavior, we'll have to see. +/datum/ai_controller/basic_controller/wolf + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_BASIC_MOB_FLEE_DISTANCE = 30, + BB_VISION_RANGE = 9, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_EMOTE = "unleashes a chilling howl, calling for aid!" + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + + //reinforcements needs to be skipped over entirely on tamed wolves because it causes them to attack their owner and then themselves + planning_subtrees = list( + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/call_reinforcements/wolf, + /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, + /datum/ai_planning_subtree/flee_target, + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/datum/ai_planning_subtree/call_reinforcements/wolf + +/datum/ai_planning_subtree/call_reinforcements/wolf/decide_to_call(datum/ai_controller/controller) + //only call reinforcements if the person who just smacked us isn't a friend to avoid hitting them once, then killing ourselves if we've been tamed + if (controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) && istype(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET], /mob)) + return !(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] in controller.blackboard[BB_FRIENDS_LIST]) + else + return FALSE + diff --git a/code/modules/mob/living/basic/icemoon/wolf/wolf_extras.dm b/code/modules/mob/living/basic/icemoon/wolf/wolf_extras.dm new file mode 100644 index 000000000000..7765ec896a1e --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/wolf/wolf_extras.dm @@ -0,0 +1,18 @@ +// Some flavor additions for wolf-related pet commands +/datum/pet_command/good_boy/wolf + speech_commands = list("good wolf") + +/datum/pet_command/follow/wolf + // Nordic-themed for a bit of extra flavor + speech_commands = list("heel", "follow", "fylgja", "fyl") + +// Contains pixel offset data for sprites riding wolves +/datum/component/riding/creature/wolf + +/datum/component/riding/creature/wolf/handle_specials() + . = ..() + set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(1, 9), TEXT_SOUTH = list(1, 9), TEXT_EAST = list(0, 9), TEXT_WEST = list(2, 9))) + set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER) + set_vehicle_dir_layer(NORTH, OBJ_LAYER) + set_vehicle_dir_layer(EAST, OBJ_LAYER) + set_vehicle_dir_layer(WEST, OBJ_LAYER) diff --git a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.dm b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.dm index 7f40e6ae0a16..bb109fdde61a 100644 --- a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.dm +++ b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.dm @@ -36,12 +36,11 @@ /mob/living/basic/mega_arachnid/Initialize(mapload) . = ..() + AddComponent(/datum/component/seethrough_mob) var/datum/action/cooldown/spell/pointed/projectile/flesh_restraints/restrain = new(src) - var/datum/action/small_sprite/mega_arachnid/mini_arachnid = new(src) var/datum/action/cooldown/mob_cooldown/secrete_acid/acid_spray = new(src) acid_spray.Grant(src) restrain.Grant(src) - mini_arachnid.Grant(src) AddElement(/datum/element/swabable, CELL_LINE_TABLE_MEGA_ARACHNID, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) AddComponent(/datum/component/appearance_on_aggro, alpha_on_aggro = 255, alpha_on_deaggro = alpha) AddComponent(/datum/component/tree_climber, climbing_distance = 15) diff --git a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_abilities.dm b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_abilities.dm index e8c4d1723e79..6e8ff992891b 100644 --- a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_abilities.dm +++ b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_abilities.dm @@ -17,7 +17,7 @@ icon_state = "tentacle_end" damage = 0 -/obj/projectile/mega_arachnid/on_hit(atom/target, blocked = FALSE) +/obj/projectile/mega_arachnid/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(!iscarbon(target) || blocked >= 100) return diff --git a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm index e2c67af24674..fa2a86787d86 100644 --- a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm +++ b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm @@ -1,7 +1,7 @@ /datum/ai_controller/basic_controller/mega_arachnid blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, - BB_BASIC_MOB_FLEEING = TRUE, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_FLEE_DISTANCE = 5, ) ai_movement = /datum/ai_movement/basic_avoidance @@ -36,7 +36,7 @@ flee_behaviour = /datum/ai_behavior/run_away_from_target/mega_arachnid /datum/ai_planning_subtree/flee_target/mega_arachnid/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard[BB_BASIC_MOB_FLEEING]) + if(controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) return var/datum/action/cooldown/slip_acid = controller.blackboard[BB_ARACHNID_SLIP] @@ -47,7 +47,6 @@ /datum/ai_behavior/run_away_from_target/mega_arachnid clear_failed_targets = FALSE - run_distance = 5 ///only engage in melee combat against cuffed targets, otherwise keep throwing restraints at them /datum/ai_planning_subtree/basic_melee_attack_subtree/mega_arachnid diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling.dm b/code/modules/mob/living/basic/jungle/seedling/seedling.dm new file mode 100644 index 000000000000..dfc20450a005 --- /dev/null +++ b/code/modules/mob/living/basic/jungle/seedling/seedling.dm @@ -0,0 +1,348 @@ +#define SEEDLING_STATE_NEUTRAL 0 +#define SEEDLING_STATE_WARMUP 1 +#define SEEDLING_STATE_ACTIVE 2 + +/** + * A mobile plant with a rapid ranged attack. + * It can pick up watering cans and look after plants. + */ +/mob/living/basic/seedling + name = "seedling" + desc = "This oversized, predatory flower conceals what can only be described as an organic energy cannon." + icon = 'icons/mob/simple/jungle/seedling.dmi' + icon_state = "seedling" + icon_living = "seedling" + icon_dead = "seedling_dead" + habitable_atmos = list("min_oxy" = 2, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = 0 + maximum_survivable_temperature = 450 + mob_biotypes = MOB_ORGANIC | MOB_PLANT + maxHealth = 100 + health = 100 + pixel_y = -14 + base_pixel_y = -14 + pixel_x = -14 + base_pixel_x = -14 + response_harm_continuous = "strikes" + response_harm_simple = "strike" + melee_damage_lower = 30 + melee_damage_upper = 30 + lighting_cutoff_green = 20 + lighting_cutoff_blue = 25 + mob_size = MOB_SIZE_LARGE + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH + ai_controller = /datum/ai_controller/basic_controller/seedling + ///the state of combat we are in + var/combatant_state = SEEDLING_STATE_NEUTRAL + ///the colors our petals can have + var/static/list/possible_colors = list(COLOR_RED, COLOR_YELLOW, COLOR_OLIVE, COLOR_CYAN) + ///appearance when we are in our normal state + var/mutable_appearance/petal_neutral + ///appearance when we are in our warmup state + var/mutable_appearance/petal_warmup + ///appearance when we are in the firing state + var/mutable_appearance/petal_active + ///appearance when we are dead + var/mutable_appearance/petal_dead + ///the bucket we carry + var/obj/item/reagent_containers/cup/held_can + ///commands we follow + var/list/seedling_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/follow, + ) + +/mob/living/basic/seedling/Initialize(mapload) + . = ..() + var/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/seed_attack = new(src) + seed_attack.Grant(src) + ai_controller.set_blackboard_key(BB_RAPIDSEEDS_ABILITY, seed_attack) + var/datum/action/cooldown/mob_cooldown/solarbeam/beam_attack = new(src) + beam_attack.Grant(src) + ai_controller.set_blackboard_key(BB_SOLARBEAM_ABILITY, beam_attack) + + var/petal_color = pick(possible_colors) + + petal_neutral = mutable_appearance(icon, "[icon_state]_overlay") + petal_neutral.color = petal_color + + petal_warmup = mutable_appearance(icon, "[icon_state]_charging_overlay") + petal_warmup.color = petal_color + + petal_active = mutable_appearance(icon, "[icon_state]_fire_overlay") + petal_active.color = petal_color + + petal_dead = mutable_appearance(icon, "[icon_state]_dead_overlay") + petal_dead.color = petal_color + + AddElement(/datum/element/wall_smasher) + AddComponent(/datum/component/obeys_commands, seedling_commands) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + RegisterSignal(src, COMSIG_KB_MOB_DROPITEM_DOWN, PROC_REF(drop_can)) + update_appearance() + +/mob/living/basic/seedling/proc/pre_attack(mob/living/puncher, atom/target) + SIGNAL_HANDLER + + if(istype(target, /obj/machinery/hydroponics)) + treat_hydro_tray(target) + return COMPONENT_HOSTILE_NO_ATTACK + + if(isnull(held_can)) + return + + if(istype(target, /obj/structure/sink) || istype(target, /obj/structure/reagent_dispensers)) + INVOKE_ASYNC(held_can, TYPE_PROC_REF(/obj/item, melee_attack_chain), src, target) + return COMPONENT_HOSTILE_NO_ATTACK + + +///seedlings can water trays, remove weeds, or remove dead plants +/mob/living/basic/seedling/proc/treat_hydro_tray(obj/machinery/hydroponics/hydro) + + if(hydro.plant_status == HYDROTRAY_PLANT_DEAD) + balloon_alert(src, "dead plant removed") + hydro.set_seed(null) + return + + if(hydro.weedlevel > 0) + balloon_alert(src, "weeds uprooted") + hydro.set_weedlevel(0) + return + + var/list/can_reagents = held_can?.reagents.reagent_list + + if(!length(can_reagents)) + return + + if((locate(/datum/reagent/water) in can_reagents) && (hydro.waterlevel < hydro.maxwater)) + INVOKE_ASYNC(held_can, TYPE_PROC_REF(/obj/item, melee_attack_chain), src, hydro) + return + +/mob/living/basic/seedling/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) + . = ..() + + if(!. || !proximity_flag || held_can) + return + + if(!istype(attack_target, /obj/item/reagent_containers/cup/watering_can)) + return + + var/obj/item/can_target = attack_target + can_target.forceMove(src) + +/mob/living/basic/seedling/proc/change_combatant_state(state) + combatant_state = state + update_appearance() + +/mob/living/basic/seedling/attackby(obj/item/can, mob/living/carbon/human/user, list/modifiers) + if(istype(can, /obj/item/reagent_containers/cup/watering_can) && isnull(held_can)) + can.forceMove(src) + return + + return ..() + +/mob/living/basic/seedling/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + if(istype(arrived, /obj/item/reagent_containers/cup/watering_can)) + held_can = arrived + update_appearance() + + return ..() + +/mob/living/basic/seedling/update_overlays() + . = ..() + if(stat == DEAD) + . += petal_dead + return + + switch(combatant_state) + if(SEEDLING_STATE_NEUTRAL) + . += petal_neutral + if(held_can) + . += mutable_appearance(icon, "seedling_can_overlay") + if(SEEDLING_STATE_WARMUP) + . += petal_warmup + if(SEEDLING_STATE_ACTIVE) + . += petal_active + +/mob/living/basic/seedling/update_icon_state() + . = ..() + if(stat == DEAD) + return + switch(combatant_state) + if(SEEDLING_STATE_NEUTRAL) + icon_state = "seedling" + if(SEEDLING_STATE_WARMUP) + icon_state = "seedling_charging" + if(SEEDLING_STATE_ACTIVE) + icon_state = "seedling_fire" + +/mob/living/basic/seedling/proc/drop_can(mob/living/user) + SIGNAL_HANDLER + + if(isnull(held_can)) + return + dropItemToGround(held_can) + return COMSIG_KB_ACTIVATED + +/mob/living/basic/seedling/Exited(atom/movable/gone, direction) + . = ..() + if(gone != held_can) + return + held_can = null + update_appearance() + +/mob/living/basic/seedling/death(gibbed) + . = ..() + if(isnull(held_can)) + return + held_can.forceMove(drop_location()) + +/mob/living/basic/seedling/Destroy() + QDEL_NULL(held_can) + return ..() + +/mob/living/basic/seedling/meanie + maxHealth = 400 + health = 400 + faction = list(FACTION_JUNGLE) + ai_controller = /datum/ai_controller/basic_controller/seedling/meanie + seedling_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/follow, + /datum/pet_command/point_targeting/attack, + /datum/pet_command/point_targeting/use_ability/solarbeam, + /datum/pet_command/point_targeting/use_ability/rapidseeds, + ) + +//abilities +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling + name = "Solar Energy" + button_icon = 'icons/obj/weapons/guns/projectiles.dmi' + button_icon_state = "seedling" + desc = "Fire small beams of solar energy." + cooldown_time = 10 SECONDS + projectile_type = /obj/projectile/seedling + default_projectile_spread = 10 + shot_count = 10 + shot_delay = 0.2 SECONDS + melee_cooldown_time = 0 SECONDS + shared_cooldown = NONE + ///how long we must charge up before firing off + var/charge_up_timer = 3 SECONDS + ///is the owner of this ability a seedling? + var/is_seedling = FALSE + +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/Grant(mob/grant_to) + . = ..() + if(isnull(owner)) + return + is_seedling = istype(owner, /mob/living/basic/seedling) + +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/IsAvailable(feedback) + . = ..() + if(!.) + return FALSE + if(!is_seedling) + return TRUE + var/mob/living/basic/seedling/seed_owner = owner + if(seed_owner.combatant_state != SEEDLING_STATE_NEUTRAL) + if(feedback) + seed_owner.balloon_alert(seed_owner, "charging!") + return FALSE + return TRUE + +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/Activate(atom/target) + if(is_seedling) + var/mob/living/basic/seedling/seed_owner = owner + seed_owner.change_combatant_state(state = SEEDLING_STATE_WARMUP) + addtimer(CALLBACK(src, PROC_REF(attack_sequence), owner, target), charge_up_timer) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/attack_sequence(mob/living/firer, atom/target) + if(is_seedling) + var/mob/living/basic/seedling/seed_owner = owner + seed_owner.change_combatant_state(state = SEEDLING_STATE_ACTIVE) + addtimer(CALLBACK(seed_owner, TYPE_PROC_REF(/mob/living/basic/seedling, change_combatant_state), SEEDLING_STATE_NEUTRAL), 4 SECONDS) + + return ..() + + +/datum/action/cooldown/mob_cooldown/solarbeam + name = "Solar Beam" + button_icon = 'icons/effects/beam.dmi' + button_icon_state = "solar_beam" + desc = "Concentrate the power of the sun onto your target!" + cooldown_time = 30 SECONDS + shared_cooldown = NONE + ///how long will it take for us to charge up the beam + var/beam_charge_up = 3 SECONDS + ///is the owner of this ability a seedling? + var/is_seedling = FALSE + +/datum/action/cooldown/mob_cooldown/solarbeam/Grant(mob/grant_to) + . = ..() + if(isnull(owner)) + return + is_seedling = istype(owner, /mob/living/basic/seedling) + +/datum/action/cooldown/mob_cooldown/solarbeam/IsAvailable(feedback) + . = ..() + if(!.) + return FALSE + if(!is_seedling) + return TRUE + var/mob/living/basic/seedling/seed_owner = owner + if(seed_owner.combatant_state != SEEDLING_STATE_NEUTRAL) + if(feedback) + seed_owner.balloon_alert(seed_owner, "charging!") + return FALSE + return TRUE + +/datum/action/cooldown/mob_cooldown/solarbeam/Activate(atom/target) + if(is_seedling) + var/mob/living/basic/seedling/seed_owner = owner + seed_owner.change_combatant_state(state = SEEDLING_STATE_WARMUP) + + var/turf/target_turf = get_turf(target) + playsound(owner, 'sound/effects/seedling_chargeup.ogg', 100, FALSE) + + var/obj/effect/temp_visual/solarbeam_killsat/owner_beam = new(get_turf(owner)) + animate(owner_beam, transform = matrix().Scale(1, 32), alpha = 255, time = beam_charge_up) + + var/obj/effect/temp_visual/solarbeam_killsat/target_beam = new(target_turf) + animate(target_beam, transform = matrix().Scale(2, 1), alpha = 255, time = beam_charge_up) + + addtimer(CALLBACK(src, PROC_REF(launch_beam), owner, target_turf), beam_charge_up) + StartCooldown() + return TRUE + +///the solarbeam will damage people, otherwise it will heal plants +/datum/action/cooldown/mob_cooldown/solarbeam/proc/launch_beam(mob/living/firer, turf/target_turf) + for(var/atom/target_atom as anything in target_turf) + + if(istype(target_atom, /obj/machinery/hydroponics)) + var/obj/machinery/hydroponics/hydro = target_atom + hydro.adjust_plant_health(10) + new /obj/effect/temp_visual/heal(target_turf, COLOR_HEALING_CYAN) + + if(!isliving(target_atom)) + continue + + var/mob/living/living_target = target_atom + living_target.adjust_fire_stacks(0.2) + living_target.ignite_mob() + living_target.adjustFireLoss(30) + + playsound(target_turf, 'sound/magic/lightningbolt.ogg', 50, TRUE) + if(!is_seedling) + return + var/mob/living/basic/seedling/seed_firer = firer + seed_firer.change_combatant_state(state = SEEDLING_STATE_NEUTRAL) + +#undef SEEDLING_STATE_NEUTRAL +#undef SEEDLING_STATE_WARMUP +#undef SEEDLING_STATE_ACTIVE diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm b/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm new file mode 100644 index 000000000000..2ed4811e46f2 --- /dev/null +++ b/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm @@ -0,0 +1,178 @@ +/datum/ai_controller/basic_controller/seedling + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_WEEDLEVEL_THRESHOLD = 3, + BB_WATERLEVEL_THRESHOLD = 90, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/find_and_hunt_target/watering_can, + /datum/ai_planning_subtree/find_and_hunt_target/fill_watercan, + /datum/ai_planning_subtree/find_and_hunt_target/treat_hydroplants, + /datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants, + ) + +/datum/ai_planning_subtree/find_and_hunt_target/watering_can + target_key = BB_WATERCAN_TARGET + finding_behavior = /datum/ai_behavior/find_hunt_target + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target + hunt_targets = list(/obj/item/reagent_containers/cup/watering_can) + hunt_range = 7 + +/datum/ai_planning_subtree/find_and_hunt_target/watering_can/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn) //we already have what we came for! + return + return ..() + +/datum/ai_planning_subtree/find_and_hunt_target/treat_hydroplants + target_key = BB_HYDROPLANT_TARGET + finding_behavior = /datum/ai_behavior/find_and_set/treatable_hydro + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/treat_hydroplant + hunt_targets = list(/obj/machinery/hydroponics) + hunt_range = 7 + +/datum/ai_behavior/find_and_set/treatable_hydro + +/datum/ai_behavior/find_and_set/treatable_hydro/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/list/possible_trays = list() + var/mob/living/living_pawn = controller.pawn + var/waterlevel_threshold = controller.blackboard[BB_WATERLEVEL_THRESHOLD] + var/weedlevel_threshold = controller.blackboard[BB_WEEDLEVEL_THRESHOLD] + var/watering_can = locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn + + for(var/obj/machinery/hydroponics/hydro in oview(search_range, controller.pawn)) + if(isnull(hydro.myseed)) + continue + if(hydro.waterlevel < waterlevel_threshold && watering_can) + possible_trays += hydro + continue + if(hydro.weedlevel > weedlevel_threshold || hydro.plant_status == HYDROTRAY_PLANT_DEAD) + possible_trays += hydro + continue + + if(possible_trays.len) + return pick(possible_trays) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/treat_hydroplant + hunt_cooldown = 2 SECONDS + always_reset_target = TRUE + +/datum/ai_behavior/hunt_target/unarmed_attack_target/treat_hydroplant/target_caught(mob/living/living_pawn, obj/machinery/hydroponics/hydro_target) + if(QDELETED(hydro_target) || QDELETED(hydro_target.myseed)) + return + + if(hydro_target.plant_status == HYDROTRAY_PLANT_DEAD) + living_pawn.manual_emote("weeps...") //weep over the dead plants + return ..() + + +/datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants + target_key = BB_BEAMABLE_HYDROPLANT_TARGET + finding_behavior = /datum/ai_behavior/find_and_set/beamable_hydroplants + hunting_behavior = /datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam + hunt_targets = list(/obj/machinery/hydroponics) + hunt_range = 7 + +/datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/datum/action/cooldown/solar_ability = controller.blackboard[BB_SOLARBEAM_ABILITY] + if(QDELETED(solar_ability) || !solar_ability.IsAvailable()) + return + return ..() + +/datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + required_distance = 2 + action_cooldown = 1 MINUTES + ability_key = BB_SOLARBEAM_ABILITY + +/datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam/setup(datum/ai_controller/controller, target_key, ability_key) + . = ..() + var/obj/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/find_and_set/beamable_hydroplants/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/list/possible_trays = list() + + for(var/obj/machinery/hydroponics/hydro in oview(search_range, controller.pawn)) + if(isnull(hydro.myseed)) + continue + if(hydro.plant_health < hydro.myseed.endurance) + possible_trays += hydro + + if(possible_trays.len) + return pick(possible_trays) + +/datum/ai_planning_subtree/find_and_hunt_target/fill_watercan + target_key = BB_LOW_PRIORITY_HUNTING_TARGET + finding_behavior = /datum/ai_behavior/find_hunt_target/suitable_dispenser + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/water_source + hunt_targets = list(/obj/structure/sink, /obj/structure/reagent_dispensers) + hunt_range = 7 + +/datum/ai_planning_subtree/find_and_hunt_target/fill_watercan/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + var/obj/item/reagent_containers/can = locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn + + if(isnull(can)) + return + if(locate(/datum/reagent/water) in can.reagents.reagent_list) + return + + return ..() + +/datum/ai_behavior/find_hunt_target/suitable_dispenser + +/datum/ai_behavior/find_hunt_target/suitable_dispenser/valid_dinner(mob/living/source, obj/structure/water_source, radius) + if(!(locate(/datum/reagent/water) in water_source.reagents.reagent_list)) + return FALSE + + return can_see(source, water_source, radius) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/water_source + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + hunt_cooldown = 5 SECONDS + +/datum/ai_controller/basic_controller/seedling/meanie + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + ) + planning_subtrees = list( + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability/seedling_rapid, + /datum/ai_planning_subtree/targeted_mob_ability/solarbeam, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/datum/ai_planning_subtree/targeted_mob_ability/seedling_rapid + ability_key = BB_RAPIDSEEDS_ABILITY + finish_planning = FALSE + +/datum/ai_planning_subtree/targeted_mob_ability/solarbeam + ability_key = BB_SOLARBEAM_ABILITY + finish_planning = FALSE + +///pet commands +/datum/pet_command/point_targeting/use_ability/solarbeam + command_name = "Launch solarbeam" + command_desc = "Command your pet to launch a solarbeam at your target!" + radial_icon = 'icons/effects/beam.dmi' + radial_icon_state = "solar_beam" + speech_commands = list("beam", "solar") + pet_ability_key = BB_SOLARBEAM_ABILITY + +/datum/pet_command/point_targeting/use_ability/rapidseeds + command_name = "Rapid seeds" + command_desc = "Command your pet to launch a volley of seeds at your target!" + radial_icon = 'icons/obj/weapons/guns/projectiles.dmi' + radial_icon_state = "seedling" + speech_commands = list("rapid", "seeds", "volley") + pet_ability_key = BB_RAPIDSEEDS_ABILITY diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm b/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm new file mode 100644 index 000000000000..e502f3ec09fb --- /dev/null +++ b/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm @@ -0,0 +1,32 @@ +/obj/projectile/seedling + name = "solar energy" + icon_state = "seedling" + damage = 10 + damage_type = BURN + light_outer_range = 2 + armor_flag = ENERGY + light_color = LIGHT_COLOR_DIM_YELLOW + speed = 1.6 + hitsound = 'sound/weapons/sear.ogg' + hitsound_wall = 'sound/weapons/effects/searwall.ogg' + nondirectional_sprite = TRUE + +/obj/projectile/seedling/on_hit(atom/target, blocked = 0, pierce_hit) + if(!isliving(target)) + return ..() + + var/mob/living/living_target = target + if(FACTION_JUNGLE in living_target.faction) + return + + return ..() + +/obj/effect/temp_visual/solarbeam_killsat + name = "beam of solar energy" + icon_state = "solar_beam" + icon = 'icons/effects/beam.dmi' + plane = LIGHTING_PLANE + layer = LIGHTING_PRIMARY_LAYER + duration = 3 SECONDS + alpha = 200 + randomdir = FALSE diff --git a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm b/code/modules/mob/living/basic/jungle/venus_human_trap.dm similarity index 62% rename from code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm rename to code/modules/mob/living/basic/jungle/venus_human_trap.dm index e5aa1d1be03f..5b08af939af9 100644 --- a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm +++ b/code/modules/mob/living/basic/jungle/venus_human_trap.dm @@ -120,13 +120,14 @@ * The result of a kudzu flower bud, these enemies use vines to drag prey close to them for attack. * * A carnivorious plant which uses vines to catch and ensnare prey. Spawns from kudzu flower buds. - * Each one has a maximum of four vines, which can be attached to a variety of things. Carbons are stunned when a vine is attached to them, and movable entities are pulled closer over time. + * Each one can attach up to two temporary vines to objects or mobs and drag them around with it. * Attempting to attach a vine to something with a vine already attached to it will pull all movable targets closer on command. * Once the prey is in melee range, melee attacks from the venus human trap heals itself for 10% of its max health, assuming the target is alive. * Akin to certain spiders, venus human traps can also be possessed and controlled by ghosts. * */ -/mob/living/simple_animal/hostile/venus_human_trap + +/mob/living/basic/venus_human_trap name = "venus human trap" desc = "Now you know how the fly feels." icon = 'icons/effects/spacevines.dmi' @@ -135,24 +136,21 @@ mob_biotypes = MOB_ORGANIC | MOB_PLANT layer = SPACEVINE_MOB_LAYER plane = GAME_PLANE_UPPER_FOV_HIDDEN - health = 50 - maxHealth = 50 - ranged = TRUE - harm_intent_damage = 5 + health = 100 + maxHealth = 100 obj_damage = 60 - melee_damage_lower = 20 + melee_damage_lower = 10 melee_damage_upper = 20 - minbodytemp = 100 + minimum_survivable_temperature = 100 istate = ISTATE_HARM|ISTATE_BLOCKING - ranged_cooldown_time = 4 SECONDS - del_on_death = TRUE + basic_mob_flags = DEL_ON_DEATH death_message = "collapses into bits of plant matter." attacked_sound = 'sound/creatures/venus_trap_hurt.ogg' death_sound = 'sound/creatures/venus_trap_death.ogg' attack_sound = 'sound/creatures/venus_trap_hit.ogg' unsuitable_heat_damage = 5 // heat damage is different from cold damage since coldmos is significantly more common than plasmafires unsuitable_cold_damage = 2 // they now do take cold damage, but this should be sufficiently small that it does not cause major issues - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) unsuitable_atmos_damage = 0 /// copied over from the code from eyeballs (the mob) to make it easier for venus human traps to see in kudzu that doesn't have the transparency mutation sight = SEE_SELF|SEE_MOBS|SEE_OBJS|SEE_TURFS @@ -163,74 +161,81 @@ faction = list(FACTION_HOSTILE,FACTION_VINES,FACTION_PLANTS) initial_language_holder = /datum/language_holder/venus unique_name = TRUE - /// A list of all the plant's vines - var/list/vines = list() - /// The maximum amount of vines a plant can have at one time - var/max_vines = 4 - /// How far away a plant can attach a vine to something - var/vine_grab_distance = 5 - /// Whether or not this plant is ghost possessable - var/playable_plant = TRUE + speed = 1.2 + melee_attack_cooldown = 1.2 SECONDS + ai_controller = /datum/ai_controller/basic_controller/human_trap + ///how much damage we take out of weeds + var/no_weed_damage = 20 + ///how much do we heal in weeds + var/weed_heal = 10 + ///if the balloon alert was shown atleast once, reset after healing in weeds + var/alert_shown = FALSE -/mob/living/simple_animal/hostile/venus_human_trap/Life(seconds_per_tick = SSMOBS_DT, times_fired) +/mob/living/basic/venus_human_trap/Initialize(mapload) . = ..() - pull_vines() + var/datum/action/cooldown/vine_tangle/tangle = new(src) + tangle.Grant(src) + ai_controller.set_blackboard_key(BB_TARGETED_ACTION, tangle) -/mob/living/simple_animal/hostile/venus_human_trap/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - . = ..() - pixel_x = base_pixel_x + (dir & (NORTH|WEST) ? 2 : -2) +/mob/living/basic/venus_human_trap/RangedAttack(atom/victim) + if(!(istate & ISTATE_HARM)) + return + var/datum/action/cooldown/mob_cooldown/tangle_ability = ai_controller.blackboard[BB_TARGETED_ACTION] + if(!istype(tangle_ability)) + return + tangle_ability.Trigger(target = victim) -/mob/living/simple_animal/hostile/venus_human_trap/AttackingTarget() +/mob/living/basic/venus_human_trap/Life(seconds_per_tick = SSMOBS_DT, times_fired) . = ..() - if(isliving(target)) - var/mob/living/L = target - if(L.stat != DEAD) - adjustHealth(-maxHealth * 0.1) - -/mob/living/simple_animal/hostile/venus_human_trap/OpenFire(atom/the_target) - for(var/datum/beam/B in vines) - if(B.target == the_target) - pull_vines() - ranged_cooldown = world.time + (ranged_cooldown_time * 0.5) - return - if(get_dist(src,the_target) > vine_grab_distance || vines.len >= max_vines) + if(!.) + return FALSE + + var/vines_in_range = locate(/obj/structure/spacevine) in range(2, src) + if(!vines_in_range && !alert_shown) + alert_shown = TRUE + balloon_alert(src, "do not leave vines!") + else if(vines_in_range) + alert_shown = FALSE + + apply_damage(vines_in_range ? weed_heal : no_weed_damage, BRUTE) //every life tick take 20 brute if not near vines or heal 10 if near vines, 5 times out of weeds = u ded + +/datum/action/cooldown/vine_tangle + name = "Tangle" + button_icon = 'icons/mob/spacevines.dmi' + button_icon_state = "Light1" + desc = "Grabs a target with a sticky vine, allowing you to pull it alongside you." + cooldown_time = 8 SECONDS + ///how many vines can we handle + var/max_vines = 2 + /// An assoc list of all the plant's vines (beam = leash) + var/list/datum/beam/vines = list() + /// How far away a plant can attach a vine to something + var/vine_grab_distance = 4 + /// how long does a vine attached to something last (and its leash) (lasts twice as long on nonliving things) + var/vine_duration = 2 SECONDS + +/datum/action/cooldown/vine_tangle/Remove(mob/remove_from) + QDEL_LIST(vines) + return ..() + +/datum/action/cooldown/vine_tangle/Activate(atom/target_atom) + if(isturf(target_atom) || istype(target_atom, /obj/structure/spacevine)) return - for(var/turf/T in get_line(src,target)) - if (T.density) + if(length(vines) >= max_vines || get_dist(owner, target_atom) > vine_grab_distance) + return + for(var/turf/blockage in get_line(owner, target_atom)) + if(blockage.is_blocked_turf(exclude_mobs = TRUE)) return - for(var/obj/O in T) - if(O.density) - return - - var/datum/beam/newVine = Beam(the_target, icon_state = "vine", maxdistance = vine_grab_distance, beam_type=/obj/effect/ebeam/vine, emissive = FALSE) - RegisterSignal(newVine, COMSIG_PARENT_QDELETING, PROC_REF(remove_vine), newVine) - vines += newVine - if(isliving(the_target)) - var/mob/living/L = the_target - L.apply_damage(85, STAMINA, BODY_ZONE_CHEST) - L.Knockdown(1 SECONDS) - ranged_cooldown = world.time + ranged_cooldown_time - -/mob/living/simple_animal/hostile/venus_human_trap/Destroy() - for(var/datum/beam/vine as anything in vines) - qdel(vine) // reference is automatically deleted by remove_vine - return ..() -/** - * Manages how the vines should affect the things they're attached to. - * - * Pulls all movable targets of the vines closer to the plant - * If the target is on the same tile as the plant, destroy the vine - * Removes any QDELETED vines from the vines list. - */ -/mob/living/simple_animal/hostile/venus_human_trap/proc/pull_vines() - for(var/datum/beam/B in vines) - if(istype(B.target, /atom/movable)) - var/atom/movable/AM = B.target - if(!AM.anchored) - step(AM, get_dir(AM, src)) - if(get_dist(src, B.target) == 0) - qdel(B) + var/datum/beam/new_vine = owner.Beam(target_atom, icon_state = "vine", time = vine_duration * (ismob(target_atom) ? 1 : 2), beam_type = /obj/effect/ebeam/vine, emissive = FALSE) + var/component = target_atom.AddComponent(/datum/component/leash, owner, vine_grab_distance) + RegisterSignal(new_vine, COMSIG_PARENT_QDELETING, PROC_REF(remove_vine), new_vine) + vines[new_vine] = component + if(isliving(target_atom)) + var/mob/living/victim = target_atom + victim.Knockdown(2 SECONDS) + StartCooldown() + return TRUE /** * Removes a vine from the list. @@ -240,9 +245,24 @@ * Arguments: * * datum/beam/vine - The vine to be removed from the list. */ -/mob/living/simple_animal/hostile/venus_human_trap/proc/remove_vine(datum/beam/vine) +/datum/action/cooldown/vine_tangle/proc/remove_vine(datum/beam/vine) SIGNAL_HANDLER + qdel(vines[vine]) vines -= vine +/datum/ai_controller/basic_controller/human_trap + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability/continue_planning, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + #undef FINAL_BUD_GROWTH_ICON diff --git a/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm index c662870393c4..90b99a533f9f 100644 --- a/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm +++ b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm @@ -43,6 +43,9 @@ /mob/living/basic/mining/basilisk/bullet_act(obj/projectile/bullet, def_zone, piercing_hit) . = ..() + if(. != BULLET_ACT_HIT) + return + if (istype(bullet, /obj/projectile/temp)) var/obj/projectile/temp/heat_bullet = bullet if (heat_bullet.temperature < 0) @@ -74,7 +77,8 @@ /datum/ai_controller/basic_controller/basilisk blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_AGGRO_RANGE = 5, ) ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm index c3e753d992e7..b6f7468697a4 100644 --- a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm @@ -117,7 +117,7 @@ to_chat(devourer, span_warning("Someone stole your dinner!")) return to_chat(target, span_userdanger("You are consumed by [devourer]!")) - devourer.visible_message("[devourer] consumes [target]!") + devourer.visible_message(span_warning("[devourer] consumes [target]!")) devourer.fully_heal() playsound(devourer, 'sound/effects/splat.ogg', 50, TRUE) //to be recieved on death diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm index 11d7168776b6..ea979febff3e 100644 --- a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm @@ -1,6 +1,6 @@ /datum/ai_controller/basic_controller/bileworm blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/bileworm(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/bileworm, ) planning_subtrees = list( @@ -9,15 +9,13 @@ /datum/ai_planning_subtree/bileworm_execute, ) -/datum/targetting_datum/basic/bileworm +/datum/targeting_strategy/basic/bileworm ignore_sight = TRUE /datum/ai_planning_subtree/bileworm_attack /datum/ai_planning_subtree/bileworm_attack/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(target)) + if (!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) return var/datum/action/cooldown/mob_cooldown/resurface = controller.blackboard[BB_BILEWORM_RESURFACE] diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm new file mode 100644 index 000000000000..689bd280c1f9 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm @@ -0,0 +1,125 @@ +/// Fires a bloody beam. Brimdemon Blast! +/datum/action/cooldown/mob_cooldown/brimbeam + name = "brimstone blast" + desc = "Unleash a barrage of infernal energies in the targeted direction." + button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + button_icon_state = "brimdemon_firing" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + click_to_activate = TRUE + cooldown_time = 5 SECONDS + melee_cooldown_time = 0 + /// How far does our beam go? + var/beam_range = 10 + /// How long does our beam last? + var/beam_duration = 2 SECONDS + /// How long do we wind up before firing? + var/charge_duration = 1 SECONDS + /// Overlay we show when we're about to fire + var/static/image/direction_overlay = image('icons/mob/simple/lavaland/lavaland_monsters.dmi', "brimdemon_telegraph_dir") + /// A list of all the beam parts. + var/list/beam_parts = list() + +/datum/action/cooldown/mob_cooldown/brimbeam/Destroy() + extinguish_laser() + return ..() + +/datum/action/cooldown/mob_cooldown/brimbeam/Activate(atom/target) + StartCooldown(360 SECONDS) + + owner.face_atom(target) + owner.move_resist = MOVE_FORCE_VERY_STRONG + owner.add_overlay(direction_overlay) + owner.balloon_alert_to_viewers("charging...") + + var/fully_charged = do_after(owner, delay = charge_duration, target = owner) + owner.cut_overlay(direction_overlay) + if (!fully_charged) + StartCooldown() + return TRUE + + if (!fire_laser()) + var/static/list/fail_emotes = list("coughs.", "wheezes.", "belches out a puff of black smoke.") + owner.manual_emote(pick(fail_emotes)) + StartCooldown() + return TRUE + + do_after(owner, delay = beam_duration, target = owner) + extinguish_laser() + StartCooldown() + return TRUE + +/// Create a laser in the direction we are facing +/datum/action/cooldown/mob_cooldown/brimbeam/proc/fire_laser() + owner.visible_message(span_danger("[owner] fires a brimbeam!")) + playsound(owner, 'sound/creatures/brimdemon.ogg', 150, FALSE, 0, 3) + var/turf/target_turf = get_ranged_target_turf(owner, owner.dir, beam_range) + var/turf/origin_turf = get_turf(owner) + var/list/affected_turfs = get_line(origin_turf, target_turf) - origin_turf + for(var/turf/affected_turf in affected_turfs) + if(affected_turf.opacity) + break + var/blocked = FALSE + for(var/obj/potential_block in affected_turf.contents) + if(potential_block.opacity) + blocked = TRUE + break + if(blocked) + break + var/atom/new_brimbeam = new /obj/effect/brimbeam(affected_turf) + new_brimbeam.dir = owner.dir + beam_parts += new_brimbeam + for(var/mob/living/hit_mob in affected_turf.contents) + hit_mob.apply_damage(damage = 25, damagetype = BURN) + to_chat(hit_mob, span_userdanger("You're blasted by [owner]'s brimbeam!")) + RegisterSignal(new_brimbeam, COMSIG_PARENT_QDELETING, PROC_REF(extinguish_laser)) // In case idk a singularity eats it or something + if(!length(beam_parts)) + return FALSE + var/atom/last_brimbeam = beam_parts[length(beam_parts)] + last_brimbeam.icon_state = "brimbeam_end" + var/atom/first_brimbeam = beam_parts[1] + first_brimbeam.icon_state = "brimbeam_start" + return TRUE + +/// Get rid of our laser when we are done with it +/datum/action/cooldown/mob_cooldown/brimbeam/proc/extinguish_laser() + if(!length(beam_parts)) + return FALSE + owner.move_resist = initial(owner.move_resist) + for(var/obj/effect/brimbeam/beam in beam_parts) + beam.disperse() + beam_parts = list() + +/// Segments of the actual beam, these hurt if you stand in them +/obj/effect/brimbeam + name = "brimbeam" + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "brimbeam_mid" + layer = ABOVE_MOB_LAYER + plane = ABOVE_GAME_PLANE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + light_color = LIGHT_COLOR_BLOOD_MAGIC + light_power = 3 + light_outer_range = 2 + +/obj/effect/brimbeam/Initialize(mapload) + . = ..() + START_PROCESSING(SSfastprocess, src) + +/obj/effect/brimbeam/Destroy() + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/obj/effect/brimbeam/process() + for(var/mob/living/hit_mob in get_turf(src)) + damage(hit_mob) + +/// Hurt the passed mob +/obj/effect/brimbeam/proc/damage(mob/living/hit_mob) + hit_mob.apply_damage(damage = 5, damagetype = BURN) + to_chat(hit_mob, span_danger("You're damaged by [src]!")) + +/// Disappear +/obj/effect/brimbeam/proc/disperse() + animate(src, time = 0.5 SECONDS, alpha = 0) + QDEL_IN(src, 0.5 SECONDS) diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.dm new file mode 100644 index 000000000000..cbc6954f1a17 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.dm @@ -0,0 +1,87 @@ +/// Lavaland mob which tries to line up with its target and fire a laser +/mob/living/basic/mining/brimdemon + name = "brimdemon" + desc = "A volatile creature resembling an enormous horned skull. Its response to almost any stimulus is to unleash a beam of infernal energy." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "brimdemon" + icon_living = "brimdemon" + icon_dead = "brimdemon_dead" + speed = 3 + maxHealth = 250 + health = 250 + friendly_verb_continuous = "scratches at" + friendly_verb_simple = "scratch at" + speak_emote = list("cackles") + melee_damage_lower = 7.5 + melee_damage_upper = 7.5 + attack_sound = 'sound/weapons/bite.ogg' + melee_attack_cooldown = 0.6 SECONDS + attack_vis_effect = ATTACK_EFFECT_BITE + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + death_message = "wails as infernal energy escapes from its wounds, leaving it an empty husk." + death_sound = 'sound/magic/demon_dies.ogg' + light_color = LIGHT_COLOR_BLOOD_MAGIC + light_power = 5 + light_outer_range = 1.4 + + ai_controller = /datum/ai_controller/basic_controller/brimdemon + + crusher_loot = /obj/item/crusher_trophy/brimdemon_fang + butcher_results = list( + /obj/item/food/meat/slab = 2, + /obj/effect/decal/cleanable/brimdust = 1, + /obj/item/organ/internal/monster_core/brimdust_sac = 1, + ) + /// How we get blasting + var/datum/action/cooldown/mob_cooldown/brimbeam/beam + +/mob/living/basic/mining/brimdemon/Initialize(mapload) + . = ..() + AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) + beam = new(src) + beam.Grant(src) + ai_controller.set_blackboard_key(BB_TARGETED_ACTION, beam) + +/mob/living/basic/mining/brimdemon/Destroy() + QDEL_NULL(beam) + return ..() + +/mob/living/basic/mining/brimdemon/RangedAttack(atom/target, modifiers) + beam.Trigger(target = target) + +/mob/living/basic/mining/brimdemon/death(gibbed) + . = ..() + if (gibbed) + return + var/obj/effect/temp_visual/brim_burst/bang = new(loc) + forceMove(bang) + +/// Show a funny animation before doing an explosion +/obj/effect/temp_visual/brim_burst + name = "bursting brimdemon" + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "brimdemon_dead" + duration = 1.9 SECONDS + +/obj/effect/temp_visual/brim_burst/Initialize(mapload) + . = ..() + addtimer(CALLBACK(src, PROC_REF(bang)), duration - (1 DECISECONDS), TIMER_DELETE_ME) + animate(src, color = "#ff8888", transform = matrix().Scale(1.1), time = 0.7 SECONDS) + animate(color = "#ffffff", transform = matrix(), time = 0.2 SECONDS) + animate(color = "#ff4444", transform = matrix().Scale(1.3), time = 0.5 SECONDS) + animate(color = "#ffffff", transform = matrix(), time = 0.2 SECONDS) + animate(color = "#ff0000", transform = matrix().Scale(1.5), time = 0.3 SECONDS) + +/// Make an explosion +/obj/effect/temp_visual/brim_burst/proc/bang() + var/turf/origin_turf = get_turf(src) + playsound(origin_turf, 'sound/effects/pop_expl.ogg', 50) + new /obj/effect/temp_visual/explosion/fast(origin_turf) + var/list/possible_targets = range(1, origin_turf) + for(var/mob/living/target in possible_targets) + var/armor = target.run_armor_check(attack_flag = BOMB) + target.apply_damage(20, damagetype = BURN, blocked = armor, spread_damage = TRUE) + + for (var/atom/movable/thing as anything in contents) + thing.forceMove(loc) diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm new file mode 100644 index 000000000000..fe209387e008 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm @@ -0,0 +1,48 @@ +/** + * Slap someone who is nearby, line up with target, blast with a beam + */ +/datum/ai_controller/basic_controller/brimdemon + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + ) + + ai_traits = PAUSE_DURING_DO_AFTER + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk/no_target + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic, + /datum/ai_planning_subtree/move_to_cardinal/brimdemon, + /datum/ai_planning_subtree/targeted_mob_ability/brimbeam, + ) + +/datum/ai_planning_subtree/move_to_cardinal/brimdemon + move_behaviour = /datum/ai_behavior/move_to_cardinal/brimdemon + +/datum/ai_behavior/move_to_cardinal/brimdemon + minimum_distance = 2 + +/datum/ai_behavior/move_to_cardinal/brimdemon/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + if (!succeeded) + return + var/mob/living/target = controller.blackboard[target_key] + var/datum/action/cooldown/ability = controller.blackboard[BB_TARGETED_ACTION] + if(!ability?.IsAvailable()) + return + ability.InterceptClickOn(caller = controller.pawn, target = target) + +/datum/ai_planning_subtree/targeted_mob_ability/brimbeam + use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/brimbeam + +/datum/ai_behavior/targeted_mob_ability/brimbeam + /// Don't shoot if too far away + var/max_target_distance = 9 + +/datum/ai_behavior/targeted_mob_ability/brimbeam/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) + var/mob/living/target = controller.blackboard[target_key] + if (QDELETED(target) || !(get_dir(controller.pawn, target) in GLOB.cardinals) || get_dist(controller.pawn, target) > max_target_distance) + finish_action(controller, succeeded = FALSE, ability_key = ability_key, target_key = target_key) + return + return ..() diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_loot.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_loot.dm new file mode 100644 index 000000000000..9a45ed99e1c0 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_loot.dm @@ -0,0 +1,56 @@ +/// Brimdemon crusher trophy, it... makes a funny sound? +/obj/item/crusher_trophy/brimdemon_fang + name = "brimdemon's fang" + icon_state = "brimdemon_fang" + desc = "A fang from a brimdemon's corpse." + denied_type = /obj/item/crusher_trophy/brimdemon_fang + /// Cartoon punching vfx + var/static/list/comic_phrases = list("BOOM", "BANG", "KABLOW", "KAPOW", "OUCH", "BAM", "KAPOW", "WHAM", "POW", "KABOOM") + +/obj/item/crusher_trophy/brimdemon_fang/effect_desc() + return "mark detonation creates visual and audiosensory effects on the target" + +/obj/item/crusher_trophy/brimdemon_fang/on_mark_detonation(mob/living/target, mob/living/user) + target.balloon_alert_to_viewers("[pick(comic_phrases)]!") + playsound(target, 'sound/lavaland/brimdemon_crush.ogg', 100) + +/// Reagent pool left by dying brimdemon +/obj/effect/decal/cleanable/brimdust + name = "brimdust" + desc = "Dust from a brimdemon. It is considered valuable for its' botanical abilities." + icon_state = "brimdust" + icon = 'icons/obj/mining.dmi' + layer = FLOOR_CLEAN_LAYER + mergeable_decal = FALSE + +/obj/effect/decal/cleanable/brimdust/Initialize(mapload) + . = ..() + reagents.add_reagent(/datum/reagent/brimdust, 15) + +/// Ashwalker ore sensor crafted from brimdemon ash +/obj/item/ore_sensor + name = "ore sensor" + desc = "Using demonic frequencies, this ear-mounted tool detects ores in the nearby terrain." + icon_state = "oresensor" + icon = 'icons/obj/mining.dmi' + slot_flags = ITEM_SLOT_EARS + var/range = 5 + var/cooldown = 4 SECONDS //between the standard and the advanced ore scanner in strength + COOLDOWN_DECLARE(ore_sensing_cooldown) + +/obj/item/ore_sensor/equipped(mob/user, slot, initial) + . = ..() + if(slot & ITEM_SLOT_EARS) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + +/obj/item/ore_sensor/dropped(mob/user, silent) + . = ..() + STOP_PROCESSING(SSobj, src) + +/obj/item/ore_sensor/process(seconds_per_tick) + if(!COOLDOWN_FINISHED(src, ore_sensing_cooldown)) + return + COOLDOWN_START(src, ore_sensing_cooldown, cooldown) + mineral_scan_pulse(get_turf(src), range) diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm index cb42ea180fee..8e3a66eb891e 100644 --- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm @@ -35,7 +35,7 @@ /datum/pet_command/free, /datum/pet_command/grub_spit, /datum/pet_command/follow, - /datum/pet_command/point_targetting/fetch, + /datum/pet_command/point_targeting/fetch, ) /mob/living/basic/mining/goldgrub/Initialize(mapload) @@ -44,13 +44,13 @@ if(mapload) generate_loot() - var/datum/action/cooldown/mob_cooldown/spit_ore/spit = new(src) - var/datum/action/cooldown/mob_cooldown/burrow/burrow = new(src) - spit.Grant(src) - burrow.Grant(src) - ai_controller.set_blackboard_key(BB_SPIT_ABILITY, spit) - ai_controller.set_blackboard_key(BB_BURROW_ABILITY, burrow) - AddElement(/datum/element/wall_smasher) + var/static/list/innate_actions = list( + /datum/action/cooldown/mob_cooldown/spit_ore = BB_SPIT_ABILITY, + /datum/action/cooldown/mob_cooldown/burrow = BB_BURROW_ABILITY, + ) + grant_actions_by_list(innate_actions) + AddElement(/datum/element/ore_collecting) + AddElement(/datum/element/wall_tearer, allow_reinforced = FALSE) AddComponent(/datum/component/ai_listen_to_weather) AddComponent(\ /datum/component/appearance_on_aggro,\ @@ -62,23 +62,16 @@ if(can_lay_eggs) make_egg_layer() -/mob/living/basic/mining/goldgrub/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) - . = ..() - if(!.) - return - - if(!proximity_flag) - return + RegisterSignal(src, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(block_bullets)) - if(istype(attack_target, /obj/item/stack/ore)) - consume_ore(attack_target) +/mob/living/basic/mining/goldgrub/proc/block_bullets(datum/source, obj/projectile/hitting_projectile) + SIGNAL_HANDLER -/mob/living/basic/mining/goldgrub/bullet_act(obj/projectile/bullet) - if(stat == DEAD) - return BULLET_ACT_FORCE_PIERCE + if(stat != CONSCIOUS) + return COMPONENT_BULLET_PIERCED - visible_message(span_danger("The [bullet.name] is repelled by [src]'s girth!")) - return BULLET_ACT_BLOCK + visible_message(span_danger("[hitting_projectile] is repelled by [source]'s girth!")) + return COMPONENT_BULLET_BLOCKED /mob/living/basic/mining/goldgrub/proc/barf_contents(gibbed) playsound(src, 'sound/effects/splat.ogg', 50, TRUE) @@ -131,12 +124,14 @@ max_eggs_held = 1,\ ) -/mob/living/basic/mining/goldgrub/proc/consume_ore(obj/item/target_ore) +/mob/living/basic/mining/goldgrub/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + if(!istype(arrived, /obj/item/stack/ore)) + return playsound(src,'sound/items/eatfood.ogg', rand(10,50), TRUE) - target_ore.forceMove(src) if(!can_lay_eggs) return - if(!istype(target_ore, /obj/item/stack/ore/bluespace_crystal) || prob(60)) + if(!istype(arrived, /obj/item/stack/ore/bluespace_crystal) || prob(60)) return new /obj/item/food/egg/green/grub_egg(get_turf(src)) @@ -181,7 +176,7 @@ var/list/friends = src.ai_controller.blackboard[BB_FRIENDS_LIST] var/mob/living/basic/mining/goldgrub/transformed_mob = src.change_mob_type(/mob/living/basic/mining/goldgrub, src.loc, new_name = new_mob_name, delete_old_mob = TRUE) transformed_mob.ai_controller.blackboard[BB_FRIENDS_LIST] = friends - + if(length(friends)) transformed_mob.tame_grub() @@ -204,4 +199,3 @@ current_growth = 0,\ location_allowlist = typecacheof(list(/turf)),\ ) - diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm index 12e7d4c8ff14..53054052e58a 100644 --- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm @@ -1,9 +1,8 @@ /datum/ai_controller/basic_controller/goldgrub blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_ORE_IGNORE_TYPES = list(/obj/item/stack/ore/iron, /obj/item/stack/ore/glass), - BB_BASIC_MOB_FLEEING = TRUE, BB_STORM_APPROACHING = FALSE, ) @@ -14,18 +13,17 @@ /datum/ai_planning_subtree/pet_planning, /datum/ai_planning_subtree/dig_away_from_danger, /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/find_and_hunt_target/consume_ores, + /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores, /datum/ai_planning_subtree/find_and_hunt_target/baby_egg, - /datum/ai_planning_subtree/grub_mine, + /datum/ai_planning_subtree/mine_walls, ) /datum/ai_controller/basic_controller/babygrub blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_ORE_IGNORE_TYPES = list(/obj/item/stack/ore/glass), BB_FIND_MOM_TYPES = list(/mob/living/basic/mining/goldgrub), BB_IGNORE_MOM_TYPES = list(/mob/living/basic/mining/goldgrub/baby), - BB_BASIC_MOB_FLEEING = TRUE, BB_STORM_APPROACHING = FALSE, ) @@ -34,29 +32,29 @@ planning_subtrees = list( /datum/ai_planning_subtree/simple_find_target, /datum/ai_planning_subtree/dig_away_from_danger, - /datum/ai_planning_subtree/find_and_hunt_target/consume_ores, + /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores, /datum/ai_planning_subtree/flee_target, /datum/ai_planning_subtree/look_for_adult, ) ///consume food! -/datum/ai_planning_subtree/find_and_hunt_target/consume_ores +/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores target_key = BB_ORE_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores - finding_behavior = /datum/ai_behavior/find_hunt_target/consume_ores + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/hunt_ores + finding_behavior = /datum/ai_behavior/find_hunt_target/hunt_ores hunt_targets = list(/obj/item/stack/ore) hunt_chance = 75 hunt_range = 9 -/datum/ai_behavior/find_hunt_target/consume_ores +/datum/ai_behavior/find_hunt_target/hunt_ores -/datum/ai_behavior/find_hunt_target/consume_ores/valid_dinner(mob/living/basic/source, obj/item/stack/ore/target, radius) +/datum/ai_behavior/find_hunt_target/hunt_ores/valid_dinner(mob/living/basic/source, obj/item/stack/ore/target, radius) var/list/forbidden_ore = source.ai_controller.blackboard[BB_ORE_IGNORE_TYPES] if(is_type_in_list(target, forbidden_ore)) return FALSE - if(target in source) + if(!isturf(target.loc)) return FALSE var/obj/item/pet_target = source.ai_controller.blackboard[BB_CURRENT_PET_TARGET] @@ -65,7 +63,7 @@ return can_see(source, target, radius) -/datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores +/datum/ai_behavior/hunt_target/unarmed_attack_target/hunt_ores always_reset_target = TRUE ///find our child's egg and pull it! @@ -108,93 +106,21 @@ if(!dig_ability.IsAvailable()) return - var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + var/has_target = controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) //a storm is coming or someone is nearby, its time to escape - if(currently_underground || !currently_underground && storm_approaching || !QDELETED(target)) + if(currently_underground) + if(has_target) + return + controller.queue_behavior(/datum/ai_behavior/use_mob_ability/burrow, BB_BURROW_ABILITY) + return SUBTREE_RETURN_FINISH_PLANNING + if(storm_approaching || has_target) controller.queue_behavior(/datum/ai_behavior/use_mob_ability/burrow, BB_BURROW_ABILITY) return SUBTREE_RETURN_FINISH_PLANNING /datum/ai_behavior/use_mob_ability/burrow behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION -///mine walls to look for food! -/datum/ai_planning_subtree/grub_mine - -/datum/ai_planning_subtree/grub_mine/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/turf/target_wall = controller.blackboard[BB_TARGET_MINERAL_WALL] - - if(QDELETED(target_wall)) - controller.queue_behavior(/datum/ai_behavior/find_mineral_wall, BB_TARGET_MINERAL_WALL) - return - - controller.queue_behavior(/datum/ai_behavior/mine_wall, BB_TARGET_MINERAL_WALL) - return SUBTREE_RETURN_FINISH_PLANNING - - -/datum/ai_behavior/find_mineral_wall - -/datum/ai_behavior/find_mineral_wall/perform(seconds_per_tick, datum/ai_controller/controller, found_wall_key) - . = ..() - - var/mob/living_pawn = controller.pawn - - for(var/turf/closed/mineral/potential_wall in oview(9, living_pawn)) - if(!check_if_mineable(living_pawn, potential_wall)) //check if its surrounded by walls - continue - controller.set_blackboard_key(found_wall_key, potential_wall) //closest wall first! - finish_action(controller, TRUE) - return - - finish_action(controller, FALSE) - -/datum/ai_behavior/find_mineral_wall/proc/check_if_mineable(mob/living/source, turf/target_wall) - var/direction_to_turf = get_dir(target_wall, source) - if(!ISDIAGONALDIR(direction_to_turf)) - return TRUE - var/list/directions_to_check = list() - for(var/direction_check in GLOB.cardinals) - if(direction_check & direction_to_turf) - directions_to_check += direction_check - - for(var/direction in directions_to_check) - var/turf/test_turf = get_step(target_wall, direction) - if(isnull(test_turf)) - continue - if(!test_turf.is_blocked_turf(ignore_atoms = list(source))) - return TRUE - return FALSE - -/datum/ai_behavior/mine_wall - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - action_cooldown = 15 SECONDS - -/datum/ai_behavior/mine_wall/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/mine_wall/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - . = ..() - var/mob/living/basic/living_pawn = controller.pawn - var/turf/closed/mineral/target = controller.blackboard[target_key] - var/is_gibtonite_turf = istype(target, /turf/closed/mineral/gibtonite) - if(QDELETED(target)) - finish_action(controller, FALSE, target_key) - return - living_pawn.melee_attack(target) - if(is_gibtonite_turf) - living_pawn.manual_emote("sighs...") //accept whats about to happen to us - - finish_action(controller, TRUE, target_key) - return - -/datum/ai_behavior/mine_wall/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - /datum/pet_command/grub_spit command_name = "Spit" command_desc = "Ask your grub pet to spit out its ores." @@ -202,7 +128,7 @@ /datum/pet_command/grub_spit/execute_action(datum/ai_controller/controller) var/datum/action/cooldown/spit_ability = controller.blackboard[BB_SPIT_ABILITY] - if(QDELETED(spit_ability) || !spit_ability.IsAvailable()) + if(!spit_ability?.IsAvailable()) return controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_SPIT_ABILITY) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath.dm index c9a8b0b07314..d1e11ea4cd2d 100644 --- a/code/modules/mob/living/basic/lavaland/goliath/goliath.dm +++ b/code/modules/mob/living/basic/lavaland/goliath/goliath.dm @@ -44,6 +44,8 @@ COOLDOWN_DECLARE(ability_animation_cooldown) /// Our base tentacles ability var/datum/action/cooldown/mob_cooldown/goliath_tentacles/tentacles + /// Our long-ranged tentacles ability + var/datum/action/cooldown/mob_cooldown/tentacle_grasp/tentacle_line /// Things we want to eat off the floor (or a plate, we're not picky) var/static/list/goliath_foods = list(/obj/item/food/grown/ash_flora, /obj/item/food/bait/worm) @@ -53,7 +55,7 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targetting/fetch, + /datum/pet_command/point_targeting/fetch, ) //monkestation edit @@ -80,10 +82,10 @@ tentacles.Grant(src) var/datum/action/cooldown/mob_cooldown/tentacle_burst/melee_tentacles = new (src) melee_tentacles.Grant(src) - AddComponent(/datum/component/revenge_ability, melee_tentacles, targetting = ai_controller.blackboard[BB_TARGETTING_DATUM], max_range = 1, target_self = TRUE) - var/datum/action/cooldown/mob_cooldown/tentacle_grasp/ranged_tentacles = new (src) - ranged_tentacles.Grant(src) - AddComponent(/datum/component/revenge_ability, ranged_tentacles, targetting = ai_controller.blackboard[BB_TARGETTING_DATUM], min_range = 2, max_range = 9) + AddComponent(/datum/component/revenge_ability, melee_tentacles, targeting = GET_TARGETING_STRATEGY(ai_controller.blackboard[BB_TARGETING_STRATEGY]), max_range = 1, target_self = TRUE) + tentacle_line = new (src) + tentacle_line.Grant(src) + AddComponent(/datum/component/revenge_ability, tentacle_line, targeting = GET_TARGETING_STRATEGY(ai_controller.blackboard[BB_TARGETING_STRATEGY]), min_range = 2, max_range = 9) tentacles_ready() RegisterSignal(src, COMSIG_MOB_ABILITY_FINISHED, PROC_REF(used_ability)) @@ -93,6 +95,7 @@ /mob/living/basic/mining/goliath/Destroy() QDEL_NULL(tentacles) + QDEL_NULL(tentacle_line) return ..() /mob/living/basic/mining/goliath/examine(mob/user) @@ -117,9 +120,7 @@ // Goliaths can summon tentacles more frequently as they take damage, scary. /mob/living/basic/mining/goliath/apply_damage(damage, damagetype, def_zone, blocked, forced, spread_damage, wound_bonus, bare_wound_bonus, sharpness, attack_direction, attacking_item) . = ..() - if (!.) - return - if (damage <= 0) + if (. <= 0) return if (tentacles.cooldown_time > 1 SECONDS) tentacles.cooldown_time -= 1 SECONDS @@ -185,6 +186,12 @@ . = ..() faction = new_friend.faction.Copy() +/mob/living/basic/mining/goliath/RangedAttack(atom/atom_target, modifiers) + tentacles?.Trigger(target = atom_target) + +/mob/living/basic/mining/goliath/ranged_secondary_attack(atom/atom_target, modifiers) + tentacle_line?.Trigger(target = atom_target) + /// Legacy Goliath mob with different sprites, largely the same behaviour /mob/living/basic/mining/goliath/ancient name = "ancient goliath" diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm index bb8adaf61c3e..31eecc036290 100644 --- a/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm +++ b/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm @@ -1,7 +1,7 @@ /// Place some grappling tentacles underfoot /datum/action/cooldown/mob_cooldown/goliath_tentacles name = "Unleash Tentacles" - desc = "Unleash burrowed tentacles at a targetted location, grappling targets after a delay." + desc = "Unleash burrowed tentacles at a targeted location, grappling targets after a delay." button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' button_icon_state = "goliath_tentacle_wiggle" background_icon_state = "bg_demon" @@ -9,7 +9,6 @@ click_to_activate = TRUE cooldown_time = 12 SECONDS melee_cooldown_time = 0 - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED shared_cooldown = NONE /// Furthest range we can activate ability at var/max_range = 7 @@ -44,7 +43,6 @@ overlay_icon_state = "bg_demon_border" cooldown_time = 24 SECONDS melee_cooldown_time = 0 - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED shared_cooldown = NONE click_to_activate = FALSE @@ -61,7 +59,7 @@ /// Summon a line of tentacles towards the target /datum/action/cooldown/mob_cooldown/tentacle_grasp name = "Tentacle Grasp" - desc = "Unleash burrowed tentacles in a line towards a targetted location, grappling targets after a delay." + desc = "Unleash burrowed tentacles in a line towards a targeted location, grappling targets after a delay." button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' button_icon_state = "goliath_tentacle_wiggle" background_icon_state = "bg_demon" @@ -69,7 +67,6 @@ click_to_activate = TRUE cooldown_time = 12 SECONDS melee_cooldown_time = 0 - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED shared_cooldown = NONE /datum/action/cooldown/mob_cooldown/tentacle_grasp/Activate(atom/target) diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm index 9774c3255352..066b25cffa46 100644 --- a/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm +++ b/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm @@ -3,13 +3,14 @@ /datum/ai_controller/basic_controller/goliath blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items/goliath, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) ai_movement = /datum/ai_movement/basic_avoidance idle_behavior = /datum/idle_behavior/idle_random_walk planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/target_retaliate/check_faction, /datum/ai_planning_subtree/simple_find_target, /datum/ai_planning_subtree/pet_planning, /datum/ai_planning_subtree/find_food, @@ -20,9 +21,6 @@ /datum/ai_planning_subtree/goliath_dig, ) -/datum/targetting_datum/basic/allow_items/goliath - stat_attack = HARD_CRIT - /datum/ai_planning_subtree/basic_melee_attack_subtree/goliath operational_datums = list(/datum/component/ai_target_timer) melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/goliath @@ -30,7 +28,7 @@ /// Go for the tentacles if they're available /datum/ai_behavior/basic_melee_attack/goliath -/datum/ai_behavior/basic_melee_attack/goliath/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key, health_ratio_key) +/datum/ai_behavior/basic_melee_attack/goliath/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, health_ratio_key) var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 if (time_on_target < MIN_TIME_TO_TENTACLE) return ..() @@ -97,8 +95,7 @@ var/target_key = BB_GOLIATH_HOLE_TARGET /datum/ai_planning_subtree/goliath_dig/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/turf/target_turf = controller.blackboard[target_key] - if (QDELETED(target_turf)) + if (!controller.blackboard_key_exists(target_key)) return controller.queue_behavior(/datum/ai_behavior/goliath_dig, target_key) return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutluncher_foodtrough.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutluncher_foodtrough.dm new file mode 100644 index 000000000000..16139da00bec --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutluncher_foodtrough.dm @@ -0,0 +1,40 @@ +/obj/structure/ore_container/gutlunch_trough + name = "gutlunch trough" + desc = "The gutlunches will eat out of it!" + icon = 'icons/obj/structures.dmi' + icon_state = "gutlunch_trough" + density = TRUE + anchored = TRUE + ///list of materials in the trough + var/list/list_of_materials = list() + +/obj/structure/ore_container/gutlunch_trough/Entered(atom/movable/mover) + if(!istype(mover, /obj/item/stack/ore)) + return ..() + if(list_of_materials[mover.type]) + return ..() + list_of_materials[mover.type] = list("pixel_x" = rand(-5, 8), "pixel_y" = rand(-2, -7)) + return ..() + +/obj/structure/ore_container/gutlunch_trough/Exited(atom/movable/mover) + if(!istype(mover, /obj/item/stack/ore) || !isnull(locate(mover.type) in contents)) + return ..() + list_of_materials -= mover.type + return ..() + +/obj/structure/ore_container/gutlunch_trough/deconstruct(disassembled = TRUE) + if(flags_1 & NODECONSTRUCT_1) + return + new /obj/item/stack/sheet/mineral/wood(drop_location(), 5) + qdel(src) + +/obj/structure/ore_container/gutlunch_trough/update_overlays() + . = ..() + for(var/ore_entry in list_of_materials) + var/obj/item/ore_item = ore_entry + var/image/ore_icon = image(icon = initial(ore_item.icon), icon_state = initial(ore_item.icon_state), layer = LOW_ITEM_LAYER) + var/list/pixel_positions = list_of_materials[ore_entry] + ore_icon.transform = ore_icon.transform.Scale(0.4, 0.4) + ore_icon.pixel_x = pixel_positions["pixel_x"] + ore_icon.pixel_y = pixel_positions["pixel_y"] + . += ore_icon diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm new file mode 100644 index 000000000000..d075bedd0af8 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm @@ -0,0 +1,166 @@ +#define MAX_ATTACK_DIFFERENCE 3 +#define MAX_LOWER_ATTACK 15 +#define MINIMUM_POSSIBLE_SPEED 1 +#define MAX_POSSIBLE_HEALTH 100 + +/mob/living/basic/mining/gutlunch + name = "gutlunch" + desc = "A scavenger that eats raw ores, often found alongside ash walkers. Produces a thick, nutritious milk." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "gutlunch" + icon_living = "gutlunch" + icon_dead = "gutlunch" + mob_biotypes = MOB_ORGANIC|MOB_BEAST + basic_mob_flags = DEL_ON_DEATH + speak_emote = list("warbles", "quavers") + faction = list(FACTION_MINING, FACTION_ASHWALKER) + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently pushes aside" + response_disarm_simple = "gently push aside" + response_harm_continuous = "squishes" + response_harm_simple = "squish" + friendly_verb_continuous = "pinches" + friendly_verb_simple = "pinch" + gold_core_spawnable = FRIENDLY_SPAWN + death_message = "is pulped into bugmash." + greyscale_config = /datum/greyscale_config/gutlunch + ///possible colors we can have + var/list/possible_colors = list(COLOR_WHITE) + ///can we breed? + var/can_breed = TRUE + +/mob/living/basic/mining/gutlunch/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + if(greyscale_config) + set_greyscale(colors = list(pick(possible_colors))) + AddElement(/datum/element/ai_retaliate) + if(!can_breed) + return + AddComponent(\ + /datum/component/breed,\ + can_breed_with = typecacheof(list(/mob/living/basic/mining/gutlunch)),\ + baby_path = /mob/living/basic/mining/gutlunch/grub,\ + post_birth = CALLBACK(src, PROC_REF(after_birth)),\ + breed_timer = 3 MINUTES,\ + ) + +/mob/living/basic/mining/gutlunch/proc/pre_attack(mob/living/puncher, atom/target) + SIGNAL_HANDLER + + if(!istype(target, /obj/structure/ore_container/gutlunch_trough)) + return + + var/obj/ore_food = locate(/obj/item/stack/ore) in target + + if(isnull(ore_food)) + balloon_alert(src, "no food!") + else + melee_attack(ore_food) + return COMPONENT_HOSTILE_NO_ATTACK + +/mob/living/basic/mining/gutlunch/proc/after_birth(mob/living/basic/mining/gutlunch/grub/baby, mob/living/partner) + var/our_color = LAZYACCESS(atom_colours, FIXED_COLOUR_PRIORITY) || COLOR_GRAY + var/partner_color = LAZYACCESS(partner.atom_colours, FIXED_COLOUR_PRIORITY) || COLOR_GRAY + baby.add_atom_colour(BlendRGB(our_color, partner_color, 1), FIXED_COLOUR_PRIORITY) + var/atom/male_parent = (gender == MALE) ? src : partner + baby.inherited_stats = new(male_parent) + +/mob/living/basic/mining/gutlunch/proc/roll_stats(input_attack, input_speed, input_health) + melee_damage_lower = rand(input_attack, min(MAX_LOWER_ATTACK, input_attack + MAX_ATTACK_DIFFERENCE)) + melee_damage_upper = melee_damage_lower + MAX_ATTACK_DIFFERENCE + speed = rand(MINIMUM_POSSIBLE_SPEED, input_speed) + maxHealth = rand(input_health, MAX_POSSIBLE_HEALTH) + health = maxHealth + +/mob/living/basic/mining/gutlunch/milk + name = "gubbuck" + gender = FEMALE + possible_colors = list("#E39FBB","#817178","#9d667d") + ai_controller = /datum/ai_controller/basic_controller/gutlunch/gutlunch_milk + ///overlay we display when our udder is full! + var/mutable_appearance/full_udder + +/mob/living/basic/mining/gutlunch/milk/Initialize(mapload) + . = ..() + var/datum/callback/milking_callback = CALLBACK(src, TYPE_PROC_REF(/atom/movable, update_overlays)) + AddComponent(\ + /datum/component/udder,\ + udder_type = /obj/item/udder/gutlunch,\ + on_milk_callback = milking_callback,\ + on_generate_callback = milking_callback,\ + ) + full_udder = mutable_appearance(icon, "gl_full") + full_udder.color = LAZYACCESS(atom_colours, FIXED_COLOUR_PRIORITY) || COLOR_GRAY + +/mob/living/basic/mining/gutlunch/warrior + name = "gunther" + gender = MALE + melee_damage_lower = 8 + melee_damage_upper = 13 + speed = 5 + maxHealth = 70 + health = 70 + ai_controller = /datum/ai_controller/basic_controller/gutlunch/gutlunch_warrior + possible_colors = list("#6d77ff","#8578e4","#97b6f6") + //pet commands when we tame the gutluncher + var/static/list/pet_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/point_targeting/attack, + /datum/pet_command/point_targeting/breed, + /datum/pet_command/follow, + /datum/pet_command/point_targeting/fetch, + /datum/pet_command/mine_walls, + ) + +/mob/living/basic/mining/gutlunch/warrior/Initialize(mapload) + . = ..() + roll_stats(melee_damage_lower, speed, maxHealth) + AddComponent(/datum/component/obeys_commands, pet_commands) + AddElement(/datum/element/wall_tearer, allow_reinforced = FALSE) + +/mob/living/basic/mining/gutlunch/milk/update_overlays(new_udder_volume, max_udder_volume) + . = ..() + if(new_udder_volume != max_udder_volume) + return + . += full_udder + +/mob/living/basic/mining/gutlunch/grub + name = "grublunch" + possible_colors = list("#cc9797", "#b74c4c") + can_breed = FALSE + gender = NEUTER + ai_controller = /datum/ai_controller/basic_controller/gutlunch/gutlunch_baby + ///list of stats we inherited + var/datum/gutlunch_inherited_stats/inherited_stats + +/mob/living/basic/mining/gutlunch/grub/Initialize(mapload) + . = ..() + transform = transform.Scale(0.6, 0.6) + AddComponent(\ + /datum/component/growth_and_differentiation,\ + growth_time = 3 MINUTES,\ + growth_probability = 100,\ + lower_growth_value = 0.5,\ + upper_growth_value = 1,\ + signals_to_kill_on = list(COMSIG_MOB_CLIENT_LOGIN),\ + optional_checks = CALLBACK(src, PROC_REF(ready_to_grow)),\ + optional_grow_behavior = CALLBACK(src, PROC_REF(determine_growth_path)),\ + ) + +/mob/living/basic/mining/gutlunch/grub/proc/ready_to_grow() + return (stat == CONSCIOUS) + +/mob/living/basic/mining/gutlunch/grub/proc/determine_growth_path() + var/final_type = prob(50) ? /mob/living/basic/mining/gutlunch/warrior : /mob/living/basic/mining/gutlunch/milk + var/mob/living/basic/mining/gutlunch/grown_mob = new final_type(get_turf(src)) + if(grown_mob.gender == MALE && inherited_stats) + grown_mob.roll_stats(inherited_stats.attack, inherited_stats.speed, inherited_stats.health) + qdel(src) + +#undef MAX_ATTACK_DIFFERENCE +#undef MAX_LOWER_ATTACK +#undef MINIMUM_POSSIBLE_SPEED +#undef MAX_POSSIBLE_HEALTH diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm new file mode 100644 index 000000000000..57893b294ce3 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm @@ -0,0 +1,119 @@ +/datum/ai_controller/basic_controller/gutlunch + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + +/datum/ai_controller/basic_controller/gutlunch/gutlunch_warrior + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/mining/gutlunch/milk), + BB_BABIES_CHILD_TYPES = list(/mob/living/basic/mining/gutlunch/grub), + BB_MAX_CHILDREN = 5, + ) + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate/check_faction, + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/make_babies, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/befriend_ashwalkers, + ) + +///find ashwalkers and add them to the list of masters +/datum/ai_planning_subtree/befriend_ashwalkers + +/datum/ai_planning_subtree/befriend_ashwalkers/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + controller.queue_behavior(/datum/ai_behavior/befriend_ashwalkers) + +/datum/ai_behavior/befriend_ashwalkers + action_cooldown = 5 SECONDS + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/befriend_ashwalkers/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + var/mob/living/living_pawn = controller.pawn + + for(var/mob/living/potential_friend in oview(9, living_pawn)) + if(!isashwalker(potential_friend)) + continue + if((living_pawn.faction.Find(REF(potential_friend)))) + continue + living_pawn.befriend(potential_friend) + to_chat(potential_friend, span_nicegreen("[living_pawn] looks at you with endearing eyes!")) + finish_action(controller, TRUE) + return + + finish_action(controller, FALSE) + return + + + +/datum/ai_controller/basic_controller/gutlunch/gutlunch_baby + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_FIND_MOM_TYPES = list(/mob/living/basic/mining/gutlunch/milk), + ) + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/flee_target, + /datum/ai_planning_subtree/look_for_adult, + ) + +/datum/ai_controller/basic_controller/gutlunch/gutlunch_milk + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/flee_target, + /datum/ai_planning_subtree/find_and_hunt_target/food_trough + ) + +///consume food! +/datum/ai_planning_subtree/find_and_hunt_target/food_trough + target_key = BB_TROUGH_TARGET + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/food_trough + finding_behavior = /datum/ai_behavior/find_hunt_target/food_trough + hunt_targets = list(/obj/structure/ore_container/gutlunch_trough) + hunt_chance = 75 + hunt_range = 9 + + +/datum/ai_planning_subtree/find_and_hunt_target/food_trough/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard[BB_CHECK_HUNGRY]) + return + return ..() + +/datum/ai_behavior/find_hunt_target/food_trough + +/datum/ai_behavior/find_hunt_target/food_trough/valid_dinner(mob/living/basic/source, obj/target, radius) + if(isnull(target)) + return FALSE + + if(isnull(locate(/obj/item/stack/ore) in target)) + return FALSE + + return can_see(source, target, radius) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/food_trough + always_reset_target = TRUE + switch_combat_mode = TRUE + +/datum/pet_command/mine_walls + command_name = "Mine" + command_desc = "Command your pet to mine down walls." + speech_commands = list("mine", "smash") + +/datum/pet_command/mine_walls/try_activate_command(mob/living/commander) + var/mob/living/parent = weak_parent.resolve() + if(isnull(parent)) + return + //no walls for us to mine + var/target_in_vicinity = locate(/turf/closed/mineral) in oview(9, parent) + if(isnull(target_in_vicinity)) + return + return ..() + +/datum/pet_command/mine_walls/execute_action(datum/ai_controller/controller) + if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) + controller.queue_behavior(/datum/ai_behavior/mine_wall, BB_CURRENT_PET_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_mineral_wall, BB_CURRENT_PET_TARGET) diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_inherit_datum.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_inherit_datum.dm new file mode 100644 index 000000000000..7051dfc2f4b2 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_inherit_datum.dm @@ -0,0 +1,14 @@ +///stats we inherit from the parent +/datum/gutlunch_inherited_stats + ///attack we inherited + var/attack + ///speed we inherited + var/speed + ///health we inherited + var/health + +/datum/gutlunch_inherited_stats/New(mob/living/basic/parent) + . = ..() + attack = parent.melee_damage_lower + speed = parent.speed + health = parent.maxHealth diff --git a/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm new file mode 100644 index 000000000000..c3365d2304bc --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm @@ -0,0 +1,142 @@ +/// Mob which retreats and spawns annoying sub-mobs to attack you +/mob/living/basic/mining/hivelord + name = "hivelord" + desc = "A levitating swarm of tiny creatures which act as a single individual. When threatened or hunting they rapidly replicate additional short-lived bodies." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "hivelord" + icon_living = "hivelord" + // icon_aggro = "hivelord_alert" + icon_dead = "hivelord_dead" + icon_gib = "syndicate_gib" + mob_biotypes = MOB_ORGANIC + speed = 2 + maxHealth = 75 + health = 75 + melee_damage_lower = 0 + melee_damage_upper = 0 + attack_verb_continuous = "weakly tackles" + attack_verb_simple = "weakly tackles" + speak_emote = list("telepathically cries") + attack_sound = 'sound/weapons/pierce.ogg' + throw_blocked_message = "passes between the bodies of the" + obj_damage = 0 + pass_flags = PASSTABLE + ai_controller = /datum/ai_controller/basic_controller/hivelord + /// Mobs to spawn when we die, varedit this to be recursive to give the players a fun surprise + var/death_spawn_type = /mob/living/basic/hivelord_brood + /// Action which spawns worms + var/datum/action/cooldown/mob_cooldown/hivelord_spawn/spawn_brood + +/mob/living/basic/mining/hivelord/Initialize(mapload) + . = ..() + var/static/list/death_loot = list(/obj/item/organ/internal/monster_core/regenerative_core) + AddElement(/datum/element/relay_attackers) + AddElement(/datum/element/death_drops, death_loot) + AddComponent(/datum/component/clickbox, icon_state = "hivelord", max_scale = INFINITY, dead_state = "hivelord_dead") // They writhe so much. + AddComponent(/datum/component/appearance_on_aggro, aggro_state = "hivelord_alert") + spawn_brood = new(src) + spawn_brood.Grant(src) + ai_controller.set_blackboard_key(BB_TARGETED_ACTION, spawn_brood) + +/mob/living/basic/mining/hivelord/Destroy() + QDEL_NULL(spawn_brood) + return ..() + +/mob/living/basic/mining/hivelord/death(gibbed) + . = ..() + var/list/safe_turfs = RANGE_TURFS(1, src) - get_turf(src) + for (var/turf/check_turf as anything in safe_turfs) + if (check_turf.is_blocked_turf(exclude_mobs = TRUE)) + safe_turfs -= check_turf + + var/turf/our_turf = get_turf(src) + for (var/i in 1 to 3) + if (!length(safe_turfs)) + return + var/turf/land_turf = pick_n_take(safe_turfs) + var/obj/effect/temp_visual/hivebrood_spawn/forecast = new(land_turf) + forecast.create_from(death_spawn_type, our_turf, CALLBACK(src, PROC_REF(complete_spawn), land_turf)) + +/// Spawns a worm on the specified turf +/mob/living/basic/mining/hivelord/proc/complete_spawn(turf/spawn_turf) + var/mob/living/brood = new death_spawn_type(spawn_turf) + brood.faction = faction + brood.ai_controller?.set_blackboard_key(ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) + brood.dir = get_dir(src, spawn_turf) + +/mob/living/basic/mining/hivelord/RangedAttack(atom/atom_target, modifiers) + spawn_brood?.Trigger(target = atom_target) + +/// Attack worms spawned by the hivelord +/mob/living/basic/hivelord_brood + name = "hivelord brood" + desc = "Short-lived attack form of the hivelord. One isn't much of a threat, but..." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "hivelord_brood" + icon_living = "hivelord_brood" + icon_dead = "hivelord_brood" + icon_gib = "syndicate_gib" + friendly_verb_continuous = "chirrups near" + friendly_verb_simple = "chirrup near" + mob_size = MOB_SIZE_SMALL + basic_mob_flags = DEL_ON_DEATH + pass_flags = PASSTABLE | PASSMOB + mob_biotypes = MOB_ORGANIC|MOB_BEAST + faction = list(FACTION_MINING) + unsuitable_atmos_damage = 0 + minimum_survivable_temperature = 0 + maximum_survivable_temperature = INFINITY + speed = 1.5 + maxHealth = 1 + health = 1 + melee_damage_lower = 2 + melee_damage_upper = 2 + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + speak_emote = list("telepathically cries") + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + obj_damage = 0 + density = FALSE + ai_controller = /datum/ai_controller/basic_controller/simple_hostile + +/mob/living/basic/hivelord_brood/Initialize(mapload) + . = ..() + add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE), INNATE_TRAIT) + AddElement(/datum/element/simple_flying) + AddComponent(/datum/component/swarming) + AddComponent(/datum/component/clickbox, icon_state = "hivelord", max_scale = INFINITY) + addtimer(CALLBACK(src, PROC_REF(death)), 10 SECONDS) + +/mob/living/basic/hivelord_brood/death(gibbed) + if (!gibbed) + new /obj/effect/temp_visual/hive_spawn_wither(get_turf(src), /* copy_from = */ src) + return ..() + +/// Plays a dispersing animation on hivelord and legion minions so they don't just vanish +/obj/effect/temp_visual/hive_spawn_wither + name = "withering spawn" + duration = 1 SECONDS + +/obj/effect/temp_visual/hive_spawn_wither/Initialize(mapload, atom/copy_from) + if (isnull(copy_from)) + . = ..() + return INITIALIZE_HINT_QDEL + icon = copy_from.icon + icon_state = copy_from.icon_state + pixel_x = copy_from.pixel_x + pixel_y = copy_from.pixel_y + duration = rand(0.5 SECONDS, 1 SECONDS) + var/matrix/transformation = matrix(transform) + transformation.Turn(rand(-70, 70)) + transformation.Scale(0.7, 0.7) + animate( + src, + pixel_x = rand(-5, 5), + pixel_y = -5, + transform = transformation, + color = "#44444400", + time = duration, + flags = ANIMATION_RELATIVE, + ) + return ..() diff --git a/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm b/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm new file mode 100644 index 000000000000..1fb05cd7a012 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm @@ -0,0 +1,14 @@ +/// Basically just keep away and shit out worms +/datum/ai_controller/basic_controller/hivelord + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_AGGRO_RANGE = 5, // Only get mad at people nearby + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/maintain_distance, + /datum/ai_planning_subtree/targeted_mob_ability, + ) diff --git a/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm b/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm new file mode 100644 index 000000000000..7d50806e63a0 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm @@ -0,0 +1,123 @@ +/// Spawns a little worm nearby +/datum/action/cooldown/mob_cooldown/hivelord_spawn + name = "Spawn Brood" + desc = "Release an attack form to an adjacent square to attack your target or anyone nearby." + button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + button_icon_state = "hivelord_brood" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + click_to_activate = TRUE + cooldown_time = 2 SECONDS + melee_cooldown_time = 0 + shared_cooldown = NONE + /// If a mob is not clicked directly, inherit targeting data from this blackboard key and setting it upon this target key + var/ai_target_key = BB_BASIC_MOB_CURRENT_TARGET + /// What are we actually spawning? + var/spawn_type = /mob/living/basic/hivelord_brood + /// Do we automatically fire with no cooldown when damaged? + var/trigger_on_hit = TRUE + /// Minimum time between triggering on hit + var/on_hit_delay = 1 SECONDS + /// Delay between triggering on hit + COOLDOWN_DECLARE(on_hit_cooldown) + +/datum/action/cooldown/mob_cooldown/hivelord_spawn/Grant(mob/granted_to) + . = ..() + if (isnull(owner)) + return + if (trigger_on_hit) + RegisterSignal(owner, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) + +/datum/action/cooldown/mob_cooldown/hivelord_spawn/Remove(mob/removed_from) + UnregisterSignal(removed_from, COMSIG_ATOM_WAS_ATTACKED) + return ..() + +/datum/action/cooldown/mob_cooldown/hivelord_spawn/Activate(atom/target) + . = ..() + if (!spawn_brood(target, target_turf = get_turf(target))) + StartCooldown(0.5 SECONDS) + return + StartCooldown() + +/// Called when someone whacks us +/datum/action/cooldown/mob_cooldown/hivelord_spawn/proc/on_attacked(atom/victim, atom/attacker, attack_flags) + SIGNAL_HANDLER + if (!trigger_on_hit || !(attack_flags & ATTACKER_DAMAGING_ATTACK) || !COOLDOWN_FINISHED(src, on_hit_cooldown)) + return + COOLDOWN_START(src, on_hit_cooldown, on_hit_delay) + spawn_brood(attacker, target_turf = get_step_away(owner, attacker), feedback = FALSE) + +/// Spawn a funny little worm +/datum/action/cooldown/mob_cooldown/hivelord_spawn/proc/spawn_brood(target, turf/target_turf, feedback = TRUE) + var/ai_target = isliving(target) ? target : null + if (isnull(ai_target)) + ai_target = owner.ai_controller?.blackboard[ai_target_key] + + var/dir_to_target = get_dir(owner, target_turf) + var/list/target_turfs = list() + for(var/i in -1 to 1) + var/turn_amount = rand(-1, 1) * 45 + var/test_dir = turn(dir_to_target, turn_amount) + var/turf/test_turf = get_step(owner, test_dir) + if (test_turf.is_blocked_turf(exclude_mobs = TRUE)) + continue + target_turfs += test_turf + + if (!length(target_turfs)) + if (feedback) + owner.balloon_alert(owner, "no room!") + StartCooldown(0.5 SECONDS) + return FALSE + + var/turf/land_turf = pick(target_turfs) + var/obj/effect/temp_visual/hivebrood_spawn/forecast = new(land_turf) + forecast.create_from(spawn_type, get_turf(owner), CALLBACK(src, PROC_REF(complete_spawn), land_turf, ai_target)) + StartCooldown() + + return TRUE + +/// Actually create a mob +/datum/action/cooldown/mob_cooldown/hivelord_spawn/proc/complete_spawn(turf/spawn_turf, target) + var/mob/living/brood = new spawn_type(spawn_turf) + brood.faction = owner.faction + brood.ai_controller?.set_blackboard_key(ai_target_key, target) + brood.dir = get_dir(owner, spawn_turf) + +#define BROOD_ARC_Y_OFFSET 8 +#define BROOD_ARC_ROTATION 45 + +/// Fast animation to show a worm spawning +/obj/effect/temp_visual/hivebrood_spawn + name = "brood spawn" + duration = 0.3 SECONDS + alpha = 0 + +/// Set up our visuals and start a timer for a callback +/obj/effect/temp_visual/hivebrood_spawn/proc/create_from(mob/living/spawn_type, turf/spawn_from, datum/callback/on_completed) + addtimer(on_completed, duration, TIMER_DELETE_ME) + + var/turf/my_turf = get_turf(src) + dir = get_dir(spawn_from, my_turf) + var/move_x = (my_turf.x - spawn_from.x) * world.icon_size + var/move_y = (my_turf.y - spawn_from.y) * world.icon_size + pixel_x = -move_x + pixel_y = -move_y + + icon = initial(spawn_type.icon) + icon_state = initial(spawn_type.icon_state) + + + animate(src, pixel_x = 0, time = duration) + animate(src, pixel_y = BROOD_ARC_Y_OFFSET - (move_y * 0.5), time = duration * 0.5, flags = ANIMATION_PARALLEL, easing = SINE_EASING | EASE_OUT) + animate(pixel_y = 0, time = duration * 0.5, easing = SINE_EASING | EASE_IN) + animate(src, alpha = 255, time = duration * 0.5, flags = ANIMATION_PARALLEL) + + if (dir & (NORTH | EAST)) + transform = matrix().Turn(-BROOD_ARC_ROTATION) + animate(src, transform = matrix(), time = duration, flags = ANIMATION_PARALLEL) + else + transform = matrix().Turn(BROOD_ARC_ROTATION) + animate(src, transform = matrix(), time = duration, flags = ANIMATION_PARALLEL) + +#undef BROOD_ARC_Y_OFFSET +#undef BROOD_ARC_ROTATION diff --git a/code/modules/mob/living/basic/lavaland/legion/legion.dm b/code/modules/mob/living/basic/lavaland/legion/legion.dm new file mode 100644 index 000000000000..989ae0f4ff8c --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion.dm @@ -0,0 +1,158 @@ +/** + * Avoids players while throwing skulls at them. + * Legion skulls heal allies, bite enemies, and infest dying humans to make more legions. + */ +/mob/living/basic/mining/legion + name = "legion" + desc = "You can still see what was once a human under the shifting mass of corruption." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "legion" + icon_living = "legion" + icon_dead = "legion" + icon_gib = "syndicate_gib" + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + basic_mob_flags = DEL_ON_DEATH + speed = 3 + maxHealth = 75 + health = 75 + obj_damage = 60 + melee_damage_lower = 15 + melee_damage_upper = 15 + attack_verb_continuous = "lashes out at" + attack_verb_simple = "lash out at" + speak_emote = list("gurgles") + attack_sound = 'sound/weapons/pierce.ogg' + throw_blocked_message = "bounces harmlessly off of" + crusher_loot = /obj/item/crusher_trophy/legion_skull + death_message = "wails in chorus and dissolves into quivering flesh." + ai_controller = /datum/ai_controller/basic_controller/legion + /// What kind of mob do we spawn? + var/brood_type = /mob/living/basic/legion_brood + /// What kind of corpse spawner do we leave behind on death? + var/corpse_type = /obj/effect/mob_spawn/corpse/human/legioninfested + /// Who is inside of us? + var/mob/living/stored_mob + +/mob/living/basic/mining/legion/Initialize(mapload) + . = ..() + AddElement(/datum/element/death_drops, get_loot_list()) + AddElement(/datum/element/content_barfer) + + var/datum/action/cooldown/mob_cooldown/skull_launcher/skull_launcher = new(src) + skull_launcher.Grant(src) + skull_launcher.spawn_type = brood_type + ai_controller.blackboard[BB_TARGETED_ACTION] = skull_launcher + +/// Create what we want to drop on death, in proc form so we can always return a static list +/mob/living/basic/mining/legion/proc/get_loot_list() + var/static/list/death_loot = list(/obj/item/organ/internal/monster_core/regenerative_core/legion) + return death_loot + +/mob/living/basic/mining/legion/Exited(atom/movable/gone, direction) + . = ..() + if (gone != stored_mob) + return + ai_controller.clear_blackboard_key(BB_LEGION_CORPSE) + stored_mob.remove_status_effect(/datum/status_effect/grouped/stasis, STASIS_LEGION_EATEN) + stored_mob.add_mood_event(MOOD_CATEGORY_LEGION_CORE, /datum/mood_event/healsbadman/long_term) // This will still probably mostly be gone before you are alive + stored_mob = null + +/mob/living/basic/mining/legion/death(gibbed) + if (isnull(stored_mob)) + new corpse_type(loc) + return ..() + +/// Put a corpse in this guy +/mob/living/basic/mining/legion/proc/consume(mob/living/consumed) + new /obj/effect/gibspawner/generic(consumed.loc) + gender = consumed.gender + name = consumed.real_name + consumed.investigate_log("has been killed by hivelord infestation.", INVESTIGATE_DEATHS) + consumed.death() + consumed.extinguish_mob() + consumed.fully_heal(HEAL_DAMAGE) + consumed.apply_status_effect(/datum/status_effect/grouped/stasis, STASIS_LEGION_EATEN) + consumed.forceMove(src) + ai_controller?.set_blackboard_key(BB_LEGION_CORPSE, consumed) + ai_controller?.set_blackboard_key(BB_LEGION_RECENT_LINES, consumed.copy_recent_speech(line_chance = 80)) + stored_mob = consumed + visible_message(span_warning("[src] staggers to [p_their()] feet!")) + if (prob(75)) + return + // Congratulations you have won a special prize: cancer + var/obj/item/organ/internal/legion_tumour/cancer = new() + cancer.Insert(consumed, special = TRUE, drop_if_replaced = FALSE) + +/// A Legion which only drops skeletons instead of corpses which might have fun loot, so it cannot be farmed +/mob/living/basic/mining/legion/spawner_made + corpse_type = /obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/charred + + +/// Like a Legion but it's an adorable snowman +/mob/living/basic/mining/legion/snow + name = "snow legion" + desc = "You can vaguely see what was once a human under the densely packed snow. Cute, but macabre." + icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + icon_state = "snowlegion" + icon_living = "snowlegion" + // icon_aggro = "snowlegion_alive" + icon_dead = "snowlegion" + brood_type = /mob/living/basic/legion_brood/snow + corpse_type = /obj/effect/mob_spawn/corpse/human/legioninfested/snow + +/mob/living/basic/mining/legion/snow/Initialize(mapload) + . = ..() + AddComponent(/datum/component/appearance_on_aggro, aggro_state = "snowlegion_alive") // Surprise! I was real! + +/// As Snow Legion but spawns corpses which don't have any exciting loot +/mob/living/basic/mining/legion/snow/spawner_made + corpse_type = /obj/effect/mob_spawn/corpse/human/legioninfested/skeleton + + +/// Like a Legion but shorter and faster +/mob/living/basic/mining/legion/dwarf + name = "dwarf legion" + desc = "You can still see what was once a rather small human under the shifting mass of corruption." + icon_state = "dwarf_legion" + icon_living = "dwarf_legion" + icon_dead = "dwarf_legion" + maxHealth = 60 + health = 60 + speed = 2 + crusher_drop_chance = 20 + corpse_type = /obj/effect/mob_spawn/corpse/human/legioninfested/dwarf + + +/// Like a Legion but larger and spawns regular Legions, not currently used anywhere and very soulful +/mob/living/basic/mining/legion/large + name = "myriad" + desc = "A legion of legions, a dead end to whatever form the Necropolis was attempting to create." + icon = 'icons/mob/simple/lavaland/64x64megafauna.dmi' + icon_state = "legion" + icon_living = "legion" + icon_dead = "legion" + health_doll_icon = "legion" + speed = 5 + health = 450 + maxHealth = 450 + melee_damage_lower = 20 + melee_damage_upper = 20 + obj_damage = 30 + pixel_x = -16 + sentience_type = SENTIENCE_BOSS + +/mob/living/basic/mining/legion/large/Initialize(mapload) + . = ..() + AddComponent(\ + /datum/component/spawner,\ + spawn_types = list(/mob/living/basic/mining/legion),\ + spawn_time = 20 SECONDS,\ + max_spawned = 3,\ + spawn_text = "peels itself off from",\ + faction = faction,\ + ) + +/// Create what we want to drop on death, in proc form so we can always return a static list +/mob/living/basic/mining/legion/large/get_loot_list() + var/static/list/death_loot = list(/obj/item/organ/internal/monster_core/regenerative_core/legion = 3, /obj/effect/mob_spawn/corpse/human/legioninfested = 4) + return death_loot diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm b/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm new file mode 100644 index 000000000000..1bae1b303537 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm @@ -0,0 +1,78 @@ +/// Keep away and launch skulls at every opportunity, prioritising injured allies +/datum/ai_controller/basic_controller/legion + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/legion, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_AGGRO_RANGE = 5, // Unobservant + BB_BASIC_MOB_FLEE_DISTANCE = 6, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/random_speech/legion, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability, + /datum/ai_planning_subtree/flee_target/legion, + ) + +/// Chase and attack whatever we are targeting, if it's friendly we will heal them +/datum/ai_controller/basic_controller/legion_brood + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/legion, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Target nearby friendlies if they are hurt (and are not themselves Legions) +/datum/targeting_strategy/basic/legion + +/datum/targeting_strategy/basic/legion/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) + if (!living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly)) + return FALSE + if (istype(the_target, living_mob.type)) + return TRUE + var/atom/created_by = living_mob.ai_controller.blackboard[BB_LEGION_BROOD_CREATOR] + if (!QDELETED(created_by) && istype(the_target, created_by.type)) + return TRUE + return the_target.stat == DEAD || the_target.health >= the_target.maxHealth + +/// Don't run away from friendlies +/datum/ai_planning_subtree/flee_target/legion + +/datum/ai_planning_subtree/flee_target/legion/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/target = controller.blackboard[target_key] + if (QDELETED(target) || target.faction_check_atom(controller.pawn)) + return // Only flee if we have a hostile target + return ..() + +/// Make spooky sounds, if we have a corpse inside then impersonate them +/datum/ai_planning_subtree/random_speech/legion + speech_chance = 1 + speak = list("Come...", "Legion...", "Why...?") + emote_hear = list("groans.", "wails.", "whimpers.") + emote_see = list("twitches.", "shudders.") + /// Stuff to specifically say into a radio + var/list/radio_speech = list("Come...", "Why...?") + +/datum/ai_planning_subtree/random_speech/legion/speak(datum/ai_controller/controller) + var/mob/living/carbon/human/victim = controller.blackboard[BB_LEGION_CORPSE] + if (QDELETED(victim) || prob(30)) + return ..() + + var/list/remembered_speech = controller.blackboard[BB_LEGION_RECENT_LINES] || list() + + if (length(remembered_speech) && prob(50)) // Don't spam the radio + controller.queue_behavior(/datum/ai_behavior/perform_speech, pick(remembered_speech)) + return + + var/obj/item/radio/mob_radio = locate() in victim.contents + if (QDELETED(mob_radio)) + return ..() // No radio, just talk funny + controller.queue_behavior(/datum/ai_behavior/perform_speech_radio, pick(radio_speech + remembered_speech), mob_radio, list(RADIO_CHANNEL_SUPPLY, RADIO_CHANNEL_COMMON)) diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm new file mode 100644 index 000000000000..5b5274a16eea --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm @@ -0,0 +1,99 @@ +/// A spooky skull which heals lavaland mobs, attacks miners, and infests their bodies +/mob/living/basic/legion_brood + name = "legion" + desc = "One of many." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "legion_head" + icon_living = "legion_head" + icon_dead = "legion_head" + icon_gib = "syndicate_gib" + basic_mob_flags = DEL_ON_DEATH + mob_size = MOB_SIZE_SMALL + pass_flags = PASSTABLE | PASSMOB + mob_biotypes = MOB_ORGANIC|MOB_BEAST + faction = list(FACTION_MINING) + unsuitable_atmos_damage = 0 + minimum_survivable_temperature = 0 + maximum_survivable_temperature = INFINITY + friendly_verb_continuous = "chatters near" + friendly_verb_simple = "chatter near" + maxHealth = 1 + health = 1 + melee_damage_lower = 12 + melee_damage_upper = 12 + obj_damage = 0 + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + attack_vis_effect = ATTACK_EFFECT_BITE + speak_emote = list("echoes") // who the fuck speaking as this mob it dies 10 seconds after it spawns + attack_sound = 'sound/weapons/pierce.ogg' + density = FALSE + ai_controller = /datum/ai_controller/basic_controller/legion_brood + /// Reference to a guy who made us + var/mob/living/created_by + +/mob/living/basic/legion_brood/Initialize(mapload) + . = ..() + add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE), INNATE_TRAIT) + AddElement(/datum/element/simple_flying) + AddComponent(/datum/component/swarming) + AddComponent(/datum/component/clickbox, icon_state = "sphere", max_scale = 2) + addtimer(CALLBACK(src, PROC_REF(death)), 10 SECONDS) + +/mob/living/basic/legion_brood/death(gibbed) + if (!gibbed) + new /obj/effect/temp_visual/hive_spawn_wither(get_turf(src), /* copy_from = */ src) + return ..() + +/mob/living/basic/legion_brood/melee_attack(mob/living/target, list/modifiers, ignore_cooldown) + if (ishuman(target) && target.stat > SOFT_CRIT) + infest(target) + return + if (isliving(target) && faction_check_atom(target) && !istype(target, created_by?.type)) + visible_message(span_warning("[src] melds with [target]'s flesh!")) + target.apply_status_effect(/datum/status_effect/regenerative_core) + new /obj/effect/temp_visual/heal(get_turf(target), COLOR_HEALING_CYAN) + death() + return + return ..() + +/// Turn the targeted mob into one of us +/mob/living/basic/legion_brood/proc/infest(mob/living/target) + visible_message(span_warning("[name] burrows into the flesh of [target]!")) + var/spawn_type = get_legion_type(target) + var/mob/living/basic/mining/legion/new_legion = new spawn_type(loc) + new_legion.consume(target) + new_legion.faction = faction.Copy() + qdel(src) + +/// Returns the kind of legion we make out of the target +/mob/living/basic/legion_brood/proc/get_legion_type(mob/living/target) + if (HAS_TRAIT(target, TRAIT_DWARF)) + return /mob/living/basic/mining/legion/dwarf + return /mob/living/basic/mining/legion + +/// Sets someone as our creator, mostly so you can't use skulls to heal yourself +/mob/living/basic/legion_brood/proc/assign_creator(mob/living/creator, copy_full_faction = TRUE) + if (copy_full_faction) + faction = creator.faction.Copy() + else + faction |= REF(creator) + created_by = creator + ai_controller?.set_blackboard_key(BB_LEGION_BROOD_CREATOR, creator) + RegisterSignal(creator, COMSIG_PARENT_QDELETING, PROC_REF(creator_destroyed)) + +/// Reference handling +/mob/living/basic/legion_brood/proc/creator_destroyed() + SIGNAL_HANDLER + created_by = null + +/// Like the Legion's summoned skull but funnier (it's snow now) +/mob/living/basic/legion_brood/snow + name = "snow legion" + icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + icon_state = "snowlegion_head" + icon_living = "snowlegion_head" + icon_dead = "snowlegion_head" + +/mob/living/basic/legion_brood/snow/get_legion_type(mob/living/target) + return /mob/living/basic/mining/legion/snow diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_tumour.dm b/code/modules/mob/living/basic/lavaland/legion/legion_tumour.dm new file mode 100644 index 000000000000..b79edb3c33c6 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion_tumour.dm @@ -0,0 +1,159 @@ +/// Left behind when a legion infects you, for medical enrichment +/obj/item/organ/internal/legion_tumour + name = "legion tumour" + desc = "A mass of pulsing flesh and dark tendrils, containing the power to regenerate flesh at a terrible cost." + failing_desc = "pulses and writhes with horrible life, reaching towards you with its tendrils!" + icon = 'icons/obj/medical/organs/mining_organs.dmi' + icon_state = "legion_remains" + zone = BODY_ZONE_CHEST + slot = ORGAN_SLOT_PARASITE_EGG + decay_factor = STANDARD_ORGAN_DECAY * 3 // About 5 minutes outside of a host + /// What stage of growth the corruption has reached. + var/stage = 0 + /// We apply this status effect periodically or when used on someone + var/applied_status = /datum/status_effect/regenerative_core + /// How long have we been in this stage? + var/elapsed_time = 0 SECONDS + /// How long does it take to advance one stage? + var/growth_time = 80 SECONDS // Long enough that if you go back to lavaland without realising it you're not totally fucked + /// What kind of mob will we transform into? + var/spawn_type = /mob/living/basic/mining/legion + /// Spooky sounds to play as you start to turn + var/static/list/spooky_sounds = list( + 'sound/voice/lowHiss1.ogg', + 'sound/voice/lowHiss2.ogg', + 'sound/voice/lowHiss3.ogg', + 'sound/voice/lowHiss4.ogg', + ) + +/obj/item/organ/internal/legion_tumour/Initialize(mapload) + . = ..() + animate_pulse() + +/obj/item/organ/internal/legion_tumour/apply_organ_damage(damage_amount, maximum, required_organtype) + var/was_failing = organ_flags & ORGAN_FAILING + . = ..() + if (was_failing != (organ_flags & ORGAN_FAILING)) + animate_pulse() + +/obj/item/organ/internal/legion_tumour/set_organ_damage(damage_amount, required_organ_flag) + . = ..() + animate_pulse() + +/// Do a heartbeat animation depending on if we're failing or not +/obj/item/organ/internal/legion_tumour/proc/animate_pulse() + animate(src, transform = matrix()) // Stop any current animation + + var/speed_divider = organ_flags & ORGAN_FAILING ? 2 : 1 + + animate(src, transform = matrix().Scale(1.1), time = 0.5 SECONDS / speed_divider, easing = SINE_EASING | EASE_OUT, loop = -1, flags = ANIMATION_PARALLEL) + animate(transform = matrix(), time = 0.5 SECONDS / speed_divider, easing = SINE_EASING | EASE_IN) + animate(transform = matrix(), time = 2 SECONDS / speed_divider) + +/obj/item/organ/internal/legion_tumour/Remove(mob/living/carbon/egg_owner, special) + . = ..() + stage = 0 + elapsed_time = 0 + +/obj/item/organ/internal/legion_tumour/attack(mob/living/target, mob/living/user, params) + if (try_apply(target, user)) + qdel(src) + return + return ..() + +/// Smear it on someone like a regen core, why not. Make sure they're alive though. +/obj/item/organ/internal/legion_tumour/proc/try_apply(mob/living/target, mob/user) + if(!user.Adjacent(target) || !isliving(target)) + return FALSE + + if (target.stat <= SOFT_CRIT && !(organ_flags & ORGAN_FAILING)) + target.add_mood_event(MOOD_CATEGORY_LEGION_CORE, /datum/mood_event/healsbadman) + target.apply_status_effect(applied_status) + + if (target != user) + target.visible_message(span_notice("[user] splatters [target] with [src]... Disgusting tendrils pull [target.p_their()] wounds shut!")) + else + to_chat(user, span_notice("You smear [src] on yourself. Disgusting tendrils pull your wounds closed.")) + return TRUE + + if (!ishuman(target)) + return FALSE + + target.visible_message(span_boldwarning("[user] splatters [target] with [src]... and it springs into horrible life!")) + var/mob/living/basic/legion_brood/skull = new(target.loc) + skull.melee_attack(target) + return TRUE + +/obj/item/organ/internal/legion_tumour/on_life(seconds_per_tick, times_fired) + . = ..() + if (QDELETED(src) || QDELETED(owner)) + return + + if (stage >= 2) + if(SPT_PROB(stage / 5, seconds_per_tick)) + to_chat(owner, span_notice("You feel a bit better.")) + owner.apply_status_effect(applied_status) // It's not all bad! + if(SPT_PROB(1, seconds_per_tick)) + owner.emote("twitch") + + switch(stage) + if(2, 3) + if(SPT_PROB(1, seconds_per_tick)) + to_chat(owner, span_danger("Your chest spasms!")) + if(SPT_PROB(1, seconds_per_tick)) + to_chat(owner, span_danger("You feel weak.")) + if(SPT_PROB(1, seconds_per_tick)) + SEND_SOUND(owner, sound(pick(spooky_sounds))) + if(SPT_PROB(2, seconds_per_tick)) + owner.vomit() + if(4, 5) + if(SPT_PROB(2, seconds_per_tick)) + to_chat(owner, span_danger("Something flexes under your skin.")) + if(SPT_PROB(2, seconds_per_tick)) + if (prob(40)) + SEND_SOUND(owner, sound('sound/voice/ghost_whisper.ogg')) + else + SEND_SOUND(owner, sound(pick(spooky_sounds))) + if(SPT_PROB(3, seconds_per_tick)) + owner.vomit(vomit_type = /obj/effect/decal/cleanable/vomit/old/black_bile) + if (prob(50)) + var/turf/check_turf = get_step(owner.loc, owner.dir) + var/atom/land_turf = (check_turf.is_blocked_turf()) ? owner.loc : check_turf + var/mob/living/basic/legion_brood/child = new(land_turf) + child.assign_creator(owner, copy_full_faction = FALSE) + + if(SPT_PROB(3, seconds_per_tick)) + to_chat(owner, span_danger("Your muscles ache.")) + owner.take_bodypart_damage(3) + + if (stage == 5) + if (SPT_PROB(10, seconds_per_tick)) + infest() + return + + elapsed_time += seconds_per_tick SECONDS * ((organ_flags & ORGAN_FAILING) ? 3 : 1) // Let's call it "matured" rather than failed + if (elapsed_time < growth_time) + return + stage++ + elapsed_time = 0 + if (stage == 5) + to_chat(owner, span_bolddanger("Something is moving under your skin!")) + +/// Consume our host +/obj/item/organ/internal/legion_tumour/proc/infest() + if (QDELETED(src) || QDELETED(owner)) + return + owner.visible_message(span_boldwarning("Black tendrils burst from [owner]'s flesh, covering them in amorphous flesh!")) + var/mob/living/basic/mining/legion/new_legion = new spawn_type(owner.loc) + new_legion.consume(owner) + qdel(src) + +/obj/item/organ/internal/legion_tumour/on_find(mob/living/finder) + . = ..() + to_chat(finder, span_warning("There's an enormous tumour in [owner]'s [zone]!")) + if(stage < 4) + to_chat(finder, span_notice("Its tendrils seem to twitch towards the light.")) + return + to_chat(finder, span_notice("Its pulsing tendrils reach all throughout the body.")) + if(prob(stage * 2)) + infest() diff --git a/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm b/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm new file mode 100644 index 000000000000..bd9b2c2aff99 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm @@ -0,0 +1,108 @@ +/// Spawns a little worm nearby +/datum/action/cooldown/mob_cooldown/skull_launcher + name = "Launch Legion" + desc = "Propel a living piece of your body to a distant location." + button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + button_icon_state = "legion_head" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + click_to_activate = TRUE + cooldown_time = 2 SECONDS + melee_cooldown_time = 0 + shared_cooldown = NONE + /// If a mob is not clicked directly, inherit targeting data from this blackboard key and setting it upon this target key + var/ai_target_key = BB_BASIC_MOB_CURRENT_TARGET + /// What are we actually spawning? + var/spawn_type = /mob/living/basic/legion_brood + /// How far can we fire? + var/max_range = 7 + +/datum/action/cooldown/mob_cooldown/skull_launcher/Activate(atom/target) + var/turf/target_turf = get_turf(target) + + if (get_dist(owner, target_turf) > max_range) + target_turf = get_ranged_target_turf_direct(owner, target_turf, max_range) + + if (target_turf.is_blocked_turf()) + var/list/near_turfs = RANGE_TURFS(1, target_turf) - target_turf + for (var/turf/check_turf as anything in near_turfs) + if (check_turf.is_blocked_turf()) + near_turfs -= check_turf + if (length(near_turfs)) + target_turf = pick(near_turfs) + else if(target_turf.is_blocked_turf(exclude_mobs = TRUE)) + owner.balloon_alert(owner, "no room!") + StartCooldown(0.5 SECONDS) + return + + var/ai_target = isliving(target) ? target : null + if (isnull(ai_target)) + ai_target = owner.ai_controller?.blackboard[ai_target_key] + + var/target_dir = get_dir(owner, target) + + var/obj/effect/temp_visual/legion_skull_depart/launch = new(get_turf(owner)) + launch.set_appearance(spawn_type) + launch.dir = target_dir + new /obj/effect/temp_visual/legion_brood_indicator(target_turf) + var/obj/effect/temp_visual/legion_skull_land/land = new(target_turf) + land.dir = target_dir + land.set_appearance(spawn_type, CALLBACK(src, PROC_REF(spawn_skull), target_turf, ai_target)) + StartCooldown() + +/// Actually create a mob +/datum/action/cooldown/mob_cooldown/skull_launcher/proc/spawn_skull(turf/spawn_location, target) + var/mob/living/basic/legion_brood/brood = new spawn_type(spawn_location) + if (istype(brood)) + brood.assign_creator(owner) + brood.ai_controller?.set_blackboard_key(ai_target_key, target) + brood.dir = get_dir(owner, spawn_location) + if (!isnull(target)) + brood.face_atom(target) + else + brood.dir = get_dir(owner, spawn_location) + + +/// Animation for launching a skull +/obj/effect/temp_visual/legion_skull_depart + name = "legion brood launch" + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "legion_head" + duration = 0.25 SECONDS + +/// Copy appearance from the passed atom type +/obj/effect/temp_visual/legion_skull_depart/proc/set_appearance(atom/spawned_type) + icon = initial(spawned_type.icon) + icon_state = initial(spawned_type.icon_state) + animate(src, alpha = 0, pixel_y = 72, time = duration) + +/// Animation for landing a skull +/obj/effect/temp_visual/legion_skull_land + name = "legion brood land" + duration = 0.5 SECONDS + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "legion_head" + alpha = 0 + pixel_y = 72 + +/// Copy appearance from the passed atom type and store what to do on animation complete +/obj/effect/temp_visual/legion_skull_land/proc/set_appearance(atom/spawned_type, datum/callback/on_completed) + icon = initial(spawned_type.icon) + icon_state = initial(spawned_type.icon_state) + animate(src, alpha = 0, pixel_y = 72, time = duration / 2) + animate(alpha = 255, pixel_y = 0, time = duration / 2) + addtimer(on_completed, duration, TIMER_DELETE_ME) + +/// A skull is going to be here! Oh no! +/obj/effect/temp_visual/legion_brood_indicator + name = "legion brood land" + duration = 0.75 SECONDS + layer = BELOW_MOB_LAYER + plane = GAME_PLANE + icon = 'icons/mob/telegraphing/telegraph.dmi' + icon_state = "skull" + +/obj/effect/temp_visual/legion_brood_indicator/Initialize(mapload) + . = ..() + animate(src, alpha = 255, time = 0.5 SECONDS) + animate(alpha = 0, time = 0.25 SECONDS) diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm index bc60ec88aa7d..2e5baae1e067 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm @@ -18,6 +18,7 @@ attack_verb_simple = "snip" attack_sound = 'sound/weapons/bite.ogg' attack_vis_effect = ATTACK_EFFECT_BITE // Closer than a scratch to a crustacean pinching effect + melee_attack_cooldown = 1 SECONDS butcher_results = list( /obj/item/food/meat/crab = 2, /obj/item/stack/sheet/bone = 2, @@ -27,24 +28,23 @@ ai_controller = /datum/ai_controller/basic_controller/lobstrosity /// Charging ability var/datum/action/cooldown/mob_cooldown/charge/basic_charge/lobster/charge - /// Limbs we will cut off an unconscious man - var/static/list/target_limbs = list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM) /// Things we will eat if we see them (arms, chiefly) var/static/list/target_foods = list(/obj/item/bodypart/arm) /mob/living/basic/mining/lobstrosity/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_SNOWSTORM_IMMUNE, INNATE_TRAIT) + AddElement(/datum/element/mob_grabber) AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) AddElement(/datum/element/basic_eating, food_types = target_foods) AddElement(\ /datum/element/amputating_limbs,\ - surgery_verb = "snipping",\ - target_zones = target_limbs,\ + surgery_verb = "begins snipping",\ + target_zones = GLOB.arm_zones,\ ) charge = new(src) charge.Grant(src) - ai_controller.set_blackboard_key(BB_TARGETTED_ACTION, charge) + ai_controller.set_blackboard_key(BB_TARGETED_ACTION, charge) /mob/living/basic/mining/lobstrosity/Destroy() QDEL_NULL(charge) @@ -68,12 +68,12 @@ /datum/action/cooldown/mob_cooldown/charge/basic_charge/lobster/hit_target(atom/movable/source, atom/target, damage_dealt) . = ..() - if(!isliving(target) || !isbasicmob(source)) + if(!isbasicmob(source)) return var/mob/living/basic/basic_source = source var/mob/living/living_target = target - basic_source.melee_attack(living_target) - basic_source.ai_controller?.set_blackboard_key(BB_BASIC_MOB_FLEEING, FALSE) + basic_source.melee_attack(living_target, ignore_cooldown = TRUE) + basic_source.ai_controller?.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) basic_source.start_pulling(living_target) /datum/action/cooldown/mob_cooldown/charge/basic_charge/lobster/do_charge(atom/movable/charger, atom/target_atom, delay, past) diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm index dcbeb1e670ca..b80d5d6b9f79 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm @@ -1,8 +1,8 @@ /datum/ai_controller/basic_controller/lobstrosity blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/lobster, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, BB_LOBSTROSITY_EXPLOIT_TRAITS = list(TRAIT_INCAPACITATED, TRAIT_FLOORED, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT), - BB_BASIC_MOB_FLEEING = TRUE, BB_LOBSTROSITY_FINGER_LUST = 0 ) @@ -19,14 +19,11 @@ /datum/ai_planning_subtree/find_fingers, ) -/datum/targetting_datum/basic/lobster - stat_attack = HARD_CRIT - /datum/ai_planning_subtree/basic_melee_attack_subtree/lobster melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/lobster /datum/ai_planning_subtree/basic_melee_attack_subtree/lobster/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (controller.blackboard[BB_BASIC_MOB_FLEEING]) + if (!controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) return if (!isnull(controller.blackboard[BB_LOBSTROSITY_TARGET_LIMB])) return @@ -36,9 +33,8 @@ return ..() /datum/ai_behavior/basic_melee_attack/lobster - action_cooldown = 1 SECONDS -/datum/ai_behavior/basic_melee_attack/lobster/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) +/datum/ai_behavior/basic_melee_attack/lobster/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) var/mob/living/target = controller.blackboard[target_key] if (isnull(target)) return ..() @@ -49,18 +45,21 @@ is_vulnerable = TRUE break if (!is_vulnerable) - controller.set_blackboard_key(BB_BASIC_MOB_FLEEING, TRUE) - if (controller.blackboard[BB_BASIC_MOB_FLEEING]) + controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) + if (!controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) finish_action(controller = controller, succeeded = TRUE, target_key = target_key) // We don't want to clear our target return - var/mob/living/living_pawn = controller.pawn - if (target.stat != CONSCIOUS) - living_pawn.start_pulling(target) // No crawling away return ..() /datum/ai_planning_subtree/flee_target/lobster flee_behaviour = /datum/ai_behavior/run_away_from_target/lobster +/datum/ai_planning_subtree/flee_target/lobster/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/datum/action/cooldown/using_action = controller.blackboard[BB_TARGETED_ACTION] + if (using_action?.IsAvailable()) + return + return ..() + /datum/ai_behavior/run_away_from_target/lobster clear_failed_targets = FALSE @@ -68,13 +67,18 @@ var/atom/target = controller.blackboard[target_key] if(isnull(target)) return ..() + for (var/trait in controller.blackboard[BB_LOBSTROSITY_EXPLOIT_TRAITS]) if (!HAS_TRAIT(target, trait)) continue - controller.set_blackboard_key(BB_BASIC_MOB_FLEEING, FALSE) + controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) finish_action(controller, succeeded = FALSE) return + var/mob/living/us = controller.pawn + if (us.pulling == target) + us.stop_pulling() // If we're running away from someone, best not to bring them with us + return ..() /// Don't use charge ability on an adjacent target, and make sure you're visible before you start diff --git a/code/modules/mob/living/basic/lavaland/mining.dm b/code/modules/mob/living/basic/lavaland/mining.dm index 64a445c9deed..825f36bed6b4 100644 --- a/code/modules/mob/living/basic/lavaland/mining.dm +++ b/code/modules/mob/living/basic/lavaland/mining.dm @@ -8,6 +8,10 @@ unsuitable_atmos_damage = 0 minimum_survivable_temperature = 0 maximum_survivable_temperature = INFINITY + // Pale purple, should be red enough to see stuff on lavaland + lighting_cutoff_red = 25 + lighting_cutoff_green = 15 + lighting_cutoff_blue = 35 /// Message to output if throwing damage is absorbed var/throw_blocked_message = "bounces off" /// What crusher trophy this mob drops, if any diff --git a/code/modules/mob/living/basic/lavaland/mook/mook.dm b/code/modules/mob/living/basic/lavaland/mook/mook.dm new file mode 100644 index 000000000000..e3e518833fab --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook.dm @@ -0,0 +1,273 @@ +//Fragile but highly aggressive wanderers that pose a large threat in numbers. +//They'll attempt to leap at their target from afar using their hatchets. +/mob/living/basic/mining/mook + name = "wanderer" + desc = "This unhealthy looking primitive seems to be talented at administiring health care." + icon = 'icons/mob/simple/jungle/mook.dmi' + icon_state = "mook" + icon_living = "mook" + icon_dead = "mook_dead" + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + gender = FEMALE + maxHealth = 150 + faction = list(FACTION_MINING, FACTION_NEUTRAL) + health = 150 + move_resist = MOVE_FORCE_OVERPOWERING + melee_damage_lower = 8 + melee_damage_upper = 8 + pass_flags_self = LETPASSTHROW + attack_sound = 'sound/weapons/rapierhit.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH + death_sound = 'sound/voice/mook_death.ogg' + ai_controller = /datum/ai_controller/basic_controller/mook/support + speed = 5 + + pixel_x = -16 + base_pixel_x = -16 + pixel_y = -16 + base_pixel_y = -16 + + ///the state of combat we are in + var/attack_state = MOOK_ATTACK_NEUTRAL + ///are we a healer? + var/is_healer = TRUE + ///the ore we are holding if any + var/obj/held_ore + ///overlay for neutral stance + var/mutable_appearance/neutral_stance + ///overlay for attacking stance + var/mutable_appearance/attack_stance + ///overlay when we hold an ore + var/mutable_appearance/ore_overlay + ///commands we obey + var/list/pet_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/point_targeting/attack, + /datum/pet_command/point_targeting/fetch, + ) + +/mob/living/basic/mining/mook/Initialize(mapload) + . = ..() + AddComponent(/datum/component/ai_retaliate_advanced, CALLBACK(src, PROC_REF(attack_intruder))) + var/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump/jump = new(src) + jump.Grant(src) + ai_controller.set_blackboard_key(BB_MOOK_JUMP_ABILITY, jump) + + ore_overlay = mutable_appearance(icon, "mook_ore_overlay") + + AddComponent(/datum/component/ai_listen_to_weather) + AddElement(/datum/element/wall_smasher) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + RegisterSignal(src, COMSIG_KB_MOB_DROPITEM_DOWN, PROC_REF(drop_ore)) + + if(is_healer) + grant_healer_abilities() + + AddComponent(/datum/component/obeys_commands, pet_commands) + +/mob/living/basic/mining/mook/proc/grant_healer_abilities() + AddComponent(\ + /datum/component/healing_touch,\ + heal_brute = melee_damage_upper,\ + heal_burn = melee_damage_upper,\ + heal_time = 0,\ + valid_targets_typecache = typecacheof(list(/mob/living/basic/mining/mook)),\ + ) + +/mob/living/basic/mining/mook/Entered(atom/movable/mover) + if(istype(mover, /obj/item/stack/ore)) + held_ore = mover + update_appearance(UPDATE_OVERLAYS) + + return ..() + +/mob/living/basic/mining/mook/Exited(atom/movable/mover) + . = ..() + if(held_ore != mover) + return + held_ore = null + update_appearance(UPDATE_OVERLAYS) + +/mob/living/basic/mining/mook/proc/pre_attack(mob/living/attacker, atom/target) + SIGNAL_HANDLER + + return attack_sequence(target) + +/mob/living/basic/mining/mook/proc/attack_sequence(atom/target) + if(istype(target, /obj/item/stack/ore) && isnull(held_ore)) + var/obj/item/ore_target = target + ore_target.forceMove(src) + return COMPONENT_HOSTILE_NO_ATTACK + + if(istype(target, /obj/structure/ore_container/material_stand)) + if(held_ore) + held_ore.forceMove(target) + return COMPONENT_HOSTILE_NO_ATTACK + + if(istype(target, /obj/structure/bonfire)) + var/obj/structure/bonfire/fire_target = target + if(!fire_target.burning) + fire_target.start_burning() + return COMPONENT_HOSTILE_NO_ATTACK + +/mob/living/basic/mining/mook/proc/change_combatant_state(state) + attack_state = state + update_appearance() + +/mob/living/basic/mining/mook/Destroy() + QDEL_NULL(held_ore) + return ..() + +/mob/living/basic/mining/mook/update_icon_state() + . = ..() + if(stat == DEAD) + return + switch(attack_state) + if(MOOK_ATTACK_NEUTRAL) + icon_state = "mook" + if(MOOK_ATTACK_WARMUP) + icon_state = "mook_warmup" + if(MOOK_ATTACK_ACTIVE) + icon_state = "mook_leap" + if(MOOK_ATTACK_STRIKE) + icon_state = "mook_strike" + +/mob/living/basic/mining/mook/update_overlays() + . = ..() + if(stat == DEAD) + return + + if(attack_state != MOOK_ATTACK_NEUTRAL || isnull(held_ore)) + return + + . += ore_overlay + +/mob/living/basic/mining/mook/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) + change_combatant_state(state = MOOK_ATTACK_ACTIVE) + return ..() + +/mob/living/basic/mining/mook/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + change_combatant_state(state = MOOK_ATTACK_NEUTRAL) + +/mob/living/basic/mining/mook/CanAllowThrough(atom/movable/mover, border_dir) + . = ..() + + if(!istype(mover, /mob/living/basic/mining/mook)) + return FALSE + + var/mob/living/basic/mining/mook/mook_moover = mover + if(mook_moover.attack_state == MOOK_ATTACK_ACTIVE) + return TRUE + +/mob/living/basic/mining/mook/proc/drop_ore(mob/living/user) + SIGNAL_HANDLER + + if(isnull(held_ore)) + return + dropItemToGround(held_ore) + return COMSIG_KB_ACTIVATED + +/mob/living/basic/mining/mook/death() + desc = "A deceased primitive. Upon closer inspection, it was suffering from severe cellular degeneration and its garments are machine made..." //Can you guess the twist + return ..() + +/mob/living/basic/mining/mook/proc/attack_intruder(mob/living/intruder) + if(istype(intruder, /mob/living/basic/mining/mook)) + return + for(var/mob/living/basic/mining/mook/villager in oview(src, 9)) + villager.ai_controller?.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, intruder) + + +/mob/living/basic/mining/mook/worker + desc = "This unhealthy looking primitive is wielding a rudimentary hatchet, swinging it with wild abandon. One isn't much of a threat, but in numbers they can quickly overwhelm a superior opponent." + gender = MALE + melee_damage_lower = 15 + melee_damage_upper = 15 + ai_controller = /datum/ai_controller/basic_controller/mook + is_healer = FALSE + +/mob/living/basic/mining/mook/worker/Initialize(mapload) + . = ..() + neutral_stance = mutable_appearance(icon, "mook_axe_overlay") + attack_stance = mutable_appearance(icon, "axe_strike_overlay") + update_appearance() + var/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/leap = new(src) + leap.Grant(src) + ai_controller.set_blackboard_key(BB_MOOK_LEAP_ABILITY, leap) + +/mob/living/basic/mining/mook/worker/attack_sequence(atom/target) + . = ..() + if(. & COMPONENT_HOSTILE_NO_ATTACK) + return + + if(attack_state == MOOK_ATTACK_STRIKE) + return COMPONENT_HOSTILE_NO_ATTACK + + change_combatant_state(state = MOOK_ATTACK_STRIKE) + addtimer(CALLBACK(src, PROC_REF(change_combatant_state), MOOK_ATTACK_NEUTRAL), 0.3 SECONDS) + +/mob/living/basic/mining/mook/worker/update_overlays() + . = ..() + if(stat == DEAD) + return + + switch(attack_state) + if(MOOK_ATTACK_STRIKE) + . += attack_stance + if(MOOK_ATTACK_NEUTRAL) + . += neutral_stance + +/mob/living/basic/mining/mook/worker/bard + desc = "It's holding a guitar?" + melee_damage_lower = 10 + melee_damage_upper = 10 + gender = MALE + attack_sound = 'sound/weapons/stringsmash.ogg' + death_sound = 'sound/voice/mook_death.ogg' + ai_controller = /datum/ai_controller/basic_controller/mook/bard + ///our guitar + var/obj/item/instrument/guitar/held_guitar + +/mob/living/basic/mining/mook/worker/bard/Initialize(mapload) + . = ..() + neutral_stance = mutable_appearance(icon, "bard_overlay") + attack_stance = mutable_appearance(icon, "bard_strike") + held_guitar = new(src) + ai_controller.set_blackboard_key(BB_SONG_INSTRUMENT, held_guitar) + update_appearance() + +/mob/living/basic/mining/mook/worker/tribal_chief + name = "tribal chief" + desc = "Acknowledge him!" + gender = MALE + melee_damage_lower = 20 + melee_damage_upper = 20 + ai_controller = /datum/ai_controller/basic_controller/mook/tribal_chief + ///overlay in our neutral state + var/static/mutable_appearance/chief_neutral = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief") + ///overlay in our striking state + var/static/mutable_appearance/chief_strike = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief_strike") + ///overlay in our active state + var/static/mutable_appearance/chief_active = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief_leap") + ///overlay in our warmup state + var/static/mutable_appearance/chief_warmup = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief_warmup") + +/mob/living/basic/mining/mook/worker/tribal_chief/Initialize(mapload) + . = ..() + update_appearance() + +/mob/living/basic/mining/mook/worker/tribal_chief/update_overlays() + . = ..() + if(stat == DEAD) + return + switch(attack_state) + if(MOOK_ATTACK_NEUTRAL) + . += chief_neutral + if(MOOK_ATTACK_WARMUP) + . += chief_warmup + if(MOOK_ATTACK_ACTIVE) + . += chief_active + if(MOOK_ATTACK_STRIKE) + . += chief_strike diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_abilities.dm b/code/modules/mob/living/basic/lavaland/mook/mook_abilities.dm new file mode 100644 index 000000000000..cfc359bd54fc --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook_abilities.dm @@ -0,0 +1,140 @@ +/datum/action/cooldown/mob_cooldown/mook_ability + ///are we a mook? + var/is_mook = FALSE + +/datum/action/cooldown/mob_cooldown/mook_ability/Grant(mob/grant_to) + . = ..() + if(isnull(owner)) + return + is_mook = istype(owner, /mob/living/basic/mining/mook) + +/datum/action/cooldown/mob_cooldown/mook_ability/IsAvailable(feedback) + . = ..() + + if(!.) + return FALSE + + if(!is_mook) + return TRUE + + var/mob/living/basic/mining/mook/mook_owner = owner + if(mook_owner.attack_state != MOOK_ATTACK_NEUTRAL) + if(feedback) + mook_owner.balloon_alert(mook_owner, "still recovering!") + return FALSE + return TRUE + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap + name = "Mook leap" + desc = "Leap towards the enemy!" + cooldown_time = 7 SECONDS + shared_cooldown = NONE + melee_cooldown_time = 0 SECONDS + ///telegraph time before jumping + var/wind_up_time = 2 SECONDS + ///intervals between each of our attacks + var/attack_interval = 0.4 SECONDS + ///how many times do we attack if we reach the target? + var/times_to_attack = 4 + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/Activate(atom/target) + if(owner.CanReach(target)) + attack_combo(target) + StartCooldown() + return TRUE + + if(is_mook) + var/mob/living/basic/mining/mook/mook_owner = owner + mook_owner.change_combatant_state(state = MOOK_ATTACK_WARMUP) + + addtimer(CALLBACK(src, PROC_REF(launch_towards_target), target), wind_up_time) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/proc/launch_towards_target(atom/target) + new /obj/effect/temp_visual/mook_dust(get_turf(owner)) + playsound(get_turf(owner), 'sound/weapons/thudswoosh.ogg', 25, TRUE) + playsound(owner, 'sound/voice/mook_leap_yell.ogg', 100, TRUE) + var/turf/target_turf = get_turf(target) + + if(!target_turf.is_blocked_turf()) + owner.throw_at(target = target_turf, range = 7, speed = 1, spin = FALSE, callback = CALLBACK(src, PROC_REF(attack_combo), target)) + return + + var/list/open_turfs = list() + + for(var/turf/possible_turf in get_adjacent_open_turfs(target)) + if(possible_turf.is_blocked_turf()) + continue + open_turfs += possible_turf + + if(!length(open_turfs)) + return + + var/turf/final_turf = get_closest_atom(/turf, open_turfs, owner) + owner.throw_at(target = final_turf, range = 7, speed = 1, spin = FALSE, callback = CALLBACK(src, PROC_REF(attack_combo), target)) + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/proc/attack_combo(atom/target) + if(!owner.CanReach(target)) + return FALSE + + for(var/i in 0 to (times_to_attack - 1)) + addtimer(CALLBACK(src, PROC_REF(attack_target), target), i * attack_interval) + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/proc/attack_target(atom/target) + if(!owner.CanReach(target) || owner.stat == DEAD) + return + var/mob/living/basic/basic_owner = owner + basic_owner.melee_attack(target, ignore_cooldown = TRUE) + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump + name = "Mook Jump" + desc = "Soar high in the air!" + cooldown_time = 14 SECONDS + shared_cooldown = NONE + melee_cooldown_time = 0 SECONDS + click_to_activate = FALSE + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump/Activate(atom/target) + var/obj/effect/landmark/drop_zone = locate(/obj/effect/landmark/mook_village) in GLOB.landmarks_list + if(drop_zone?.z == owner.z) + var/turf/jump_destination = get_turf(drop_zone) + jump_to_turf(jump_destination) + StartCooldown() + return TRUE + var/list/potential_turfs = list() + for(var/turf/open_turf in oview(9, owner)) + if(!open_turf.is_blocked_turf()) + potential_turfs += open_turf + if(!length(potential_turfs)) + return FALSE + jump_to_turf(pick(potential_turfs)) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump/proc/jump_to_turf(turf/target) + if(is_mook) + var/mob/living/basic/mining/mook/mook_owner = owner + mook_owner.change_combatant_state(state = MOOK_ATTACK_ACTIVE) + new /obj/effect/temp_visual/mook_dust(get_turf(owner)) + playsound(get_turf(owner), 'sound/weapons/thudswoosh.ogg', 50, TRUE) + animate(owner, pixel_y = owner.base_pixel_y + 146, time = 0.5 SECONDS) + addtimer(CALLBACK(src, PROC_REF(land_on_turf), target), 0.5 SECONDS) + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump/proc/land_on_turf(turf/target) + do_teleport(owner, target, precision = 3, no_effects = TRUE) + animate(owner, pixel_y = owner.base_pixel_y, time = 0.5 SECONDS) + new /obj/effect/temp_visual/mook_dust(get_turf(owner)) + if(is_mook) + addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob/living/basic/mining/mook, change_combatant_state), MOOK_ATTACK_NEUTRAL), 0.5 SECONDS) + +/obj/effect/temp_visual/mook_dust + name = "dust" + desc = "It's just a dust cloud!" + icon = 'icons/mob/simple/jungle/mook.dmi' + icon_state = "mook_leap_cloud" + layer = BELOW_MOB_LAYER + plane = GAME_PLANE + base_pixel_y = -16 + base_pixel_x = -16 + duration = 1 SECONDS diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm new file mode 100644 index 000000000000..cf79eb06aa60 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm @@ -0,0 +1,427 @@ +///commands the chief can pick from +GLOBAL_LIST_INIT(mook_commands, list( + new /datum/pet_command/point_targeting/attack, + new /datum/pet_command/point_targeting/fetch, +)) + +/datum/ai_controller/basic_controller/mook + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mook, + BB_BLACKLIST_MINERAL_TURFS = list(/turf/closed/mineral/gibtonite, /turf/closed/mineral/strong), + BB_MAXIMUM_DISTANCE_TO_VILLAGE = 7, + BB_STORM_APPROACHING = FALSE, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/look_for_village, + /datum/ai_planning_subtree/targeted_mob_ability/leap, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/find_and_hunt_target/material_stand, + /datum/ai_planning_subtree/use_mob_ability/mook_jump, + /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/mook, + /datum/ai_planning_subtree/mine_walls/mook, + /datum/ai_planning_subtree/wander_away_from_village, + ) + +///check for faction if not a ash walker, otherwise just attack +/datum/targeting_strategy/basic/mook/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) + if(FACTION_ASHWALKER in living_mob.faction) + return FALSE + + return ..() + +/datum/ai_planning_subtree/targeted_mob_ability/leap + ability_key = BB_MOOK_LEAP_ABILITY + +/datum/ai_planning_subtree/use_mob_ability/mook_jump + ability_key = BB_MOOK_JUMP_ABILITY + +///jump towards the village when we have found ore or there is a storm coming +/datum/ai_planning_subtree/use_mob_ability/mook_jump/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] + var/mob/living/living_pawn = controller.pawn + var/obj/effect/home = controller.blackboard[BB_HOME_VILLAGE] + if(QDELETED(home)) + return + if(get_dist(living_pawn, home) < controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE]) + return + if(home.z != living_pawn.z) + return + if(!storm_approaching && !(locate(/obj/item/stack/ore) in living_pawn)) + return + + controller.clear_blackboard_key(BB_TARGET_MINERAL_WALL) + return ..() + +///hunt ores that we will haul off back to the village +/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/mook + +/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/mook/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(locate(/obj/item/stack/ore) in living_pawn) + return + return ..() + +///deposit ores into the stand! +/datum/ai_planning_subtree/find_and_hunt_target/material_stand + target_key = BB_MATERIAL_STAND_TARGET + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/material_stand + finding_behavior = /datum/ai_behavior/find_hunt_target + hunt_targets = list(/obj/structure/ore_container/material_stand) + hunt_range = 9 + +/datum/ai_planning_subtree/find_and_hunt_target/material_stand/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(!locate(/obj/item/stack/ore) in living_pawn) + return + return ..() + +/datum/ai_behavior/hunt_target/unarmed_attack_target/material_stand + required_distance = 0 + always_reset_target = TRUE + switch_combat_mode = TRUE + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT + +///try to face the counter when depositing ores +/datum/ai_behavior/hunt_target/unarmed_attack_target/material_stand/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) + . = ..() + var/atom/hunt_target = controller.blackboard[hunting_target_key] + if (QDELETED(hunt_target)) + return FALSE + var/list/possible_turfs = list() + var/list/directions = list(SOUTH, SOUTHEAST) + + for(var/direction in directions) + var/turf/bottom_turf = get_step(hunt_target, direction) + if(!bottom_turf.is_blocked_turf()) + possible_turfs += bottom_turf + + if(!length(possible_turfs)) + return FALSE + set_movement_target(controller, pick(possible_turfs)) + +///look for our village +/datum/ai_planning_subtree/look_for_village + +/datum/ai_planning_subtree/look_for_village/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_HOME_VILLAGE)) + return + + controller.queue_behavior(/datum/ai_behavior/find_village, BB_HOME_VILLAGE) + +/datum/ai_behavior/find_village + +/datum/ai_behavior/find_village/perform(seconds_per_tick, datum/ai_controller/controller, village_key) + . = ..() + + var/obj/effect/landmark/home_marker = locate(/obj/effect/landmark/mook_village) in GLOB.landmarks_list + if(isnull(home_marker)) + finish_action(controller, FALSE) + return + + controller.set_blackboard_key(village_key, home_marker) + finish_action(controller, TRUE) + +///explore the lands away from the village to look for ore +/datum/ai_planning_subtree/wander_away_from_village + +/datum/ai_planning_subtree/wander_away_from_village/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] + ///if we have ores to deposit or a storm is approaching, dont wander away + if(storm_approaching || (locate(/obj/item/stack/ore) in living_pawn)) + return + + if(controller.blackboard_key_exists(BB_HOME_VILLAGE)) + controller.queue_behavior(/datum/ai_behavior/wander, BB_HOME_VILLAGE) + +/datum/ai_behavior/wander + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + required_distance = 0 + /// distance we will wander away from the village + var/wander_distance = 9 + +/datum/ai_behavior/wander/setup(datum/ai_controller/controller, village_key) + . = ..() + var/mob/living/living_pawn = controller.pawn + var/obj/effect/target = controller.blackboard[village_key] + if(QDELETED(target)) + return FALSE + + if(target.z != living_pawn.z) + return FALSE + + var/list/angle_directions = list() + for(var/direction in GLOB.alldirs) + angle_directions += dir2angle(direction) + + var/angle_to_home = get_angle(living_pawn, target) + angle_directions -= angle_to_home + angle_directions -= (angle_to_home + 45) + angle_directions -= (angle_to_home - 45) + shuffle_inplace(angle_directions) + + var/turf/wander_destination = get_turf(living_pawn) + for(var/angle in angle_directions) + var/turf/test_turf = get_furthest_turf(living_pawn, angle, target) + if(isnull(test_turf)) + continue + var/distance_from_target = get_dist(target, test_turf) + if(distance_from_target <= get_dist(target, wander_destination)) + continue + wander_destination = test_turf + if(distance_from_target == wander_distance) + break + + set_movement_target(controller, wander_destination) + +/datum/ai_behavior/wander/proc/get_furthest_turf(atom/source, angle, atom/target) + var/turf/return_turf + for(var/i in 1 to wander_distance) + var/turf/test_destination = get_ranged_target_turf_direct(source, target, range = i, offset = angle) + if(test_destination.is_blocked_turf(source_atom = source)) + break + return_turf = test_destination + return return_turf + +/datum/ai_behavior/wander/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) + . = ..() + finish_action(controller, TRUE) + +/datum/ai_planning_subtree/mine_walls/mook + find_wall_behavior = /datum/ai_behavior/find_mineral_wall/mook + +/datum/ai_planning_subtree/mine_walls/mook/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] + if(storm_approaching || locate(/obj/item/stack/ore) in living_pawn) + return + return ..() + +/datum/ai_behavior/find_mineral_wall/mook + +/datum/ai_behavior/find_mineral_wall/mook/check_if_mineable(datum/ai_controller/controller, turf/target_wall) + var/list/forbidden_turfs = controller.blackboard[BB_BLACKLIST_MINERAL_TURFS] + if(is_type_in_list(target_wall, forbidden_turfs)) + return FALSE + return ..() + +///bard mook plays nice music for the village +/datum/ai_controller/basic_controller/mook/bard + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mook, + BB_MAXIMUM_DISTANCE_TO_VILLAGE = 10, + BB_STORM_APPROACHING = FALSE, + BB_SONG_LINES = MOOK_SONG, + ) + idle_behavior = /datum/idle_behavior/walk_near_target/mook_village + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/look_for_village, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/play_music_for_visitor, + /datum/ai_planning_subtree/use_mob_ability/mook_jump, + /datum/ai_planning_subtree/generic_play_instrument, + ) + + +///find an audience to follow and play music for! +/datum/ai_planning_subtree/play_music_for_visitor + +/datum/ai_planning_subtree/play_music_for_visitor/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard_key_exists(BB_MOOK_MUSIC_AUDIENCE)) + controller.queue_behavior(/datum/ai_behavior/find_and_set/music_audience, BB_MOOK_MUSIC_AUDIENCE, /mob/living/carbon/human) + return + var/atom/home = controller.blackboard[BB_HOME_VILLAGE] + if(isnull(home)) + return + + var/atom/human_target = controller.blackboard[BB_MOOK_MUSIC_AUDIENCE] + if(get_dist(human_target, home) > controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE] || controller.blackboard[BB_STORM_APPROACHING]) + controller.clear_blackboard_key(BB_MOOK_MUSIC_AUDIENCE) + return + + controller.queue_behavior(/datum/ai_behavior/travel_towards, BB_MOOK_MUSIC_AUDIENCE) + +/datum/ai_behavior/find_and_set/music_audience + +/datum/ai_behavior/find_and_set/music_audience/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/atom/home = controller.blackboard[BB_HOME_VILLAGE] + for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) + if(target.stat > UNCONSCIOUS || !target.mind) + continue + if(isnull(home) || get_dist(target, home) > controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE]) + continue + return target + +/datum/idle_behavior/walk_near_target/mook_village + target_key = BB_HOME_VILLAGE + +///healer mooks guard the village from intruders and heal the miner mooks when they come home +/datum/ai_controller/basic_controller/mook/support + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mook, + BB_MAXIMUM_DISTANCE_TO_VILLAGE = 10, + BB_STORM_APPROACHING = FALSE, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + ) + idle_behavior = /datum/idle_behavior/walk_near_target/mook_village + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/look_for_village, + /datum/ai_planning_subtree/acknowledge_chief, + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/use_mob_ability/mook_jump, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/find_and_hunt_target/injured_mooks, + ) + +///tree to find and register our leader +/datum/ai_planning_subtree/acknowledge_chief/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_MOOK_TRIBAL_CHIEF)) + return + controller.queue_behavior(/datum/ai_behavior/find_and_set/find_chief, BB_MOOK_TRIBAL_CHIEF, /mob/living/basic/mining/mook/worker/tribal_chief) + +/datum/ai_behavior/find_and_set/find_chief/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/mob/living/chief = locate(locate_path) in oview(search_range, controller.pawn) + if(isnull(chief)) + return null + var/mob/living/living_pawn = controller.pawn + living_pawn.befriend(chief) + return chief + +///find injured miner mooks after they come home from a long day of work +/datum/ai_planning_subtree/find_and_hunt_target/injured_mooks + target_key = BB_INJURED_MOOK + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/injured_mooks + finding_behavior = /datum/ai_behavior/find_hunt_target/injured_mooks + hunt_targets = list(/mob/living/basic/mining/mook/worker) + hunt_range = 9 + +///we only heal when the mooks are home during a storm +/datum/ai_planning_subtree/find_and_hunt_target/injured_mooks/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard[BB_STORM_APPROACHING]) + return ..() + + +/datum/ai_behavior/find_hunt_target/injured_mooks + +/datum/ai_behavior/find_hunt_target/injured_mooks/valid_dinner(mob/living/source, mob/living/injured_mook) + return (injured_mook.health < injured_mook.maxHealth) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/injured_mooks + +/datum/ai_behavior/hunt_target/unarmed_attack_target/injured_mooks + always_reset_target = TRUE + hunt_cooldown = 10 SECONDS + + +///the chief would rather command his mooks to attack people than attack them himself +/datum/ai_controller/basic_controller/mook/tribal_chief + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mook, + BB_STORM_APPROACHING = FALSE, + ) + idle_behavior = /datum/idle_behavior/walk_near_target/mook_village + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/look_for_village, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability/leap, + /datum/ai_planning_subtree/issue_commands, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/find_and_hunt_target/material_stand, + /datum/ai_planning_subtree/use_mob_ability/mook_jump, + /datum/ai_planning_subtree/find_and_hunt_target/bonfire, + /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/tribal_chief, + ) + +/datum/ai_planning_subtree/issue_commands + ///how far we look for a mook to command + var/command_distance = 5 + +/datum/ai_planning_subtree/issue_commands/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!locate(/mob/living/basic/mining/mook) in oview(command_distance, controller.pawn)) + return + if(controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_BASIC_MOB_CURRENT_TARGET, /datum/pet_command/point_targeting/attack) + return + + var/atom/ore_target = controller.blackboard[BB_ORE_TARGET] + var/mob/living/living_pawn = controller.pawn + if(isnull(ore_target)) + return + if(get_dist(ore_target, living_pawn) <= 1) + return + + controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_ORE_TARGET, /datum/pet_command/point_targeting/fetch) + +/datum/ai_behavior/issue_commands + action_cooldown = 5 SECONDS + +/datum/ai_behavior/issue_commands/perform(seconds_per_tick, datum/ai_controller/controller, target_key, command_path) + . = ..() + var/mob/living/basic/living_pawn = controller.pawn + var/atom/target = controller.blackboard[target_key] + + if(isnull(target)) + finish_action(controller, FALSE) + return + + var/datum/pet_command/to_command = locate(command_path) in GLOB.mook_commands + if(isnull(to_command)) + finish_action(controller, FALSE) + return + + var/issue_command = pick(to_command.speech_commands) + living_pawn.say(issue_command, forced = "controller") + living_pawn._pointed(target) + finish_action(controller, TRUE) + + +///find an ore, only pick it up when a mook brings it close to us +/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/tribal_chief + +/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/tribal_chief/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(locate(/obj/item/stack/ore) in living_pawn) + return + + var/atom/target_ore = controller.blackboard[BB_ORE_TARGET] + + if(isnull(target_ore)) + return ..() + + if(!isturf(target_ore.loc)) //picked up by someone else + controller.clear_blackboard_key(BB_ORE_TARGET) + return + + if(get_dist(target_ore, living_pawn) > 1) + return + + return ..() + +/datum/ai_planning_subtree/find_and_hunt_target/bonfire + target_key = BB_MOOK_BONFIRE_TARGET + finding_behavior = /datum/ai_behavior/find_hunt_target/bonfire + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/bonfire + hunt_targets = list(/obj/structure/bonfire) + hunt_range = 9 + + +/datum/ai_behavior/find_hunt_target/bonfire + +/datum/ai_behavior/find_hunt_target/bonfire/valid_dinner(mob/living/source, obj/structure/bonfire/fire, radius) + if(fire.burning) + return FALSE + + return can_see(source, fire, radius) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/bonfire + always_reset_target = TRUE diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_village.dm b/code/modules/mob/living/basic/lavaland/mook/mook_village.dm new file mode 100644 index 000000000000..ca2719ed7c67 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook_village.dm @@ -0,0 +1,25 @@ +///unique items that spawn at the mook village +/obj/structure/ore_container/material_stand + name = "material stand" + desc = "Is everyone free to use this thing?" + icon = 'icons/mob/simple/jungle/mook.dmi' + icon_state = "material_stand" + density = TRUE + anchored = TRUE + resistance_flags = INDESTRUCTIBLE + bound_width = 64 + bound_height = 64 + +///put ore icons on the counter! +/obj/structure/ore_container/material_stand/update_overlays() + . = ..() + for(var/obj/item/stack/ore/ore_item in contents) + var/image/ore_icon = image(icon = initial(ore_item.icon), icon_state = initial(ore_item.icon_state), layer = LOW_ITEM_LAYER) + ore_icon.transform = ore_icon.transform.Scale(0.6, 0.6) + ore_icon.pixel_x = rand(9, 17) + ore_icon.pixel_y = rand(2, 4) + . += ore_icon + +/obj/effect/landmark/mook_village + name = "mook village landmark" + icon_state = "x" diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher.dm index 327c202a67db..1c2ecefc6667 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher.dm @@ -66,7 +66,8 @@ var/datum/action/cooldown/mob_cooldown/watcher_gaze/gaze = new gaze_attack(src) gaze.Grant(src) - ai_controller.set_blackboard_key(BB_WATCHER_GAZE, gaze) + ai_controller.set_blackboard_key(BB_GENERIC_ACTION, gaze) + AddComponent(/datum/component/revenge_ability, gaze, targeting = GET_TARGETING_STRATEGY(ai_controller.blackboard[BB_TARGETING_STRATEGY])) /mob/living/basic/mining/watcher/update_overlays() . = ..() diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm index 61099d665996..b26929777d00 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm @@ -1,17 +1,18 @@ /datum/ai_controller/basic_controller/watcher blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) + ai_traits = PAUSE_DURING_DO_AFTER ai_movement = /datum/ai_movement/basic_avoidance idle_behavior = /datum/idle_behavior/idle_random_walk planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/target_retaliate/check_faction, /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/maintain_distance, /datum/ai_planning_subtree/use_mob_ability/gaze, /datum/ai_planning_subtree/targeted_mob_ability/overwatch, /datum/ai_planning_subtree/ranged_skirmish/watcher, - /datum/ai_planning_subtree/maintain_distance, ) /datum/ai_planning_subtree/targeted_mob_ability/overwatch @@ -41,13 +42,10 @@ return ..() /datum/ai_planning_subtree/ranged_skirmish/watcher - attack_behavior = /datum/ai_behavior/ranged_skirmish/watcher + min_range = 0 /datum/ai_planning_subtree/ranged_skirmish/watcher/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] if (QDELETED(target) || HAS_TRAIT(target, TRAIT_OVERWATCHED)) return // Don't bully people who are playing red light green light return ..() - -/datum/ai_behavior/ranged_skirmish/watcher - min_range = 0 diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm index 082157569609..d7c215de1278 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm @@ -8,10 +8,10 @@ button_icon_state = "gaze" background_icon_state = "bg_demon" overlay_icon_state = "bg_demon_border" - cooldown_time = 30 SECONDS - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED + cooldown_time = 20 SECONDS click_to_activate = FALSE shared_cooldown = NONE + melee_cooldown_time = 0 SECONDS /// At what range do we check for vision? var/effect_radius = 7 /// How long does it take to play our various animation stages diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm index faee2fda7200..5c9cef1027d3 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm @@ -9,7 +9,6 @@ background_icon_state = "bg_demon" overlay_icon_state = "bg_demon_border" cooldown_time = 20 SECONDS - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED click_to_activate = TRUE shared_cooldown = NONE /// Furthest range we can activate ability at diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_projectiles.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_projectiles.dm index 2680e9aa914c..40afd58c1da2 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher_projectiles.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_projectiles.dm @@ -7,7 +7,7 @@ armor_flag = ENERGY temperature = -50 -/obj/projectile/temp/watcher/on_hit(mob/living/target, blocked = 0) +/obj/projectile/temp/watcher/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() if (!isliving(target)) return diff --git a/code/modules/mob/living/basic/minebots/minebot.dm b/code/modules/mob/living/basic/minebots/minebot.dm new file mode 100644 index 000000000000..fbed972508b6 --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot.dm @@ -0,0 +1,173 @@ +/mob/living/basic/mining_drone + name = "\improper Nanotrasen minebot" + desc = "The instructions printed on the side read: This is a small robot used to support miners, can be set to search and collect loose ore, or to help fend off wildlife. Insert any type of ore into it to make it start listening to your commands!" + gender = NEUTER + icon = 'icons/mob/silicon/aibots.dmi' + icon_state = "mining_drone" + icon_living = "mining_drone" + basic_mob_flags = DEL_ON_DEATH + status_flags = CANSTUN|CANKNOCKDOWN|CANPUSH + mouse_opacity = MOUSE_OPACITY_ICON + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = 0 + health = 125 + maxHealth = 125 + melee_damage_lower = 15 + melee_damage_upper = 15 + obj_damage = 10 + attack_verb_continuous = "drills" + attack_verb_simple = "drill" + attack_sound = 'sound/weapons/circsawhit.ogg' + sentience_type = SENTIENCE_MINEBOT + speak_emote = list("states") + mob_biotypes = MOB_ROBOTIC + death_message = "blows apart!" + light_system = MOVABLE_LIGHT + light_outer_range = 6 + light_on = FALSE + ai_controller = /datum/ai_controller/basic_controller/minebot + ///the access card we use to access mining + var/obj/item/card/id/access_card + ///the gun we use to kill + var/obj/item/gun/energy/recharge/kinetic_accelerator/minebot/stored_gun + ///the commands our owner can give us + var/list/pet_commands = list( + /datum/pet_command/idle/minebot, + /datum/pet_command/minebot_ability/light, + /datum/pet_command/minebot_ability/dump, + /datum/pet_command/automate_mining, + /datum/pet_command/free/minebot, + /datum/pet_command/follow, + /datum/pet_command/point_targeting/attack/minebot, + ) + +/mob/living/basic/mining_drone/Initialize(mapload) + . = ..() + + var/static/list/death_drops = list(/obj/effect/decal/cleanable/robot_debris/old) + AddElement(/datum/element/death_drops, death_drops) + add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE), INNATE_TRAIT) + AddElement(/datum/element/footstep, FOOTSTEP_OBJ_ROBOT, 1, -6, sound_vary = TRUE) + AddComponent(\ + /datum/component/tameable,\ + food_types = list(/obj/item/stack/ore),\ + tame_chance = 100,\ + bonus_tame_chance = 5,\ + after_tame = CALLBACK(src, PROC_REF(activate_bot)),\ + ) + + var/datum/action/cooldown/mob_cooldown/minedrone/toggle_light/toggle_light_action = new(src) + var/datum/action/cooldown/mob_cooldown/minedrone/toggle_meson_vision/toggle_meson_vision_action = new(src) + var/datum/action/cooldown/mob_cooldown/minedrone/dump_ore/dump_ore_action = new(src) + toggle_light_action.Grant(src) + toggle_meson_vision_action.Grant(src) + dump_ore_action.Grant(src) + ai_controller.set_blackboard_key(BB_MINEBOT_LIGHT_ABILITY, toggle_light_action) + ai_controller.set_blackboard_key(BB_MINEBOT_DUMP_ABILITY, dump_ore_action) + + stored_gun = new(src) + var/obj/item/implant/radio/mining/comms = new(src) + comms.implant(src) + access_card = new /obj/item/card/id/advanced/gold(src) + SSid_access.apply_trim_to_card(access_card, /datum/id_trim/job/shaft_miner) + + RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(attempt_access)) + +/mob/living/basic/mining_drone/set_combat_mode(new_mode, silent = TRUE) + . = ..() + icon_state = (istate & ISTATE_HARM) ? "mining_drone_offense" : "mining_drone" + balloon_alert(src, "now [(istate & ISTATE_HARM) ? "attacking" : "collecting"]") + +/mob/living/basic/mining_drone/examine(mob/user) + . = ..() + if(health < maxHealth) + if(health >= maxHealth * 0.5) + . += span_warning("[p_They()] look slightly dented.") + else + . += span_boldwarning("[p_They()] look severely dented!") + + if(isnull(stored_gun) || !stored_gun.max_mod_capacity) + return + + . += "[stored_gun.get_remaining_mod_capacity()]% mod capacity remaining." + + for(var/obj/item/borg/upgrade/modkit/modkit as anything in stored_gun.modkits) + . += span_notice("There is \a [modkit] installed, using [modkit.cost]% capacity.") + + +/mob/living/basic/mining_drone/welder_act(mob/living/user, obj/item/welder) + if(user.istate & ISTATE_HARM) + return FALSE + if(istate & ISTATE_HARM) + user.balloon_alert(user, "can't repair in attack mode!") + return TRUE + if(maxHealth == health) + user.balloon_alert(user, "at full integrity!") + return TRUE + if(welder.use_tool(src, user, 0, volume=40)) + adjustBruteLoss(-15) + user.balloon_alert(user, "successfully repaired!") + return TRUE + +/mob/living/basic/mining_drone/attackby(obj/item/item_used, mob/user, params) + if(item_used.tool_behaviour == TOOL_CROWBAR || istype(item_used, /obj/item/borg/upgrade/modkit)) + item_used.melee_attack_chain(user, stored_gun, params) + return + + return ..() + +/mob/living/basic/mining_drone/attack_hand(mob/living/carbon/human/user, list/modifiers) + . = ..() + + if(. ||( user.istate & ISTATE_HARM)) + return + set_combat_mode(!(istate & ISTATE_HARM)) + balloon_alert(user, "now [(istate & ISTATE_HARM) ? "attacking wildlife" : "collecting loose ore"]") + +/mob/living/basic/mining_drone/RangedAttack(atom/target) + if(!(istate & ISTATE_HARM)) + return + stored_gun.afterattack(target, src) + + +/mob/living/basic/mining_drone/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) + . = ..() + + if(!. || !proximity_flag || (istate & ISTATE_HARM)) + return + + if(istype(attack_target, /obj/item/stack/ore)) + var/obj/item/target_ore = attack_target + target_ore.forceMove(src) + +/mob/living/basic/mining_drone/proc/drop_ore() + to_chat(src, span_notice("You dump your stored ore.")) + for(var/obj/item/stack/ore/dropped_item in contents) + dropped_item.forceMove(get_turf(src)) + +/mob/living/basic/mining_drone/proc/attempt_access(mob/drone, obj/door_attempt) + SIGNAL_HANDLER + + if(door_attempt.check_access(access_card)) + return ACCESS_ALLOWED + return ACCESS_DISALLOWED + +/mob/living/basic/mining_drone/proc/activate_bot() + AddComponent(/datum/component/obeys_commands, pet_commands) + +/mob/living/basic/mining_drone/death(gibbed) + drop_ore() + + if(isnull(stored_gun)) + return ..() + + for(var/obj/item/borg/upgrade/modkit/modkit as anything in stored_gun.modkits) + modkit.uninstall(stored_gun) + + return ..() + +/mob/living/basic/mining_drone/Destroy() + QDEL_NULL(stored_gun) + QDEL_NULL(access_card) + return ..() + diff --git a/code/modules/mob/living/basic/minebots/minebot_abilities.dm b/code/modules/mob/living/basic/minebots/minebot_abilities.dm new file mode 100644 index 000000000000..4f119fd9b66a --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot_abilities.dm @@ -0,0 +1,51 @@ + +/datum/action/cooldown/mob_cooldown/minedrone + button_icon = 'icons/mob/actions/actions_mecha.dmi' + background_icon_state = "bg_default" + overlay_icon_state = "bg_default_border" + click_to_activate = FALSE + +/datum/action/cooldown/mob_cooldown/minedrone/toggle_light + name = "Toggle Light" + button_icon_state = "mech_lights_off" + +/datum/action/cooldown/mob_cooldown/minedrone/Activate() + owner.set_light_on(!owner.light_on) + owner.balloon_alert(owner, "lights [owner.light_on ? "on" : "off"]!") + +/datum/action/cooldown/mob_cooldown/minedrone/dump_ore + name = "Dump Ore" + button_icon_state = "mech_eject" + +/datum/action/cooldown/mob_cooldown/minedrone/dump_ore/IsAvailable(feedback = TRUE) + if(locate(/obj/item/stack/ore) in owner.contents) + return TRUE + + if(feedback) + owner.balloon_alert(owner, "no ore!") + return FALSE + +/datum/action/cooldown/mob_cooldown/minedrone/dump_ore/Activate() + var/mob/living/basic/mining_drone/user = owner + user.drop_ore() + +/datum/action/cooldown/mob_cooldown/minedrone/toggle_meson_vision + name = "Toggle Meson Vision" + button_icon_state = "meson" + +/datum/action/cooldown/mob_cooldown/minedrone/toggle_meson_vision/Activate() + if(owner.sight & SEE_TURFS) + owner.clear_sight(SEE_TURFS) + owner.lighting_cutoff_red += 5 + owner.lighting_cutoff_green += 15 + owner.lighting_cutoff_blue += 5 + else + owner.add_sight(SEE_TURFS) + owner.lighting_cutoff_red -= 5 + owner.lighting_cutoff_green -= 15 + owner.lighting_cutoff_blue -= 5 + + owner.sync_lighting_plane_cutoff() + + to_chat(owner, span_notice("You toggle your meson vision [(owner.sight & SEE_TURFS) ? "on" : "off"].")) + diff --git a/code/modules/mob/living/basic/minebots/minebot_ai.dm b/code/modules/mob/living/basic/minebots/minebot_ai.dm new file mode 100644 index 000000000000..91b8500300ac --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot_ai.dm @@ -0,0 +1,218 @@ +/datum/ai_controller/basic_controller/minebot + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_BLACKLIST_MINERAL_TURFS = list(/turf/closed/mineral/gibtonite), + BB_AUTOMATED_MINING = FALSE, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot, + /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot, + /datum/ai_planning_subtree/minebot_mining, + /datum/ai_planning_subtree/locate_dead_humans, + ) + +///find dead humans and report their location on the radio +/datum/ai_planning_subtree/locate_dead_humans/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_NEARBY_DEAD_MINER)) + controller.queue_behavior(/datum/ai_behavior/send_sos_message, BB_NEARBY_DEAD_MINER) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_and_set/unconscious_human, BB_NEARBY_DEAD_MINER, /mob/living/carbon/human) + +/datum/ai_behavior/find_and_set/unconscious_human/search_tactic(datum/ai_controller/controller, locate_path, search_range) + for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) + if(target.stat >= UNCONSCIOUS && target.mind) + return target + +/datum/ai_behavior/send_sos_message + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + action_cooldown = 2 MINUTES + +/datum/ai_behavior/send_sos_message/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/carbon/target = controller.blackboard[target_key] + var/mob/living/living_pawn = controller.pawn + if(QDELETED(target) || is_station_level(target.z)) + finish_action(controller, FALSE, target_key) + return + var/turf/target_turf = get_turf(target) + var/obj/item/implant/radio/radio_implant = locate(/obj/item/implant/radio) in living_pawn.contents + if(!radio_implant) + finish_action(controller, FALSE, target_key) + return + var/message = "ALERT, [target] in need of help at coordinates: [target_turf.x], [target_turf.y], [target_turf.z]!" + radio_implant.radio.talk_into(living_pawn, message, RADIO_CHANNEL_SUPPLY) + finish_action(controller, TRUE, target_key) + +/datum/ai_behavior/send_sos_message/finish_action(datum/ai_controller/controller, success, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + +///operational datums is null because we dont use a ranged component, we use a gun in our contents +/datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot + operational_datums = null + ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/minebot + +/datum/ai_behavior/basic_ranged_attack/minebot + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT + avoid_friendly_fire = TRUE + +/datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(!(living_pawn.istate & ISTATE_HARM)) //we are not on attack mode + return + return ..() + +///mine walls if we are on automated mining mode +/datum/ai_planning_subtree/minebot_mining/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard[BB_AUTOMATED_MINING]) + return + if(controller.blackboard_key_exists(BB_TARGET_MINERAL_TURF)) + controller.queue_behavior(/datum/ai_behavior/minebot_mine_turf, BB_TARGET_MINERAL_TURF) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_mineral_wall/minebot, BB_TARGET_MINERAL_TURF) + +/datum/ai_behavior/find_mineral_wall/minebot + +/datum/ai_behavior/find_mineral_wall/minebot/check_if_mineable(datum/ai_controller/controller, turf/target_wall) + var/list/forbidden_turfs = controller.blackboard[BB_BLACKLIST_MINERAL_TURFS] + var/turf/previous_unreachable_wall = controller.blackboard[BB_PREVIOUS_UNREACHABLE_WALL] + if(is_type_in_list(target_wall, forbidden_turfs) || target_wall == previous_unreachable_wall) + return FALSE + controller.clear_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL) + return ..() + +/datum/ai_behavior/minebot_mine_turf + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + required_distance = 2 + action_cooldown = 3 SECONDS + +/datum/ai_behavior/minebot_mine_turf/setup(datum/ai_controller/controller, target_key) + . = ..() + var/turf/target = controller.blackboard[target_key] + if(isnull(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/minebot_mine_turf/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/basic/living_pawn = controller.pawn + var/turf/target = controller.blackboard[target_key] + + if(QDELETED(target)) + finish_action(controller, FALSE, target_key) + return + + if(check_obstacles_in_path(controller, target)) + finish_action(controller, FALSE, target_key) + return + + if(!(living_pawn.istate & ISTATE_HARM)) + living_pawn.set_combat_mode(TRUE) + + living_pawn.RangedAttack(target) + finish_action(controller, TRUE, target_key) + return + +/datum/ai_behavior/minebot_mine_turf/proc/check_obstacles_in_path(datum/ai_controller/controller, turf/target) + var/mob/living/source = controller.pawn + var/list/turfs_in_path = get_line(source, target) - target + for(var/turf/turf in turfs_in_path) + if(turf.is_blocked_turf(ignore_atoms = list(source))) + controller.set_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL, target) + return TRUE + return FALSE + +/datum/ai_behavior/minebot_mine_turf/finish_action(datum/ai_controller/controller, success, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + +///store ores in our body +/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores/minebot + hunt_chance = 100 + +/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/automated_mining = controller.blackboard[BB_AUTOMATED_MINING] + var/mob/living/living_pawn = controller.pawn + + if(!automated_mining && (living_pawn.istate & ISTATE_HARM)) //are we not on automated mining or collect mode? + return + + return ..() + +/datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores/minebot + hunt_cooldown = 2 SECONDS + +/datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores/minebot/target_caught(mob/living/hunter, obj/item/stack/ore/hunted) + if(hunter.istate & ISTATE_HARM) + hunter.set_combat_mode(FALSE) + return ..() + +///pet commands +/datum/pet_command/free/minebot + +/datum/pet_command/free/minebot/execute_action(datum/ai_controller/controller) + controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE) + return ..() + +/datum/pet_command/automate_mining + command_name = "Automate mining" + command_desc = "Make your minebot automatically mine!" + radial_icon = 'icons/obj/mining.dmi' + radial_icon_state = "pickaxe" + speech_commands = list("mine") + +/datum/pet_command/automate_mining/execute_action(datum/ai_controller/controller) + controller.set_blackboard_key(BB_AUTOMATED_MINING, TRUE) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + +/datum/pet_command/minebot_ability + command_name = "Minebot ability" + command_desc = "Make your minebot use one of its abilities." + radial_icon = 'icons/mob/actions/actions_mecha.dmi' + ///the ability we will use + var/ability_key + +/datum/pet_command/minebot_ability/execute_action(datum/ai_controller/controller) + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + if(!ability?.IsAvailable()) + return + controller.queue_behavior(/datum/ai_behavior/use_mob_ability, ability_key) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/pet_command/minebot_ability/light + command_name = "Toggle lights" + command_desc = "Make your minebot toggle its lights." + speech_commands = list("light") + radial_icon_state = "mech_lights_off" + ability_key = BB_MINEBOT_LIGHT_ABILITY + +/datum/pet_command/minebot_ability/dump + command_name = "Dump ore" + command_desc = "Make your minebot dump all its ore!" + speech_commands = list("dump", "ore") + radial_icon_state = "mech_eject" + ability_key = BB_MINEBOT_DUMP_ABILITY + +/datum/pet_command/point_targeting/attack/minebot + attack_behaviour = /datum/ai_behavior/basic_ranged_attack/minebot + +/datum/pet_command/point_targeting/attack/minebot/execute_action(datum/ai_controller/controller) + controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE) + var/mob/living/living_pawn = controller.pawn + if(!(living_pawn.istate & ISTATE_HARM)) + living_pawn.set_combat_mode(TRUE) + return ..() + +/datum/pet_command/idle/minebot + +/datum/pet_command/idle/minebot/execute_action(datum/ai_controller/controller) + controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE) + return ..() diff --git a/code/modules/mob/living/basic/minebots/minebot_upgrades.dm b/code/modules/mob/living/basic/minebots/minebot_upgrades.dm new file mode 100644 index 000000000000..6f5d43af1afc --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot_upgrades.dm @@ -0,0 +1,60 @@ +/obj/item/mine_bot_upgrade + name = "minebot melee upgrade" + desc = "A minebot upgrade." + icon_state = "door_electronics" + icon = 'icons/obj/assemblies/module.dmi' + +/obj/item/mine_bot_upgrade/afterattack(mob/living/basic/mining_drone/minebot, mob/user, proximity) + . = ..() + if(!istype(minebot) || !proximity) + return + upgrade_bot(minebot, user) + +/obj/item/mine_bot_upgrade/proc/upgrade_bot(mob/living/basic/mining_drone/minebot, mob/user) + if(minebot.melee_damage_upper != initial(minebot.melee_damage_upper)) + user.balloon_alert(user, "already has armor!") + return + minebot.melee_damage_lower += 7 + minebot.melee_damage_upper += 7 + to_chat(user, span_notice("You increase the close-quarter combat abilities of [minebot].")) + qdel(src) + +//Health + +/obj/item/mine_bot_upgrade/health + name = "minebot armor upgrade" + +/obj/item/mine_bot_upgrade/health/upgrade_bot(mob/living/basic/mining_drone/minebot, mob/user) + if(minebot.maxHealth != initial(minebot.maxHealth)) + to_chat(user, span_warning("[minebot] already has reinforced armor!")) + return + minebot.maxHealth += 45 + minebot.updatehealth() + to_chat(user, span_notice("You reinforce the armor of [minebot].")) + qdel(src) + +//AI + +/obj/item/slimepotion/slime/sentience/mining + name = "minebot AI upgrade" + desc = "Can be used to grant sentience to minebots. It's incompatible with minebot armor and melee upgrades, and will override them." + icon_state = "door_electronics" + icon = 'icons/obj/assemblies/module.dmi' + sentience_type = SENTIENCE_MINEBOT + ///health boost to add + var/base_health_add = 5 + ///damage boost to add + var/base_damage_add = 1 + ///speed boost to add + var/base_speed_add = 1 + ///cooldown boost to add + var/base_cooldown_add = 10 + +/obj/item/slimepotion/slime/sentience/mining/after_success(mob/living/user, mob/living/basic_mob) + if(!istype(basic_mob, /mob/living/basic/mining_drone)) + return + var/mob/living/basic/mining_drone/minebot = basic_mob + minebot.maxHealth = initial(minebot.maxHealth) + base_health_add + minebot.melee_damage_lower = initial(minebot.melee_damage_lower) + base_damage_add + minebot.melee_damage_upper = initial(minebot.melee_damage_upper) + base_damage_add + minebot.stored_gun?.recharge_time += base_cooldown_add diff --git a/code/modules/mob/living/basic/pets/dog/_dog.dm b/code/modules/mob/living/basic/pets/dog/_dog.dm index 778c0c2a1dd4..474a092d10f7 100644 --- a/code/modules/mob/living/basic/pets/dog/_dog.dm +++ b/code/modules/mob/living/basic/pets/dog/_dog.dm @@ -7,10 +7,10 @@ speech_commands = list("good dog") // Set correct attack behaviour -/datum/pet_command/point_targetting/attack/dog +/datum/pet_command/point_targeting/attack/dog attack_behaviour = /datum/ai_behavior/basic_melee_attack/dog -/datum/pet_command/point_targetting/attack/dog/set_command_active(mob/living/parent, mob/living/commander) +/datum/pet_command/point_targeting/attack/dog/set_command_active(mob/living/parent, mob/living/commander) . = ..() parent.ai_controller.set_blackboard_key(BB_DOG_HARASS_HARM, TRUE) @@ -33,14 +33,15 @@ attack_verb_simple = "bite" attack_sound = 'sound/weapons/bite.ogg' attack_vis_effect = ATTACK_EFFECT_BITE + melee_attack_cooldown = 0.8 SECONDS /// Instructions you can give to dogs var/static/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/good_boy/dog, /datum/pet_command/follow/dog, - /datum/pet_command/point_targetting/attack/dog, - /datum/pet_command/point_targetting/fetch, + /datum/pet_command/point_targeting/attack/dog, + /datum/pet_command/point_targeting/fetch, /datum/pet_command/play_dead, ) diff --git a/code/modules/mob/living/basic/pets/dog/corgi.dm b/code/modules/mob/living/basic/pets/dog/corgi.dm index 0b92fc38a05c..c8bb4c88b4ac 100644 --- a/code/modules/mob/living/basic/pets/dog/corgi.dm +++ b/code/modules/mob/living/basic/pets/dog/corgi.dm @@ -26,6 +26,8 @@ var/is_slow = FALSE ///Item slots that are available for this corgi to equip stuff into var/list/strippable_inventory_slots = list() + ///can this mob breed? + var/can_breed = TRUE /mob/living/basic/pet/dog/corgi/Initialize(mapload) . = ..() @@ -34,6 +36,13 @@ AddElement(/datum/element/swabable, CELL_LINE_TABLE_CORGI, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(on_tried_access)) RegisterSignals(src, list(COMSIG_BASICMOB_LOOK_ALIVE, COMSIG_BASICMOB_LOOK_DEAD), PROC_REF(on_appearance_change)) + if(!can_breed) + return + AddComponent(\ + /datum/component/breed,\ + can_breed_with = typecacheof(list(/mob/living/basic/pet/dog/corgi)),\ + baby_path = /mob/living/basic/pet/dog/corgi/puppy,\ + ) /mob/living/basic/pet/dog/corgi/Destroy() QDEL_NULL(inventory_head) @@ -521,9 +530,11 @@ icon_dead = "puppy_dead" density = FALSE pass_flags = PASSMOB + ai_controller = /datum/ai_controller/basic_controller/dog/puppy mob_size = MOB_SIZE_SMALL collar_icon_state = "puppy" strippable_inventory_slots = list(/datum/strippable_item/pet_collar, /datum/strippable_item/corgi_id) //puppies are too small to handle hats and back slot items + can_breed = FALSE //PUPPY IAN! SQUEEEEEEEEE~ /mob/living/basic/pet/dog/corgi/puppy/ian diff --git a/code/modules/mob/living/basic/pets/fox.dm b/code/modules/mob/living/basic/pets/fox.dm index 578a64ba08dd..7d74a6a36e41 100644 --- a/code/modules/mob/living/basic/pets/fox.dm +++ b/code/modules/mob/living/basic/pets/fox.dm @@ -38,9 +38,9 @@ /datum/ai_controller/basic_controller/fox blackboard = list( - BB_BASIC_MOB_FLEEING = TRUE, - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/of_size/ours_or_smaller/ignore_faction, - BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction + BB_ALWAYS_IGNORE_FACTION = TRUE, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/ours_or_smaller, + BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance @@ -61,12 +61,6 @@ /datum/ai_planning_subtree/random_speech/fox, ) -// Foxes will attack other station pets regardless of faction. -/datum/targetting_datum/basic/of_size/ours_or_smaller/ignore_faction - -/datum/targetting_datum/basic/of_size/ours_or_smaller/ignore_faction/faction_check(mob/living/living_mob, mob/living/the_target) - return FALSE - // The captain's fox, Renault /mob/living/basic/pet/fox/renault name = "Renault" diff --git a/code/modules/mob/living/basic/pets/parrot/_parrot.dm b/code/modules/mob/living/basic/pets/parrot/_parrot.dm new file mode 100644 index 000000000000..ff83a0a27131 --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/_parrot.dm @@ -0,0 +1,458 @@ +#define FORCED_SPEECH_COOLDOWN_DURATION 5 SECONDS + +GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list( + /datum/strippable_item/parrot_headset, +))) + + +/// Parrots! Klepto bastards that imitate your speech and hoard your shit. +/mob/living/basic/parrot + name = "parrot" + desc = "The parrot squawks, \"They're a Parrot! BAWWK!\"" + icon = 'icons/mob/simple/animal.dmi' + icon_state = "parrot_fly" + icon_living = "parrot_fly" + icon_dead = "parrot_dead" + density = FALSE + health = 80 + maxHealth = 80 + pass_flags = PASSTABLE | PASSMOB + + guaranteed_butcher_results = list(/obj/item/food/cracker = 1) + melee_damage_upper = 10 + melee_damage_lower = 5 + + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently moves aside" + response_disarm_simple = "gently move aside" + response_harm_continuous = "swats" + response_harm_simple = "swat" + istate = ISTATE_HARM + attack_verb_continuous = "chomps" + attack_verb_simple = "chomp" + attack_vis_effect = ATTACK_EFFECT_BITE + friendly_verb_continuous = "grooms" + friendly_verb_simple = "groom" + mob_size = MOB_SIZE_SMALL + gold_core_spawnable = FRIENDLY_SPAWN + + ai_controller = /datum/ai_controller/basic_controller/parrot + + /// Icon we use while sitting + var/icon_sit = "parrot_sit" + + ///Headset for Poly to yell at engineers :) + var/obj/item/radio/headset/ears = null + + ///Parrots are kleptomaniacs. This variable ... stores the item a parrot is holding. + var/obj/item/held_item = null + + /// The blackboard key we use to store the string we're repeating + var/speech_blackboard_key = BB_PARROT_REPEAT_STRING + /// The generic probability odds we have to do a speech-related action + var/speech_probability_rate = 5 + /// The generic probability odds we have to switch out our speech string + var/speech_shuffle_rate = 30 + + /// Contains all of the perches that parrots will generally sit on until something catches their eye. + var/static/list/desired_perches = typecacheof(list( + /obj/machinery/computer, + /obj/machinery/dna_scannernew, + /obj/machinery/nuclearbomb, + /obj/machinery/recharge_station, + /obj/machinery/smartfridge, + /obj/machinery/suit_storage_unit, + /obj/machinery/telecomms, + /obj/machinery/teleport, + /obj/structure/displaycase, + /obj/structure/filingcabinet, + /obj/structure/frame/computer, + )) + ///items we wont pick up + var/static/list/ignore_items = typecacheof(list(/obj/item/radio)) + + /// Food that Poly loves to eat (spoiler alert it's just crackers) + var/static/list/edibles = list( + /obj/item/food/cracker, + ) + + /// Tracks the times when we send a phrase through either being pet or attack to ensure it's not abused to flood + COOLDOWN_DECLARE(forced_speech_cooldown) + +/mob/living/basic/parrot/Initialize(mapload) + . = ..() + setup_headset() + update_speech_blackboards() + ai_controller.set_blackboard_key(BB_PARROT_PERCH_TYPES, desired_perches) + ai_controller.set_blackboard_key(BB_IGNORE_ITEMS, ignore_items) + AddElement(/datum/element/pet_bonus) // parrots will listen for the signal this element sends in order to say something in response to the pat + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/strippable, GLOB.strippable_parrot_items) + AddElement(/datum/element/simple_flying) + AddComponent(/datum/component/listen_and_repeat, desired_phrases = get_static_list_of_phrases(), blackboard_key = BB_PARROT_REPEAT_STRING) + AddComponent(\ + /datum/component/tameable,\ + food_types = edibles,\ + tame_chance = 100,\ + bonus_tame_chance = 0,\ + after_tame = CALLBACK(src, PROC_REF(tamed)),\ + ) + + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attacking)) + RegisterSignal(src, COMSIG_MOB_CLICKON, PROC_REF(on_click)) + RegisterSignal(src, COMSIG_PARENT_ATTACKBY_SECONDARY, PROC_REF(on_attacked)) // this means we could have a peaceful interaction, like getting a cracker + RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_injured)) // this means we got hurt and it's go time + RegisterSignal(src, COMSIG_ANIMAL_PET, PROC_REF(on_pet)) + RegisterSignal(src, COMSIG_KB_MOB_DROPITEM_DOWN, PROC_REF(drop_item_on_signal)) + +/mob/living/basic/parrot/Destroy() + // should have cleaned these up on death, but let's be super safe in case that didn't happen + if(!QDELETED(ears)) + QDEL_NULL(ears) + if(!QDELETED(held_item)) + QDEL_NULL(held_item) + return ..() + +/mob/living/basic/parrot/death(gibbed) + if(held_item) + held_item.forceMove(drop_location()) + held_item = null + + if(ears) + ears.forceMove(drop_location()) + ears = null + + if(!isnull(buckled)) + buckled.unbuckle_mob(src, force = TRUE) + buckled = null + pixel_x = base_pixel_x + pixel_y = base_pixel_y + + return ..() + +/mob/living/basic/parrot/examine(mob/user) + . = ..() + . += "It appears to [isnull(held_item) ? "not be holding anything." : "be holding \a [held_item]."]" + + if(stat != DEAD) + return + + if(HAS_MIND_TRAIT(user, TRAIT_NAIVE)) + . += pick( + "It seems tired and shagged out after a long squawk.", + "It seems to be pining for the fjords.", + "It's resting. It's a beautiful bird. Lovely plumage.", + ) + else + . += pick( + "This is a late parrot.", + "This is an ex-parrot.", + "This parrot is no more.", + ) + +/mob/living/basic/parrot/say_dead(message) + return // this is so flarped + +/mob/living/basic/parrot/get_status_tab_items() + . = ..() + . += "Held Item: [held_item]" + +/mob/living/basic/parrot/Process_Spacemove(movement_dir = 0, continuous_move = FALSE) + if(stat != DEAD) // parrots have evolved to let them fly in space because fucking uhhhhhhhhhh + return TRUE + return ..() + +/mob/living/basic/parrot/radio(message, list/message_mods = list(), list/spans, language) //literally copied from human/radio(), but there's no other way to do this. at least it's better than it used to be. + . = ..() + if(. != NONE) + return + + if(message_mods[MODE_HEADSET]) + if(ears) + ears.talk_into(src, message, , spans, language, message_mods) + return ITALICS | REDUCE_RANGE + else if(message_mods[RADIO_EXTENSION] == MODE_DEPARTMENT) + if(ears) + ears.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods) + return ITALICS | REDUCE_RANGE + else if(message_mods[RADIO_EXTENSION] in GLOB.radiochannels) + if(ears) + ears.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods) + return ITALICS | REDUCE_RANGE + + return NONE + +/mob/living/basic/parrot/update_icon_state() + . = ..() + if(HAS_TRAIT(src, TRAIT_PARROT_PERCHED)) + icon_state = icon_sit + else + icon_state = icon_living + +/// Proc that we just use to see if we're rightclicking something for perch behavior or dropping the item we currently ahve +/mob/living/basic/parrot/proc/on_click(mob/living/basic/source, atom/target, params) + SIGNAL_HANDLER + if(!LAZYACCESS(params, RIGHT_CLICK) || !CanReach(target)) + return + if(start_perching(target) && !isnull(held_item)) + drop_held_item(gently = TRUE) + +/// Proc that handles sending the signal and returning a valid phrase to say. Will not do anything if we don't have a stat or if we're cliented. +/// Will return either a string or null. +/mob/living/basic/parrot/proc/get_phrase() + if(!isnull(client) || stat != CONSCIOUS) + return null + + if(!COOLDOWN_FINISHED(src, forced_speech_cooldown)) + return null + + var/return_value = SEND_SIGNAL(src, COMSIG_NEEDS_NEW_PHRASE) + if(return_value & NO_NEW_PHRASE_AVAILABLE) + return null + + COOLDOWN_START(src, forced_speech_cooldown, FORCED_SPEECH_COOLDOWN_DURATION) + return ai_controller.blackboard[BB_PARROT_REPEAT_STRING] + +/// Proc that listens for when a parrot is pet so we can dispatch a voice line. +/mob/living/basic/parrot/proc/on_pet(mob/living/basic/source, mob/living/petter, modifiers) + SIGNAL_HANDLER + var/return_value = get_phrase() + if(isnull(return_value)) + return + + INVOKE_ASYNC(src, TYPE_PROC_REF(/atom/movable, say), message = return_value, forced = "parrot oneliner on pet") + +/// Proc that ascertains the type of perch we're dealing with and starts the perching process. +/// Returns TRUE if we started perching, FALSE otherwise. +/mob/living/basic/parrot/proc/start_perching(atom/target) + if(HAS_TRAIT(src, TRAIT_PARROT_PERCHED)) + balloon_alert(src, "already perched!") + return FALSE + + if(ishuman(target)) + return perch_on_human(target) + + if(!is_type_in_typecache(target, desired_perches)) + return FALSE + + forceMove(get_turf(target)) + drop_held_item(gently = TRUE) // comfy :) + toggle_perched(perched = TRUE) + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(after_move)) + return TRUE + +/mob/living/basic/parrot/proc/after_move(atom/source) + SIGNAL_HANDLER + + UnregisterSignal(src, COMSIG_MOVABLE_MOVED) + toggle_perched(perched = FALSE) + +/// Proc that will perch us on a human. Returns TRUE if we perched, FALSE otherwise. +/mob/living/basic/parrot/proc/perch_on_human(mob/living/carbon/human/target) + if(LAZYLEN(target.buckled_mobs) >= target.max_buckled_mobs) + balloon_alert(src, "can't perch on them!") + return FALSE + + forceMove(get_turf(target)) + if(!target.buckle_mob(src, TRUE)) + return FALSE + + to_chat(src, span_notice("You sit on [target]'s shoulder.")) + toggle_perched(perched = TRUE) + RegisterSignal(src, COMSIG_LIVING_SET_BUCKLED, PROC_REF(on_unbuckle)) + return TRUE + +/mob/living/basic/parrot/proc/on_unbuckle(mob/living/source, atom/movable/new_buckled) + SIGNAL_HANDLER + + if(new_buckled) + return + UnregisterSignal(src, COMSIG_LIVING_SET_BUCKLED) + toggle_perched(perched = FALSE) + +/mob/living/basic/parrot/proc/toggle_perched(perched) + if(!perched) + REMOVE_TRAIT(src, TRAIT_PARROT_PERCHED, TRAIT_GENERIC) + else + ADD_TRAIT(src, TRAIT_PARROT_PERCHED, TRAIT_GENERIC) + update_appearance(UPDATE_ICON_STATE) + +/// Master proc which will determine the intent of OUR attacks on an object and summon the relevant procs accordingly. +/// This is pretty much meant for players, AI will use the task-specific procs instead. +/mob/living/basic/parrot/proc/pre_attacking(mob/living/basic/source, atom/target) + SIGNAL_HANDLER + if(stat != CONSCIOUS) + return + + if(isitem(target) && steal_from_ground(target)) + return COMPONENT_HOSTILE_NO_ATTACK + + if(iscarbon(target) && steal_from_mob(target)) + return COMPONENT_HOSTILE_NO_ATTACK + +/// Picks up an item from the ground and puts it in our claws. Returns TRUE if we picked it up, FALSE otherwise. +/mob/living/basic/parrot/proc/steal_from_ground(obj/item/target) + if(!isnull(held_item)) + balloon_alert(src, "already holding something!") + return FALSE + + if(target.w_class > WEIGHT_CLASS_SMALL) + balloon_alert(src, "too big to pick up!") + return FALSE + + pick_up_item(target) + visible_message( + span_notice("[src] grabs [held_item]!"), + span_notice("You grab [held_item]!"), + span_hear("You hear the sounds of wings flapping furiously."), + ) + return TRUE + +/// Looks for an item that we can snatch and puts it in our claws. Returns TRUE if we picked it up, FALSE otherwise. +/mob/living/basic/parrot/proc/steal_from_mob(mob/living/carbon/victim) + if(!isnull(held_item)) + balloon_alert(src, "already holding something!") + return FALSE + + for(var/obj/item/stealable in victim.held_items) + if(stealable.w_class > WEIGHT_CLASS_SMALL) + continue + + if(!victim.temporarilyRemoveItemFromInventory(stealable)) + continue + + visible_message( + span_notice("[src] grabs [held_item] out of [victim]'s hand!"), + span_notice("You snag [held_item] out of [victim]'s hand!"), + span_hear("You hear the sounds of wings flapping furiously."), + ) + pick_up_item(stealable) + return TRUE + + return FALSE + +/// If we're right-clicked on with a cracker, we eat the cracker. +/mob/living/basic/parrot/proc/on_attacked(mob/living/basic/source, obj/item/thing, mob/living/attacker, params) + SIGNAL_HANDLER + if(!istype(thing, /obj/item/food/cracker)) // Poly wants a cracker + return + + consume_cracker(thing) // potential clash with the tameable element so we'll leave it to that to handle qdeling the cracker. + return COMPONENT_NO_AFTERATTACK + +/// Eats a cracker (or anything i guess). This would be nice to eventually fold into the basic_eating element but we do too much snowflake inventory code stuff for this to be reliable presently. +/// We don't qdel the item here, we assume the invoking proc will have handled that somehow. +/// Returns TRUE if we ate the thing. +/mob/living/basic/parrot/proc/consume_cracker(obj/item/thing) + to_chat(src, span_notice("[src] eagerly devours \the [thing].")) + if(!istype(thing, /obj/item/food/cracker)) + return TRUE // we still ate it + + if(health < maxHealth) + adjustBruteLoss(-10) + speech_probability_rate *= 1.27 + speech_shuffle_rate += 10 + update_speech_blackboards() + return TRUE + +/// Handles special behavior whenever we are injured. +/mob/living/basic/parrot/proc/on_injured(mob/living/basic/source, mob/living/attacker, attack_flags) + SIGNAL_HANDLER + if(!isnull(client) || stat == CONSCIOUS) + return + + drop_held_item(gently = FALSE) + + var/return_value = get_phrase() + if(isnull(return_value)) + return + + INVOKE_ASYNC(src, TYPE_PROC_REF(/atom/movable, say), message = return_value, forced = "parrot oneliner on attack") + +/// Handles picking up the item we're holding, done in its own proc because of a snowflake edge case we need to account for. No additional logic beyond that. +/// Returns TRUE if we picked it up, FALSE otherwise. +/mob/living/basic/parrot/proc/pick_up_item(obj/item/target) + if(istype(target, /obj/item/food/cracker)) + consume_cracker(target) + qdel(target) + return + + target.forceMove(src) + held_item = target + +/// Handles dropping items we're holding. Gently is a special modifier we can use for special interactions. +/mob/living/basic/parrot/proc/drop_held_item(gently = TRUE) + if(isnull(held_item)) + balloon_alert(src, "nothing to drop!") + return + + if(stat != CONSCIOUS) // don't gotta do shit + return + + if(!gently && isgrenade(held_item)) + var/obj/item/grenade/bomb = held_item + balloon_alert(src, "bombs away!") // you'll likely die too so we can get away with the `!` here + bomb.forceMove(drop_location()) + bomb.detonate() + return + + balloon_alert(src, "dropped item") + held_item.forceMove(drop_location()) + +/mob/living/basic/parrot/Exited(atom/movable/gone, direction) + . = ..() + if(gone != held_item) + return + held_item = null + +/mob/living/basic/parrot/vv_edit_var(var_name, vval) + . = ..() // give admins an easier time when it comes to fucking with poly + switch(var_name) + if(NAMEOF(src, speech_probability_rate)) + update_speech_blackboards() + if(NAMEOF(src, speech_shuffle_rate)) + update_speech_blackboards() + +/// Updates our speech blackboards mob-side to reflect the current speech on the controller to ensure everything is synchronized. +/mob/living/basic/parrot/proc/update_speech_blackboards() + ai_controller.set_blackboard_key(BB_PARROT_REPEAT_PROBABILITY, speech_probability_rate) + ai_controller.set_blackboard_key(BB_PARROT_PHRASE_CHANGE_PROBABILITY, speech_shuffle_rate) + +/// Will simply set up the headset for the parrot to use. Stub, implemented on subtypes. +/mob/living/basic/parrot/proc/setup_headset() + return + +/// Gets a static list of phrases we wish to pass to the element. +/mob/living/basic/parrot/proc/get_static_list_of_phrases() + var/static/list/default_phrases = list( + "BAWWWWK george mellons griffing me!", + "Cracker?", + "Hello!", + "Hi!", + ) + + return default_phrases + +/// Gets the available channels that this parrot has access to. Returns a list of the channels we can use. +/mob/living/basic/parrot/proc/get_available_channels() + var/list/returnable_list = list() + if(isnull(ears)) + return returnable_list + + var/list/headset_channels = ears.channels + for(var/channel in headset_channels) + returnable_list += GLOB.channel_tokens[channel] // will return something like ":e" or ":c" y'know + + return returnable_list + +/mob/living/basic/parrot/proc/tamed() + new /obj/effect/temp_visual/heart(drop_location()) + +/mob/living/basic/parrot/proc/drop_item_on_signal(mob/living/user) + SIGNAL_HANDLER + + drop_held_item() + return COMSIG_KB_ACTIVATED + +#undef FORCED_SPEECH_COOLDOWN_DURATION diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm new file mode 100644 index 000000000000..ea0b9a710117 --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm @@ -0,0 +1,40 @@ +/datum/ai_controller/basic_controller/parrot + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, + BB_HOARD_LOCATION_RANGE = 9, + ) + + ai_traits = STOP_MOVING_WHEN_PULLED + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk/parrot + + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/perch_on_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/hoard_items, + /datum/ai_planning_subtree/parrot_as_in_repeat, // always get a witty oneliner in when you can + ) + +/datum/idle_behavior/idle_random_walk/parrot + ///chance of us moving while perched + var/walk_chance_when_perched = 5 + +/datum/idle_behavior/idle_random_walk/parrot/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + walk_chance = HAS_TRAIT(living_pawn, TRAIT_PARROT_PERCHED) ? walk_chance_when_perched : initial(walk_chance) + return ..() + +/datum/ai_behavior/travel_towards/and_drop + +/datum/ai_behavior/travel_towards/and_drop/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + var/mob/living/living_mob = controller.pawn + var/obj/drop_item = locate(/obj/item) in (living_mob.contents - typecache_filter_list(living_mob.contents, controller.blackboard[BB_IGNORE_ITEMS])) + drop_item?.forceMove(get_turf(living_mob)) + +/datum/ai_behavior/basic_melee_attack/interact_once/parrot + +/datum/ai_behavior/basic_melee_attack/interact_once/parrot/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + controller.set_blackboard_key(BB_ALWAYS_IGNORE_FACTION, FALSE) diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/ghost_parrot_controller.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/ghost_parrot_controller.dm new file mode 100644 index 000000000000..58bdef408a5e --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/ghost_parrot_controller.dm @@ -0,0 +1,37 @@ +/// Used for ghost poly. +/datum/ai_controller/basic_controller/parrot/ghost + planning_subtrees = list( + /datum/ai_planning_subtree/parrot_as_in_repeat, + /datum/ai_planning_subtree/possess_humans, + /datum/ai_planning_subtree/hoard_items, + ) + +///subtree to possess humans +/datum/ai_planning_subtree/possess_humans + ///chance we go possess humans + var/possess_chance = 2 + +/datum/ai_planning_subtree/possess_humans/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + + if(controller.blackboard_key_exists(BB_PERCH_TARGET)) + controller.queue_behavior(/datum/ai_behavior/perch_on_target/haunt, BB_PERCH_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + + + if(!SPT_PROB(possess_chance, seconds_per_tick)) + if(ishuman(living_pawn.loc)) + return SUBTREE_RETURN_FINISH_PLANNING + return + + if(ishuman(living_pawn.loc)) + controller.set_blackboard_key(living_pawn.loc) + return + + controller.queue_behavior(/datum/ai_behavior/find_and_set/conscious_person, BB_PERCH_TARGET) + + +/datum/ai_behavior/perch_on_target/haunt + +/datum/ai_behavior/perch_on_target/haunt/check_human_conditions(mob/living/living_human) + return (living_human.stat != DEAD) diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm new file mode 100644 index 000000000000..7484cbfe6751 --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm @@ -0,0 +1,74 @@ +///subtree to steal items +/datum/ai_planning_subtree/hoard_items + var/theft_chance = 5 + +/datum/ai_planning_subtree/hoard_items/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + + var/turf/myspace = controller.blackboard[BB_HOARD_LOCATION] + + if(isnull(myspace) || myspace.is_blocked_turf(source_atom = controller.pawn) || get_dist(myspace, controller.pawn) > controller.blackboard[BB_HOARD_LOCATION_RANGE]) + controller.queue_behavior(/datum/ai_behavior/find_and_set/hoard_location, BB_HOARD_LOCATION, /turf/open) + return + + //we have an item, go drop! + var/list/our_contents = living_pawn.contents - typecache_filter_list(living_pawn.contents, controller.blackboard[BB_IGNORE_ITEMS]) + if(length(our_contents)) + controller.queue_behavior(/datum/ai_behavior/travel_towards/and_drop, BB_HOARD_LOCATION) + return SUBTREE_RETURN_FINISH_PLANNING + + if(controller.blackboard_key_exists(BB_HOARD_ITEM_TARGET)) + controller.queue_behavior(/datum/ai_behavior/basic_melee_attack/interact_once, BB_HOARD_ITEM_TARGET, BB_TARGETING_STRATEGY) + return SUBTREE_RETURN_FINISH_PLANNING + + if(!SPT_PROB(theft_chance, seconds_per_tick)) + return + controller.queue_behavior(/datum/ai_behavior/find_and_set/hoard_item, BB_HOARD_ITEM_TARGET) + +/datum/ai_behavior/find_and_set/hoard_location + +/datum/ai_behavior/find_and_set/hoard_location/search_tactic(datum/ai_controller/controller, locate_path, search_range) + for(var/turf/open/candidate in oview(search_range, controller.pawn)) + if(isspaceturf(candidate) || isopenspaceturf(candidate)) + continue + if(candidate.is_blocked_turf(source_atom = controller.pawn)) + continue + return candidate + + return null + +/datum/ai_behavior/find_and_set/hoard_item + action_cooldown = 5 SECONDS + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/find_and_set/hoard_item/search_tactic(datum/ai_controller/controller, locate_path, search_range) + if(!controller.blackboard_key_exists(BB_HOARD_LOCATION)) + return null + var/turf/nest_turf = controller.blackboard[BB_HOARD_LOCATION] + var/mob/living/living_pawn = controller.pawn + for(var/atom/potential_victim in oview(search_range, controller.pawn)) + if(is_type_in_typecache(potential_victim, controller.blackboard[BB_IGNORE_ITEMS])) + continue + if(potential_victim.loc == nest_turf) + continue + if(isitem(potential_victim)) + var/obj/item/item_steal = potential_victim + if(item_steal.w_class <= WEIGHT_CLASS_SMALL) + return potential_victim + if(!ishuman(potential_victim)) + continue + if(living_pawn.faction.Find(REF(potential_victim))) + continue //dont steal from friends + if(holding_valuable(controller, potential_victim)) + controller.set_blackboard_key(BB_ALWAYS_IGNORE_FACTION, TRUE) + return potential_victim + + return null + +/datum/ai_behavior/find_and_set/hoard_item/proc/holding_valuable(datum/ai_controller/controller, mob/living/human_target) + for(var/obj/item/potential_item in human_target.held_items) + if(is_type_in_typecache(potential_item, controller.blackboard[BB_IGNORE_ITEMS])) + continue + if(potential_item.w_class <= WEIGHT_CLASS_SMALL) + return TRUE + return FALSE diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm new file mode 100644 index 000000000000..1bb5c6fc960a --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm @@ -0,0 +1,78 @@ +///subtree to perch on targets +/datum/ai_planning_subtree/perch_on_target + ///perchance... + var/perch_chance = 5 + ///chance we unbuckle + var/unperch_chance = 15 + + +/datum/ai_planning_subtree/perch_on_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + var/atom/buckled_too = living_pawn.buckled + + //do we have a current target or is chance to unbuckle has passed? then unbuckle! + if(buckled_too) + if((SPT_PROB(unperch_chance, seconds_per_tick) || controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))) + controller.queue_behavior(/datum/ai_behavior/unbuckle_mob) + return + return SUBTREE_RETURN_FINISH_PLANNING + + //if we are perched, we can go find something else to perch too + var/final_chance = HAS_TRAIT(living_pawn, TRAIT_PARROT_PERCHED) ? unperch_chance : perch_chance + + if(!SPT_PROB(final_chance, seconds_per_tick) || controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + return + + if(controller.blackboard_key_exists(BB_PERCH_TARGET)) + controller.queue_behavior(/datum/ai_behavior/perch_on_target, BB_PERCH_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + + //50 50 chance to look for an object, or a friend + if(prob(50)) + controller.queue_behavior(/datum/ai_behavior/find_and_set/nearby_friends, BB_PERCH_TARGET) + return + + controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list, BB_PERCH_TARGET, controller.blackboard[BB_PARROT_PERCH_TYPES]) + +/// Parrot behavior that allows them to perch on a target. +/datum/ai_behavior/perch_on_target + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/perch_on_target/setup(datum/ai_controller/controller, target_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + + set_movement_target(controller, target) + +/datum/ai_behavior/perch_on_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + finish_action(controller, FALSE, target_key) + return + + var/mob/living/basic/parrot/living_pawn = controller.pawn + + if(!ishuman(target)) + living_pawn.start_perching(target) + finish_action(controller, TRUE, target_key) + return + + if(!check_human_conditions(target)) + finish_action(controller, FALSE, target_key) + return + + living_pawn.start_perching(target) + finish_action(controller, TRUE, target_key) + +/datum/ai_behavior/perch_on_target/proc/check_human_conditions(mob/living/living_human) + if(living_human.stat == DEAD || LAZYLEN(living_human.buckled_mobs) >= living_human.max_buckled_mobs) + return FALSE + + return TRUE + +/datum/ai_behavior/perch_on_target/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parroting_action.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parroting_action.dm new file mode 100644 index 000000000000..d1488a60b3bb --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parroting_action.dm @@ -0,0 +1,50 @@ +/// When a parrot... parrots... +/datum/ai_planning_subtree/parrot_as_in_repeat + operational_datums = list(/datum/component/listen_and_repeat) + +/datum/ai_planning_subtree/parrot_as_in_repeat/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/atom/speaking_pawn = controller.pawn + + var/switch_up_probability = controller.blackboard[BB_PARROT_PHRASE_CHANGE_PROBABILITY] + if(SPT_PROB(switch_up_probability, seconds_per_tick) || isnull(controller.blackboard[BB_PARROT_REPEAT_STRING])) + if(SEND_SIGNAL(speaking_pawn, COMSIG_NEEDS_NEW_PHRASE) & NO_NEW_PHRASE_AVAILABLE) + return + + if(!SPT_PROB(controller.blackboard[BB_PARROT_REPEAT_PROBABILITY], seconds_per_tick)) + return + + var/potential_string = controller.blackboard[BB_PARROT_REPEAT_STRING] + if(isnull(potential_string)) + stack_trace("Parrot As In Repeat Subtree somehow is getting a null potential string while not getting `NO_NEW_PHRASE_AVAILABLE`!") + return + + controller.queue_behavior(/datum/ai_behavior/perform_speech/parrot, potential_string) + +/datum/ai_behavior/perform_speech/parrot + action_cooldown = 7.5 SECONDS // gets really annoying (moreso than usual) really fast otherwise + +/datum/ai_behavior/perform_speech/parrot/perform(seconds_per_tick, datum/ai_controller/controller, speech, speech_sound) + var/mob/living/basic/parrot/speaking_pawn = controller.pawn + var/list/available_channels = speaking_pawn.get_available_channels() + var/modified_speech = speech + var/use_radio = prob(50) // we might not even use the radio if we even have a channel + +#define HAS_CHANNEL_PREFIX (speech[1] in GLOB.department_radio_prefixes) && (copytext_char(speech, 2, 3) in GLOB.department_radio_keys) // determine if we need to crop the channel prefix + + if(!length(available_channels)) // might not even use the radio at all + if(HAS_CHANNEL_PREFIX) + modified_speech = copytext_char(speech, 3) + + else + if(HAS_CHANNEL_PREFIX) + modified_speech = "[use_radio ? pick(available_channels) : ""][copytext_char(speech, 3)]" + else + modified_speech = "[use_radio ? pick(available_channels) : ""][speech]" + + + speaking_pawn.say(modified_speech, forced = "AI Controller") + if(speech_sound) + playsound(speaking_pawn, speech_sound, 80, vary = TRUE) + finish_action(controller, TRUE) + +#undef HAS_CHANNEL_PREFIX diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_items.dm b/code/modules/mob/living/basic/pets/parrot/parrot_items.dm new file mode 100644 index 000000000000..c071bf7fdbe6 --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_items.dm @@ -0,0 +1,60 @@ +/datum/strippable_item/parrot_headset + key = STRIPPABLE_ITEM_PARROT_HEADSET + +/datum/strippable_item/parrot_headset/get_item(atom/source) + var/mob/living/basic/parrot/poly/parrot_source = source + return istype(parrot_source) ? parrot_source.ears : null + +/datum/strippable_item/parrot_headset/try_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if (!.) + return FALSE + + if (!istype(equipping, /obj/item/radio/headset)) + to_chat(user, span_warning("[equipping] won't fit!")) + return FALSE + + return TRUE + +// There is no delay for putting a headset on a parrot. +/datum/strippable_item/parrot_headset/start_equip(atom/source, obj/item/equipping, mob/user) + return TRUE + +/datum/strippable_item/parrot_headset/finish_equip(atom/source, obj/item/equipping, mob/user) + var/obj/item/radio/headset/radio = equipping + if (!istype(radio)) + return + + var/mob/living/basic/parrot/parrot_source = source + if (!istype(parrot_source)) + return + + if (!user.transferItemToLoc(radio, source)) + return + + parrot_source.ears = radio + + to_chat(user, span_notice("You fit [radio] onto [source].")) + +/datum/strippable_item/parrot_headset/start_unequip(atom/source, mob/user) + . = ..() + if (!.) + return FALSE + + var/mob/living/basic/parrot/parrot_source = source + if (!istype(parrot_source)) + return + + if (parrot_source.stat == CONSCIOUS) + var/list/list_of_channels = parrot_source.get_available_channels() + parrot_source.say("[list_of_channels ? "[pick(list_of_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!", forced = "attempted headset removal") + + return TRUE + +/datum/strippable_item/parrot_headset/finish_unequip(atom/source, mob/user) + var/mob/living/basic/parrot/parrot_source = source + if (!istype(parrot_source)) + return + + parrot_source.ears.forceMove(parrot_source.drop_location()) + parrot_source.ears = null diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_subtypes.dm b/code/modules/mob/living/basic/pets/parrot/parrot_subtypes.dm new file mode 100644 index 000000000000..a0e8a4b52945 --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_subtypes.dm @@ -0,0 +1,14 @@ +// this file is for parrots that aren't poly + +/// Parrot that will just randomly spawn with a headset. Nothing too special beyond that. +/mob/living/basic/parrot/headsetted + +/mob/living/basic/parrot/headsetted/setup_headset() + var/headset = pick( + /obj/item/radio/headset/headset_cargo, + /obj/item/radio/headset/headset_eng, + /obj/item/radio/headset/headset_med, + /obj/item/radio/headset/headset_sci, + /obj/item/radio/headset/headset_sec, + ) + ears = new headset(src) diff --git a/code/modules/mob/living/basic/pets/parrot/poly.dm b/code/modules/mob/living/basic/pets/parrot/poly.dm new file mode 100644 index 000000000000..607c0e0eb26c --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/poly.dm @@ -0,0 +1,241 @@ +/// Default poly, presumably died the last shift and has no special traits. +#define POLY_DEFAULT "default" +/// Poly has survived a number of rounds equivalent to the longest survival of his being. +#define POLY_LONGEST_SURVIVAL "longest_survival" +/// Poly has survived a number of rounds equivalent to the longest deathstreak of his being. +#define POLY_BEATING_DEATHSTREAK "longest_deathstreak" +/// Poly has only just survived a round, and is doing a consecutive one. +#define POLY_CONSECUTIVE_ROUND "consecutive_round" +/// haunt filter we apply to who we possess +#define POLY_POSSESS_FILTER +/// haunt filter color we apply to who we possess +#define POLY_POSSESS_GLOW "#522059" + +/// The classically famous compadre to the Chief Engineer, Poly. +/mob/living/basic/parrot/poly + name = "Poly" + desc = "Poly the Parrot. An expert on quantum cracker theory." + gold_core_spawnable = NO_SPAWN + speech_probability_rate = 13 + + /// Callback to save our memory at the end of the round. + var/datum/callback/roundend_callback = null + /// Did we write the memory to disk? + var/memory_saved = FALSE + /// How long has this bird been alive for? + var/rounds_survived = 0 + /// How long have we survived for at max? + var/longest_survival = 0 + /// How many rounds in a row have we been dead for? + var/longest_deathstreak = 0 + +/mob/living/basic/parrot/poly/Initialize(mapload) + . = ..() + + if(!memory_saved) + roundend_callback = CALLBACK(src, PROC_REF(Write_Memory)) + SSticker.OnRoundend(roundend_callback) + + update_appearance() + +/mob/living/basic/parrot/poly/Destroy() + LAZYREMOVE(SSticker.round_end_events, roundend_callback) // we do the memory writing stuff on death, but this is important to yeet as fast as we can if we need to destroy + roundend_callback = null + return ..() + +/mob/living/basic/parrot/poly/death(gibbed) + if(HAS_TRAIT(src, TRAIT_DONT_WRITE_MEMORY)) + return ..() // Don't read memory either. + if(!memory_saved) + Write_Memory(TRUE) + var/special_status = determine_special_poly() + if(special_status == POLY_LONGEST_SURVIVAL || special_status == POLY_BEATING_DEATHSTREAK || prob(0.666)) + var/mob/living/basic/parrot/poly/ghost/specter = new(loc) + if(mind) + mind.transfer_to(specter) + else + specter.key = key + return ..() + +/mob/living/basic/parrot/poly/get_static_list_of_phrases() // there's only one poly, so there should only be one ongoing list of phrases. i guess + var/static/list/phrases_to_return = list() + if(length(phrases_to_return)) + return phrases_to_return + + phrases_to_return += read_memory() // must come first!!! + // now add some valuable lines every poly should have + phrases_to_return += list( + ":e Check the crystal, you chucklefucks!", + ":e OH GOD ITS ABOUT TO DELAMINATE CALL THE SHUTTLE", + ":e WHO TOOK THE DAMN MODSUITS?", + ":e Wire the solars, you lazy bums!", + "Poly wanna cracker!", + ) + switch(determine_special_poly()) + if(POLY_DEFAULT) + phrases_to_return += pick("...alive?", "This isn't parrot heaven!", "I live, I die, I live again!", "The void fades!") + if(POLY_LONGEST_SURVIVAL) + phrases_to_return += pick("...[longest_survival].", "The things I've seen!", "I have lived many lives!", "What are you before me?") + if(POLY_BEATING_DEATHSTREAK) + phrases_to_return += pick("What are you waiting for!", "Violence breeds violence!", "Blood! Blood!", "Strike me down if you dare!") + if(POLY_CONSECUTIVE_ROUND) + phrases_to_return += pick("...again?", "No, It was over!", "Let me out!", "It never ends!") + + return phrases_to_return + +/mob/living/basic/parrot/poly/update_desc() + . = ..() + switch(determine_special_poly()) + if(POLY_LONGEST_SURVIVAL) + desc += " Old as sin, and just as loud. Claimed to be [rounds_survived]." + if(POLY_BEATING_DEATHSTREAK) + desc += " The squawks of [-rounds_survived] dead parrots ring out in your ears..." + if(POLY_CONSECUTIVE_ROUND) + desc += " Over [rounds_survived] shifts without a \"terrible\" \"accident\"!" + +/mob/living/basic/parrot/poly/update_icon() + . = ..() + switch(determine_special_poly()) + if(POLY_LONGEST_SURVIVAL) + add_atom_colour("#EEEE22", FIXED_COLOUR_PRIORITY) + if(POLY_BEATING_DEATHSTREAK) + add_atom_colour("#BB7777", FIXED_COLOUR_PRIORITY) + +/// Reads the memory of the parrot, and updates the necessary variables. Returns a list of phrases to add to the parrot's speech buffer. +/mob/living/basic/parrot/poly/proc/read_memory() + RETURN_TYPE(/list) + var/list/returnable_list = list() + if(fexists("data/npc_saves/Poly.sav")) //legacy compatability to convert old format to new + var/savefile/legacy = new /savefile("data/npc_saves/Poly.sav") + legacy["phrases"] >> returnable_list + legacy["roundssurvived"] >> rounds_survived + legacy["longestsurvival"] >> longest_survival + legacy["longestdeathstreak"] >> longest_deathstreak + fdel("data/npc_saves/Poly.sav") + + else + var/json_file = file("data/npc_saves/Poly.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + returnable_list = json["phrases"] + rounds_survived = json["roundssurvived"] + longest_survival = json["longestsurvival"] + longest_deathstreak = json["longestdeathstreak"] + + return returnable_list + +/// Determines the type of Poly we might have here based on the statistics we got from the memory. +/mob/living/basic/parrot/poly/proc/determine_special_poly() + if(rounds_survived == longest_survival) + return POLY_LONGEST_SURVIVAL + else if(rounds_survived == longest_deathstreak) + return POLY_BEATING_DEATHSTREAK + else if(rounds_survived > 0) + return POLY_CONSECUTIVE_ROUND + else + return POLY_DEFAULT + +/mob/living/basic/parrot/poly/Write_Memory(dead, gibbed) + . = ..() + if(!. || memory_saved) // if we die, no more memory + return FALSE + + if(!dead && (stat != DEAD)) + dead = FALSE + + var/file_path = "data/npc_saves/Poly.json" + var/list/file_data = list() + + var/list/exportable_speech_buffer = ai_controller.blackboard[BB_EXPORTABLE_STRING_BUFFER_LIST] // should have been populated when we sent the signal out on parent + if(!!length(exportable_speech_buffer)) + file_data["phrases"] = exportable_speech_buffer + + if(dead) + file_data["roundssurvived"] = min(rounds_survived - 1, 0) + file_data["longestsurvival"] = longest_survival + if(rounds_survived - 1 < longest_deathstreak) + file_data["longestdeathstreak"] = rounds_survived - 1 + else + file_data["longestdeathstreak"] = longest_deathstreak + else + + file_data["roundssurvived"] = max(rounds_survived, 0) + 1 + if(rounds_survived + 1 > longest_survival) + file_data["longestsurvival"] = rounds_survived + 1 + else + file_data["longestsurvival"] = longest_survival + file_data["longestdeathstreak"] = longest_deathstreak + + var/formatted_data +#if DM_VERSION >= 515 + formatted_data = json_encode(file_data, JSON_PRETTY_PRINT) +#else + formatted_data = json_encode(file_data) +#endif + + rustg_file_write(formatted_data, file_path) + memory_saved = TRUE + return TRUE + +/mob/living/basic/parrot/poly/setup_headset() + ears = new /obj/item/radio/headset/headset_eng(src) + +/mob/living/basic/parrot/poly/ghost + name = "The Ghost of Poly" + desc = "Doomed to squawk the Earth." + color = "#FFFFFF77" + status_flags = GODMODE + sentience_type = SENTIENCE_BOSS //This is so players can't mindswap into ghost poly to become a literal god + incorporeal_move = INCORPOREAL_MOVE_BASIC + butcher_results = list(/obj/item/ectoplasm = 1) + ai_controller = /datum/ai_controller/basic_controller/parrot/ghost + speech_probability_rate = 1 + +/mob/living/basic/parrot/poly/ghost/Initialize(mapload) + // block anything and everything that could possibly happen with writing memory for ghosts + memory_saved = TRUE + ADD_TRAIT(src, TRAIT_DONT_WRITE_MEMORY, INNATE_TRAIT) + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) + return ..() + +//we perch on human souls +/mob/living/basic/parrot/poly/ghost/perch_on_human(mob/living/carbon/human/target) + if(loc == target) //dismount + forceMove(get_turf(target)) + return FALSE + if(ishuman(loc)) + balloon_alert(src, "already possessing!") + return FALSE + forceMove(target) + return TRUE + +/mob/living/basic/parrot/poly/ghost/proc/on_moved(atom/movable/movable, atom/old_loc) + SIGNAL_HANDLER + + if(ishuman(old_loc)) + var/mob/living/unpossessed_human = old_loc + unpossessed_human.remove_filter(POLY_POSSESS_FILTER) + return + + if(!ishuman(loc)) + return + + var/mob/living/possessed_human = loc + possessed_human.add_filter(POLY_POSSESS_FILTER, 2, list("type" = "outline", "color" = POLY_POSSESS_GLOW, "size" = 2)) + var/filter = possessed_human.get_filter(POLY_POSSESS_FILTER) + + if(filter) + animate(filter, alpha = 200, time = 2 SECONDS, loop = -1) + animate(alpha = 60, time = 2 SECONDS) + + var/datum/disease/parrot_possession/on_possession = new /datum/disease/parrot_possession + on_possession.set_parrot(src) + possessed_human.ForceContractDisease(on_possession, make_copy = FALSE, del_on_fail = TRUE) + +#undef POLY_DEFAULT +#undef POLY_LONGEST_SURVIVAL +#undef POLY_BEATING_DEATHSTREAK +#undef POLY_CONSECUTIVE_ROUND +#undef POLY_POSSESS_FILTER +#undef POLY_POSSESS_GLOW diff --git a/code/modules/mob/living/basic/pets/penguin.dm b/code/modules/mob/living/basic/pets/penguin.dm index 5729a8c761dd..82c46ea08321 100644 --- a/code/modules/mob/living/basic/pets/penguin.dm +++ b/code/modules/mob/living/basic/pets/penguin.dm @@ -84,7 +84,7 @@ /datum/ai_controller/basic_controller/penguin blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED @@ -178,7 +178,7 @@ /datum/ai_controller/basic_controller/penguin/baby blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FIND_MOM_TYPES = list(/mob/living/basic/pet/penguin), BB_IGNORE_MOM_TYPES = list(/mob/living/basic/pet/penguin/baby), ) diff --git a/code/modules/mob/living/simple_animal/friendly/sloth.dm b/code/modules/mob/living/basic/pets/sloth.dm similarity index 50% rename from code/modules/mob/living/simple_animal/friendly/sloth.dm rename to code/modules/mob/living/basic/pets/sloth.dm index 8e308c58d12d..0b1546ccf93d 100644 --- a/code/modules/mob/living/simple_animal/friendly/sloth.dm +++ b/code/modules/mob/living/basic/pets/sloth.dm @@ -1,63 +1,68 @@ -GLOBAL_DATUM(cargo_sloth, /mob/living/simple_animal/sloth) +GLOBAL_DATUM(cargo_sloth, /mob/living/basic/sloth) -/mob/living/simple_animal/sloth +/mob/living/basic/sloth name = "sloth" desc = "An adorable, sleepy creature." icon = 'icons/mob/simple/pets.dmi' icon_state = "sloth" icon_living = "sloth" icon_dead = "sloth_dead" + speak_emote = list("yawns") - emote_hear = list("snores.","yawns.") - emote_see = list("dozes off.", "looks around sleepily.") - speak_chance = 1 - turns_per_move = 5 + can_be_held = TRUE - butcher_results = list(/obj/item/food/meat/slab = 3) + held_state = "sloth" + response_help_continuous = "pets" response_help_simple = "pet" response_disarm_continuous = "gently pushes aside" response_disarm_simple = "gently push aside" response_harm_continuous = "kicks" response_harm_simple = "kick" + + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + mob_biotypes = MOB_ORGANIC|MOB_BEAST gold_core_spawnable = FRIENDLY_SPAWN + melee_damage_lower = 18 melee_damage_upper = 18 health = 50 maxHealth = 50 - speed = 10 - held_state = "sloth" - ///In the case 'melee_damage_upper' is somehow raised above 0 - attack_verb_continuous = "bites" - attack_verb_simple = "bite" - attack_sound = 'sound/weapons/bite.ogg' - attack_vis_effect = ATTACK_EFFECT_BITE + speed = 10 // speed is fucking weird man. they aren't fast though don't worry + butcher_results = list(/obj/item/food/meat/slab = 3) - footstep_type = FOOTSTEP_MOB_CLAW + ai_controller = /datum/ai_controller/basic_controller/sloth -/mob/living/simple_animal/sloth/Initialize(mapload) +/mob/living/basic/sloth/Initialize(mapload) . = ..() AddElement(/datum/element/pet_bonus, "slowly smiles!") + AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_CLAW) + AddElement(/datum/element/ai_retaliate) + AddComponent(/datum/component/tree_climber) + + if(!mapload || isnull(GLOB.cargo_sloth) || !is_station_level(z)) + return + // If someone adds non-cargo sloths to maps we'll have a problem but we're fine for now - if(!GLOB.cargo_sloth && mapload && is_station_level(z)) - GLOB.cargo_sloth = src + GLOB.cargo_sloth = src -/mob/living/simple_animal/sloth/Destroy() +/mob/living/basic/sloth/Destroy() if(GLOB.cargo_sloth == src) GLOB.cargo_sloth = null return ..() -//Cargo Sloth -/mob/living/simple_animal/sloth/paperwork +/mob/living/basic/sloth/paperwork name = "Paperwork" desc = "Cargo's pet sloth. About as useful as the rest of the techs." + gender = MALE gold_core_spawnable = NO_SPAWN -//Cargo Sloth 2 - -/mob/living/simple_animal/sloth/citrus +/mob/living/basic/sloth/citrus name = "Citrus" desc = "Cargo's pet sloth. She's dressed in a horrible sweater." icon_state = "cool_sloth" @@ -66,3 +71,26 @@ GLOBAL_DATUM(cargo_sloth, /mob/living/simple_animal/sloth) gender = FEMALE butcher_results = list(/obj/item/toy/spinningtoy = 1) gold_core_spawnable = NO_SPAWN + +/// They're really passive in game, so they just wanna get away if you start smacking them. No trees in space from them to use for clawing your eyes out, but they will try if desperate. +/datum/ai_controller/basic_controller/sloth + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + + ai_traits = STOP_MOVING_WHEN_PULLED + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate/to_flee, + /datum/ai_planning_subtree/flee_target/from_flee_key, + /datum/ai_planning_subtree/climb_trees, + /datum/ai_planning_subtree/random_speech/sloth, + ) + +/datum/ai_planning_subtree/random_speech/sloth + speech_chance = 1 + emote_hear = list("snores.", "yawns.") + emote_see = list("dozes off.", "looks around sleepily.") diff --git a/code/modules/mob/living/simple_animal/hostile/skeleton.dm b/code/modules/mob/living/basic/ruin_defender/skeleton.dm similarity index 60% rename from code/modules/mob/living/simple_animal/hostile/skeleton.dm rename to code/modules/mob/living/basic/ruin_defender/skeleton.dm index 8655f5f5a8b5..c8de723b9513 100644 --- a/code/modules/mob/living/simple_animal/hostile/skeleton.dm +++ b/code/modules/mob/living/basic/ruin_defender/skeleton.dm @@ -1,58 +1,69 @@ -/mob/living/simple_animal/hostile/skeleton +/mob/living/basic/skeleton name = "reanimated skeleton" desc = "A real bonefied skeleton, doesn't seem like it wants to socialize." gender = NEUTER icon = 'icons/mob/simple/simple_human.dmi' mob_biotypes = MOB_UNDEAD|MOB_HUMANOID - turns_per_move = 5 speak_emote = list("rattles") - emote_see = list("rattles") - istate = ISTATE_HARM|ISTATE_BLOCKING maxHealth = 40 health = 40 - speed = 1 - harm_intent_damage = 5 + basic_mob_flags = DEL_ON_DEATH melee_damage_lower = 15 melee_damage_upper = 15 - minbodytemp = 0 - maxbodytemp = 1500 - healable = 0 //they're skeletons how would bruise packs help them?? + unsuitable_atmos_damage = 0 + unsuitable_cold_damage = 0 + unsuitable_heat_damage = 0 attack_verb_continuous = "slashes" attack_verb_simple = "slash" - attack_sound = 'sound/hallucinations/growl1.ogg' + attack_sound = 'sound/weapons/slash.ogg' attack_vis_effect = ATTACK_EFFECT_CLAW - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 5 - robust_searching = 1 - stat_attack = HARD_CRIT faction = list(FACTION_SKELETON) // Going for a sort of pale bluegreen here, shooting for boneish lighting_cutoff_red = 15 lighting_cutoff_green = 25 lighting_cutoff_blue = 35 - footstep_type = FOOTSTEP_MOB_SHOE death_message = "collapses into a pile of bones!" - del_on_death = TRUE - loot = list(/obj/effect/decal/remains/human) + ai_controller = /datum/ai_controller/basic_controller/skeleton + /// Loot this mob drops on death. + var/list/loot = list(/obj/effect/decal/remains/human) /// Path of the outfit we give to the mob's visuals. var/outfit = null /// Path of the species we give to the mob's visuals. var/species = /datum/species/skeleton /// Path of the held item we give to the mob's visuals. var/held_item + /// Types of milk skeletons like to drink + var/static/list/good_drinks = list( + /obj/item/reagent_containers/condiment/milk, + ) + /// Bad milk that skeletons hate + var/static/list/bad_drinks = list( + /obj/item/reagent_containers/condiment/soymilk, + ) -/mob/living/simple_animal/hostile/skeleton/Initialize(mapload) +/mob/living/basic/skeleton/Initialize(mapload) . = ..() apply_dynamic_human_appearance(src, outfit, species, r_hand = held_item) + AddElement(/datum/element/footstep, FOOTSTEP_MOB_SHOE) + if(LAZYLEN(loot)) + loot = string_list(loot) + AddElement(/datum/element/death_drops, loot) + AddElement(/datum/element/basic_eating, heal_amt = 50, drinking = TRUE, food_types = good_drinks) + AddElement(/datum/element/basic_eating, heal_amt = 0, damage_amount = 25, damage_type = BURN, drinking = TRUE, food_types = bad_drinks) + ADD_TRAIT(src, TRAIT_SNOWSTORM_IMMUNE, INNATE_TRAIT) + ai_controller?.set_blackboard_key(BB_BASIC_FOODS, good_drinks + bad_drinks) -/mob/living/simple_animal/hostile/skeleton/eskimo - name = "undead eskimo" - desc = "The reanimated remains of some poor traveler." +/mob/living/basic/skeleton/settler + name = "undead settler" + desc = "The reanimated remains of some poor settler." maxHealth = 55 health = 55 - weather_immunities = list(TRAIT_SNOWSTORM_IMMUNE) melee_damage_lower = 17 melee_damage_upper = 20 + attack_verb_continuous = "jabs" + attack_verb_simple = "jab" + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH death_message = "collapses into a pile of bones, its gear falling to the floor!" loot = list( /obj/effect/decal/remains/human, @@ -60,27 +71,28 @@ /obj/item/clothing/shoes/winterboots, /obj/item/clothing/suit/hooded/wintercoat, ) - outfit = /datum/outfit/eskimo + outfit = /datum/outfit/settler held_item = /obj/item/spear -/datum/outfit/eskimo - name = "Eskimo" +/datum/outfit/settler + name = "Settler" suit = /obj/item/clothing/suit/hooded/wintercoat shoes = /obj/item/clothing/shoes/winterboots -/mob/living/simple_animal/hostile/skeleton/templar +/mob/living/basic/skeleton/templar name = "undead templar" desc = "The reanimated remains of a holy templar knight." maxHealth = 150 health = 150 - weather_immunities = list(TRAIT_SNOWSTORM_IMMUNE) speed = 2 - speak_chance = 1 - speak = list("THE GODS WILL IT!","DEUS VULT!","REMOVE KABAB!") force_threshold = 10 //trying to simulate actually having armor obj_damage = 50 melee_damage_lower = 25 melee_damage_upper = 30 + attack_verb_continuous = "slices" + attack_verb_simple = "slice" + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH death_message = "collapses into a pile of bones, its gear clanging as it hits the ground!" loot = list( /obj/effect/decal/remains/human, @@ -96,17 +108,16 @@ suit = /obj/item/clothing/suit/chaplainsuit/armor/templar r_hand = /obj/item/claymore/weak -/mob/living/simple_animal/hostile/skeleton/ice +/mob/living/basic/skeleton/ice name = "ice skeleton" desc = "A reanimated skeleton protected by a thick sheet of natural ice armor. Looks slow, though." speed = 5 maxHealth = 75 health = 75 - weather_immunities = list(TRAIT_SNOWSTORM_IMMUNE) color = rgb(114,228,250) loot = list(/obj/effect/decal/remains/human{color = rgb(114,228,250)}) -/mob/living/simple_animal/hostile/skeleton/plasmaminer +/mob/living/basic/skeleton/plasmaminer name = "shambling miner" desc = "A plasma-soaked miner, their exposed limbs turned into a grossly incandescent bone seemingly made of plasma." icon_state = "plasma_miner" @@ -114,7 +125,6 @@ icon_dead = "plasma_miner" maxHealth = 150 health = 150 - harm_intent_damage = 10 melee_damage_lower = 15 melee_damage_upper = 20 light_color = LIGHT_COLOR_PURPLE @@ -124,20 +134,19 @@ outfit = /datum/outfit/plasma_miner species = /datum/species/plasmaman -/mob/living/simple_animal/hostile/skeleton/plasmaminer/jackhammer +/mob/living/basic/skeleton/plasmaminer/jackhammer desc = "A plasma-soaked miner, their exposed limbs turned into a grossly incandescent bone seemingly made of plasma. They seem to still have their mining tool in their hand, gripping tightly." icon_state = "plasma_miner_tool" icon_living = "plasma_miner_tool" icon_dead = "plasma_miner_tool" maxHealth = 185 health = 185 - harm_intent_damage = 15 melee_damage_lower = 20 melee_damage_upper = 25 attack_verb_continuous = "blasts" attack_verb_simple = "blast" attack_sound = 'sound/weapons/sonic_jackhammer.ogg' - attack_vis_effect = null // jackhammer moment + attack_vis_effect = null loot = list(/obj/effect/decal/remains/plasma, /obj/item/pickaxe/drill/jackhammer) held_item = /obj/item/pickaxe/drill/jackhammer @@ -146,3 +155,24 @@ uniform = /obj/item/clothing/under/rank/cargo/miner/lavaland suit = /obj/item/clothing/suit/hooded/explorer mask = /obj/item/clothing/mask/gas/explorer + +// Skeleton AI + +/// Skeletons mostly just beat people to death, but they'll also find and drink milk. +/datum/ai_controller/basic_controller/skeleton + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_EMOTE_KEY = "rattles", + BB_EMOTE_CHANCE = 20, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + + planning_subtrees = list( + /datum/ai_planning_subtree/run_emote, + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) diff --git a/code/modules/mob/living/basic/ruin_defender/stickman.dm b/code/modules/mob/living/basic/ruin_defender/stickman.dm index b0e40d9b8ad4..55776584d31c 100644 --- a/code/modules/mob/living/basic/ruin_defender/stickman.dm +++ b/code/modules/mob/living/basic/ruin_defender/stickman.dm @@ -13,6 +13,7 @@ attack_verb_simple = "punch" melee_damage_lower = 10 melee_damage_upper = 10 + melee_attack_cooldown = 1.5 SECONDS attack_sound = 'sound/weapons/punch1.ogg' istate = ISTATE_HARM|ISTATE_BLOCKING faction = list(FACTION_STICKMAN) @@ -28,22 +29,16 @@ /datum/ai_controller/basic_controller/stickman blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic() + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance idle_behavior = /datum/idle_behavior/idle_random_walk planning_subtrees = list( /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/stickman + /datum/ai_planning_subtree/basic_melee_attack_subtree ) -/datum/ai_planning_subtree/basic_melee_attack_subtree/stickman - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/stickman - -/datum/ai_behavior/basic_melee_attack/stickman - action_cooldown = 1.5 SECONDS - /mob/living/basic/stickman/dog name = "Angry Stick Dog" desc = "Stickman's best friend, if he could see him at least." diff --git a/code/modules/mob/living/basic/ruin_defender/wizard/wizard.dm b/code/modules/mob/living/basic/ruin_defender/wizard/wizard.dm new file mode 100644 index 000000000000..2dfb4d41e054 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/wizard/wizard.dm @@ -0,0 +1,90 @@ +/mob/living/basic/wizard + name = "Space Wizard" + desc = "A wizard is never early. Nor is he late. He arrives exactly at the worst possible moment." + icon = 'icons/mob/simple/simple_human.dmi' + icon_state = "wizard" + icon_living = "wizard" + icon_dead = "wizard_dead" + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + sentience_type = SENTIENCE_HUMANOID + speed = 0 + maxHealth = 100 + health = 100 + melee_damage_lower = 5 + melee_damage_upper = 5 + attack_verb_continuous = "punches" + attack_verb_simple = "punch" + attack_sound = 'sound/weapons/punch1.ogg' + istate = ISTATE_HARM | ISTATE_BLOCKING + habitable_atmos = list("min_oxy" = 5, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) + unsuitable_atmos_damage = 7.5 + faction = list(ROLE_WIZARD) + basic_mob_flags = DEL_ON_DEATH + ai_controller = /datum/ai_controller/basic_controller/wizard + + /// A list of possible wizard corpses, and therefore wizard outfits, to select from + var/static/list/wizard_outfits = list( + /obj/effect/mob_spawn/corpse/human/wizard = 5, + /obj/effect/mob_spawn/corpse/human/wizard/red = 3, + /obj/effect/mob_spawn/corpse/human/wizard/yellow = 3, + /obj/effect/mob_spawn/corpse/human/wizard/black = 3, + /obj/effect/mob_spawn/corpse/human/wizard/marisa = 1, + //The tape wizard should go here, but its hat doesn't render correctly for some reason. + ) + /// A specified wizard corpse spawner to use. If null, picks from the list above instead. + var/selected_outfit + + /// Typepath for the wizard's targeted spell. If null, selects randomly. + var/targeted_spell_path + /// List of possible targeted spells to pick from + var/static/list/targeted_spell_list = list( + /datum/action/cooldown/spell/pointed/projectile/fireball/lesser, + /datum/action/cooldown/spell/pointed/projectile/lightningbolt, + ) + + /// Typepath for the wizard's secondary spell. If null, selects randomly. + var/secondary_spell_path + /// List of possible secondary spells to pick from + var/static/list/secondary_spell_list = list( + /datum/action/cooldown/spell/aoe/magic_missile, + /datum/action/cooldown/spell/charged/beam/tesla, + /datum/action/cooldown/spell/aoe/repulse, + /datum/action/cooldown/spell/conjure/the_traps, + ) + +/mob/living/basic/wizard/Initialize(mapload) + . = ..() + if(!selected_outfit) + selected_outfit = pick_weight(wizard_outfits) + apply_dynamic_human_appearance(src, mob_spawn_path = selected_outfit, r_hand = /obj/item/staff) + var/list/remains = string_list(list( + selected_outfit, + /obj/item/staff + )) + AddElement(/datum/element/death_drops, remains) + AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_SHOE) + + if(isnull(targeted_spell_path)) + targeted_spell_path = pick(targeted_spell_list) + if(isnull(secondary_spell_path)) + secondary_spell_path = pick(secondary_spell_list) + + var/datum/action/cooldown/spell/targeted_spell = new targeted_spell_path(src) + targeted_spell.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) + targeted_spell.Grant(src) + ai_controller.set_blackboard_key(BB_WIZARD_TARGETED_SPELL, targeted_spell) + + var/datum/action/cooldown/spell/secondary_spell = new secondary_spell_path(src) + secondary_spell.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) + secondary_spell.Grant(src) + ai_controller.set_blackboard_key(BB_WIZARD_SECONDARY_SPELL, secondary_spell) + + var/datum/action/cooldown/spell/teleport/radius_turf/blink/lesser/blink_spell = new(src) + blink_spell.Grant(src) + ai_controller.set_blackboard_key(BB_WIZARD_BLINK_SPELL, blink_spell) + +/// Uses the colors and loadout of the original wizard simplemob +/mob/living/basic/wizard/classic + selected_outfit = /obj/effect/mob_spawn/corpse/human/wizard + targeted_spell_path = /datum/action/cooldown/spell/pointed/projectile/fireball/lesser + secondary_spell_path = /datum/action/cooldown/spell/aoe/magic_missile diff --git a/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm b/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm new file mode 100644 index 000000000000..4430b1339a74 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm @@ -0,0 +1,53 @@ +#define WIZARD_SPELL_COOLDOWN (1 SECONDS) + +/** + * Wizards run away from their targets while flinging spells at them and blinking constantly. + */ +/datum/ai_controller/basic_controller/wizard + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/maintain_distance/cover_minimum_distance, + /datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/primary, + /datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/secondary, + /datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/blink, + ) + +/** + * Cast a wizard spell. There is a minimum cooldown between spellcasts to prevent overwhelming spam. + * + * Though only the primary spell is actually targeted, all spells use targeted behavior so that they + * only get used in combat. + */ +/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell + use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/wizard_spell + +/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if (controller.blackboard[BB_WIZARD_SPELL_COOLDOWN] > world.time) + return + return ..() + +/datum/ai_behavior/targeted_mob_ability/wizard_spell/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) + . = ..() + controller.set_blackboard_key(BB_WIZARD_SPELL_COOLDOWN, world.time + WIZARD_SPELL_COOLDOWN) + +/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/primary + ability_key = BB_WIZARD_TARGETED_SPELL + +/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/secondary + ability_key = BB_WIZARD_SECONDARY_SPELL + +/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/blink + ability_key = BB_WIZARD_BLINK_SPELL + +/datum/ai_behavior/use_mob_ability/wizard_spell/perform(seconds_per_tick, datum/ai_controller/controller, ability_key) + . = ..() + controller.set_blackboard_key(BB_WIZARD_SPELL_COOLDOWN, world.time + WIZARD_SPELL_COOLDOWN) + +#undef WIZARD_SPELL_COOLDOWN diff --git a/code/modules/mob/living/basic/ruin_defender/wizard/wizard_spells.dm b/code/modules/mob/living/basic/ruin_defender/wizard/wizard_spells.dm new file mode 100644 index 000000000000..c49d87c730a5 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/wizard/wizard_spells.dm @@ -0,0 +1,17 @@ +// Lesser versions of wizard spells to be used by AI wizards + +/// Lesser fireball, which is slightly less "instant death" than the normal one +/datum/action/cooldown/spell/pointed/projectile/fireball/lesser + name = "Lesser Fireball" + projectile_type = /obj/projectile/magic/fireball/lesser + cooldown_time = 10 SECONDS + +/obj/projectile/magic/fireball/lesser + damage = 0 + exp_light = 1 + +/// Lesser Blink, shorter range than the normal blink spell +/datum/action/cooldown/spell/teleport/radius_turf/blink/lesser + name = "Lesser Blink" + outer_tele_radius = 3 + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC diff --git a/code/modules/mob/living/basic/space_fauna/ant.dm b/code/modules/mob/living/basic/space_fauna/ant.dm index f38319f0d585..27aa66cb0521 100644 --- a/code/modules/mob/living/basic/space_fauna/ant.dm +++ b/code/modules/mob/living/basic/space_fauna/ant.dm @@ -45,7 +45,7 @@ /datum/ai_controller/basic_controller/ant blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/space_fauna/bear/_bear.dm b/code/modules/mob/living/basic/space_fauna/bear/_bear.dm index ae039b9e2c0f..cc34f271caca 100644 --- a/code/modules/mob/living/basic/space_fauna/bear/_bear.dm +++ b/code/modules/mob/living/basic/space_fauna/bear/_bear.dm @@ -42,7 +42,7 @@ /mob/living/basic/bear/Initialize(mapload) . = ..() - ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) + add_traits(list(TRAIT_SPACEWALK, TRAIT_FENCE_CLIMBER), INNATE_TRAIT) AddElement(/datum/element/ai_retaliate) AddComponent(/datum/component/tree_climber, climbing_distance = 15) AddElement(/datum/element/swabable, CELL_LINE_TABLE_BEAR, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) diff --git a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm index 14ab5676ead6..7ec94a9cd02d 100644 --- a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm +++ b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm @@ -1,6 +1,3 @@ -/datum/ai_behavior/basic_melee_attack/bear - action_cooldown = 2 SECONDS - /datum/ai_behavior/find_hunt_target/find_hive /datum/ai_behavior/find_hunt_target/find_hive/valid_dinner(mob/living/source, obj/structure/beebox/hive, radius) diff --git a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm index 3e5d22a31afb..75746abefc80 100644 --- a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm +++ b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm @@ -1,6 +1,6 @@ /datum/ai_controller/basic_controller/bear blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance @@ -8,16 +8,13 @@ planning_subtrees = list( /datum/ai_planning_subtree/target_retaliate, /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/bear, + /datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/climb_trees, /datum/ai_planning_subtree/find_and_hunt_target/find_hive, /datum/ai_planning_subtree/find_and_hunt_target/find_honeycomb, /datum/ai_planning_subtree/random_speech/bear, ) -/datum/ai_planning_subtree/basic_melee_attack_subtree/bear - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/bear - /datum/ai_planning_subtree/find_and_hunt_target/find_hive target_key = BB_FOUND_HONEY hunting_behavior = /datum/ai_behavior/hunt_target/find_hive diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm index d49fd27be6cc..b42000f27701 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -32,6 +32,7 @@ attack_vis_effect = ATTACK_EFFECT_BITE attack_verb_continuous = "bites" attack_verb_simple = "bite" + melee_attack_cooldown = 1.5 SECONDS response_help_continuous = "pets" response_help_simple = "pet" response_disarm_continuous = "gently pushes aside" @@ -57,7 +58,7 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targetting/attack/carp + /datum/pet_command/point_targeting/attack ) /// Carp want to eat raw meat var/static/list/desired_food = list(/obj/item/food/meat/slab, /obj/item/food/meat/rawcutlet) @@ -120,7 +121,7 @@ teleport = new(src) teleport.Grant(src) ai_controller.set_blackboard_key(BB_CARP_RIFT, teleport) - ai_controller.set_blackboard_key(BB_OBSTACLE_TARGETTING_WHITELIST, allowed_obstacle_targets) + ai_controller.set_blackboard_key(BB_OBSTACLE_TARGETING_WHITELIST, allowed_obstacle_targets) /mob/living/basic/carp/Destroy() @@ -129,8 +130,8 @@ /// Tell the elements and the blackboard what food we want to eat /mob/living/basic/carp/proc/setup_eating() - AddElement(/datum/element/basic_eating, 10, 0, null, desired_food) - AddElement(/datum/element/basic_eating, 0, 10, BRUTE, desired_trash) // We are killing our planet + AddElement(/datum/element/basic_eating, food_types = desired_food) + AddElement(/datum/element/basic_eating, heal_amt = 0, damage_amount = 10, damage_type = BRUTE, food_types = desired_trash) // We are killing our planet ai_controller.set_blackboard_key(BB_BASIC_FOODS, desired_food + desired_trash) /// Set a random colour on the carp, override to do something else diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm index c0f5143f18c2..9e767bab3af1 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm @@ -1,17 +1,8 @@ #define MAGICARP_SPELL_TARGET_SEEK_RANGE 4 -/datum/pet_command/point_targetting/attack/carp - attack_behaviour = /datum/ai_behavior/basic_melee_attack/carp - -/datum/pet_command/point_targetting/use_ability/magicarp +/datum/pet_command/point_targeting/use_ability/magicarp pet_ability_key = BB_MAGICARP_SPELL -/datum/ai_planning_subtree/basic_melee_attack_subtree/carp - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/carp - -/datum/ai_behavior/basic_melee_attack/carp - action_cooldown = 1.5 SECONDS - /datum/ai_planning_subtree/attack_obstacle_in_path/carp attack_behaviour = /datum/ai_behavior/attack_obstructions/carp @@ -20,40 +11,38 @@ /// As basic attack tree but interrupt if your health gets low or if your spell is off cooldown /datum/ai_planning_subtree/basic_melee_attack_subtree/magicarp - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/carp/magic + melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/magicarp /// Interrupt your attack chain if: you have a spell, it's not on cooldown, and it has a target -/datum/ai_behavior/basic_melee_attack/carp/magic +/datum/ai_behavior/basic_melee_attack/magicarp -/datum/ai_behavior/basic_melee_attack/carp/magic/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key, health_ratio_key) +/datum/ai_behavior/basic_melee_attack/magicarp/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, health_ratio_key) var/datum/action/cooldown/using_action = controller.blackboard[BB_MAGICARP_SPELL] if (QDELETED(using_action)) return ..() - if (!controller.blackboard[BB_MAGICARP_SPELL_SPECIAL_TARGETTING] && using_action.IsAvailable()) + if (!controller.blackboard[BB_MAGICARP_SPELL_SPECIAL_TARGETING] && using_action.IsAvailable()) finish_action(controller, succeeded = FALSE) return return ..() /** * Find a target for the magicarp's spell - * This gets weird because different spells want different targetting + * This gets weird because different spells want different targeting * but I didn't want a new ai controller for every different spell */ /datum/ai_planning_subtree/find_nearest_magicarp_spell_target /datum/ai_planning_subtree/find_nearest_magicarp_spell_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) var/datum/action/cooldown/using_action = controller.blackboard[BB_MAGICARP_SPELL] - if (QDELETED(using_action)) - return - if (!using_action.IsAvailable()) + if (!using_action?.IsAvailable()) return - var/spell_targetting = controller.blackboard[BB_MAGICARP_SPELL_SPECIAL_TARGETTING] - if (!spell_targetting) - controller.queue_behavior(/datum/ai_behavior/find_potential_targets/nearest/magicarp, BB_MAGICARP_SPELL_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + var/spell_targeting = controller.blackboard[BB_MAGICARP_SPELL_SPECIAL_TARGETING] + if (!spell_targeting) + controller.queue_behavior(/datum/ai_behavior/find_potential_targets/nearest/magicarp, BB_MAGICARP_SPELL_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) return - switch(spell_targetting) + switch(spell_targeting) if (MAGICARP_SPELL_CORPSES) controller.queue_behavior(/datum/ai_behavior/find_and_set/friendly_corpses, BB_MAGICARP_SPELL_TARGET, MAGICARP_SPELL_TARGET_SEEK_RANGE) return diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm index 88a9f6706f2e..fc6997896b0d 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm @@ -12,9 +12,10 @@ if (!rift_behaviour) CRASH("Forgot to specify rift behaviour for [src]") - var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if (!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + return var/datum/action/cooldown/using_action = controller.blackboard[BB_CARP_RIFT] - if (QDELETED(target) || QDELETED(using_action) || !using_action.IsAvailable()) + if (!using_action?.IsAvailable()) return controller.queue_behavior(rift_behaviour, BB_CARP_RIFT, BB_BASIC_MOB_CURRENT_TARGET) @@ -30,7 +31,7 @@ finish_planning = TRUE /datum/ai_planning_subtree/make_carp_rift/panic_teleport/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (!controller.blackboard[BB_BASIC_MOB_FLEEING]) + if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) return return ..() diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm index 75c915c16a11..0befb20987c0 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm @@ -9,8 +9,9 @@ */ /datum/ai_controller/basic_controller/carp blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(), - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends() + BB_BASIC_MOB_STOP_FLEEING = TRUE, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) ai_movement = /datum/ai_movement/basic_avoidance @@ -25,7 +26,7 @@ /datum/ai_planning_subtree/attack_obstacle_in_path/carp, /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift, /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree/carp, + /datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/carp_migration, ) @@ -35,8 +36,9 @@ */ /datum/ai_controller/basic_controller/carp/pet blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends() + BB_BASIC_MOB_STOP_FLEEING = TRUE, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) ai_traits = STOP_MOVING_WHEN_PULLED planning_subtrees = list( @@ -48,7 +50,7 @@ /datum/ai_planning_subtree/attack_obstacle_in_path/carp, /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift, /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree/carp, + /datum/ai_planning_subtree/basic_melee_attack_subtree, ) /** @@ -71,3 +73,27 @@ /datum/ai_planning_subtree/basic_melee_attack_subtree/magicarp, /datum/ai_planning_subtree/carp_migration, ) + +/** + * Carp which bites back, but doesn't look for targets and doesnt do as much damage + * Still migrate and stuff + */ +/datum/ai_controller/basic_controller/carp/passive + blackboard = list( + BB_BASIC_MOB_STOP_FLEEING = TRUE, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + ) + ai_traits = STOP_MOVING_WHEN_PULLED + planning_subtrees = list( + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, + /datum/ai_planning_subtree/make_carp_rift/panic_teleport, + /datum/ai_planning_subtree/flee_target, + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/attack_obstacle_in_path/carp, + /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift, + /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/carp_migration, + ) diff --git a/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm b/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm index 5051335a5571..65d16cfb490d 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm @@ -56,8 +56,8 @@ GLOBAL_LIST_INIT(magicarp_spell_colours, list( /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targetting/attack/carp, - /datum/pet_command/point_targetting/use_ability/magicarp, + /datum/pet_command/point_targeting/attack, + /datum/pet_command/point_targeting/use_ability/magicarp, ) /// List of all projectiles we can fire. /// Non-static, because subtypes can have their own lists. @@ -91,15 +91,15 @@ GLOBAL_LIST_INIT(magicarp_spell_colours, list( ai_controller.set_blackboard_key(BB_MAGICARP_SPELL, spell) assign_spell_ai(spell_type) -/// If you have certain spells, use a different targetting datum +/// If you have certain spells, use a different targeting strategy /mob/living/basic/carp/magic/proc/assign_spell_ai(spell_type) - var/static/list/spell_special_targetting = list( + var/static/list/spell_special_targeting = list( /obj/projectile/magic/animate = MAGICARP_SPELL_OBJECTS, /obj/projectile/magic/door = MAGICARP_SPELL_WALLS, /obj/projectile/magic/resurrection = MAGICARP_SPELL_CORPSES, ) - ai_controller.set_blackboard_key(BB_MAGICARP_SPELL_SPECIAL_TARGETTING, spell_special_targetting[spell_type]) + ai_controller.set_blackboard_key(BB_MAGICARP_SPELL_SPECIAL_TARGETING, spell_special_targeting[spell_type]) /// Shoot when you click away from you /mob/living/basic/carp/magic/RangedAttack(atom/atom_target, modifiers) diff --git a/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm b/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm index c896ad3152f9..b764404bd052 100644 --- a/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm +++ b/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm @@ -65,7 +65,7 @@ /datum/ai_controller/basic_controller/cat_butcherer blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm index d20873f0ce94..29ea1dfc352e 100644 --- a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm +++ b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm @@ -1,6 +1,6 @@ /datum/ai_controller/basic_controller/eyeball blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/eyeball, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/eyeball, BB_EYE_DAMAGE_THRESHOLD = 10, ) @@ -17,16 +17,12 @@ /datum/ai_planning_subtree/heal_the_blind /datum/ai_planning_subtree/heal_the_blind/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/carbon/target = controller.blackboard[BB_BLIND_TARGET] + if(controller.blackboard_key_exists(BB_BLIND_TARGET)) + controller.queue_behavior(/datum/ai_behavior/heal_eye_damage, BB_BLIND_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_the_blind, BB_BLIND_TARGET, BB_EYE_DAMAGE_THRESHOLD) - if(QDELETED(target)) - controller.queue_behavior(/datum/ai_behavior/find_the_blind, BB_BLIND_TARGET, BB_EYE_DAMAGE_THRESHOLD) - return - - controller.queue_behavior(/datum/ai_behavior/heal_eye_damage, BB_BLIND_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/targetting_datum/basic/eyeball/can_attack(mob/living/owner, atom/target) +/datum/targeting_strategy/basic/eyeball/can_attack(mob/living/owner, atom/target, vision_range) . = ..() if(!.) return FALSE diff --git a/code/modules/mob/living/basic/space_fauna/faithless.dm b/code/modules/mob/living/basic/space_fauna/faithless.dm index b279856412c3..39f5652d1a0a 100644 --- a/code/modules/mob/living/basic/space_fauna/faithless.dm +++ b/code/modules/mob/living/basic/space_fauna/faithless.dm @@ -18,6 +18,7 @@ attack_verb_continuous = "grips" attack_verb_simple = "grip" attack_sound = 'sound/hallucinations/growl1.ogg' + melee_attack_cooldown = 1 SECONDS speak_emote = list("growls") unsuitable_atmos_damage = 0 @@ -29,15 +30,33 @@ ai_controller = /datum/ai_controller/basic_controller/faithless + /// What are the odds we paralyze a target on attack + var/paralyze_chance = 12 + /// How long do we paralyze a target for if we attack them + var/paralyze_duration = 2 SECONDS + /mob/living/basic/faithless/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) + AddElement(/datum/element/door_pryer) AddElement(/datum/element/footstep, FOOTSTEP_MOB_SHOE) - AddComponent(/datum/component/pry_open_door) + AddElement(/datum/element/mob_grabber, steal_from_others = FALSE) + +/mob/living/basic/faithless/melee_attack(atom/target, list/modifiers, ignore_cooldown) + . = ..() + if (!. || !isliving(target)) + return + + var/mob/living/living_target = target + if (prob(paralyze_chance)) + living_target.Paralyze(paralyze_duration) + living_target.visible_message(span_danger("\The [src] knocks \the [target] down!"), \ + span_userdanger("\The [src] knocks you down!")) /datum/ai_controller/basic_controller/faithless blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/faithless(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = UNCONSCIOUS, BB_LOW_PRIORITY_HUNTING_TARGET = null, // lights ) @@ -47,37 +66,7 @@ /datum/ai_planning_subtree/simple_find_target, /datum/ai_planning_subtree/attack_obstacle_in_path, /datum/ai_planning_subtree/attack_obstacle_in_path/low_priority_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/faithless, + /datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures, /datum/ai_planning_subtree/random_speech/faithless, ) - -/datum/targetting_datum/basic/faithless - stat_attack = UNCONSCIOUS - -/datum/ai_planning_subtree/basic_melee_attack_subtree/faithless - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/faithless - -/datum/ai_behavior/basic_melee_attack/faithless - action_cooldown = 1 SECONDS - /// What are the odds we paralyze a target - var/paralyze_chance = 12 - /// How long do we paralyze a target for if we attack them - var/paralyze_duration = 2 SECONDS - -/datum/ai_behavior/basic_melee_attack/faithless/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - var/mob/living/living_pawn = controller.pawn - - if(!isliving(target)) - return - var/mob/living/living_target = target - if(living_target.pulledby != living_pawn && !HAS_AI_CONTROLLER_TYPE(living_target.pulledby, /datum/ai_controller/basic_controller/faithless)) //Dont steal from my fellow faithless. - if(living_pawn.Adjacent(living_target) && isturf(living_target.loc) && living_target.stat == SOFT_CRIT) - living_target.grabbedby(living_pawn) //Drag their bodies around as a menace. - if(prob(paralyze_chance) && iscarbon(target)) - var/mob/living/carbon/carbon_target = target - carbon_target.Paralyze(paralyze_duration) - carbon_target.visible_message(span_danger("\The [living_pawn] knocks down \the [carbon_target]!"), \ - span_userdanger("\The [living_pawn] knocks you down!")) diff --git a/code/modules/mob/living/basic/space_fauna/garden_gnome.dm b/code/modules/mob/living/basic/space_fauna/garden_gnome.dm index 034bf0deb0fa..337a9f661f23 100644 --- a/code/modules/mob/living/basic/space_fauna/garden_gnome.dm +++ b/code/modules/mob/living/basic/space_fauna/garden_gnome.dm @@ -17,6 +17,7 @@ attack_verb_continuous = "punches" attack_verb_simple = "punch" attack_sound = 'sound/weapons/punch1.ogg' + melee_attack_cooldown = 1.2 SECONDS damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) speak_emote = list("announces") @@ -124,7 +125,7 @@ /datum/ai_controller/basic_controller/garden_gnome blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance @@ -132,12 +133,6 @@ planning_subtrees = list( /datum/ai_planning_subtree/target_retaliate, /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/garden_gnome, + /datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/random_speech/garden_gnome, ) - -/datum/ai_planning_subtree/basic_melee_attack_subtree/garden_gnome - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/garden_gnome - -/datum/ai_behavior/basic_melee_attack/garden_gnome - action_cooldown = 1.2 SECONDS diff --git a/code/modules/mob/living/basic/space_fauna/ghost.dm b/code/modules/mob/living/basic/space_fauna/ghost.dm index d53f5c9fb157..b7230710e573 100644 --- a/code/modules/mob/living/basic/space_fauna/ghost.dm +++ b/code/modules/mob/living/basic/space_fauna/ghost.dm @@ -91,7 +91,7 @@ /datum/ai_controller/basic_controller/ghost blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm index 4cdaba09759e..28cffa4ed8e3 100644 --- a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm @@ -64,6 +64,8 @@ /datum/ai_behavior/basic_ranged_attack/hivebot action_cooldown = 3 SECONDS + avoid_friendly_fire = TRUE /datum/ai_behavior/basic_ranged_attack/hivebot_rapid action_cooldown = 1.5 SECONDS + avoid_friendly_fire = TRUE diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm index c5173e3d7f99..067b0a03c13c 100644 --- a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm @@ -1,6 +1,6 @@ /datum/ai_controller/basic_controller/hivebot blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance @@ -32,7 +32,7 @@ /datum/ai_controller/basic_controller/hivebot/ranged/rapid planning_subtrees = list( /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/hivebot_rapid, /datum/ai_planning_subtree/attack_obstacle_in_path, /datum/ai_planning_subtree/hive_communicate, ) @@ -54,14 +54,10 @@ if(!SPT_PROB(relay_chance, seconds_per_tick)) return - var/mob/hive_target = controller.blackboard[BB_HIVE_PARTNER] - - if(QDELETED(hive_target)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/hive_partner, BB_HIVE_PARTNER, /mob/living/basic/hivebot) - return - - controller.queue_behavior(/datum/ai_behavior/relay_message, BB_HIVE_PARTNER) - return SUBTREE_RETURN_FINISH_PLANNING + if (controller.blackboard_key_exists(BB_HIVE_PARTNER)) + controller.queue_behavior(/datum/ai_behavior/relay_message, BB_HIVE_PARTNER) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_and_set/hive_partner, BB_HIVE_PARTNER, /mob/living/basic/hivebot) /datum/ai_planning_subtree/find_and_hunt_target/repair_machines target_key = BB_MACHINE_TARGET diff --git a/code/modules/mob/living/basic/space_fauna/killer_tomato.dm b/code/modules/mob/living/basic/space_fauna/killer_tomato.dm index a3fd64eb7986..c859289b56d7 100644 --- a/code/modules/mob/living/basic/space_fauna/killer_tomato.dm +++ b/code/modules/mob/living/basic/space_fauna/killer_tomato.dm @@ -42,7 +42,7 @@ /datum/ai_controller/basic_controller/killer_tomato blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/space_fauna/lightgeist.dm b/code/modules/mob/living/basic/space_fauna/lightgeist.dm index f75a9d90a507..7fa070ea8a76 100644 --- a/code/modules/mob/living/basic/space_fauna/lightgeist.dm +++ b/code/modules/mob/living/basic/space_fauna/lightgeist.dm @@ -21,6 +21,7 @@ health = 2 melee_damage_lower = 5 melee_damage_upper = 5 + melee_attack_cooldown = 5 SECONDS friendly_verb_continuous = "taps" friendly_verb_simple = "tap" density = FALSE @@ -65,10 +66,10 @@ complete_text = "%TARGET%'s wounds mend together.",\ ) -/mob/living/basic/lightgeist/melee_attack(atom/target, list/modifiers) - if (isliving(target)) +/mob/living/basic/lightgeist/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE) + . = ..() + if (. && isliving(target)) faction |= REF(target) // Anyone we heal will treat us as a friend - return ..() /mob/living/basic/lightgeist/ghost() . = ..() @@ -77,7 +78,7 @@ /datum/ai_controller/basic_controller/lightgeist blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/lightgeist, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/lightgeist, ) ai_traits = STOP_MOVING_WHEN_PULLED @@ -86,17 +87,17 @@ planning_subtrees = list( /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/lightgeist, // We heal things by attacking them + /datum/ai_planning_subtree/basic_melee_attack_subtree, // We heal things by attacking them ) /// Attack only mobs who have damage that we can heal, I think this is specific enough not to be a generic type -/datum/targetting_datum/lightgeist +/datum/targeting_strategy/lightgeist /// Types of mobs we can heal, not in a blackboard key because there is no point changing this at runtime because the component will already exist var/heal_biotypes = MOB_ORGANIC | MOB_MINERAL /// Type of limb we can heal var/required_bodytype = BODYTYPE_ORGANIC -/datum/targetting_datum/lightgeist/can_attack(mob/living/living_mob, mob/living/target) +/datum/targeting_strategy/lightgeist/can_attack(mob/living/living_mob, mob/living/target, vision_range) if (!isliving(target) || target.stat == DEAD) return FALSE if (!(heal_biotypes & target.mob_biotypes)) @@ -111,9 +112,3 @@ continue return TRUE return FALSE - -/datum/ai_planning_subtree/basic_melee_attack_subtree/lightgeist - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/lightgeist - -/datum/ai_behavior/basic_melee_attack/lightgeist - action_cooldown = 5 SECONDS diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm index 7684011bd667..09659956c581 100644 --- a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm @@ -1,7 +1,7 @@ /// A spellcasting AI which does not move /datum/ai_controller/basic_controller/meteor_heart blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGETLESS_TIME = 0, ) diff --git a/code/modules/mob/living/basic/space_fauna/morph.dm b/code/modules/mob/living/basic/space_fauna/morph.dm index 342a06fb5f11..0c6b3a942837 100644 --- a/code/modules/mob/living/basic/space_fauna/morph.dm +++ b/code/modules/mob/living/basic/space_fauna/morph.dm @@ -199,7 +199,7 @@ /// Only real human-powered intelligence is capable of playing prop hunt in SS13 (until further notice). /datum/ai_controller/basic_controller/morph blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/space_fauna/mushroom.dm b/code/modules/mob/living/basic/space_fauna/mushroom.dm index 2ddf8df16c56..ed6741bd082c 100644 --- a/code/modules/mob/living/basic/space_fauna/mushroom.dm +++ b/code/modules/mob/living/basic/space_fauna/mushroom.dm @@ -57,31 +57,24 @@ /datum/ai_controller/basic_controller/mushroom blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/mushroom, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mushroom, + BB_TARGET_MINIMUM_STAT = DEAD, ) ai_movement = /datum/ai_movement/basic_avoidance idle_behavior = /datum/idle_behavior/idle_random_walk planning_subtrees = list( /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/mushroom, + /datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/find_and_hunt_target/mushroom_food, ) -/datum/targetting_datum/basic/mushroom - stat_attack = DEAD +/datum/targeting_strategy/basic/mushroom ///we only attacked another mushrooms -/datum/targetting_datum/basic/mushroom/faction_check(mob/living/living_mob, mob/living/the_target) - return !living_mob.faction_check_mob(the_target, exact_match = check_factions_exactly) - - -/datum/ai_planning_subtree/basic_melee_attack_subtree/mushroom - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/mushroom - -/datum/ai_behavior/basic_melee_attack/mushroom - action_cooldown = 2 SECONDS +/datum/targeting_strategy/basic/mushroom/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) + return !living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly) /datum/ai_planning_subtree/find_and_hunt_target/mushroom_food target_key = BB_LOW_PRIORITY_HUNTING_TARGET diff --git a/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm b/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm index 35d597e53ca9..d49932fb7046 100644 --- a/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm +++ b/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm @@ -14,6 +14,7 @@ attack_verb_simple = "punch" attack_sound = 'sound/weapons/bladeslice.ogg' attack_vis_effect = ATTACK_EFFECT_SLASH + melee_attack_cooldown = 1 SECONDS faction = list(FACTION_NETHER) speak_emote = list("screams") death_message = "falls apart into a fine dust." @@ -25,22 +26,9 @@ lighting_cutoff_green = 15 lighting_cutoff_blue = 40 - ai_controller = /datum/ai_controller/basic_controller/blankbody + ai_controller = /datum/ai_controller/basic_controller/simple_hostile_obstacles /mob/living/basic/blankbody/Initialize(mapload) . = ..() AddElement(/datum/element/swabable, CELL_LINE_TABLE_NETHER, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 0) AddComponent(/datum/component/health_scaling_effects, min_health_attack_modifier_lower = 8, min_health_attack_modifier_upper = 14) - -/datum/ai_controller/basic_controller/blankbody - blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), - ) - - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/average_speed, - ) diff --git a/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm b/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm index 5fabbf2afb2e..cdde6ad05e4c 100644 --- a/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm +++ b/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm @@ -15,6 +15,7 @@ gold_core_spawnable = HOSTILE_SPAWN attack_sound = 'sound/weapons/bite.ogg' attack_vis_effect = ATTACK_EFFECT_BITE + melee_attack_cooldown = 1 SECONDS faction = list(FACTION_NETHER) speak_emote = list("screams") death_message = "gets his head split open." @@ -26,7 +27,7 @@ lighting_cutoff_green = 25 lighting_cutoff_blue = 15 - ai_controller = /datum/ai_controller/basic_controller/creature + ai_controller = /datum/ai_controller/basic_controller/simple_hostile_obstacles /mob/living/basic/creature/Initialize(mapload) . = ..() @@ -100,16 +101,3 @@ exit_jaunt(cast_on) return enter_jaunt(cast_on) - -/datum/ai_controller/basic_controller/creature - blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), - ) - - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/average_speed, - ) diff --git a/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm b/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm index 57d90da264ab..3f445ea1261b 100644 --- a/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm +++ b/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm @@ -12,6 +12,7 @@ speed = 1 attack_verb_continuous = "lacerates" attack_verb_simple = "lacerate" + melee_attack_cooldown = 1 SECONDS gold_core_spawnable = HOSTILE_SPAWN attack_sound = 'sound/weapons/bladeslice.ogg' attack_vis_effect = ATTACK_EFFECT_SLASH @@ -27,7 +28,7 @@ lighting_cutoff_green = 15 lighting_cutoff_blue = 50 - ai_controller = /datum/ai_controller/basic_controller/migo + ai_controller = /datum/ai_controller/basic_controller/simple_hostile_obstacles var/static/list/migo_sounds /// Odds migo will dodge var/dodge_prob = 10 @@ -70,16 +71,3 @@ . = Move(get_step(loc,pick(cdir, ccdir))) if(!.)//Can't dodge there so we just carry on . = Move(moving_to, move_direction) - -/datum/ai_controller/basic_controller/migo - blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), - ) - - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/average_speed, - ) diff --git a/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.dm b/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.dm index b99692bea9e7..972293103127 100644 --- a/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.dm +++ b/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.dm @@ -53,7 +53,7 @@ /datum/ai_controller/basic_controller/paper_wizard blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_WRITING_LIST = list( "I can turn the paper into gold and ink into diamonds!", "Your fate is written and sealed!", @@ -110,8 +110,7 @@ faction = list(FACTION_STICKMAN) melee_damage_lower = 1 melee_damage_upper = 5 - - ai_controller = /datum/ai_controller/basic_controller/wizard_copy + ai_controller = /datum/ai_controller/basic_controller/simple_hostile /mob/living/basic/paper_wizard/copy/Initialize(mapload) . = ..() @@ -141,18 +140,6 @@ new /obj/effect/temp_visual/small_smoke/halfsecond(get_turf(src)) qdel(src) //I see through your ruse! -/datum/ai_controller/basic_controller/wizard_copy - blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, - ) - - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - //fancy effects /obj/effect/temp_visual/paper_scatter name = "scattering paper" diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm index 98ddfdf9ca35..36d22d2637af 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm @@ -53,6 +53,7 @@ AddElement(/datum/element/waddling) AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/door_pryer, pry_time = 5 SECONDS, interaction_key = REGALRAT_INTERACTION) AddComponent(\ /datum/component/ghost_direct_control,\ poll_candidates = poll_ghosts,\ @@ -80,7 +81,7 @@ return if(ismouse(user)) - if(user.faction_check_mob(src, exact_match = TRUE)) + if(user.faction_check_atom(src, exact_match = TRUE)) . += span_notice("This is your king. Long live [p_their()] majesty!") else . += span_warning("This is a false king! Strike [p_them()] down!") @@ -173,10 +174,6 @@ if(isnull(mind)) return - if(istype(target, /obj/machinery/door/airlock)) - INVOKE_ASYNC(src, PROC_REF(pry_door), target) - return COMPONENT_HOSTILE_NO_ATTACK - if(!(istate &ISTATE_HARM)) INVOKE_ASYNC(src, PROC_REF(poison_target), target) return COMPONENT_HOSTILE_NO_ATTACK @@ -194,7 +191,7 @@ balloon_alert(src, "already dead!") return FALSE - if(living_target.faction_check_mob(src, exact_match = TRUE)) + if(living_target.faction_check_atom(src, exact_match = TRUE)) balloon_alert(src, "one of your soldiers!") return FALSE @@ -234,43 +231,7 @@ heal_bodypart_damage(amount) qdel(target) -/** - * Allows rat king to pry open an airlock if it isn't locked. - * - * A proc used for letting the rat king pry open airlocks instead of just attacking them. - * This allows the rat king to traverse the station when there is a lack of vents or - * accessible doors, something which is common in certain rat king spawn points. - * - * Returns TRUE if the door opens, FALSE otherwise. - */ -/mob/living/basic/regal_rat/proc/pry_door(target) - if(DOING_INTERACTION(src, REGALRAT_INTERACTION)) - return FALSE - - var/obj/machinery/door/airlock/prying_door = target - if(!prying_door.density || prying_door.locked || prying_door.welded || prying_door.seal) - return FALSE - - visible_message( - span_warning("[src] begins prying open the airlock..."), - span_notice("You begin digging your claws into the airlock..."), - span_warning("You hear groaning metal..."), - ) - var/time_to_open = 0.5 SECONDS - - if(prying_door.hasPower()) - time_to_open = 5 SECONDS - playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, vary = TRUE) - - if(!do_after(src, time_to_open, prying_door, interaction_key = REGALRAT_INTERACTION)) - return FALSE - - if(!prying_door.open(BYPASS_DOOR_CHECKS)) - balloon_alert(src, "failed to open!") - return FALSE - - return TRUE - +/// Regal rat subtype which can be possessed by ghosts /mob/living/basic/regal_rat/controlled poll_ghosts = TRUE diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm index 14f6f7fde3b2..0f90a8b2ed02 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm @@ -53,15 +53,17 @@ var/static/list/mouse_commands = list( /datum/pet_command/idle, /datum/pet_command/free, + /datum/pet_command/protect_owner, /datum/pet_command/follow, - /datum/pet_command/point_targetting/attack/mouse + /datum/pet_command/point_targeting/attack/mouse ) /// Commands you can give to glockroaches var/static/list/glockroach_commands = list( /datum/pet_command/idle, /datum/pet_command/free, + /datum/pet_command/protect_owner/glockroach, /datum/pet_command/follow, - /datum/pet_command/point_targetting/attack/glockroach + /datum/pet_command/point_targeting/attack/glockroach ) /datum/action/cooldown/mob_cooldown/riot/Activate(atom/target) @@ -158,6 +160,7 @@ nearby_roach.melee_damage_upper += 4 nearby_roach.obj_damage += 5 nearby_roach.ai_controller = new /datum/ai_controller/basic_controller/cockroach/sewer(nearby_roach) + nearby_roach.melee_attack_cooldown = 0.8 SECONDS nearby_roach.icon_state += "_sewer" nearby_roach.maxHealth += 1 @@ -189,7 +192,7 @@ return TRUE // Command you can give to a mouse to make it kill someone -/datum/pet_command/point_targetting/attack/mouse +/datum/pet_command/point_targeting/attack/mouse speech_commands = list("attack", "sic", "kill", "cheese em") command_feedback = "squeak!" // Frogs and roaches can squeak too it's fine pointed_reaction = "and squeaks aggressively" @@ -197,7 +200,7 @@ attack_behaviour = /datum/ai_behavior/basic_melee_attack // Command you can give to a mouse to make it kill someone -/datum/pet_command/point_targetting/attack/glockroach +/datum/pet_command/point_targeting/attack/glockroach speech_commands = list("attack", "sic", "kill", "cheese em") command_feedback = "squeak!" pointed_reaction = "and cocks its gun" @@ -242,3 +245,6 @@ else if(prob(5)) C.vomit() return ..() + +/datum/pet_command/protect_owner/glockroach + protect_behavior = /datum/ai_behavior/basic_ranged_attack/glockroach diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm index 2a1db00b0e46..073029bef3c2 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm @@ -1,8 +1,7 @@ /datum/ai_controller/basic_controller/regal_rat blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, - BB_BASIC_MOB_FLEEING = FALSE, - BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance @@ -13,7 +12,7 @@ planning_subtrees = list( /datum/ai_planning_subtree/target_retaliate/to_flee, /datum/ai_planning_subtree/targeted_mob_ability/riot, - /datum/ai_planning_subtree/flee_target, + /datum/ai_planning_subtree/flee_target/from_flee_key, /datum/ai_planning_subtree/attack_obstacle_in_path, /datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/use_mob_ability/domain, @@ -22,6 +21,7 @@ /datum/ai_planning_subtree/targeted_mob_ability/riot target_key = BB_BASIC_MOB_FLEE_TARGET // we only want to trigger this when provoked, manpower is low nowadays ability_key = BB_RAISE_HORDE_ABILITY + finish_planning = FALSE /datum/ai_planning_subtree/use_mob_ability/domain ability_key = BB_DOMAIN_ABILITY diff --git a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm new file mode 100644 index 000000000000..b3c6935c92ef --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm @@ -0,0 +1,444 @@ +/// Source for a trait we get when we're stunned +#define REVENANT_STUNNED_TRAIT "revenant_got_stunned" + +/// Revenants: "Ghosts" that are invisible and move like ghosts, cannot take damage while invisible +/// Can hear deadchat, but are NOT normal ghosts and do NOT have x-ray vision +/// Admin-spawn or random event +/mob/living/basic/revenant + name = "revenant" + desc = "A malevolent spirit." + icon = 'icons/mob/simple/mob.dmi' + icon_state = "revenant_idle" + mob_biotypes = MOB_SPIRIT + incorporeal_move = INCORPOREAL_MOVE_JAUNT + invisibility = INVISIBILITY_REVENANT + health = INFINITY //Revenants don't use health, they use essence instead + maxHealth = INFINITY + plane = GHOST_PLANE + sight = SEE_SELF + throwforce = 0 + + // Going for faint purple spoopy ghost + lighting_cutoff_red = 20 + lighting_cutoff_green = 15 + lighting_cutoff_blue = 35 + + friendly_verb_continuous = "touches" + friendly_verb_simple = "touch" + response_help_continuous = "passes through" + response_help_simple = "pass through" + response_disarm_continuous = "swings through" + response_disarm_simple = "swing through" + response_harm_continuous = "punches through" + response_harm_simple = "punch through" + unsuitable_atmos_damage = 0 + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) //I don't know how you'd apply those, but revenants no-sell them anyway. + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = 0 + maximum_survivable_temperature = INFINITY + + status_flags = NONE + density = FALSE + move_resist = MOVE_FORCE_OVERPOWERING + mob_size = MOB_SIZE_TINY + pass_flags = PASSTABLE | PASSGRILLE | PASSMOB + speed = 1 + unique_name = TRUE + hud_possible = list(ANTAG_HUD) + hud_type = /datum/hud/revenant + + /// The icon we use while just floating around. + var/icon_idle = "revenant_idle" + /// The icon we use while in a revealed state. + var/icon_reveal = "revenant_revealed" + /// The icon we use when stunned (temporarily frozen) + var/icon_stun = "revenant_stun" + /// The icon we use while draining someone. + var/icon_drain = "revenant_draining" + + /// Are we currently dormant (ectoplasm'd)? + var/dormant = FALSE + /// Are we currently draining someone? + var/draining = FALSE + /// Have we already given this revenant abilities? + var/generated_objectives_and_spells = FALSE + + /// Lazylist of drained mobs to ensure that we don't steal a soul from someone twice + var/list/drained_mobs = null + /// List of action ability datums to grant on Initialize. Keep in mind that anything with the `/aoe/revenant` subtype starts locked by default. + var/static/list/datum/action/abilities = list( + /datum/action/cooldown/spell/aoe/revenant/blight, + /datum/action/cooldown/spell/aoe/revenant/defile, + /datum/action/cooldown/spell/aoe/revenant/haunt_object, + /datum/action/cooldown/spell/aoe/revenant/malfunction, + /datum/action/cooldown/spell/aoe/revenant/overload, + /datum/action/cooldown/spell/list_target/telepathy/revenant, + ) + + /// The resource, and health, of revenants. + var/essence = 75 + /// The regeneration cap of essence (go figure); regenerates every Life() tick up to this amount. + var/max_essence = 75 + /// If the revenant regenerates essence or not + var/essence_regenerating = TRUE + /// How much essence regenerates per second + var/essence_regen_amount = 2.5 + /// How much essence the revenant has stolen + var/essence_accumulated = 0 + /// How much stolen essence is available for unlocks + var/essence_excess = 0 + /// How long the revenant is revealed for, is about 2 seconds times this var. + var/unreveal_time = 0 + /// How many perfect, regen-cap increasing souls the revenant has. //TODO, add objective for getting a perfect soul(s?) + var/perfectsouls = 0 + +/mob/living/basic/revenant/Initialize(mapload) + . = ..() + AddElement(/datum/element/simple_flying) + add_traits(list(TRAIT_SPACEWALK, TRAIT_SIXTHSENSE, TRAIT_FREE_HYPERSPACE_MOVEMENT), INNATE_TRAIT) + + for(var/ability in abilities) + var/datum/action/spell = new ability(src) + spell.Grant(src) + + RegisterSignal(src, COMSIG_LIVING_BANED, PROC_REF(on_baned)) + RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_move)) + RegisterSignal(src, COMSIG_LIVING_LIFE, PROC_REF(on_life)) + set_random_revenant_name() + + GLOB.revenant_relay_mobs |= src + +/mob/living/basic/revenant/Destroy() + GLOB.revenant_relay_mobs -= src + return ..() + +/mob/living/basic/revenant/Login() + . = ..() + if(!. || isnull(client)) + return FALSE + + var/static/cached_string = null + if(isnull(cached_string)) + cached_string = examine_block(jointext(create_login_string(), "\n")) + + to_chat(src, cached_string, type = MESSAGE_TYPE_INFO) + + if(generated_objectives_and_spells) + return TRUE + + generated_objectives_and_spells = TRUE + mind.set_assigned_role(SSjob.GetJobType(/datum/job/revenant)) + mind.special_role = ROLE_REVENANT + SEND_SOUND(src, sound('sound/effects/ghost.ogg')) + mind.add_antag_datum(/datum/antagonist/revenant) + return TRUE + +/// Signal Handler Injection to handle Life() stuff for revenants +/mob/living/basic/revenant/proc/on_life(seconds_per_tick = SSMOBS_DT, times_fired) + SIGNAL_HANDLER + + if(dormant) + return COMPONENT_LIVING_CANCEL_LIFE_PROCESSING + + if(HAS_TRAIT(src, TRAIT_REVENANT_REVEALED) && essence <= 0) + death() + return COMPONENT_LIVING_CANCEL_LIFE_PROCESSING + + if(essence_regenerating && !HAS_TRAIT(src, TRAIT_REVENANT_INHIBITED) && essence < max_essence) //While inhibited, essence will not regenerate + var/change_in_time = DELTA_WORLD_TIME(SSmobs) + essence = min(essence + (essence_regen_amount * change_in_time), max_essence) + update_mob_action_buttons() //because we update something required by our spells in life, we need to update our buttons + + update_appearance(UPDATE_ICON) + update_health_hud() + +/mob/living/basic/revenant/get_status_tab_items() + . = ..() + . += "Current Essence: [essence >= max_essence ? essence : "[essence] / [max_essence]"] E" + . += "Total Essence Stolen: [essence_accumulated] SE" + . += "Unused Stolen Essence: [essence_excess] SE" + . += "Perfect Souls Stolen: [perfectsouls]" + +/mob/living/basic/revenant/update_health_hud() + if(isnull(hud_used)) + return + + var/essencecolor = "#8F48C6" + if(essence > max_essence) + essencecolor = "#9A5ACB" //oh boy you've got a lot of essence + else if(essence <= 0) + essencecolor = "#1D2953" //oh jeez you're dying + hud_used.healths.maptext = MAPTEXT("
[essence]E
") + +/mob/living/basic/revenant/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) + if(!message) + return + + if(client) + if(client.prefs.muted & MUTE_IC) + to_chat(src, span_boldwarning("You cannot send IC messages (muted).")) + return + if (!(ignore_spam || forced) && client.handle_spam_prevention(message, MUTE_IC)) + return + + if(sanitize) + message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)) + + log_talk(message, LOG_SAY) + var/rendered = span_deadsay("UNDEAD: [src] says, \"[message]\"") + relay_to_list_and_observers(rendered, GLOB.revenant_relay_mobs, src) + +/mob/living/basic/revenant/ClickOn(atom/A, params) //revenants can't interact with the world directly, so we gotta do some wacky override stuff + var/list/modifiers = params2list(params) + if(LAZYACCESS(modifiers, SHIFT_CLICK)) + ShiftClickOn(A) + return + if(LAZYACCESS(modifiers, ALT_CLICK)) + AltClickNoInteract(src, A) + return + if(LAZYACCESS(modifiers, RIGHT_CLICK)) + ranged_secondary_attack(A, modifiers) + return + + if(ishuman(A) && in_range(src, A)) + attempt_harvest(A) + +/mob/living/basic/revenant/ranged_secondary_attack(atom/target, modifiers) + if(HAS_TRAIT(src, TRAIT_REVENANT_INHIBITED) || HAS_TRAIT(src, TRAIT_REVENANT_REVEALED) || HAS_TRAIT(src, TRAIT_NO_TRANSFORM) || !Adjacent(target) || !incorporeal_move_check(target)) + return + + var/list/icon_dimensions = get_icon_dimensions(target.icon) + var/orbitsize = (icon_dimensions["width"] + icon_dimensions["height"]) * 0.5 + orbitsize -= (orbitsize / world.icon_size) * (world.icon_size * 0.25) + orbit(target, orbitsize) + +/mob/living/basic/revenant/adjust_health(amount, updating_health = TRUE, forced = FALSE) + if(!forced && !HAS_TRAIT(src, TRAIT_REVENANT_REVEALED)) + return 0 + + . = amount + + essence = max(0, essence - amount) + if(updating_health) + update_health_hud() + if(essence == 0) + death() + + return . + +/mob/living/basic/revenant/orbit(atom/target) + setDir(SOUTH) // reset dir so the right directional sprites show up + return ..() + +/mob/living/basic/revenant/update_icon_state() + . = ..() + + if(HAS_TRAIT(src, TRAIT_REVENANT_REVEALED)) + icon_state = icon_reveal + return + + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) + if(draining) + icon_state = icon_drain + else + icon_state = icon_stun + + return + + icon_state = icon_idle + +/mob/living/basic/revenant/med_hud_set_health() + return //we use no hud + +/mob/living/basic/revenant/med_hud_set_status() + return //we use no hud + +/mob/living/basic/revenant/dust(just_ash, drop_items, force) + death() + +/mob/living/basic/revenant/gib() + death() + +/mob/living/basic/revenant/can_perform_action(atom/movable/target, action_bitflags) + return FALSE + +/mob/living/basic/revenant/ex_act(severity, target) + return FALSE //Immune to the effects of explosions. + +/mob/living/basic/revenant/blob_act(obj/structure/blob/attacking_blob) + return //blah blah blobs aren't in tune with the spirit world, or something. + +/mob/living/basic/revenant/singularity_act() + return //don't walk into the singularity expecting to find corpses, okay? + +/mob/living/basic/revenant/narsie_act() + return //most humans will now be either bones or harvesters, but we're still un-alive. + +/mob/living/basic/revenant/bullet_act() + if(!HAS_TRAIT(src, TRAIT_REVENANT_REVEALED) || dormant) + return BULLET_ACT_FORCE_PIERCE + return ..() + +/mob/living/basic/revenant/death() + if(!HAS_TRAIT(src, TRAIT_REVENANT_REVEALED) || dormant) //Revenants cannot die if they aren't revealed //or are already dead + return + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, REVENANT_STUNNED_TRAIT) + dormant = TRUE + + visible_message( + span_warning("[src] lets out a waning screech as violet mist swirls around its dissolving body!"), + span_revendanger("NO! No... it's too late, you can feel your essence [pick("breaking apart", "drifting away")]..."), + ) + + invisibility = 0 + icon_state = "revenant_draining" + playsound(src, 'sound/effects/screech.ogg', 100, TRUE) + + animate(src, alpha = 0, time = 3 SECONDS) + addtimer(CALLBACK(src, PROC_REF(move_to_ectoplasm)), 3 SECONDS) + +/// Forces the mob, once dormant, to move inside ectoplasm until it can regenerate. +/mob/living/basic/revenant/proc/move_to_ectoplasm() + if(QDELETED(src) || !dormant) // something fucky happened, abort. we MUST be dormant to go inside the ectoplasm. + return + + visible_message(span_danger("[src]'s body breaks apart into a fine pile of blue dust.")) + + var/obj/item/ectoplasm/revenant/goop = new(get_turf(src)) // the ectoplasm will handle moving us out of dormancy + goop.old_ckey = client.ckey + goop.revenant = src + forceMove(goop) + +/mob/living/basic/revenant/proc/on_move(datum/source, atom/entering_loc) + SIGNAL_HANDLER + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) // just in case it occurs, need to provide some feedback + balloon_alert(src, "can't move!") + return + + if(isnull(orbiting) || incorporeal_move_check(entering_loc)) + return + + // we're about to go somewhere we aren't meant to, end the orbit and block the move. feedback will be given in `incorporeal_move_check()` + orbiting.end_orbit(src) + return COMPONENT_MOVABLE_BLOCK_PRE_MOVE + +/// Generates the information the player needs to know how to play their role, and returns it as a list. +/mob/living/basic/revenant/proc/create_login_string() + RETURN_TYPE(/list) + var/list/returnable_list = list() + returnable_list += span_deadsay(span_boldbig("You are a revenant.")) + returnable_list += span_bold("Your formerly mundane spirit has been infused with alien energies and empowered into a revenant.") + returnable_list += span_bold("You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds.") + returnable_list += span_bold("You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable.") + returnable_list += span_bold("To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities.") + returnable_list += span_bold("You do not remember anything of your past lives, nor will you remember anything about this one after your death.") + returnable_list += span_bold("Be sure to read the wiki page to learn more.") + return returnable_list + +/mob/living/basic/revenant/proc/set_random_revenant_name() + var/list/built_name_strings = list() + built_name_strings += pick(strings(REVENANT_NAME_FILE, "spirit_type")) + built_name_strings += " of " + built_name_strings += pick(strings(REVENANT_NAME_FILE, "adverb")) + built_name_strings += pick(strings(REVENANT_NAME_FILE, "theme")) + name = built_name_strings.Join("") + +/mob/living/basic/revenant/proc/on_baned(obj/item/weapon, mob/living/user) + SIGNAL_HANDLER + visible_message( + span_warning("[src] violently flinches!"), + span_revendanger("As [weapon] passes through you, you feel your essence draining away!"), + ) + apply_status_effect(/datum/status_effect/revenant/inhibited, 3 SECONDS) + +/// Incorporeal move check: blocked by holy-watered tiles and salt piles. +/mob/living/basic/revenant/proc/incorporeal_move_check(atom/destination) + var/turf/open/floor/step_turf = get_turf(destination) + if(isnull(step_turf)) + return TRUE // what? whatever let it happen + + if(step_turf.turf_flags & NOJAUNT) + to_chat(src, span_warning("Some strange aura is blocking the way.")) + return FALSE + + if(locate(/obj/effect/decal/cleanable/food/salt) in step_turf) + balloon_alert(src, "blocked by salt!") + apply_status_effect(/datum/status_effect/revenant/revealed, 2 SECONDS) + apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, 2 SECONDS) + return FALSE + + if(locate(/obj/effect/blessing) in step_turf) + to_chat(src, span_warning("Holy energies block your path!")) + return FALSE + + return TRUE + +/mob/living/basic/revenant/proc/cast_check(essence_cost) + if(QDELETED(src)) + return + + var/turf/current = get_turf(src) + + if(isclosedturf(current)) + to_chat(src, span_revenwarning("You cannot use abilities from inside of a wall.")) + return FALSE + + for(var/obj/thing in current) + if(!thing.density || thing.CanPass(src, get_dir(current, src))) + continue + to_chat(src, span_revenwarning("You cannot use abilities inside of a dense object.")) + return FALSE + + if(HAS_TRAIT(src, TRAIT_REVENANT_INHIBITED)) + to_chat(src, span_revenwarning("Your powers have been suppressed by a nullifying energy!")) + return FALSE + + if(!change_essence_amount(essence_cost, TRUE)) + to_chat(src, span_revenwarning("You lack the essence to use that ability.")) + return FALSE + + return TRUE + +/mob/living/basic/revenant/proc/unlock(essence_cost) + if(essence_excess < essence_cost) + return FALSE + essence_excess -= essence_cost + update_mob_action_buttons() + return TRUE + +/mob/living/basic/revenant/proc/death_reset() + REMOVE_TRAIT(src, TRAIT_NO_TRANSFORM, REVENANT_STUNNED_TRAIT) + forceMove(get_turf(src)) + // clean slate, so no more debilitating effects + remove_status_effect(/datum/status_effect/revenant/revealed) + remove_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant) + remove_status_effect(/datum/status_effect/revenant/inhibited) + draining = FALSE + dormant = FALSE + incorporeal_move = INCORPOREAL_MOVE_JAUNT + invisibility = INVISIBILITY_REVENANT + alpha = 255 + +/mob/living/basic/revenant/proc/change_essence_amount(essence_to_change_by, silent = FALSE, source = null) + if(QDELETED(src)) + return FALSE + + if((essence + essence_to_change_by) < 0) + return FALSE + + essence = max(0, essence + essence_to_change_by) + update_health_hud() + + if(essence_to_change_by > 0) + essence_accumulated = max(0, essence_accumulated + essence_to_change_by) + essence_excess = max(0, essence_excess + essence_to_change_by) + + update_mob_action_buttons() + if(!silent) + if(essence_to_change_by > 0) + to_chat(src, span_revennotice("Gained [essence_to_change_by]E [source ? "from [source]":""].")) + else + to_chat(src, span_revenminor("Lost [essence_to_change_by]E [source ? "from [source]":""].")) + return TRUE + +#undef REVENANT_STUNNED_TRAIT diff --git a/code/modules/antagonists/revenant/revenant_abilities.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm similarity index 63% rename from code/modules/antagonists/revenant/revenant_abilities.dm rename to code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm index 51979f2ec1db..93e61e6cb826 100644 --- a/code/modules/antagonists/revenant/revenant_abilities.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm @@ -1,122 +1,6 @@ #define REVENANT_DEFILE_MIN_DAMAGE 30 #define REVENANT_DEFILE_MAX_DAMAGE 50 - -/mob/living/simple_animal/revenant/ClickOn(atom/A, params) //revenants can't interact with the world directly. - var/list/modifiers = params2list(params) - if(LAZYACCESS(modifiers, SHIFT_CLICK)) - ShiftClickOn(A) - return - if(LAZYACCESS(modifiers, ALT_CLICK)) - AltClickNoInteract(src, A) - return - if((istate & ISTATE_SECONDARY)) - ranged_secondary_attack(A, modifiers) - return - - if(ishuman(A)) - //Humans are tagged, so this is fine - if(REF(A) in drained_mobs) - to_chat(src, span_revenwarning("[A]'s soul is dead and empty.") ) - else if(in_range(src, A)) - Harvest(A) - -/mob/living/simple_animal/revenant/ranged_secondary_attack(atom/target, modifiers) - if(revealed || notransform || inhibited || !Adjacent(target) || !incorporeal_move_check(target)) - return - var/icon/I = icon(target.icon,target.icon_state,target.dir) - var/orbitsize = (I.Width()+I.Height())*0.5 - orbitsize -= (orbitsize/world.icon_size)*(world.icon_size*0.25) - orbit(target, orbitsize) - -//Harvest; activated by clicking the target, will try to drain their essence. -/mob/living/simple_animal/revenant/proc/Harvest(mob/living/carbon/human/target) - if(!castcheck(0)) - return - if(draining) - to_chat(src, span_revenwarning("You are already siphoning the essence of a soul!")) - return - if(!target.stat) - to_chat(src, span_revennotice("[target.p_their(TRUE)] soul is too strong to harvest.")) - if(prob(10)) - to_chat(target, span_revennotice("You feel as if you are being watched.")) - return - log_combat(src, target, "started to harvest") - face_atom(target) - draining = TRUE - essence_drained += rand(15, 20) - to_chat(src, span_revennotice("You search for the soul of [target].")) - if(do_after(src, rand(10, 20), target, timed_action_flags = IGNORE_HELD_ITEM)) //did they get deleted in that second? - if(target.ckey) - to_chat(src, span_revennotice("[target.p_their(TRUE)] soul burns with intelligence.")) - essence_drained += rand(20, 30) - if(target.stat != DEAD && !HAS_TRAIT(target, TRAIT_WEAK_SOUL)) - to_chat(src, span_revennotice("[target.p_their(TRUE)] soul blazes with life!")) - essence_drained += rand(40, 50) - if(HAS_TRAIT(target, TRAIT_WEAK_SOUL) && !target.ckey) - to_chat(src, span_revennotice("[target.p_their(TRUE)] soul is weak and underdeveloped. They won't be worth very much.")) - essence_drained = 5 - else - to_chat(src, span_revennotice("[target.p_their(TRUE)] soul is weak and faltering.")) - if(do_after(src, rand(15, 20), target, timed_action_flags = IGNORE_HELD_ITEM)) //did they get deleted NOW? - switch(essence_drained) - if(1 to 30) - to_chat(src, span_revennotice("[target] will not yield much essence. Still, every bit counts.")) - if(30 to 70) - to_chat(src, span_revennotice("[target] will yield an average amount of essence.")) - if(70 to 90) - to_chat(src, span_revenboldnotice("Such a feast! [target] will yield much essence to you.")) - if(90 to INFINITY) - to_chat(src, span_revenbignotice("Ah, the perfect soul. [target] will yield massive amounts of essence to you.")) - if(do_after(src, rand(15, 25), target, timed_action_flags = IGNORE_HELD_ITEM)) //how about now - if(!target.stat) - to_chat(src, span_revenwarning("[target.p_theyre(TRUE)] now powerful enough to fight off your draining.")) - to_chat(target, span_boldannounce("You feel something tugging across your body before subsiding.")) - draining = 0 - essence_drained = 0 - return //hey, wait a minute... - to_chat(src, span_revenminor("You begin siphoning essence from [target]'s soul.")) - if(target.stat != DEAD) - to_chat(target, span_warning("You feel a horribly unpleasant draining sensation as your grip on life weakens...")) - if(target.stat == SOFT_CRIT) - target.Stun(46) - reveal(46) - stun(46) - target.visible_message(span_warning("[target] suddenly rises slightly into the air, [target.p_their()] skin turning an ashy gray.")) - if(target.can_block_magic(MAGIC_RESISTANCE_HOLY)) - to_chat(src, span_revenminor("Something's wrong! [target] seems to be resisting the siphoning, leaving you vulnerable!")) - target.visible_message(span_warning("[target] slumps onto the ground."), \ - span_revenwarning("Violet lights, dancing in your vision, receding--")) - draining = FALSE - return - var/datum/beam/B = Beam(target,icon_state="drain_life") - if(do_after(src, 46, target, timed_action_flags = IGNORE_HELD_ITEM)) //As one cannot prove the existance of ghosts, ghosts cannot prove the existance of the target they were draining. - change_essence_amount(essence_drained, FALSE, target) - if(essence_drained <= 90 && target.stat != DEAD && !HAS_TRAIT(target, TRAIT_WEAK_SOUL)) - essence_regen_cap += 5 - to_chat(src, span_revenboldnotice("The absorption of [target]'s living soul has increased your maximum essence level. Your new maximum essence is [essence_regen_cap].")) - if(essence_drained > 90) - essence_regen_cap += 15 - perfectsouls++ - to_chat(src, span_revenboldnotice("The perfection of [target]'s soul has increased your maximum essence level. Your new maximum essence is [essence_regen_cap].")) - to_chat(src, span_revennotice("[target]'s soul has been considerably weakened and will yield no more essence for the time being.")) - target.visible_message(span_warning("[target] slumps onto the ground."), \ - span_revenwarning("Violets lights, dancing in your vision, getting clo--")) - drained_mobs += REF(target) - if(target.stat != DEAD) - target.investigate_log("has died from revenant harvest.", INVESTIGATE_DEATHS) - target.death(FALSE) - else - to_chat(src, span_revenwarning("[target ? "[target] has":"[target.p_theyve(TRUE)]"] been drawn out of your grasp. The link has been broken.")) - if(target) //Wait, target is WHERE NOW? - target.visible_message(span_warning("[target] slumps onto the ground."), \ - span_revenwarning("Violets lights, dancing in your vision, receding--")) - qdel(B) - else - to_chat(src, span_revenwarning("You are not close enough to siphon [target ? "[target]'s":"[target.p_their()]"] soul. The link has been broken.")) - draining = FALSE - essence_drained = 0 - //Transmit: the revemant's only direct way to communicate. Sends a single message silently to a single mob /datum/action/cooldown/spell/list_target/telepathy/revenant name = "Revenant Transmit" @@ -170,8 +54,8 @@ stack_trace("[type] was owned by a non-revenant mob, please don't.") return FALSE - var/mob/living/simple_animal/revenant/ghost = owner - if(ghost.inhibited) + var/mob/living/basic/revenant/ghost = owner + if(ghost.dormant || HAS_TRAIT(ghost, TRAIT_REVENANT_INHIBITED)) return FALSE if(locked && ghost.essence_excess <= unlock_amount) return FALSE @@ -187,7 +71,7 @@ return things -/datum/action/cooldown/spell/aoe/revenant/before_cast(mob/living/simple_animal/revenant/cast_on) +/datum/action/cooldown/spell/aoe/revenant/before_cast(mob/living/basic/revenant/cast_on) . = ..() if(. & SPELL_CANCEL_CAST) return @@ -205,16 +89,16 @@ reset_spell_cooldown() return . | SPELL_CANCEL_CAST - if(!cast_on.castcheck(-cast_amount)) + if(!cast_on.cast_check(-cast_amount)) reset_spell_cooldown() return . | SPELL_CANCEL_CAST -/datum/action/cooldown/spell/aoe/revenant/after_cast(mob/living/simple_animal/revenant/cast_on) +/datum/action/cooldown/spell/aoe/revenant/after_cast(mob/living/basic/revenant/cast_on) . = ..() if(reveal_duration > 0 SECONDS) - cast_on.reveal(reveal_duration) + cast_on.apply_status_effect(/datum/status_effect/revenant/revealed, reveal_duration) if(stun_duration > 0 SECONDS) - cast_on.stun(stun_duration) + cast_on.apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, stun_duration) //Overload Light: Breaks a light that's online and sends out lightning bolts to all nearby people. /datum/action/cooldown/spell/aoe/revenant/overload @@ -230,10 +114,10 @@ /// The range the shocks from the lights go var/shock_range = 2 - /// The damage the shcoskf rom the lgihts do + /// The damage the shocks from the lights do var/shock_damage = 15 -/datum/action/cooldown/spell/aoe/revenant/overload/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster) +/datum/action/cooldown/spell/aoe/revenant/overload/cast_on_thing_in_aoe(turf/victim, mob/living/basic/revenant/caster) for(var/obj/machinery/light/light in victim) if(!light.on) continue @@ -245,7 +129,7 @@ new /obj/effect/temp_visual/revenant(get_turf(light)) addtimer(CALLBACK(src, PROC_REF(overload_shock), light, caster), 20) -/datum/action/cooldown/spell/aoe/revenant/overload/proc/overload_shock(obj/machinery/light/to_shock, mob/living/simple_animal/revenant/caster) +/datum/action/cooldown/spell/aoe/revenant/overload/proc/overload_shock(obj/machinery/light/to_shock, mob/living/basic/revenant/caster) flick("[to_shock.base_state]2", to_shock) for(var/mob/living/carbon/human/human_mob in view(shock_range, to_shock)) if(human_mob == caster) @@ -270,7 +154,7 @@ reveal_duration = 4 SECONDS stun_duration = 2 SECONDS -/datum/action/cooldown/spell/aoe/revenant/defile/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster) +/datum/action/cooldown/spell/aoe/revenant/defile/cast_on_thing_in_aoe(turf/victim, mob/living/basic/revenant/caster) for(var/obj/effect/blessing/blessing in victim) qdel(blessing) new /obj/effect/temp_visual/revenant(victim) @@ -319,7 +203,7 @@ unlock_amount = 125 // A note to future coders: do not replace this with an EMP because it will wreck malf AIs and everyone will hate you. -/datum/action/cooldown/spell/aoe/revenant/malfunction/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster) +/datum/action/cooldown/spell/aoe/revenant/malfunction/cast_on_thing_in_aoe(turf/victim, mob/living/basic/revenant/caster) for(var/mob/living/simple_animal/bot/bot in victim) if(!(bot.bot_cover_flags & BOT_COVER_EMAGGED)) new /obj/effect/temp_visual/revenant(bot.loc) @@ -360,7 +244,7 @@ cast_amount = 50 unlock_amount = 75 -/datum/action/cooldown/spell/aoe/revenant/blight/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster) +/datum/action/cooldown/spell/aoe/revenant/blight/cast_on_thing_in_aoe(turf/victim, mob/living/basic/revenant/caster) for(var/mob/living/mob in victim) if(mob == caster) continue @@ -433,7 +317,7 @@ return things -/datum/action/cooldown/spell/aoe/revenant/haunt_object/cast_on_thing_in_aoe(obj/item/victim, mob/living/simple_animal/revenant/caster) +/datum/action/cooldown/spell/aoe/revenant/haunt_object/cast_on_thing_in_aoe(obj/item/victim, mob/living/basic/revenant/caster) var/distance_from_caster = get_dist(get_turf(victim), get_turf(caster)) var/chance_of_haunting = 150 * (1 / distance_from_caster) if(!prob(chance_of_haunting)) diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm new file mode 100644 index 000000000000..e59dd1f772fe --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm @@ -0,0 +1,83 @@ +/// Parent type for all unique revenant status effects +/datum/status_effect/revenant + +/datum/status_effect/revenant/on_creation(mob/living/new_owner, duration) + if(isnum(duration)) + src.duration = duration + return ..() + +/datum/status_effect/revenant/revealed + id = "revenant_revealed" + +/datum/status_effect/revenant/revealed/on_creation(mob/living/new_owner, duration) + if(isnum(duration)) + src.duration = duration + return ..() + +/datum/status_effect/revenant/revealed/on_apply() + . = ..() + if(!.) + return FALSE + owner.orbiting?.end_orbit(src) + + ADD_TRAIT(owner, TRAIT_REVENANT_REVEALED, TRAIT_STATUS_EFFECT(id)) + owner.invisibility = 0 + owner.incorporeal_move = FALSE + owner.update_appearance(UPDATE_ICON) + owner.update_mob_action_buttons() + +/datum/status_effect/revenant/revealed/on_remove() + REMOVE_TRAIT(owner, TRAIT_REVENANT_REVEALED, TRAIT_STATUS_EFFECT(id)) + + owner.incorporeal_move = INCORPOREAL_MOVE_JAUNT + owner.invisibility = INVISIBILITY_REVENANT + owner.update_appearance(UPDATE_ICON) + owner.update_mob_action_buttons() + return ..() + +/datum/status_effect/revenant/inhibited + id = "revenant_inhibited" + +/datum/status_effect/revenant/inhibited/on_creation(mob/living/new_owner, duration) + if(isnum(duration)) + src.duration = duration + return ..() + +/datum/status_effect/revenant/inhibited/on_apply() + . = ..() + if(!.) + return FALSE + owner.orbiting?.end_orbit(src) + + ADD_TRAIT(owner, TRAIT_REVENANT_INHIBITED, TRAIT_STATUS_EFFECT(id)) + owner.update_appearance(UPDATE_ICON) + + owner.balloon_alert(owner, "inhibited!") + +/datum/status_effect/revenant/inhibited/on_remove() + REMOVE_TRAIT(owner, TRAIT_REVENANT_INHIBITED, TRAIT_STATUS_EFFECT(id)) + owner.update_appearance(UPDATE_ICON) + + owner.balloon_alert(owner, "uninhibited") + return ..() + +/datum/status_effect/incapacitating/paralyzed/revenant + id = "revenant_paralyzed" + +/datum/status_effect/incapacitating/paralyzed/revenant/on_apply() + . = ..() + if(!.) + return FALSE + owner.orbiting?.end_orbit(src) + + ADD_TRAIT(owner, TRAIT_NO_TRANSFORM, TRAIT_STATUS_EFFECT(id)) + owner.balloon_alert(owner, "can't move!") + owner.update_mob_action_buttons() + owner.update_appearance(UPDATE_ICON) + +/datum/status_effect/incapacitating/paralyzed/revenant/on_remove() + REMOVE_TRAIT(owner, TRAIT_NO_TRANSFORM, TRAIT_STATUS_EFFECT(id)) + owner.update_mob_action_buttons() + owner.balloon_alert(owner, "can move again") + + return ..() diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_harvest.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_harvest.dm new file mode 100644 index 000000000000..21b8cd7feb16 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_harvest.dm @@ -0,0 +1,143 @@ +// This file contains the proc we use for revenant harvesting because it is a very long and bulky proc that takes up a lot of space elsewhere + +/// Container proc for `harvest()`, handles the pre-checks as well as potential early-exits for any reason. +/// Will return FALSE if we can't execute `harvest()`, or will otherwise the result of `harvest()`: a boolean value. +/mob/living/basic/revenant/proc/attempt_harvest(mob/living/carbon/human/target) + if(LAZYFIND(drained_mobs, REF(target))) + to_chat(src, span_revenwarning("[target]'s soul is dead and empty.")) + return FALSE + + if(!cast_check(0)) + return FALSE + + if(draining) + to_chat(src, span_revenwarning("You are already siphoning the essence of a soul!")) + return FALSE + + draining = TRUE + var/value_to_return = harvest_soul(target) + if(!value_to_return) + log_combat(src, target, "stopped the harvest of") + draining = FALSE + + return value_to_return + +/// Harvest; activated by clicking a target, will try to drain their essence. Handles all messages and handling of the target. +/// Returns FALSE if we exit out of the harvest, TRUE if it is fully done. +/mob/living/basic/revenant/proc/harvest_soul(mob/living/carbon/human/target) // this isn't in the main revenant code file because holyyyy shit it's long + if(QDELETED(target)) // what + return FALSE + + // cache pronouns in case they get deleted as well as be a nice micro-opt due to the multiple times we use them + var/target_their = target.p_their() + var/target_Their = target.p_Their() + var/target_Theyre = target.p_Theyre() + var/target_They_have = "[target.p_They()] [target.p_have()]" + + if(target.stat == CONSCIOUS) + to_chat(src, span_revennotice("[target_Their] soul is too strong to harvest.")) + if(prob(10)) + to_chat(target, span_revennotice("You feel as if you are being watched.")) + return FALSE + + log_combat(src, target, "started to harvest") + face_atom(target) + var/essence_drained = rand(15, 20) + + to_chat(src, span_revennotice("You search for the soul of [target].")) + + if(!do_after(src, (rand(10, 20) DECISECONDS), target, timed_action_flags = IGNORE_HELD_ITEM)) //did they get deleted in that second? + return FALSE + + var/target_has_client = !isnull(target.client) + if(target_has_client || target.ckey) // any target that has been occupied with a ckey is considered "intelligent" + to_chat(src, span_revennotice("[target_Their] soul burns with intelligence.")) + essence_drained += rand(20, 30) + + if(target.stat != DEAD && !HAS_TRAIT(target, TRAIT_WEAK_SOUL)) + to_chat(src, span_revennotice("[target_Their] soul blazes with life!")) + essence_drained += rand(40, 50) + + if(!target_has_client && HAS_TRAIT(target, TRAIT_WEAK_SOUL)) + to_chat(src, span_revennotice("[target_Their] soul is weak and underdeveloped. They won't be worth very much.")) + essence_drained = 5 + + to_chat(src, span_revennotice("[target_Their] soul is weak and faltering. It's time to harvest.")) + + if(!do_after(src, (rand(15, 20) DECISECONDS), target, timed_action_flags = IGNORE_HELD_ITEM)) + to_chat(src, span_revennotice("The harvest is abandoned.")) + return FALSE + + switch(essence_drained) + if(1 to 30) + to_chat(src, span_revennotice("[target] will not yield much essence. Still, every bit counts.")) + if(30 to 70) + to_chat(src, span_revennotice("[target] will yield an average amount of essence.")) + if(70 to 90) + to_chat(src, span_revenboldnotice("Such a feast! [target] will yield much essence to you.")) + if(90 to INFINITY) + to_chat(src, span_revenbignotice("Ah, the perfect soul. [target] will yield massive amounts of essence to you.")) + + if(!do_after(src, (rand(15, 25) DECISECONDS), target, timed_action_flags = IGNORE_HELD_ITEM)) //how about now + to_chat(src, span_revenwarning("You are not close enough to siphon [target ? "[target]'s" : "[target_their]"] soul. The link has been broken.")) + return FALSE + + if(target.stat == CONSCIOUS) + to_chat(src, span_revenwarning("[target_Theyre] now powerful enough to fight off your draining!")) + to_chat(target, span_boldannounce("You feel something tugging across your body before subsiding.")) //hey, wait a minute... + return FALSE + + to_chat(src, span_revenminor("You begin siphoning essence from [target]'s soul.")) + if(target.stat != DEAD) + to_chat(target, span_warning("You feel a horribly unpleasant draining sensation as your grip on life weakens...")) + if(target.stat == SOFT_CRIT) + target.Stun(46) + + apply_status_effect(/datum/status_effect/revenant/revealed, 5 SECONDS) + apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, 5 SECONDS) + + target.visible_message(span_warning("[target] suddenly rises slightly into the air, [target_their] skin turning an ashy gray.")) + + if(target.can_block_magic(MAGIC_RESISTANCE_HOLY)) + to_chat(src, span_revenminor("Something's wrong! [target] seems to be resisting the siphoning, leaving you vulnerable!")) + target.visible_message( + span_warning("[target] slumps onto the ground."), + span_revenwarning("Violet lights, dancing in your vision, receding--"), + ) + return FALSE + + var/datum/beam/draining_beam = Beam(target, icon_state = "drain_life") + if(!do_after(src, 4.6 SECONDS, target, timed_action_flags = IGNORE_HELD_ITEM)) //As one cannot prove the existance of ghosts, ghosts cannot prove the existance of the target they were draining. + to_chat(src, span_revenwarning("[target ? "[target]'s soul has" : "[target_They_have]"] been drawn out of your grasp. The link has been broken.")) + if(target) + target.visible_message( + span_warning("[target] slumps onto the ground."), + span_revenwarning("Violet lights, dancing in your vision, receding--"), + ) + qdel(draining_beam) + return FALSE + + change_essence_amount(essence_drained, FALSE, target) + + if(essence_drained <= 90 && target.stat != DEAD && !HAS_TRAIT(target, TRAIT_WEAK_SOUL)) + max_essence += 5 + to_chat(src, span_revenboldnotice("The absorption of [target]'s living soul has increased your maximum essence level. Your new maximum essence is [max_essence].")) + + if(essence_drained > 90) + max_essence += 15 + perfectsouls++ + to_chat(src, span_revenboldnotice("The perfection of [target]'s soul has increased your maximum essence level. Your new maximum essence is [max_essence].")) + + to_chat(src, span_revennotice("[target]'s soul has been considerably weakened and will yield no more essence for the time being.")) + target.visible_message( + span_warning("[target] slumps onto the ground."), + span_revenwarning("Violet lights, dancing in your vision, getting clo--"), + ) + + LAZYADD(drained_mobs, REF(target)) + if(target.stat != DEAD) + target.investigate_log("has died from revenant harvest.", INVESTIGATE_DEATHS) + target.death(FALSE) + + qdel(draining_beam) + return TRUE diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm new file mode 100644 index 000000000000..2c4299258eea --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm @@ -0,0 +1,110 @@ +//reforming +/obj/item/ectoplasm/revenant + name = "glimmering residue" + desc = "A pile of fine blue dust. Small tendrils of violet mist swirl around it." + icon = 'icons/effects/effects.dmi' + icon_state = "revenantEctoplasm" + w_class = WEIGHT_CLASS_SMALL + /// Are we currently reforming? + var/reforming = TRUE + /// Are we inert (aka distorted such that we can't reform)? + var/inert = FALSE + /// The key of the revenant that we started the reform as + var/old_ckey + /// The revenant we're currently storing + var/mob/living/basic/revenant/revenant + +/obj/item/ectoplasm/revenant/Initialize(mapload) + . = ..() + addtimer(CALLBACK(src, PROC_REF(try_reform)), 1 MINUTES) + +/obj/item/ectoplasm/revenant/Destroy() + if(!QDELETED(revenant)) + qdel(revenant) + return ..() + +/obj/item/ectoplasm/revenant/attack_self(mob/user) + if(!reforming || inert) + return ..() + user.visible_message( + span_notice("[user] scatters [src] in all directions."), + span_notice("You scatter [src] across the area. The particles slowly fade away."), + ) + user.dropItemToGround(src) + qdel(src) + +/obj/item/ectoplasm/revenant/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(inert) + return + visible_message(span_notice("[src] breaks into particles upon impact, which fade away to nothingness.")) + qdel(src) + +/obj/item/ectoplasm/revenant/examine(mob/user) + . = ..() + if(inert) + . += span_revennotice("It seems inert.") + else if(reforming) + . += span_revenwarning("It is shifting and distorted. It would be wise to destroy this.") + +/obj/item/ectoplasm/revenant/suicide_act(mob/living/user) + user.visible_message(span_suicide("[user] is inhaling [src]! It looks like [user.p_theyre()] trying to visit the shadow realm!")) + qdel(src) + return OXYLOSS + +/obj/item/ectoplasm/revenant/proc/try_reform() + if(reforming) + reforming = FALSE + reform() + else + inert = TRUE + visible_message(span_warning("[src] settles down and seems lifeless.")) + +/// Actually moves the revenant out of ourself +/obj/item/ectoplasm/revenant/proc/reform() + if(QDELETED(src) || QDELETED(revenant) || inert) + return + + message_admins("Revenant ectoplasm was left undestroyed for 1 minute and is reforming into a new revenant.") + forceMove(drop_location()) //In case it's in a backpack or someone's hand + + var/user_name = old_ckey + if(isnull(revenant.client)) + var/mob/potential_user = get_new_user() + var/datum/mind/potential_mind = potential_user?.mind + if(isnull(potential_mind)) + return + + potential_mind.transfer_to(revenant) + user_name = potential_user.ckey + qdel(potential_user) + + message_admins("[user_name] has been [old_ckey == user_name ? "re":""]made into a revenant by reforming ectoplasm.") + revenant.log_message("was [old_ckey == user_name ? "re":""]made as a revenant by reforming ectoplasm.", LOG_GAME) + visible_message(span_revenboldnotice("[src] suddenly rises into the air before fading away.")) + + revenant.death_reset() + revenant = null + qdel(src) + +/// Handles giving the revenant a new client to control it +/obj/item/ectoplasm/revenant/proc/get_new_user() + message_admins("The new revenant's old client either could not be found or is in a new, living mob - grabbing a random candidate instead...") + var/list/candidates = poll_candidates_for_mob("Do you want to be [revenant.name] (reforming)?", ROLE_REVENANT, ROLE_REVENANT, 5 SECONDS, revenant) + + if(!LAZYLEN(candidates)) + message_admins("No candidates were found for the new revenant.") + inert = TRUE + visible_message(span_revenwarning("[src] settles down and seems lifeless.")) + qdel(revenant) + return null + + var/mob/dead/observer/potential_client = pick(candidates) + if(isnull(potential_client)) + qdel(revenant) + message_admins("No ckey was found for the new revenant. Oh well!") + inert = TRUE + visible_message(span_revenwarning("[src] settles down and seems lifeless.")) + return null + + return potential_client diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_objectives.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_objectives.dm new file mode 100644 index 000000000000..7dd391c17e47 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_objectives.dm @@ -0,0 +1,37 @@ +/datum/objective/revenant + +/datum/objective/revenant/New() + target_amount = rand(350, 600) + explanation_text = "Absorb [target_amount] points of essence from humans." + return ..() + +/datum/objective/revenant/check_completion() + if(!isrevenant(owner.current)) + return FALSE + var/mob/living/basic/revenant/owner_mob = owner.current + if(QDELETED(owner_mob) || owner_mob.stat == DEAD) + return FALSE + var/essence_stolen = owner_mob.essence_accumulated + return essence_stolen >= target_amount + +/datum/objective/revenant_fluff + +/datum/objective/revenant_fluff/New() + var/list/explanation_texts = list( + "Assist and exacerbate existing threats at critical moments.", + "Cause as much chaos and anger as you can without being killed.", + "Damage and render as much of the station rusted and unusable as possible.", + "Disable and cause malfunctions in as many machines as possible.", + "Ensure that any holy weapons are rendered unusable.", + "Heed and obey the requests of the dead, provided that carrying them out wouldn't be too inconvenient or self-destructive.", + "Impersonate or be worshipped as a God.", + "Make the captain as miserable as possible.", + "Make the clown as miserable as possible.", + "Make the crew as miserable as possible.", + "Prevent the use of energy weapons where possible.", + ) + explanation_text = pick(explanation_texts) + return ..() + +/datum/objective/revenant_fluff/check_completion() + return TRUE diff --git a/code/modules/mob/living/simple_animal/friendly/robot_customer.dm b/code/modules/mob/living/basic/space_fauna/robot_customer.dm similarity index 63% rename from code/modules/mob/living/simple_animal/friendly/robot_customer.dm rename to code/modules/mob/living/basic/space_fauna/robot_customer.dm index baa09c0eb86e..e084e11f403c 100644 --- a/code/modules/mob/living/simple_animal/friendly/robot_customer.dm +++ b/code/modules/mob/living/basic/space_fauna/robot_customer.dm @@ -1,45 +1,56 @@ ///Robot customers -/mob/living/simple_animal/robot_customer - name = "space-tourist bot" +/mob/living/basic/robot_customer + name = "tourist bot" maxHealth = 150 health = 150 desc = "I wonder what they'll order..." gender = NEUTER + icon = 'icons/mob/simple/tourists.dmi' icon_state = "amerifat" icon_living = "amerifat" - ///Override so it uses datum ai - can_have_ai = FALSE - AIStatus = AI_OFF - del_on_death = TRUE + + basic_mob_flags = DEL_ON_DEATH mob_biotypes = MOB_ROBOTIC|MOB_HUMANOID sentience_type = SENTIENCE_ARTIFICIAL - ai_controller = /datum/ai_controller/robot_customer + unsuitable_atmos_damage = 0 - minbodytemp = 0 - maxbodytemp = 1000 + minimum_survivable_temperature = TCMB + maximum_survivable_temperature = T0C + 1000 + + ai_controller = /datum/ai_controller/robot_customer + + /// The clothes that we draw on this tourist. var/clothes_set = "amerifat_clothes" + /// Reference to the hud that we show when the player hovers over us. var/datum/atom_hud/hud_to_show_on_hover - -/mob/living/simple_animal/robot_customer/Initialize(mapload, datum/customer_data/customer_data = /datum/customer_data/american, datum/venue/attending_venue = SSrestaurant.all_venues[/datum/venue/restaurant]) - ADD_TRAIT(src, list(TRAIT_NOMOBSWAP, TRAIT_NO_TELEPORT, TRAIT_STRONG_GRABBER), INNATE_TRAIT) // never suffer a bitch to fuck with you - AddElement(/datum/element/footstep, FOOTSTEP_OBJ_ROBOT, 1, -6, sound_vary = TRUE) +/mob/living/basic/robot_customer/Initialize( + mapload, + datum/customer_data/customer_data = /datum/customer_data/american, + datum/venue/attending_venue = SSrestaurant.all_venues[/datum/venue/restaurant], +) var/datum/customer_data/customer_info = SSrestaurant.all_customers[customer_data] - clothes_set = pick(customer_info.clothing_sets) ai_controller = customer_info.ai_controller_used + . = ..() + + ADD_TRAIT(src, list(TRAIT_NOMOBSWAP, TRAIT_NO_TELEPORT, TRAIT_STRONG_GRABBER), INNATE_TRAIT) // never suffer a bitch to fuck with you + AddElement(/datum/element/footstep, FOOTSTEP_OBJ_ROBOT, 1, -6, sound_vary = TRUE) + ai_controller.set_blackboard_key(BB_CUSTOMER_CUSTOMERINFO, customer_info) ai_controller.set_blackboard_key(BB_CUSTOMER_ATTENDING_VENUE, attending_venue) ai_controller.set_blackboard_key(BB_CUSTOMER_PATIENCE, customer_info.total_patience) + icon = customer_info.base_icon icon_state = customer_info.base_icon_state name = "[pick(customer_info.name_prefixes)]-bot" color = rgb(rand(80,255), rand(80,255), rand(80,255)) - update_icon() + clothes_set = pick(customer_info.clothing_sets) + update_appearance(UPDATE_ICON) ///Clean up on the mobs seat etc when its deleted (Either by murder or because it left) -/mob/living/simple_animal/robot_customer/Destroy() +/mob/living/basic/robot_customer/Destroy() var/datum/venue/attending_venue = ai_controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] var/obj/structure/holosign/robot_seat/our_seat = ai_controller.blackboard[BB_CUSTOMER_MY_SEAT] attending_venue.current_visitors -= src @@ -49,18 +60,18 @@ return ..() ///Robots need robot gibs...! -/mob/living/simple_animal/robot_customer/spawn_gibs() +/mob/living/basic/robot_customer/spawn_gibs() new /obj/effect/gibspawner/robot(drop_location(), src) -/mob/living/simple_animal/robot_customer/MouseEntered(location, control, params) +/mob/living/basic/robot_customer/MouseEntered(location, control, params) . = ..() hud_to_show_on_hover?.show_to(usr) -/mob/living/simple_animal/robot_customer/MouseExited(location, control, params) +/mob/living/basic/robot_customer/MouseExited(location, control, params) . = ..() hud_to_show_on_hover?.hide_from(usr) -/mob/living/simple_animal/robot_customer/update_overlays() +/mob/living/basic/robot_customer/update_overlays() . = ..() var/datum/customer_data/customer_info = ai_controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] @@ -82,21 +93,24 @@ if(bonus_overlays) . += bonus_overlays -/mob/living/simple_animal/robot_customer/send_speech(message, message_range, obj/source, bubble_type, list/spans, datum/language/message_language, list/message_mods, forced) +/mob/living/basic/robot_customer/send_speech(message, message_range, obj/source, bubble_type, list/spans, datum/language/message_language, list/message_mods, forced, tts_message, list/tts_filter) . = ..() var/datum/customer_data/customer_info = ai_controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] playsound(src, customer_info.speech_sound, 30, extrarange = MEDIUM_RANGE_SOUND_EXTRARANGE, falloff_distance = 5) -/mob/living/simple_animal/robot_customer/examine(mob/user) +/mob/living/basic/robot_customer/examine(mob/user) . = ..() - // this should be handled by the ai controller - if(ai_controller.blackboard[BB_CUSTOMER_CURRENT_ORDER]) - var/datum/venue/attending_venue = ai_controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] - var/wanted_item = ai_controller.blackboard[BB_CUSTOMER_CURRENT_ORDER] - var/order - if(istype(wanted_item, /datum/custom_order)) - var/datum/custom_order/custom_order = wanted_item - order = custom_order.get_order_line(attending_venue) - else - order = attending_venue.order_food_line(wanted_item) - . += span_notice("Their order was: \"[order].\"") + if(isnull(ai_controller.blackboard[BB_CUSTOMER_CURRENT_ORDER])) + return + + var/datum/venue/attending_venue = ai_controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] + var/wanted_item = ai_controller.blackboard[BB_CUSTOMER_CURRENT_ORDER] + var/order = "nothing" + + if(istype(wanted_item, /datum/custom_order)) + var/datum/custom_order/custom_order = wanted_item + order = custom_order.get_order_line(attending_venue) + else + order = attending_venue.order_food_line(wanted_item) + + . += span_notice("Their order was: \"[order].\"") diff --git a/code/modules/mob/living/basic/space_fauna/snake/snake.dm b/code/modules/mob/living/basic/space_fauna/snake/snake.dm new file mode 100644 index 000000000000..135402a5b441 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/snake/snake.dm @@ -0,0 +1,86 @@ + +/mob/living/basic/snake + name = "snake" + desc = "A slithery snake. These legless reptiles are the bane of mice and adventurers alike." + icon_state = "snake" + icon_living = "snake" + icon_dead = "snake_dead" + speak_emote = list("hisses") + + health = 20 + maxHealth = 20 + melee_damage_lower = 5 + melee_damage_upper = 6 + obj_damage = 0 + environment_smash = ENVIRONMENT_SMASH_NONE + + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "shoos" + response_disarm_simple = "shoo" + response_harm_continuous = "steps on" + response_harm_simple = "step on" + + density = FALSE + pass_flags = PASSTABLE | PASSMOB + mob_size = MOB_SIZE_SMALL + + faction = list(FACTION_HOSTILE) + mob_biotypes = MOB_ORGANIC | MOB_BEAST | MOB_REPTILE + gold_core_spawnable = FRIENDLY_SPAWN + + ai_controller = /datum/ai_controller/basic_controller/snake + + /// List of stuff (mice) that we want to eat + var/static/list/edibles = list( + /mob/living/basic/mouse, + /obj/item/food/deadmouse, + ) + +/mob/living/basic/snake/Initialize(mapload, special_reagent) + . = ..() + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_SNAKE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + + AddElement(/datum/element/basic_eating, heal_amt = 2, food_types = edibles) + ai_controller.set_blackboard_key(BB_BASIC_FOODS, edibles) + + AddComponent(\ + /datum/component/tameable,\ + food_types = list(/obj/item/food/deadmouse),\ + tame_chance = 75,\ + bonus_tame_chance = 10,\ + ) // snakes are really fond of food, especially in the cold darkness of space :) + + if(isnull(special_reagent)) + special_reagent = /datum/reagent/toxin + + AddElement(/datum/element/venomous, special_reagent, 4) + +/mob/living/basic/snake/befriend(mob/living/new_friend) + . = ..() + visible_message("[src] hisses happily as it seems to bond with [new_friend].") + +/// Snakes are primarily concerned with getting those tasty, tasty mice, but aren't afraid to strike back at those who attack them +/datum/ai_controller/basic_controller/snake + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends/allow_items, + ) + + ai_traits = STOP_MOVING_WHEN_PULLED + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/random_speech/snake, + ) diff --git a/code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm b/code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm new file mode 100644 index 000000000000..3eb404761c52 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm @@ -0,0 +1,6 @@ +/datum/ai_planning_subtree/random_speech/snake + speech_chance = 5 + speak = list("hsssss","sssSSsssss...","hiisssss") + sound = list('sound/creatures/snake_hissing1.ogg', 'sound/creatures/snake_hissing2.ogg') + emote_hear = list("hisses.") + emote_see = list("slithers around.", "glances.", "stares.") diff --git a/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_breath.dm b/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_breath.dm new file mode 100644 index 000000000000..5be5038b3a41 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_breath.dm @@ -0,0 +1,32 @@ +/// A space dragon's fire breath, toasts lunch AND buffs your friends +/datum/action/cooldown/mob_cooldown/fire_breath/carp + desc = "A Space Dragon's burning breath not only chars its foes, but invigorates Space Carp as well." + fire_damage = 30 + mech_damage = 50 + fire_range = 20 + fire_temperature = 700 // Even hotter than a megafauna for some reason + shared_cooldown = NONE + melee_cooldown_time = 0 SECONDS + +/datum/action/cooldown/mob_cooldown/fire_breath/carp/on_burn_mob(mob/living/barbecued, mob/living/source) + if (!source.faction_check_atom(barbecued)) + return ..() + to_chat(barbecued, span_notice("[source]'s fiery breath fills you with energy!")) + barbecued.apply_status_effect(/datum/status_effect/carp_invigoration) + +/// Makes you run faster for the duration +/datum/status_effect/carp_invigoration + id = "carp_invigorated" + alert_type = null + duration = 8 SECONDS + +/datum/status_effect/carp_invigoration/on_apply() + . = ..() + if (!.) + return + owner.add_filter("anger_glow", 3, list("type" = "outline", "color" = COLOR_CARP_RIFT_RED, "size" = 2)) + owner.add_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) + +/datum/status_effect/carp_invigoration/on_remove() + owner.remove_filter("anger_glow") + owner.remove_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) diff --git a/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_gust.dm b/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_gust.dm new file mode 100644 index 000000000000..074804de86c9 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_gust.dm @@ -0,0 +1,94 @@ +/// Default additional time to spend stunned per usage of ability +#define DEFAULT_ACTIVATED_ENDLAG 3 DECISECONDS + +/// Rise into the air and slam down, knocking people away. No real cooldown but has escalating endlag if used in quick succession. +/datum/action/cooldown/mob_cooldown/wing_buffet + name = "Wing Buffet" + desc = "Rise into the air and release a powerful gust from your wings, blowing attackers away. Becomes more tiring if used in quick succession." + button_icon = 'icons/effects/magic.dmi' + button_icon_state = "tornado" + cooldown_time = 1 SECONDS + melee_cooldown_time = 0 + click_to_activate = FALSE + shared_cooldown = NONE + /// Timer we use to track our current action + var/active_timer + /// How far away can we reach people? + var/gust_distance = 4 + /// How long to animate for before we start? + var/windup_time = 1.2 SECONDS + /// Minimum amount of stun time following use of wing buffet + var/minimum_endlag = 4 DECISECONDS + /// Amount of extra time to stay stunned after the end of the ability + var/additional_endlag = 0 DECISECONDS + /// Amount of time to add to endlag after each successful use of the ability + var/endlag_per_activation = DEFAULT_ACTIVATED_ENDLAG + /// How much accumulated stun time do we subtract every second? Takes a full minute to regen off a single use :( + var/endlag_decay_per_second = DEFAULT_ACTIVATED_ENDLAG / 60 + /// Increase the effect of our accumulated additional stun time by this much if space dragon has lost some rifts + var/exhaustion_multiplier = 5 + /// List of traits we apply while the ability is ongoing, stops us from moving around and such + var/static/list/applied_traits = list( + TRAIT_IMMOBILIZED, + TRAIT_INCAPACITATED, + TRAIT_NO_FLOATING_ANIM, + TRAIT_WING_BUFFET, + ) + +/datum/action/cooldown/mob_cooldown/wing_buffet/Grant(mob/granted_to) + . = ..() + RegisterSignal(granted_to, COMSIG_LIVING_LIFE, PROC_REF(on_life)) + +/datum/action/cooldown/mob_cooldown/wing_buffet/Remove(mob/removed_from) + . = ..() + deltimer(active_timer) + UnregisterSignal(removed_from, COMSIG_LIVING_LIFE) + removed_from.remove_traits(applied_traits + TRAIT_WING_BUFFET_TIRED, REF(src)) + +/// Decay our accumulated additional tiredness +/datum/action/cooldown/mob_cooldown/wing_buffet/proc/on_life(mob/living/liver, seconds_per_tick, times_fired) + SIGNAL_HANDLER + if (liver.stat == DEAD) + return // not so life now buddy + additional_endlag = max(0, additional_endlag - (endlag_decay_per_second * seconds_per_tick)) + +/datum/action/cooldown/mob_cooldown/wing_buffet/Activate(atom/target) + begin_sequence() + StartCooldown() + return TRUE + +/// Rise up into the air +/datum/action/cooldown/mob_cooldown/wing_buffet/proc/begin_sequence() + owner.add_traits(applied_traits, REF(src)) // No moving till we're done + owner.update_appearance(UPDATE_ICON) + animate(owner, pixel_y = 20, time = windup_time) + active_timer = addtimer(CALLBACK(src, PROC_REF(ground_pound)), windup_time, TIMER_DELETE_ME | TIMER_STOPPABLE) + +/// Slam into the ground +/datum/action/cooldown/mob_cooldown/wing_buffet/proc/ground_pound() + if (QDELETED(owner)) + return + owner.pixel_y = 0 + playsound(owner, 'sound/effects/gravhit.ogg', 100, TRUE) + for (var/mob/living/candidate in view(gust_distance, owner)) + if(candidate == owner || candidate.faction_check_atom(owner)) + continue + owner.visible_message(span_boldwarning("[candidate] is knocked back by the gust!")) + to_chat(candidate, span_userdanger("You're knocked back by the gust!")) + var/dir_to_target = get_dir(get_turf(owner), get_turf(candidate)) + var/throwtarget = get_edge_target_turf(target, dir_to_target) + candidate.safe_throw_at(throwtarget, range = 10, speed = 1, thrower = owner) + candidate.Paralyze(5 SECONDS) + + var/endlag_multiplier = HAS_TRAIT(owner, TRAIT_RIFT_FAILURE) ? exhaustion_multiplier : 1 + var/stun_time = minimum_endlag + (additional_endlag * endlag_multiplier) + additional_endlag += endlag_per_activation * endlag_multiplier // double dips, rough + ADD_TRAIT(owner, TRAIT_WING_BUFFET_TIRED, REF(src)) + owner.update_appearance(UPDATE_ICON) + active_timer = addtimer(CALLBACK(src, PROC_REF(complete_ability)), stun_time, TIMER_DELETE_ME | TIMER_STOPPABLE) + +/datum/action/cooldown/mob_cooldown/wing_buffet/proc/complete_ability() + owner.remove_traits(applied_traits + TRAIT_WING_BUFFET_TIRED, REF(src)) + owner.update_appearance(UPDATE_ICON) + +#undef DEFAULT_ACTIVATED_ENDLAG diff --git a/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm b/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm new file mode 100644 index 000000000000..549f352b990a --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm @@ -0,0 +1,230 @@ +/// You can't make a dragon darker than this, it'd be hard to see +#define REJECT_DARK_COLOUR_THRESHOLD 50 +/// Any interactions executed by the space dragon +#define DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION "space dragon interaction" + +/** + * Advanced stage of the space carp life cycle, spawned as a midround antagonist or via traitor transformation. + * Can eat corpses to heal, blow people back with its wings, and obviously as a dragon it breathes fire. It can even tear through walls. + * The midround even version also creates rifts which summon carp, and heals when near them. + */ +/mob/living/basic/space_dragon + name = "Space Dragon" + desc = "A serpentine leviathan whose flight defies all modern understanding of physics. Said to be the ultimate stage in the life cycle of the Space Carp." + icon = 'icons/mob/nonhuman-player/spacedragon.dmi' + icon_state = "spacedragon" + icon_living = "spacedragon" + icon_dead = "spacedragon_dead" + health_doll_icon = "spacedragon" + faction = list(FACTION_CARP) + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + gender = NEUTER + maxHealth = 400 + health = 400 + unsuitable_cold_damage = 0 + unsuitable_heat_damage = 0 + unsuitable_atmos_damage = 0 + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0.5, OXY = 1) + istate = ISTATE_HARM + speed = 0 + movement_type = FLYING + attack_verb_continuous = "chomps" + attack_verb_simple = "chomp" + attack_sound = 'sound/magic/demon_attack1.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + obj_damage = 50 + melee_damage_upper = 35 + melee_damage_lower = 35 + melee_attack_cooldown = CLICK_CD_MELEE + mob_size = MOB_SIZE_LARGE + armour_penetration = 30 + pixel_x = -16 + base_pixel_x = -16 + maptext_height = 64 + maptext_width = 64 + mouse_opacity = MOUSE_OPACITY_ICON + death_sound = 'sound/creatures/space_dragon_roar.ogg' + death_message = "screeches in agony as it collapses to the floor, its life extinguished." + butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30) + + /// The colour of the space dragon + var/chosen_colour + /// Minimum devastation damage dealt coefficient based on max health + var/devastation_damage_min_percentage = 0.4 + /// Maximum devastation damage dealt coefficient based on max health + var/devastation_damage_max_percentage = 0.75 + /// Our fire breath action + var/datum/action/cooldown/mob_cooldown/fire_breath/carp/fire_breath + /// Our wing flap action + var/datum/action/cooldown/mob_cooldown/wing_buffet/buffet + +/mob/living/basic/space_dragon/Initialize(mapload) + . = ..() + add_traits(list(TRAIT_SPACEWALK, TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_NO_FLOATING_ANIM, TRAIT_HEALS_FROM_CARP_RIFTS), INNATE_TRAIT) + AddElement(/datum/element/simple_flying) + AddElement(/datum/element/content_barfer) + AddElement(/datum/element/wall_tearer, do_after_key = DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION) + AddElement(/datum/element/door_pryer, pry_time = 4 SECONDS, interaction_key = DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION) + AddComponent(/datum/component/seethrough_mob) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + RegisterSignal(src, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_changed)) + RegisterSignal(src, COMSIG_ATOM_PRE_EX_ACT, PROC_REF(on_exploded)) + + fire_breath = new(src) + fire_breath.Grant(src) + + buffet = new(src) + buffet.Grant(src) + +/mob/living/basic/space_dragon/Destroy() + QDEL_NULL(fire_breath) + QDEL_NULL(buffet) + return ..() + +/mob/living/basic/space_dragon/Login() + . = ..() + if(!isnull(chosen_colour)) + return + rename_dragon() + select_colour() + +/// Allows the space dragon to pick a funny name +/mob/living/basic/space_dragon/proc/rename_dragon() + var/chosen_name = sanitize_name(reject_bad_text(tgui_input_text(src, "What would you like your name to be?", "Choose Your Name", real_name, MAX_NAME_LEN))) + if(!chosen_name) // Null or empty or rejected + to_chat(src, span_warning("Not a valid name, please try again.")) + rename_dragon() + return + to_chat(src, span_notice("Your name is now [span_name("[chosen_name]")], the feared Space Dragon.")) + fully_replace_character_name(null, chosen_name) + +/// Select scale colour with the colour picker +/mob/living/basic/space_dragon/proc/select_colour() + chosen_colour = input(src, "What colour would you like to be?" ,"Colour Selection", COLOR_WHITE) as color|null + if(!chosen_colour) // Redo proc until we get a color + to_chat(src, span_warning("Not a valid colour, please try again.")) + select_colour() + return + var/temp_hsv = RGBtoHSV(chosen_colour) + if(ReadHSV(temp_hsv)[3] < REJECT_DARK_COLOUR_THRESHOLD) + to_chat(src, span_danger("Invalid colour. Your colour is not bright enough.")) + select_colour() + return + add_atom_colour(chosen_colour, FIXED_COLOUR_PRIORITY) + update_appearance(UPDATE_OVERLAYS) + +/mob/living/basic/space_dragon/update_icon_state() + . = ..() + if (stat == DEAD) + return + if (!HAS_TRAIT(src, TRAIT_WING_BUFFET)) + icon_state = icon_living + return + if (HAS_TRAIT(src, TRAIT_WING_BUFFET_TIRED)) + icon_state = "spacedragon_gust_2" + return + icon_state = "spacedragon_gust" + +/mob/living/basic/space_dragon/update_overlays() + . = ..() + var/overlay_state = "overlay_base" + if (stat == DEAD) + overlay_state = "overlay_dead" + else if (HAS_TRAIT(src, TRAIT_WING_BUFFET_TIRED)) + overlay_state = "overlay_gust_2" + else if (HAS_TRAIT(src, TRAIT_WING_BUFFET)) + overlay_state = "overlay_gust" + + var/mutable_appearance/overlay = mutable_appearance(icon, overlay_state) + overlay.appearance_flags = RESET_COLOR + . += overlay + +/mob/living/basic/space_dragon/melee_attack(obj/vehicle/sealed/mecha/target, list/modifiers, ignore_cooldown) + . = ..() + if (!. || !ismecha(target)) + return + target.take_damage(obj_damage, BRUTE, MELEE) + +/// Before we attack something, check if we want to do something else instead +/mob/living/basic/space_dragon/proc/pre_attack(mob/living/source, atom/target) + SIGNAL_HANDLER + if (target == src) + return COMPONENT_HOSTILE_NO_ATTACK // Easy to misclick yourself, let's not + if (DOING_INTERACTION(source, DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION)) + balloon_alert(source, "busy!") + return COMPONENT_HOSTILE_NO_ATTACK + if (!isliving(target)) + return + var/mob/living/living_target = target + if (living_target.stat != DEAD) + return + INVOKE_ASYNC(src, PROC_REF(try_eat), living_target) + return COMPONENT_HOSTILE_NO_ATTACK + +/// Try putting something inside us +/mob/living/basic/space_dragon/proc/try_eat(mob/living/food) + balloon_alert(src, "swallowing...") + if (do_after(src, 3 SECONDS, target = food)) + eat(food) + +/// Succeed in putting something inside us +/mob/living/basic/space_dragon/proc/eat(mob/living/food) + adjust_health(-food.maxHealth * 0.25) + if (QDELETED(food) || food.loc == src) + return FALSE + playsound(src, 'sound/magic/demon_attack1.ogg', 60, TRUE) + visible_message(span_boldwarning("[src] swallows [food] whole!")) + food.extinguish_mob() // It's wet in there, and our food is likely to be on fire. Let's be decent and not husk them. + food.forceMove(src) + return TRUE + +/mob/living/basic/space_dragon/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + if (isliving(arrived)) + RegisterSignal(arrived, COMSIG_MOB_STATCHANGE, PROC_REF(eaten_stat_changed)) + +/mob/living/basic/space_dragon/Exited(atom/movable/gone, direction) + . = ..() + if (isliving(gone)) + UnregisterSignal(gone, COMSIG_MOB_STATCHANGE) + +/// Release consumed mobs if they transition from dead to alive +/mob/living/basic/space_dragon/proc/eaten_stat_changed(mob/living/eaten) + SIGNAL_HANDLER + if (eaten.stat == DEAD) + return + new /obj/effect/decal/cleanable/vomit(loc) + playsound(src, 'sound/effects/splat.ogg', vol = 50, vary = TRUE) + visible_message(span_danger("[src] vomits up [eaten]!")) + eaten.forceMove(loc) + eaten.Paralyze(5 SECONDS) + +/mob/living/basic/space_dragon/RangedAttack(atom/target, modifiers) + fire_breath.Trigger(target = target) + +/mob/living/basic/space_dragon/ranged_secondary_attack(atom/target, modifiers) + buffet.Trigger() + +/// When our stat changes, make sure we are using the correct overlay +/mob/living/basic/space_dragon/proc/on_stat_changed() + SIGNAL_HANDLER + update_appearance(UPDATE_OVERLAYS) + +/// We take devastating bomb damage as a random percentage of our maximum health instead of being gibbed +/mob/living/basic/space_dragon/proc/on_exploded(mob/living/source, severity, target, origin) + SIGNAL_HANDLER + if (severity != EXPLODE_DEVASTATE) + return + var/damage_coefficient = rand(devastation_damage_min_percentage, devastation_damage_max_percentage) + adjustBruteLoss(initial(maxHealth)*damage_coefficient) + return COMPONENT_CANCEL_EX_ACT // we handled it + +/// Subtype used by the midround/event +/mob/living/basic/space_dragon/spawn_with_antag + +/mob/living/basic/space_dragon/spawn_with_antag/mind_initialize() + . = ..() + mind.add_antag_datum(/datum/antagonist/space_dragon) + +#undef REJECT_DARK_COLOUR_THRESHOLD +#undef DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION diff --git a/code/modules/mob/living/basic/space_fauna/spaceman.dm b/code/modules/mob/living/basic/space_fauna/spaceman.dm index d89574dfca3b..417b2feda89f 100644 --- a/code/modules/mob/living/basic/space_fauna/spaceman.dm +++ b/code/modules/mob/living/basic/space_fauna/spaceman.dm @@ -32,7 +32,7 @@ /datum/ai_controller/basic_controller/spaceman blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm index 24148e7c071a..25fcb3b7a4cf 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm @@ -1,10 +1,10 @@ /// Attacks people it can see, spins webs if it can't see anything to attack. /datum/ai_controller/basic_controller/giant_spider blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) - ai_traits = STOP_MOVING_WHEN_PULLED + ai_traits = STOP_MOVING_WHEN_PULLED | PAUSE_DURING_DO_AFTER ai_movement = /datum/ai_movement/basic_avoidance idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking @@ -30,7 +30,7 @@ /// Used by Araneus, who only attacks those who attack first. He is house-trained and will not web up the HoS office. /datum/ai_controller/basic_controller/giant_spider/retaliate blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) planning_subtrees = list( @@ -43,9 +43,8 @@ /// Retaliates, hunts other maintenance creatures, runs away from larger attackers, and spins webs. /datum/ai_controller/basic_controller/giant_spider/pest blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/of_size/ours_or_smaller(), // Hunt mobs our size - BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic/of_size/larger(), // Run away from mobs bigger than we are - BB_BASIC_MOB_FLEEING = TRUE, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/ours_or_smaller, // Hunt mobs our size + BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/larger, // Run away from mobs bigger than we are ) idle_behavior = /datum/idle_behavior/idle_random_walk diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spiders.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spiders.dm index 6189a084a5c2..d436e69b1af9 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spiders.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spiders.dm @@ -225,7 +225,6 @@ heal_brute = 15,\ heal_burn = 15,\ heal_time = 3 SECONDS,\ - self_targetting = HEALING_TOUCH_SELF_ONLY,\ interaction_key = DOAFTER_SOURCE_SPIDER,\ valid_targets_typecache = typecacheof(list(/mob/living/basic/spider/growing/young/tangle, /mob/living/basic/spider/giant/tangle)),\ extra_checks = CALLBACK(src, PROC_REF(can_mend)),\ @@ -497,7 +496,6 @@ AddComponent(/datum/component/healing_touch,\ heal_brute = 45,\ heal_burn = 45,\ - self_targetting = HEALING_TOUCH_SELF_ONLY,\ interaction_key = DOAFTER_SOURCE_SPIDER,\ valid_targets_typecache = typecacheof(list(/mob/living/basic/spider/giant/hunter/flesh)),\ extra_checks = CALLBACK(src, PROC_REF(can_mend)),\ diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider.dm b/code/modules/mob/living/basic/space_fauna/spider/spider.dm index 91b823eac1da..817520fdd308 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spider.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spider.dm @@ -48,7 +48,7 @@ /mob/living/basic/spider/Initialize(mapload) . = ..() - ADD_TRAIT(src, TRAIT_WEB_SURFER, INNATE_TRAIT) + add_traits(list(TRAIT_WEB_SURFER, TRAIT_FENCE_CLIMBER), INNATE_TRAIT) AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) AddElement(/datum/element/nerfed_pulling, GLOB.typecache_general_bad_things_to_easily_move) AddElement(/datum/element/prevent_attacking_of_types, GLOB.typecache_general_bad_hostile_attack_targets, "this tastes awful!") diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm index 28a543be3acf..01477cd84354 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm @@ -5,7 +5,6 @@ button_icon_state = "lay_eggs" background_icon_state = "bg_alien" overlay_icon_state = "bg_alien_border" - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED cooldown_time = 0 melee_cooldown_time = 0 shared_cooldown = NONE diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm index b7062a2dc17c..d4f587c93864 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm @@ -6,7 +6,6 @@ button_icon_state = "lay_web" background_icon_state = "bg_alien" overlay_icon_state = "bg_alien_border" - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED cooldown_time = 0 SECONDS melee_cooldown_time = 0 shared_cooldown = NONE diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm index 536f09cef8d1..e7771f075a87 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm @@ -8,7 +8,6 @@ overlay_icon_state = "bg_alien_border" button_icon = 'icons/mob/actions/actions_animal.dmi' button_icon_state = "wrap_0" - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED click_to_activate = TRUE ranged_mousepointer = 'icons/effects/mouse_pointers/wrap_target.dmi' shared_cooldown = NONE diff --git a/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm index a09107aebf41..56ffded6d3cd 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm @@ -59,8 +59,7 @@ /// Opportunistically hops in and out of vents, if it can find one. We aren't interested in attacking due to how weak we are, we gotta be quick and hidey. /datum/ai_controller/basic_controller/spiderling blackboard = list( - BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic/of_size/larger, // Run away from mobs bigger than we are - BB_BASIC_MOB_FLEEING = TRUE, + BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/larger, // Run away from mobs bigger than we are BB_VENTCRAWL_COOLDOWN = 20 SECONDS, // enough time to get splatted while we're out in the open. BB_TIME_TO_GIVE_UP_ON_VENT_PATHING = 30 SECONDS, ) diff --git a/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm index 57b9da542b70..43a31df366be 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm @@ -29,7 +29,8 @@ /// Used by all young spiders if they ever appear. /datum/ai_controller/basic_controller/young_spider blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_FLEE_DISTANCE = 6, ) ai_traits = STOP_MOVING_WHEN_PULLED @@ -46,6 +47,3 @@ /datum/ai_planning_subtree/find_unwebbed_turf, /datum/ai_planning_subtree/spin_web, ) - -/datum/ai_behavior/run_away_from_target/young_spider - run_distance = 6 diff --git a/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider_subtypes.dm b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider_subtypes.dm index 4e100fc0af49..8eaa92b58fc2 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider_subtypes.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider_subtypes.dm @@ -122,7 +122,6 @@ heal_brute = 10,\ heal_burn = 10,\ heal_time = 3 SECONDS,\ - self_targetting = HEALING_TOUCH_SELF_ONLY,\ interaction_key = DOAFTER_SOURCE_SPIDER,\ valid_targets_typecache = typecacheof(list(/mob/living/basic/spider/growing/young/tangle, /mob/living/basic/spider/giant/tangle)),\ extra_checks = CALLBACK(src, PROC_REF(can_mend)),\ diff --git a/code/modules/mob/living/basic/space_fauna/statue/statue.dm b/code/modules/mob/living/basic/space_fauna/statue/statue.dm index 938914a7f64a..f906044cdab6 100644 --- a/code/modules/mob/living/basic/space_fauna/statue/statue.dm +++ b/code/modules/mob/living/basic/space_fauna/statue/statue.dm @@ -27,6 +27,7 @@ attack_verb_simple = "claw" attack_sound = 'sound/hallucinations/growl1.ogg' attack_vis_effect = ATTACK_EFFECT_CLAW + melee_attack_cooldown = 1 SECONDS faction = list(FACTION_STATUE) speak_emote = list("screams") @@ -144,14 +145,14 @@ /datum/ai_controller/basic_controller/statue blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_LOW_PRIORITY_HUNTING_TARGET = null, // lights ) ai_movement = /datum/ai_movement/basic_avoidance planning_subtrees = list( /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/statue, + /datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures, ) @@ -160,3 +161,20 @@ /datum/ai_behavior/basic_melee_attack/statue action_cooldown = 1 SECONDS + +/mob/living/basic/statue/frosty + name = "Frosty" + desc = "Just a snowman. Just a snowman. Oh god, it's just a snowman." + icon_dead = "snowman" + icon_living = "snowman" + icon_state = "snowman" + health = 5000 + maxHealth = 5000 + melee_damage_lower = 65 + melee_damage_upper = 65 + faction = list("statue","mining") + +/mob/living/basic/statue/frosty/Initialize(mapload) + . = ..() + var/static/list/death_loot = list(/obj/item/dnainjector/geladikinesis) + AddElement(/datum/element/death_drops, death_loot) diff --git a/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm b/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm new file mode 100644 index 000000000000..02fd9ed1bcf1 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm @@ -0,0 +1,102 @@ +/// A nasty little robotic bug that dusts people on attack. Jeepers. This should be a very, very, very rare spawn. +/mob/living/basic/supermatter_spider + name = "supermatter spider" + desc= "A sliver of supermatter placed upon a robotically enhanced pedestal." + + icon = 'icons/mob/simple/smspider.dmi' + icon_state = "smspider" + icon_living = "smspider" + icon_dead = "smspider_dead" + + gender = NEUTER + mob_biotypes = MOB_BUG|MOB_ROBOTIC + speak_emote = list("vibrates") + + + attack_verb_continuous = "slices" + attack_verb_simple = "slice" + attack_sound = 'sound/effects/supermatter.ogg' + attack_vis_effect = ATTACK_EFFECT_CLAW + + maxHealth = 10 + health = 10 + minimum_survivable_temperature = TCMB + maximum_survivable_temperature = T0C + 1250 + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + death_message = "falls to the ground, its shard dulling to a miserable grey!" + + faction = list(FACTION_HOSTILE) + + // Gold, supermatter tinted + lighting_cutoff_red = 30 + lighting_cutoff_green = 30 + lighting_cutoff_blue = 10 + + ai_controller = /datum/ai_controller/basic_controller/supermatter_spider + + /// If we successfully dust something, should we die? + var/single_use = TRUE + +/mob/living/basic/supermatter_spider/Initialize(mapload) + . = ..() + AddComponent(/datum/component/swarming) + + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) + + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_attack)) + +/// Proc that we call on attacking something to dust 'em. +/mob/living/basic/supermatter_spider/proc/on_attack(mob/living/basic/source, atom/target) + SIGNAL_HANDLER + + if(isliving(target)) + var/mob/living/victim = target + victim.investigate_log("has been dusted by [src].", INVESTIGATE_DEATHS) + dust_feedback(target) + victim.dust() + if(single_use) + death() + return COMPONENT_HOSTILE_NO_ATTACK + + if(!isturf(target)) + dust_feedback(target) + qdel(target) + if(single_use) + death() + return COMPONENT_HOSTILE_NO_ATTACK + +/// Simple proc that plays the supermatter dusting sound and sends a visible message. +/mob/living/basic/supermatter_spider/proc/dust_feedback(atom/target) + playsound(get_turf(src), 'sound/effects/supermatter.ogg', 10, TRUE) + visible_message(span_danger("[src] knocks into [target], turning [target.p_them()] to dust in a brilliant flash of light!")) + +/mob/living/basic/supermatter_spider/overcharged + name = "overcharged supermatter spider" + desc = "A sliver of overcharged supermatter placed upon a robotically enhanced pedestal. This one seems especially dangerous." + icon_state = "smspideroc" + icon_living = "smspideroc" + maxHealth = 25 + health = 25 + single_use = FALSE + +/datum/ai_controller/basic_controller/supermatter_spider + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/random_speech/supermatter_spider, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/datum/ai_planning_subtree/random_speech/supermatter_spider + speech_chance = 7 + emote_hear = list("clinks", "clanks") + emote_see = list("vibrates") diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/fugu_gland.dm b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/fugu_gland.dm index 485343c0a496..72226ec3e5de 100644 --- a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/fugu_gland.dm +++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/fugu_gland.dm @@ -15,7 +15,7 @@ if(fugu_blacklist) return fugu_blacklist = typecacheof(list( - /mob/living/simple_animal/hostile/guardian, + /mob/living/basic/guardian, )) /obj/item/fugu_gland/afterattack(atom/target, mob/user, proximity_flag) @@ -32,6 +32,7 @@ return ADD_TRAIT(animal, TRAIT_FUGU_GLANDED, type) + animal.AddComponent(/datum/component/seethrough_mob) animal.maxHealth *= 1.5 animal.health = min(animal.maxHealth, animal.health * 1.5) animal.melee_damage_lower = max((animal.melee_damage_lower * 2), 10) diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm index d459648892b0..a9e2b538bdd7 100644 --- a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm +++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm @@ -65,7 +65,7 @@ fugu.melee_damage_upper = 20 fugu.status_flags |= GODMODE fugu.obj_damage = 60 - fugu.ai_controller.set_blackboard_key(BB_BASIC_MOB_FLEEING, FALSE) + fugu.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) fugu.ai_controller.CancelActions() /datum/status_effect/inflated/on_remove() @@ -84,7 +84,7 @@ if (fugu.stat != DEAD) fugu.icon_state = "Fugu0" fugu.obj_damage = 0 - fugu.ai_controller.set_blackboard_key(BB_BASIC_MOB_FLEEING, TRUE) + fugu.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) fugu.ai_controller.CancelActions() /// Remove status effect if we die diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm index f632a38dc1b2..1b4e2cdd6201 100644 --- a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm @@ -1,10 +1,7 @@ -#define WUMBO_ATTACK_COOLDOWN 2.5 SECONDS - /// Cowardly when small, aggressive when big. Tries to transform whenever possible. /datum/ai_controller/basic_controller/wumborian_fugu blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), - BB_BASIC_MOB_FLEEING = TRUE, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance @@ -15,23 +12,15 @@ /datum/ai_planning_subtree/targeted_mob_ability/inflate, /datum/ai_planning_subtree/flee_target, /datum/ai_planning_subtree/attack_obstacle_in_path/wumborian_fugu, - /datum/ai_planning_subtree/basic_melee_attack_subtree/wumborian_fugu, + /datum/ai_planning_subtree/basic_melee_attack_subtree, ) -/datum/ai_planning_subtree/basic_melee_attack_subtree/wumborian_fugu - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/wumborian_fugu - -/datum/ai_behavior/basic_melee_attack/wumborian_fugu - action_cooldown = WUMBO_ATTACK_COOLDOWN - /datum/ai_planning_subtree/attack_obstacle_in_path/wumborian_fugu attack_behaviour = /datum/ai_behavior/attack_obstructions/wumborian_fugu /datum/ai_behavior/attack_obstructions/wumborian_fugu can_attack_turfs = TRUE - action_cooldown = WUMBO_ATTACK_COOLDOWN + action_cooldown = 2.5 SECONDS /datum/ai_planning_subtree/targeted_mob_ability/inflate ability_key = BB_FUGU_INFLATE - -#undef WUMBO_ATTACK_COOLDOWN diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm index 76334fe22fee..d963bdf41016 100644 --- a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm +++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm @@ -29,6 +29,7 @@ melee_damage_upper = 0 attack_sound = 'sound/weapons/punch1.ogg' attack_vis_effect = ATTACK_EFFECT_BITE + melee_attack_cooldown = 2.5 SECONDS attack_verb_continuous = "chomps" attack_verb_simple = "chomp" friendly_verb_continuous = "floats near" @@ -49,7 +50,9 @@ /mob/living/basic/wumborian_fugu/Initialize(mapload) . = ..() - AddElement(/datum/element/death_drops, loot = list(/obj/item/fugu_gland)) + AddComponent(/datum/component/seethrough_mob) + var/static/list/death_loot = list(/obj/item/fugu_gland) + AddElement(/datum/element/death_drops, death_loot) add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE), ROUNDSTART_TRAIT) expand = new(src) expand.Grant(src) diff --git a/code/modules/mob/living/basic/syndicate/syndicate_ai.dm b/code/modules/mob/living/basic/syndicate/syndicate_ai.dm deleted file mode 100644 index 9e2c86e36023..000000000000 --- a/code/modules/mob/living/basic/syndicate/syndicate_ai.dm +++ /dev/null @@ -1,71 +0,0 @@ -/datum/ai_controller/basic_controller/syndicate - blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/syndicate() - ) - - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path/syndicate, - /datum/ai_planning_subtree/basic_melee_attack_subtree/syndicate - ) - -/datum/targetting_datum/basic/syndicate - stat_attack = HARD_CRIT - -/datum/ai_planning_subtree/basic_melee_attack_subtree/syndicate - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/syndicate - -/datum/ai_behavior/basic_melee_attack/syndicate - action_cooldown = 1.2 SECONDS - -/datum/ai_planning_subtree/attack_obstacle_in_path/syndicate - attack_behaviour = /datum/ai_behavior/attack_obstructions/syndicate - -/datum/ai_behavior/attack_obstructions/syndicate - action_cooldown = 1.2 SECONDS - -/datum/ai_controller/basic_controller/syndicate/ranged - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/syndicate - ) -/datum/ai_planning_subtree/basic_ranged_attack_subtree/syndicate - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/syndicate - -/datum/ai_behavior/basic_ranged_attack/syndicate - required_distance = 5 - -/datum/ai_controller/basic_controller/syndicate/ranged/burst - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/syndicate_burst - ) - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/syndicate_burst - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/syndicate_burst - -/datum/ai_behavior/basic_ranged_attack/syndicate_burst - shots = 3 - action_cooldown = 3 SECONDS - -/datum/ai_controller/basic_controller/syndicate/ranged/shotgunner - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/syndicate_shotgun - ) - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/syndicate_shotgun - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/syndicate_shotgun - -/datum/ai_behavior/basic_ranged_attack/syndicate_shotgun - shots = 2 - burst_interval = 0.6 SECONDS - action_cooldown = 3 SECONDS - required_distance = 1 - -/datum/ai_controller/basic_controller/syndicate/viscerator - blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic() - ) diff --git a/code/modules/mob/living/basic/trader/trader.dm b/code/modules/mob/living/basic/trader/trader.dm new file mode 100644 index 000000000000..52b1ab5ab711 --- /dev/null +++ b/code/modules/mob/living/basic/trader/trader.dm @@ -0,0 +1,78 @@ +/mob/living/basic/trader + name = "Trader" + desc = "Come buy some!" + unique_name = FALSE + icon = 'icons/mob/simple/simple_human.dmi' + maxHealth = 200 + health = 200 + melee_damage_lower = 10 + melee_damage_upper = 10 + attack_verb_continuous = "punches" + attack_verb_simple = "punch" + attack_sound = 'sound/weapons/punch1.ogg' + basic_mob_flags = DEL_ON_DEATH + unsuitable_atmos_damage = 2.5 + move_resist = MOVE_FORCE_STRONG + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + sentience_type = SENTIENCE_HUMANOID + speed = 0 + + ai_controller = /datum/ai_controller/basic_controller/trader + + ///Sound used when item sold/bought + var/sell_sound = 'sound/effects/cashregister.ogg' + ///The currency name + var/currency_name = "credits" + ///The spawner we use to create our look + var/spawner_path = /obj/effect/mob_spawn/corpse/human/generic_assistant + ///Our species to create our look + var/species_path = /datum/species/human + ///The loot we drop when we die + var/loot = list(/obj/effect/mob_spawn/corpse/human/generic_assistant) + ///Casing used to shoot during retaliation + var/ranged_attack_casing = /obj/item/ammo_casing/shotgun/buckshot + ///Sound to make while doing a retalitory attack + var/ranged_attack_sound = 'sound/weapons/gun/pistol/shot.ogg' + ///Weapon path, for visuals + var/held_weapon_visual = /obj/item/gun/ballistic/shotgun + + ///Type path for the trader datum to use for retrieving the traders wares, speech, etc + var/trader_data_path = /datum/trader_data + + +/mob/living/basic/trader/Initialize(mapload) + . = ..() + apply_dynamic_human_appearance(src, species_path = species_path, mob_spawn_path = spawner_path, r_hand = held_weapon_visual) + + var/datum/trader_data/trader_data = new trader_data_path + AddComponent(/datum/component/trader, trader_data = trader_data) + AddComponent(/datum/component/ranged_attacks, casing_type = ranged_attack_casing, projectile_sound = ranged_attack_sound, cooldown_time = 3 SECONDS) + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/ai_swap_combat_mode, BB_BASIC_MOB_CURRENT_TARGET, string_list(trader_data.say_phrases[TRADER_BATTLE_START_PHRASE]), string_list(trader_data.say_phrases[TRADER_BATTLE_END_PHRASE])) + if(LAZYLEN(loot)) + loot = string_list(loot) + AddElement(/datum/element/death_drops, loot) + + var/datum/action/setup_shop/setup_shop = new (src, trader_data.shop_spot_type, trader_data.sign_type, trader_data.sell_sound, trader_data.say_phrases[TRADER_SHOP_OPENING_PHRASE]) + setup_shop.Grant(src) + ai_controller.set_blackboard_key(BB_SETUP_SHOP, setup_shop) + +/mob/living/basic/trader/mrbones + name = "Mr. Bones" + desc = "A skeleton merchant, he seems very humerus." + speak_emote = list("rattles") + speech_span = SPAN_SANS + mob_biotypes = MOB_UNDEAD|MOB_HUMANOID + icon_state = "mrbones" + gender = MALE + + ai_controller = /datum/ai_controller/basic_controller/trader/jumpscare + + sell_sound = 'sound/voice/hiss2.ogg' + species_path = /datum/species/skeleton + spawner_path = /obj/effect/mob_spawn/corpse/human/skeleton/mrbones + loot = list(/obj/effect/decal/remains/human) + ranged_attack_casing = /obj/item/ammo_casing/energy/bolt/halloween + held_weapon_visual = /obj/item/gun/ballistic/revolver + + trader_data_path = /datum/trader_data/mr_bones diff --git a/code/modules/mob/living/basic/trader/trader_actions.dm b/code/modules/mob/living/basic/trader/trader_actions.dm new file mode 100644 index 000000000000..ded9fbd46d0d --- /dev/null +++ b/code/modules/mob/living/basic/trader/trader_actions.dm @@ -0,0 +1,72 @@ +/datum/action/setup_shop + name = "Setup shop" + desc = "Summons a wacky sales sign, and a comfy sitting spot to conduct your business from." + button_icon = 'icons/mob/actions/actions_trader.dmi' + button_icon_state = "setup_shop" + /// The shop spot + var/datum/weakref/shop_spot_ref + /// The server this console is connected to. + var/datum/weakref/sign_ref + /// The type of the chair we sit on + var/shop_spot_type + /// The type of our advertising sign + var/sign_type + /// The sound we make when we summon our shop gear + var/shop_sound + /// Lines we say when we open our shop + var/opening_lines + +/datum/action/setup_shop/IsAvailable(feedback = FALSE) + . = ..() + if (!.) + return FALSE + if(shop_spot_ref?.resolve()) + if(feedback) + owner.balloon_alert(owner, "already set up!") + return FALSE + return TRUE + +/datum/action/setup_shop/New(Target, shop_spot_type = /obj/structure/chair/plastic, sign_type = /obj/structure/trader_sign, sell_sound = 'sound/effects/cashregister.ogg', opening_lines = list("Welcome to my shop, friend!")) + . = ..() + + src.shop_spot_type = shop_spot_type + src.sign_type = sign_type + src.shop_sound = sell_sound + src.opening_lines = opening_lines + +/datum/action/setup_shop/Trigger(trigger_flags) + . = ..() + if(!.) + return + + owner.say(pick(opening_lines)) + var/obj/shop_spot = new shop_spot_type(owner.loc) + shop_spot.dir = owner.dir + shop_spot_ref = WEAKREF(shop_spot) + owner.ai_controller?.set_blackboard_key(BB_SHOP_SPOT, shop_spot) + + playsound(owner, shop_sound, 50, TRUE) + + var/turf/sign_turf + + sign_turf = try_find_valid_spot(owner.loc, turn(shop_spot.dir, -90)) + if(isnull(sign_turf)) //No space to my left, lets try right + sign_turf = try_find_valid_spot(owner.loc, turn(shop_spot.dir, 90)) + + if(isnull(sign_turf)) + return + + var/obj/sign = sign_ref?.resolve() + if(QDELETED(sign)) + var/obj/new_sign = new sign_type(sign_turf) + sign_ref = WEAKREF(sign) + do_sparks(3, FALSE, new_sign) + else + do_teleport(sign,sign_turf) + +///Look for a spot we can place our sign on +/datum/action/setup_shop/proc/try_find_valid_spot(origin_turf, direction_to_check) + var/turf/sign_turf = get_step(origin_turf, direction_to_check) + if(sign_turf && !isgroundlessturf(sign_turf) && !isclosedturf(sign_turf) && !sign_turf.is_blocked_turf()) + return sign_turf + return null diff --git a/code/modules/mob/living/basic/trader/trader_ai.dm b/code/modules/mob/living/basic/trader/trader_ai.dm new file mode 100644 index 000000000000..5f447ab3229a --- /dev/null +++ b/code/modules/mob/living/basic/trader/trader_ai.dm @@ -0,0 +1,95 @@ +/datum/ai_controller/basic_controller/trader + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk/not_while_on_target/trader + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/trader, + /datum/ai_planning_subtree/prepare_travel_to_destination/trader, + /datum/ai_planning_subtree/travel_to_point/and_clear_target, + /datum/ai_planning_subtree/setup_shop, + ) + +/datum/ai_controller/basic_controller/trader/jumpscare + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/trader, + /datum/ai_planning_subtree/prepare_travel_to_destination/trader, + /datum/ai_planning_subtree/travel_to_point/and_clear_target, + /datum/ai_planning_subtree/setup_shop/jumpscare, + ) + +/datum/ai_planning_subtree/basic_ranged_attack_subtree/trader + ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/trader + +/datum/ai_behavior/basic_ranged_attack/trader + action_cooldown = 3 SECONDS + avoid_friendly_fire = TRUE + +///Subtree to find our very first customer and set up our shop after walking right into their face +/datum/ai_planning_subtree/setup_shop + ///What do we do in order to offer our deals? + var/datum/ai_behavior/setup_shop/setup_shop_behavior = /datum/ai_behavior/setup_shop + +/datum/ai_planning_subtree/setup_shop/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + + //If we don't have our ability, return + if(!controller.blackboard_key_exists(BB_SETUP_SHOP)) + return + + //If we already have a shop spot, return + if(controller.blackboard_key_exists(BB_SHOP_SPOT)) + return + + //If we don't have a costurmer to greet, look for one + if(!controller.blackboard_key_exists(BB_FIRST_CUSTOMER)) + controller.queue_behavior(/datum/ai_behavior/find_and_set/conscious_person, BB_FIRST_CUSTOMER, /mob/living/carbon/human) + return + + //We have our first customer, time to tell them about incredible deals + controller.queue_behavior(setup_shop_behavior, BB_FIRST_CUSTOMER) + return SUBTREE_RETURN_FINISH_PLANNING + +///The ai will create a shop the moment they see a potential costumer +/datum/ai_behavior/setup_shop + +/datum/ai_behavior/setup_shop/setup(datum/ai_controller/controller, target_key) + var/obj/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/ai_behavior/setup_shop/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + + //We lost track of our costumer or our ability, abort + if(!controller.blackboard_key_exists(target_key) || !controller.blackboard_key_exists(BB_SETUP_SHOP)) + finish_action(controller, FALSE, target_key) + return + + var/datum/action/setup_shop/shop = controller.blackboard[BB_SETUP_SHOP] + shop.Trigger() + + controller.clear_blackboard_key(BB_FIRST_CUSTOMER) + + finish_action(controller, TRUE, target_key) + +/datum/idle_behavior/idle_random_walk/not_while_on_target/trader + target_key = BB_SHOP_SPOT + +///Version of setup show where the trader will run at you to assault you with incredible deals +/datum/ai_planning_subtree/setup_shop/jumpscare + setup_shop_behavior = /datum/ai_behavior/setup_shop/jumpscare + +/datum/ai_behavior/setup_shop/jumpscare + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH + +/datum/ai_behavior/setup_shop/jumpscare/setup(datum/ai_controller/controller, target_key) + . = ..() + if(.) + set_movement_target(controller, controller.blackboard[target_key]) + +/datum/ai_behavior/setup_shop/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/trader/trader_data.dm b/code/modules/mob/living/basic/trader/trader_data.dm new file mode 100644 index 000000000000..9762dc02be50 --- /dev/null +++ b/code/modules/mob/living/basic/trader/trader_data.dm @@ -0,0 +1,156 @@ +///Used to contain the traders initial wares, and speech +/datum/trader_data + + ///The item that marks the shopkeeper will sit on + var/shop_spot_type = /obj/structure/chair/plastic + ///The sign that will greet the customers + var/sign_type = /obj/structure/trader_sign + ///Sound used when item sold/bought + var/sell_sound = 'sound/effects/cashregister.ogg' + ///The currency name + var/currency_name = "credits" + ///The initial products that the trader offers + var/list/initial_products = list( + /obj/item/food/burger/ghost = list(PAYCHECK_CREW * 4, INFINITY), + ) + ///The initial products that the trader buys + var/list/initial_wanteds = list( + /obj/item/ectoplasm = list(PAYCHECK_CREW * 2, INFINITY, ""), + ) + ///The speech data of the trader + var/list/say_phrases = list( + ITEM_REJECTED_PHRASE = list( + "Sorry, I'm not a fan of anything you're showing me. Give me something better and we'll talk.", + ), + ITEM_SELLING_CANCELED_PHRASE = list( + "What a shame, tell me if you changed your mind.", + ), + ITEM_SELLING_ACCEPTED_PHRASE = list( + "Pleasure doing business with you.", + ), + INTERESTED_PHRASE = list( + "Hey, you've got an item that interests me, I'd like to buy it, I'll give you some cash for it, deal?", + ), + BUY_PHRASE = list( + "Pleasure doing business with you.", + ), + NO_CASH_PHRASE = list( + "Sorry adventurer, I can't give credit! Come back when you're a little mmmmm... richer!", + ), + NO_STOCK_PHRASE = list( + "Sorry adventurer, but that item is not in stock at the moment.", + ), + NOT_WILLING_TO_BUY_PHRASE = list( + "I don't want to buy that item for the time being, check back another time.", + ), + ITEM_IS_WORTHLESS_PHRASE = list( + "This item seems to be worthless on a closer look, I won't buy this.", + ), + TRADER_HAS_ENOUGH_ITEM_PHRASE = list( + "I already bought enough of this for the time being.", + ), + TRADER_LORE_PHRASE = list( + "Hello! I am the test trader.", + "Oooooooo~!", + ), + TRADER_NOT_BUYING_ANYTHING = list( + "I'm currently buying nothing at the moment.", + ), + TRADER_NOT_SELLING_ANYTHING = list( + "I'm currently selling nothing at the moment.", + ), + TRADER_BATTLE_START_PHRASE = list( + "Thief!", + ), + TRADER_BATTLE_END_PHRASE = list( + "That is a discount I call death.", + ), + TRADER_SHOP_OPENING_PHRASE = list( + "Welcome to my shop, friend!", + ), + ) + +/** + * Depending on the passed parameter/override, returns a randomly picked string out of a list + * + * Do note when overriding this argument, you will need to ensure pick(the list) doesn't get supplied with a list of zero length + * Arguments: + * * say_text - (String) a define that matches the key of a entry in say_phrases + */ +/datum/trader_data/proc/return_trader_phrase(say_text) + if(!length(say_phrases[say_text])) + return + return pick(say_phrases[say_text]) + +/datum/trader_data/mr_bones + shop_spot_type = /obj/structure/chair/wood/wings + sign_type = /obj/structure/trader_sign/mrbones + sell_sound = 'sound/voice/hiss2.ogg' + + initial_products = list( + /obj/item/clothing/head/helmet/skull = list(PAYCHECK_CREW * 3, INFINITY), + /obj/item/clothing/mask/bandana/skull/black = list(PAYCHECK_CREW, INFINITY), + /obj/item/food/cookie/sugar/spookyskull = list(PAYCHECK_CREW * 0.2, INFINITY), + /obj/item/instrument/trombone/spectral = list(PAYCHECK_CREW * 200, INFINITY), + /obj/item/shovel/serrated = list(PAYCHECK_CREW * 3, INFINITY), + ) + + initial_wanteds = list( + /obj/item/reagent_containers/condiment/milk = list(PAYCHECK_CREW * 20, INFINITY, ""), + /obj/item/stack/sheet/bone = list(PAYCHECK_CREW * 8.4, INFINITY, ", per sheet of bone"), + ) + + say_phrases = list( + ITEM_REJECTED_PHRASE = list( + "Sorry, I'm not a fan of anything you're showing me. Give me something better and we'll talk.", + ), + ITEM_SELLING_CANCELED_PHRASE = list( + "What a shame, tell me if you changed your mind.", + ), + ITEM_SELLING_ACCEPTED_PHRASE = list( + "Pleasure doing business with you.", + ), + INTERESTED_PHRASE = list( + "Hey, you've got an item that interests me, I'd like to buy it, I'll give you some cash for it, deal?", + ), + BUY_PHRASE = list( + "Bone appetit!", + ), + NO_CASH_PHRASE = list( + "Sorry adventurer, I can't give credit! Come back when you're a little mmmmm... richer!", + ), + NO_STOCK_PHRASE = list( + "Sorry adventurer, but that item is not in stock at the moment.", + ), + NOT_WILLING_TO_BUY_PHRASE = list( + "I don't want to buy that item for the time being, check back another time.", + ), + ITEM_IS_WORTHLESS_PHRASE = list( + "This item seems to be worthless on a closer look, I won't buy this.", + ), + TRADER_HAS_ENOUGH_ITEM_PHRASE = list( + "I already bought enough of this for the time being.", + ), + TRADER_LORE_PHRASE = list( + "Hello, I am Mr. Bones!", + "The ride never ends!", + "I'd really like a refreshing carton of milk!", + "I'm willing to play big prices for BONES! Need materials to make merch, eh?", + "It's a beautiful day outside. Birds are singing, Flowers are blooming... On days like these, kids like you... Should be buying my wares!", + ), + TRADER_NOT_BUYING_ANYTHING = list( + "I'm currently buying nothing at the moment.", + ), + TRADER_NOT_SELLING_ANYTHING = list( + "I'm currently selling nothing at the moment.", + ), + TRADER_BATTLE_START_PHRASE = list( + "The ride ends for you!", + ), + TRADER_BATTLE_END_PHRASE = list( + "Mr. Bones never misses!", + ), + TRADER_SHOP_OPENING_PHRASE = list( + "My wild ride is open!", + ), + ) diff --git a/code/modules/mob/living/basic/trader/trader_items.dm b/code/modules/mob/living/basic/trader/trader_items.dm new file mode 100644 index 000000000000..173e33d4c2f6 --- /dev/null +++ b/code/modules/mob/living/basic/trader/trader_items.dm @@ -0,0 +1,36 @@ +///Sale signs +/obj/structure/trader_sign + name = "holographic store sign" + desc = "A holographic sign that promises great deals." + icon = 'icons/obj/trader_signs.dmi' + icon_state = "faceless" + anchored = TRUE + armor_type = /datum/armor/trader_sign + max_integrity = 15 + layer = FLY_LAYER + +/datum/armor/trader_sign + bullet = 50 + laser = 50 + energy = 50 + fire = 20 + acid = 20 + +/obj/structure/trader_sign/Initialize(mapload) + . = ..() + add_overlay("sign") + makeHologram() + + +/obj/structure/trader_sign/mrbones + icon_state = "mrbones" + + +///Spawners for outfits +/obj/effect/mob_spawn/corpse/human/skeleton/mrbones + mob_species = /datum/species/skeleton + outfit = /datum/outfit/mrbonescorpse + +/datum/outfit/mrbonescorpse + name = "Mr Bones' Corpse" + head = /obj/item/clothing/head/hats/tophat diff --git a/code/modules/mob/living/basic/tree.dm b/code/modules/mob/living/basic/tree.dm index f26685c1cd4a..5d20a877e9f7 100644 --- a/code/modules/mob/living/basic/tree.dm +++ b/code/modules/mob/living/basic/tree.dm @@ -54,8 +54,10 @@ /mob/living/basic/tree/Initialize(mapload) . = ..() + AddComponent(/datum/component/seethrough_mob) AddElement(/datum/element/swabable, CELL_LINE_TABLE_PINE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) - AddElement(/datum/element/death_drops, list(/obj/item/stack/sheet/mineral/wood)) + var/list/death_loot = string_list(list(/obj/item/stack/sheet/mineral/wood)) + AddElement(/datum/element/death_drops, death_loot) AddComponent(/datum/component/aggro_emote, emote_list = string_list(list("growls")), emote_chance = 20) /mob/living/basic/tree/Life(seconds_per_tick = SSMOBS_DT, times_fired) @@ -72,7 +74,7 @@ our_turf.air.gases[/datum/gas/carbon_dioxide][MOLES] -= amt our_turf.atmos_spawn_air("o2=[amt]") -/mob/living/basic/tree/melee_attack(atom/target, list/modifiers) +/mob/living/basic/tree/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE) . = ..() if(!.) @@ -98,19 +100,13 @@ /datum/ai_controller/basic_controller/tree blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking planning_subtrees = list( /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/tree, + /datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/random_speech/tree, ) - -/datum/ai_planning_subtree/basic_melee_attack_subtree/tree - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/tree - -/datum/ai_behavior/basic_melee_attack/tree - action_cooldown = 2 SECONDS diff --git a/code/modules/mob/living/basic/trooper/nanotrasen.dm b/code/modules/mob/living/basic/trooper/nanotrasen.dm new file mode 100644 index 000000000000..6da3d2d9fb21 --- /dev/null +++ b/code/modules/mob/living/basic/trooper/nanotrasen.dm @@ -0,0 +1,99 @@ +/// Nanotrasen Private Security forces +/mob/living/basic/trooper/nanotrasen + name = "\improper Nanotrasen Private Security Officer" + desc = "An officer of Nanotrasen's private security force. Seems rather unpleased to meet you." + speed = 0 + melee_damage_lower = 10 + melee_damage_upper = 15 + faction = list(ROLE_DEATHSQUAD) + loot = list(/obj/effect/mob_spawn/corpse/human/nanotrasensoldier) + mob_spawner = /obj/effect/mob_spawn/corpse/human/nanotrasensoldier + +/// A variant that calls for reinforcements on spotting a target +/mob/living/basic/trooper/nanotrasen/screaming + ai_controller = /datum/ai_controller/basic_controller/trooper/calls_reinforcements + +/mob/living/basic/trooper/nanotrasen/ranged + ai_controller = /datum/ai_controller/basic_controller/trooper/ranged + r_hand = /obj/item/gun/ballistic/automatic/pistol/m1911 + /// Type of bullet we use + var/casingtype = /obj/item/ammo_casing/c45 + /// Sound to play when firing weapon + var/projectilesound = 'sound/weapons/gun/pistol/shot_alt.ogg' + /// number of burst shots + var/burst_shots + /// Time between taking shots + var/ranged_cooldown = 1 SECONDS + +/mob/living/basic/trooper/nanotrasen/ranged/Initialize(mapload) + . = ..() + AddComponent(\ + /datum/component/ranged_attacks,\ + casing_type = casingtype,\ + projectile_sound = projectilesound,\ + cooldown_time = ranged_cooldown,\ + burst_shots = burst_shots,\ + ) + if (ranged_cooldown <= 1 SECONDS) + AddComponent(/datum/component/ranged_mob_full_auto) + +/mob/living/basic/trooper/nanotrasen/ranged/smg + ai_controller = /datum/ai_controller/basic_controller/trooper/ranged/burst + casingtype = /obj/item/ammo_casing/c46x30mm + projectilesound = 'sound/weapons/gun/smg/shot.ogg' + r_hand = /obj/item/gun/ballistic/automatic/wt550 + burst_shots = 3 + ranged_cooldown = 3 SECONDS + +/mob/living/basic/trooper/nanotrasen/ranged/assault + name = "Nanotrasen Assault Officer" + desc = "Nanotrasen Assault Officer. Contact CentCom if you saw him on your station. Prepare to die, if you've been found near Syndicate property." + + casingtype = /obj/item/ammo_casing/a223/weak + burst_shots = 4 + ranged_cooldown = 3 SECONDS + projectilesound = 'sound/weapons/gun/smg/shot.ogg' + r_hand = /obj/item/gun/ballistic/automatic/ar + loot = list(/obj/effect/mob_spawn/corpse/human/nanotrasenassaultsoldier) + mob_spawner = /obj/effect/mob_spawn/corpse/human/nanotrasenassaultsoldier + +/mob/living/basic/trooper/nanotrasen/ranged/elite + name = "Nanotrasen Elite Assault Officer" + desc = "Pray for your life, syndicate. Run while you can." + maxHealth = 150 + health = 150 + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + unsuitable_cold_damage = 0 + casingtype = /obj/item/ammo_casing/energy/laser + burst_shots = 3 + projectilesound = 'sound/weapons/laser.ogg' + ranged_cooldown = 5 SECONDS + faction = list(ROLE_DEATHSQUAD) + loot = list(/obj/effect/gibspawner/human) + mob_spawner = /obj/effect/mob_spawn/corpse/human/nanotrasenelitesoldier + r_hand = /obj/item/gun/energy/pulse/carbine/lethal + +/// A more peaceful variant that will only attack when attacked, or when another Nanotrasen officer calls for help. +/mob/living/basic/trooper/nanotrasen/peaceful + desc = "An officer of Nanotrasen's private security force." + ai_controller = /datum/ai_controller/basic_controller/trooper/peaceful + +/mob/living/basic/trooper/nanotrasen/peaceful/Initialize(mapload) + . = ..() + var/datum/callback/retaliate_callback = CALLBACK(src, PROC_REF(ai_retaliate_behaviour)) + AddComponent(/datum/component/ai_retaliate_advanced, retaliate_callback) + +/mob/living/basic/trooper/nanotrasen/ranged/smg/peaceful + desc = "An officer of Nanotrasen's private security force." + ai_controller = /datum/ai_controller/basic_controller/trooper/ranged/burst/peaceful + +/mob/living/basic/trooper/nanotrasen/ranged/smg/peaceful/Initialize(mapload) + . = ..() + var/datum/callback/retaliate_callback = CALLBACK(src, PROC_REF(ai_retaliate_behaviour)) + AddComponent(/datum/component/ai_retaliate_advanced, retaliate_callback) + +/mob/living/basic/trooper/nanotrasen/proc/ai_retaliate_behaviour(mob/living/attacker) + if (!istype(attacker)) + return + for (var/mob/living/basic/trooper/nanotrasen/potential_trooper in oview(src, 7)) + potential_trooper.ai_controller.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, attacker) diff --git a/code/modules/mob/living/basic/syndicate/russian.dm b/code/modules/mob/living/basic/trooper/russian.dm similarity index 66% rename from code/modules/mob/living/basic/syndicate/russian.dm rename to code/modules/mob/living/basic/trooper/russian.dm index 8bf11eecded7..6e5e34d16b92 100644 --- a/code/modules/mob/living/basic/syndicate/russian.dm +++ b/code/modules/mob/living/basic/trooper/russian.dm @@ -1,8 +1,5 @@ -/** - * Russian subtype of Syndicate troops - * We're a subtype because we are nearly the same mob with a different Faction. - */ -/mob/living/basic/syndicate/russian +/// Russian trooper subtype +/mob/living/basic/trooper/russian name = "Russian Mobster" desc = "For the Motherland!" speed = 0 @@ -11,6 +8,10 @@ unsuitable_cold_damage = 1 unsuitable_heat_damage = 1 faction = list(FACTION_RUSSIAN) + attack_verb_continuous = "slashes" + attack_verb_simple = "slash" + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH mob_spawner = /obj/effect/mob_spawn/corpse/human/russian r_hand = /obj/item/knife/kitchen @@ -19,8 +20,8 @@ /obj/item/knife/kitchen, ) -/mob/living/basic/syndicate/russian/ranged - ai_controller = /datum/ai_controller/basic_controller/syndicate/ranged +/mob/living/basic/trooper/russian/ranged + ai_controller = /datum/ai_controller/basic_controller/trooper/ranged mob_spawner = /obj/effect/mob_spawn/corpse/human/russian/ranged r_hand = /obj/item/gun/ballistic/automatic/pistol loot = list( @@ -30,8 +31,9 @@ var/casingtype = /obj/item/ammo_casing/n762 var/projectilesound = 'sound/weapons/gun/revolver/shot.ogg' -/mob/living/basic/syndicate/russian/ranged/Initialize(mapload) +/mob/living/basic/trooper/russian/ranged/Initialize(mapload) . = ..() AddComponent(/datum/component/ranged_attacks, casing_type = casingtype, projectile_sound = projectilesound, cooldown_time = 1 SECONDS) -/mob/living/basic/syndicate/russian/ranged/lootless + +/mob/living/basic/trooper/russian/ranged/lootless loot = list() diff --git a/code/modules/mob/living/basic/syndicate/syndicate.dm b/code/modules/mob/living/basic/trooper/syndicate.dm similarity index 64% rename from code/modules/mob/living/basic/syndicate/syndicate.dm rename to code/modules/mob/living/basic/trooper/syndicate.dm index ccd6f386b310..2c6923a63f9a 100644 --- a/code/modules/mob/living/basic/syndicate/syndicate.dm +++ b/code/modules/mob/living/basic/trooper/syndicate.dm @@ -1,43 +1,15 @@ -///////////////Base Mob//////////// - -/mob/living/basic/syndicate +/// Syndicate troopers +/mob/living/basic/trooper/syndicate name = "Syndicate Operative" desc = "Death to Nanotrasen." - icon = 'icons/mob/simple/simple_human.dmi' - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - sentience_type = SENTIENCE_HUMANOID - maxHealth = 100 - health = 100 - basic_mob_flags = DEL_ON_DEATH speed = 1.1 melee_damage_lower = 10 melee_damage_upper = 10 - attack_verb_continuous = "punches" - attack_verb_simple = "punch" - attack_sound = 'sound/weapons/punch1.ogg' - istate = ISTATE_HARM|ISTATE_BLOCKING - unsuitable_atmos_damage = 7.5 - unsuitable_cold_damage = 7.5 - unsuitable_heat_damage = 7.5 faction = list(ROLE_SYNDICATE) - ai_controller = /datum/ai_controller/basic_controller/syndicate - /// Loot this mob drops on death. - var/loot = list(/obj/effect/mob_spawn/corpse/human/syndicatesoldier) - /// Path of the mob spawner we base the mob's visuals off of. - var/mob_spawner = /obj/effect/mob_spawn/corpse/human/syndicatesoldier - /// Path of the right hand held item we give to the mob's visuals. - var/r_hand - /// Path of the left hand held item we give to the mob's visuals. - var/l_hand - -/mob/living/basic/syndicate/Initialize(mapload) - . = ..() - apply_dynamic_human_appearance(src, mob_spawn_path = mob_spawner, r_hand = r_hand, l_hand = l_hand) - if(LAZYLEN(loot)) - AddElement(/datum/element/death_drops, loot) - AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_SHOE) + loot = list(/obj/effect/mob_spawn/corpse/human/syndicatesoldier) + mob_spawner = /obj/effect/mob_spawn/corpse/human/syndicatesoldier -/mob/living/basic/syndicate/space +/mob/living/basic/trooper/syndicate/space name = "Syndicate Commando" maxHealth = 170 health = 170 @@ -46,18 +18,18 @@ minimum_survivable_temperature = 0 mob_spawner = /obj/effect/mob_spawn/corpse/human/syndicatecommando -/mob/living/basic/syndicate/space/Initialize(mapload) +/mob/living/basic/trooper/syndicate/space/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) set_light(4) -/mob/living/basic/syndicate/space/stormtrooper +/mob/living/basic/trooper/syndicate/space/stormtrooper name = "Syndicate Stormtrooper" maxHealth = 250 health = 250 mob_spawner = /obj/effect/mob_spawn/corpse/human/syndicatestormtrooper -/mob/living/basic/syndicate/melee //dude with a knife and no shields +/mob/living/basic/trooper/syndicate/melee //dude with a knife and no shields melee_damage_lower = 15 melee_damage_upper = 15 loot = list(/obj/effect/gibspawner/human) @@ -68,13 +40,13 @@ r_hand = /obj/item/knife/combat/survival var/projectile_deflect_chance = 0 -/mob/living/basic/syndicate/melee/bullet_act(obj/projectile/projectile) +/mob/living/basic/trooper/syndicate/melee/bullet_act(obj/projectile/projectile) if(prob(projectile_deflect_chance)) visible_message(span_danger("[src] blocks [projectile] with its shield!")) return BULLET_ACT_BLOCK return ..() -/mob/living/basic/syndicate/melee/space +/mob/living/basic/trooper/syndicate/melee/space name = "Syndicate Commando" maxHealth = 170 health = 170 @@ -82,18 +54,18 @@ minimum_survivable_temperature = 0 mob_spawner = /obj/effect/mob_spawn/corpse/human/syndicatecommando -/mob/living/basic/syndicate/melee/space/Initialize(mapload) +/mob/living/basic/trooper/syndicate/melee/space/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) set_light(4) -/mob/living/basic/syndicate/melee/space/stormtrooper +/mob/living/basic/trooper/syndicate/melee/space/stormtrooper name = "Syndicate Stormtrooper" maxHealth = 250 health = 250 mob_spawner = /obj/effect/mob_spawn/corpse/human/syndicatestormtrooper -/mob/living/basic/syndicate/melee/sword +/mob/living/basic/trooper/syndicate/melee/sword melee_damage_lower = 30 melee_damage_upper = 30 attack_verb_continuous = "slashes" @@ -107,7 +79,7 @@ r_hand = /obj/item/melee/energy/sword/saber/red l_hand = /obj/item/shield/energy -/mob/living/basic/syndicate/melee/sword/space +/mob/living/basic/trooper/syndicate/melee/sword/space name = "Syndicate Commando" maxHealth = 170 health = 170 @@ -116,11 +88,11 @@ projectile_deflect_chance = 50 mob_spawner = /obj/effect/mob_spawn/corpse/human/syndicatecommando -/mob/living/basic/syndicate/melee/sword/space/Initialize(mapload) +/mob/living/basic/trooper/syndicate/melee/sword/space/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) -/mob/living/basic/syndicate/melee/sword/space/stormtrooper +/mob/living/basic/trooper/syndicate/melee/sword/space/stormtrooper name = "Syndicate Stormtrooper" maxHealth = 250 health = 250 @@ -129,26 +101,36 @@ ///////////////Guns//////////// -/mob/living/basic/syndicate/ranged +/mob/living/basic/trooper/syndicate/ranged loot = list(/obj/effect/gibspawner/human) - ai_controller = /datum/ai_controller/basic_controller/syndicate/ranged + ai_controller = /datum/ai_controller/basic_controller/trooper/ranged r_hand = /obj/item/gun/ballistic/automatic/pistol /// Type of bullet we use var/casingtype = /obj/item/ammo_casing/c9mm /// Sound to play when firing weapon var/projectilesound = 'sound/weapons/gun/pistol/shot.ogg' + /// number of burst shots + var/burst_shots /// Time between taking shots var/ranged_cooldown = 1 SECONDS -/mob/living/basic/syndicate/ranged/Initialize(mapload) +/mob/living/basic/trooper/syndicate/ranged/Initialize(mapload) . = ..() - AddComponent(/datum/component/ranged_attacks, casing_type = casingtype, projectile_sound = projectilesound, cooldown_time = ranged_cooldown) - -/mob/living/basic/syndicate/ranged/infiltrator //shuttle loan event + AddComponent(\ + /datum/component/ranged_attacks,\ + casing_type = casingtype,\ + projectile_sound = projectilesound,\ + cooldown_time = ranged_cooldown,\ + burst_shots = burst_shots,\ + ) + if (ranged_cooldown <= 1 SECONDS) + AddComponent(/datum/component/ranged_mob_full_auto) + +/mob/living/basic/trooper/syndicate/ranged/infiltrator //shuttle loan event projectilesound = 'sound/weapons/gun/smg/shot_suppressed.ogg' loot = list(/obj/effect/mob_spawn/corpse/human/syndicatesoldier) -/mob/living/basic/syndicate/ranged/space +/mob/living/basic/trooper/syndicate/ranged/space name = "Syndicate Commando" maxHealth = 170 health = 170 @@ -156,30 +138,31 @@ minimum_survivable_temperature = 0 mob_spawner = /obj/effect/mob_spawn/corpse/human/syndicatecommando -/mob/living/basic/syndicate/ranged/space/Initialize(mapload) +/mob/living/basic/trooper/syndicate/ranged/space/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) set_light(4) -/mob/living/basic/syndicate/ranged/space/stormtrooper +/mob/living/basic/trooper/syndicate/ranged/space/stormtrooper name = "Syndicate Stormtrooper" maxHealth = 250 health = 250 mob_spawner = /obj/effect/mob_spawn/corpse/human/syndicatestormtrooper -/mob/living/basic/syndicate/ranged/smg +/mob/living/basic/trooper/syndicate/ranged/smg casingtype = /obj/item/ammo_casing/c45 projectilesound = 'sound/weapons/gun/smg/shot.ogg' - ai_controller = /datum/ai_controller/basic_controller/syndicate/ranged/burst + ai_controller = /datum/ai_controller/basic_controller/trooper/ranged/burst + burst_shots = 3 ranged_cooldown = 3 SECONDS r_hand = /obj/item/gun/ballistic/automatic/c20r -/mob/living/basic/syndicate/ranged/smg/pilot //caravan ambush ruin +/mob/living/basic/trooper/syndicate/ranged/smg/pilot //caravan ambush ruin name = "Syndicate Salvage Pilot" loot = list(/obj/effect/mob_spawn/corpse/human/syndicatepilot) mob_spawner = /obj/effect/mob_spawn/corpse/human/syndicatepilot -/mob/living/basic/syndicate/ranged/smg/space +/mob/living/basic/trooper/syndicate/ranged/smg/space name = "Syndicate Commando" maxHealth = 170 health = 170 @@ -187,24 +170,25 @@ minimum_survivable_temperature = 0 mob_spawner = /obj/effect/mob_spawn/corpse/human/syndicatecommando -/mob/living/basic/syndicate/ranged/smg/space/Initialize(mapload) +/mob/living/basic/trooper/syndicate/ranged/smg/space/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) set_light(4) -/mob/living/basic/syndicate/ranged/smg/space/stormtrooper +/mob/living/basic/trooper/syndicate/ranged/smg/space/stormtrooper name = "Syndicate Stormtrooper" maxHealth = 250 health = 250 mob_spawner = /obj/effect/mob_spawn/corpse/human/syndicatestormtrooper -/mob/living/basic/syndicate/ranged/shotgun +/mob/living/basic/trooper/syndicate/ranged/shotgun casingtype = /obj/item/ammo_casing/shotgun/buckshot //buckshot (up to 72.5 brute) fired in a two-round burst - ai_controller = /datum/ai_controller/basic_controller/syndicate/ranged/shotgunner + ai_controller = /datum/ai_controller/basic_controller/trooper/ranged/shotgunner ranged_cooldown = 3 SECONDS + burst_shots = 2 r_hand = /obj/item/gun/ballistic/shotgun/bulldog -/mob/living/basic/syndicate/ranged/shotgun/space +/mob/living/basic/trooper/syndicate/ranged/shotgun/space name = "Syndicate Commando" maxHealth = 170 health = 170 @@ -213,12 +197,12 @@ speed = 1 mob_spawner = /obj/effect/mob_spawn/corpse/human/syndicatecommando -/mob/living/basic/syndicate/ranged/shotgun/space/Initialize(mapload) +/mob/living/basic/trooper/syndicate/ranged/shotgun/space/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) set_light(4) -/mob/living/basic/syndicate/ranged/shotgun/space/stormtrooper +/mob/living/basic/trooper/syndicate/ranged/shotgun/space/stormtrooper name = "Syndicate Stormtrooper" maxHealth = 250 health = 250 @@ -258,7 +242,7 @@ bubble_icon = "syndibot" gold_core_spawnable = HOSTILE_SPAWN death_message = "is smashed into pieces!" - ai_controller = /datum/ai_controller/basic_controller/syndicate/viscerator + ai_controller = /datum/ai_controller/basic_controller/trooper/viscerator /mob/living/basic/viscerator/Initialize(mapload) . = ..() diff --git a/code/modules/mob/living/basic/trooper/trooper.dm b/code/modules/mob/living/basic/trooper/trooper.dm new file mode 100644 index 000000000000..e13c517a3c41 --- /dev/null +++ b/code/modules/mob/living/basic/trooper/trooper.dm @@ -0,0 +1,36 @@ +/mob/living/basic/trooper + icon = 'icons/mob/simple/simple_human.dmi' + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + sentience_type = SENTIENCE_HUMANOID + maxHealth = 100 + health = 100 + basic_mob_flags = DEL_ON_DEATH + speed = 1.1 + melee_damage_lower = 10 + melee_damage_upper = 10 + attack_verb_continuous = "punches" + attack_verb_simple = "punch" + attack_sound = 'sound/weapons/punch1.ogg' + melee_attack_cooldown = 1.2 SECONDS + istate = ISTATE_HARM + unsuitable_atmos_damage = 7.5 + unsuitable_cold_damage = 7.5 + unsuitable_heat_damage = 7.5 + ai_controller = /datum/ai_controller/basic_controller/trooper + + /// Loot this mob drops on death. + var/loot = list(/obj/effect/mob_spawn/corpse/human) + /// Path of the mob spawner we base the mob's visuals off of. + var/mob_spawner = /obj/effect/mob_spawn/corpse/human + /// Path of the right hand held item we give to the mob's visuals. + var/r_hand + /// Path of the left hand held item we give to the mob's visuals. + var/l_hand + +/mob/living/basic/trooper/Initialize(mapload) + . = ..() + apply_dynamic_human_appearance(src, mob_spawn_path = mob_spawner, r_hand = r_hand, l_hand = l_hand) + if(LAZYLEN(loot)) + loot = string_list(loot) + AddElement(/datum/element/death_drops, loot) + AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_SHOE) diff --git a/code/modules/mob/living/basic/trooper/trooper_ai.dm b/code/modules/mob/living/basic/trooper/trooper_ai.dm new file mode 100644 index 000000000000..1f762bb7ba87 --- /dev/null +++ b/code/modules/mob/living/basic/trooper/trooper_ai.dm @@ -0,0 +1,99 @@ +/datum/ai_controller/basic_controller/trooper + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_SAY = "411 in progress, requesting backup!" + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + ) + +/datum/ai_planning_subtree/basic_melee_attack_subtree/trooper + melee_attack_behavior = /datum/ai_behavior/basic_melee_attack + +/datum/ai_planning_subtree/attack_obstacle_in_path/trooper + attack_behaviour = /datum/ai_behavior/attack_obstructions/trooper + +/datum/ai_behavior/attack_obstructions/trooper + action_cooldown = 1.2 SECONDS + +/datum/ai_controller/basic_controller/trooper/calls_reinforcements + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/call_reinforcements, + /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + ) + +/datum/ai_controller/basic_controller/trooper/peaceful + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/call_reinforcements, + /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + ) + +/datum/ai_controller/basic_controller/trooper/ranged + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper, + /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + ) + +/datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper + ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/trooper + +/datum/ai_behavior/basic_ranged_attack/trooper + action_cooldown = 1 SECONDS + required_distance = 5 + avoid_friendly_fire = TRUE + +/datum/ai_controller/basic_controller/trooper/ranged/burst + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_burst, + /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + ) + +/datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_burst + ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/trooper_burst + +/datum/ai_behavior/basic_ranged_attack/trooper_burst + action_cooldown = 3 SECONDS + avoid_friendly_fire = TRUE + +/datum/ai_controller/basic_controller/trooper/ranged/burst/peaceful + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/call_reinforcements, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_burst, + /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + ) + +/datum/ai_controller/basic_controller/trooper/ranged/shotgunner + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_shotgun, + /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + ) + +/datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_shotgun + ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/trooper_shotgun + +/datum/ai_behavior/basic_ranged_attack/trooper_shotgun + action_cooldown = 3 SECONDS + required_distance = 1 + avoid_friendly_fire = TRUE + +/datum/ai_controller/basic_controller/trooper/viscerator + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) diff --git a/code/modules/mob/living/basic/vermin/cockroach.dm b/code/modules/mob/living/basic/vermin/cockroach.dm index a535e25f495a..c55b94cd0182 100644 --- a/code/modules/mob/living/basic/vermin/cockroach.dm +++ b/code/modules/mob/living/basic/vermin/cockroach.dm @@ -52,8 +52,8 @@ /datum/ai_controller/basic_controller/cockroach blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) ai_traits = STOP_MOVING_WHEN_PULLED @@ -85,10 +85,21 @@ faction = list(FACTION_HOSTILE, FACTION_MAINT_CREATURES) ai_controller = /datum/ai_controller/basic_controller/cockroach/glockroach cockroach_cell_line = CELL_LINE_TABLE_GLOCKROACH + ///number of burst shots + var/burst_shots + ///cooldown between attacks + var/ranged_cooldown = 1 SECONDS /mob/living/basic/cockroach/glockroach/Initialize(mapload) . = ..() - AddComponent(/datum/component/ranged_attacks, casing_type = /obj/item/ammo_casing/glockroach, cooldown_time = 1 SECONDS) + AddComponent(\ + /datum/component/ranged_attacks,\ + casing_type = /obj/item/ammo_casing/glockroach,\ + burst_shots = burst_shots,\ + cooldown_time = ranged_cooldown,\ + ) + if (ranged_cooldown <= 1 SECONDS) + AddComponent(/datum/component/ranged_mob_full_auto) /datum/ai_controller/basic_controller/cockroach/glockroach planning_subtrees = list( @@ -114,6 +125,7 @@ melee_damage_lower = 2.5 melee_damage_upper = 10 obj_damage = 10 + melee_attack_cooldown = 1 SECONDS gold_core_spawnable = HOSTILE_SPAWN attack_sound = 'sound/weapons/bladeslice.ogg' attack_vis_effect = ATTACK_EFFECT_SLASH @@ -141,36 +153,26 @@ /datum/ai_planning_subtree/pet_planning, /datum/ai_planning_subtree/random_speech/insect, /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/hauberoach, //If we are attacking someone, this will prevent us from hunting + /datum/ai_planning_subtree/basic_melee_attack_subtree, //If we are attacking someone, this will prevent us from hunting /datum/ai_planning_subtree/find_and_hunt_target/roach, ) -/datum/ai_planning_subtree/basic_melee_attack_subtree/hauberoach - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/hauberoach - -/datum/ai_behavior/basic_melee_attack/hauberoach //Slightly slower, as this is being made in feature freeze ;) - action_cooldown = 1 SECONDS - /datum/ai_controller/basic_controller/cockroach/sewer planning_subtrees = list( /datum/ai_planning_subtree/pet_planning, /datum/ai_planning_subtree/random_speech/insect, /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/sewer, + /datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/find_and_hunt_target/roach, ) -/datum/ai_planning_subtree/basic_melee_attack_subtree/sewer - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/sewer - -/datum/ai_behavior/basic_melee_attack/sewer - action_cooldown = 0.8 SECONDS - /mob/living/basic/cockroach/glockroach/mobroach name = "mobroach" desc = "WE'RE FUCKED, THAT GLOCKROACH HAS A TOMMYGUN!" icon_state = "mobroach" ai_controller = /datum/ai_controller/basic_controller/cockroach/mobroach + burst_shots = 4 + ranged_cooldown = 2 SECONDS /datum/ai_controller/basic_controller/cockroach/mobroach planning_subtrees = list( @@ -185,5 +187,4 @@ ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/mobroach /datum/ai_behavior/basic_ranged_attack/mobroach - shots = 4 action_cooldown = 2 SECONDS diff --git a/code/modules/mob/living/basic/vermin/crab.dm b/code/modules/mob/living/basic/vermin/crab.dm index abe5c25117b6..2a4ce7bb912e 100644 --- a/code/modules/mob/living/basic/vermin/crab.dm +++ b/code/modules/mob/living/basic/vermin/crab.dm @@ -77,9 +77,9 @@ ///The basic ai controller for crabs /datum/ai_controller/basic_controller/crab blackboard = list( - BB_BASIC_MOB_FLEEING = FALSE, - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/of_size/ours_or_smaller, - BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction, + BB_ALWAYS_IGNORE_FACTION = TRUE, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/ours_or_smaller, + BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED diff --git a/code/modules/mob/living/basic/vermin/frog.dm b/code/modules/mob/living/basic/vermin/frog.dm index 282ed17b00c2..5ec6a15a6bfd 100644 --- a/code/modules/mob/living/basic/vermin/frog.dm +++ b/code/modules/mob/living/basic/vermin/frog.dm @@ -17,6 +17,7 @@ obj_damage = 10 attack_verb_continuous = "bites" attack_verb_simple = "bite" + melee_attack_cooldown = 2.5 SECONDS response_help_continuous = "pets" response_help_simple = "pet" response_disarm_continuous = "pokes" @@ -76,8 +77,8 @@ /datum/ai_controller/basic_controller/frog blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) ai_movement = /datum/ai_movement/basic_avoidance @@ -85,19 +86,13 @@ planning_subtrees = list( /datum/ai_planning_subtree/target_retaliate, /datum/ai_planning_subtree/random_speech/frog, - /datum/ai_planning_subtree/basic_melee_attack_subtree/frog, + /datum/ai_planning_subtree/basic_melee_attack_subtree, ) -/datum/ai_planning_subtree/basic_melee_attack_subtree/frog - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/frog - -/datum/ai_behavior/basic_melee_attack/frog - action_cooldown = 2.5 SECONDS - /datum/ai_controller/basic_controller/frog/trash planning_subtrees = list( /datum/ai_planning_subtree/pet_planning, /datum/ai_planning_subtree/random_speech/frog, /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/frog, + /datum/ai_planning_subtree/basic_melee_attack_subtree, ) diff --git a/code/modules/mob/living/basic/vermin/lizard.dm b/code/modules/mob/living/basic/vermin/lizard.dm index 4038e8486921..780ed6ee981e 100644 --- a/code/modules/mob/living/basic/vermin/lizard.dm +++ b/code/modules/mob/living/basic/vermin/lizard.dm @@ -45,12 +45,12 @@ . = ..() ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) AddElement(/datum/element/pet_bonus, "sticks its tongue out contentedly!") - AddElement(/datum/element/basic_eating, 5, 0, null, edibles) + AddElement(/datum/element/basic_eating, heal_amt = 5, food_types = edibles) ai_controller.set_blackboard_key(BB_BASIC_FOODS, edibles) /datum/ai_controller/basic_controller/lizard blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, ) ai_traits = STOP_MOVING_WHEN_PULLED diff --git a/code/modules/mob/living/basic/vermin/mouse.dm b/code/modules/mob/living/basic/vermin/mouse.dm index 53922a92e466..bd9bb8fb3b2d 100644 --- a/code/modules/mob/living/basic/vermin/mouse.dm +++ b/code/modules/mob/living/basic/vermin/mouse.dm @@ -74,7 +74,7 @@ /mob/living/basic/mouse/examine(mob/user) . = ..() - var/sameside = user.faction_check_mob(src, exact_match = TRUE) + var/sameside = user.faction_check_atom(src, exact_match = TRUE) if(isregalrat(user)) if(sameside) . += span_notice("This rat serves under you.") @@ -381,11 +381,11 @@ /// The mouse AI controller /datum/ai_controller/basic_controller/mouse - blackboard = list( - BB_BASIC_MOB_FLEEING = TRUE, // Always cowardly + blackboard = list( // Always cowardly BB_CURRENT_HUNTING_TARGET = null, // cheese BB_LOW_PRIORITY_HUNTING_TARGET = null, // cable - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), // Use this to find people to run away from + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, // Use this to find people to run away from + BB_BASIC_MOB_FLEE_DISTANCE = 3, ) ai_traits = STOP_MOVING_WHEN_PULLED @@ -405,9 +405,6 @@ ) /// Don't look for anything to run away from if you are distracted by being adjacent to cheese -/datum/ai_planning_subtree/flee_target/mouse - flee_behaviour = /datum/ai_behavior/run_away_from_target/mouse - /datum/ai_planning_subtree/flee_target/mouse /datum/ai_planning_subtree/flee_target/mouse/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) @@ -416,16 +413,11 @@ return // We see some cheese, which is more important than our life return ..() -/datum/ai_planning_subtree/flee_target/mouse/select - -/datum/ai_behavior/run_away_from_target/mouse - run_distance = 3 // Mostly exists in small tunnels, don't get ahead of yourself - /// AI controller for rats, slightly more complex than mice becuase they attack people /datum/ai_controller/basic_controller/mouse/rat blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_BASIC_MOB_CURRENT_TARGET = null, // heathen BB_CURRENT_HUNTING_TARGET = null, // cheese BB_LOW_PRIORITY_HUNTING_TARGET = null, // cable diff --git a/code/modules/mob/living/basic/vermin/space_bat.dm b/code/modules/mob/living/basic/vermin/space_bat.dm index 6fa1fd7aed5f..dc3c1b85aede 100644 --- a/code/modules/mob/living/basic/vermin/space_bat.dm +++ b/code/modules/mob/living/basic/vermin/space_bat.dm @@ -42,7 +42,7 @@ ///Controller for space bats, has nothing unique, just retaliation. /datum/ai_controller/basic_controller/space_bat blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = STOP_MOVING_WHEN_PULLED diff --git a/code/modules/mob/living/brain/life.dm b/code/modules/mob/living/brain/life.dm index 1cbc979d365c..57d73d126e2f 100644 --- a/code/modules/mob/living/brain/life.dm +++ b/code/modules/mob/living/brain/life.dm @@ -1,8 +1,6 @@ /mob/living/brain/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if (notransform) - return - if(!loc) + if(isnull(loc) || HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return . = ..() handle_emp_damage(seconds_per_tick, times_fired) diff --git a/code/modules/mob/living/carbon/alien/adult/adult_update_icons.dm b/code/modules/mob/living/carbon/alien/adult/adult_update_icons.dm index 4ca4f12b977e..5a4fd7c20ff2 100644 --- a/code/modules/mob/living/carbon/alien/adult/adult_update_icons.dm +++ b/code/modules/mob/living/carbon/alien/adult/adult_update_icons.dm @@ -76,7 +76,7 @@ //Royals have bigger sprites, so inhand things must be handled differently. /mob/living/carbon/alien/adult/royal/update_held_items() - ..() + . = ..() remove_overlay(HANDS_LAYER) var/list/hands = list() diff --git a/code/modules/mob/living/carbon/alien/adult/queen.dm b/code/modules/mob/living/carbon/alien/adult/queen.dm index 1f344a98fb2d..b21a921ae066 100644 --- a/code/modules/mob/living/carbon/alien/adult/queen.dm +++ b/code/modules/mob/living/carbon/alien/adult/queen.dm @@ -19,6 +19,7 @@ . = ..() // as a wise man once wrote: "pull over that ass too fat" REMOVE_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + AddComponent(/datum/component/seethrough_mob) /mob/living/carbon/alien/adult/royal/on_lying_down(new_lying_angle) . = ..() @@ -54,9 +55,6 @@ var/datum/action/cooldown/spell/aoe/repulse/xeno/tail_whip = new(src) tail_whip.Grant(src) - var/datum/action/small_sprite/queen/smallsprite = new(src) - smallsprite.Grant(src) - var/datum/action/cooldown/alien/promote/promotion = new(src) promotion.Grant(src) diff --git a/code/modules/mob/living/carbon/alien/larva/life.dm b/code/modules/mob/living/carbon/alien/larva/life.dm index 325e2b10742b..2e2674e14e76 100644 --- a/code/modules/mob/living/carbon/alien/larva/life.dm +++ b/code/modules/mob/living/carbon/alien/larva/life.dm @@ -1,7 +1,7 @@ /mob/living/carbon/alien/larva/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if (notransform) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return if(!..() || IS_IN_STASIS(src) || (amount_grown >= max_grown)) return // We're dead, in stasis, or already grown. diff --git a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm index 333e318f9e0a..7d07f1c20224 100644 --- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm +++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm @@ -111,8 +111,7 @@ var/mob/living/carbon/alien/larva/new_xeno = new(xeno_loc) new_xeno.key = ghost.key SEND_SOUND(new_xeno, sound('sound/voice/hiss5.ogg',0,0,0,100)) //To get the player's attention - new_xeno.add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), type) //so we don't move during the bursting animation - new_xeno.notransform = 1 + new_xeno.add_traits(list(TRAIT_HANDS_BLOCKED, TRAIT_IMMOBILIZED, TRAIT_NO_TRANSFORM), type) //so we don't move during the bursting animation new_xeno.invisibility = INVISIBILITY_MAXIMUM sleep(0.6 SECONDS) @@ -121,10 +120,8 @@ qdel(new_xeno) CRASH("AttemptGrow failed due to the early qdeletion of source or owner.") - if(new_xeno) - REMOVE_TRAIT(new_xeno, TRAIT_IMMOBILIZED, type) - REMOVE_TRAIT(new_xeno, TRAIT_HANDS_BLOCKED, type) - new_xeno.notransform = 0 + if(!isnull(new_xeno)) + new_xeno.remove_traits(list(TRAIT_HANDS_BLOCKED, TRAIT_IMMOBILIZED, TRAIT_NO_TRANSFORM), type) new_xeno.invisibility = 0 if(gib_on_success) diff --git a/code/modules/mob/living/carbon/alien/special/facehugger.dm b/code/modules/mob/living/carbon/alien/special/facehugger.dm index ff046dc87b4e..1c4a19b78d63 100644 --- a/code/modules/mob/living/carbon/alien/special/facehugger.dm +++ b/code/modules/mob/living/carbon/alien/special/facehugger.dm @@ -103,7 +103,7 @@ if(CanHug(AM) && Adjacent(AM)) return Leap(AM) -/obj/item/clothing/mask/facehugger/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, quickstart = TRUE) +/obj/item/clothing/mask/facehugger/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, gentle, quickstart = TRUE) . = ..() if(!.) return diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 337c7e0d7e10..3800b3499ec4 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -11,6 +11,7 @@ COMSIG_CARBON_DISARM_COLLIDE = PROC_REF(disarm_collision), ) AddElement(/datum/element/connect_loc, loc_connections) + ADD_TRAIT(src, TRAIT_CAN_HOLD_ITEMS, INNATE_TRAIT) // Carbons are assumed to be innately capable of having arms, we check their arms count instead /mob/living/carbon/Destroy() //This must be done first, so the mob ghosts correctly before DNA etc is nulled @@ -28,63 +29,13 @@ QDEL_NULL(dna) GLOB.carbon_list -= src -/mob/living/carbon/perform_hand_swap(held_index) - . = ..() - if(!.) - return - - if(!held_index) - held_index = (active_hand_index % held_items.len)+1 - - if(!isnum(held_index)) - CRASH("You passed [held_index] into swap_hand instead of a number. WTF man") - - var/oindex = active_hand_index - active_hand_index = held_index - if(hud_used) - var/atom/movable/screen/inventory/hand/H - H = hud_used.hand_slots["[oindex]"] - if(H) - H.update_appearance() - H = hud_used.hand_slots["[held_index]"] - if(H) - H.update_appearance() - - -/mob/living/carbon/activate_hand(selhand) //l/r OR 1-held_items.len - if(!selhand) - selhand = (active_hand_index % held_items.len)+1 - - if(istext(selhand)) - selhand = lowertext(selhand) - if(selhand == "right" || selhand == "r") - selhand = 2 - if(selhand == "left" || selhand == "l") - selhand = 1 - - if(selhand != active_hand_index) - swap_hand(selhand) - else - mode() // Activate held item - -/mob/living/carbon/attackby(obj/item/I, mob/living/user, params) - for(var/datum/surgery/operations as anything in surgeries) - if((user.istate & ISTATE_HARM)) - break - if(body_position != LYING_DOWN && (operations.surgery_flags & SURGERY_REQUIRE_RESTING)) - continue - if(!(operations.surgery_flags & SURGERY_SELF_OPERABLE) && (user == src)) - continue - var/list/modifiers = params2list(params) - if(operations.next_step(user, modifiers)) - return TRUE - +/mob/living/carbon/attackby(obj/item/item, mob/living/user, params) if(!all_wounds || !(!(user.istate & ISTATE_HARM) || user == src)) return ..() for(var/i in shuffle(all_wounds)) var/datum/wound/W = i - if(W.try_treating(I, user)) + if(W.try_treating(item, user)) return 1 return ..() @@ -164,6 +115,9 @@ return FALSE var/atom/movable/thrown_thing var/obj/item/held_item = get_active_held_item() + var/verb_text = pick("throw", "toss", "hurl", "chuck", "fling") + if(prob(0.5)) + verb_text = "yeet" var/neckgrab_throw = FALSE // we can't check for if it's a neckgrab throw when totaling up power_throw since we've already stopped pulling them by then, so get it early if(!held_item) if(pulling && isliving(pulling) && grab_state >= GRAB_AGGRESSIVE) @@ -194,8 +148,12 @@ power_throw++ if(neckgrab_throw) power_throw++ - visible_message(span_danger("[src] throws [thrown_thing][power_throw ? " really hard!" : "."]"), \ - span_danger("You throw [thrown_thing][power_throw ? " really hard!" : "."]")) + if(isitem(thrown_thing)) + var/obj/item/thrown_item = thrown_thing + if(thrown_item.throw_verb) + verb_text = thrown_item.throw_verb + visible_message(span_danger("[src] [plural_s(verb_text)] [thrown_thing][power_throw ? " really hard!" : "."]"), \ + span_danger("You [verb_text] [thrown_thing][power_throw ? " really hard!" : "."]")) log_message("has thrown [thrown_thing] [power_throw > 0 ? "really hard" : ""]", LOG_ATTACK) var/extra_throw_range = HAS_TRAIT(src, TRAIT_THROWINGARM) ? 2 : 0 newtonian_move(get_dir(target, src)) @@ -1253,12 +1211,17 @@ else wound_type = forced_type else - wound_type = pick(GLOB.global_all_wound_types) + for (var/datum/wound/path as anything in shuffle(GLOB.all_wound_pregen_data)) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[path] + if (pregen_data.can_be_applied_to(scar_part, random_roll = TRUE)) + wound_type = path + break - var/datum/wound/phantom_wound = new wound_type - scaries.generate(scar_part, phantom_wound) - scaries.fake = TRUE - QDEL_NULL(phantom_wound) + if (wound_type) // can feasibly happen, if its an inorganic limb/cant be wounded/scarred + var/datum/wound/phantom_wound = new wound_type + scaries.generate(scar_part, phantom_wound) + scaries.fake = TRUE + QDEL_NULL(phantom_wound) /mob/living/carbon/is_face_visible() return !(wear_mask?.flags_inv & HIDEFACE) && !(head?.flags_inv & HIDEFACE) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index ae8f529b62f4..116e3efea6bc 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -99,8 +99,8 @@ send_item_attack_message(I, user, affecting.plaintext_zone, affecting) if(I.force) var/attack_direction = get_dir(user, src) - apply_damage(I.force, I.damtype, affecting, wound_bonus = I.wound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.get_sharpness(), attack_direction = attack_direction) - if(I.damtype == BRUTE && (IS_ORGANIC_LIMB(affecting) || HAS_TRAIT(affecting.owner, TRAIT_ROBOT_CAN_BLEED))) + apply_damage(I.force, I.damtype, affecting, wound_bonus = I.wound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.get_sharpness(), attack_direction = attack_direction, attacking_item = I) + if(I.damtype == BRUTE && affecting.can_bleed()) if(prob(33)) I.add_mob_blood(src) var/turf/location = get_turf(src) @@ -127,22 +127,35 @@ var/message_verb_simple = length(I.attack_verb_simple) ? "[pick(I.attack_verb_simple)]" : "attack" var/extra_wound_details = "" + if(I.damtype == BRUTE && hit_bodypart.can_dismember()) + var/mangled_state = hit_bodypart.get_mangled_state() - var/bio_state = hit_bodypart.biological_state - if((mangled_state & BODYPART_MANGLED_FLESH) && (mangled_state & BODYPART_MANGLED_BONE)) + + var/bio_status = hit_bodypart.get_bio_state_status() + + var/has_exterior = ((bio_status & ANATOMY_EXTERIOR)) + var/has_interior = ((bio_status & ANATOMY_INTERIOR)) + + var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR))) + var/interior_ready_to_dismember = (!has_interior || ((mangled_state & BODYPART_MANGLED_INTERIOR))) + + var/dismemberable = ((hit_bodypart.dismemberable_by_wound()) || hit_bodypart.dismemberable_by_total_damage()) + if (dismemberable) extra_wound_details = ", threatening to sever it entirely" - else if((mangled_state & BODYPART_MANGLED_FLESH && I.get_sharpness()) || ((mangled_state & BODYPART_MANGLED_BONE) && (bio_state & BIO_BONE) && !(bio_state & BIO_FLESH))) - extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] through to the bone" - else if((mangled_state & BODYPART_MANGLED_BONE && I.get_sharpness()) || ((mangled_state & BODYPART_MANGLED_FLESH) && (bio_state & BIO_FLESH) && !(bio_state & BIO_BONE))) - extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] at the remaining tissue" + else if((has_interior && (has_exterior && exterior_ready_to_dismember) && I.get_sharpness())) + var/bone_text = hit_bodypart.get_internal_description() + extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] through to the [bone_text]" + else if(has_exterior && ((has_interior && interior_ready_to_dismember) && I.get_sharpness())) + var/tissue_text = hit_bodypart.get_external_description() + extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] at the remaining [tissue_text]" var/message_hit_area = "" if(hit_area) message_hit_area = " in the [hit_area]" var/attack_message_spectator = "[src] [message_verb_continuous][message_hit_area] with [I][extra_wound_details]!" var/attack_message_victim = "You're [message_verb_continuous][message_hit_area] with [I][extra_wound_details]!" - var/attack_message_attacker = "You [message_verb_simple] [src][message_hit_area] with [I]!" + var/attack_message_attacker = "You [message_verb_simple] [src][message_hit_area] with [I][extra_wound_details]!" if(user in viewers(src, null)) attack_message_spectator = "[user] [message_verb_continuous] [src][message_hit_area] with [I][extra_wound_details]!" attack_message_victim = "[user] [message_verb_continuous] you[message_hit_area] with [I][extra_wound_details]!" @@ -155,10 +168,10 @@ return TRUE -/mob/living/carbon/attack_drone(mob/living/simple_animal/drone/user) +/mob/living/carbon/attack_drone(mob/living/basic/drone/user) return //so we don't call the carbon's attack_hand(). -/mob/living/carbon/attack_drone_secondary(mob/living/simple_animal/drone/user) +/mob/living/carbon/attack_drone_secondary(mob/living/basic/drone/user) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN //ATTACK HAND IGNORING PARENT RETURN VALUE @@ -693,14 +706,16 @@ return ..() var/obj/item/bodypart/grasped_part = get_bodypart(zone_selected) - if(!grasped_part?.get_modified_bleed_rate()) + if(!grasped_part?.can_be_grasped()) return var/starting_hand_index = active_hand_index if(starting_hand_index == grasped_part.held_index) to_chat(src, span_danger("You can't grasp your [grasped_part.name] with itself!")) return - to_chat(src, span_warning("You try grasping at your [grasped_part.name], trying to stop the bleeding...")) + var/bleed_rate = grasped_part.get_modified_bleed_rate() + var/bleeding_text = (bleed_rate ? ", trying to stop the bleeding" : "") + to_chat(src, span_warning("You try grasping at your [grasped_part.name][bleeding_text]...")) if(!do_after(src, 0.75 SECONDS)) to_chat(src, span_danger("You fail to grasp your [grasped_part.name].")) return @@ -712,6 +727,17 @@ return grasp.grasp_limb(grasped_part) +/// If TRUE, the owner of this bodypart can try grabbing it to slow bleeding, as well as various other effects. +/obj/item/bodypart/proc/can_be_grasped() + if (get_modified_bleed_rate()) + return TRUE + + for (var/datum/wound/iterated_wound as anything in wounds) + if (iterated_wound.wound_flags & CAN_BE_GRASPED) + return TRUE + + return FALSE + /// an abstract item representing you holding your own limb to staunch the bleeding, see [/mob/living/carbon/proc/grabbedby] will probably need to find somewhere else to put this. /obj/item/hand_item/self_grasp name = "self-grasp" @@ -756,7 +782,9 @@ RegisterSignal(user, COMSIG_PARENT_QDELETING, PROC_REF(qdel_void)) RegisterSignals(grasped_part, list(COMSIG_CARBON_REMOVE_LIMB, COMSIG_PARENT_QDELETING), PROC_REF(qdel_void)) - user.visible_message(span_danger("[user] grasps at [user.p_their()] [grasped_part.name], trying to stop the bleeding."), span_notice("You grab hold of your [grasped_part.name] tightly."), vision_distance=COMBAT_MESSAGE_RANGE) + var/bleed_rate = grasped_part.get_modified_bleed_rate() + var/bleeding_text = (bleed_rate ? ", trying to stop the bleeding" : "") + user.visible_message(span_danger("[user] grasps at [user.p_their()] [grasped_part.name][bleeding_text]."), span_notice("You grab hold of your [grasped_part.name] tightly."), vision_distance=COMBAT_MESSAGE_RANGE) playsound(get_turf(src), 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) return TRUE diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index f5cee1ec422c..84dc1029808b 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -101,6 +101,9 @@ /// All of the scars a carbon has afflicted throughout their limbs var/list/all_scars + /// Assoc list of BODY_ZONE -> wounding_type. Set when a limb is dismembered, unset when one is attached. Used for determining what scar to add when it comes time to generate them. + var/list/body_zone_dismembered_by + /// Simple modifier for whether this mob can handle greater or lesser skillchip complexity. See /datum/mutation/human/biotechcompat/ for example. var/skillchip_complexity_modifier = 0 @@ -113,6 +116,11 @@ /// Stores the result of our last known top_offset generation for optimisation purposes when drawing limb icons. var/last_top_offset + /// A bitfield of "bodytypes", updated by /obj/item/bodypart/proc/synchronize_bodytypes() + var/bodytype = BODYTYPE_HUMANOID | BODYTYPE_ORGANIC + + var/is_leaning = FALSE + COOLDOWN_DECLARE(bleeding_message_cd) var/next_smell = 0 /// Cooldown for the next smell diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm index b6aad62e1072..5363d4c5d11a 100644 --- a/code/modules/mob/living/carbon/carbon_update_icons.dm +++ b/code/modules/mob/living/carbon/carbon_update_icons.dm @@ -259,8 +259,8 @@ #undef NEXT_PARENT_COMMAND /mob/living/carbon/regenerate_icons() - if(notransform) - return 1 + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) + return icon_render_keys = list() //Clear this bad larry out update_held_items() update_worn_handcuffs() @@ -269,11 +269,17 @@ update_body() /mob/living/carbon/update_held_items() + . = ..() remove_overlay(HANDS_LAYER) if (handcuffed) drop_all_held_items() return + overlays_standing[HANDS_LAYER] = get_held_overlays() + apply_overlay(HANDS_LAYER) + +/// Generate held item overlays +/mob/living/carbon/proc/get_held_overlays() var/list/hands = list() for(var/obj/item/I in held_items) if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) @@ -294,9 +300,7 @@ icon_file = I.righthand_file hands += I.build_worn_icon(default_layer = HANDS_LAYER, default_icon_file = icon_file, isinhands = TRUE) - - overlays_standing[HANDS_LAYER] = hands - apply_overlay(HANDS_LAYER) + return hands /mob/living/carbon/update_fire_overlay(stacks, on_fire, last_icon_state, suffix = "") var/fire_icon = "[dna?.species.fire_overlay || "human"]_[stacks > MOB_BIG_FIRE_STACK_THRESHOLD ? "big_fire" : "small_fire"][suffix]" diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm index 0505c47fdc89..ab8130933229 100644 --- a/code/modules/mob/living/carbon/damage_procs.dm +++ b/code/modules/mob/living/carbon/damage_procs.dm @@ -1,43 +1,79 @@ -/mob/living/carbon/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null) - SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMAGE, damage, damagetype, def_zone) - var/hit_percent = (100-blocked)/100 - if(!damage || (!forced && hit_percent <= 0)) - return 0 +/mob/living/carbon/apply_damage( + damage = 0, + damagetype = BRUTE, + def_zone = null, + blocked = 0, + forced = FALSE, + spread_damage = FALSE, + wound_bonus = 0, + bare_wound_bonus = 0, + sharpness = NONE, + attack_direction = null, + attacking_item, +) + // Spread damage should always have def zone be null + if(spread_damage) + def_zone = null + + // Otherwise if def zone is null, we'll get a random bodypart / zone to hit. + // ALso we'll automatically covnert string def zones into bodyparts to pass into parent call. + else if(!isbodypart(def_zone)) + var/random_zone = check_zone(def_zone || get_random_valid_zone(def_zone)) + def_zone = get_bodypart(random_zone) || bodyparts[1] + + . = ..() + // Taking brute or burn to bodyparts gives a damage flash + if(def_zone && (damagetype == BRUTE || damagetype == BURN)) + damageoverlaytemp += . + + return . + +/mob/living/carbon/human/apply_damage( + damage = 0, + damagetype = BRUTE, + def_zone = null, + blocked = 0, + forced = FALSE, + spread_damage = FALSE, + wound_bonus = 0, + bare_wound_bonus = 0, + sharpness = NONE, + attack_direction = null, + attacking_item, +) + + // Add relevant DR modifiers into blocked value to pass to parent + blocked += physiology?.damage_resistance + blocked += dna?.species?.damage_modifier + return ..() - var/obj/item/bodypart/BP = null - if(!spread_damage) - if(isbodypart(def_zone)) //we specified a bodypart object - BP = def_zone - else - if(!def_zone) - def_zone = get_random_valid_zone(def_zone) - BP = get_bodypart(check_zone(def_zone)) - if(!BP) - BP = bodyparts[1] +/mob/living/carbon/human/get_incoming_damage_modifier( + damage = 0, + damagetype = BRUTE, + def_zone = null, + sharpness = NONE, + attack_direction = null, + attacking_item, +) + var/final_mod = ..() - var/damage_amount = forced ? damage : damage * hit_percent switch(damagetype) if(BRUTE) - if(BP) - if(BP.receive_damage(damage_amount, 0, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness, attack_direction = attack_direction)) - update_damage_overlays() - else //no bodypart, we deal damage with a more general method. - adjustBruteLoss(damage_amount, forced = forced) + final_mod *= physiology.brute_mod if(BURN) - if(BP) - if(BP.receive_damage(0, damage_amount, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness, attack_direction = attack_direction)) - update_damage_overlays() - else - adjustFireLoss(damage_amount, forced = forced) + final_mod *= physiology.burn_mod if(TOX) - adjustToxLoss(damage_amount, forced = forced) + final_mod *= physiology.tox_mod if(OXY) - adjustOxyLoss(damage_amount, forced = forced) + final_mod *= physiology.oxy_mod if(CLONE) - adjustCloneLoss(damage_amount, forced = forced) + final_mod *= physiology.clone_mod if(STAMINA) - stamina.adjust(-damage_amount, forced = forced) - return TRUE + final_mod *= physiology.stamina_mod + if(BRAIN) + final_mod *= physiology.brain_mod + + return final_mod //These procs fetch a cumulative total damage from all bodyparts /mob/living/carbon/getBruteLoss() @@ -59,11 +95,8 @@ if(target_area) if((target_area.area_flags & PASSIVE_AREA) && amount > 0) return FALSE - - if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEALS)) - return FALSE - if(!forced && (status_flags & GODMODE)) - return FALSE + if(!can_adjust_brute_loss(amount, forced, required_bodytype)) + return 0 if(amount > 0) take_overall_damage(brute = amount, updating_health = updating_health, required_bodytype = required_bodytype) else @@ -82,11 +115,8 @@ if(target_area) if((target_area.area_flags & PASSIVE_AREA) && amount > 0) return FALSE - - if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEALS)) - return FALSE - if(!forced && (status_flags & GODMODE)) - return FALSE + if(!can_adjust_fire_loss(amount, forced, required_bodytype)) + return 0 if(amount > 0) take_overall_damage(burn = amount, updating_health = updating_health, required_bodytype = required_bodytype) else @@ -100,16 +130,13 @@ return adjustFireLoss(diff, updating_health, forced, required_bodytype) -/mob/living/carbon/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = MOB_ORGANIC) +/mob/living/carbon/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL) var/area/target_area = get_area(src) if(target_area) if((target_area.area_flags & PASSIVE_AREA) && amount > 0) return FALSE - - if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEALS)) - return FALSE - if(!forced && !(mob_biotypes & required_biotype)) - return + if(!can_adjust_tox_loss(amount, forced, required_biotype)) + return 0 if(!forced && HAS_TRAIT(src, TRAIT_TOXINLOVER)) //damage becomes healing and healing becomes damage amount = -amount if(HAS_TRAIT(src, TRAIT_TOXIMMUNE)) //Prevents toxin damage, but not healing diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 9949cfccbc0a..00e69ade86a1 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -111,28 +111,6 @@ GLOBAL_LIST_EMPTY(features_by_species) var/brutemod = 1 ///multiplier for burn damage var/burnmod = 1 - ///multiplier for damage from cold temperature - var/coldmod = 1 - ///multiplier for damage from hot temperature - var/heatmod = 1 - ///multiplier for stun durations - var/stunmod = 1 - ///multiplier for money paid at payday - var/payday_modifier = 1 - ///Base electrocution coefficient. Basically a multiplier for damage from electrocutions. - var/siemens_coeff = 1 - ///To use MUTCOLOR with a fixed color that's independent of the mcolor feature in DNA. - var/fixed_mut_color = "" - ///Special mutation that can be found in the genepool exclusively in this species. Dont leave empty or changing species will be a headache - var/inert_mutation = /datum/mutation/human/dwarfism - ///Used to set the mob's death_sound upon species change - var/death_sound - ///Sounds to override barefeet walking - var/list/special_step_sounds - ///Special sound for grabbing - var/grab_sound - /// A path to an outfit that is important for species life e.g. plasmaman outfit - var/datum/outfit/outfit_important_for_life //Used for metabolizing reagents. We're going to assume you're a meatbag unless you say otherwise. var/reagent_tag = PROCESS_ORGANIC @@ -204,6 +182,33 @@ GLOBAL_LIST_EMPTY(features_by_species) ///Replaces default bladder with a different organ var/obj/item/organ/internal/bladder/mutantbladder = /obj/item/organ/internal/bladder + /// Flat modifier on all damage taken via [apply_damage][/mob/living/proc/apply_damage] (so being punched, shot, etc.) + /// IE: 10 = 10% less damage taken. + var/damage_modifier = 0 + ///multiplier for damage from cold temperature + var/coldmod = 1 + ///multiplier for damage from hot temperature + var/heatmod = 1 + ///multiplier for stun durations + var/stunmod = 1 + ///multiplier for money paid at payday + var/payday_modifier = 1.0 + ///Base electrocution coefficient. Basically a multiplier for damage from electrocutions. + var/siemens_coeff = 1 + ///To use MUTCOLOR with a fixed color that's independent of the mcolor feature in DNA. + var/fixed_mut_color = "" + ///A fixed hair color that's independent of the mcolor feature in DNA. + var/fixed_hair_color = "" + ///Special mutation that can be found in the genepool exclusively in this species. Dont leave empty or changing species will be a headache + var/inert_mutation = /datum/mutation/human/dwarfism + ///Used to set the mob's death_sound upon species change + var/death_sound + ///Sounds to override barefeet walking + var/list/special_step_sounds + ///Special sound for grabbing + var/grab_sound + /// A path to an outfit that is important for species life e.g. plasmaman outfit + var/datum/outfit/outfit_important_for_life ///Bitflag that controls what in game ways something can select this species as a spawnable source, such as magic mirrors. See [mob defines][code/__DEFINES/mobs.dm] for possible sources. var/changesource_flags = NONE @@ -1157,9 +1162,6 @@ GLOBAL_LIST_EMPTY(features_by_species) chem.overdose_start(H) H.log_message("has started overdosing on [chem.name] at [chem.volume] units.", LOG_GAME) -/datum/species/proc/check_species_weakness(obj/item, mob/living/attacker) - return 1 //This is not a boolean, it's the multiplier for the damage that the user takes from the item. The force of the item is multiplied by this value - /** * Equip the outfit required for life. Replaces items currently worn. */ @@ -1407,28 +1409,36 @@ GLOBAL_LIST_EMPTY(features_by_species) span_userdanger("You block [weapon]!")) return FALSE - var/hit_area - if(!affecting) //Something went wrong. Maybe the limb is missing? - affecting = human.bodyparts[1] - - hit_area = affecting.plaintext_zone - var/def_zone = affecting.body_zone - - var/armor_block = human.run_armor_check(affecting, MELEE, span_notice("Your armor has protected your [hit_area]!"), span_warning("Your armor has softened a hit to your [hit_area]!"),weapon.armour_penetration, weak_against_armour = weapon.weak_against_armour) - armor_block = min(ARMOR_MAX_BLOCK, armor_block) //cap damage reduction at 90% - var/Iwound_bonus = weapon.wound_bonus - + affecting ||= human.bodyparts[1] //Something went wrong. Maybe the limb is missing? + var/hit_area = affecting.plaintext_zone + var/armor_block = min(human.run_armor_check( + def_zone = affecting, + attack_flag = MELEE, + absorb_text = span_notice("Your armor has protected your [hit_area]!"), + soften_text = span_warning("Your armor has softened a hit to your [hit_area]!"), + armour_penetration = weapon.armour_penetration, + weak_against_armour = weapon.weak_against_armour, + ), ARMOR_MAX_BLOCK) //cap damage reduction at 90% + + var/modified_wound_bonus = weapon.wound_bonus // this way, you can't wound with a surgical tool on help intent if they have a surgery active and are lying down, so a misclick with a circular saw on the wrong limb doesn't bleed them dry (they still get hit tho) if((weapon.item_flags & SURGICAL_TOOL) && !(user.istate & ISTATE_HARM) && human.body_position == LYING_DOWN && (LAZYLEN(human.surgeries) > 0)) - Iwound_bonus = CANT_WOUND - - var/weakness = check_species_weakness(weapon, user) + modified_wound_bonus = CANT_WOUND human.send_item_attack_message(weapon, user, hit_area, affecting) + human.apply_damage( + damage = weapon.force, + damagetype = weapon.damtype, + def_zone = affecting, + blocked = armor_block, + wound_bonus = modified_wound_bonus, + bare_wound_bonus = weapon.bare_wound_bonus, + sharpness = weapon.get_sharpness(), + attack_direction = get_dir(user, human), + attacking_item = weapon, + ) - var/attack_direction = get_dir(user, human) - apply_damage(weapon.force * weakness, weapon.damtype, def_zone, armor_block, human, wound_bonus = Iwound_bonus, bare_wound_bonus = weapon.bare_wound_bonus, sharpness = weapon.get_sharpness(), attack_direction = attack_direction) if(!weapon.force) return FALSE //item force is zero @@ -1438,7 +1448,7 @@ GLOBAL_LIST_EMPTY(features_by_species) if(!(prob(25 + (weapon.force * 2)))) return TRUE - if(IS_ORGANIC_LIMB(affecting)) + if(affecting.can_bleed()) weapon.add_mob_blood(human) //Make the weapon bloody, not the person. if(prob(weapon.force * 2)) //blood spatter! bloody = TRUE @@ -1500,73 +1510,6 @@ GLOBAL_LIST_EMPTY(features_by_species) return TRUE -/datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null) - SEND_SIGNAL(H, COMSIG_MOB_APPLY_DAMAGE, damage, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction) - var/hit_percent = (100-(blocked+armor))/100 - hit_percent = (hit_percent * (100-H.physiology.damage_resistance))/100 - if(!damage || (!forced && hit_percent <= 0)) - return 0 - - var/obj/item/bodypart/BP = null - if(!spread_damage) - if(isbodypart(def_zone)) - BP = def_zone - else - if(!def_zone) - def_zone = H.get_random_valid_zone(def_zone) - BP = H.get_bodypart(check_zone(def_zone)) - if(!BP) - BP = H.bodyparts[1] - - switch(damagetype) - if(BRUTE) - H.damageoverlaytemp = 20 - var/damage_amount = forced ? damage : damage * hit_percent * brutemod * H.physiology.brute_mod - if(BP) - if(BP.receive_damage(damage_amount, 0, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness, attack_direction = attack_direction)) - H.update_damage_overlays() - else//no bodypart, we deal damage with a more general method. - H.adjustBruteLoss(damage_amount) - if(BURN) - H.damageoverlaytemp = 20 - var/damage_amount = forced ? damage : damage * hit_percent * burnmod * H.physiology.burn_mod - if(BP) - if(BP.receive_damage(0, damage_amount, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness, attack_direction = attack_direction)) - H.update_damage_overlays() - else - H.adjustFireLoss(damage_amount) - if(TOX) - var/damage_amount = forced ? damage : damage * hit_percent * H.physiology.tox_mod - H.adjustToxLoss(damage_amount) - if(OXY) - var/damage_amount = forced ? damage : damage * hit_percent * H.physiology.oxy_mod - H.adjustOxyLoss(damage_amount) - if(CLONE) - var/damage_amount = forced ? damage : damage * hit_percent * H.physiology.clone_mod - H.adjustCloneLoss(damage_amount) - if(STAMINA) - var/damage_amount = forced ? damage : damage * hit_percent * H.physiology.stamina_mod - H.stamina.adjust(-damage_amount) - if(BRAIN) - var/damage_amount = forced ? damage : damage * hit_percent * H.physiology.brain_mod - H.adjustOrganLoss(ORGAN_SLOT_BRAIN, damage_amount) - SEND_SIGNAL(H, COMSIG_MOB_AFTER_APPLY_DAMAGE, damage, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction) - return 1 - -/datum/species/proc/on_hit(obj/projectile/P, mob/living/carbon/human/H) - // called when hit by a projectile - switch(P.type) - if(/obj/projectile/energy/floramut) // overwritten by plants/pods - H.show_message(span_notice("The radiation beam dissipates harmlessly through your body.")) - if(/obj/projectile/energy/florayield) - H.show_message(span_notice("The radiation beam dissipates harmlessly through your body.")) - if(/obj/projectile/energy/florarevolution) - H.show_message(span_notice("The radiation beam dissipates harmlessly through your body.")) - -/datum/species/proc/bullet_act(obj/projectile/P, mob/living/carbon/human/H) - // called before a projectile hit - return 0 - ////////////////////////// // ENVIRONMENT HANDLERS // ////////////////////////// @@ -1807,19 +1750,26 @@ GLOBAL_LIST_EMPTY(features_by_species) // Lets pick a random body part and check for an existing burn var/obj/item/bodypart/bodypart = pick(humi.bodyparts) - var/datum/wound/burn/existing_burn = locate(/datum/wound/burn) in bodypart.wounds - + var/datum/wound/existing_burn + for (var/datum/wound/iterated_wound as anything in bodypart.wounds) + var/datum/wound_pregen_data/pregen_data = iterated_wound.get_pregen_data() + if (pregen_data.wound_series in GLOB.wounding_types_to_series[WOUND_BURN]) + existing_burn = iterated_wound + break // If we have an existing burn try to upgrade it + var/severity if(existing_burn) switch(existing_burn.severity) if(WOUND_SEVERITY_MODERATE) if(humi.bodytemperature > BODYTEMP_HEAT_WOUND_LIMIT + 400) // 800k - bodypart.force_wound_upwards(/datum/wound/burn/severe) + severity = WOUND_SEVERITY_SEVERE if(WOUND_SEVERITY_SEVERE) if(humi.bodytemperature > BODYTEMP_HEAT_WOUND_LIMIT + 2800) // 3200k - bodypart.force_wound_upwards(/datum/wound/burn/critical) + severity = WOUND_SEVERITY_CRITICAL else // If we have no burn apply the lowest level burn - bodypart.force_wound_upwards(/datum/wound/burn/moderate) + severity = WOUND_SEVERITY_MODERATE + + humi.cause_wound_of_type_and_severity(WOUND_BURN, bodypart, severity, wound_source = "hot temperatures") // always take some burn damage var/burn_damage = HEAT_DAMAGE_LEVEL_1 @@ -1839,11 +1789,12 @@ GLOBAL_LIST_EMPTY(features_by_species) switch(adjusted_pressure) // Very high pressure, show an alert and take damage if(HAZARD_HIGH_PRESSURE to INFINITY) - if(!HAS_TRAIT(H, TRAIT_RESISTHIGHPRESSURE)) - H.adjustBruteLoss(min(((adjusted_pressure / HAZARD_HIGH_PRESSURE) - 1) * PRESSURE_DAMAGE_COEFFICIENT, MAX_HIGH_PRESSURE_DAMAGE) * H.physiology.pressure_mod * seconds_per_tick, required_bodytype = BODYTYPE_ORGANIC) - H.throw_alert(ALERT_PRESSURE, /atom/movable/screen/alert/highpressure, 2) - else + if(HAS_TRAIT(H, TRAIT_RESISTHIGHPRESSURE)) H.clear_alert(ALERT_PRESSURE) + else + var/pressure_damage = min(((adjusted_pressure / HAZARD_HIGH_PRESSURE) - 1) * PRESSURE_DAMAGE_COEFFICIENT, MAX_HIGH_PRESSURE_DAMAGE) * H.physiology.pressure_mod * H.physiology.brute_mod * seconds_per_tick + H.adjustBruteLoss(pressure_damage, required_bodytype = BODYTYPE_ORGANIC) + H.throw_alert(ALERT_PRESSURE, /atom/movable/screen/alert/highpressure, 2) // High pressure, show an alert if(WARNING_HIGH_PRESSURE to HAZARD_HIGH_PRESSURE) @@ -1867,7 +1818,8 @@ GLOBAL_LIST_EMPTY(features_by_species) if(HAS_TRAIT(H, TRAIT_RESISTLOWPRESSURE)) H.clear_alert(ALERT_PRESSURE) else - H.adjustBruteLoss(LOW_PRESSURE_DAMAGE * H.physiology.pressure_mod * seconds_per_tick) + var/pressure_damage = LOW_PRESSURE_DAMAGE * H.physiology.pressure_mod * H.physiology.brute_mod * seconds_per_tick + H.adjustBruteLoss(pressure_damage, required_bodytype = BODYTYPE_ORGANIC) H.throw_alert(ALERT_PRESSURE, /atom/movable/screen/alert/lowpressure, 2) diff --git a/code/modules/mob/living/carbon/human/damage_procs.dm b/code/modules/mob/living/carbon/human/damage_procs.dm deleted file mode 100644 index a3d47d496adc..000000000000 --- a/code/modules/mob/living/carbon/human/damage_procs.dm +++ /dev/null @@ -1,4 +0,0 @@ - -/// depending on the species, it will run the corresponding apply_damage code there -/mob/living/carbon/human/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null) - return dna.species.apply_damage(damage, damagetype, def_zone, blocked, src, forced, spread_damage, wound_bonus, bare_wound_bonus, sharpness, attack_direction) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index cf71bec52b7b..2660d5e3196f 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -10,7 +10,7 @@ var/obj/item/bodypart/affecting = get_bodypart(check_zone(def_zone)) if(affecting) return checkarmor(affecting, type) - //If a specific bodypart is targetted, check how that bodypart is protected and return the value. + //If a specific bodypart is targeted, check how that bodypart is protected and return the value. //If you don't specify a bodypart, it checks ALL your bodyparts for protection, and averages out the values for(var/X in bodyparts) @@ -44,55 +44,43 @@ covering_part += C return covering_part -/mob/living/carbon/human/on_hit(obj/projectile/P) - if(dna?.species) - dna.species.on_hit(P, src) - - /mob/living/carbon/human/bullet_act(obj/projectile/P, def_zone, piercing_hit = FALSE) - if(dna?.species) - var/spec_return = dna.species.bullet_act(P, src) - if(spec_return) - return spec_return - //MARTIAL ART STUFF - if(mind) - if(mind.martial_art && mind.martial_art.can_use(src)) //Some martial arts users can deflect projectiles! - var/martial_art_result = mind.martial_art.on_projectile_hit(src, P, def_zone) - if(!(martial_art_result == BULLET_ACT_HIT)) - return martial_art_result - - if(!(P.original == src && P.firer == src)) //can't block or reflect when shooting yourself - if(P.reflectable & REFLECT_NORMAL) - if(check_reflect(def_zone)) // Checks if you've passed a reflection% check - visible_message(span_danger("The [P.name] gets reflected by [src]!"), \ - span_userdanger("The [P.name] gets reflected by [src]!")) - // Find a turf near or on the original location to bounce to - if(!isturf(loc)) //Open canopy mech (ripley) check. if we're inside something and still got hit - P.force_hit = TRUE //The thing we're in passed the bullet to us. Pass it back, and tell it to take the damage. - loc.bullet_act(P, def_zone, piercing_hit) - return BULLET_ACT_HIT - if(P.starting) - var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/turf/curloc = get_turf(src) - - // redirect the projectile - P.original = locate(new_x, new_y, P.z) - P.starting = curloc - P.firer = src - P.yo = new_y - curloc.y - P.xo = new_x - curloc.x - var/new_angle_s = P.Angle + rand(120,240) - while(new_angle_s > 180) // Translate to regular projectile degrees - new_angle_s -= 360 - P.set_angle(new_angle_s) - - return BULLET_ACT_FORCE_PIERCE // complete projectile permutation - - if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration)) - P.on_hit(src, 100, def_zone, piercing_hit) - return BULLET_ACT_HIT + if(P.firer == src && P.original == src) //can't block or reflect when shooting yourself + return ..() + + if(P.reflectable & REFLECT_NORMAL) + if(check_reflect(def_zone)) // Checks if you've passed a reflection% check + visible_message( + span_danger("The [P.name] gets reflected by [src]!"), + span_userdanger("The [P.name] gets reflected by [src]!"), + ) + // Find a turf near or on the original location to bounce to + if(!isturf(loc)) //Open canopy mech (ripley) check. if we're inside something and still got hit + P.force_hit = TRUE //The thing we're in passed the bullet to us. Pass it back, and tell it to take the damage. + loc.bullet_act(P, def_zone, piercing_hit) + return BULLET_ACT_HIT + if(P.starting) + var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/turf/curloc = get_turf(src) + + // redirect the projectile + P.original = locate(new_x, new_y, P.z) + P.starting = curloc + P.firer = src + P.yo = new_y - curloc.y + P.xo = new_x - curloc.x + var/new_angle_s = P.Angle + rand(120,240) + while(new_angle_s > 180) // Translate to regular projectile degrees + new_angle_s -= 360 + P.set_angle(new_angle_s) + + return BULLET_ACT_FORCE_PIERCE // complete projectile permutation + + if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration, P.damage_type)) + P.on_hit(src, 100, def_zone, piercing_hit) + return BULLET_ACT_HIT return ..() diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 091a219b872a..80fd5ade9649 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -65,7 +65,11 @@ var/list/datum/bioware = list() /// What types of mobs are allowed to ride/buckle to this mob - var/static/list/can_ride_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/simple_animal/slime, /mob/living/simple_animal/parrot)) + var/static/list/can_ride_typecache = typecacheof(list( + /mob/living/basic/parrot, + /mob/living/carbon/human, + /mob/living/simple_animal/slime, + )) var/lastpuke = 0 var/account_id diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 6637b6174ca3..ccd30def5fce 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -171,13 +171,15 @@ if(LAZYLEN(scar_data) != SCAR_SAVE_LENGTH) return // invalid, should delete var/version = text2num(scar_data[SCAR_SAVE_VERS]) - if(!version || version < SCAR_CURRENT_VERSION) // get rid of old scars + if(!version || version != SCAR_CURRENT_VERSION) // get rid of scars using a incompatable version return if(specified_char_index && (mind?.original_character_slot_index != specified_char_index)) return + if (isnull(text2num(scar_data[SCAR_SAVE_BIOLOGY]))) + return var/obj/item/bodypart/the_part = get_bodypart("[scar_data[SCAR_SAVE_ZONE]]") var/datum/scar/scaries = new - return scaries.load(the_part, scar_data[SCAR_SAVE_VERS], scar_data[SCAR_SAVE_DESC], scar_data[SCAR_SAVE_PRECISE_LOCATION], text2num(scar_data[SCAR_SAVE_SEVERITY]), text2num(scar_data[SCAR_SAVE_BIOLOGY]), text2num(scar_data[SCAR_SAVE_CHAR_SLOT])) + return scaries.load(the_part, scar_data[SCAR_SAVE_VERS], scar_data[SCAR_SAVE_DESC], scar_data[SCAR_SAVE_PRECISE_LOCATION], text2num(scar_data[SCAR_SAVE_SEVERITY]), text2num(scar_data[SCAR_SAVE_BIOLOGY]), text2num(scar_data[SCAR_SAVE_CHAR_SLOT]), text2num(scar_data[SCAR_SAVE_CHECK_ANY_BIO])) /// Read all the scars we have for the designated character/scar slots, verify they're good/dump them if they're old/wrong format, create them on the user, and write the scars that passed muster back to the file /mob/living/carbon/human/proc/load_persistent_scars() diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm index 2c371e78b7ff..e7ce621533be 100644 --- a/code/modules/mob/living/carbon/human/human_update_icons.dm +++ b/code/modules/mob/living/carbon/human/human_update_icons.dm @@ -550,12 +550,7 @@ There are several things that need to be remembered: apply_overlay(LEGCUFF_LAYER) throw_alert("legcuffed", /atom/movable/screen/alert/restrained/legcuffed, new_master = src.legcuffed) -/mob/living/carbon/human/update_held_items() - remove_overlay(HANDS_LAYER) - if (handcuffed) - drop_all_held_items() - return - +/mob/living/carbon/human/get_held_overlays() var/list/hands = list() for(var/obj/item/worn_item in held_items) if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) @@ -589,10 +584,7 @@ There are several things that need to be remembered: hand_overlay.pixel_y += dna.species.offset_features[OFFSET_HANDS][2] hands += hand_overlay - - - overlays_standing[HANDS_LAYER] = hands - apply_overlay(HANDS_LAYER) + return hands /proc/wear_female_version(t_color, icon, layer, type, greyscale_colors) var/index = "[t_color]-[greyscale_colors]" diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 214451c1cbec..678442bd79d3 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -19,7 +19,7 @@ #define THERMAL_PROTECTION_HAND_RIGHT 0.025 /mob/living/carbon/human/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if(notransform) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return . = ..() diff --git a/code/modules/mob/living/carbon/human/physiology.dm b/code/modules/mob/living/carbon/human/physiology.dm index f83cc5e6203c..3d52aab08928 100644 --- a/code/modules/mob/living/carbon/human/physiology.dm +++ b/code/modules/mob/living/carbon/human/physiology.dm @@ -1,19 +1,34 @@ //Stores several modifiers in a way that isn't cleared by changing species /datum/physiology - var/brute_mod = 1 // % of brute damage taken from all sources - var/burn_mod = 1 // % of burn damage taken from all sources - var/tox_mod = 1 // % of toxin damage taken from all sources - var/oxy_mod = 1 // % of oxygen damage taken from all sources - var/clone_mod = 1 // % of clone damage taken from all sources - var/stamina_mod = 1 // % of stamina damage taken from all sources - var/brain_mod = 1 // % of brain damage taken from all sources + /// Multiplier to brute damage received. + /// IE: A brute mod of 0.9 = 10% less brute damage. + /// Only applies to damage dealt via [apply_damage][/mob/living/proc/apply_damage] unless factored in manually. + var/brute_mod = 1 + /// Multiplier to burn damage received + var/burn_mod = 1 + /// Multiplier to toxin damage received + var/tox_mod = 1 + /// Multiplier to oxygen damage received + var/oxy_mod = 1 + /// Multiplier to clone damage received + var/clone_mod = 1 + /// Multiplier to stamina damage received + var/stamina_mod = 1 + /// Multiplier to brain damage received + var/brain_mod = 1 - var/pressure_mod = 1 // % of brute damage taken from low or high pressure (stacks with brute_mod) - var/heat_mod = 1 // % of burn damage taken from heat (stacks with burn_mod) - var/cold_mod = 1 // % of burn damage taken from cold (stacks with burn_mod) + /// Multiplier to damage taken from high / low pressure exposure, stacking with the brute modifier + var/pressure_mod = 1 + /// Multiplier to damage taken from high temperature exposure, stacking with the burn modifier + var/heat_mod = 1 + /// Multiplier to damage taken from low temperature exposure, stacking with the toxin modifier + var/cold_mod = 1 - var/damage_resistance = 0 // %damage reduction from all sources + /// Flat damage reduction from taking damage + /// Unlike the other modifiers, this is not a multiplier. + /// IE: DR of 10 = 10% less damage. + var/damage_resistance = 0 var/siemens_coeff = 1 // resistance to shocks diff --git a/code/modules/mob/living/carbon/human/species_types/abductors.dm b/code/modules/mob/living/carbon/human/species_types/abductors.dm index 3706d1144570..ad38e3331f93 100644 --- a/code/modules/mob/living/carbon/human/species_types/abductors.dm +++ b/code/modules/mob/living/carbon/human/species_types/abductors.dm @@ -11,6 +11,8 @@ TRAIT_NOHUNGER, TRAIT_VIRUSIMMUNE, TRAIT_NOBLOOD, + TRAIT_NODISMEMBER, + TRAIT_NEVER_WOUNDED ) mutanttongue = /obj/item/organ/internal/tongue/abductor mutantstomach = null diff --git a/code/modules/mob/living/carbon/human/species_types/flypeople.dm b/code/modules/mob/living/carbon/human/species_types/flypeople.dm index c70567af7e95..86897dec5cfb 100644 --- a/code/modules/mob/living/carbon/human/species_types/flypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/flypeople.dm @@ -35,17 +35,19 @@ BODY_ZONE_CHEST = /obj/item/bodypart/chest/fly, ) -/datum/species/fly/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H, seconds_per_tick, times_fired) - if(chem.type == /datum/reagent/toxin/pestkiller) - H.adjustToxLoss(3 * REM * seconds_per_tick) - H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM * seconds_per_tick) - return TRUE - return ..() +/datum/species/fly/on_species_gain(mob/living/carbon/human/human_who_gained_species, datum/species/old_species, pref_load) + . = ..() + RegisterSignal(human_who_gained_species, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS, PROC_REF(damage_weakness)) -/datum/species/fly/check_species_weakness(obj/item/weapon, mob/living/attacker) - if(istype(weapon, /obj/item/melee/flyswatter)) - return 30 //Flyswatters deal 30x damage to flypeople. - return 1 +/datum/species/fly/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load) + . = ..() + UnregisterSignal(C, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS) + +/datum/species/fly/proc/damage_weakness(datum/source, list/damage_mods, damage_amount, damagetype, def_zone, sharpness, attack_direction, obj/item/attacking_item) + SIGNAL_HANDLER + + if(istype(attacking_item, /obj/item/melee/flyswatter)) + damage_mods += 30 // Yes, a 30x damage modifier /datum/species/fly/get_species_description() return "With no official documentation or knowledge of the origin of \ 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 ff0ad9078547..45d36076c6f6 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -11,15 +11,14 @@ inherent_traits = list( TRAIT_GENELESS, TRAIT_NOBREATH, - TRAIT_NODISMEMBER, + TRAIT_NOBLOOD, TRAIT_NOFIRE, TRAIT_PIERCEIMMUNE, TRAIT_RADIMMUNE, - TRAIT_RESISTCOLD, - TRAIT_RESISTHEAT, - TRAIT_RESISTHIGHPRESSURE, - TRAIT_RESISTLOWPRESSURE, - TRAIT_NOBLOOD, + TRAIT_NO_DNA_COPY, + TRAIT_NO_TRANSFORMATION_STING, + TRAIT_NODISMEMBER, + TRAIT_NEVER_WOUNDED ) mutantheart = null mutantlungs = null @@ -404,11 +403,6 @@ if(COOLDOWN_FINISHED(src, radiation_emission_cooldown) && user != H) radiation_emission(H) -/datum/species/golem/uranium/on_hit(obj/projectile/P, mob/living/carbon/human/H) - ..() - if(COOLDOWN_FINISHED(src, radiation_emission_cooldown)) - radiation_emission(H) - //Immune to physical bullets and resistant to brute, but very vulnerable to burn damage. Dusts on death. /datum/species/golem/sand name = "Sand Golem" @@ -431,6 +425,7 @@ new /obj/item/stack/ore/glass(get_turf(H)) qdel(H) +/* /datum/species/golem/sand/bullet_act(obj/projectile/P, mob/living/carbon/human/H) if(!(P.original == H && P.firer == H)) if(P.armor_flag == BULLET || P.armor_flag == BOMB) @@ -439,6 +434,7 @@ span_userdanger("The [P.name] sinks harmlessly in [H]'s sandy body!")) return BULLET_ACT_BLOCK return ..() +*/ //Reflects lasers and resistant to burn damage, but very vulnerable to brute damage. Shatters on death. /datum/species/golem/glass @@ -463,6 +459,7 @@ new /obj/item/shard(get_turf(H)) qdel(H) +/* /datum/species/golem/glass/bullet_act(obj/projectile/P, mob/living/carbon/human/H) if(!(P.original == H && P.firer == H)) //self-shots don't reflect if(P.armor_flag == LASER || P.armor_flag == ENERGY) @@ -476,6 +473,7 @@ P.preparePixelProjectile(locate(clamp(new_x, 1, world.maxx), clamp(new_y, 1, world.maxy), H.z), H) return BULLET_ACT_FORCE_PIERCE return ..() +*/ //Teleports when hit or when it wants to /datum/species/golem/bluespace @@ -519,11 +517,6 @@ if(world.time > last_teleport + teleport_cooldown && user != H) reactive_teleport(H) -/datum/species/golem/bluespace/on_hit(obj/projectile/P, mob/living/carbon/human/H) - ..() - if(world.time > last_teleport + teleport_cooldown) - reactive_teleport(H) - /datum/species/golem/bluespace/on_species_gain(mob/living/carbon/C, datum/species/old_species) ..() if(ishuman(C)) @@ -635,12 +628,6 @@ new /obj/item/grown/bananapeel/specialpeel(get_turf(H)) COOLDOWN_START(src, banana_cooldown, banana_delay) -/datum/species/golem/bananium/on_hit(obj/projectile/P, mob/living/carbon/human/H) - ..() - if(COOLDOWN_FINISHED(src, banana_cooldown)) - new /obj/item/grown/bananapeel/specialpeel(get_turf(H)) - COOLDOWN_START(src, banana_cooldown, banana_delay) - /datum/species/golem/bananium/spec_hitby(atom/movable/AM, mob/living/carbon/human/H) ..() var/obj/item/I @@ -962,12 +949,14 @@ var/last_gong_time = 0 var/gong_cooldown = 150 +/* /datum/species/golem/bronze/bullet_act(obj/projectile/P, mob/living/carbon/human/H) if(!(world.time > last_gong_time + gong_cooldown)) return ..() if(P.armor_flag == BULLET || P.armor_flag == BOMB) gong(H) return ..() +*/ /datum/species/golem/bronze/spec_hitby(atom/movable/AM, mob/living/carbon/human/H) ..() @@ -984,10 +973,6 @@ if(world.time > last_gong_time + gong_cooldown) gong(H) -/datum/species/golem/bronze/on_hit(obj/projectile/P, mob/living/carbon/human/H) - ..() - if(world.time > last_gong_time + gong_cooldown) - gong(H) /datum/species/golem/bronze/proc/gong(mob/living/carbon/human/H) last_gong_time = world.time 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 8378976261fa..ac9265606ba2 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -272,12 +272,12 @@ if(!isslimeperson(H)) return CHECK_DNA_AND_SPECIES(H) - H.visible_message("[owner] gains a look of \ - concentration while standing perfectly still.", - "You focus intently on moving your body while \ - standing perfectly still...") + H.visible_message( + span_notice("[owner] gains a look of concentration while standing perfectly still."), + span_notice("You focus intently on moving your body while standing perfectly still..."), + ) - H.notransform = TRUE + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, REF(src)) if(do_after(owner, delay = 6 SECONDS, target = owner, timed_action_flags = IGNORE_HELD_ITEM)) if(H.blood_volume >= BLOOD_VOLUME_SLIME_SPLIT) @@ -287,7 +287,7 @@ else to_chat(H, span_warning("...but fail to stand perfectly still!")) - H.notransform = FALSE + REMOVE_TRAIT(src, TRAIT_NO_TRANSFORM, REF(src)) /datum/action/innate/split_body/proc/make_dupe() var/mob/living/carbon/human/H = owner @@ -313,7 +313,7 @@ SEND_SIGNAL(spare, COMSIG_NANITE_SYNC, owner_nanites, TRUE, TRUE) //The trues are to copy activation as well H.blood_volume *= 0.45 - H.notransform = 0 + REMOVE_TRAIT(H, TRAIT_NO_TRANSFORM, REF(src)) var/datum/species/jelly/slime/origin_datum = H.dna.species origin_datum.bodies |= spare @@ -323,10 +323,10 @@ H.transfer_quirk_datums(spare) H.mind.transfer_to(spare) - spare.visible_message("[H] distorts as a new body \ - \"steps out\" of [H.p_them()].", - "...and after a moment of disorentation, \ - you're besides yourself!") + spare.visible_message( + span_warning("[H] distorts as a new body \"steps out\" of [H.p_them()]."), + span_notice("...and after a moment of disorentation, you're besides yourself!"), + ) /datum/action/innate/swap_body diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm index 7c89d711bc41..518132aed5e4 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -51,16 +51,19 @@ return randname -/datum/species/moth/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H, seconds_per_tick, times_fired) +/datum/species/moth/on_species_gain(mob/living/carbon/human/human_who_gained_species, datum/species/old_species, pref_load) . = ..() - if(chem.type == /datum/reagent/toxin/pestkiller) - H.adjustToxLoss(3 * REM * seconds_per_tick) - H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM * seconds_per_tick) - -/datum/species/moth/check_species_weakness(obj/item/weapon, mob/living/attacker) - if(istype(weapon, /obj/item/melee/flyswatter)) - return 10 //flyswatters deal 10x damage to moths - return 1 + RegisterSignal(human_who_gained_species, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS, PROC_REF(damage_weakness)) + +/datum/species/moth/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load) + . = ..() + UnregisterSignal(C, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS) + +/datum/species/moth/proc/damage_weakness(datum/source, list/damage_mods, damage_amount, damagetype, def_zone, sharpness, attack_direction, obj/item/attacking_item) + SIGNAL_HANDLER + + if(istype(attacking_item, /obj/item/melee/flyswatter)) + damage_mods += 10 // Yes, a 10x damage modifier /datum/species/moth/randomize_features(mob/living/carbon/human/human_mob) diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm index a1812d845ec6..9812b2fad673 100644 --- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm @@ -13,6 +13,8 @@ TRAIT_RADIMMUNE, TRAIT_VIRUSIMMUNE, TRAIT_NOBLOOD, + TRAIT_NODISMEMBER, + TRAIT_NEVER_WOUNDED ) inherent_factions = list(FACTION_FAITHLESS) changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm index 1b21c8620134..49c8a32db086 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -45,6 +45,11 @@ new_vampire.skin_tone = "albino" new_vampire.update_body(0) new_vampire.set_safe_hunger_level() + RegisterSignal(new_vampire, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS, PROC_REF(damage_weakness)) + +/datum/species/vampire/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load) + . = ..() + UnregisterSignal(C, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS) /datum/species/vampire/spec_life(mob/living/carbon/human/vampire, seconds_per_tick, times_fired) . = ..() @@ -66,10 +71,11 @@ vampire.adjust_fire_stacks(3 * seconds_per_tick) vampire.ignite_mob() -/datum/species/vampire/check_species_weakness(obj/item/weapon, mob/living/attacker) - if(istype(weapon, /obj/item/nullrod/whip)) - return 2 //Whips deal 2x damage to vampires. Vampire killer. - return 1 +/datum/species/vampire/proc/damage_weakness(datum/source, list/damage_mods, damage_amount, damagetype, def_zone, sharpness, attack_direction, obj/item/attacking_item) + SIGNAL_HANDLER + + if(istype(attacking_item, /obj/item/nullrod/whip)) + damage_mods += 2 /datum/species/vampire/get_species_description() return "A classy Vampire! They descend upon Space Station Thirteen Every year to spook the crew! \"Bleeg!!\"" diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm index a859d6fd0f0d..b7f9b81de523 100644 --- a/code/modules/mob/living/carbon/human/species_types/zombies.dm +++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm @@ -131,11 +131,6 @@ /datum/species/zombie/infectious/spec_stun(mob/living/carbon/human/H,amount) . = min(20, amount) -/datum/species/zombie/infectious/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, spread_damage = FALSE, forced = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null) - . = ..() - if(.) - COOLDOWN_START(src, regen_cooldown, REGENERATION_DELAY) - /datum/species/zombie/infectious/spec_life(mob/living/carbon/C, seconds_per_tick, times_fired) . = ..() C.set_combat_mode(TRUE) // THE SUFFERING MUST FLOW diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 9f167ad038a6..7aa38d0e373f 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -1,6 +1,5 @@ /mob/living/carbon/Life(seconds_per_tick = SSMOBS_DT, times_fired) - - if(notransform) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return if(isopenturf(loc)) diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index b005a2db66ca..bb0b71635fae 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -1,59 +1,148 @@ /** - * Applies damage to this mob + * Applies damage to this mob. * * Sends [COMSIG_MOB_APPLY_DAMAGE] * * Arguuments: - * * damage - amount of damage - * * damagetype - one of [BRUTE], [BURN], [TOX], [OXY], [CLONE], [STAMINA] - * * def_zone - zone that is being hit if any - * * blocked - armor value applied - * * forced - bypass hit percentage - * * spread_damage - used in overrides + * * damage - Amount of damage + * * damagetype - What type of damage to do. one of [BRUTE], [BURN], [TOX], [OXY], [CLONE], [STAMINA], [BRAIN]. + * * def_zone - What body zone is being hit. Or a reference to what bodypart is being hit. + * * blocked - Percent modifier to damage. 100 = 100% less damage dealt, 50% = 50% less damage dealt. + * * forced - "Force" exactly the damage dealt. This means it skips damage modifier from blocked. + * * spread_damage - For carbons, spreads the damage across all bodyparts rather than just the targeted zone. + * * wound_bonus - Bonus modifier for wound chance. + * * bare_wound_bonus - Bonus modifier for wound chance on bare skin. + * * sharpness - Sharpness of the weapon. + * * attack_direction - Direction of the attack from the attacker to [src]. + * * attacking_item - Item that is attacking [src]. * - * Returns TRUE if damage applied + * Returns the amount of damage dealt. */ -/mob/living/proc/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null) - SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMAGE, damage, damagetype, def_zone) - var/hit_percent = (100-blocked)/100 - if(!damage || (!forced && hit_percent <= 0)) - return FALSE - var/damage_amount = forced ? damage : damage * hit_percent +/mob/living/proc/apply_damage( + damage = 0, + damagetype = BRUTE, + def_zone = null, + blocked = 0, + forced = FALSE, + spread_damage = FALSE, + wound_bonus = 0, + bare_wound_bonus = 0, + sharpness = NONE, + attack_direction = null, + attacking_item, +) + SHOULD_CALL_PARENT(TRUE) + var/damage_amount = damage + if(!forced) + damage_amount *= ((100 - blocked) / 100) + damage_amount *= get_incoming_damage_modifier(damage_amount, damagetype, def_zone, sharpness, attack_direction, attacking_item) + if(damage_amount <= 0) + return 0 + + SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMAGE, damage_amount, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction, attacking_item) + + var/damage_dealt = 0 switch(damagetype) if(BRUTE) - adjustBruteLoss(damage_amount, forced = forced) + if(isbodypart(def_zone)) + var/obj/item/bodypart/actual_hit = def_zone + var/delta = actual_hit.get_damage() + if(actual_hit.receive_damage( + brute = damage_amount, + burn = 0, + forced = forced, + wound_bonus = wound_bonus, + bare_wound_bonus = bare_wound_bonus, + sharpness = sharpness, + attack_direction = attack_direction, + damage_source = attacking_item, + )) + update_damage_overlays() + damage_dealt = actual_hit.get_damage() - delta // Unfortunately bodypart receive_damage doesn't return damage dealt so we do it manually + else + damage_dealt = adjustBruteLoss(damage_amount, forced = forced) if(BURN) - adjustFireLoss(damage_amount, forced = forced) + if(isbodypart(def_zone)) + var/obj/item/bodypart/actual_hit = def_zone + var/delta = actual_hit.get_damage() + if(actual_hit.receive_damage( + brute = 0, + burn = damage_amount, + forced = forced, + wound_bonus = wound_bonus, + bare_wound_bonus = bare_wound_bonus, + sharpness = sharpness, + attack_direction = attack_direction, + damage_source = attacking_item, + )) + update_damage_overlays() + damage_dealt = delta - actual_hit.get_damage() // See above + else + damage_dealt = adjustFireLoss(damage_amount, forced = forced) if(TOX) - adjustToxLoss(damage_amount, forced = forced) + damage_dealt = adjustToxLoss(damage_amount, forced = forced) if(OXY) - adjustOxyLoss(damage_amount, forced = forced) + damage_dealt = adjustOxyLoss(damage_amount, forced = forced) if(CLONE) - adjustCloneLoss(damage_amount, forced = forced) + damage_dealt = adjustCloneLoss(damage_amount, forced = forced) if(STAMINA) - stamina.adjust(-damage_amount, forced = forced) - SEND_SIGNAL(src, COMSIG_MOB_AFTER_APPLY_DAMAGE, damage, damagetype, def_zone) - return TRUE + damage_dealt = stamina.adjust(-damage) + if(BRAIN) + damage_dealt = adjustOrganLoss(ORGAN_SLOT_BRAIN, damage_amount) + + SEND_SIGNAL(src, COMSIG_MOB_AFTER_APPLY_DAMAGE, damage_dealt, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction, attacking_item) + return damage_dealt + +/** + * Used in tandem with [/mob/living/proc/apply_damage] to calculate modifier applied into incoming damage + */ +/mob/living/proc/get_incoming_damage_modifier( + damage = 0, + damagetype = BRUTE, + def_zone = null, + sharpness = NONE, + attack_direction = null, + attacking_item, +) + SHOULD_CALL_PARENT(TRUE) + SHOULD_BE_PURE(TRUE) + + var/list/damage_mods = list() + SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS, damage_mods, damage, damagetype, def_zone, sharpness, attack_direction, attacking_item) + + var/final_mod = 1 + for(var/new_mod in damage_mods) + final_mod *= new_mod + return final_mod + +/** + * Simply a wrapper for calling mob adjustXLoss() procs to heal a certain damage type, + * when you don't know what damage type you're healing exactly. + */ +/mob/living/proc/heal_damage_type(heal_amount = 0, damagetype = BRUTE) + heal_amount = abs(heal_amount) * -1 -///like [apply_damage][/mob/living/proc/apply_damage] except it always uses the damage procs -/mob/living/proc/apply_damage_type(damage = 0, damagetype = BRUTE) switch(damagetype) if(BRUTE) - return adjustBruteLoss(damage) + return adjustBruteLoss(heal_amount) if(BURN) - return adjustFireLoss(damage) + return adjustFireLoss(heal_amount) if(TOX) - return adjustToxLoss(damage) + return adjustToxLoss(heal_amount) if(OXY) - return adjustOxyLoss(damage) + return adjustOxyLoss(heal_amount) if(CLONE) - return adjustCloneLoss(damage) + return adjustCloneLoss(heal_amount) if(STAMINA) - return stamina.adjust(-damage) + return stamina.adjust(heal_amount) /// return the damage amount for the type given -/mob/living/proc/get_damage_amount(damagetype = BRUTE) +/** + * Simply a wrapper for calling mob getXLoss() procs to get a certain damage type, + * when you don't know what damage type you're getting exactly. + */ +/mob/living/proc/get_current_damage_of_type(damagetype = BRUTE) switch(damagetype) if(BRUTE) return getBruteLoss() @@ -68,26 +157,34 @@ if(STAMINA) return stamina.loss -/// applies multiple damages at once via [/mob/living/proc/apply_damage] -/mob/living/proc/apply_damages(brute = 0, burn = 0, tox = 0, oxy = 0, clone = 0, def_zone = null, blocked = FALSE, stamina = 0, brain = 0) - if(blocked >= 100) - return 0 +/// Applies multiple damages at once via [apply_damage][/mob/living/proc/apply_damage] +/mob/living/proc/apply_damages( + brute = 0, + burn = 0, + tox = 0, + oxy = 0, + clone = 0, + def_zone = null, + blocked = 0, + stamina = 0, + brain = 0, +) + var/total_damage = 0 if(brute) - apply_damage(brute, BRUTE, def_zone, blocked) + total_damage += apply_damage(brute, BRUTE, def_zone, blocked) if(burn) - apply_damage(burn, BURN, def_zone, blocked) + total_damage += apply_damage(burn, BURN, def_zone, blocked) if(tox) - apply_damage(tox, TOX, def_zone, blocked) + total_damage += apply_damage(tox, TOX, def_zone, blocked) if(oxy) - apply_damage(oxy, OXY, def_zone, blocked) + total_damage += apply_damage(oxy, OXY, def_zone, blocked) if(clone) - apply_damage(clone, CLONE, def_zone, blocked) + total_damage += apply_damage(clone, CLONE, def_zone, blocked) if(stamina) - apply_damage(stamina, STAMINA, def_zone, blocked) + total_damage += apply_damage(stamina, STAMINA, def_zone, blocked) if(brain) - apply_damage(brain, BRAIN, def_zone, blocked) - return 1 - + total_damage += apply_damage(brain, BRAIN, def_zone, blocked) + return total_damage /// applies various common status effects or common hardcoded mob effects /mob/living/proc/apply_effect(effect = 0,effecttype = EFFECT_STUN, blocked = 0) @@ -159,21 +256,39 @@ return TRUE +/// Returns a multiplier to apply to a specific kind of damage +/mob/living/proc/get_damage_mod(damage_type) + switch(damage_type) + if (OXY) + return HAS_TRAIT(src, TRAIT_NOBREATH) ? 0 : 1 + if (TOX) + if (HAS_TRAIT(src, TRAIT_TOXINLOVER)) + return -1 + return HAS_TRAIT(src, TRAIT_TOXIMMUNE) ? 0 : 1 + return 1 /mob/living/proc/getBruteLoss() return bruteloss -/mob/living/proc/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) +/mob/living/proc/can_adjust_brute_loss(amount, forced, required_bodytype) var/area/target_area = get_area(src) if(target_area) if((target_area.area_flags & PASSIVE_AREA) && amount > 0) return FALSE - - if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEALS)) - return FALSE if(!forced && (status_flags & GODMODE)) return FALSE + if(SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_BRUTE_DAMAGE, BRUTE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return FALSE + return TRUE + +/mob/living/proc/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype = ALL) + if (!can_adjust_brute_loss(amount, forced, required_bodytype)) + return 0 + . = bruteloss bruteloss = clamp((bruteloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) + . -= bruteloss + if(!.) // no change, no need to update + return 0 if(updating_health) updatehealth() return amount @@ -189,18 +304,29 @@ /mob/living/proc/getOxyLoss() return oxyloss -/mob/living/proc/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype, required_respiration_type = ALL) +/mob/living/proc/can_adjust_oxy_loss(amount, forced, required_biotype, required_respiration_type) + var/area/target_area = get_area(src) + if(target_area) + if((target_area.area_flags & PASSIVE_AREA) && amount > 0) + return FALSE if(!forced) if(status_flags & GODMODE) - return + return FALSE + if (required_respiration_type) + var/obj/item/organ/internal/lungs/affected_lungs = get_organ_slot(ORGAN_SLOT_LUNGS) + if(isnull(affected_lungs)) + if(!(mob_respiration_type & required_respiration_type)) // if the mob has no lungs, use mob_respiration_type + return FALSE + else + if(!(affected_lungs.respiration_type & required_respiration_type)) // otherwise use the lungs' respiration_type + return FALSE + if(SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_OXY_DAMAGE, OXY, amount, forced) & COMPONENT_IGNORE_CHANGE) + return FALSE + return TRUE - var/obj/item/organ/internal/lungs/affected_lungs = get_organ_slot(ORGAN_SLOT_LUNGS) - if(isnull(affected_lungs)) - if(!(mob_respiration_type & required_respiration_type)) // if the mob has no lungs, use mob_respiration_type - return - else - if(!(affected_lungs.respiration_type & required_respiration_type)) // otherwise use the lungs' respiration_type - return +/mob/living/proc/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL, required_respiration_type = ALL) + if(!can_adjust_oxy_loss(amount, forced, required_biotype, required_respiration_type)) + return 0 . = oxyloss oxyloss = clamp((oxyloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) if(updating_health) @@ -228,11 +354,27 @@ /mob/living/proc/getToxLoss() return toxloss -/mob/living/proc/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) +/mob/living/proc/can_adjust_tox_loss(amount, forced, required_biotype) + if(!forced && ((status_flags & GODMODE) || !(mob_biotypes & required_biotype))) + return FALSE + if(SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_TOX_DAMAGE, TOX, amount, forced) & COMPONENT_IGNORE_CHANGE) + return FALSE + return TRUE + +/mob/living/proc/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL) var/area/target_area = get_area(src) if(target_area) if((target_area.area_flags & PASSIVE_AREA) && amount > 0) return FALSE + if(!can_adjust_tox_loss(amount, forced, required_biotype)) + return 0 + . = toxloss + toxloss = clamp((toxloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) + . -= toxloss + if(!.) // no change, no need to update + return FALSE + if(updating_health) + updatehealth() if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEALS)) return FALSE @@ -258,43 +400,62 @@ /mob/living/proc/getFireLoss() return fireloss -/mob/living/proc/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) +/mob/living/proc/can_adjust_fire_loss(amount, forced, required_bodytype) + if(!forced && (status_flags & GODMODE)) + return FALSE + if(SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_BURN_DAMAGE, BURN, amount, forced) & COMPONENT_IGNORE_CHANGE) + return FALSE + return TRUE + +/mob/living/proc/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype = ALL) var/area/target_area = get_area(src) if(target_area) if((target_area.area_flags & PASSIVE_AREA) && amount > 0) return FALSE - - if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEALS)) - return FALSE - if(!forced && (status_flags & GODMODE)) - return FALSE + if(!can_adjust_fire_loss(amount, forced, required_bodytype)) + return 0 + . = fireloss fireloss = clamp((fireloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) + . -= fireloss + if(. == 0) // no change, no need to update + return if(updating_health) updatehealth() return amount /mob/living/proc/setFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) if(!forced && (status_flags & GODMODE)) - return + return 0 . = fireloss fireloss = amount + . -= fireloss + if(. == 0) // no change, no need to update + return 0 if(updating_health) updatehealth() /mob/living/proc/getCloneLoss() return cloneloss -/mob/living/proc/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) +/mob/living/proc/can_adjust_clone_loss(amount, forced, required_biotype) + if(!forced && (!(mob_biotypes & required_biotype) || status_flags & GODMODE || HAS_TRAIT(src, TRAIT_NOCLONELOSS))) + return FALSE + if(SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_CLONE_DAMAGE, CLONE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return FALSE + return TRUE + +/mob/living/proc/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL) var/area/target_area = get_area(src) if(target_area) if((target_area.area_flags & PASSIVE_AREA) && amount > 0) return FALSE - - if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEALS)) - return FALSE - if(!forced && ( (status_flags & GODMODE) || HAS_TRAIT(src, TRAIT_NOCLONELOSS)) ) - return FALSE + if(!can_adjust_clone_loss(amount, forced, required_biotype)) + return 0 + . = cloneloss cloneloss = clamp((cloneloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) + . -= cloneloss + if(. == 0) // no change, no need to update + return 0 if(updating_health) updatehealth() return amount @@ -357,11 +518,11 @@ ///heal up to amount damage, in a given order /mob/living/proc/heal_ordered_damage(amount, list/damage_types) - . = amount //we'll return the amount of damage healed - for(var/i in damage_types) - var/amount_to_heal = min(amount, get_damage_amount(i)) //heal only up to the amount of damage we have + . = 0 //we'll return the amount of damage healed + for(var/damagetype in damage_types) + var/amount_to_heal = min(abs(amount), get_current_damage_of_type(damagetype)) //heal only up to the amount of damage we have if(amount_to_heal) - apply_damage_type(-amount_to_heal, i) + . += heal_damage_type(amount_to_heal, damagetype) amount -= amount_to_heal //remove what we healed from our current amount if(!amount) break diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index a4c331267820..55b97ff38ed4 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -55,6 +55,7 @@ dust_animation() spawn_dust(just_ash) + ghostize() QDEL_IN(src,5) // since this is sometimes called in the middle of movement, allow half a second for movement to finish, ghosting to happen and animation to play. Looks much nicer and doesn't cause multiple runtimes. /mob/living/proc/dust_animation() diff --git a/code/modules/mob/living/inhand_holder.dm b/code/modules/mob/living/inhand_holder.dm index c018b3c029bf..b4c9fbd34aa1 100644 --- a/code/modules/mob/living/inhand_holder.dm +++ b/code/modules/mob/living/inhand_holder.dm @@ -114,11 +114,11 @@ desc = "This drone is scared and has curled up into a ball!" /obj/item/clothing/head/mob_holder/drone/update_visuals(mob/living/L) - var/mob/living/simple_animal/drone/D = L - if(!D) + var/mob/living/basic/drone/drone = L + if(!drone) return ..() icon = 'icons/mob/silicon/drone.dmi' - icon_state = "[D.visualAppearance]_hat" + icon_state = "[drone.visualAppearance]_hat" /obj/item/clothing/head/mob_holder/destructible diff --git a/code/modules/mob/living/init_signals.dm b/code/modules/mob/living/init_signals.dm index 6c4d59f93df1..fae78c27fc86 100644 --- a/code/modules/mob/living/init_signals.dm +++ b/code/modules/mob/living/init_signals.dm @@ -44,6 +44,7 @@ RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_SKITTISH), PROC_REF(on_skittish_trait_gain)) RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_SKITTISH), PROC_REF(on_skittish_trait_loss)) + RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_UNDENSE), SIGNAL_REMOVETRAIT(TRAIT_UNDENSE)), PROC_REF(undense_changed)) RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_NEGATES_GRAVITY), SIGNAL_REMOVETRAIT(TRAIT_NEGATES_GRAVITY)), PROC_REF(on_negate_gravity)) RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_IGNORING_GRAVITY), SIGNAL_REMOVETRAIT(TRAIT_IGNORING_GRAVITY)), PROC_REF(on_ignore_gravity)) RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_FORCED_GRAVITY), SIGNAL_REMOVETRAIT(TRAIT_FORCED_GRAVITY)), PROC_REF(on_force_gravity)) @@ -245,3 +246,8 @@ /mob/living/proc/on_loc_force_gravity(datum/source) SIGNAL_HANDLER refresh_gravity() + +/// Called when [TRAIT_UNDENSE] is gained or lost +/mob/living/proc/undense_changed(datum/source) + SIGNAL_HANDLER + update_density() diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 8508b1c2ca8f..27995ce1ce00 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -12,7 +12,10 @@ /mob/living/proc/Life(seconds_per_tick = SSMOBS_DT, times_fired) set waitfor = FALSE - SEND_SIGNAL(src, COMSIG_LIVING_LIFE, seconds_per_tick, times_fired) + var/signal_result = SEND_SIGNAL(src, COMSIG_LIVING_LIFE, seconds_per_tick, times_fired) + + if(signal_result & COMPONENT_LIVING_CANCEL_LIFE_PROCESSING) // mmm less work + return if (client) var/turf/T = get_turf(src) @@ -34,9 +37,7 @@ log_game("Z-TRACKING: [src] of type [src.type] has a Z-registration despite not having a client.") update_z(null) - if (notransform) - return - if(!loc) + if(isnull(loc) || HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return if(!IS_IN_STASIS(src)) @@ -51,8 +52,6 @@ handle_diseases(seconds_per_tick, times_fired)// DEAD check is in the proc itself; we want it to spread even if the mob is dead, but to handle its disease-y properties only if you're not. - handle_wounds(seconds_per_tick, times_fired) - if (QDELETED(src)) // diseases can qdel the mob via transformations return @@ -67,6 +66,8 @@ handle_gravity(seconds_per_tick, times_fired) + handle_wounds(seconds_per_tick, times_fired) + if(machine) machine.check_eye(src) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 34155f199a1f..00482b707df6 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -660,8 +660,7 @@ /mob/living/proc/on_lying_down(new_lying_angle) if(layer == initial(layer)) //to avoid things like hiding larvas. layer = LYING_MOB_LAYER //so mob lying always appear behind standing mobs - add_traits(list(TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED), LYING_DOWN_TRAIT) - set_density(FALSE) // We lose density and stop bumping passable dense things. + add_traits(list(TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED, TRAIT_UNDENSE), LYING_DOWN_TRAIT) if(HAS_TRAIT(src, TRAIT_FLOORED) && !(dir & (NORTH|SOUTH))) setDir(pick(NORTH, SOUTH)) // We are and look helpless. body_position_pixel_y_offset = PIXEL_Y_OFFSET_LYING @@ -672,9 +671,15 @@ /mob/living/proc/on_standing_up() if(layer == LYING_MOB_LAYER) layer = initial(layer) - set_density(initial(density)) // We were prone before, so we become dense and things can bump into us again. - remove_traits(list(TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED), LYING_DOWN_TRAIT) - body_position_pixel_y_offset = 0 + remove_traits(list(TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED, TRAIT_UNDENSE), LYING_DOWN_TRAIT) + // Make sure it doesn't go out of the southern bounds of the tile when standing. + body_position_pixel_y_offset = (resize-1) * world.icon_size/2 + +/mob/living/proc/update_density() + if(HAS_TRAIT(src, TRAIT_UNDENSE)) + set_density(FALSE) + else + set_density(TRUE) //Recursive function to find everything a mob is holding. Really shitty proc tbh. /mob/living/get_contents() @@ -1242,7 +1247,7 @@ return /mob/living/can_hold_items(obj/item/I) - return usable_hands && ..() + return ..() && HAS_TRAIT(src, TRAIT_CAN_HOLD_ITEMS) && usable_hands /mob/living/can_perform_action(atom/movable/target, action_bitflags) if(!istype(target)) @@ -1324,14 +1329,13 @@ * Returns a mob (what our mob turned into) or null (if we failed). */ /mob/living/proc/wabbajack(what_to_randomize, change_flags = WABBAJACK) - if(stat == DEAD || notransform || (GODMODE & status_flags)) + if(stat == DEAD || (GODMODE & status_flags) || HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return if(SEND_SIGNAL(src, COMSIG_LIVING_PRE_WABBAJACKED, what_to_randomize) & STOP_WABBAJACK) return - notransform = TRUE - add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), MAGIC_TRAIT) + add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED, TRAIT_NO_TRANSFORM), MAGIC_TRAIT) icon = null cut_overlays() invisibility = INVISIBILITY_ABSTRACT @@ -1374,7 +1378,7 @@ if(WABBAJACK_ROBOT) var/static/list/robot_options = list( /mob/living/silicon/robot = 200, - /mob/living/simple_animal/drone/polymorphed = 200, + /mob/living/basic/drone/polymorphed = 200, /mob/living/silicon/robot/model/syndicate = 1, /mob/living/silicon/robot/model/syndicate/medical = 1, /mob/living/silicon/robot/model/syndicate/saboteur = 1, @@ -1415,6 +1419,7 @@ var/picked_animal = pick( /mob/living/basic/bat, /mob/living/basic/bear, + /mob/living/basic/blob_minion/blobbernaut, /mob/living/basic/butterfly, /mob/living/basic/carp, /mob/living/basic/carp/magic, @@ -1422,9 +1427,8 @@ /mob/living/basic/chicken, /mob/living/basic/cow, /mob/living/basic/crab, - /mob/living/basic/spider/giant, - /mob/living/basic/spider/giant/hunter, - /mob/living/basic/mining/goliath, + /mob/living/basic/goat, + /mob/living/basic/gorilla, /mob/living/basic/headslug, /mob/living/basic/killer_tomato, /mob/living/basic/lizard, @@ -1433,6 +1437,7 @@ /mob/living/basic/morph, /mob/living/basic/mouse, /mob/living/basic/mushroom, + /mob/living/basic/parrot, /mob/living/basic/pet/dog/breaddog, /mob/living/basic/pet/dog/corgi, /mob/living/basic/pet/dog/pug, @@ -1442,14 +1447,7 @@ /mob/living/basic/statue, /mob/living/basic/stickman, /mob/living/basic/stickman/dog, - /mob/living/simple_animal/hostile/blob/blobbernaut/independent, - /mob/living/simple_animal/hostile/gorilla, - /mob/living/simple_animal/hostile/megafauna/dragon/lesser, - /mob/living/simple_animal/hostile/retaliate/goat, - /mob/living/simple_animal/hostile/blob/blobbernaut/independent, /mob/living/simple_animal/hostile/megafauna/dragon/lesser, - /mob/living/simple_animal/hostile/gorilla, - /mob/living/simple_animal/parrot, /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/cat/cak, ) @@ -1747,10 +1745,10 @@ GLOBAL_LIST_EMPTY(fire_appearances) return//dont open the mobs inventory if you are picking them up . = ..() -/mob/living/proc/mob_pickup(mob/living/L) +/mob/living/proc/mob_pickup(mob/living/user) var/obj/item/clothing/head/mob_holder/holder = new(get_turf(src), src, held_state, head_icon, held_lh, held_rh, worn_slot_flags) - L.visible_message(span_warning("[L] scoops up [src]!")) - L.put_in_hands(holder) + user.visible_message(span_warning("[user] scoops up [src]!")) + user.put_in_hands(holder) /mob/living/proc/set_name() numba = rand(1, 1000) @@ -2482,7 +2480,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) /mob/living/proc/compare_sentience_type(compare_type) return FALSE -/// Proc called when targetted by a lazarus injector +/// Proc called when targeted by a lazarus injector /mob/living/proc/lazarus_revive(mob/living/reviver, malfunctioning) revive(HEAL_ALL) befriend(reviver) @@ -2490,7 +2488,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) if (malfunctioning) reviver.log_message("has revived mob [key_name(src)] with a malfunctioning lazarus injector.", LOG_GAME) -/// Proc for giving a mob a new 'friend', generally used for AI control and targetting. Returns false if already friends. +/// Proc for giving a mob a new 'friend', generally used for AI control and targeting. Returns false if already friends. /mob/living/proc/befriend(mob/living/new_friend) SHOULD_CALL_PARENT(TRUE) var/friend_ref = REF(new_friend) @@ -2550,14 +2548,10 @@ GLOBAL_LIST_EMPTY(fire_appearances) return var/del_mob = FALSE var/mob/old_mob - var/ai_control = FALSE - var/list/possible_players = list("None", "Poll Ghosts") + sort_list(GLOB.clients) + var/list/possible_players = list("Poll Ghosts") + sort_list(GLOB.clients) var/client/guardian_client = tgui_input_list(admin, "Pick the player to put in control.", "Guardian Controller", possible_players) - if(!guardian_client) + if(isnull(guardian_client)) return - else if(guardian_client == "None") - guardian_client = null - ai_control = (tgui_alert(admin, "Do you want to give the spirit AI control?", "Guardian Controller", list("Yes", "No")) == "Yes") else if(guardian_client == "Poll Ghosts") var/list/candidates = poll_ghost_candidates("Do you want to play as an admin created Guardian Spirit of [real_name]?", ROLE_PAI, FALSE, 100, POLL_IGNORE_HOLOPARASITE) if(LAZYLEN(candidates)) @@ -2570,7 +2564,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) old_mob = guardian_client.mob if(isobserver(old_mob) || tgui_alert(admin, "Do you want to delete [guardian_client]'s old mob?", "Guardian Controller", list("Yes"," No")) == "Yes") del_mob = TRUE - var/picked_type = tgui_input_list(admin, "Pick the guardian type.", "Guardian Controller", subtypesof(/mob/living/simple_animal/hostile/guardian)) + var/picked_type = tgui_input_list(admin, "Pick the guardian type.", "Guardian Controller", subtypesof(/mob/living/basic/guardian)) var/picked_theme = tgui_input_list(admin, "Pick the guardian theme.", "Guardian Controller", list(GUARDIAN_THEME_TECH, GUARDIAN_THEME_MAGIC, GUARDIAN_THEME_CARP, GUARDIAN_THEME_MINER, "Random")) if(picked_theme == "Random") picked_theme = null //holopara code handles not having a theme by giving a random one @@ -2578,20 +2572,16 @@ GLOBAL_LIST_EMPTY(fire_appearances) var/picked_color = input(admin, "Set the guardian's color, cancel to let player set it.", "Guardian Controller", "#ffffff") as color|null if(tgui_alert(admin, "Confirm creation.", "Guardian Controller", list("Yes", "No")) != "Yes") return - var/mob/living/simple_animal/hostile/guardian/summoned_guardian = new picked_type(src, picked_theme) + var/mob/living/basic/guardian/summoned_guardian = new picked_type(src, picked_theme) summoned_guardian.set_summoner(src, different_person = TRUE) if(picked_name) summoned_guardian.fully_replace_character_name(null, picked_name) if(picked_color) - summoned_guardian.set_guardian_color(picked_color) + summoned_guardian.set_guardian_colour(picked_color) summoned_guardian.key = guardian_client?.key guardian_client?.init_verbs() if(del_mob) qdel(old_mob) - if(ai_control) - summoned_guardian.can_have_ai = TRUE - summoned_guardian.toggle_ai(AI_ON) - summoned_guardian.manifest() message_admins(span_adminnotice("[key_name_admin(admin)] gave a guardian spirit controlled by [guardian_client || "AI"] to [src].")) log_admin("[key_name(admin)] gave a guardian spirit controlled by [guardian_client] to [src].") SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Guardian Spirit") diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 63356d481827..63bffa5995b0 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -86,24 +86,49 @@ /mob/living/proc/is_pepper_proof(check_flags = ALL) return null -/mob/living/proc/on_hit(obj/projectile/P) - return BULLET_ACT_HIT +/// Checks if the mob's ears (BOTH EARS, BOWMANS NEED NOT APPLY) are covered by something. +/// Returns the atom covering the mob's ears, or null if their ears are uncovered. +/mob/living/proc/is_ears_covered() + return null -/mob/living/bullet_act(obj/projectile/P, def_zone, piercing_hit = FALSE) +/mob/living/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE) . = ..() - if(P.is_hostile_projectile() && (. != BULLET_ACT_BLOCK)) - var/attack_direction = get_dir(P.starting, src) - // we need a second, silent armor check to actually know how much to reduce damage taken, as opposed to - // on [/atom/proc/bullet_act] where it's just to pass it to the projectile's on_hit(). - var/armor_check = check_projectile_armor(def_zone, P, is_silent = TRUE) - armor_check = min(ARMOR_MAX_BLOCK, armor_check) //cap damage reduction at 90% - apply_damage(P.damage, P.damage_type, def_zone, armor_check, wound_bonus=P.wound_bonus, bare_wound_bonus=P.bare_wound_bonus, sharpness = P.sharpness, attack_direction = attack_direction) - apply_effects(P.stun, P.knockdown, P.unconscious, P.slur, P.stutter, P.eyeblur, P.drowsy, armor_check, P.stamina, P.jitter, P.paralyze, P.immobilize) - if(P.paralyze_timer) - Disorient(6 SECONDS, 1, paralyze = 5 SECONDS, stack_status = FALSE) - if(P.dismemberment) - check_projectile_dismemberment(P, def_zone) - return . ? BULLET_ACT_HIT : BULLET_ACT_BLOCK + if(. != BULLET_ACT_HIT) + return . + if(!hitting_projectile.is_hostile_projectile()) + return BULLET_ACT_HIT + + // we need a second, silent armor check to actually know how much to reduce damage taken, as opposed to + // on [/atom/proc/bullet_act] where it's just to pass it to the projectile's on_hit(). + var/armor_check = check_projectile_armor(def_zone, hitting_projectile, is_silent = TRUE) + + apply_damage( + damage = hitting_projectile.damage, + damagetype = hitting_projectile.damage_type, + def_zone = def_zone, + blocked = min(ARMOR_MAX_BLOCK, armor_check), //cap damage reduction at 90% + wound_bonus = hitting_projectile.wound_bonus, + bare_wound_bonus = hitting_projectile.bare_wound_bonus, + sharpness = hitting_projectile.sharpness, + attack_direction = get_dir(hitting_projectile.starting, src), + ) + apply_effects( + stun = hitting_projectile.stun, + knockdown = hitting_projectile.knockdown, + unconscious = hitting_projectile.unconscious, + slur = (mob_biotypes & MOB_ROBOTIC) ? 0 SECONDS : hitting_projectile.slur, // Don't want your cyborgs to slur from being ebow'd + stutter = (mob_biotypes & MOB_ROBOTIC) ? 0 SECONDS : hitting_projectile.stutter, // Don't want your cyborgs to stutter from being tazed + eyeblur = hitting_projectile.eyeblur, + drowsy = hitting_projectile.drowsy, + blocked = armor_check, + stamina = hitting_projectile.stamina, + jitter = (mob_biotypes & MOB_ROBOTIC) ? 0 SECONDS : hitting_projectile.jitter, // Cyborgs can jitter but not from being shot + paralyze = hitting_projectile.paralyze, + immobilize = hitting_projectile.immobilize, + ) + if(hitting_projectile.dismemberment) + check_projectile_dismemberment(hitting_projectile, def_zone) + return BULLET_ACT_HIT /mob/living/check_projectile_armor(def_zone, obj/projectile/impacting_projectile, is_silent) return run_armor_check(def_zone, impacting_projectile.armor_flag, "","",impacting_projectile.armour_penetration, "", is_silent, impacting_projectile.weak_against_armour) @@ -157,6 +182,7 @@ return ..() /mob/living/fire_act() + . = ..() adjust_fire_stacks(3) ignite_mob() @@ -289,10 +315,23 @@ /mob/living/attack_hand(mob/living/carbon/human/user, list/modifiers) . = ..() + if(.) + return TRUE + + for(var/datum/surgery/operations as anything in surgeries) + if(user.istate & ISTATE_HARM) + break + if(IS_IN_INVALID_SURGICAL_POSITION(src, operations)) + continue + if(operations.next_step(user, modifiers)) + return TRUE + var/martial_result = user.apply_martial_art(src, modifiers) if (martial_result != MARTIAL_ATTACK_INVALID) return martial_result + return FALSE + /mob/living/attack_paw(mob/living/carbon/human/user, list/modifiers) if(isturf(loc) && istype(loc.loc, /area/misc/start)) to_chat(user, "No attacking people at spawn, you jackass.") @@ -437,17 +476,17 @@ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(cult_ending_helper), CULT_VICTORY_MASS_CONVERSION), 120) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(ending_helper)), 270) if(client) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, src, cultoverride = TRUE) + makeNewConstruct(/mob/living/basic/construct/harvester, src, cultoverride = TRUE) else switch(rand(1, 4)) if(1) - new /mob/living/simple_animal/hostile/construct/juggernaut/hostile(get_turf(src)) + new /mob/living/basic/construct/juggernaut/hostile(get_turf(src)) if(2) - new /mob/living/simple_animal/hostile/construct/wraith/hostile(get_turf(src)) + new /mob/living/basic/construct/wraith/hostile(get_turf(src)) if(3) - new /mob/living/simple_animal/hostile/construct/artificer/hostile(get_turf(src)) + new /mob/living/basic/construct/artificer/hostile(get_turf(src)) if(4) - new /mob/living/simple_animal/hostile/construct/proteon/hostile(get_turf(src)) + new /mob/living/basic/construct/proteon/hostile(get_turf(src)) spawn_dust() investigate_log("has been gibbed by Nar'Sie.", INVESTIGATE_DEATHS) gib() diff --git a/code/modules/mob/living/living_fov.dm b/code/modules/mob/living/living_fov.dm index 0c5acb7b40e4..279e8bebe328 100644 --- a/code/modules/mob/living/living_fov.dm +++ b/code/modules/mob/living/living_fov.dm @@ -57,6 +57,8 @@ if(fov_type > highest_fov) highest_fov = fov_type fov_view = highest_fov + if(HAS_TRAIT(src, TRAIT_EXPANDED_FOV)) + fov_view += 30 update_fov_client() /// Updates the FOV for the client. diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm index 9430586a406b..57f6ebcf5457 100644 --- a/code/modules/mob/living/living_say.dm +++ b/code/modules/mob/living/living_say.dm @@ -261,6 +261,8 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( return TRUE /mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range=0) + if((SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_HEAR, args) & COMSIG_MOVABLE_CANCEL_HEARING) || !GET_CLIENT(src)) + return FALSE if(!GET_CLIENT(src)) return //monkestation edit @@ -473,7 +475,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( I.talk_into(src, message, , spans, language, message_mods) return ITALICS | REDUCE_RANGE - return 0 + return NONE /mob/living/say_mod(input, list/message_mods = list()) if(message_mods[WHISPER_MODE] == MODE_WHISPER) diff --git a/code/modules/mob/living/silicon/ai/ai_defense.dm b/code/modules/mob/living/silicon/ai/ai_defense.dm index c343e677f21d..ed69f00e1233 100644 --- a/code/modules/mob/living/silicon/ai/ai_defense.dm +++ b/code/modules/mob/living/silicon/ai/ai_defense.dm @@ -54,10 +54,6 @@ -/mob/living/silicon/ai/bullet_act(obj/projectile/Proj) - . = ..(Proj) - updatehealth() - /mob/living/silicon/ai/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash, length = 25) return // no eyes, no flashing diff --git a/code/modules/mob/living/silicon/damage_procs.dm b/code/modules/mob/living/silicon/damage_procs.dm index f4da9925fb2f..8dbecfff49b7 100644 --- a/code/modules/mob/living/silicon/damage_procs.dm +++ b/code/modules/mob/living/silicon/damage_procs.dm @@ -1,17 +1,3 @@ - -/mob/living/silicon/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null) - var/hit_percent = (100-blocked)/100 - if((!damage || (!forced && hit_percent <= 0))) - return 0 - var/damage_amount = forced ? damage : damage * hit_percent - switch(damagetype) - if(BRUTE) - adjustBruteLoss(damage_amount, forced = forced) - if(BURN) - adjustFireLoss(damage_amount, forced = forced) - return 1 - - /mob/living/silicon/apply_effect(effect = 0,effecttype = EFFECT_STUN, blocked = FALSE) return FALSE //The only effect that can hit them atm is flashes and they still directly edit so this works for now. (This was written in at least 2016. Help) diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm index 7df2e7d33906..be713b429a6b 100644 --- a/code/modules/mob/living/silicon/robot/inventory.dm +++ b/code/modules/mob/living/silicon/robot/inventory.dm @@ -401,6 +401,7 @@ /mob/living/silicon/robot/perform_hand_swap() cycle_modules() + return TRUE /mob/living/silicon/robot/can_hold_items(obj/item/I) return (I && (I in model.modules)) //Only if it's part of our model. diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm index 0f3dde4b3592..ae56f65b0cdf 100644 --- a/code/modules/mob/living/silicon/robot/life.dm +++ b/code/modules/mob/living/silicon/robot/life.dm @@ -1,5 +1,5 @@ /mob/living/silicon/robot/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if (src.notransform) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return ..() diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm index cf3eb6f03dca..9c4bd6c05c05 100644 --- a/code/modules/mob/living/silicon/robot/robot_defense.dm +++ b/code/modules/mob/living/silicon/robot/robot_defense.dm @@ -423,8 +423,14 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real if (stat != DEAD) adjustBruteLoss(30) -/mob/living/silicon/robot/bullet_act(obj/projectile/Proj, def_zone) + return TRUE + +/mob/living/silicon/robot/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE) . = ..() - updatehealth() - if(prob(75) && Proj.damage > 0) - spark_system.start() + if(prob(25) || . != BULLET_ACT_HIT) + return + if(hitting_projectile.damage_type != BRUTE && hitting_projectile.damage_type != BURN) + return + if(!hitting_projectile.is_hostile_projectile() || hitting_projectile.damage <= 0) + return + spark_system.start() diff --git a/code/modules/mob/living/silicon/robot/robot_model.dm b/code/modules/mob/living/silicon/robot/robot_model.dm index 829b0f6e4ddb..0a96428f2c15 100644 --- a/code/modules/mob/living/silicon/robot/robot_model.dm +++ b/code/modules/mob/living/silicon/robot/robot_model.dm @@ -248,6 +248,9 @@ return new_model /obj/item/robot_model/proc/be_transformed_to(obj/item/robot_model/old_model, forced = FALSE) + if(HAS_TRAIT(robot, TRAIT_NO_TRANSFORM)) + robot.balloon_alert(robot, "can't transform right now!") + return FALSE if(islist(borg_skins) && !forced) var/mob/living/silicon/robot/cyborg = loc var/list/reskin_icons = list() @@ -289,7 +292,7 @@ var/mob/living/silicon/robot/cyborg = loc sleep(0.1 SECONDS) flick("[cyborg_base_icon]_transform", cyborg) - cyborg.notransform = TRUE + ADD_TRAIT(cyborg, TRAIT_NO_TRANSFORM, REF(src)) if(locked_transform) cyborg.SetLockdown(TRUE) cyborg.set_anchored(TRUE) @@ -301,7 +304,7 @@ cyborg.SetLockdown(FALSE) cyborg.setDir(SOUTH) cyborg.set_anchored(FALSE) - cyborg.notransform = FALSE + REMOVE_TRAIT(cyborg, TRAIT_NO_TRANSFORM, REF(src)) cyborg.updatehealth() cyborg.update_icons() cyborg.notify_ai(AI_NOTIFICATION_NEW_MODEL) diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm index c6ba84897b91..b0c7b8cc3e03 100644 --- a/code/modules/mob/living/silicon/silicon_defense.dm +++ b/code/modules/mob/living/silicon/silicon_defense.dm @@ -80,13 +80,13 @@ user.add_mood_event("pet_borg", /datum/mood_event/pet_borg) -/mob/living/silicon/attack_drone(mob/living/simple_animal/drone/M) - if((M.istate & ISTATE_HARM)) +/mob/living/silicon/attack_drone(mob/living/basic/drone/user) + if((user.istate & ISTATE_HARM)) return return ..() -/mob/living/silicon/attack_drone_secondary(mob/living/simple_animal/drone/M) - if((M.istate & ISTATE_HARM)) +/mob/living/silicon/attack_drone_secondary(mob/living/basic/drone/user) + if((user.istate & ISTATE_HARM)) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN return ..() @@ -115,21 +115,22 @@ M.visible_message(span_boldwarning("[M] is thrown off of [src]!")) flash_act(affect_silicon = 1) -/mob/living/silicon/bullet_act(obj/projectile/Proj, def_zone, piercing_hit = FALSE) - SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, Proj, def_zone) - if((Proj.damage_type == BRUTE || Proj.damage_type == BURN)) - adjustBruteLoss(Proj.damage) - if(prob(Proj.damage*1.5)) - for(var/mob/living/M in buckled_mobs) - M.visible_message(span_boldwarning("[M] is knocked off of [src]!")) - unbuckle_mob(M) - M.Paralyze(40) - if(Proj.stun || Proj.knockdown || Proj.paralyze) - for(var/mob/living/M in buckled_mobs) - unbuckle_mob(M) - M.visible_message(span_boldwarning("[M] is knocked off of [src] by the [Proj]!")) - Proj.on_hit(src, 0, piercing_hit) - return BULLET_ACT_HIT +/mob/living/silicon/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE) + . = ..() + if(. != BULLET_ACT_HIT) + return . + + var/prob_of_knocking_dudes_off = 0 + if(hitting_projectile.damage_type == BRUTE || hitting_projectile.damage_type == BURN) + prob_of_knocking_dudes_off = hitting_projectile.damage * 1.5 + if(hitting_projectile.stun || hitting_projectile.knockdown || hitting_projectile.paralyze) + prob_of_knocking_dudes_off = 100 + + if(prob(prob_of_knocking_dudes_off)) + for(var/mob/living/buckled in buckled_mobs) + buckled.visible_message(span_boldwarning("[buckled] is knocked off of [src] by [hitting_projectile]!")) + unbuckle_mob(buckled) + buckled.Paralyze(4 SECONDS) /mob/living/silicon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash/static, length = 25) if(affect_silicon) diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm index 4ef8e75e9f22..78322167a80c 100644 --- a/code/modules/mob/living/simple_animal/animal_defense.dm +++ b/code/modules/mob/living/simple_animal/animal_defense.dm @@ -98,20 +98,20 @@ var/damage = rand(user.melee_damage_lower, user.melee_damage_upper) return attack_threshold_check(damage, user.melee_damage_type) -/mob/living/simple_animal/attack_slime(mob/living/simple_animal/slime/M, list/modifiers) +/mob/living/simple_animal/attack_slime(mob/living/simple_animal/slime/user, list/modifiers) if(..()) //successful slime attack var/damage = rand(15, 25) - if(M.is_adult) + if(user.is_adult) damage = rand(20, 35) return attack_threshold_check(damage) -/mob/living/simple_animal/attack_drone(mob/living/simple_animal/drone/M) - if((M.istate & ISTATE_HARM)) //No kicking dogs even as a rogue drone. Use a weapon. +/mob/living/simple_animal/attack_drone(mob/living/basic/drone/user) + if(user.istate & ISTATE_HARM) //No kicking dogs even as a rogue drone. Use a weapon. return return ..() -/mob/living/simple_animal/attack_drone_secondary(mob/living/simple_animal/drone/M) - if((M.istate & ISTATE_HARM)) +/mob/living/simple_animal/attack_drone_secondary(mob/living/basic/drone/user) + if(user.istate & ISTATE_HARM) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN return ..() diff --git a/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm b/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm index 828be3deebcb..f2a2f9fbb65b 100644 --- a/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm +++ b/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm @@ -12,6 +12,9 @@ var/block_chance = 50 +/mob/living/simple_animal/bot/secbot/grievous/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(block_bullets)) /mob/living/simple_animal/bot/secbot/grievous/toy //A toy version of general beepsky! name = "Genewul Bweepskee" @@ -21,10 +24,15 @@ baton_type = /obj/item/toy/sword weapon_force = 0 -/mob/living/simple_animal/bot/secbot/grievous/bullet_act(obj/projectile/P) - visible_message(span_warning("[src] deflects [P] with its energy swords!")) - playsound(src, 'sound/weapons/blade1.ogg', 50, TRUE) - return BULLET_ACT_BLOCK +/mob/living/simple_animal/bot/secbot/grievous/proc/block_bullets(datum/source, obj/projectile/hitting_projectile) + SIGNAL_HANDLER + + if(stat != CONSCIOUS) + return NONE + + visible_message(span_warning("[source] deflects [hitting_projectile] with its energy swords!")) + playsound(source, 'sound/weapons/blade1.ogg', 50, TRUE) + return COMPONENT_BULLET_BLOCKED /mob/living/simple_animal/bot/secbot/grievous/on_entered(datum/source, atom/movable/AM) . = ..() diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 9fccd6d7ca5c..abd48ad9cabf 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -461,11 +461,15 @@ return do_sparks(5, TRUE, src) -/mob/living/simple_animal/bot/bullet_act(obj/projectile/Proj, def_zone, piercing_hit = FALSE) - if(Proj && (Proj.damage_type == BRUTE || Proj.damage_type == BURN)) - if(prob(75) && Proj.damage > 0) - do_sparks(5, TRUE, src) - return ..() +/mob/living/simple_animal/bot/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE) + . = ..() + if(prob(25) || . != BULLET_ACT_HIT) + return + if(hitting_projectile.damage_type != BRUTE && hitting_projectile.damage_type != BURN) + return + if(!hitting_projectile.is_hostile_projectile() || hitting_projectile.damage <= 0) + return + do_sparks(5, TRUE, src) /mob/living/simple_animal/bot/emp_act(severity) . = ..() diff --git a/code/modules/mob/living/simple_animal/bot/firebot.dm b/code/modules/mob/living/simple_animal/bot/firebot.dm index e928c903231c..a5bddd2292c4 100644 --- a/code/modules/mob/living/simple_animal/bot/firebot.dm +++ b/code/modules/mob/living/simple_animal/bot/firebot.dm @@ -108,8 +108,9 @@ ..() if(!(bot_cover_flags & BOT_COVER_EMAGGED)) return - if(user) - to_chat(user, span_danger("[src] buzzes and beeps.")) + + to_chat(user, span_warning("You enable the very ironically named \"fighting with fire\" mode, and disable the targeting safeties.")) // heheehe. funny + audible_message(span_danger("[src] buzzes oddly!")) playsound(src, SFX_SPARKS, 75, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) if(user) diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm index 971d9b87f1fd..387b77b41be6 100644 --- a/code/modules/mob/living/simple_animal/bot/medbot.dm +++ b/code/modules/mob/living/simple_animal/bot/medbot.dm @@ -568,10 +568,10 @@ healies *= 1.1 if(bot_cover_flags & BOT_COVER_EMAGGED) patient.reagents.add_reagent(/datum/reagent/toxin/chloralhydrate, 5) - patient.apply_damage_type((healies*1),treatment_method) + patient.apply_damage((healies * 1), treatment_method, spread_damage = TRUE) log_combat(src, patient, "pretended to tend wounds on", "internal tools", "([uppertext(treatment_method)]) (EMAGGED)") else - patient.apply_damage_type((healies*-1),treatment_method) //don't need to check treatment_method since we know by this point that they were actually damaged. + patient.heal_damage_type((healies * 1), treatment_method) //don't need to check treatment_method since we know by this point that they were actually damaged. log_combat(src, patient, "tended the wounds of", "internal tools", "([uppertext(treatment_method)])") C.visible_message(span_notice("[src] tends the wounds of [patient]!"), \ "[span_green("[src] tends your wounds!")]") diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm index 5cc8f7255976..3b35b1616296 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -253,11 +253,14 @@ update_appearance() /mob/living/simple_animal/bot/secbot/bullet_act(obj/projectile/Proj) + . = ..() + if(. != BULLET_ACT_HIT) + return + if(istype(Proj, /obj/projectile/beam) || istype(Proj, /obj/projectile/bullet)) if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE)) if(Proj.is_hostile_projectile() && Proj.damage < src.health && ishuman(Proj.firer)) retaliate(Proj.firer) - return ..() /mob/living/simple_animal/bot/secbot/UnarmedAttack(atom/attack_target, proximity_flag) if(!(bot_mode_flags & BOT_MODE_ON)) diff --git a/code/modules/mob/living/simple_animal/damage_procs.dm b/code/modules/mob/living/simple_animal/damage_procs.dm index 0ad4b9401d4b..ee632224e230 100644 --- a/code/modules/mob/living/simple_animal/damage_procs.dm +++ b/code/modules/mob/living/simple_animal/damage_procs.dm @@ -18,31 +18,47 @@ if(AIStatus == AI_IDLE) toggle_ai(AI_ON) +/mob/living/simple_animal/get_damage_mod(damage_type) + var/modifier = ..() + if (damage_type in damage_coeff) + return modifier * damage_coeff[damage_type] + return modifier + /mob/living/simple_animal/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + if(!can_adjust_brute_loss(amount, forced, required_bodytype)) + return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[BRUTE]) . = adjustHealth(amount * damage_coeff[BRUTE] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/simple_animal/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + if(!can_adjust_fire_loss(amount, forced, required_bodytype)) + return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[BURN]) . = adjustHealth(amount * damage_coeff[BURN] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/simple_animal/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype, required_respiration_type) + if(!can_adjust_oxy_loss(amount, forced, required_biotype, required_respiration_type)) + return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[OXY]) . = adjustHealth(amount * damage_coeff[OXY] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/simple_animal/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) + if(!can_adjust_tox_loss(amount, forced, required_biotype)) + return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[TOX]) . = adjustHealth(amount * damage_coeff[TOX] * CONFIG_GET(number/damage_multiplier), updating_health, forced) -/mob/living/simple_animal/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE) +/mob/living/simple_animal/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) + if(!can_adjust_clone_loss(amount, forced, required_biotype)) + return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[CLONE]) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm deleted file mode 100644 index bf5cc4922103..000000000000 --- a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm +++ /dev/null @@ -1,89 +0,0 @@ - -/////////////////// -//DRONE INVENTORY// -/////////////////// -//Drone inventory -//Drone hands - - -/mob/living/simple_animal/drone/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE) - if(..()) - update_held_items() - if(I == head) - head = null - update_worn_head() - if(I == internal_storage) - internal_storage = null - update_inv_internal_storage() - return TRUE - return FALSE - - -/mob/living/simple_animal/drone/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE) - switch(slot) - if(ITEM_SLOT_HEAD) - if(head) - return FALSE - if(!((I.slot_flags & ITEM_SLOT_HEAD) || (I.slot_flags & ITEM_SLOT_MASK))) - return FALSE - return TRUE - if(ITEM_SLOT_DEX_STORAGE) - if(internal_storage) - return FALSE - return TRUE - ..() - - -/mob/living/simple_animal/drone/get_item_by_slot(slot_id) - switch(slot_id) - if(ITEM_SLOT_HEAD) - return head - if(ITEM_SLOT_DEX_STORAGE) - return internal_storage - - return ..() - -/mob/living/simple_animal/drone/get_slot_by_item(obj/item/looking_for) - if(internal_storage == looking_for) - return ITEM_SLOT_DEX_STORAGE - if(head == looking_for) - return ITEM_SLOT_HEAD - return ..() - -/mob/living/simple_animal/drone/equip_to_slot(obj/item/I, slot) - if(!slot) - return - if(!istype(I)) - return - - var/index = get_held_index_of_item(I) - if(index) - held_items[index] = null - update_held_items() - - if(I.pulledby) - I.pulledby.stop_pulling() - - I.screen_loc = null // will get moved if inventory is visible - I.forceMove(src) - SET_PLANE_EXPLICIT(I, ABOVE_HUD_PLANE, src) - - switch(slot) - if(ITEM_SLOT_HEAD) - head = I - update_worn_head() - if(ITEM_SLOT_DEX_STORAGE) - internal_storage = I - update_inv_internal_storage() - else - to_chat(src, span_danger("You are trying to equip this item to an unsupported inventory slot. Report this to a coder!")) - return - - //Call back for item being equipped to drone - I.on_equipped(src, slot) - -/mob/living/simple_animal/drone/getBackSlot() - return ITEM_SLOT_DEX_STORAGE - -/mob/living/simple_animal/drone/getBeltSlot() - return ITEM_SLOT_DEX_STORAGE diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm deleted file mode 100644 index baeb6123d251..000000000000 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ /dev/null @@ -1,121 +0,0 @@ -//goat -/mob/living/simple_animal/hostile/retaliate/goat - name = "goat" - desc = "Not known for their pleasant disposition." - icon_state = "goat" - icon_living = "goat" - icon_dead = "goat_dead" - speak = list("EHEHEHEHEH","eh?") - speak_emote = list("brays") - emote_hear = list("brays.") - emote_see = list("shakes their head.", "stamps a foot.", "glares around.") - speak_chance = 1 - turns_per_move = 5 - butcher_results = list(/obj/item/food/meat/slab = 4) - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "gently pushes aside" - response_disarm_simple = "gently push aside" - response_harm_continuous = "kicks" - response_harm_simple = "kick" - faction = list(FACTION_NEUTRAL) - mob_biotypes = MOB_ORGANIC|MOB_BEAST - attack_same = 1 - attack_verb_continuous = "kicks" - attack_verb_simple = "kick" - attack_sound = 'sound/weapons/punch1.ogg' - attack_vis_effect = ATTACK_EFFECT_KICK - health = 40 - maxHealth = 40 - minbodytemp = 180 - melee_damage_lower = 1 - melee_damage_upper = 2 - environment_smash = ENVIRONMENT_SMASH_NONE - stop_automated_movement_when_pulled = 1 - blood_volume = BLOOD_VOLUME_NORMAL - - footstep_type = FOOTSTEP_MOB_SHOE - -/mob/living/simple_animal/hostile/retaliate/goat/Initialize(mapload) - AddComponent(/datum/component/udder) - AddElement(/datum/element/cliff_walking) //we walk the cliff - . = ..() - -/mob/living/simple_animal/hostile/retaliate/goat/Life(seconds_per_tick = SSMOBS_DT, times_fired) - . = ..() - if(.) - //chance to go crazy and start wacking stuff - if(!enemies.len && SPT_PROB(0.5, seconds_per_tick)) - Retaliate() - - if(enemies.len && SPT_PROB(5, seconds_per_tick)) - enemies.Cut() - LoseTarget() - src.visible_message(span_notice("[src] calms down.")) - if(stat != CONSCIOUS) - return - - eat_plants() - if(pulledby) - return - - for(var/direction in shuffle(list(1,2,4,8,5,6,9,10))) - var/turf/step = get_step(src, direction) - - if(!istype(step)) - return - - var/vine = locate(/obj/structure/spacevine) in step - var/mushroom = locate(/obj/structure/glowshroom) in step - var/flower = locate(/obj/structure/alien/resin/flower_bud) in step - - if(vine || mushroom || flower) - Move(step, get_dir(src, step)) - -/mob/living/simple_animal/hostile/retaliate/goat/Retaliate() - ..() - src.visible_message(span_danger("[src] gets an evil-looking gleam in [p_their()] eye.")) - -/mob/living/simple_animal/hostile/retaliate/goat/Move() - . = ..() - if(!stat) - eat_plants() - -/mob/living/simple_animal/hostile/retaliate/goat/proc/eat_plants() - var/obj/structure/spacevine/vine = locate(/obj/structure/spacevine) in loc - if(vine) - vine.eat(src) - - var/obj/structure/alien/resin/flower_bud/flower = locate(/obj/structure/alien/resin/flower_bud) in loc - if(flower) - flower.take_damage(rand(30, 50), BRUTE, 0) - - var/obj/structure/glowshroom/mushroom = locate(/obj/structure/glowshroom) in loc - if(mushroom) - qdel(mushroom) - - if((vine || flower || mushroom) && prob(10)) - say("Nom") // bon appetit - playsound(src, 'sound/items/eatfood.ogg', rand(30, 50), TRUE) - -/mob/living/simple_animal/hostile/retaliate/goat/AttackingTarget() - . = ..() - - if(!. || !isliving(target)) - return - - var/mob/living/plant_target = target - if(!(plant_target.mob_biotypes & MOB_PLANT)) - return - - plant_target.adjustBruteLoss(20) - playsound(src, 'sound/items/eatfood.ogg', rand(30, 50), TRUE) - var/obj/item/bodypart/edible_bodypart - - if(ishuman(plant_target)) - var/mob/living/carbon/human/plant_man = target - edible_bodypart = pick(plant_man.bodyparts) - edible_bodypart.dismember() - - plant_target.visible_message(span_warning("[src] takes a big chomp out of [plant_target]!"), \ - span_userdanger("[src] takes a big chomp out of your [edible_bodypart || "body"]!")) diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm deleted file mode 100644 index 21b52c5f8018..000000000000 --- a/code/modules/mob/living/simple_animal/guardian/guardian.dm +++ /dev/null @@ -1,638 +0,0 @@ -GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians - -/mob/living/simple_animal/hostile/guardian - name = "Guardian Spirit" - real_name = "Guardian Spirit" - desc = "A mysterious being that stands by its charge, ever vigilant." - speak_emote = list("hisses") - gender = NEUTER - mob_biotypes = NONE - bubble_icon = "guardian" - response_help_continuous = "passes through" - response_help_simple = "pass through" - response_disarm_continuous = "flails at" - response_disarm_simple = "flail at" - response_harm_continuous = "punches" - response_harm_simple = "punch" - icon = 'icons/mob/nonhuman-player/guardian.dmi' - icon_state = "magicbase" - icon_living = "magicbase" - icon_dead = "magicbase" - speed = 0 - istate = ISTATE_HARM|ISTATE_BLOCKING - stop_automated_movement = 1 - attack_sound = 'sound/weapons/punch1.ogg' - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxbodytemp = INFINITY - attack_verb_continuous = "punches" - attack_verb_simple = "punch" - maxHealth = INFINITY //The spirit itself is invincible - health = INFINITY - healable = FALSE //don't brusepack the guardian - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) //how much damage from each damage type we transfer to the owner - environment_smash = ENVIRONMENT_SMASH_STRUCTURES - obj_damage = 40 - melee_damage_lower = 15 - melee_damage_upper = 15 - del_on_death = TRUE - loot = list(/obj/effect/temp_visual/guardian/phase/out) - AIStatus = AI_OFF - can_have_ai = FALSE - light_system = MOVABLE_LIGHT - light_outer_range = 3 - light_on = FALSE - hud_type = /datum/hud/guardian - dextrous_hud_type = /datum/hud/dextrous/guardian //if we're set to dextrous, account for it. - faction = list() - - /// The guardian's color, used for their sprite, chat, and some effects made by it. - var/guardian_color - /// List of overlays we use. - var/list/guardian_overlays[GUARDIAN_TOTAL_LAYERS] - - /// The summoner of the guardian, the one it's intended to guard! - var/mob/living/summoner - /// How far from the summoner the guardian can be. - var/range = 10 - - /// Which toggle button the HUD uses. - var/toggle_button_type = /atom/movable/screen/guardian/toggle_mode/inactive - /// Name used by the guardian creator. - var/creator_name = "Error" - /// Description used by the guardian creator. - var/creator_desc = "This shouldn't be here! Report it on GitHub!" - /// Icon used by the guardian creator. - var/creator_icon = "fuck" - - /// A string explaining to the guardian what they can do. - var/playstyle_string = span_boldholoparasite("You are a Guardian without any type. You shouldn't exist!") - /// The fluff string we actually use. - var/used_fluff_string - /// Fluff string from tarot cards. - var/magic_fluff_string = span_holoparasite("You draw the Coder, symbolizing bugs and errors. This shouldn't happen! Submit a bug report!") - /// Fluff string from holoparasite injectors. - var/tech_fluff_string = span_holoparasite("BOOT SEQUENCE COMPLETE. ERROR MODULE LOADED. THIS SHOULDN'T HAPPEN. Submit a bug report!") - /// Fluff string from holocarp fishsticks. - var/carp_fluff_string = span_holoparasite("CARP CARP CARP SOME SORT OF HORRIFIC BUG BLAME THE CODERS CARP CARP CARP") - /// Fluff string from the dusty shard. - var/miner_fluff_string = span_holoparasite("You encounter... Mythril, it shouldn't exist... Submit a bug report!") - - /// Are we forced to not be able to manifest/recall? - var/locked = FALSE - /// Cooldown between manifests/recalls. - COOLDOWN_DECLARE(manifest_cooldown) - /// Cooldown between the summoner resetting the guardian's client. - COOLDOWN_DECLARE(resetting_cooldown) - -/mob/living/simple_animal/hostile/guardian/Initialize(mapload, theme) - . = ..() - GLOB.parasites += src - update_theme(theme) - AddElement(/datum/element/simple_flying) - manifest_effects() - -/mob/living/simple_animal/hostile/guardian/Destroy() //if deleted by admins or something random, cut from the summoner - if(is_deployed()) - recall_effects() - if(!QDELETED(summoner)) - cut_summoner(different_person = TRUE) - return ..() - -/// Setter for our summoner mob. -/mob/living/simple_animal/hostile/guardian/proc/set_summoner(mob/living/to_who, different_person = FALSE) - if(QDELETED(to_who)) - qdel(src) //no gettin off scot-free pal......... - return - if(summoner) - cut_summoner(different_person) - summoner = to_who - update_health_hud() - med_hud_set_health() - med_hud_set_status() - add_verb(to_who, list( - /mob/living/proc/guardian_comm, - /mob/living/proc/guardian_recall, - /mob/living/proc/guardian_reset, - )) - if(different_person) - if(mind) - mind.enslave_mind_to_creator(to_who) - else //mindless guardian, manually give them factions - faction += summoner.faction - summoner.faction += "[REF(src)]" - remove_all_languages(LANGUAGE_MASTER) - copy_languages(to_who, LANGUAGE_MASTER) // make sure holoparasites speak same language as master - update_atom_languages() - RegisterSignal(to_who, COMSIG_MOVABLE_MOVED, PROC_REF(check_distance)) - RegisterSignal(to_who, COMSIG_PARENT_QDELETING, PROC_REF(on_summoner_deletion)) - RegisterSignal(to_who, COMSIG_LIVING_DEATH, PROC_REF(on_summoner_death)) - RegisterSignal(to_who, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_summoner_health_update)) - RegisterSignal(to_who, COMSIG_LIVING_ON_WABBAJACKED, PROC_REF(on_summoner_wabbajacked)) - RegisterSignal(to_who, COMSIG_LIVING_SHAPESHIFTED, PROC_REF(on_summoner_shapeshifted)) - RegisterSignal(to_who, COMSIG_LIVING_UNSHAPESHIFTED, PROC_REF(on_summoner_unshapeshifted)) - recall(forced = TRUE) - if(to_who.stat == DEAD) - on_summoner_death(to_who) - -/mob/living/simple_animal/hostile/guardian/proc/cut_summoner(different_person = FALSE) - if(is_deployed()) - recall_effects() - forceMove(get_turf(src)) - UnregisterSignal(summoner, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH, COMSIG_LIVING_HEALTH_UPDATE, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_SHAPESHIFTED, COMSIG_LIVING_UNSHAPESHIFTED)) - if(different_person) - summoner.faction -= "[REF(src)]" - faction -= summoner.faction - mind?.remove_all_antag_datums() - if(!length(summoner.get_all_linked_holoparasites() - src)) - remove_verb(summoner, list( - /mob/living/proc/guardian_comm, - /mob/living/proc/guardian_recall, - /mob/living/proc/guardian_reset, - )) - summoner = null - -/// Signal proc for [COMSIG_LIVING_ON_WABBAJACKED], when our summoner is wabbajacked we should be alerted. -/mob/living/simple_animal/hostile/guardian/proc/on_summoner_wabbajacked(mob/living/source, mob/living/new_mob) - SIGNAL_HANDLER - - set_summoner(new_mob) - to_chat(src, span_holoparasite("Your summoner has changed form!")) - -/// Signal proc for [COMSIG_LIVING_SHAPESHIFTED], when our summoner is shapeshifted we should change to the new mob -/mob/living/simple_animal/hostile/guardian/proc/on_summoner_shapeshifted(mob/living/source, mob/living/new_shape) - SIGNAL_HANDLER - - set_summoner(new_shape) - to_chat(src, span_holoparasite("Your summoner has shapeshifted into that of a [new_shape]!")) - -/// Signal proc for [COMSIG_LIVING_UNSHAPESHIFTED], when our summoner unshapeshifts go back to that mob -/mob/living/simple_animal/hostile/guardian/proc/on_summoner_unshapeshifted(mob/living/source, mob/living/old_summoner) - SIGNAL_HANDLER - - set_summoner(old_summoner) - to_chat(src, span_holoparasite("Your summoner has shapeshifted back into their normal form!")) - -// Ha, no -/mob/living/simple_animal/hostile/guardian/wabbajack(what_to_randomize, change_flags = WABBAJACK) - visible_message(span_warning("[src] resists the polymorph!")) - -/mob/living/simple_animal/hostile/guardian/proc/on_summoner_health_update(mob/living/source) - SIGNAL_HANDLER - - update_health_hud() - med_hud_set_health() - med_hud_set_status() - -/mob/living/simple_animal/hostile/guardian/med_hud_set_health() - var/image/holder = hud_list?[HEALTH_HUD] - if(isnull(holder)) - return - holder.icon_state = "hud[RoundHealth(summoner || src)]" - var/icon/size_check = icon(icon, icon_state, dir) - holder.pixel_y = size_check.Height() - world.icon_size - -/mob/living/simple_animal/hostile/guardian/med_hud_set_status() - var/image/holder = hud_list?[STATUS_HUD] - if(isnull(holder)) - return - var/icon/size_check = icon(icon, icon_state, dir) - holder.pixel_y = size_check.Height() - world.icon_size - var/mob/living/checking_mob = summoner || src - if(checking_mob.stat == DEAD || HAS_TRAIT(checking_mob, TRAIT_FAKEDEATH)) - holder.icon_state = "huddead" - else - holder.icon_state = "hudhealthy" - -/mob/living/simple_animal/hostile/guardian/Destroy() - GLOB.parasites -= src - return ..() - -/mob/living/simple_animal/hostile/guardian/proc/update_theme(theme) //update the guardian's theme - if(!theme) - theme = pick(GUARDIAN_THEME_MAGIC, GUARDIAN_THEME_TECH, GUARDIAN_THEME_CARP, GUARDIAN_THEME_MINER) - switch(theme)//should make it easier to create new stand designs in the future if anyone likes that - if(GUARDIAN_THEME_MAGIC) - name = "Guardian Spirit" - real_name = "Guardian Spirit" - bubble_icon = "guardian" - icon_state = "magicbase" - icon_living = "magicbase" - icon_dead = "magicbase" - used_fluff_string = magic_fluff_string - if(GUARDIAN_THEME_TECH) - name = "Holoparasite" - real_name = "Holoparasite" - bubble_icon = "holo" - icon_state = "techbase" - icon_living = "techbase" - icon_dead = "techbase" - used_fluff_string = tech_fluff_string - if(GUARDIAN_THEME_MINER) - name = "Power Miner" - real_name = "Power Miner" - bubble_icon = "guardian" - icon_state = "minerbase" - icon_living = "minerbase" - icon_dead = "minerbase" - used_fluff_string = miner_fluff_string - if(GUARDIAN_THEME_CARP) - name = "Holocarp" - real_name = "Holocarp" - bubble_icon = "holo" - icon_state = null //entirely handled by overlays - icon_living = null - icon_dead = null - speak_emote = string_list(list("gnashes")) - desc = "A mysterious fish that stands by its charge, ever vigilant." - attack_verb_continuous = "bites" - attack_verb_simple = "bite" - attack_sound = 'sound/weapons/bite.ogg' - attack_vis_effect = ATTACK_EFFECT_BITE - used_fluff_string = carp_fluff_string - guardian_overlays[GUARDIAN_COLOR_LAYER] = mutable_appearance(icon, theme) - apply_overlay(GUARDIAN_COLOR_LAYER) - -/mob/living/simple_animal/hostile/guardian/Login() //if we have a mind, set its name to ours when it logs in - . = ..() - if(!. || !client) - return FALSE - if(!summoner) - to_chat(src, span_boldholoparasite("For some reason, somehow, you have no summoner. Please report this bug immediately.")) - else - to_chat(src, span_holoparasite("You are a [real_name], bound to serve [summoner.real_name].")) - to_chat(src, span_holoparasite("You are capable of manifesting or recalling to your master with the buttons on your HUD. You will also find a button to communicate with [summoner.p_them()] privately there.")) - to_chat(src, span_holoparasite("While personally invincible, you will die if [summoner.real_name] does, and any damage dealt to you will have a portion passed on to [summoner.p_them()] as you feed upon [summoner.p_them()] to sustain yourself.")) - to_chat(src, playstyle_string) - if(!guardian_color) - locked = TRUE - guardian_rename() - guardian_recolor() - locked = FALSE - -/mob/living/simple_animal/hostile/guardian/mind_initialize() - . = ..() - if(!summoner) - to_chat(src, span_boldholoparasite("For some reason, somehow, you have no summoner. Please report this bug immediately.")) - return - mind.enslave_mind_to_creator(summoner) //once our mind is created, we become enslaved to our summoner. cant be done in the first run of set_summoner, because by then we dont have a mind yet. - -/mob/living/simple_animal/hostile/guardian/proc/guardian_recolor() - if(!client) - return - var/chosen_guardian_color = input(src, "What would you like your color to be?","Choose Your Color","#ffffff") as color|null - if(!chosen_guardian_color) //redo proc until we get a color - to_chat(src, span_warning("Not a valid color, please try again.")) - guardian_recolor() - return - set_guardian_color(chosen_guardian_color) - -/mob/living/simple_animal/hostile/guardian/proc/set_guardian_color(colour) - guardian_color = colour - set_light_color(guardian_color) - var/mutable_appearance/guardian_color_overlay = guardian_overlays[GUARDIAN_COLOR_LAYER] - remove_overlay(GUARDIAN_COLOR_LAYER) - guardian_color_overlay.color = guardian_color - guardian_overlays[GUARDIAN_COLOR_LAYER] = guardian_color_overlay - apply_overlay(GUARDIAN_COLOR_LAYER) - -/mob/living/simple_animal/hostile/guardian/proc/guardian_rename() - if(!client) - return - var/new_name = sanitize_name(reject_bad_text(tgui_input_text(src, "What would you like your name to be?", "Choose Your Name", real_name, MAX_NAME_LEN))) - if(!new_name) //redo proc until we get a good name - to_chat(src, span_warning("Not a valid name, please try again.")) - guardian_rename() - return - to_chat(src, span_notice("Your new name [span_name("[new_name]")] anchors itself in your mind.")) - fully_replace_character_name(null, new_name) - -/mob/living/simple_animal/hostile/guardian/proc/on_summoner_death(mob/living/source) - SIGNAL_HANDLER - - cut_summoner() - forceMove(source.loc) - to_chat(src, span_danger("Your summoner has died!")) - visible_message(span_bolddanger("\The [src] dies along with its user!")) - source.visible_message(span_bolddanger("[source]'s body is completely consumed by the strain of sustaining [src]!")) - source.dust(drop_items = TRUE) - death(TRUE) - -/mob/living/simple_animal/hostile/guardian/proc/on_summoner_deletion(mob/living/source) - SIGNAL_HANDLER - - cut_summoner() - to_chat(src, span_danger("Your summoner is gone!")) - qdel(src) - -/mob/living/simple_animal/hostile/guardian/get_status_tab_items() - . += ..() - if(summoner) - var/healthpercent = health_percentage(summoner) - . += "Summoner Health: [round(healthpercent, 0.5)]%" - if(!COOLDOWN_FINISHED(src, manifest_cooldown)) - . += "Manifest/Recall Cooldown Remaining: [DisplayTimeText(COOLDOWN_TIMELEFT(src, manifest_cooldown))]" - -/mob/living/simple_animal/hostile/guardian/Move() //Returns to summoner if they move out of range - . = ..() - check_distance() - -/mob/living/simple_animal/hostile/guardian/proc/check_distance() - SIGNAL_HANDLER - - if(!summoner) - return - if(get_dist(get_turf(summoner), get_turf(src)) <= range) - return - to_chat(src, span_holoparasite("You moved out of range, and were pulled back! You can only move [range] meters from [summoner.real_name]!")) - visible_message(span_danger("\The [src] jumps back to its user.")) - if(istype(summoner.loc, /obj/effect)) - recall(forced = TRUE) - else - new /obj/effect/temp_visual/guardian/phase/out(loc) - forceMove(summoner.loc) - new /obj/effect/temp_visual/guardian/phase(loc) - -/mob/living/simple_animal/hostile/guardian/can_suicide() - return FALSE - -/mob/living/simple_animal/hostile/guardian/proc/is_deployed() - return loc != summoner || !summoner - -/mob/living/simple_animal/hostile/guardian/AttackingTarget(atom/attacked_target) - if(!is_deployed()) - to_chat(src, span_bolddanger("You must be manifested to attack!")) - return FALSE - else - return ..() - -/mob/living/simple_animal/hostile/guardian/death(gibbed) - . = ..() - if(!QDELETED(summoner)) - to_chat(summoner, span_bolddanger("Your [name] died somehow!")) - summoner.dust() - -/mob/living/simple_animal/hostile/guardian/update_health_hud() - var/severity = 0 - var/healthpercent = health_percentage(summoner || src) - switch(healthpercent) - if(100 to INFINITY) - severity = 0 - if(85 to 100) - severity = 1 - if(70 to 85) - severity = 2 - if(55 to 70) - severity = 3 - if(40 to 55) - severity = 4 - if(25 to 40) - severity = 5 - else - severity = 6 - if(severity > 0) - overlay_fullscreen("brute", /atom/movable/screen/fullscreen/brute, severity) - else - clear_fullscreen("brute") - if(hud_used?.healths) - hud_used.healths.maptext = MAPTEXT("
[round(healthpercent, 0.5)]%
") - -/mob/living/simple_animal/hostile/guardian/adjustHealth(amount, updating_health = TRUE, forced = FALSE) //The spirit is invincible, but passes on damage to the summoner - . = amount - if(!summoner) - return ..() - if(!is_deployed()) - return FALSE - summoner.adjustBruteLoss(amount) - if(amount < 0 || QDELETED(summoner)) - return - to_chat(summoner, span_bolddanger("Your [name] is under attack! You take damage!")) - summoner.visible_message(span_bolddanger("Blood sprays from [summoner] as [src] takes damage!")) - switch(summoner.stat) - if(UNCONSCIOUS, HARD_CRIT) - to_chat(summoner, span_bolddanger("Your body can't take the strain of sustaining [src] in this condition, it begins to fall apart!")) - summoner.adjustCloneLoss(amount * 0.5) //dying hosts take 50% bonus damage as cloneloss - -/mob/living/simple_animal/hostile/guardian/ex_act(severity, target) - switch(severity) - if(EXPLODE_DEVASTATE) - investigate_log("has been gibbed by an explosion.", INVESTIGATE_DEATHS) - gib() - return TRUE - if(EXPLODE_HEAVY) - adjustBruteLoss(60) - if(EXPLODE_LIGHT) - adjustBruteLoss(30) - -/mob/living/simple_animal/hostile/guardian/gib() - death(TRUE) - -/mob/living/simple_animal/hostile/guardian/dust(just_ash, drop_items, force) - death(TRUE) - -//HAND HANDLING - -/mob/living/simple_animal/hostile/guardian/equip_to_slot(obj/item/equipped_item, slot) - if(!slot) - return FALSE - if(!istype(equipped_item)) - return FALSE - - . = TRUE - var/index = get_held_index_of_item(equipped_item) - if(index) - held_items[index] = null - update_held_items() - - if(equipped_item.pulledby) - equipped_item.pulledby.stop_pulling() - - equipped_item.screen_loc = null // will get moved if inventory is visible - equipped_item.forceMove(src) - SET_PLANE_EXPLICIT(equipped_item, ABOVE_HUD_PLANE, src) - equipped_item.on_equipped(src, slot) - -/mob/living/simple_animal/hostile/guardian/proc/apply_overlay(cache_index) - if((. = guardian_overlays[cache_index])) - add_overlay(.) - -/mob/living/simple_animal/hostile/guardian/proc/remove_overlay(cache_index) - var/overlay = guardian_overlays[cache_index] - if(overlay) - cut_overlay(overlay) - guardian_overlays[cache_index] = null - -/mob/living/simple_animal/hostile/guardian/update_held_items() - remove_overlay(GUARDIAN_HANDS_LAYER) - var/list/hands_overlays = list() - var/obj/item/l_hand = get_item_for_held_index(1) - var/obj/item/r_hand = get_item_for_held_index(2) - - if(r_hand) - hands_overlays += r_hand.build_worn_icon(default_layer = GUARDIAN_HANDS_LAYER, default_icon_file = r_hand.righthand_file, isinhands = TRUE) - - if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) - SET_PLANE_EXPLICIT(r_hand, ABOVE_HUD_PLANE, src) - r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand)) - client.screen |= r_hand - - if(l_hand) - hands_overlays += l_hand.build_worn_icon(default_layer = GUARDIAN_HANDS_LAYER, default_icon_file = l_hand.lefthand_file, isinhands = TRUE) - - if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) - SET_PLANE_EXPLICIT(l_hand, ABOVE_HUD_PLANE, src) - l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand)) - client.screen |= l_hand - - if(length(hands_overlays)) - guardian_overlays[GUARDIAN_HANDS_LAYER] = hands_overlays - apply_overlay(GUARDIAN_HANDS_LAYER) - -/mob/living/simple_animal/hostile/guardian/regenerate_icons() - update_held_items() - -//MANIFEST, RECALL, TOGGLE MODE/LIGHT, SHOW TYPE - -/mob/living/simple_animal/hostile/guardian/proc/manifest(forced) - if(is_deployed() || istype(summoner.loc, /obj/effect) || (!COOLDOWN_FINISHED(src, manifest_cooldown) && !forced) || locked) - return FALSE - forceMove(summoner.loc) - new /obj/effect/temp_visual/guardian/phase(loc) - COOLDOWN_START(src, manifest_cooldown, 1 SECONDS) - reset_perspective() - manifest_effects() - return TRUE - -/mob/living/simple_animal/hostile/guardian/proc/recall(forced) - if(!is_deployed() || !summoner || (!COOLDOWN_FINISHED(src, manifest_cooldown) && !forced) || locked) - return FALSE - new /obj/effect/temp_visual/guardian/phase/out(loc) - forceMove(summoner) - COOLDOWN_START(src, manifest_cooldown, 1 SECONDS) - recall_effects() - return TRUE - -/mob/living/simple_animal/hostile/guardian/proc/manifest_effects() - return - -/mob/living/simple_animal/hostile/guardian/proc/recall_effects() - return - -/mob/living/simple_animal/hostile/guardian/proc/toggle_modes() - to_chat(src, span_bolddanger("You don't have another mode!")) - -/mob/living/simple_animal/hostile/guardian/proc/toggle_light() - if(!light_on) - to_chat(src, span_notice("You activate your light.")) - set_light_on(TRUE) - else - to_chat(src, span_notice("You deactivate your light.")) - set_light_on(FALSE) - - -/mob/living/simple_animal/hostile/guardian/verb/check_type() - set name = "Check Guardian Type" - set category = "Guardian" - set desc = "Check what type you are." - to_chat(src, playstyle_string) - -//COMMUNICATION - -/mob/living/simple_animal/hostile/guardian/proc/communicate() - if(!summoner) - return - var/sender_key = key - var/input = tgui_input_text(src, "Enter a message to tell your summoner", "Guardian") - if(sender_key != key || !input) //guardian got reset, or did not enter anything - return - - var/preliminary_message = span_boldholoparasite("[input]") //apply basic color/bolding - var/my_message = "[src]: [preliminary_message]" //add source, color source with the guardian's color - - to_chat(summoner, "[my_message]") - var/list/guardians = summoner.get_all_linked_holoparasites() - for(var/guardian in guardians) - to_chat(guardian, "[my_message]") - for(var/dead_mob in GLOB.dead_mob_list) - var/link = FOLLOW_LINK(dead_mob, src) - to_chat(dead_mob, "[link] [my_message]") - - src.log_talk(input, LOG_SAY, tag="guardian") - -/mob/living/proc/guardian_comm() - set name = "Communicate" - set category = "Guardian" - set desc = "Communicate telepathically with your guardian." - var/input = tgui_input_text(src, "Enter a message to tell your guardian", "Message") - if(!input) - return - - var/preliminary_message = span_boldholoparasite("[input]") //apply basic color/bolding - var/my_message = span_boldholoparasite("[src]: [preliminary_message]") //add source, color source with default grey... - - to_chat(src, "[my_message]") - var/list/guardians = get_all_linked_holoparasites() - for(var/mob/living/simple_animal/hostile/guardian/guardian as anything in guardians) - to_chat(guardian, "[src]: [preliminary_message]" ) - for(var/dead_mob in GLOB.dead_mob_list) - var/link = FOLLOW_LINK(dead_mob, src) - to_chat(dead_mob, "[link] [my_message]") - - src.log_talk(input, LOG_SAY, tag="guardian") - -//FORCE RECALL/RESET - -/mob/living/proc/guardian_recall() - set name = "Recall Guardian" - set category = "Guardian" - set desc = "Forcibly recall your guardian." - var/list/guardians = get_all_linked_holoparasites() - for(var/mob/living/simple_animal/hostile/guardian/guardian in guardians) - guardian.recall() - -/mob/living/proc/guardian_reset() - set name = "Reset Guardian Player (5 Minute Cooldown)" - set category = "Guardian" - set desc = "Re-rolls which ghost will control your Guardian. Can be used once per 5 minutes." - - var/list/guardians = get_all_linked_holoparasites() - for(var/mob/living/simple_animal/hostile/guardian/resetting_guardian as anything in guardians) - if(!COOLDOWN_FINISHED(resetting_guardian, resetting_cooldown)) - guardians -= resetting_guardian //clear out guardians that are already reset - - var/mob/living/simple_animal/hostile/guardian/chosen_guardian = tgui_input_list(src, "Pick the guardian you wish to reset", "Guardian Reset", sort_names(guardians)) - if(isnull(chosen_guardian)) - to_chat(src, span_holoparasite("You decide not to reset [length(guardians) > 1 ? "any of your guardians":"your guardian"].")) - return - - to_chat(src, span_holoparasite("You attempt to reset [chosen_guardian.real_name]'s personality...")) - var/list/mob/dead/observer/ghost_candidates = poll_ghost_candidates("Do you want to play as [src.real_name]'s Guardian Spirit?", ROLE_PAI, FALSE, 100) - if(!LAZYLEN(ghost_candidates)) - to_chat(src, span_holoparasite("There were no ghosts willing to take control of [chosen_guardian.real_name]. Looks like you're stuck with it for now.")) - return - - var/mob/dead/observer/candidate = pick(ghost_candidates) - to_chat(chosen_guardian, span_holoparasite("Your user reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.")) - to_chat(src, span_boldholoparasite("Your [chosen_guardian.real_name] has been successfully reset.")) - message_admins("[key_name_admin(candidate)] has taken control of ([ADMIN_LOOKUPFLW(chosen_guardian)])") - chosen_guardian.ghostize(FALSE) - chosen_guardian.key = candidate.key - COOLDOWN_START(chosen_guardian, resetting_cooldown, 5 MINUTES) - chosen_guardian.guardian_rename() //give it a new color and name, to show it's a new person - chosen_guardian.guardian_recolor() - -////////parasite tracking/finding procs - -/// Returns a list of all holoparasites that has this mob as a summoner. -/mob/living/proc/get_all_linked_holoparasites() - RETURN_TYPE(/list) - var/list/all_parasites = list() - for(var/mob/living/simple_animal/hostile/guardian/stand as anything in GLOB.parasites) - if(stand.summoner != src) - continue - all_parasites += stand - return all_parasites - -/// Returns true if this holoparasite has the same summoner as the passed holoparasite. -/mob/living/simple_animal/hostile/guardian/proc/hasmatchingsummoner(mob/living/simple_animal/hostile/guardian/other_guardian) - return istype(other_guardian) && other_guardian.summoner == summoner diff --git a/code/modules/mob/living/simple_animal/guardian/types/assassin.dm b/code/modules/mob/living/simple_animal/guardian/types/assassin.dm deleted file mode 100644 index 340e92a5b423..000000000000 --- a/code/modules/mob/living/simple_animal/guardian/types/assassin.dm +++ /dev/null @@ -1,107 +0,0 @@ -//Assassin -/mob/living/simple_animal/hostile/guardian/assassin - melee_damage_lower = 15 - melee_damage_upper = 15 - attack_verb_continuous = "slashes" - attack_verb_simple = "slash" - attack_sound = 'sound/weapons/bladeslice.ogg' - attack_vis_effect = ATTACK_EFFECT_SLASH - sharpness = SHARP_POINTY - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) - playstyle_string = span_holoparasite("As an assassin type you do medium damage and have no damage resistance, but can enter stealth, massively increasing the damage of your next attack and causing it to ignore armor. Stealth is broken when you attack or take damage.") - magic_fluff_string = span_holoparasite("..And draw the Space Ninja, a lethal, invisible assassin.") - tech_fluff_string = span_holoparasite("Boot sequence complete. Assassin modules loaded. Holoparasite swarm online.") - carp_fluff_string = span_holoparasite("CARP CARP CARP! Caught one! It's an assassin carp! Just when you thought it was safe to go back to the water... which is unhelpful, because we're in space.") - miner_fluff_string = span_holoparasite("You encounter... Glass, a sharp, fragile attacker.") - creator_name = "Assassin" - creator_desc = "Does medium damage and takes full damage, but can enter stealth, causing its next attack to do massive damage and ignore armor. However, it becomes briefly unable to recall after attacking from stealth." - creator_icon = "assassin" - toggle_button_type = /atom/movable/screen/guardian/toggle_mode/assassin - /// Is it in stealth mode? - var/toggle = FALSE - /// Time between going in stealth. - var/stealth_cooldown_time = 16 SECONDS - /// Damage added in stealth mode. - var/damage_bonus = 35 - /// Our wound bonus when in stealth mode. - var/stealth_wound_bonus = -20 //from -100, you can now wound! - /// Screen alert given when we are able to stealth. - var/atom/movable/screen/alert/canstealthalert - /// Screen alert given when we are in stealth. - var/atom/movable/screen/alert/instealthalert - /// Cooldown for the stealth toggle. - COOLDOWN_DECLARE(stealth_cooldown) - -/mob/living/simple_animal/hostile/guardian/assassin/get_status_tab_items() - . = ..() - if(!COOLDOWN_FINISHED(src, stealth_cooldown)) - . += "Stealth Cooldown Remaining: [DisplayTimeText(COOLDOWN_TIMELEFT(src, stealth_cooldown))]" - -/mob/living/simple_animal/hostile/guardian/assassin/AttackingTarget(atom/attacked_target) - . = ..() - if(.) - if(toggle && (isliving(target) || istype(target, /obj/structure/window) || istype(target, /obj/structure/grille))) - toggle_modes(forced = TRUE) - -/mob/living/simple_animal/hostile/guardian/assassin/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(. > 0 && toggle) - toggle_modes(forced = TRUE) - -/mob/living/simple_animal/hostile/guardian/assassin/recall_effects() - if(toggle) - toggle_modes(forced = TRUE) - -/mob/living/simple_animal/hostile/guardian/assassin/toggle_modes(forced = FALSE) - if(toggle) - melee_damage_lower -= damage_bonus - melee_damage_upper -= damage_bonus - armour_penetration = initial(armour_penetration) - wound_bonus = initial(wound_bonus) - obj_damage = initial(obj_damage) - environment_smash = initial(environment_smash) - alpha = initial(alpha) - if(!forced) - to_chat(src, span_bolddanger("You exit stealth.")) - else - visible_message(span_danger("\The [src] suddenly appears!")) - COOLDOWN_START(src, stealth_cooldown, stealth_cooldown_time) //we were forced out of stealth and go on cooldown - addtimer(CALLBACK(src, PROC_REF(updatestealthalert)), stealth_cooldown_time) - COOLDOWN_START(src, manifest_cooldown, 4 SECONDS) //can't recall for 4 seconds - updatestealthalert() - toggle = FALSE - else if(COOLDOWN_FINISHED(src, stealth_cooldown)) - if(!is_deployed()) - to_chat(src, span_bolddanger("You have to be manifested to enter stealth!")) - return - melee_damage_lower += damage_bonus - melee_damage_upper += damage_bonus - armour_penetration = 100 - wound_bonus = stealth_wound_bonus - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - new /obj/effect/temp_visual/guardian/phase/out(get_turf(src)) - alpha = 15 - if(!forced) - to_chat(src, span_bolddanger("You enter stealth, empowering your next attack.")) - updatestealthalert() - toggle = TRUE - else if(!forced) - to_chat(src, span_bolddanger("You cannot yet enter stealth, wait another [DisplayTimeText(COOLDOWN_TIMELEFT(src, stealth_cooldown))]!")) - -/mob/living/simple_animal/hostile/guardian/assassin/proc/updatestealthalert() - if(!COOLDOWN_FINISHED(src, stealth_cooldown)) - clear_alert("instealth") - instealthalert = null - clear_alert("canstealth") - canstealthalert = null - return - if(toggle && !instealthalert) - instealthalert = throw_alert("instealth", /atom/movable/screen/alert/instealth) - clear_alert("canstealth") - canstealthalert = null - else if(!toggle && !canstealthalert) - canstealthalert = throw_alert("canstealth", /atom/movable/screen/alert/canstealth) - clear_alert("instealth") - instealthalert = null - diff --git a/code/modules/mob/living/simple_animal/guardian/types/charger.dm b/code/modules/mob/living/simple_animal/guardian/types/charger.dm deleted file mode 100644 index e1e0de66b00a..000000000000 --- a/code/modules/mob/living/simple_animal/guardian/types/charger.dm +++ /dev/null @@ -1,74 +0,0 @@ -//Charger -/mob/living/simple_animal/hostile/guardian/charger - melee_damage_lower = 15 - melee_damage_upper = 15 - ranged = TRUE //technically - ranged_message = "charges" - ranged_cooldown_time = 4 SECONDS - speed = -0.5 - damage_coeff = list(BRUTE = 0.75, BURN = 0.75, TOX = 0.75, CLONE = 0.75, STAMINA = 0, OXY = 0.75) - playstyle_string = span_holoparasite("As a charger type you do medium damage, have light damage resistance, move very fast, can be ridden, and can charge at a location, damaging any target hit and forcing them to drop any items they are holding.") - magic_fluff_string = span_holoparasite("..And draw the Hunter, an alien master of rapid assault.") - tech_fluff_string = span_holoparasite("Boot sequence complete. Charge modules loaded. Holoparasite swarm online.") - carp_fluff_string = span_holoparasite("CARP CARP CARP! Caught one! It's a charger carp, that likes running at people. But it doesn't have any legs...") - miner_fluff_string = span_holoparasite("You encounter... Titanium, a lightweight, agile fighter.") - creator_name = "Charger" - creator_desc = "Moves very fast, does medium damage on attack, can be ridden and can charge at targets, damaging the first target hit and forcing them to drop any items they are holding." - creator_icon = "charger" - /// Is it currently charging at something? - var/charging = FALSE - /// How much damage it does while charging. - var/charge_damage = 20 - -/mob/living/simple_animal/hostile/guardian/charger/Initialize(mapload, theme) - . = ..() - AddElement(/datum/element/ridable, /datum/component/riding/creature/guardian) - -/mob/living/simple_animal/hostile/guardian/charger/get_status_tab_items() - . = ..() - if(!COOLDOWN_FINISHED(src, ranged_cooldown)) - . += "Charge Cooldown Remaining: [DisplayTimeText(COOLDOWN_TIMELEFT(src, ranged_cooldown))]" - -/mob/living/simple_animal/hostile/guardian/charger/OpenFire(atom/target) - if(charging) - return - visible_message(span_danger("[src] [ranged_message] at [target]!")) - COOLDOWN_START(src, ranged_cooldown, ranged_cooldown_time) - clear_alert(ALERT_CHARGE) - addtimer(CALLBACK(src, PROC_REF(throw_alert), ALERT_CHARGE, /atom/movable/screen/alert/cancharge), ranged_cooldown_time) - Shoot(target) - -/mob/living/simple_animal/hostile/guardian/charger/Shoot(atom/targeted_atom) - charging = TRUE - playsound(src, 'sound/items/modsuit/loader_launch.ogg', 75, TRUE) - throw_at(targeted_atom, range, speed = 1.5, thrower = src, spin = FALSE, diagonals_first = TRUE, callback = CALLBACK(src, PROC_REF(charging_end))) - -/mob/living/simple_animal/hostile/guardian/charger/proc/charging_end() - charging = FALSE - -/mob/living/simple_animal/hostile/guardian/charger/Move() - if(charging) - new /obj/effect/temp_visual/decoy/fading(loc, src) - return ..() - -/mob/living/simple_animal/hostile/guardian/charger/check_distance() - if(!charging) - ..() - -/mob/living/simple_animal/hostile/guardian/charger/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(!charging) - return ..() - if(!isliving(hit_atom) || hit_atom == summoner || hasmatchingsummoner(hit_atom)) - return - var/mob/living/hit_mob = hit_atom - if(ishuman(hit_mob)) - var/mob/living/carbon/human/hit_human = hit_mob - if(hit_human.check_shields(src, charge_damage, name, attack_type = THROWN_PROJECTILE_ATTACK)) - return - hit_mob.drop_all_held_items() - hit_mob.visible_message(span_danger("[src] slams into [hit_mob]!"), span_userdanger("[src] slams into you!")) - hit_mob.apply_damage(charge_damage, BRUTE) - playsound(hit_mob, 'sound/effects/meteorimpact.ogg', 100, TRUE) - shake_camera(hit_mob, 4, 3) - shake_camera(src, 2, 3) - diff --git a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm deleted file mode 100644 index bdbc0e8f4d34..000000000000 --- a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm +++ /dev/null @@ -1,97 +0,0 @@ -//Dextrous -/mob/living/simple_animal/hostile/guardian/dextrous - melee_damage_lower = 10 - melee_damage_upper = 10 - damage_coeff = list(BRUTE = 0.75, BURN = 0.75, TOX = 0.75, CLONE = 0.75, STAMINA = 0, OXY = 0.75) - playstyle_string = span_holoparasite("As a dextrous type you can hold items, store an item within yourself, and have medium damage resistance, but do low damage on attacks. Recalling and leashing will force you to drop unstored items!") - magic_fluff_string = span_holoparasite("..And draw the Drone, a dextrous master of construction and repair.") - tech_fluff_string = span_holoparasite("Boot sequence complete. Dextrous combat modules loaded. Holoparasite swarm online.") - carp_fluff_string = span_holoparasite("CARP CARP CARP! You caught one! It can hold stuff in its fins, sort of.") - miner_fluff_string = span_holoparasite("You encounter... Gold, a malleable constructor.") - creator_name = "Dextrous" - creator_desc = "Does low damage on attack, but is capable of holding items and storing a single item within it. It will drop items held in its hands when it recalls, but it will retain the stored item." - creator_icon = "dextrous" - dextrous = TRUE - held_items = list(null, null) - var/obj/item/internal_storage //what we're storing within ourself - -/mob/living/simple_animal/hostile/guardian/dextrous/death(gibbed) - . = ..() - if(internal_storage) - dropItemToGround(internal_storage) - -/mob/living/simple_animal/hostile/guardian/dextrous/examine(mob/user) - if(dextrous) - . = list("This is [icon2html(src)] \a [src]!\n[desc]") - for(var/obj/item/held_item in held_items) - if(held_item.item_flags & (ABSTRACT|EXAMINE_SKIP|HAND_ITEM)) - continue - . += "It has [held_item.get_examine_string(user)] in its [get_held_index_name(get_held_index_of_item(held_item))]." - if(internal_storage && !(internal_storage.item_flags & ABSTRACT)) - . += "It is holding [internal_storage.get_examine_string(user)] in its internal storage." - . += "" - else - return ..() - -/mob/living/simple_animal/hostile/guardian/dextrous/recall_effects() - drop_all_held_items() - -/mob/living/simple_animal/hostile/guardian/dextrous/check_distance() - if(!summoner || get_dist(get_turf(summoner), get_turf(src)) <= range) - return - drop_all_held_items() - ..() //lose items, then return - -//SLOT HANDLING BULLSHIT FOR INTERNAL STORAGE -/mob/living/simple_animal/hostile/guardian/dextrous/doUnEquip(obj/item/equipped_item, force, newloc, no_move, invdrop = TRUE, silent = FALSE) - if(..()) - update_held_items() - if(equipped_item == internal_storage) - internal_storage = null - update_inv_internal_storage() - return TRUE - return FALSE - -/mob/living/simple_animal/hostile/guardian/dextrous/can_equip(obj/item/equipped_item, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE) - switch(slot) - if(ITEM_SLOT_DEX_STORAGE) - if(internal_storage) - return FALSE - return TRUE - ..() - -/mob/living/simple_animal/hostile/guardian/dextrous/get_item_by_slot(slot_id) - if(slot_id == ITEM_SLOT_DEX_STORAGE) - return internal_storage - return ..() - -/mob/living/simple_animal/hostile/guardian/dextrous/get_slot_by_item(obj/item/looking_for) - if(internal_storage == looking_for) - return ITEM_SLOT_DEX_STORAGE - return ..() - -/mob/living/simple_animal/hostile/guardian/dextrous/equip_to_slot(obj/item/equipped_item, slot) - if(!..()) - return - - switch(slot) - if(ITEM_SLOT_DEX_STORAGE) - internal_storage = equipped_item - update_inv_internal_storage() - else - to_chat(src, span_danger("You are trying to equip this item to an unsupported inventory slot. Report this to a coder!")) - -/mob/living/simple_animal/hostile/guardian/dextrous/getBackSlot() - return ITEM_SLOT_DEX_STORAGE - -/mob/living/simple_animal/hostile/guardian/dextrous/getBeltSlot() - return ITEM_SLOT_DEX_STORAGE - -/mob/living/simple_animal/hostile/guardian/dextrous/proc/update_inv_internal_storage() - if(internal_storage && client && hud_used?.hud_shown) - internal_storage.screen_loc = ui_id - client.screen += internal_storage - -/mob/living/simple_animal/hostile/guardian/dextrous/regenerate_icons() - ..() - update_inv_internal_storage() diff --git a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm b/code/modules/mob/living/simple_animal/guardian/types/explosive.dm deleted file mode 100644 index df5fce2380d0..000000000000 --- a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm +++ /dev/null @@ -1,72 +0,0 @@ -#define UNREGISTER_BOMB_SIGNALS(A) \ - do { \ - UnregisterSignal(A, boom_signals); \ - UnregisterSignal(A, COMSIG_PARENT_EXAMINE); \ - } while (0) - -//Explosive -/mob/living/simple_animal/hostile/guardian/explosive - melee_damage_lower = 15 - melee_damage_upper = 15 - damage_coeff = list(BRUTE = 0.6, BURN = 0.6, TOX = 0.6, CLONE = 0.6, STAMINA = 0, OXY = 0.6) - range = 13 - playstyle_string = span_holoparasite("As an explosive type, you have moderate close combat abilities and are capable of converting nearby items and objects into disguised bombs via right-click.") - magic_fluff_string = span_holoparasite("..And draw the Scientist, master of explosive death.") - tech_fluff_string = span_holoparasite("Boot sequence complete. Explosive modules active. Holoparasite swarm online.") - carp_fluff_string = span_holoparasite("CARP CARP CARP! Caught one! It's an explosive carp! Boom goes the fishy.") - miner_fluff_string = span_holoparasite("You encounter... Gibtonite, an explosive fighter.") - creator_name = "Explosive" - creator_desc = "High damage resist and medium power attack that may explosively teleport targets. Can turn any object, including objects too large to pick up, into a bomb, dealing explosive damage to the next person to touch it. The object will return to normal after the trap is triggered or after a delay." - creator_icon = "explosive" - /// Static list of signals that activate the boom. - var/static/list/boom_signals = list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_BUMPED, COMSIG_ATOM_ATTACK_HAND) - /// After this amount of time passses, boom deactivates. - var/decay_time = 1 MINUTES - /// Time between bombs. - var/bomb_cooldown_time = 20 SECONDS - /// The cooldown timer between bombs. - COOLDOWN_DECLARE(bomb_cooldown) - -/mob/living/simple_animal/hostile/guardian/explosive/get_status_tab_items() - . = ..() - if(!COOLDOWN_FINISHED(src, bomb_cooldown)) - . += "Bomb Cooldown Remaining: [DisplayTimeText(COOLDOWN_TIMELEFT(src, bomb_cooldown))]" - -/mob/living/simple_animal/hostile/guardian/explosive/UnarmedAttack(atom/attack_target, proximity_flag) - if((istate & ISTATE_SECONDARY) && proximity_flag && isobj(attack_target)) - plant_bomb(attack_target) - return - return ..() - -/mob/living/simple_animal/hostile/guardian/explosive/proc/plant_bomb(obj/planting_on) - if(!COOLDOWN_FINISHED(src, bomb_cooldown)) - to_chat(src, span_bolddanger("Your powers are on cooldown! You must wait [DisplayTimeText(COOLDOWN_TIMELEFT(src, bomb_cooldown))] between bombs.")) - return - to_chat(src, span_bolddanger("Success! Bomb armed!")) - COOLDOWN_START(src, bomb_cooldown, bomb_cooldown_time) - RegisterSignal(planting_on, COMSIG_PARENT_EXAMINE, PROC_REF(display_examine)) - RegisterSignals(planting_on, boom_signals, PROC_REF(kaboom)) - addtimer(CALLBACK(src, PROC_REF(disable), planting_on), decay_time, TIMER_UNIQUE|TIMER_OVERRIDE) - -/mob/living/simple_animal/hostile/guardian/explosive/proc/kaboom(atom/source, mob/living/explodee) - SIGNAL_HANDLER - if(!istype(explodee)) - return - if(explodee == src || explodee == summoner || hasmatchingsummoner(explodee)) - return - to_chat(explodee, span_bolddanger("[source] was boobytrapped!")) - to_chat(src, span_bolddanger("Success! Your trap caught [explodee]")) - playsound(source, 'sound/effects/explosion2.ogg', 200, TRUE) - new /obj/effect/temp_visual/explosion(get_turf(source)) - EX_ACT(explodee, EXPLODE_HEAVY) - UNREGISTER_BOMB_SIGNALS(source) - -/mob/living/simple_animal/hostile/guardian/explosive/proc/disable(obj/rigged_obj) - to_chat(src, span_bolddanger("Failure! Your trap didn't catch anyone this time.")) - UNREGISTER_BOMB_SIGNALS(rigged_obj) - -/mob/living/simple_animal/hostile/guardian/explosive/proc/display_examine(datum/source, mob/user, text) - SIGNAL_HANDLER - text += span_holoparasite("It glows with a strange light!") - -#undef UNREGISTER_BOMB_SIGNALS diff --git a/code/modules/mob/living/simple_animal/guardian/types/gaseous.dm b/code/modules/mob/living/simple_animal/guardian/types/gaseous.dm deleted file mode 100644 index 7808f8a6b482..000000000000 --- a/code/modules/mob/living/simple_animal/guardian/types/gaseous.dm +++ /dev/null @@ -1,103 +0,0 @@ -//Gaseous -/mob/living/simple_animal/hostile/guardian/gaseous - melee_damage_lower = 10 - melee_damage_upper = 10 - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 0) - range = 7 - playstyle_string = span_holoparasite("As a gaseous type, you have only light damage resistance, but you can expel gas in an area. In addition, your punches cause sparks, and you make your summoner inflammable.") - magic_fluff_string = span_holoparasite("..And draw the Atmospheric Technician, flooding the area with gas!") - tech_fluff_string = span_holoparasite("Boot sequence complete. Atmospheric modules activated. Holoparasite swarm online.") - carp_fluff_string = span_holoparasite("CARP CARP CARP! You caught one! OH GOD, EVERYTHING'S ON FIRE. Except you and the fish.") - miner_fluff_string = span_holoparasite("You encounter... Plasma, the bringer of fire.") - creator_name = "Gaseous" - creator_desc = "Creates sparks on touch and continuously expels a gas of its choice. Automatically extinguishes the user if they catch on fire." - creator_icon = "gaseous" - toggle_button_type = /atom/movable/screen/guardian/toggle_mode/gases - /// Gas being expelled. - var/expelled_gas = null - /// Rate of temperature stabilization per second. - var/temp_stabilization_rate = 0.1 - /// Possible gases to expel, with how much moles they create. - var/static/list/possible_gases = list( - /datum/gas/oxygen = 50, - /datum/gas/nitrogen = 750, //overpressurizing is hard!. - /datum/gas/water_vapor = 1, //you need incredibly little water vapor for the effects to kick in - /datum/gas/nitrous_oxide = 15, - /datum/gas/carbon_dioxide = 50, - /datum/gas/plasma = 3, - /datum/gas/bz = 10, - ) - /// Gas colors, used for the particles. - var/static/list/gas_colors = list( - /datum/gas/oxygen = "#63BFDD", //color of frozen oxygen - /datum/gas/nitrogen = "#777777", //grey (grey) - /datum/gas/water_vapor = "#96ADCF", //water is slightly blue - /datum/gas/nitrous_oxide = "#FEFEFE", //white like the sprite - /datum/gas/carbon_dioxide = "#222222", //black like coal - /datum/gas/plasma = "#B233CC", //color of the plasma sprite - /datum/gas/bz = "#FAFF00", //color of the bz metabolites reagent - ) - -/mob/living/simple_animal/hostile/guardian/gaseous/Initialize(mapload, theme) - . = ..() - RegisterSignal(src, COMSIG_ATOM_PRE_PRESSURE_PUSH, PROC_REF(stop_pressure)) - -/mob/living/simple_animal/hostile/guardian/gaseous/AttackingTarget(atom/attacked_target) - . = ..() - if(!isliving(target)) - return - do_sparks(1, TRUE, target) - -/mob/living/simple_animal/hostile/guardian/gaseous/recall(forced) - expelled_gas = null - QDEL_NULL(particles) //need to delete before putting in another object - . = ..() - if(. && summoner) - UnregisterSignal(summoner, COMSIG_ATOM_PRE_PRESSURE_PUSH) - -/mob/living/simple_animal/hostile/guardian/gaseous/manifest(forced) - . = ..() - if(. && summoner) - RegisterSignal(summoner, COMSIG_ATOM_PRE_PRESSURE_PUSH, PROC_REF(stop_pressure)) - -/mob/living/simple_animal/hostile/guardian/gaseous/Life(seconds_per_tick, times_fired) - . = ..() - if(summoner) - summoner.extinguish_mob() - summoner.set_fire_stacks(0, remove_wet_stacks = FALSE) - summoner.adjust_bodytemperature(get_temp_change_amount((summoner.get_body_temp_normal() - summoner.bodytemperature), temp_stabilization_rate * seconds_per_tick)) - if(!expelled_gas) - return - var/datum/gas_mixture/mix_to_spawn = new() - mix_to_spawn.add_gas(expelled_gas) - mix_to_spawn.gases[expelled_gas][MOLES] = possible_gases[expelled_gas] * seconds_per_tick - mix_to_spawn.temperature = T20C - var/turf/open/our_turf = get_turf(src) - our_turf.assume_air(mix_to_spawn) - -/mob/living/simple_animal/hostile/guardian/gaseous/toggle_modes() - var/list/gases = list("None") - for(var/datum/gas/gas as anything in possible_gases) - gases[initial(gas.name)] = gas - var/picked_gas = tgui_input_list(src, "Select a gas to expel.", "Gas Producer", gases) - if(picked_gas == "None") - expelled_gas = null - QDEL_NULL(particles) - to_chat(src, span_notice("You stopped expelling gas.")) - return - var/gas_type = gases[picked_gas] - if(!picked_gas || !gas_type) - return - to_chat(src, span_bolddanger("You are now expelling [picked_gas].")) - investigate_log("set their gas type to [picked_gas].", INVESTIGATE_ATMOS) - expelled_gas = gas_type - if(!particles) - particles = new /particles/smoke/steam() - particles.position = list(-1, 8, 0) - particles.fadein = 5 - particles.height = 200 - particles.color = gas_colors[gas_type] - -/mob/living/simple_animal/hostile/guardian/gaseous/proc/stop_pressure(datum/source) - SIGNAL_HANDLER - return COMSIG_ATOM_BLOCKS_PRESSURE diff --git a/code/modules/mob/living/simple_animal/guardian/types/gravitokinetic.dm b/code/modules/mob/living/simple_animal/guardian/types/gravitokinetic.dm deleted file mode 100644 index 2e8dabf8f584..000000000000 --- a/code/modules/mob/living/simple_animal/guardian/types/gravitokinetic.dm +++ /dev/null @@ -1,91 +0,0 @@ -//gravitokinetic -/mob/living/simple_animal/hostile/guardian/gravitokinetic - melee_damage_lower = 15 - melee_damage_upper = 15 - damage_coeff = list(BRUTE = 0.75, BURN = 0.75, TOX = 0.75, CLONE = 0.75, STAMINA = 0, OXY = 0.75) - playstyle_string = span_holoparasite("As a gravitokinetic type, you can right-click to make the gravity on the ground stronger, and punching applies this effect to a target.") - magic_fluff_string = span_holoparasite("..And draw the Singularity, an anomalous force of terror.") - tech_fluff_string = span_holoparasite("Boot sequence complete. Gravitokinetic modules loaded. Holoparasite swarm online.") - carp_fluff_string = span_holoparasite("CARP CARP CARP! Caught one! It's a gravitokinetic carp! Now do you understand the gravity of the situation?") - miner_fluff_string = span_holoparasite("You encounter... Bananium, a master of gravity business.") - creator_name = "Gravitokinetic" - creator_desc = "Attacks will apply crushing gravity to the target. Can target the ground as well to slow targets advancing on you, but this will affect the user." - creator_icon = "gravitokinetic" - /// Targets we have applied our effects on. - var/list/gravity_targets = list() - /// Distance in which our ability works - var/gravity_power_range = 10 - /// Gravity added on punches. - var/punch_gravity = 5 - /// Gravity added to turfs. - var/turf_gravity = 3 - -/mob/living/simple_animal/hostile/guardian/gravitokinetic/Initialize(mapload, theme) - . = ..() - AddElement(/datum/element/forced_gravity, 1) - -/mob/living/simple_animal/hostile/guardian/gravitokinetic/set_summoner(mob/to_who, different_person) - . = ..() - to_who.AddElement(/datum/element/forced_gravity, 1) - -/mob/living/simple_animal/hostile/guardian/gravitokinetic/cut_summoner(different_person) - summoner.RemoveElement(/datum/element/forced_gravity, 1) - return ..() - -///Removes gravity from affected mobs upon guardian death to prevent permanent effects -/mob/living/simple_animal/hostile/guardian/gravitokinetic/death() - . = ..() - for(var/gravity_target in gravity_targets) - remove_gravity(gravity_target) - -/mob/living/simple_animal/hostile/guardian/gravitokinetic/AttackingTarget(atom/attacked_target) - . = ..() - if(isliving(target) && !hasmatchingsummoner(attacked_target) && target != src && target != summoner && !gravity_targets[target]) - to_chat(src, span_bolddanger("Your punch has applied heavy gravity to [target]!")) - add_gravity(target, punch_gravity) - to_chat(target, span_userdanger("Everything feels really heavy!")) - -/mob/living/simple_animal/hostile/guardian/gravitokinetic/UnarmedAttack(atom/attack_target, proximity_flag) - if((istate & ISTATE_SECONDARY) && proximity_flag && !gravity_targets[target]) - slam_turf(attack_target) - return - return ..() - -/mob/living/simple_animal/hostile/guardian/gravitokinetic/proc/slam_turf(turf/open/slammed) - if(!isopenturf(slammed) || isgroundlessturf(slammed)) - to_chat(src, span_warning("You cannot add gravity to this!")) - return - visible_message(span_danger("[src] slams their fist into the [slammed]!"), span_notice("You modify the gravity of the [slammed].")) - do_attack_animation(slammed) - add_gravity(slammed, turf_gravity) - -/mob/living/simple_animal/hostile/guardian/gravitokinetic/recall_effects() - to_chat(src, span_bolddanger("You have released your gravitokinetic powers!")) - for(var/gravity_target in gravity_targets) - remove_gravity(gravity_target) - -/mob/living/simple_animal/hostile/guardian/gravitokinetic/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - . = ..() - for(var/gravity_target in gravity_targets) - if(get_dist(src, gravity_target) > gravity_power_range) - remove_gravity(gravity_target) - -/mob/living/simple_animal/hostile/guardian/gravitokinetic/proc/add_gravity(atom/target, new_gravity = 3) - if(gravity_targets[target]) - return - target.AddElement(/datum/element/forced_gravity, new_gravity) - gravity_targets[target] = new_gravity - RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(distance_check)) - playsound(src, 'sound/effects/gravhit.ogg', 100, TRUE) - -/mob/living/simple_animal/hostile/guardian/gravitokinetic/proc/remove_gravity(atom/target) - if(isnull(gravity_targets[target])) - return - UnregisterSignal(target, COMSIG_MOVABLE_MOVED) - target.RemoveElement(/datum/element/forced_gravity, gravity_targets[target]) - gravity_targets -= target - -/mob/living/simple_animal/hostile/guardian/gravitokinetic/proc/distance_check(atom/movable/moving_target, old_loc, dir, forced) - SIGNAL_HANDLER - if(get_dist(src, moving_target) > gravity_power_range) - remove_gravity(moving_target) diff --git a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm b/code/modules/mob/living/simple_animal/guardian/types/lightning.dm deleted file mode 100644 index 9739445f7c9c..000000000000 --- a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm +++ /dev/null @@ -1,111 +0,0 @@ -/obj/effect/ebeam/chain - name = "lightning chain" - layer = LYING_MOB_LAYER - plane = GAME_PLANE_FOV_HIDDEN - -//Lightning -/mob/living/simple_animal/hostile/guardian/lightning - melee_damage_lower = 7 - melee_damage_upper = 7 - attack_verb_continuous = "shocks" - attack_verb_simple = "shock" - melee_damage_type = BURN - attack_sound = 'sound/machines/defib_zap.ogg' - damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7) - range = 7 - playstyle_string = span_holoparasite("As a lightning type, you will apply lightning chains to targets on attack and have a lightning chain to your summoner. Lightning chains will shock anyone near them.") - magic_fluff_string = span_holoparasite("..And draw the Tesla, a shocking, lethal source of power.") - tech_fluff_string = span_holoparasite("Boot sequence complete. Lightning modules active. Holoparasite swarm online.") - carp_fluff_string = span_holoparasite("CARP CARP CARP! Caught one! It's a lightning carp! Everyone else goes zap zap.") - miner_fluff_string = span_holoparasite("You encounter... Iron, a conductive master of lightning.") - creator_name = "Lightning" - creator_desc = "Attacks apply lightning chains to targets. Has a lightning chain to the user. Lightning chains shock everything near them, doing constant damage." - creator_icon = "lightning" - /// Beam datum of our lightning chain to the summoner. - var/datum/beam/summonerchain - /// List of all lightning chains attached to enemies. - var/list/enemychains = list() - /// Amount of shocks we've given through the chain to the summoner. - var/successfulshocks = 0 - /// Cooldown between shocks. - COOLDOWN_DECLARE(shock_cooldown) - -/mob/living/simple_animal/hostile/guardian/lightning/AttackingTarget(atom/attacked_target) - . = ..() - if(!. || !isliving(target) || target == summoner || hasmatchingsummoner(target)) - return - cleardeletedchains() - for(var/datum/beam/chain as anything in enemychains) - if(chain.target == target) - return //oh this guy already HAS a chain, let's not chain again - if(length(enemychains) > 2) - var/datum/beam/enemy_chain = pick(enemychains) - qdel(enemy_chain) - enemychains -= enemy_chain - enemychains += Beam(target, "lightning[rand(1,12)]", maxdistance=7, beam_type=/obj/effect/ebeam/chain) - -/mob/living/simple_animal/hostile/guardian/lightning/manifest_effects() - START_PROCESSING(SSfastprocess, src) - if(summoner) - summonerchain = Beam(summoner, "lightning[rand(1,12)]", beam_type=/obj/effect/ebeam/chain) - -/mob/living/simple_animal/hostile/guardian/lightning/recall_effects() - STOP_PROCESSING(SSfastprocess, src) - removechains() - -/mob/living/simple_animal/hostile/guardian/lightning/process(seconds_per_tick) - if(!COOLDOWN_FINISHED(src, shock_cooldown)) - return - if(successfulshocks > 5) - successfulshocks = 0 - if(shockallchains()) - successfulshocks++ - COOLDOWN_START(src, shock_cooldown, 0.3 SECONDS) - -/mob/living/simple_animal/hostile/guardian/lightning/proc/cleardeletedchains() - if(summonerchain && QDELETED(summonerchain)) - summonerchain = null - for(var/datum/chain as anything in enemychains) - if(QDELETED(chain)) - enemychains -= chain - -/mob/living/simple_animal/hostile/guardian/lightning/proc/shockallchains() - . = 0 - cleardeletedchains() - if(summonerchain) - . += chainshock(summonerchain) - for(var/chain in enemychains) - . += chainshock(chain) - -/mob/living/simple_animal/hostile/guardian/lightning/proc/removechains() - QDEL_NULL(summonerchain) - for(var/chain in enemychains) - qdel(chain) - enemychains = list() - -/mob/living/simple_animal/hostile/guardian/lightning/proc/chainshock(datum/beam/B) //fuck you, fuck this - . = 0 - var/list/turfs = list() - for(var/E in B.elements) - var/obj/effect/ebeam/chainpart = E - if(chainpart && chainpart.x && chainpart.y && chainpart.z) - var/turf/T = get_turf_pixel(chainpart) - turfs |= T - if(T != get_turf(B.origin) && T != get_turf(B.target)) - for(var/turf/TU in circle_range(T, 1)) - turfs |= TU - for(var/turf in turfs) - var/turf/T = turf - for(var/mob/living/L in T) - if(L.stat != DEAD && L != src && L != summoner) - if(hasmatchingsummoner(L)) //if the summoner matches don't hurt them - continue - if(successfulshocks > 4) - L.electrocute_act(0) - L.visible_message( - span_danger("[L] was shocked by the lightning chain!"), \ - span_userdanger("You are shocked by the lightning chain!"), \ - span_hear("You hear a heavy electrical crack.") \ - ) - L.adjustFireLoss(1.2) //adds up very rapidly - . = 1 diff --git a/code/modules/mob/living/simple_animal/guardian/types/protector.dm b/code/modules/mob/living/simple_animal/guardian/types/protector.dm deleted file mode 100644 index 1e7dc6ba54dd..000000000000 --- a/code/modules/mob/living/simple_animal/guardian/types/protector.dm +++ /dev/null @@ -1,76 +0,0 @@ -//Protector -/mob/living/simple_animal/hostile/guardian/protector - melee_damage_lower = 15 - melee_damage_upper = 15 - range = 15 //worse for it due to how it leashes - damage_coeff = list(BRUTE = 0.4, BURN = 0.4, TOX = 0.4, CLONE = 0.4, STAMINA = 0, OXY = 0.4) - playstyle_string = span_holoparasite("As a protector type you cause your summoner to leash to you instead of you leashing to them and have two modes; Combat Mode, where you do and take medium damage, and Protection Mode, where you do and take almost no damage, but move slightly slower.") - magic_fluff_string = span_holoparasite("..And draw the Guardian, a stalwart protector that never leaves the side of its charge.") - tech_fluff_string = span_holoparasite("Boot sequence complete. Protector modules loaded. Holoparasite swarm online.") - carp_fluff_string = span_holoparasite("CARP CARP CARP! You caught one! Wait, no... it caught you! The fisher has become the fishy.") - miner_fluff_string = span_holoparasite("You encounter... Uranium, a very resistant guardian.") - creator_name = "Protector" - creator_desc = "Causes you to teleport to it when out of range, unlike other parasites. Has two modes; Combat, where it does and takes medium damage, and Protection, where it does and takes almost no damage but moves slightly slower." - creator_icon = "protector" - toggle_button_type = /atom/movable/screen/guardian/toggle_mode - /// Damage removed in protecting mode. - var/damage_penalty = 13 - /// Is it in protecting mode? - var/toggle = FALSE - /// Overlay of our protection shield. - var/mutable_appearance/shield_overlay - -/mob/living/simple_animal/hostile/guardian/protector/ex_act(severity) - if(severity >= EXPLODE_DEVASTATE) - adjustBruteLoss(400) //if in protector mode, will do 20 damage and not actually necessarily kill the summoner - else - . = ..() - if(QDELETED(src)) - return FALSE - if(toggle) - visible_message(span_danger("The explosion glances off [src]'s energy shielding!")) - -/mob/living/simple_animal/hostile/guardian/protector/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(. > 0 && toggle) - var/image/flash_overlay = new('icons/effects/effects.dmi', src, "shield-flash", layer+0.01, dir = pick(GLOB.cardinals)) - flash_overlay.color = guardian_color - flick_overlay_view(flash_overlay, 0.5 SECONDS) - -/mob/living/simple_animal/hostile/guardian/protector/toggle_modes() - if(COOLDOWN_FINISHED(src, manifest_cooldown)) - return - COOLDOWN_START(src, manifest_cooldown, 1 SECONDS) - if(toggle) - cut_overlay(shield_overlay) - melee_damage_lower += damage_penalty - melee_damage_upper += damage_penalty - speed = initial(speed) - damage_coeff = list(BRUTE = 0.4, BURN = 0.4, TOX = 0.4, CLONE = 0.4, STAMINA = 0, OXY = 0.4) - to_chat(src, span_bolddanger("You switch to combat mode.")) - toggle = FALSE - else - if(!shield_overlay) - shield_overlay = mutable_appearance('icons/effects/effects.dmi', "shield-grey") - shield_overlay.color = guardian_color - add_overlay(shield_overlay) - melee_damage_lower -= damage_penalty - melee_damage_upper -= damage_penalty - speed = 1 - damage_coeff = list(BRUTE = 0.05, BURN = 0.05, TOX = 0.05, CLONE = 0.05, STAMINA = 0, OXY = 0.05) //damage? what's damage? - to_chat(src, span_bolddanger("You switch to protection mode.")) - toggle = TRUE - -/mob/living/simple_animal/hostile/guardian/protector/check_distance() //snap to what? snap to the guardian! - if(!summoner || get_dist(summoner, src) <= range) - return - if(istype(summoner.loc, /obj/effect)) - to_chat(src, span_holoparasite("You moved out of range, and were pulled back! You can only move [range] meters from [summoner.real_name]!")) - visible_message(span_danger("\The [src] jumps back to its user.")) - recall(forced = TRUE) - return - to_chat(summoner, span_holoparasite("You moved out of range, and were pulled back! You can only move [range] meters from [real_name]!")) - summoner.visible_message(span_danger("\The [summoner] jumps back to [summoner.p_their()] protector.")) - new /obj/effect/temp_visual/guardian/phase/out(get_turf(summoner)) - summoner.forceMove(get_turf(src)) - new /obj/effect/temp_visual/guardian/phase(get_turf(summoner)) diff --git a/code/modules/mob/living/simple_animal/guardian/types/ranged.dm b/code/modules/mob/living/simple_animal/guardian/types/ranged.dm deleted file mode 100644 index d93b14912ebd..000000000000 --- a/code/modules/mob/living/simple_animal/guardian/types/ranged.dm +++ /dev/null @@ -1,182 +0,0 @@ -//Ranged -/obj/projectile/guardian - name = "crystal spray" - icon_state = "guardian" - damage = 5 - damage_type = BRUTE - armour_penetration = 100 - -/mob/living/simple_animal/hostile/guardian/ranged - istate = NONE - friendly_verb_continuous = "quietly assesses" - friendly_verb_simple = "quietly assess" - melee_damage_lower = 10 - melee_damage_upper = 10 - damage_coeff = list(BRUTE = 0.9, BURN = 0.9, TOX = 0.9, CLONE = 0.9, STAMINA = 0, OXY = 0.9) - projectiletype = /obj/projectile/guardian - ranged_cooldown_time = 1 //fast! - projectilesound = 'sound/effects/hit_on_shattered_glass.ogg' - ranged = 1 - range = 13 - playstyle_string = span_holoparasite("As a ranged type, you have only light damage resistance, but are capable of spraying shards of crystal at incredibly high speed. You can also deploy surveillance snares to monitor enemy movement. Finally, you can switch to scout mode, in which you can't attack, but can move without limit.") - magic_fluff_string = span_holoparasite("..And draw the Sentinel, an alien master of ranged combat.") - tech_fluff_string = span_holoparasite("Boot sequence complete. Ranged combat modules active. Holoparasite swarm online.") - carp_fluff_string = span_holoparasite("CARP CARP CARP! Caught one, it's a ranged carp. This fishy can watch people pee in the ocean.") - miner_fluff_string = span_holoparasite("You encounter... Diamond, a powerful projectile thrower.") - creator_name = "Ranged" - creator_desc = "Has two modes. Ranged; which fires a constant stream of weak, armor-ignoring projectiles. Scout; where it cannot attack, but can move through walls and is quite hard to see. Can lay surveillance snares, which alert it when crossed, in either mode." - creator_icon = "ranged" - see_invisible = SEE_INVISIBLE_LIVING - toggle_button_type = /atom/movable/screen/guardian/toggle_mode - /// List of all deployed snares. - var/list/snares = list() - /// Is it in scouting mode? - var/toggle = FALSE - /// Maximum snares deployed at once. - var/max_snares = 6 - /// Lower damage before scouting. - var/previous_lower_damage = 0 - /// Upper damage before scouting. - var/previous_upper_damage = 0 - -/mob/living/simple_animal/hostile/guardian/ranged/toggle_modes() - if(is_deployed() && summoner) - to_chat(src, span_bolddanger("You have to be recalled to toggle modes!")) - return - if(toggle) - ranged = initial(ranged) - melee_damage_lower = previous_lower_damage - melee_damage_upper = previous_upper_damage - previous_lower_damage = 0 - previous_upper_damage = 0 - obj_damage = initial(obj_damage) - environment_smash = initial(environment_smash) - alpha = 255 - range = initial(range) - to_chat(src, span_bolddanger("You switch to combat mode.")) - toggle = FALSE - else - ranged = 0 - previous_lower_damage = melee_damage_lower - melee_damage_lower = 0 - previous_upper_damage = melee_damage_upper - melee_damage_upper = 0 - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - alpha = 45 - range = 255 - to_chat(src, span_bolddanger("You switch to scout mode.")) - toggle = TRUE - - -/mob/living/simple_animal/hostile/guardian/ranged/Shoot(atom/targeted_atom) - . = ..() - if(!istype(., /obj/projectile)) - return - var/obj/projectile/shot_projectile = . - shot_projectile.color = guardian_color - -/mob/living/simple_animal/hostile/guardian/ranged/toggle_light() - var/msg - switch(lighting_cutoff) - if (LIGHTING_CUTOFF_VISIBLE) - lighting_cutoff_red = 10 - lighting_cutoff_green = 10 - lighting_cutoff_blue = 15 - msg = "You activate your night vision." - if (LIGHTING_CUTOFF_MEDIUM) - lighting_cutoff_red = 25 - lighting_cutoff_green = 25 - lighting_cutoff_blue = 35 - msg = "You increase your night vision." - if (LIGHTING_CUTOFF_HIGH) - lighting_cutoff_red = 35 - lighting_cutoff_green = 35 - lighting_cutoff_blue = 50 - msg = "You maximize your night vision." - else - lighting_cutoff_red = 0 - lighting_cutoff_green = 0 - lighting_cutoff_blue = 0 - msg = "You deactivate your night vision." - sync_lighting_plane_cutoff() - to_chat(src, span_notice(msg)) - - -/mob/living/simple_animal/hostile/guardian/ranged/verb/Snare() - set name = "Set Surveillance Snare" - set category = "Guardian" - set desc = "Set an invisible snare that will alert you when living creatures walk over it. Max of 5" - if(length(snares) < max_snares) - var/turf/snare_loc = get_turf(src) - var/obj/effect/snare/new_snare = new /obj/effect/snare(snare_loc, src) - new_snare.name = "[get_area(snare_loc)] snare ([rand(1, 1000)])" - snares += new_snare - to_chat(src, span_bolddanger("Surveillance snare deployed!")) - else - to_chat(src, span_bolddanger("You have too many snares deployed. Remove some first.")) - -/mob/living/simple_animal/hostile/guardian/ranged/verb/DisarmSnare() - set name = "Remove Surveillance Snare" - set category = "Guardian" - set desc = "Disarm unwanted surveillance snares." - var/picked_snare = tgui_input_list(src, "Pick which snare to remove.", "Remove Snare", sort_names(snares)) - if(isnull(picked_snare)) - return - qdel(picked_snare) - to_chat(src, span_bolddanger("Snare disarmed.")) - -/obj/effect/snare - name = "snare" - desc = "You shouldn't be seeing this!" - invisibility = INVISIBILITY_ABSTRACT - var/datum/weakref/guardian_ref - -/obj/effect/snare/Initialize(mapload, spawning_guardian) - . = ..() - guardian_ref = WEAKREF(spawning_guardian) - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = PROC_REF(on_entered), - ) - AddElement(/datum/element/connect_loc, loc_connections) - -/obj/effect/snare/Destroy(force) - var/mob/living/simple_animal/hostile/guardian/ranged/spawning_guardian = guardian_ref?.resolve() - if(spawning_guardian) - spawning_guardian.snares -= src - return ..() - -/obj/effect/snare/proc/on_entered(datum/source, crossed_object) - SIGNAL_HANDLER - var/mob/living/simple_animal/hostile/guardian/ranged/spawning_guardian = guardian_ref?.resolve() - if(!spawning_guardian) - qdel(src) - return - if(!isliving(crossed_object) || crossed_object == spawning_guardian || spawning_guardian.hasmatchingsummoner(crossed_object)) - return - send_message(spawning_guardian.summoner || spawning_guardian, crossed_object) - -/obj/effect/snare/proc/send_message(mob/living/recipient, crossed_object) - to_chat(recipient, span_bolddanger("[crossed_object] has crossed [name].")) - var/list/guardians = recipient.get_all_linked_holoparasites() - for(var/guardian in guardians) - send_message(guardian, crossed_object) - -/obj/effect/snare/singularity_act() - return - -/obj/effect/snare/singularity_pull() - return - -/mob/living/simple_animal/hostile/guardian/ranged/manifest_effects() - if(toggle) - incorporeal_move = INCORPOREAL_MOVE_BASIC - -/mob/living/simple_animal/hostile/guardian/ranged/recall_effects() - // To stop scout mode from moving when recalled - incorporeal_move = FALSE - -/mob/living/simple_animal/hostile/guardian/ranged/AttackingTarget(atom/attacked_target) - if(toggle) - return - return ..() diff --git a/code/modules/mob/living/simple_animal/guardian/types/standard.dm b/code/modules/mob/living/simple_animal/guardian/types/standard.dm deleted file mode 100644 index 89f671d3ffa1..000000000000 --- a/code/modules/mob/living/simple_animal/guardian/types/standard.dm +++ /dev/null @@ -1,41 +0,0 @@ -//Standard -/mob/living/simple_animal/hostile/guardian/standard - damage_coeff = list(BRUTE = 0.5, BURN = 0.5, TOX = 0.5, CLONE = 0.5, STAMINA = 0, OXY = 0.5) - melee_damage_lower = 20 - melee_damage_upper = 20 - wound_bonus = -5 //you can wound! - obj_damage = 80 - next_move_modifier = 0.8 //attacks 20% faster - environment_smash = ENVIRONMENT_SMASH_WALLS - playstyle_string = span_holoparasite("As a standard type you have no special abilities, but have a high damage resistance and a powerful attack capable of smashing through walls.") - magic_fluff_string = span_holoparasite("..And draw the Assistant, faceless and generic, but never to be underestimated.") - tech_fluff_string = span_holoparasite("Boot sequence complete. Standard combat modules loaded. Holoparasite swarm online.") - carp_fluff_string = span_holoparasite("CARP CARP CARP! You caught one! It's really boring and standard. Better punch some walls to ease the tension.") - miner_fluff_string = span_holoparasite("You encounter... Adamantine, a powerful attacker.") - creator_name = "Standard" - creator_desc = "Devastating close combat attacks and high damage resistance. Can smash through weak walls." - creator_icon = "standard" - /// The text we shout when attacking. - var/battlecry = "AT" - -/mob/living/simple_animal/hostile/guardian/standard/verb/Battlecry() - set name = "Set Battlecry" - set category = "Guardian" - set desc = "Choose what you shout as you punch people." - var/input = tgui_input_text(src, "What do you want your battlecry to be?", "Battle Cry", max_length = 6) - if(input) - battlecry = input - -/mob/living/simple_animal/hostile/guardian/standard/AttackingTarget(atom/attacked_target) - . = ..() - if(!isliving(target) || attacked_target == src) - return - var/msg = "" - for(var/i in 1 to 9) - msg += battlecry - say("[msg]!!", ignore_spam = TRUE) - for(var/j in 1 to 4) - addtimer(CALLBACK(src, PROC_REF(do_attack_sound), target.loc), j) - -/mob/living/simple_animal/hostile/guardian/standard/proc/do_attack_sound(atom/playing_from) - playsound(playing_from, attack_sound, 50, TRUE, TRUE) diff --git a/code/modules/mob/living/simple_animal/guardian/types/support.dm b/code/modules/mob/living/simple_animal/guardian/types/support.dm deleted file mode 100644 index 6a7e008a165b..000000000000 --- a/code/modules/mob/living/simple_animal/guardian/types/support.dm +++ /dev/null @@ -1,138 +0,0 @@ -//Support -/mob/living/simple_animal/hostile/guardian/support - speed = 0 - damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7) - melee_damage_lower = 15 - melee_damage_upper = 15 - playstyle_string = span_holoparasite("As a support type, you may right-click to heal targets. In addition, alt-clicking on an adjacent object or mob will warp them to your bluespace beacon after a short delay.") - magic_fluff_string = span_holoparasite("..And draw the Chief Medical Officer, a potent force of life... and death.") - carp_fluff_string = span_holoparasite("CARP CARP CARP! You caught a support carp. It's a kleptocarp!") - tech_fluff_string = span_holoparasite("Boot sequence complete. Support modules active. Holoparasite swarm online.") - miner_fluff_string = span_holoparasite("You encounter... Bluespace, the master of support.") - creator_name = "Support" - creator_desc = "Does medium damage, but can heal its targets and create beacons to teleport people and things to." - creator_icon = "support" - /// Is it in healing mode? - var/toggle = FALSE - /// How much we heal per hit. - var/healing_amount = 5 - /// Our teleportation beacon. - var/obj/structure/receiving_pad/beacon - /// Time it takes to teleport. - var/teleporting_time = 6 SECONDS - /// Time between creating beacons. - var/beacon_cooldown_time = 5 MINUTES - /// Cooldown between creating beacons. - COOLDOWN_DECLARE(beacon_cooldown) - -/mob/living/simple_animal/hostile/guardian/support/Initialize(mapload) - . = ..() - var/datum/atom_hud/medsensor = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - medsensor.show_to(src) - -/mob/living/simple_animal/hostile/guardian/support/get_status_tab_items() - . = ..() - if(!COOLDOWN_FINISHED(src, beacon_cooldown)) - . += "Beacon Cooldown Remaining: [DisplayTimeText(COOLDOWN_TIMELEFT(src, beacon_cooldown))]" - -/mob/living/simple_animal/hostile/guardian/support/UnarmedAttack(atom/attack_target, proximity_flag) - if((istate & ISTATE_SECONDARY) && proximity_flag && isliving(attack_target)) - heal_target(attack_target) - return - return ..() - -/mob/living/simple_animal/hostile/guardian/support/proc/heal_target(mob/living/target) - do_attack_animation(target, ATTACK_EFFECT_PUNCH) - target.visible_message(span_notice("[src] heals [target]!"),\ - span_userdanger("[src] heals you!"), null, COMBAT_MESSAGE_RANGE, src) - to_chat(src, span_notice("You heal [target]!")) - playsound(target, attack_sound, 50, TRUE, TRUE, frequency = -1) //play punch in REVERSE - target.adjustBruteLoss(-healing_amount) - target.adjustFireLoss(-healing_amount) - target.adjustOxyLoss(-healing_amount) - target.adjustToxLoss(-healing_amount) - var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(target)) - heal_effect.color = guardian_color - -/mob/living/simple_animal/hostile/guardian/support/verb/Beacon() - set name = "Place Bluespace Beacon" - set category = "Guardian" - set desc = "Mark a floor as your beacon point, allowing you to warp targets to it. Your beacon will not work at extreme distances." - - if(!COOLDOWN_FINISHED(src, beacon_cooldown)) - to_chat(src, span_bolddanger("Your power is on cooldown. You must wait five minutes between placing beacons.")) - return - - var/turf/beacon_loc = get_turf(src.loc) - if(!isfloorturf(beacon_loc)) - return - - if(beacon) - beacon.disappear() - beacon = null - - beacon = new(beacon_loc, src) - - to_chat(src, span_bolddanger("Beacon placed! You may now warp targets and objects to it, including your user, via Alt+Click.")) - - COOLDOWN_START(src, beacon_cooldown, beacon_cooldown_time) - -/obj/structure/receiving_pad - name = "bluespace receiving pad" - icon = 'icons/turf/floors.dmi' - desc = "A receiving zone for bluespace teleportations." - icon_state = "light_on-8" - light_outer_range = MINIMUM_USEFUL_LIGHT_RANGE - density = FALSE - anchored = TRUE - plane = FLOOR_PLANE - layer = ABOVE_OPEN_TURF_LAYER - -/obj/structure/receiving_pad/New(loc, mob/living/simple_animal/hostile/guardian/spawning_guardian) - . = ..() - add_atom_colour(spawning_guardian?.guardian_color, FIXED_COLOUR_PRIORITY) - -/obj/structure/receiving_pad/proc/disappear() - visible_message(span_notice("[src] vanishes!")) - qdel(src) - -/mob/living/simple_animal/hostile/guardian/support/AltClickOn(atom/movable/target) - teleport_to_beacon(target) - -/mob/living/simple_animal/hostile/guardian/support/proc/teleport_to_beacon(atom/movable/teleport_target) - if(!istype(teleport_target)) - return - if(!beacon) - to_chat(src, span_bolddanger("You need a beacon placed to warp things!")) - return - if(!is_deployed()) - to_chat(src, span_bolddanger("You must be manifested to warp a target!")) - return - if(!Adjacent(teleport_target)) - to_chat(src, span_bolddanger("You must be adjacent to your target!")) - return - if(teleport_target.anchored) - to_chat(src, span_bolddanger("Your target is anchored!")) - return - var/turf/target_turf = get_turf(teleport_target) - if(beacon.z != target_turf.z) - to_chat(src, span_bolddanger("The beacon is too far away to warp to!")) - return - to_chat(src, span_bolddanger("You begin to warp [teleport_target].")) - teleport_target.visible_message(span_danger("[teleport_target] starts to glow faintly!"), \ - span_userdanger("You start to faintly glow, and you feel strangely weightless!")) - do_attack_animation(teleport_target) - playsound(teleport_target, attack_sound, 50, TRUE, TRUE, frequency = -1) //play punch in REVERSE - if(!do_after(src, teleporting_time, teleport_target)) //now start the channel - to_chat(src, span_bolddanger("You need to hold still!")) - return - new /obj/effect/temp_visual/guardian/phase/out(target_turf) - if(isliving(teleport_target)) - var/mob/living/living_target = teleport_target - living_target.flash_act() - teleport_target.visible_message( - span_danger("[teleport_target] disappears in a flash of light!"), \ - span_userdanger("Your vision is obscured by a flash of light!"), \ - ) - do_teleport(teleport_target, beacon, 0, channel = TELEPORT_CHANNEL_BLUESPACE) - new /obj/effect/temp_visual/guardian/phase(get_turf(teleport_target)) diff --git a/code/modules/mob/living/simple_animal/hostile/blob.dm b/code/modules/mob/living/simple_animal/hostile/blob.dm deleted file mode 100644 index 7b84d2db18ce..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/blob.dm +++ /dev/null @@ -1,99 +0,0 @@ -//Do not spawn -/mob/living/simple_animal/hostile/blob - icon = 'icons/mob/nonhuman-player/blob.dmi' - pass_flags = PASSBLOB - faction = list(ROLE_BLOB) - bubble_icon = "blob" - speak_emote = null //so we use verb_yell/verb_say/etc - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxbodytemp = INFINITY - unique_name = 1 - istate = ISTATE_HARM|ISTATE_BLOCKING - // ... Blob colored lighting - lighting_cutoff_red = 20 - lighting_cutoff_green = 40 - lighting_cutoff_blue = 30 - initial_language_holder = /datum/language_holder/empty - retreat_distance = null //! retreat doesn't obey pass_flags, so won't work on blob mobs. - /// Blob camera that controls the blob - var/mob/camera/blob/overmind = null - /// The factory producing spores, blobbernauts - var/obj/structure/blob/special/factory = null - /// If this is related to anything else - var/independent = FALSE - -/mob/living/simple_animal/hostile/blob/update_icons() - if(overmind) - add_atom_colour(overmind.blobstrain.color, FIXED_COLOUR_PRIORITY) - else - remove_atom_colour(FIXED_COLOUR_PRIORITY) - -/mob/living/simple_animal/hostile/blob/Initialize(mapload) - . = ..() - if(!independent) //no pulling people deep into the blob - remove_verb(src, /mob/living/verb/pulled) - else - pass_flags &= ~PASSBLOB - -/mob/living/simple_animal/hostile/blob/Destroy() - if(overmind) - overmind.blob_mobs -= src - return ..() - -/mob/living/simple_animal/hostile/blob/get_status_tab_items() - . = ..() - if(overmind) - . += "Blobs to Win: [overmind.blobs_legit.len]/[overmind.blobwincount]" - -/mob/living/simple_animal/hostile/blob/blob_act(obj/structure/blob/B) - if(stat != DEAD && health < maxHealth) - for(var/unused in 1 to 2) - var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(src)) //hello yes you are being healed - if(overmind) - heal_effect.color = overmind.blobstrain.complementary_color - else - heal_effect.color = "#000000" - adjustHealth(-maxHealth*BLOBMOB_HEALING_MULTIPLIER) - -/mob/living/simple_animal/hostile/blob/fire_act(exposed_temperature, exposed_volume) - ..() - if(exposed_temperature) - adjustFireLoss(clamp(0.01 * exposed_temperature, 1, 5)) - else - adjustFireLoss(5) - -/mob/living/simple_animal/hostile/blob/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - if(istype(mover, /obj/structure/blob)) - return TRUE - -///override to use astar/JPS instead of walk_to so we can take our blob pass_flags into account. -/mob/living/simple_animal/hostile/blob/Goto(target, delay, minimum_distance) - if(prevent_goto_movement) - return FALSE - if(target == src.target) - approaching_target = TRUE - else - approaching_target = FALSE - - SSmove_manager.jps_move(moving = src, chasing = target, delay = delay, repath_delay = 2 SECONDS, minimum_distance = minimum_distance, simulated_only = FALSE, skip_first = TRUE, timeout = 5 SECONDS, flags = MOVEMENT_LOOP_IGNORE_GLIDE) - return TRUE - -/mob/living/simple_animal/hostile/blob/Process_Spacemove(movement_dir = 0, continuous_move = FALSE) - for(var/obj/structure/blob/blob in range(1, src)) - return 1 - return ..() - -/mob/living/simple_animal/hostile/blob/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) - if(sanitize) - message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)) - var/spanned_message = say_quote(message) - var/rendered = "\[Blob Telepathy\] [real_name] [spanned_message]" - for(var/creature in GLOB.mob_list) - if(isovermind(creature) || isblobmonster(creature)) - to_chat(creature, rendered) - if(isobserver(creature)) - var/link = FOLLOW_LINK(creature, src) - to_chat(creature, "[link] [rendered]") - diff --git a/code/modules/mob/living/simple_animal/hostile/blobbernaut.dm b/code/modules/mob/living/simple_animal/hostile/blobbernaut.dm deleted file mode 100644 index 507c4c584319..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/blobbernaut.dm +++ /dev/null @@ -1,109 +0,0 @@ -/mob/living/simple_animal/hostile/blob/blobbernaut - name = "blobbernaut" - desc = "A hulking, mobile chunk of blobmass." - icon_state = "blobbernaut" - icon_living = "blobbernaut" - icon_dead = "blobbernaut_dead" - health = BLOBMOB_BLOBBERNAUT_HEALTH - maxHealth = BLOBMOB_BLOBBERNAUT_HEALTH - damage_coeff = list(BRUTE = 0.5, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) - melee_damage_lower = BLOBMOB_BLOBBERNAUT_DMG_SOLO_LOWER - melee_damage_upper = BLOBMOB_BLOBBERNAUT_DMG_SOLO_UPPER - obj_damage = BLOBMOB_BLOBBERNAUT_DMG_OBJ - attack_verb_continuous = "slams" - attack_verb_simple = "slam" - attack_sound = 'sound/effects/blobattack.ogg' - verb_say = "gurgles" - verb_ask = "demands" - verb_exclaim = "roars" - verb_yell = "bellows" - force_threshold = 10 - pressure_resistance = 50 - mob_size = MOB_SIZE_LARGE - hud_type = /datum/hud/living/blobbernaut - -/mob/living/simple_animal/hostile/blob/blobbernaut/Initialize(mapload) - . = ..() - add_cell_sample() - -/mob/living/simple_animal/hostile/blob/blobbernaut/mind_initialize() - . = ..() - if(independent | !overmind) - return - var/datum/antagonist/blob_minion/blobbernaut/naut = new(overmind) - mind.add_antag_datum(naut) - -/mob/living/simple_animal/hostile/blob/blobbernaut/add_cell_sample() - AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBBERNAUT, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) - -/mob/living/simple_animal/hostile/blob/blobbernaut/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if(!..()) - return FALSE - var/list/blobs_in_area = range(2, src) - - if(independent) - return FALSE // strong independent blobbernaut that don't need no blob - - var/damagesources = 0 - - if(!(locate(/obj/structure/blob) in blobs_in_area)) - damagesources++ - - if(!factory) - damagesources++ - else - if(locate(/obj/structure/blob/special/core) in blobs_in_area) - adjustHealth(-maxHealth*BLOBMOB_BLOBBERNAUT_HEALING_CORE * seconds_per_tick) - var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(src)) //hello yes you are being healed - if(overmind) - heal_effect.color = overmind.blobstrain.complementary_color - else - heal_effect.color = "#000000" - if(locate(/obj/structure/blob/special/node) in blobs_in_area) - adjustHealth(-maxHealth*BLOBMOB_BLOBBERNAUT_HEALING_NODE * seconds_per_tick) - var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(src)) - if(overmind) - heal_effect.color = overmind.blobstrain.complementary_color - else - heal_effect.color = "#000000" - - if(!damagesources) - return FALSE - - adjustHealth(maxHealth * BLOBMOB_BLOBBERNAUT_HEALTH_DECAY * damagesources * seconds_per_tick) //take 2.5% of max health as damage when not near the blob or if the naut has no factory, 5% if both - var/image/image = new('icons/mob/nonhuman-player/blob.dmi', src, "nautdamage", MOB_LAYER+0.01) - image.appearance_flags = RESET_COLOR - - if(overmind) - image.color = overmind.blobstrain.complementary_color - - flick_overlay_view(image, 8) - -/mob/living/simple_animal/hostile/blob/blobbernaut/AttackingTarget() - . = ..() - if(. && isliving(target) && overmind) - overmind.blobstrain.blobbernaut_attack(target, src) - -/mob/living/simple_animal/hostile/blob/blobbernaut/update_icons() - ..() - if(overmind) //if we have an overmind, we're doing chemical reactions instead of pure damage - melee_damage_lower = BLOBMOB_BLOBBERNAUT_DMG_LOWER - melee_damage_upper = BLOBMOB_BLOBBERNAUT_DMG_UPPER - attack_verb_continuous = overmind.blobstrain.blobbernaut_message - else - melee_damage_lower = initial(melee_damage_lower) - melee_damage_upper = initial(melee_damage_upper) - attack_verb_continuous = initial(attack_verb_continuous) - -/mob/living/simple_animal/hostile/blob/blobbernaut/death(gibbed) - ..(gibbed) - if(factory) - factory.naut = null //remove this naut from its factory - factory.max_integrity = initial(factory.max_integrity) - flick("blobbernaut_death", src) - -/mob/living/simple_animal/hostile/blob/blobbernaut/independent - independent = TRUE - gold_core_spawnable = HOSTILE_SPAWN - - diff --git a/code/modules/mob/living/simple_animal/hostile/blobspore.dm b/code/modules/mob/living/simple_animal/hostile/blobspore.dm deleted file mode 100644 index 9ef4f5b31cdd..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/blobspore.dm +++ /dev/null @@ -1,170 +0,0 @@ -/mob/living/simple_animal/hostile/blob/blobspore - name = "blob spore" - desc = "A floating, fragile spore." - icon_state = "blobpod" - icon_living = "blobpod" - health_doll_icon = "blobpod" - health = BLOBMOB_SPORE_HEALTH - maxHealth = BLOBMOB_SPORE_HEALTH - verb_say = "psychically pulses" - verb_ask = "psychically probes" - verb_exclaim = "psychically yells" - verb_yell = "psychically screams" - melee_damage_lower = BLOBMOB_SPORE_DMG_LOWER - melee_damage_upper = BLOBMOB_SPORE_DMG_UPPER - environment_smash = ENVIRONMENT_SMASH_NONE - obj_damage = 0 - attack_verb_continuous = "hits" - attack_verb_simple = "hit" - attack_sound = 'sound/weapons/genhit1.ogg' - del_on_death = TRUE - death_message = "explodes into a cloud of gas!" - gold_core_spawnable = NO_SPAWN //gold slime cores should only spawn the independent subtype - /// Size of cloud produced from a dying spore - var/death_cloud_size = 1 - /// The attached person - var/mob/living/carbon/human/corpse - /// If this is attached to a person - var/is_zombie = FALSE - /// Whether or not this is a fragile spore from Distributed Neurons - var/is_weak = FALSE - -/mob/living/simple_animal/hostile/blob/blobspore/Initialize(mapload, obj/structure/blob/special/linked_node) - . = ..() - AddElement(/datum/element/simple_flying) - - if(!istype(linked_node)) - return - - factory = linked_node - factory.spores += src - if(linked_node.overmind && istype(linked_node.overmind.blobstrain, /datum/blobstrain/reagent/distributed_neurons) && !istype(src, /mob/living/simple_animal/hostile/blob/blobspore/weak)) - notify_ghosts("A controllable spore has been created in \the [get_area(src)].", source = src, action = NOTIFY_ORBIT, flashwindow = FALSE, header = "Sentient Spore Created") - add_cell_sample() - -/mob/living/simple_animal/hostile/blob/blobspore/mind_initialize() - . = ..() - if(independent || !overmind) - return FALSE - var/datum/antagonist/blob_minion/blob_zombie/zombie = new(overmind) - mind.add_antag_datum(zombie) - -/mob/living/simple_animal/hostile/blob/blobspore/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if(!is_zombie && isturf(loc)) - for(var/mob/living/carbon/human/target in view(src,1)) //Only for corpse right next to/on same tile - if(!is_weak && target.stat == DEAD) - zombify(target) - break - if(factory && !is_valid_z_level(get_turf(src), get_turf(factory))) - death() - return ..() - -/mob/living/simple_animal/hostile/blob/blobspore/attack_ghost(mob/user) - . = ..() - if(.) - return - humanize_pod(user) - -/mob/living/simple_animal/hostile/blob/blobspore/death(gibbed) - // On death, create a small smoke of harmful gas (s-Acid) - var/datum/effect_system/fluid_spread/smoke/chem/spores = new - var/turf/location = get_turf(src) - - // Create the reagents to put into the air - create_reagents(10) - - if(overmind?.blobstrain) - overmind.blobstrain.on_sporedeath(src) - else - reagents.add_reagent(/datum/reagent/toxin/spore, 10) - - // Attach the smoke spreader and setup/start it. - spores.attach(location) - spores.set_up(death_cloud_size, holder = src, location = location, carry = reagents, silent = TRUE) - spores.start() - if(factory) - factory.spore_delay = world.time + factory.spore_cooldown //put the factory on cooldown - - return ..() - -/mob/living/simple_animal/hostile/blob/blobspore/Destroy() - if(factory) - factory.spores -= src - factory = null - if(corpse) - corpse.forceMove(loc) - corpse = null - return ..() - -/mob/living/simple_animal/hostile/blob/blobspore/update_icons() - if(overmind) - add_atom_colour(overmind.blobstrain.complementary_color, FIXED_COLOUR_PRIORITY) - else - remove_atom_colour(FIXED_COLOUR_PRIORITY) - if(!is_zombie) - return FALSE - - copy_overlays(corpse, TRUE) - var/mutable_appearance/blob_head_overlay = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "blob_head") - if(overmind) - blob_head_overlay.color = overmind.blobstrain.complementary_color - color = initial(color) // looks better. - add_overlay(blob_head_overlay) - -/mob/living/simple_animal/hostile/blob/blobspore/add_cell_sample() - AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBSPORE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) - -/mob/living/simple_animal/hostile/blob/blobspore/independent - gold_core_spawnable = HOSTILE_SPAWN - independent = TRUE - -/mob/living/simple_animal/hostile/blob/blobspore/weak - name = "fragile blob spore" - health = 15 - maxHealth = 15 - melee_damage_lower = 1 - melee_damage_upper = 2 - death_cloud_size = 0 - is_weak = TRUE - -/** Ghost control a blob zombie */ -/mob/living/simple_animal/hostile/blob/blobspore/proc/humanize_pod(mob/user) - if((!overmind || istype(src, /mob/living/simple_animal/hostile/blob/blobspore/weak) || !istype(overmind.blobstrain, /datum/blobstrain/reagent/distributed_neurons)) && !is_zombie) - return FALSE - if(key || stat) - return FALSE - var/pod_ask = tgui_alert(usr, "Are you bulbous enough?", "Blob Spore", list("Yes", "No")) - if(pod_ask != "Yes" || QDELETED(src)) - return FALSE - if(key) - to_chat(user, span_warning("Someone else already took this spore!")) - return FALSE - key = user.key - log_message("took control of [name].", LOG_GAME) - -/** Zombifies a dead mob, turning it into a blob zombie */ -/mob/living/simple_animal/hostile/blob/blobspore/proc/zombify(mob/living/carbon/human/target) - is_zombie = 1 - if(target.wear_suit) - maxHealth += target.get_armor_rating(MELEE) // That zombie's got armor, I want armor! - maxHealth += 40 - health = maxHealth - name = "blob zombie" - desc = "A shambling corpse animated by the blob." - mob_biotypes |= MOB_HUMANOID - melee_damage_lower += 8 - melee_damage_upper += 11 - obj_damage = 20 // now that it has a corpse to puppet, it can properly attack structures - environment_smash = ENVIRONMENT_SMASH_STRUCTURES - movement_type = GROUND - death_cloud_size = 0 - icon = target.icon - icon_state = "zombie" - target.hairstyle = null - target.update_body_parts() - target.forceMove(src) - corpse = target - update_icons() - visible_message(span_warning("The corpse of [target.name] suddenly rises!")) - if(!key) - notify_ghosts("\A [src] has been created in \the [get_area(src)].", source = src, action = NOTIFY_ORBIT, flashwindow = FALSE, header = "Blob Zombie Created") diff --git a/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm b/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm deleted file mode 100644 index bc1852ba57fa..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm +++ /dev/null @@ -1,139 +0,0 @@ -/mob/living/simple_animal/hostile/boss - name = "\improper A Perfectly Generic Boss Placeholder" - desc = "" - robust_searching = 1 - stat_attack = HARD_CRIT - status_flags = 0 - istate = ISTATE_HARM|ISTATE_BLOCKING - sentience_type = SENTIENCE_BOSS - gender = NEUTER - var/list/boss_abilities = list() //list of /datum/action/boss - var/datum/boss_active_timed_battle/atb - var/point_regen_delay = 1 - - -/mob/living/simple_animal/hostile/boss/Initialize(mapload) - . = ..() - - atb = new() - atb.point_regen_delay = point_regen_delay - atb.boss = src - - for(var/ab in boss_abilities) - boss_abilities -= ab - var/datum/action/boss/AB = new ab() - AB.boss = src - AB.Grant(src) - boss_abilities += AB - - atb.assign_abilities(boss_abilities) - - -/mob/living/simple_animal/hostile/boss/Destroy() - qdel(atb) - atb = null - for(var/ab in boss_abilities) - var/datum/action/boss/AB = ab - AB.boss = null - AB.Remove(src) - qdel(AB) - boss_abilities.Cut() - return ..() - - -//Action datum for bosses -//Override Trigger() as shown below to do things -/datum/action/boss - check_flags = AB_CHECK_CONSCIOUS //Incase the boss is given a player - var/boss_cost = 100 //Cost of usage for the boss' AI 1-100 - var/usage_probability = 100 - var/mob/living/simple_animal/hostile/boss/boss - var/boss_type = /mob/living/simple_animal/hostile/boss - var/needs_target = TRUE //Does the boss need to have a target? (Only matters for the AI) - var/say_when_triggered = "" //What does the boss Say() when the ability triggers? - -/datum/action/boss/Trigger(trigger_flags) - . = ..() - if(!.) - return - if(!istype(boss, boss_type)) - return - if(!boss.atb) - return - if(boss.atb.points < boss_cost) - return - if(!boss.client) - if(needs_target && !boss.target) - return - if(boss) - if(say_when_triggered) - boss.say(say_when_triggered, forced = "boss action") - if(!boss.atb.spend(boss_cost)) - return - -//Example: -/* -/datum/action/boss/selfgib/Trigger(trigger_flags) - if(..()) - boss.gib() -*/ - - -//Designed for boss mobs only -/datum/boss_active_timed_battle - var/list/abilities //a list of /datum/action/boss owned by a boss mob - var/point_regen_delay = 5 - var/points = 50 //1-100, start with 50 so we can use some abilities but not insta-buttfug somebody - var/next_point_time = 0 - var/chance_to_hold_onto_points = 50 - var/highest_cost = 0 - var/mob/living/simple_animal/hostile/boss/boss - - -/datum/boss_active_timed_battle/New() - ..() - START_PROCESSING(SSobj, src) - - -/datum/boss_active_timed_battle/proc/assign_abilities(list/L) - if(!L) - return 0 - abilities = L - for(var/ab in abilities) - var/datum/action/boss/AB = ab - if(AB.boss_cost > highest_cost) - highest_cost = AB.boss_cost - - -/datum/boss_active_timed_battle/proc/spend(cost) - if(cost <= points) - points = max(0,points-cost) - return 1 - return 0 - - -/datum/boss_active_timed_battle/proc/refund(cost) - points = min(points+cost, 100) - - -/datum/boss_active_timed_battle/process() - if(world.time >= next_point_time) - next_point_time = world.time + point_regen_delay - points = min(100, ++points) //has to be out of 100 - - if(abilities) - chance_to_hold_onto_points = highest_cost*0.5 - if(points != 100 && prob(chance_to_hold_onto_points)) - return //Let's save our points for a better ability (unless we're at max points, in which case we can't save anymore!) - if(!boss.client) - abilities = shuffle(abilities) - for(var/ab in abilities) - var/datum/action/boss/AB = ab - if(prob(AB.usage_probability) && AB.Trigger()) - break - - -/datum/boss_active_timed_battle/Destroy() - abilities = null - SSobj.processing.Remove(src) - return ..() diff --git a/code/modules/mob/living/simple_animal/hostile/constructs/constructs.dm b/code/modules/mob/living/simple_animal/hostile/constructs/constructs.dm deleted file mode 100644 index 56206da9dbc3..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/constructs/constructs.dm +++ /dev/null @@ -1,136 +0,0 @@ -/mob/living/simple_animal/hostile/construct - name = "Construct" - real_name = "Construct" - desc = "" - gender = NEUTER - mob_biotypes = NONE - speak_emote = list("hisses") - response_help_continuous = "thinks better of touching" - response_help_simple = "think better of touching" - response_disarm_continuous = "flails at" - response_disarm_simple = "flail at" - response_harm_continuous = "punches" - response_harm_simple = "punch" - speak_chance = 1 - icon = 'icons/mob/nonhuman-player/cult.dmi' - speed = 0 - istate = ISTATE_HARM|ISTATE_BLOCKING - stop_automated_movement = 1 - status_flags = CANPUSH - attack_sound = 'sound/weapons/punch1.ogg' - // Vivid red, cause cult theme - lighting_cutoff_red = 30 - lighting_cutoff_green = 5 - lighting_cutoff_blue = 20 - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxbodytemp = INFINITY - healable = 0 - faction = list(FACTION_CULT) - pressure_resistance = 100 - unique_name = 1 - AIStatus = AI_OFF //normal constructs don't have AI - loot = list(/obj/item/ectoplasm) - del_on_death = TRUE - initial_language_holder = /datum/language_holder/construct - death_message = "collapses in a shattered heap." - /// List of spells that this construct can cast - var/list/construct_spells = list() - /// Flavor text shown to players when they spawn as this construct - var/playstyle_string = "You are a generic construct! Your job is to not exist, and you should probably adminhelp this." - /// The construct's master - var/master = null - /// Whether this construct is currently seeking nar nar - var/seeking = FALSE - /// Whether this construct can repair other constructs or cult buildings. - var/can_repair = FALSE - /// Whether this construct can repair itself. Works independently of can_repair. - var/can_repair_self = FALSE - /// Theme controls color. THEME_CULT is red THEME_WIZARD is purple and THEME_HOLY is blue - var/theme = THEME_CULT - -/mob/living/simple_animal/hostile/construct/Initialize(mapload) - . = ..() - AddElement(/datum/element/simple_flying) - add_traits(list(TRAIT_HEALS_FROM_CULT_PYLONS, TRAIT_SPACEWALK), INNATE_TRAIT) - for(var/spell in construct_spells) - var/datum/action/new_spell = new spell(src) - new_spell.Grant(src) - - var/spell_count = 1 - for(var/datum/action/spell as anything in actions) - if(!(spell.type in construct_spells)) - continue - - var/pos = 2 + spell_count * 31 - if(construct_spells.len >= 4) - pos -= 31 * (construct_spells.len - 4) - spell.default_button_position = "6:[pos],4:-2" // Set the default position to this random position - spell_count++ - update_action_buttons() - - if(icon_state) - add_overlay("glow_[icon_state]_[theme]") - -/mob/living/simple_animal/hostile/construct/Login() - . = ..() - if(!. || !client) - return FALSE - to_chat(src, playstyle_string) - -/mob/living/simple_animal/hostile/construct/examine(mob/user) - var/pronoun = p_they(TRUE) - var/plural = p_s() - var/text_span - switch(theme) - if(THEME_CULT) - text_span = "cult" - if(THEME_WIZARD) - text_span = "purple" - if(THEME_HOLY) - text_span = "blue" - . = list("This is [icon2html(src, user)] \a [src]!\n[desc]") - if(health < maxHealth) - if(health >= maxHealth/2) - . += span_warning("[pronoun] look[plural] slightly dented.") - else - . += span_warning("[pronoun] look[plural] severely dented!") - . += "" - -/mob/living/simple_animal/hostile/construct/attack_animal(mob/living/simple_animal/user, list/modifiers) - if(!isconstruct(user)) - if(src != user) - return ..() - return - - var/mob/living/simple_animal/hostile/construct/doll = user - if(!doll.can_repair || (doll == src && !doll.can_repair_self)) - return ..() - if(theme != doll.theme) - return ..() - - if(health >= maxHealth) - if(src != user) - to_chat(user, span_cult("You cannot repair [src]'s dents, as [p_they()] [p_have()] none!")) - else - to_chat(user, span_cult("You cannot repair your own dents, as you have none!")) - return - - adjustHealth(-5) - if(src == user) - user.visible_message(span_danger("[user] repairs some of [p_their()] own dents."), \ - span_cult("You repair some of your own dents, leaving you at [user.health]/[user.maxHealth] health.")) - return - - Beam(user, icon_state="sendbeam", time = 4) - user.visible_message(span_danger("[user] repairs some of \the [src]'s dents."), \ - span_cult("You repair some of [src]'s dents, leaving [src] at [health]/[maxHealth] health.")) - - -/mob/living/simple_animal/hostile/construct/narsie_act() - return - -/mob/living/simple_animal/hostile/construct/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE) - return FALSE - diff --git a/code/modules/mob/living/simple_animal/hostile/constructs/juggernaut.dm b/code/modules/mob/living/simple_animal/hostile/constructs/juggernaut.dm deleted file mode 100644 index 3eb80cf7a88e..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/constructs/juggernaut.dm +++ /dev/null @@ -1,75 +0,0 @@ -/mob/living/simple_animal/hostile/construct/juggernaut - name = "Juggernaut" - real_name = "Juggernaut" - desc = "A massive, armored construct built to spearhead attacks and soak up enemy fire." - icon_state = "juggernaut" - icon_living = "juggernaut" - maxHealth = 150 - health = 150 - response_harm_continuous = "harmlessly punches" - response_harm_simple = "harmlessly punch" - harm_intent_damage = 0 - obj_damage = 90 - melee_damage_lower = 25 - melee_damage_upper = 25 - attack_verb_continuous = "smashes their armored gauntlet into" - attack_verb_simple = "smash your armored gauntlet into" - speed = 2.5 - environment_smash = ENVIRONMENT_SMASH_WALLS - attack_sound = 'sound/weapons/punch3.ogg' - status_flags = 0 - mob_size = MOB_SIZE_LARGE - force_threshold = 10 - construct_spells = list( - /datum/action/cooldown/spell/forcewall/cult, - /datum/action/cooldown/spell/basic_projectile/juggernaut, - /datum/action/innate/cult/create_rune/wall, - ) - playstyle_string = "You are a Juggernaut. Though slow, your shell can withstand heavy punishment, \ - create shield walls, rip apart enemies and walls alike, and even deflect energy weapons." - -/mob/living/simple_animal/hostile/construct/juggernaut/hostile //actually hostile, will move around, hit things - AIStatus = AI_ON - environment_smash = ENVIRONMENT_SMASH_STRUCTURES //only token destruction, don't smash the cult wall NO STOP - -/mob/living/simple_animal/hostile/construct/juggernaut/bullet_act(obj/projectile/bullet) - if(!istype(bullet, /obj/projectile/energy) && !istype(bullet, /obj/projectile/beam)) - return ..() - if(!prob(40 - round(bullet.damage / 3))) // reflect chance - return ..() - - apply_damage(bullet.damage * 0.5, bullet.damage_type) - visible_message(span_danger("The [bullet.name] is reflected by [src]'s armored shell!"), \ - span_userdanger("The [bullet.name] is reflected by your armored shell!")) - - if(!bullet.starting) - return BULLET_ACT_FORCE_PIERCE - // Find a turf near or on the original location to bounce to - var/new_x = bullet.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) - var/new_y = bullet.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) - var/turf/current_tile = get_turf(src) - - // redirect the projectile - bullet.original = locate(new_x, new_y, bullet.z) - bullet.starting = current_tile - bullet.firer = src - bullet.yo = new_y - current_tile.y - bullet.xo = new_x - current_tile.x - var/new_angle_s = bullet.Angle + rand(120,240) - while(new_angle_s > 180) // Translate to regular projectile degrees - new_angle_s -= 360 - bullet.set_angle(new_angle_s) - - return BULLET_ACT_FORCE_PIERCE // complete projectile permutation - - -//////////////////////////Juggernaut-alts//////////////////////////// -/mob/living/simple_animal/hostile/construct/juggernaut/angelic - theme = THEME_HOLY - loot = list(/obj/item/ectoplasm/angelic) - -/mob/living/simple_animal/hostile/construct/juggernaut/mystic - theme = THEME_WIZARD - loot = list(/obj/item/ectoplasm/mystic) - -/mob/living/simple_animal/hostile/construct/juggernaut/noncult diff --git a/code/modules/mob/living/simple_animal/hostile/constructs/wraith.dm b/code/modules/mob/living/simple_animal/hostile/constructs/wraith.dm deleted file mode 100644 index e7ef22a9e073..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/constructs/wraith.dm +++ /dev/null @@ -1,78 +0,0 @@ -/mob/living/simple_animal/hostile/construct/wraith - name = "Wraith" - real_name = "Wraith" - desc = "A wicked, clawed shell constructed to assassinate enemies and sow chaos behind enemy lines." - icon_state = "wraith" - icon_living = "wraith" - maxHealth = 65 - health = 65 - melee_damage_lower = 20 - melee_damage_upper = 20 - retreat_distance = 2 //AI wraiths will move in and out of combat - attack_verb_continuous = "slashes" - attack_verb_simple = "slash" - attack_sound = 'sound/weapons/bladeslice.ogg' - attack_vis_effect = ATTACK_EFFECT_SLASH - construct_spells = list( - /datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift, - /datum/action/innate/cult/create_rune/tele, - ) - playstyle_string = "You are a Wraith. Though relatively fragile, you are fast, deadly, \ - can phase through walls, and your attacks will lower the cooldown on phasing." - - // Accomplishing various things gives you a refund on jaunt, to jump in and out. - /// The seconds refunded per attack - var/attack_refund = 1 SECONDS - /// The seconds refunded when putting a target into critical - var/crit_refund = 5 SECONDS - -/mob/living/simple_animal/hostile/construct/wraith/AttackingTarget() //refund jaunt cooldown when attacking living targets - var/prev_stat - var/mob/living/living_target = target - - if(isliving(living_target) && !IS_CULTIST(living_target)) - prev_stat = living_target.stat - - . = ..() - if(!. || !isnum(prev_stat)) - return - - var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/jaunt = locate() in actions - if(!jaunt) - return - - var/total_refund = 0 SECONDS - // they're dead, and you killed them - full refund - if(QDELETED(living_target) || (living_target.stat == DEAD && prev_stat != DEAD)) - total_refund += jaunt.cooldown_time - // you knocked them into critical - else if(HAS_TRAIT(living_target, TRAIT_CRITICAL_CONDITION) && prev_stat == CONSCIOUS) - total_refund += crit_refund - - if(living_target.stat != DEAD && prev_stat != DEAD) - total_refund += attack_refund - - jaunt.next_use_time -= total_refund - jaunt.build_all_button_icons() - -/mob/living/simple_animal/hostile/construct/wraith/hostile //actually hostile, will move around, hit things - AIStatus = AI_ON - -//////////////////////////Wraith-alts//////////////////////////// -/mob/living/simple_animal/hostile/construct/wraith/angelic - theme = THEME_HOLY - construct_spells = list( - /datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/angelic, - /datum/action/innate/cult/create_rune/tele, - ) - loot = list(/obj/item/ectoplasm/angelic) - -/mob/living/simple_animal/hostile/construct/wraith/mystic - theme = THEME_WIZARD - construct_spells = list( - /datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/mystic, - /datum/action/innate/cult/create_rune/tele, - ) - loot = list(/obj/item/ectoplasm/mystic) - -/mob/living/simple_animal/hostile/construct/wraith/noncult diff --git a/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm b/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm deleted file mode 100644 index 55007a1121ce..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm +++ /dev/null @@ -1,173 +0,0 @@ -#define GORILLA_TOTAL_LAYERS 1 - -/mob/living/simple_animal/hostile/gorilla - name = "Gorilla" - desc = "A ground-dwelling, predominantly herbivorous ape that inhabits the forests of central Africa." - icon = 'icons/mob/simple/gorilla.dmi' - icon_state = "crawling" - icon_living = "crawling" - icon_dead = "dead" - health_doll_icon = "crawling" - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - speak_chance = 80 - maxHealth = 220 - health = 220 - loot = list(/obj/effect/gibspawner/generic/animal) - butcher_results = list(/obj/item/food/meat/slab/gorilla = 4) - response_help_continuous = "prods" - response_help_simple = "prod" - response_disarm_continuous = "challenges" - response_disarm_simple = "challenge" - response_harm_continuous = "thumps" - response_harm_simple = "thump" - speed = 0.5 - melee_damage_lower = 15 - melee_damage_upper = 18 - damage_coeff = list(BRUTE = 1, BURN = 1.5, TOX = 1.5, CLONE = 0, STAMINA = 0, OXY = 1.5) - obj_damage = 20 - environment_smash = ENVIRONMENT_SMASH_WALLS - attack_verb_continuous = "pummels" - attack_verb_simple = "pummel" - attack_sound = 'sound/weapons/punch1.ogg' - dextrous = TRUE - held_items = list(null, null) - faction = list(FACTION_MONKEY, FACTION_JUNGLE) - robust_searching = TRUE - stat_attack = HARD_CRIT - minbodytemp = 270 - maxbodytemp = 350 - unique_name = TRUE - footstep_type = FOOTSTEP_MOB_BAREFOOT - - var/list/gorilla_overlays[GORILLA_TOTAL_LAYERS] - var/oogas = 0 - -// Gorillas like to dismember limbs from unconscious mobs. -// Returns null when the target is not an unconscious carbon mob; a list of limbs (possibly empty) otherwise. -/mob/living/simple_animal/hostile/gorilla/proc/get_target_bodyparts(atom/hit_target) - if(!iscarbon(hit_target)) - return - - var/mob/living/carbon/carbon_target = hit_target - if(carbon_target.stat < UNCONSCIOUS) - return - - var/list/parts = list() - for(var/obj/item/bodypart/part as anything in carbon_target.bodyparts) - if(part.body_part == HEAD || part.body_part == CHEST) - continue - if(part.bodypart_flags & BODYPART_UNREMOVABLE) - continue - parts += part - return parts - -/mob/living/simple_animal/hostile/gorilla/AttackingTarget(atom/attacked_target) - . = ..() - if(!.) - return - - if(client) - oogaooga() - - var/list/parts = get_target_bodyparts(target) - if(length(parts)) - var/obj/item/bodypart/to_dismember = pick(parts) - to_dismember.dismember() - return - - if(isliving(target)) - var/mob/living/living_target = target - if(prob(80)) - living_target.throw_at(get_edge_target_turf(living_target, dir), rand(1, 2), 7, src) - - else - living_target.Paralyze(2 SECONDS) - visible_message(span_danger("[src] knocks [living_target] down!")) - -/mob/living/simple_animal/hostile/gorilla/CanAttack(atom/the_target) - var/list/parts = get_target_bodyparts(target) - return ..() && !ismonkey(the_target) && (!parts || length(parts) > 3) - -/mob/living/simple_animal/hostile/gorilla/CanSmashTurfs(turf/T) - return iswallturf(T) - -/mob/living/simple_animal/hostile/gorilla/gib(no_brain) - if(!no_brain) - var/mob/living/brain/gorilla_brain = new(drop_location()) - gorilla_brain.name = real_name - gorilla_brain.real_name = real_name - mind?.transfer_to(gorilla_brain) - return ..() - -/mob/living/simple_animal/hostile/gorilla/handle_automated_speech(override) - if(speak_chance && (override || prob(speak_chance))) - playsound(src, 'sound/creatures/gorilla.ogg', 50) - return ..() - -/mob/living/simple_animal/hostile/gorilla/can_use_guns(obj/item/G) - to_chat(src, span_warning("Your meaty finger is much too large for the trigger guard!")) - return FALSE - -/mob/living/simple_animal/hostile/gorilla/proc/oogaooga() - oogas -= 1 - if(oogas <= 0) - oogas = rand(2,6) - playsound(src, 'sound/creatures/gorilla.ogg', 50) - -/mob/living/simple_animal/hostile/gorilla/lesser - name = "lesser Gorilla" - desc = "An adolescent Gorilla. It may not be fully grown but, much like a banana, that just means it's sturdier and harder to chew!" - speak_chance = 100 // compensating for something - maxHealth = 120 - health = 120 - butcher_results = list(/obj/item/food/meat/slab/gorilla = 2) - speed = 0.35 - melee_damage_lower = 10 - melee_damage_upper = 15 - obj_damage = 15 - stat_attack = SOFT_CRIT - unique_name = TRUE - -/mob/living/simple_animal/hostile/gorilla/lesser/Initialize(mapload) - . = ..() - transform *= 0.75 // smolrilla - -/mob/living/simple_animal/hostile/gorilla/cargo_domestic - name = "Cargorilla" // Overriden, normally - icon = 'icons/mob/simple/cargorillia.dmi' - desc = "Cargo's pet gorilla. They seem to have an 'I love Mom' tattoo." - maxHealth = 200 - health = 200 - faction = list(FACTION_NEUTRAL, FACTION_MONKEY, FACTION_JUNGLE) - gold_core_spawnable = NO_SPAWN - unique_name = FALSE - -/mob/living/simple_animal/hostile/gorilla/cargo_domestic/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_PACIFISM, INNATE_TRAIT) - AddComponent(/datum/component/crate_carrier) - -/// Poll ghosts for control of the gorilla. -/mob/living/simple_animal/hostile/gorilla/cargo_domestic/proc/poll_for_gorilla() - AddComponent(\ - /datum/component/ghost_direct_control,\ - poll_candidates = TRUE,\ - poll_length = 30 SECONDS,\ - role_name = "Cargorilla",\ - assumed_control_message = "You are Cargorilla, a pacifistic friend of the station and carrier of freight.",\ - poll_ignore_key = POLL_IGNORE_CARGORILLA,\ - after_assumed_control = CALLBACK(src, PROC_REF(became_player_controlled)),\ - ) - -/// Called once a ghost assumes control -/mob/living/simple_animal/hostile/gorilla/cargo_domestic/proc/became_player_controlled() - mind.set_assigned_role(SSjob.GetJobType(/datum/job/cargo_technician)) - mind.special_role = "Cargorilla" - to_chat(src, span_notice("You can pick up crates by clicking on them, and drop them by clicking on the ground.")) - -/obj/item/card/id/advanced/cargo_gorilla - name = "cargorilla ID" - desc = "A card used to provide ID and determine access across the station. A gorilla-sized ID for a gorilla-sized cargo technician." - trim = /datum/id_trim/job/cargo_technician - -#undef GORILLA_TOTAL_LAYERS diff --git a/code/modules/mob/living/simple_animal/hostile/gorilla/visuals_icons.dm b/code/modules/mob/living/simple_animal/hostile/gorilla/visuals_icons.dm deleted file mode 100644 index 39dfe8f7d899..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/gorilla/visuals_icons.dm +++ /dev/null @@ -1,56 +0,0 @@ -#define GORILLA_HANDS_LAYER 1 - -/mob/living/simple_animal/hostile/gorilla/proc/apply_overlay(cache_index) - . = gorilla_overlays[cache_index] - if(.) - add_overlay(.) - -/mob/living/simple_animal/hostile/gorilla/proc/remove_overlay(cache_index) - var/I = gorilla_overlays[cache_index] - if(I) - cut_overlay(I) - gorilla_overlays[cache_index] = null - -/mob/living/simple_animal/hostile/gorilla/update_held_items() - cut_overlays("standing_overlay") - remove_overlay(GORILLA_HANDS_LAYER) - - var/standing = FALSE - for(var/I in held_items) - if(I) - standing = TRUE - break - if(!standing) - if(stat != DEAD) - icon_state = "crawling" - set_varspeed(0.5) - return ..() - if(stat != DEAD) - icon_state = "standing" - set_varspeed(1) // Gorillas are slow when standing up. - - var/list/hands_overlays = list() - - var/obj/item/l_hand = get_item_for_held_index(1) - var/obj/item/r_hand = get_item_for_held_index(2) - - if(r_hand) - var/mutable_appearance/r_hand_overlay = r_hand.build_worn_icon(default_layer = GORILLA_HANDS_LAYER, default_icon_file = r_hand.righthand_file, isinhands = TRUE) - r_hand_overlay.pixel_y -= 1 - hands_overlays += r_hand_overlay - - if(l_hand) - var/mutable_appearance/l_hand_overlay = l_hand.build_worn_icon(default_layer = GORILLA_HANDS_LAYER, default_icon_file = l_hand.lefthand_file, isinhands = TRUE) - l_hand_overlay.pixel_y -= 1 - hands_overlays += l_hand_overlay - - if(hands_overlays.len) - gorilla_overlays[GORILLA_HANDS_LAYER] = hands_overlays - apply_overlay(GORILLA_HANDS_LAYER) - add_overlay("standing_overlay") - return ..() - -/mob/living/simple_animal/hostile/gorilla/regenerate_icons() - update_held_items() - -#undef GORILLA_HANDS_LAYER diff --git a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm b/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm deleted file mode 100644 index 0c98f67eef46..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm +++ /dev/null @@ -1,427 +0,0 @@ -/mob/living/simple_animal/hostile/heretic_summon - name = "Eldritch Demon" - real_name = "Eldritch Demon" - desc = "A horror from beyond this realm." - icon = 'icons/mob/nonhuman-player/eldritch_mobs.dmi' - gender = NEUTER - mob_biotypes = NONE - attack_sound = 'sound/weapons/punch1.ogg' - response_help_continuous = "thinks better of touching" - response_help_simple = "think better of touching" - response_disarm_continuous = "flails at" - response_disarm_simple = "flail at" - response_harm_continuous = "reaps" - response_harm_simple = "tears" - speak_emote = list("screams") - speak_chance = 1 - speed = 0 - istate = ISTATE_HARM|ISTATE_BLOCKING - stop_automated_movement = TRUE - AIStatus = AI_OFF - // Sort of greenish brown, to match the vibeTM - lighting_cutoff_red = 20 - lighting_cutoff_green = 25 - lighting_cutoff_blue = 5 - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxbodytemp = INFINITY - healable = FALSE - movement_type = GROUND - pressure_resistance = 100 - del_on_death = TRUE - death_message = "implodes into itself." - loot = list(/obj/effect/gibspawner/human) - faction = list(FACTION_HERETIC) - simple_mob_flags = SILENCE_RANGED_MESSAGE - - /// Innate spells that are added when a beast is created. - var/list/actions_to_add - -/mob/living/simple_animal/hostile/heretic_summon/Initialize(mapload) - . = ..() - for(var/spell in actions_to_add) - var/datum/action/cooldown/spell/new_spell = new spell(src) - new_spell.Grant(src) - -/mob/living/simple_animal/hostile/heretic_summon/raw_prophet - name = "Raw Prophet" - real_name = "Raw Prophet" - desc = "An abomination stitched together from a few severed arms and one lost eye." - icon_state = "raw_prophet" - icon_living = "raw_prophet" - status_flags = CANPUSH - melee_damage_lower = 5 - melee_damage_upper = 10 - maxHealth = 65 - health = 65 - sight = SEE_MOBS|SEE_OBJS|SEE_TURFS - loot = list(/obj/effect/gibspawner/human, /obj/item/bodypart/arm/left, /obj/item/organ/internal/eyes) - actions_to_add = list( - /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/long, - /datum/action/cooldown/spell/list_target/telepathy/eldritch, - /datum/action/cooldown/spell/pointed/blind/eldritch, - /datum/action/innate/expand_sight, - ) - /// A weakref to the last target we smacked. Hitting targets consecutively does more damage. - var/datum/weakref/last_target - -/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/Initialize(mapload) - . = ..() - var/on_link_message = "You feel something new enter your sphere of mind... \ - You hear whispers of people far away, screeches of horror and a huming of welcome to [src]'s Mansus Link." - - var/on_unlink_message = "Your mind shatters as [src]'s Mansus Link leaves your mind." - - AddComponent(/datum/component/mind_linker, \ - network_name = "Mansus Link", \ - chat_color = "#568b00", \ - linker_action_path = /datum/action/cooldown/spell/pointed/manse_link, \ - link_message = on_link_message, \ - unlink_message = on_unlink_message, \ - post_unlink_callback = CALLBACK(src, PROC_REF(after_unlink)), \ - speech_action_background_icon_state = "bg_heretic", \ - ) - -/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/attack_animal(mob/living/simple_animal/user, list/modifiers) - if(user == src) // Easy to hit yourself + very fragile = accidental suicide, prevent that - return - - return ..() - -/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/AttackingTarget(atom/attacked_target) - if(WEAKREF(attacked_target) == last_target) - melee_damage_lower = min(melee_damage_lower + 5, 30) - melee_damage_upper = min(melee_damage_upper + 5, 35) - else - melee_damage_lower = initial(melee_damage_lower) - melee_damage_upper = initial(melee_damage_upper) - - . = ..() - if(!.) - return - - SpinAnimation(5, 1) - last_target = WEAKREF(attacked_target) - -/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - . = ..() - var/rotation_degree = (360 / 3) - if(movement_dir & WEST || movement_dir & SOUTH) - rotation_degree *= -1 - - var/matrix/to_turn = matrix(transform) - to_turn = turn(transform, rotation_degree) - animate(src, transform = to_turn, time = 0.1 SECONDS) - -/* - * Callback for the mind_linker component. - * Stuns people who are ejected from the network. - */ -/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/proc/after_unlink(mob/living/unlinked_mob) - if(QDELETED(unlinked_mob) || unlinked_mob.stat == DEAD) - return - - INVOKE_ASYNC(unlinked_mob, TYPE_PROC_REF(/mob, emote), "scream") - unlinked_mob.AdjustParalyzed(0.5 SECONDS) //micro stun - -// What if we took a linked list... But made it a mob? -/// The "Terror of the Night" / Armsy, a large worm made of multiple bodyparts that occupies multiple tiles -/mob/living/simple_animal/hostile/heretic_summon/armsy - name = "Terror of the night" - real_name = "Armsy" - desc = "An abomination made from dozens and dozens of severed and malformed limbs piled onto each other." - icon_state = "armsy_start" - icon_living = "armsy_start" - maxHealth = 200 - health = 200 - melee_damage_lower = 10 - melee_damage_upper = 15 - move_force = MOVE_FORCE_OVERPOWERING - move_resist = MOVE_FORCE_OVERPOWERING - pull_force = MOVE_FORCE_OVERPOWERING - movement_type = GROUND - mob_size = MOB_SIZE_HUGE - sentience_type = SENTIENCE_BOSS - environment_smash = ENVIRONMENT_SMASH_RWALLS - mob_biotypes = MOB_ORGANIC|MOB_EPIC - obj_damage = 200 - ranged_cooldown_time = 5 - ranged = TRUE - rapid = 1 - actions_to_add = list(/datum/action/cooldown/spell/worm_contract) - ///Previous segment in the chain - var/mob/living/simple_animal/hostile/heretic_summon/armsy/back - ///Next segment in the chain - var/mob/living/simple_animal/hostile/heretic_summon/armsy/front - ///Your old location - var/oldloc - ///Allow / disallow pulling - var/allow_pulling = FALSE - ///How many arms do we have to eat to expand? - var/stacks_to_grow = 5 - ///Currently eaten arms - var/current_stacks = 0 - ///Does this follow other pieces? - var/follow = TRUE - -/* - * Arguments - * * spawn_bodyparts - whether we spawn additional armsy bodies until we reach length. - * * worm_length - the length of the worm we're creating. Below 3 doesn't work very well. - */ -/mob/living/simple_animal/hostile/heretic_summon/armsy/Initialize(mapload, spawn_bodyparts = TRUE, worm_length = 6) - . = ..() - if(worm_length < 3) - stack_trace("[type] created with invalid len ([worm_length]). Reverting to 3.") - worm_length = 3 //code breaks below 3, let's just not allow it. - - oldloc = loc - RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(update_chain_links)) - if(!spawn_bodyparts) - return - - AddComponent(/datum/component/blood_walk, \ - blood_type = /obj/effect/decal/cleanable/blood/tracks, \ - target_dir_change = TRUE) - - allow_pulling = TRUE - // Sets the hp of the head to be exactly the (length * hp), so the head is de facto the hardest to destroy. - maxHealth = worm_length * maxHealth - health = maxHealth - - // The previous link in the chain - var/mob/living/simple_animal/hostile/heretic_summon/armsy/prev = src - // The current link in the chain - var/mob/living/simple_animal/hostile/heretic_summon/armsy/current - - for(var/i in 1 to worm_length) - current = new type(drop_location(), FALSE) - current.icon_state = "armsy_mid" - current.icon_living = "armsy_mid" - current.AIStatus = AI_OFF - current.front = prev - prev.back = current - prev = current - - prev.icon_state = "armsy_end" - prev.icon_living = "armsy_end" - -/mob/living/simple_animal/hostile/heretic_summon/armsy/adjustBruteLoss(amount, updating_health, forced, required_bodytype) - if(back) - return back.adjustBruteLoss(amount, updating_health, forced) - - return ..() - -/mob/living/simple_animal/hostile/heretic_summon/armsy/adjustFireLoss(amount, updating_health, forced, required_bodytype) - if(back) - return back.adjustFireLoss(amount, updating_health, forced) - - return ..() - -// We are literally a vessel of otherworldly destruction, we bring our own gravity unto this plane -/mob/living/simple_animal/hostile/heretic_summon/armsy/has_gravity(turf/T) - return TRUE - -/mob/living/simple_animal/hostile/heretic_summon/armsy/can_be_pulled() - return FALSE - -/// Updates every body in the chain to force move onto a single tile. -/mob/living/simple_animal/hostile/heretic_summon/armsy/proc/contract_next_chain_into_single_tile() - if(!back) - return - - back.forceMove(loc) - back.contract_next_chain_into_single_tile() - -/* - * Recursively get the length of our chain. - */ -/mob/living/simple_animal/hostile/heretic_summon/armsy/proc/get_length() - . = 1 - if(back) - . += back.get_length() - -/// Updates the next mob in the chain to move to our last location. Fixes the chain if somehow broken. -/mob/living/simple_animal/hostile/heretic_summon/armsy/proc/update_chain_links() - SIGNAL_HANDLER - - if(!follow) - return - - if(back && back.loc != oldloc) - back.Move(oldloc) - - // self fixing properties if somehow broken - if(front && loc != front.oldloc) - forceMove(front.oldloc) - - oldloc = loc - -/mob/living/simple_animal/hostile/heretic_summon/armsy/Destroy() - if(front) - front.icon_state = "armsy_end" - front.icon_living = "armsy_end" - front.back = null - front = null - if(back) - QDEL_NULL(back) // chain destruction baby - return ..() - -/* - * Handle healing our chain. - * - * Eating arms off the ground heals us, - * and if we eat enough arms while above - * a certain health threshold, we even gain back parts! - */ -/mob/living/simple_animal/hostile/heretic_summon/armsy/proc/heal() - if(back) - back.heal() - return - - adjustBruteLoss(-maxHealth * 0.5, FALSE) - adjustFireLoss(-maxHealth * 0.5, FALSE) - - if(health < maxHealth * 0.8) - return - - current_stacks++ - if(current_stacks < stacks_to_grow) - return - - var/mob/living/simple_animal/hostile/heretic_summon/armsy/prev = new type(drop_location(), FALSE) - icon_state = "armsy_mid" - icon_living = "armsy_mid" - back = prev - prev.icon_state = "armsy_end" - prev.icon_living = "armsy_end" - prev.front = src - prev.AIStatus = AI_OFF - current_stacks = 0 - -/mob/living/simple_animal/hostile/heretic_summon/armsy/Shoot(atom/targeted_atom) - GiveTarget(targeted_atom) - AttackingTarget() - -/mob/living/simple_animal/hostile/heretic_summon/armsy/AttackingTarget() - if(istype(target, /obj/item/bodypart/arm)) - playsound(src, 'sound/magic/demon_consume.ogg', 50, TRUE) - qdel(target) - heal() - return - if(target == back || target == front) - return - if(back) - back.GiveTarget(target) - back.AttackingTarget() - if(!Adjacent(target)) - return - do_attack_animation(target) - - if(iscarbon(target)) - var/mob/living/carbon/carbon_target = target - if(HAS_TRAIT(carbon_target, TRAIT_NODISMEMBER)) - return ..() - - var/list/parts_to_remove = list() - for(var/obj/item/bodypart/bodypart in carbon_target.bodyparts) - if(bodypart.body_part != HEAD && bodypart.body_part != CHEST && bodypart.body_part != LEG_LEFT && bodypart.body_part != LEG_RIGHT) - if(!(bodypart.bodypart_flags & BODYPART_UNREMOVABLE)) - parts_to_remove += bodypart - - if(parts_to_remove.len && prob(10)) - var/obj/item/bodypart/lost_arm = pick(parts_to_remove) - lost_arm.dismember() - - return ..() - -/mob/living/simple_animal/hostile/heretic_summon/armsy/prime - name = "Lord of the Night" - real_name = "Master of Decay" - maxHealth = 400 - health = 400 - melee_damage_lower = 30 - melee_damage_upper = 50 - -/mob/living/simple_animal/hostile/heretic_summon/armsy/prime/Initialize(mapload, spawn_bodyparts = TRUE, worm_length = 9) - . = ..() - var/matrix/matrix_transformation = matrix() - matrix_transformation.Scale(1.4, 1.4) - transform = matrix_transformation - -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit - name = "Rust Walker" - real_name = "Rusty" - desc = "An incomprehensible abomination. Everywhere it steps, it appears to be actively seeping life out of its surroundings." - icon_state = "rust_walker_s" - icon_living = "rust_walker_s" - status_flags = CANPUSH - maxHealth = 75 - health = 75 - melee_damage_lower = 15 - melee_damage_upper = 20 - sight = SEE_TURFS - actions_to_add = list( - /datum/action/cooldown/spell/aoe/rust_conversion/small, - /datum/action/cooldown/spell/basic_projectile/rust_wave/short, - ) - -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit/setDir(newdir) - . = ..() - if(newdir == NORTH) - icon_state = "rust_walker_n" - else if(newdir == SOUTH) - icon_state = "rust_walker_s" - update_appearance(UPDATE_ICON_STATE) - -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - . = ..() - playsound(src, 'sound/effects/footstep/rustystep1.ogg', 100, TRUE) - -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if(stat == DEAD) - return ..() - - var/turf/our_turf = get_turf(src) - if(HAS_TRAIT(our_turf, TRAIT_RUSTY)) - adjustBruteLoss(-1.5 * seconds_per_tick, FALSE) - adjustFireLoss(-1.5 * seconds_per_tick, FALSE) - - return ..() - -/mob/living/simple_animal/hostile/heretic_summon/ash_spirit - name = "Ash Man" - real_name = "Ashy" - desc = "An incomprehensible abomination. As it moves, a thin trail of ash follows, appearing from seemingly nowhere." - icon_state = "ash_walker" - icon_living = "ash_walker" - status_flags = CANPUSH - maxHealth = 75 - health = 75 - melee_damage_lower = 15 - melee_damage_upper = 20 - sight = SEE_TURFS - actions_to_add = list( - /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash, - /datum/action/cooldown/spell/pointed/cleave, - /datum/action/cooldown/spell/fire_sworn, - ) - -/mob/living/simple_animal/hostile/heretic_summon/stalker - name = "Flesh Stalker" - real_name = "Flesh Stalker" - desc = "An abomination made from several limbs and organs. Every moment you stare at it, it appears to shift and change unnaturally." - icon_state = "stalker" - icon_living = "stalker" - status_flags = CANPUSH - maxHealth = 150 - health = 150 - melee_damage_lower = 15 - melee_damage_upper = 20 - sight = SEE_MOBS - actions_to_add = list( - /datum/action/cooldown/spell/shapeshift/eldritch, - /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash, - /datum/action/cooldown/spell/emp/eldritch, - ) diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index ec5b53c0bce6..7e76d70f0f7d 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -172,7 +172,7 @@ Goto(P.starting, move_to_delay, 3) return ..() -//////////////HOSTILE MOB TARGETTING AND AGGRESSION//////////// +//////////////HOSTILE MOB TARGETING AND AGGRESSION//////////// /mob/living/simple_animal/hostile/proc/ListTargets() //Step 1, find out what we can see var/atom/target_from = GET_TARGETS_FROM(src) @@ -196,7 +196,7 @@ possible_targets = ListTargets() for(var/atom/pos_targ as anything in possible_targets) - if(Found(pos_targ)) //Just in case people want to override targetting + if(Found(pos_targ)) //Just in case people want to override targeting all_potential_targets = list(pos_targ) break @@ -259,7 +259,7 @@ if(search_objects < 2) if(isliving(the_target)) var/mob/living/L = the_target - var/faction_check = faction_check_mob(L) + var/faction_check = faction_check_atom(L) if(robust_searching) if(faction_check && !attack_same) return FALSE @@ -417,7 +417,7 @@ SSmove_manager.stop_looping(src) LoseAggro() -//////////////END HOSTILE MOB TARGETTING AND AGGRESSION//////////// +//////////////END HOSTILE MOB TARGETING AND AGGRESSION//////////// /mob/living/simple_animal/hostile/death(gibbed) LoseTarget() @@ -428,7 +428,7 @@ playsound(loc, 'sound/machines/chime.ogg', 50, TRUE, -1) var/atom/target_from = GET_TARGETS_FROM(src) for(var/mob/living/simple_animal/hostile/M in oview(distance, target_from)) - if(faction_check_mob(M, TRUE)) + if(faction_check_atom(M, TRUE)) if(M.AIStatus == AI_OFF) return else @@ -440,7 +440,7 @@ for(var/mob/living/L in T) if(L == src || L == A) continue - if(faction_check_mob(L) && !attack_same) + if(faction_check_atom(L) && !attack_same) return TRUE /mob/living/simple_animal/hostile/proc/OpenFire(atom/A) diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm b/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm index 73b7f6aa6260..102fd13c532c 100644 --- a/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm +++ b/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm @@ -40,7 +40,7 @@ nondirectional_sprite = TRUE impact_effect_type = /obj/effect/temp_visual/leaper_projectile_impact -/obj/projectile/leaper/on_hit(atom/target, blocked = FALSE) +/obj/projectile/leaper/on_hit(atom/target, blocked = 0, pierce_hit) ..() if (!isliving(target)) return @@ -148,6 +148,7 @@ /mob/living/simple_animal/hostile/jungle/leaper/Initialize(mapload) . = ..() + AddComponent(/datum/component/seethrough_mob) remove_verb(src, /mob/living/verb/pulled) add_cell_sample() @@ -215,9 +216,8 @@ if(z != target.z) return hopping = TRUE - set_density(FALSE) + add_traits(list(TRAIT_UNDENSE, TRAIT_NO_TRANSFORM), LEAPING_TRAIT) pass_flags |= PASSMOB - notransform = TRUE var/turf/new_turf = locate((target.x + rand(-3,3)),(target.y + rand(-3,3)),target.z) if(player_hop) new_turf = get_turf(target) @@ -228,8 +228,7 @@ throw_at(new_turf, max(3,get_dist(src,new_turf)), 1, src, FALSE, callback = CALLBACK(src, PROC_REF(FinishHop))) /mob/living/simple_animal/hostile/jungle/leaper/proc/FinishHop() - set_density(TRUE) - notransform = FALSE + remove_traits(list(TRAIT_UNDENSE, TRAIT_NO_TRANSFORM), LEAPING_TRAIT) pass_flags &= ~PASSMOB hopping = FALSE playsound(src.loc, 'sound/effects/meteorimpact.ogg', 100, TRUE) @@ -240,18 +239,17 @@ /mob/living/simple_animal/hostile/jungle/leaper/proc/BellyFlop() var/turf/new_turf = get_turf(target) hopping = TRUE - notransform = TRUE + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, LEAPING_TRAIT) new /obj/effect/temp_visual/leaper_crush(new_turf) - addtimer(CALLBACK(src, PROC_REF(BellyFlopHop), new_turf), 30) + addtimer(CALLBACK(src, PROC_REF(BellyFlopHop), new_turf), 3 SECONDS) /mob/living/simple_animal/hostile/jungle/leaper/proc/BellyFlopHop(turf/T) - set_density(FALSE) + ADD_TRAIT(src, TRAIT_UNDENSE, LEAPING_TRAIT) throw_at(T, get_dist(src,T),1,src, FALSE, callback = CALLBACK(src, PROC_REF(Crush))) /mob/living/simple_animal/hostile/jungle/leaper/proc/Crush() hopping = FALSE - set_density(TRUE) - notransform = FALSE + remove_traits(list(TRAIT_UNDENSE, TRAIT_NO_TRANSFORM), LEAPING_TRAIT) playsound(src, 'sound/effects/meteorimpact.ogg', 200, TRUE) for(var/mob/living/L in orange(1, src)) L.adjustBruteLoss(35) diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm b/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm deleted file mode 100644 index 2e65ba824568..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm +++ /dev/null @@ -1,229 +0,0 @@ -#define MOOK_ATTACK_NEUTRAL 0 -#define MOOK_ATTACK_WARMUP 1 -#define MOOK_ATTACK_ACTIVE 2 -#define MOOK_ATTACK_RECOVERY 3 -#define ATTACK_INTERMISSION_TIME 5 - -//Fragile but highly aggressive wanderers that pose a large threat in numbers. -//They'll attempt to leap at their target from afar using their hatchets. -/mob/living/simple_animal/hostile/jungle/mook - name = "wanderer" - desc = "This unhealthy looking primitive is wielding a rudimentary hatchet, swinging it with wild abandon. One isn't much of a threat, but in numbers they can quickly overwhelm a superior opponent." - icon = 'icons/mob/simple/jungle/mook.dmi' - icon_state = "mook" - icon_living = "mook" - icon_dead = "mook_dead" - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - SET_BASE_PIXEL(-16, -8) - - maxHealth = 45 - health = 45 - melee_damage_lower = 30 - melee_damage_upper = 30 - ranged = TRUE - ranged_cooldown_time = 10 - pass_flags_self = LETPASSTHROW - robust_searching = TRUE - stat_attack = HARD_CRIT - attack_sound = 'sound/weapons/rapierhit.ogg' - attack_vis_effect = ATTACK_EFFECT_SLASH - death_sound = 'sound/voice/mook_death.ogg' - aggro_vision_range = 15 //A little more aggressive once in combat to balance out their really low HP - var/attack_state = MOOK_ATTACK_NEUTRAL - var/struck_target_leap = FALSE - - footstep_type = FOOTSTEP_MOB_BAREFOOT - -/mob/living/simple_animal/hostile/jungle/mook/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - if(istype(mover, /mob/living/simple_animal/hostile/jungle/mook)) - var/mob/living/simple_animal/hostile/jungle/mook/mook_moover = mover - if(mook_moover.attack_state == MOOK_ATTACK_ACTIVE && mook_moover.throwing) - return TRUE - -/mob/living/simple_animal/hostile/jungle/mook/death() - desc = "A deceased primitive. Upon closer inspection, it was suffering from severe cellular degeneration and its garments are machine made..."//Can you guess the twist - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/AttackingTarget() - if(isliving(target)) - if(ranged_cooldown <= world.time && attack_state == MOOK_ATTACK_NEUTRAL) - var/mob/living/L = target - if(L.incapacitated()) - WarmupAttack(forced_slash_combo = TRUE) - return - WarmupAttack() - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/Goto() - if(attack_state != MOOK_ATTACK_NEUTRAL) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/Move() - if(attack_state == MOOK_ATTACK_WARMUP || attack_state == MOOK_ATTACK_RECOVERY) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/proc/WarmupAttack(forced_slash_combo = FALSE) - if(attack_state == MOOK_ATTACK_NEUTRAL && target) - attack_state = MOOK_ATTACK_WARMUP - SSmove_manager.stop_looping(src) - update_icons() - if(prob(50) && get_dist(src,target) <= 3 || forced_slash_combo) - addtimer(CALLBACK(src, PROC_REF(SlashCombo)), ATTACK_INTERMISSION_TIME) - return - addtimer(CALLBACK(src, PROC_REF(LeapAttack)), ATTACK_INTERMISSION_TIME + rand(0,3)) - return - attack_state = MOOK_ATTACK_RECOVERY - ResetNeutral() - -/mob/living/simple_animal/hostile/jungle/mook/proc/SlashCombo() - if(attack_state == MOOK_ATTACK_WARMUP && !stat) - attack_state = MOOK_ATTACK_ACTIVE - update_icons() - SlashAttack() - addtimer(CALLBACK(src, PROC_REF(SlashAttack)), 3) - addtimer(CALLBACK(src, PROC_REF(SlashAttack)), 6) - addtimer(CALLBACK(src, PROC_REF(AttackRecovery)), 9) - -/mob/living/simple_animal/hostile/jungle/mook/proc/SlashAttack() - if(target && !stat && attack_state == MOOK_ATTACK_ACTIVE) - melee_damage_lower = 15 - melee_damage_upper = 15 - var/mob_direction = get_dir(src,target) - var/atom/target_from = GET_TARGETS_FROM(src) - if(get_dist(src,target) > 1) - step(src,mob_direction) - if(isturf(target_from.loc) && target.Adjacent(target_from) && isliving(target)) - var/mob/living/L = target - L.attack_animal(src) - return - var/swing_turf = get_step(src,mob_direction) - new /obj/effect/temp_visual/kinetic_blast(swing_turf) - playsound(src, 'sound/weapons/slashmiss.ogg', 50, TRUE) - -/mob/living/simple_animal/hostile/jungle/mook/proc/LeapAttack() - if(target && !stat && attack_state == MOOK_ATTACK_WARMUP) - attack_state = MOOK_ATTACK_ACTIVE - set_density(FALSE) - melee_damage_lower = 30 - melee_damage_upper = 30 - update_icons() - new /obj/effect/temp_visual/mook_dust(get_turf(src)) - playsound(src, 'sound/weapons/thudswoosh.ogg', 25, TRUE) - playsound(src, 'sound/voice/mook_leap_yell.ogg', 100, TRUE) - var/target_turf = get_turf(target) - throw_at(target_turf, 7, 1, src, FALSE, callback = CALLBACK(src, PROC_REF(AttackRecovery))) - return - attack_state = MOOK_ATTACK_RECOVERY - ResetNeutral() - -/mob/living/simple_animal/hostile/jungle/mook/proc/AttackRecovery() - if(attack_state == MOOK_ATTACK_ACTIVE && !stat) - attack_state = MOOK_ATTACK_RECOVERY - set_density(TRUE) - face_atom(target) - if(!struck_target_leap) - update_icons() - struck_target_leap = FALSE - if(prob(40)) - attack_state = MOOK_ATTACK_NEUTRAL - if(target) - if(isliving(target)) - var/mob/living/L = target - if(L.incapacitated() && L.stat != DEAD) - addtimer(CALLBACK(src, PROC_REF(WarmupAttack), TRUE), ATTACK_INTERMISSION_TIME) - return - addtimer(CALLBACK(src, PROC_REF(WarmupAttack)), ATTACK_INTERMISSION_TIME) - return - addtimer(CALLBACK(src, PROC_REF(ResetNeutral)), ATTACK_INTERMISSION_TIME) - -/mob/living/simple_animal/hostile/jungle/mook/proc/ResetNeutral() - if(attack_state == MOOK_ATTACK_RECOVERY) - attack_state = MOOK_ATTACK_NEUTRAL - ranged_cooldown = world.time + ranged_cooldown_time - update_icons() - if(target && !stat) - update_icons() - Goto(target, move_to_delay, minimum_distance) - -/mob/living/simple_animal/hostile/jungle/mook/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - if(isliving(hit_atom) && attack_state == MOOK_ATTACK_ACTIVE) - var/mob/living/L = hit_atom - if(CanAttack(L)) - L.attack_animal(src) - struck_target_leap = TRUE - set_density(TRUE) - update_icons() - var/mook_under_us = FALSE - for(var/A in get_turf(src)) - if(struck_target_leap && mook_under_us) - break - if(A == src) - continue - if(isliving(A)) - var/mob/living/ML = A - if(!struck_target_leap && CanAttack(ML))//Check if some joker is attempting to use rest to evade us - struck_target_leap = TRUE - ML.attack_animal(src) - set_density(TRUE) - struck_target_leap = TRUE - update_icons() - continue - if(istype(ML, /mob/living/simple_animal/hostile/jungle/mook) && !mook_under_us)//If we land on the same tile as another mook, spread out so we don't stack our sprite on the same tile - var/mob/living/simple_animal/hostile/jungle/mook/M = ML - if(!M.stat) - mook_under_us = TRUE - var/anydir = pick(GLOB.cardinals) - Move(get_step(src, anydir), anydir) - continue - -/mob/living/simple_animal/hostile/jungle/mook/handle_automated_action() - if(attack_state) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/OpenFire() - if(isliving(target)) - var/mob/living/L = target - if(L.incapacitated()) - return - WarmupAttack() - -/mob/living/simple_animal/hostile/jungle/mook/update_icons() - . = ..() - if(!stat) - switch(attack_state) - if(MOOK_ATTACK_NEUTRAL) - icon_state = "mook" - if(MOOK_ATTACK_WARMUP) - icon_state = "mook_warmup" - if(MOOK_ATTACK_ACTIVE) - if(!density) - icon_state = "mook_leap" - return - if(struck_target_leap) - icon_state = "mook_strike" - return - icon_state = "mook_slash_combo" - if(MOOK_ATTACK_RECOVERY) - icon_state = "mook" - -/obj/effect/temp_visual/mook_dust - name = "dust" - desc = "It's just a dust cloud!" - icon = 'icons/mob/simple/jungle/mook.dmi' - icon_state = "mook_leap_cloud" - layer = BELOW_MOB_LAYER - plane = GAME_PLANE - SET_BASE_PIXEL(-16, -16) - duration = 10 - -#undef MOOK_ATTACK_NEUTRAL -#undef MOOK_ATTACK_WARMUP -#undef MOOK_ATTACK_ACTIVE -#undef MOOK_ATTACK_RECOVERY -#undef ATTACK_INTERMISSION_TIME diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm deleted file mode 100644 index f4f726bc0bac..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm +++ /dev/null @@ -1,240 +0,0 @@ -#define SEEDLING_STATE_NEUTRAL 0 -#define SEEDLING_STATE_WARMUP 1 -#define SEEDLING_STATE_ACTIVE 2 -#define SEEDLING_STATE_RECOVERY 3 - -//A plant rooted in the ground that forfeits its melee attack in favor of ranged barrages. -//It will fire flurries of solar energy, and occasionally charge up a powerful blast that makes it vulnerable to attack. -/mob/living/simple_animal/hostile/jungle/seedling - name = "seedling" - desc = "This oversized, predatory flower conceals what can only be described as an organic energy cannon, and it will not die until its hidden vital organs are sliced out. \ - The concentrated streams of energy it sometimes produces require its full attention, attacking it during this time will prevent it from finishing its attack." - icon = 'icons/mob/simple/jungle/seedling.dmi' - icon_state = "seedling" - icon_living = "seedling" - icon_dead = "seedling_dead" - mob_biotypes = MOB_ORGANIC | MOB_PLANT - maxHealth = 100 - health = 100 - melee_damage_lower = 30 - melee_damage_upper = 30 - SET_BASE_PIXEL(-16, -14) - - minimum_distance = 3 - move_to_delay = 20 - vision_range = 9 - aggro_vision_range = 15 - ranged = TRUE - ranged_cooldown_time = 10 - projectiletype = /obj/projectile/seedling - projectilesound = 'sound/weapons/pierce.ogg' - robust_searching = TRUE - stat_attack = HARD_CRIT - move_resist = MOVE_FORCE_EXTREMELY_STRONG - var/combatant_state = SEEDLING_STATE_NEUTRAL - var/mob/living/beam_debuff_target - var/solar_beam_identifier = 0 - -/obj/projectile/seedling - name = "solar energy" - icon_state = "seedling" - damage = 10 - damage_type = BURN - light_outer_range = 2 - armor_flag = ENERGY - light_color = LIGHT_COLOR_DIM_YELLOW - hitsound = 'sound/weapons/sear.ogg' - hitsound_wall = 'sound/weapons/effects/searwall.ogg' - nondirectional_sprite = TRUE - -/obj/projectile/seedling/Bump(atom/A)//Stops seedlings from destroying other jungle mobs through FF - if(isliving(A)) - var/mob/living/L = A - if(FACTION_JUNGLE in L.faction) - return FALSE - return ..() - -/obj/effect/temp_visual/solarbeam_killsat - name = "beam of solar energy" - icon_state = "solar_beam" - icon = 'icons/effects/beam.dmi' - plane = LIGHTING_PLANE - layer = LIGHTING_PRIMARY_LAYER - duration = 5 - randomdir = FALSE - -/datum/status_effect/seedling_beam_indicator - id = "seedling beam indicator" - duration = 30 - status_type = STATUS_EFFECT_MULTIPLE - alert_type = null - tick_interval = 1 - var/atom/movable/screen/seedling/seedling_screen_object - var/atom/target - - -/datum/status_effect/seedling_beam_indicator/on_creation(mob/living/new_owner, target_plant) - . = ..() - if(.) - target = target_plant - tick() - -/datum/status_effect/seedling_beam_indicator/on_apply() - if(owner.client) - seedling_screen_object = new /atom/movable/screen/seedling() - owner.client.screen += seedling_screen_object - tick() - return ..() - -/datum/status_effect/seedling_beam_indicator/Destroy() - if(owner) - if(owner.client) - owner.client.screen -= seedling_screen_object - return ..() - -/datum/status_effect/seedling_beam_indicator/tick() - var/target_angle = get_angle(owner, target) - var/matrix/final = matrix() - final.Turn(target_angle) - seedling_screen_object.transform = final - -/atom/movable/screen/seedling - icon = 'icons/mob/simple/jungle/arachnid.dmi' - icon_state = "seedling_beam_indicator" - screen_loc = "CENTER:-16,CENTER:-16" - -/mob/living/simple_animal/hostile/jungle/seedling/Goto() - if(combatant_state != SEEDLING_STATE_NEUTRAL) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/seedling/AttackingTarget() - if(isliving(target)) - if(ranged_cooldown <= world.time && combatant_state == SEEDLING_STATE_NEUTRAL) - OpenFire(target) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/seedling/OpenFire() - WarmupAttack() - -/mob/living/simple_animal/hostile/jungle/seedling/proc/WarmupAttack() - if(combatant_state == SEEDLING_STATE_NEUTRAL) - combatant_state = SEEDLING_STATE_WARMUP - SSmove_manager.stop_looping(src) - update_icons() - var/target_dist = get_dist(src,target) - var/living_target_check = isliving(target) - if(living_target_check) - if(target_dist > 7)//Offscreen check - SolarBeamStartup(target) - return - if(get_dist(src,target) >= 4 && prob(40)) - SolarBeamStartup(target) - return - addtimer(CALLBACK(src, PROC_REF(Volley)), 5) - -/mob/living/simple_animal/hostile/jungle/seedling/proc/SolarBeamStartup(mob/living/living_target)//It's more like requiem than final spark - if(combatant_state == SEEDLING_STATE_WARMUP && target) - combatant_state = SEEDLING_STATE_ACTIVE - living_target.apply_status_effect(/datum/status_effect/seedling_beam_indicator, src) - beam_debuff_target = living_target - playsound(src,'sound/effects/seedling_chargeup.ogg', 100, FALSE) - if(get_dist(src,living_target) > 7) - playsound(living_target,'sound/effects/seedling_chargeup.ogg', 100, FALSE) - solar_beam_identifier = world.time - addtimer(CALLBACK(src, PROC_REF(Beamu), living_target, solar_beam_identifier), 35) - -/mob/living/simple_animal/hostile/jungle/seedling/proc/Beamu(mob/living/living_target, beam_id = 0) - if(combatant_state == SEEDLING_STATE_ACTIVE && living_target && beam_id == solar_beam_identifier) - if(living_target.z == z) - update_icons() - var/obj/effect/temp_visual/solarbeam_killsat/S = new (get_turf(src)) - var/matrix/starting = matrix() - starting.Scale(1,32) - starting.Translate(0,520) - S.transform = starting - var/obj/effect/temp_visual/solarbeam_killsat/K = new (get_turf(living_target)) - var/matrix/final = matrix() - final.Scale(1,32) - final.Translate(0,512) - K.transform = final - living_target.adjustFireLoss(30) - living_target.adjust_fire_stacks(0.2)//Just here for the showmanship - living_target.ignite_mob() - playsound(living_target,'sound/weapons/sear.ogg', 50, TRUE) - addtimer(CALLBACK(src, PROC_REF(AttackRecovery)), 5) - return - AttackRecovery() - -/mob/living/simple_animal/hostile/jungle/seedling/proc/Volley() - if(combatant_state == SEEDLING_STATE_WARMUP && target) - combatant_state = SEEDLING_STATE_ACTIVE - update_icons() - var/datum/callback/cb = CALLBACK(src, PROC_REF(InaccurateShot)) - for(var/i in 1 to 13) - addtimer(cb, i) - addtimer(CALLBACK(src, PROC_REF(AttackRecovery)), 14) - -/mob/living/simple_animal/hostile/jungle/seedling/proc/InaccurateShot() - if(!QDELETED(target) && combatant_state == SEEDLING_STATE_ACTIVE && !stat) - if(get_dist(src,target) <= 3)//If they're close enough just aim straight at them so we don't miss at point blank ranges - Shoot(target) - return - var/turf/our_turf = get_turf(src) - var/obj/projectile/seedling/readied_shot = new /obj/projectile/seedling(our_turf) - readied_shot.preparePixelProjectile(target, src, null, rand(-10, 10)) - readied_shot.fire() - playsound(src, projectilesound, 100, TRUE) - -/mob/living/simple_animal/hostile/jungle/seedling/proc/AttackRecovery() - if(combatant_state == SEEDLING_STATE_ACTIVE) - combatant_state = SEEDLING_STATE_RECOVERY - update_icons() - ranged_cooldown = world.time + ranged_cooldown_time - if(target) - face_atom(target) - addtimer(CALLBACK(src, PROC_REF(ResetNeutral)), 10) - -/mob/living/simple_animal/hostile/jungle/seedling/proc/ResetNeutral() - combatant_state = SEEDLING_STATE_NEUTRAL - if(target && !stat) - update_icons() - Goto(target, move_to_delay, minimum_distance) - -/mob/living/simple_animal/hostile/jungle/seedling/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(combatant_state == SEEDLING_STATE_ACTIVE && beam_debuff_target) - beam_debuff_target.remove_status_effect(/datum/status_effect/seedling_beam_indicator) - beam_debuff_target = null - solar_beam_identifier = 0 - AttackRecovery() - -/mob/living/simple_animal/hostile/jungle/seedling/update_icons() - . = ..() - if(!stat) - switch(combatant_state) - if(SEEDLING_STATE_NEUTRAL) - icon_state = "seedling" - if(SEEDLING_STATE_WARMUP) - icon_state = "seedling_charging" - if(SEEDLING_STATE_ACTIVE) - icon_state = "seedling_fire" - if(SEEDLING_STATE_RECOVERY) - icon_state = "seedling" - -/mob/living/simple_animal/hostile/jungle/seedling/GiveTarget() - if(target) - if(combatant_state == SEEDLING_STATE_WARMUP || combatant_state == SEEDLING_STATE_ACTIVE)//So it doesn't 180 and blast you in the face while it's firing at someone else - return - return ..() - -/mob/living/simple_animal/hostile/jungle/seedling/LoseTarget() - if(combatant_state == SEEDLING_STATE_WARMUP || combatant_state == SEEDLING_STATE_ACTIVE) - return - return ..() - -#undef SEEDLING_STATE_NEUTRAL -#undef SEEDLING_STATE_WARMUP -#undef SEEDLING_STATE_ACTIVE -#undef SEEDLING_STATE_RECOVERY diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm index ece63945e320..689e1391bfd6 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm @@ -50,11 +50,10 @@ var/chosen_attack = 1 /// Attack actions, sets chosen_attack to the number in the action var/list/attack_action_types = list() - /// If there is a small sprite icon for players controlling the megafauna to use - var/small_sprite_type /mob/living/simple_animal/hostile/megafauna/Initialize(mapload) . = ..() + AddComponent(/datum/component/seethrough_mob) AddElement(/datum/element/simple_flying) if(gps_name && true_spawn) AddComponent(/datum/component/gps, gps_name) @@ -63,9 +62,6 @@ for(var/action_type in attack_action_types) var/datum/action/innate/megafauna_attack/attack_action = new action_type() attack_action.Grant(src) - if(small_sprite_type) - var/datum/action/small_sprite/small_action = new small_sprite_type() - small_action.Grant(src) /mob/living/simple_animal/hostile/megafauna/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) //Safety check diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm index 55b80a8d0b89..8e7a48f5c2cb 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm @@ -67,7 +67,6 @@ Difficulty: Hard score_achievement_type = /datum/award/score/bubblegum_score death_message = "sinks into a pool of blood, fleeing the battle. You've won, for now... " death_sound = 'sound/magic/enter_blood.ogg' - small_sprite_type = /datum/action/small_sprite/megafauna/bubblegum faction = list(FACTION_MINING, FACTION_BOSS, FACTION_HELL) /// Check to see if we should spawn blood var/spawn_blood = TRUE @@ -138,7 +137,7 @@ Difficulty: Hard . = list() for(var/mob/living/L in targets) var/list/bloodpool = get_bloodcrawlable_pools(get_turf(L), 0) - if(bloodpool.len && (!faction_check_mob(L) || L.stat == DEAD)) + if(bloodpool.len && (!faction_check_atom(L) || L.stat == DEAD)) . += L /** @@ -202,7 +201,7 @@ Difficulty: Hard new /obj/effect/temp_visual/bubblegum_hands/leftsmack(T) SLEEP_CHECK_DEATH(4, src) for(var/mob/living/L in T) - if(!faction_check_mob(L)) + if(!faction_check_atom(L)) to_chat(L, span_userdanger("[src] rends you!")) playsound(T, attack_sound, 100, TRUE, -1) var/limb_to_hit = L.get_bodypart(L.get_random_valid_zone(even_weights = TRUE)) @@ -218,7 +217,7 @@ Difficulty: Hard new /obj/effect/temp_visual/bubblegum_hands/leftthumb(T) SLEEP_CHECK_DEATH(6, src) for(var/mob/living/L in T) - if(!faction_check_mob(L)) + if(!faction_check_atom(L)) if(L.stat != CONSCIOUS) to_chat(L, span_userdanger("[src] drags you through the blood!")) playsound(T, 'sound/magic/enter_blood.ogg', 100, TRUE, -1) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 88a009c23a43..265bde8e1282 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -55,7 +55,6 @@ loot = list(/obj/structure/closet/crate/necropolis/colossus) death_message = "disintegrates, leaving a glowing core in its wake." death_sound = 'sound/magic/demon_dies.ogg' - small_sprite_type = /datum/action/small_sprite/megafauna/colossus /// Spiral shots ability var/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots/colossus/spiral_shots /// Random shots ablity @@ -187,7 +186,6 @@ damage = 25 armour_penetration = 50 speed = 2 - eyeblur = 0 damage_type = BRUTE pass_flags = PASSTABLE plane = GAME_PLANE @@ -198,7 +196,7 @@ direct_target = TRUE return ..(target, direct_target, ignore_loc, cross_failed) -/obj/projectile/colossus/on_hit(atom/target, blocked = FALSE) +/obj/projectile/colossus/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) var/mob/living/dust_mob = target @@ -598,11 +596,10 @@ /obj/structure/closet/stasis/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) if(isliving(arrived) && holder_animal) - var/mob/living/L = arrived - L.notransform = 1 - ADD_TRAIT(L, TRAIT_MUTE, STASIS_MUTE) - L.status_flags |= GODMODE - L.mind.transfer_to(holder_animal) + var/mob/living/possessor = arrived + possessor.add_traits(list(TRAIT_UNDENSE, TRAIT_NO_TRANSFORM), STASIS_MUTE) + possessor.status_flags |= GODMODE + possessor.mind.transfer_to(holder_animal) var/datum/action/exit_possession/escape = new(holder_animal) escape.Grant(holder_animal) remove_verb(holder_animal, /mob/living/verb/pulled) @@ -610,9 +607,8 @@ /obj/structure/closet/stasis/dump_contents(kill = TRUE) STOP_PROCESSING(SSobj, src) for(var/mob/living/possessor in src) - REMOVE_TRAIT(possessor, TRAIT_MUTE, STASIS_MUTE) + possessor.remove_traits(list(TRAIT_UNDENSE, TRAIT_NO_TRANSFORM), STASIS_MUTE) possessor.status_flags &= ~GODMODE - possessor.notransform = FALSE if(kill || !isanimal_or_basicmob(loc)) possessor.investigate_log("has died from [src].", INVESTIGATE_DEATHS) possessor.death(FALSE) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm index 8596283166ef..e01821f8d3c3 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm @@ -198,7 +198,7 @@ Difficulty: Extremely Hard homing_turn_speed = 3 damage_type = BURN -/obj/projectile/colossus/frost_orb/on_hit(atom/target, blocked = FALSE) +/obj/projectile/colossus/frost_orb/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isturf(target) || isobj(target)) EX_ACT(target, EXPLODE_HEAVY) @@ -224,7 +224,7 @@ Difficulty: Extremely Hard range = 150 damage_type = BRUTE -/obj/projectile/colossus/ice_blast/on_hit(atom/target, blocked = FALSE) +/obj/projectile/colossus/ice_blast/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isturf(target) || isobj(target)) EX_ACT(target, EXPLODE_HEAVY) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm index 7d2f9226d5a3..c9c55baabd26 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm @@ -1,7 +1,3 @@ -///used whenever the drake generates a hotspot -#define DRAKE_FIRE_TEMP 500 -///used whenever the drake generates a hotspot -#define DRAKE_FIRE_EXPOSURE 50 ///used to see if the drake is enraged or not #define DRAKE_ENRAGED (health < maxHealth*0.5) @@ -73,7 +69,6 @@ death_message = "collapses into a pile of bones, its flesh sloughing away." death_sound = 'sound/magic/demon_dies.ogg' footstep_type = FOOTSTEP_MOB_HEAVY - small_sprite_type = /datum/action/small_sprite/megafauna/drake /// Fire cone ability var/datum/action/cooldown/mob_cooldown/fire_breath/cone/fire_cone /// Meteors ability @@ -176,36 +171,6 @@ remove_atom_colour(TEMPORARY_COLOUR_PRIORITY) set_light_range(initial(light_outer_range)) -//fire line keeps going even if dragon is deleted -/proc/dragon_fire_line(atom/source, list/turfs, frozen = FALSE) - var/list/hit_list = list() - for(var/turf/T in turfs) - if(isclosedturf(T)) - break - var/obj/effect/hotspot/drake_fire_hotspot = new /obj/effect/hotspot(T) - if(frozen) - drake_fire_hotspot.add_atom_colour(COLOR_BLUE_LIGHT, FIXED_COLOUR_PRIORITY) - T.hotspot_expose(DRAKE_FIRE_TEMP,DRAKE_FIRE_EXPOSURE,1) - for(var/mob/living/L in T.contents) - if(L in hit_list || istype(L, source.type)) - continue - hit_list += L - if(!frozen) - L.adjustFireLoss(20) - to_chat(L, span_userdanger("You're hit by [source]'s fire breath!")) - continue - L.adjustFireLoss(10) - L.apply_status_effect(/datum/status_effect/ice_block_talisman, 20) - to_chat(L, span_userdanger("You're hit by [source]'s freezing breath!")) - - // deals damage to mechs - for(var/obj/vehicle/sealed/mecha/M in T.contents) - if(M in hit_list) - continue - hit_list += M - M.take_damage(45, BRUTE, MELEE, 1) - sleep(0.15 SECONDS) - /mob/living/simple_animal/hostile/megafauna/dragon/ex_act(severity, target) if(severity <= EXPLODE_LIGHT) return FALSE @@ -372,8 +337,5 @@ return #undef DRAKE_ENRAGED -#undef DRAKE_FIRE_EXPOSURE -#undef DRAKE_FIRE_TEMP - #undef SWOOP_DAMAGEABLE #undef SWOOP_INVULNERABLE diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index b9be840b9ae9..3807ede11573 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -356,13 +356,13 @@ Difficulty: Hard animate(src, alpha = 0, time = 2, easing = EASE_OUT) //fade out SLEEP_CHECK_DEATH(1, src) visible_message(span_hierophant_warning("[src] fades out!")) - set_density(FALSE) + ADD_TRAIT(src, TRAIT_UNDENSE, VANISHING_TRAIT) SLEEP_CHECK_DEATH(2, src) forceMove(T) SLEEP_CHECK_DEATH(1, src) animate(src, alpha = 255, time = 2, easing = EASE_IN) //fade IN SLEEP_CHECK_DEATH(1, src) - set_density(TRUE) + REMOVE_TRAIT(src, TRAIT_UNDENSE, VANISHING_TRAIT) visible_message(span_hierophant_warning("[src] fades in!")) SLEEP_CHECK_DEATH(1, src) //at this point the blasts we made detonate blinking = FALSE @@ -441,7 +441,7 @@ Difficulty: Hard /mob/living/simple_animal/hostile/megafauna/hierophant/CanAttack(atom/the_target) . = ..() - if(istype(the_target, /mob/living/simple_animal/hostile/asteroid/hivelordbrood)) //ignore temporary targets in favor of more permanent targets + if(istype(the_target, /mob/living/basic/legion_brood)) //ignore temporary targets in favor of more permanent targets return FALSE /mob/living/simple_animal/hostile/megafauna/hierophant/GiveTarget(new_target) @@ -697,7 +697,7 @@ Difficulty: Hard return for(var/mob/living/L in T.contents - hit_things) //find and damage mobs... hit_things += L - if((friendly_fire_check && caster?.faction_check_mob(L)) || L.stat == DEAD) + if((friendly_fire_check && caster?.faction_check_atom(L)) || L.stat == DEAD) continue if(L.client) flash_color(L.client, "#660099", 1) @@ -722,7 +722,7 @@ Difficulty: Hard hit_things += M for(var/O in M.occupants) var/mob/living/occupant = O - if(friendly_fire_check && caster?.faction_check_mob(occupant)) + if(friendly_fire_check && caster?.faction_check_atom(occupant)) continue to_chat(occupant, span_userdanger("Your [M.name] is struck by a [name]!")) playsound(M,'sound/weapons/sear.ogg', 50, TRUE, -4) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm index 25d3ebf67fcf..346ac82ba06f 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm @@ -60,7 +60,6 @@ attack_action_types = list(/datum/action/innate/megafauna_attack/create_skull, /datum/action/innate/megafauna_attack/charge_target, /datum/action/innate/megafauna_attack/create_turrets) - small_sprite_type = /datum/action/small_sprite/megafauna/legion var/size = LEGION_LARGE var/charging = FALSE @@ -142,10 +141,9 @@ ///Attack proc. Spawns a singular legion skull. /mob/living/simple_animal/hostile/megafauna/legion/proc/create_legion_skull() - var/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/A = new(loc) - A.GiveTarget(target) - A.friends = friends - A.faction = faction + var/mob/living/basic/legion_brood/minion = new(loc) + minion.assign_creator(src) + minion.ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] = target //CHARGE @@ -211,7 +209,7 @@ var/mob/living/living_target = target switch(living_target.stat) if(UNCONSCIOUS, HARD_CRIT) - var/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/legion = new(loc) + var/mob/living/basic/legion_brood/legion = new(loc) legion.infest(living_target) @@ -271,14 +269,14 @@ density = TRUE layer = ABOVE_OBJ_LAYER armor_type = /datum/armor/structure_legionturret + //Compared with the targeted mobs. If they have the faction, turret won't shoot. + faction = list(FACTION_MINING) ///What kind of projectile the actual damaging part should be. var/projectile_type = /obj/projectile/beam/legion ///Time until the tracer gets shot var/initial_firing_time = 18 ///How long it takes between shooting the tracer and the projectile. var/shot_delay = 8 - ///Compared with the targeted mobs. If they have the faction, turret won't shoot. - var/faction = list(FACTION_MINING) /datum/armor/structure_legionturret laser = 100 @@ -326,7 +324,6 @@ hitsound = 'sound/magic/magic_missile.ogg' damage = 19 range = 6 - eyeblur = 0 light_color = COLOR_SOFT_RED impact_effect_type = /obj/effect/temp_visual/kinetic_blast tracer_type = /obj/effect/projectile/tracer/legion diff --git a/code/modules/mob/living/simple_animal/hostile/mimic.dm b/code/modules/mob/living/simple_animal/hostile/mimic.dm index 207ae7de7149..fcc5af943de2 100644 --- a/code/modules/mob/living/simple_animal/hostile/mimic.dm +++ b/code/modules/mob/living/simple_animal/hostile/mimic.dm @@ -336,14 +336,14 @@ GLOBAL_LIST_INIT(animatable_blacklist, list(/obj/structure/table, /obj/structure if(locked) return if(!opened) - set_density(FALSE) + ADD_TRAIT(src, TRAIT_UNDENSE, MIMIC_TRAIT) opened = TRUE icon_state = "crateopen" playsound(src, open_sound, 50, TRUE) for(var/atom/movable/AM in src) AM.forceMove(loc) else - set_density(TRUE) + REMOVE_TRAIT(src, TRAIT_UNDENSE, MIMIC_TRAIT) opened = FALSE icon_state = "crate" playsound(src, close_sound, 50, TRUE) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/brimdemon.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/brimdemon.dm deleted file mode 100644 index c55f9ffb76b5..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/brimdemon.dm +++ /dev/null @@ -1,242 +0,0 @@ -#define BRIMBEAM_RANGE 10 - -/mob/living/simple_animal/hostile/asteroid/brimdemon - name = "brimdemon" - desc = "A misshapen demon with big, red eyes and a hinged mouth. Not much is known about the creatures \ - due to their response to any unexpected stimulus being \"brimbeam\", a deadly blood-laser barrage." - icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' - icon_state = "brimdemon" - icon_living = "brimdemon" - icon_dead = "brimdemon_dead" - mob_biotypes = MOB_ORGANIC|MOB_BEAST - speak_emote = list("cackles") - emote_taunt = list("screeches") - emote_hear = list("cackles","screeches") - istate = ISTATE_HARM|ISTATE_BLOCKING - stat_attack = HARD_CRIT - ranged_cooldown_time = 5 SECONDS - vision_range = 6 - retreat_distance = 2 - speed = 3 - move_to_delay = 5 - maxHealth = 250 - health = 250 - obj_damage = 15 - melee_damage_lower = 7.5 - melee_damage_upper = 7.5 - rapid_melee = 2 // every second attack - attack_verb_continuous = "bites" - attack_verb_simple = "bite" - attack_sound = 'sound/weapons/bite.ogg' - attack_vis_effect = ATTACK_EFFECT_BITE - butcher_results = list( - /obj/item/food/meat/slab = 2, - /obj/effect/decal/cleanable/brimdust = 1, - /obj/item/organ/internal/monster_core/brimdust_sac = 1, - ) - loot = list() - robust_searching = TRUE - footstep_type = FOOTSTEP_MOB_CLAW - death_message = "wails as infernal energy escapes from its wounds, leaving it an empty husk." - death_sound = 'sound/magic/demon_dies.ogg' - light_color = LIGHT_COLOR_BLOOD_MAGIC - light_power = 5 - light_outer_range = 1.4 - crusher_loot = /obj/item/crusher_trophy/brimdemon_fang - /// Are we charging/firing? If yes stops our movement. - var/firing = FALSE - /// A list of all the beam parts. - var/list/beamparts = list() - -/mob/living/simple_animal/hostile/asteroid/brimdemon/Destroy() - QDEL_LIST(beamparts) - return ..() - -/mob/living/simple_animal/hostile/asteroid/brimdemon/Login() - ranged = TRUE - return ..() - -/mob/living/simple_animal/hostile/asteroid/brimdemon/Logout() - ranged = FALSE - return ..() - -/mob/living/simple_animal/hostile/asteroid/brimdemon/death() - firing = FALSE - cut_overlay("brimdemon_telegraph_dir") - move_resist = initial(move_resist) - return ..() - -/mob/living/simple_animal/hostile/asteroid/brimdemon/Goto(target, delay, minimum_distance) - if(firing) - return FALSE - return ..() - -/mob/living/simple_animal/hostile/asteroid/brimdemon/MoveToTarget(list/possible_targets) - if(firing) - return FALSE - return ..() - -/mob/living/simple_animal/hostile/asteroid/brimdemon/AttackingTarget(atom/attacked_target) - if(firing) - return FALSE - return ..() - -/mob/living/simple_animal/hostile/asteroid/brimdemon/Move(atom/newloc, dir, step_x , step_y) - if(firing) - return FALSE - return ..() - -/mob/living/simple_animal/hostile/asteroid/brimdemon/OpenFire() - if(firing) - balloon_alert(src, "already firing!") - return - if(!COOLDOWN_FINISHED(src, ranged_cooldown)) - balloon_alert(src, "on cooldown!") - return - firing = TRUE - set_dir_on_move = FALSE - icon_state = "brimdemon_firing" - move_resist = MOVE_FORCE_VERY_STRONG - add_overlay("brimdemon_telegraph_dir") - visible_message(span_danger("[src] starts charging!")) - balloon_alert(src, "charging...") - addtimer(CALLBACK(src, PROC_REF(fire_laser)), 1 SECONDS) - COOLDOWN_START(src, ranged_cooldown, ranged_cooldown_time) - -/mob/living/simple_animal/hostile/asteroid/brimdemon/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - . = ..() - check_fire() - -/mob/living/simple_animal/hostile/asteroid/brimdemon/proc/check_fire() - if(key || QDELETED(target) || get_dist(src, target) > BRIMBEAM_RANGE || !(get_dir(src, target) in GLOB.cardinals)) - return - face_atom(target) - OpenFire() - -/// Fires a brimbeam, getting a line of turfs between it and the direction to the target and creating a brimbeam effect on every one of them. -/mob/living/simple_animal/hostile/asteroid/brimdemon/proc/fire_laser() - if(stat == DEAD) - return - visible_message(span_danger("[src] fires a brimbeam!")) - balloon_alert(src, "brimbeam fired") - playsound(src, 'sound/creatures/brimdemon.ogg', 150, FALSE, 0, 3) - cut_overlay("brimdemon_telegraph_dir") - var/turf/target_turf = get_ranged_target_turf(src, dir, BRIMBEAM_RANGE) - var/turf/origin_turf = get_turf(src) - var/list/affected_turfs = get_line(origin_turf, target_turf) - origin_turf - for(var/turf/affected_turf in affected_turfs) - var/blocked = FALSE - if(affected_turf.opacity) - blocked = TRUE - for(var/obj/potential_block in affected_turf.contents) - if(potential_block.opacity) - blocked = TRUE - break - if(blocked) - break - var/atom/new_brimbeam = new /obj/effect/brimbeam(affected_turf) - new_brimbeam.dir = dir - beamparts += new_brimbeam - for(var/mob/living/hit_mob in affected_turf.contents) - hit_mob.adjustFireLoss(25) - to_chat(hit_mob, span_userdanger("You're hit by [src]'s brimbeam!")) - if(length(beamparts)) - var/atom/last_brimbeam = beamparts[length(beamparts)] - last_brimbeam.icon_state = "brimbeam_end" - var/atom/first_brimbeam = beamparts[1] - first_brimbeam.icon_state = "brimbeam_start" - addtimer(CALLBACK(src, PROC_REF(end_laser)), 2 SECONDS) - -/// Deletes all the brimbeam parts and sets variables back to their initial ones. -/mob/living/simple_animal/hostile/asteroid/brimdemon/proc/end_laser() - if(stat != DEAD) - icon_state = initial(icon_state) - move_resist = initial(move_resist) - set_dir_on_move = initial(set_dir_on_move) - firing = FALSE - for(var/obj/effect/brimbeam/beam in beamparts) - animate(beam, time = 0.5 SECONDS, alpha = 0) - QDEL_IN(beam, 0.5 SECONDS) - beamparts -= beam - -/obj/effect/brimbeam - name = "brimbeam" - icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' - icon_state = "brimbeam_mid" - layer = ABOVE_MOB_LAYER - plane = ABOVE_GAME_PLANE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - light_color = LIGHT_COLOR_BLOOD_MAGIC - light_power = 3 - light_outer_range = 2 - -/obj/effect/brimbeam/Initialize(mapload) - . = ..() - START_PROCESSING(SSfastprocess, src) - -/obj/effect/brimbeam/Destroy() - STOP_PROCESSING(SSfastprocess, src) - return ..() - -/obj/effect/brimbeam/process() - for(var/mob/living/hit_mob in get_turf(src)) - damage(hit_mob) - -/obj/effect/brimbeam/proc/damage(mob/living/hit_mob) - hit_mob.adjustFireLoss(5) - to_chat(hit_mob, span_danger("You're damaged by [src]!")) - -/obj/item/crusher_trophy/brimdemon_fang - name = "brimdemon's fang" - icon_state = "brimdemon_fang" - desc = "A fang from a brimdemon's corpse." - denied_type = /obj/item/crusher_trophy/brimdemon_fang - var/static/list/comic_phrases = list("BOOM", "BANG", "KABLOW", "KAPOW", "OUCH", "BAM", "KAPOW", "WHAM", "POW", "KABOOM") - -/obj/item/crusher_trophy/brimdemon_fang/effect_desc() - return "mark detonation creates visual and audiosensory effects on the target" - -/obj/item/crusher_trophy/brimdemon_fang/on_mark_detonation(mob/living/target, mob/living/user) - target.balloon_alert_to_viewers("[pick(comic_phrases)]!") - playsound(target, 'sound/lavaland/brimdemon_crush.ogg', 100) - -/obj/effect/decal/cleanable/brimdust - name = "brimdust" - desc = "Dust from a brimdemon. It is considered valuable for its' botanical abilities." - icon_state = "brimdust" - icon = 'icons/obj/mining.dmi' - layer = FLOOR_CLEAN_LAYER - mergeable_decal = FALSE - -/obj/effect/decal/cleanable/brimdust/Initialize(mapload) - . = ..() - reagents.add_reagent(/datum/reagent/brimdust, 15) - -/obj/item/ore_sensor - name = "ore sensor" - desc = "Using demonic frequencies, this ear-mounted tool detects ores in the nearby terrain." - icon_state = "oresensor" - icon = 'icons/obj/mining.dmi' - slot_flags = ITEM_SLOT_EARS - var/range = 5 - var/cooldown = 4 SECONDS //between the standard and the advanced ore scanner in strength - COOLDOWN_DECLARE(ore_sensing_cooldown) - -/obj/item/ore_sensor/equipped(mob/user, slot, initial) - . = ..() - if(slot & ITEM_SLOT_EARS) - START_PROCESSING(SSobj, src) - else - STOP_PROCESSING(SSobj, src) - -/obj/item/ore_sensor/dropped(mob/user, silent) - . = ..() - STOP_PROCESSING(SSobj, src) - -/obj/item/ore_sensor/process(seconds_per_tick) - if(!COOLDOWN_FINISHED(src, ore_sensing_cooldown)) - return - COOLDOWN_START(src, ore_sensing_cooldown, cooldown) - mineral_scan_pulse(get_turf(src), range) - -#undef BRIMBEAM_RANGE diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm index 77bd86b758f6..0055806b7d84 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm @@ -28,6 +28,7 @@ //Gives player-controlled variants the ability to swap attacks /mob/living/simple_animal/hostile/asteroid/elite/Initialize(mapload) . = ..() + AddComponent(/datum/component/seethrough_mob) for(var/action_type in attack_action_types) var/datum/action/innate/elite_attack/attack_action = new action_type() attack_action.Grant(src) @@ -36,7 +37,7 @@ /mob/living/simple_animal/hostile/asteroid/elite/AttackingTarget() if(ishostile(target)) var/mob/living/simple_animal/hostile/M = target - if(faction_check_mob(M)) + if(faction_check_atom(M)) return FALSE if(istype(target, /obj/structure/elite_tumor)) var/obj/structure/elite_tumor/T = target diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm index cb22ef43b849..73b57c4fe3b6 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm @@ -225,14 +225,13 @@ damage = 20 armour_penetration = 25 //was 50 changed because 50 was waay too much monkestation 20 edit speed = 2 - eyeblur = 0 damage_type = BRUTE pass_flags = PASSTABLE -/obj/projectile/herald/on_hit(atom/target, blocked = FALSE) +/obj/projectile/herald/on_hit(atom/target, blocked = 0, pierce_hit) if(ismob(target) && ismob(firer)) var/mob/living/mob_target = target - if(mob_target.faction_check_mob(firer)) + if(mob_target.faction_check_atom(firer)) damage = 0 . = ..() @@ -245,7 +244,7 @@ damage = 0 color = rgb(255,255,102) -/obj/projectile/herald/teleshot/on_hit(atom/target, blocked = FALSE) +/obj/projectile/herald/teleshot/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() firer.forceMove(get_turf(src)) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm index 613bcfd1f7ee..fa50663b9ca0 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm @@ -149,7 +149,7 @@ var/throwtarget = get_edge_target_turf(src, move_dir) for(var/mob/living/trample_target in T.contents - hit_things - src) hit_things += trample_target - if(faction_check_mob(trample_target)) + if(faction_check_atom(trample_target)) continue visible_message(span_boldwarning("[src] tramples and kicks [trample_target]!")) to_chat(trample_target, span_userdanger("[src] tramples you and kicks you away!")) @@ -328,10 +328,9 @@ /obj/item/crusher_trophy/legionnaire_spine/on_mark_detonation(mob/living/target, mob/living/user) if(!prob(bonus_value) || target.stat == DEAD) return - var/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/A = new /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion(user.loc) - A.GiveTarget(target) - A.friends += user - A.faction = user.faction.Copy() + var/mob/living/basic/legion_brood/minion = new (user.loc) + minion.assign_creator(user) + minion.ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] = target /obj/item/crusher_trophy/legionnaire_spine/attack_self(mob/user) if(!isliving(user)) @@ -342,9 +341,9 @@ to_chat(LivingUser, "You need to wait longer to use this again.") return LivingUser.visible_message(span_boldwarning("[LivingUser] shakes the [src] and summons a legion skull!")) - var/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/LegionSkull = new /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion(LivingUser.loc) - LegionSkull.friends += LivingUser - LegionSkull.faction = LivingUser.faction.Copy() + + var/mob/living/basic/legion_brood/minion = new (LivingUser.loc) + minion.assign_creator(LivingUser) next_use_time = world.time + 4 SECONDS #undef LEGIONNAIRE_CHARGE diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm index 0fae77d08ceb..b4f171a10ceb 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm @@ -147,13 +147,13 @@ new /obj/effect/temp_visual/hierophant/blast/damaging/pandora(t, src) animate(src, alpha = 0, time = 2, easing = EASE_OUT) //fade out visible_message(span_hierophant_warning("[src] fades out!")) - set_density(FALSE) + ADD_TRAIT(src, TRAIT_UNDENSE, VANISHING_TRAIT) addtimer(CALLBACK(src, PROC_REF(pandora_teleport_3), T), 2) /mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/pandora_teleport_3(turf/T) forceMove(T) animate(src, alpha = 255, time = 2, easing = EASE_IN) //fade IN - set_density(TRUE) + REMOVE_TRAIT(src, TRAIT_UNDENSE, VANISHING_TRAIT) visible_message(span_hierophant_warning("[src] fades in!")) /mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/aoe_squares(target) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm deleted file mode 100644 index 8858612f8cee..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm +++ /dev/null @@ -1,130 +0,0 @@ -//Gutlunches, passive mods that devour blood and gibs -/mob/living/simple_animal/hostile/asteroid/gutlunch - name = "gutlunch" - desc = "A scavenger that eats raw meat, often found alongside ash walkers. Produces a thick, nutritious milk." - icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' - icon_state = "gutlunch" - icon_living = "gutlunch" - icon_dead = "gutlunch" - mob_biotypes = MOB_ORGANIC|MOB_BEAST - speak_emote = list("warbles", "quavers") - emote_hear = list("trills.") - emote_see = list("sniffs.", "burps.") - weather_immunities = list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE) - faction = list(FACTION_MINING, FACTION_ASHWALKER) - density = FALSE - speak_chance = 1 - turns_per_move = 8 - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - move_to_delay = 15 - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "gently pushes aside" - response_disarm_simple = "gently push aside" - response_harm_continuous = "squishes" - response_harm_simple = "squish" - friendly_verb_continuous = "pinches" - friendly_verb_simple = "pinch" - istate = NONE - gold_core_spawnable = FRIENDLY_SPAWN - stat_attack = HARD_CRIT - gender = NEUTER - stop_automated_movement = FALSE - stop_automated_movement_when_pulled = TRUE - stat_exclusive = TRUE - robust_searching = TRUE - search_objects = 3 //Ancient simplemob AI shitcode. This makes them ignore all other mobs. - del_on_death = TRUE - loot = list(/obj/effect/decal/cleanable/blood/gibs) - death_message = "is pulped into bugmash." - - animal_species = /mob/living/simple_animal/hostile/asteroid/gutlunch - childtype = list(/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch = 100) - - wanted_objects = list(/obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/blood/gibs/, /obj/item/organ) - -/mob/living/simple_animal/hostile/asteroid/gutlunch/Initialize(mapload) - . = ..() - if(wanted_objects.len) - AddComponent(/datum/component/udder, /obj/item/udder/gutlunch, CALLBACK(src, PROC_REF(regenerate_icons)), CALLBACK(src, PROC_REF(regenerate_icons))) - ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) - -/mob/living/simple_animal/hostile/asteroid/gutlunch/CanAttack(atom/the_target) // Gutlunch-specific version of CanAttack to handle stupid stat_exclusive = true crap so we don't have to do it for literally every single simple_animal/hostile except the two that spawn in lavaland - if(!the_target || !isturf(the_target.loc)) // bail out on invalids - return FALSE - - if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it - return FALSE - - if(isliving(the_target)) - var/mob/living/L = the_target - - if(faction_check_mob(L) && !attack_same) - return FALSE - if(L.stat > stat_attack || L.stat != stat_attack && stat_exclusive) - return FALSE - - return TRUE - - if(isobj(the_target) && is_type_in_typecache(the_target, wanted_objects)) - return TRUE - - return FALSE - -/mob/living/simple_animal/hostile/asteroid/gutlunch/regenerate_icons(new_udder_volume, max_udder_volume) - cut_overlays() - var/static/gutlunch_full_overlay - if(isnull(gutlunch_full_overlay)) - gutlunch_full_overlay = iconstate2appearance(icon, "gl_full") - if(new_udder_volume == max_udder_volume) - add_overlay(gutlunch_full_overlay) - ..() - -//Male gutlunch. They're smaller and more colorful! -/mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck - name = "gubbuck" - gender = MALE - -/mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck/Initialize(mapload) - . = ..() - add_atom_colour(pick("#E39FBB", "#D97D64", "#CF8C4A"), FIXED_COLOUR_PRIORITY) - resize = 0.85 - update_transform() - -//Lady gutlunch. They make the babby. -/mob/living/simple_animal/hostile/asteroid/gutlunch/guthen - name = "guthen" - gender = FEMALE - -/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch - name = "grublunch" - wanted_objects = list() //They don't eat. - gold_core_spawnable = NO_SPAWN - var/growth = 0 - -//Baby gutlunch -/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch/Initialize(mapload) - . = ..() - add_atom_colour("#9E9E9E", FIXED_COLOUR_PRIORITY) //Somewhat hidden - resize = 0.45 - update_transform() - -/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch/Life(seconds_per_tick = SSMOBS_DT, times_fired) - ..() - growth++ - if(growth > 50) //originally used a timer for this but it was more of a problem than it was worth. - growUp() - -/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch/proc/growUp() - var/mob/living/L - if(prob(45)) - L = new /mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck(loc) - else - L = new /mob/living/simple_animal/hostile/asteroid/gutlunch/guthen(loc) - mind?.transfer_to(L) - L.faction = faction - L.setDir(dir) - L.Stun(20, ignore_canstun = TRUE) - visible_message(span_notice("[src] grows up into [L].")) - qdel(src) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm deleted file mode 100644 index 90cedd348418..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm +++ /dev/null @@ -1,320 +0,0 @@ -/mob/living/simple_animal/hostile/asteroid/hivelord - name = "hivelord" - desc = "A truly alien creature, it is a mass of unknown organic material, constantly fluctuating. When attacking, pieces of it split off and attack in tandem with the original." - icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' - icon_state = "Hivelord" - icon_living = "Hivelord" - icon_aggro = "Hivelord_alert" - icon_dead = "Hivelord_dead" - icon_gib = "syndicate_gib" - mob_biotypes = MOB_ORGANIC - move_to_delay = 14 - ranged = 1 - vision_range = 5 - aggro_vision_range = 9 - speed = 3 - maxHealth = 75 - health = 75 - harm_intent_damage = 5 - melee_damage_lower = 0 - melee_damage_upper = 0 - attack_verb_continuous = "lashes out at" - attack_verb_simple = "lash out at" - speak_emote = list("telepathically cries") - attack_sound = 'sound/weapons/pierce.ogg' - throw_message = "falls right through the strange body of the" - ranged_cooldown = 0 - ranged_cooldown_time = 20 - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - retreat_distance = 3 - minimum_distance = 3 - pass_flags = PASSTABLE - loot = list(/obj/item/organ/internal/monster_core/regenerative_core) - var/brood_type = /mob/living/simple_animal/hostile/asteroid/hivelordbrood - var/has_clickbox = TRUE - -/mob/living/simple_animal/hostile/asteroid/hivelord/Initialize(mapload) - . = ..() - if(has_clickbox) - AddComponent(/datum/component/clickbox, icon_state = "hivelord", max_scale = INFINITY, dead_state = "hivelord_dead") //they writhe so much. - -/mob/living/simple_animal/hostile/asteroid/hivelord/OpenFire(the_target) - if(world.time >= ranged_cooldown) - var/mob/living/simple_animal/hostile/asteroid/hivelordbrood/A = new brood_type(src.loc) - - A.flags_1 |= (flags_1 & ADMIN_SPAWNED_1) - A.GiveTarget(target) - A.friends = friends - A.faction = faction.Copy() - ranged_cooldown = world.time + ranged_cooldown_time - -/mob/living/simple_animal/hostile/asteroid/hivelord/AttackingTarget() - OpenFire() - return TRUE - -/mob/living/simple_animal/hostile/asteroid/hivelord/death(gibbed) - mouse_opacity = MOUSE_OPACITY_ICON - ..(gibbed) - -//A fragile but rapidly produced creature -/mob/living/simple_animal/hostile/asteroid/hivelordbrood - name = "hivelord brood" - desc = "A fragment of the original Hivelord, rallying behind its original. One isn't much of a threat, but..." - icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' - icon_state = "Hivelordbrood" - icon_living = "Hivelordbrood" - icon_aggro = "Hivelordbrood" - icon_dead = "Hivelordbrood" - icon_gib = "syndicate_gib" - move_to_delay = 1 - friendly_verb_continuous = "buzzes near" - friendly_verb_simple = "buzz near" - vision_range = 10 - speed = 3 - maxHealth = 1 - health = 1 - harm_intent_damage = 5 - melee_damage_lower = 2 - melee_damage_upper = 2 - attack_verb_continuous = "slashes" - attack_verb_simple = "slash" - speak_emote = list("telepathically cries") - attack_sound = 'sound/weapons/pierce.ogg' - attack_vis_effect = ATTACK_EFFECT_SLASH - throw_message = "falls right through the strange body of the" - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - pass_flags = PASSTABLE | PASSMOB - density = TRUE - del_on_death = 1 - var/clickbox_state = "hivelord" - var/clickbox_max_scale = INFINITY - -/mob/living/simple_animal/hostile/asteroid/hivelordbrood/Initialize(mapload) - . = ..() - addtimer(CALLBACK(src, PROC_REF(death)), 100) - AddElement(/datum/element/simple_flying) - AddComponent(/datum/component/swarming) - AddComponent(/datum/component/clickbox, icon_state = clickbox_state, max_scale = clickbox_max_scale) - -//Legion -/mob/living/simple_animal/hostile/asteroid/hivelord/legion - name = "legion" - desc = "You can still see what was once a human under the shifting mass of corruption." - icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' - icon_state = "legion" - icon_living = "legion" - icon_aggro = "legion" - icon_dead = "legion" - icon_gib = "syndicate_gib" - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - mouse_opacity = MOUSE_OPACITY_ICON - obj_damage = 60 - melee_damage_lower = 15 - melee_damage_upper = 15 - attack_verb_continuous = "lashes out at" - attack_verb_simple = "lash out at" - speak_emote = list("echoes") - attack_sound = 'sound/weapons/pierce.ogg' - throw_message = "bounces harmlessly off of" - crusher_loot = /obj/item/crusher_trophy/legion_skull - loot = list(/obj/item/organ/internal/monster_core/regenerative_core/legion) - brood_type = /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion - del_on_death = 1 - stat_attack = HARD_CRIT - robust_searching = 1 - has_clickbox = FALSE - var/dwarf_mob = FALSE - var/mob/living/carbon/human/stored_mob - -/mob/living/simple_animal/hostile/asteroid/hivelord/legion/random/Initialize(mapload) - . = ..() - if(prob(5)) - new /mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf(loc) - return INITIALIZE_HINT_QDEL - -/mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf - name = "dwarf legion" - desc = "You can still see what was once a rather small human under the shifting mass of corruption." - icon_state = "dwarf_legion" - icon_living = "dwarf_legion" - icon_aggro = "dwarf_legion" - icon_dead = "dwarf_legion" - maxHealth = 60 - health = 60 - speed = 2 //faster! - crusher_drop_mod = 20 - dwarf_mob = TRUE - -/mob/living/simple_animal/hostile/asteroid/hivelord/legion/death(gibbed) - visible_message(span_warning("The skulls on [src] wail in anger as they flee from their dying host!")) - var/turf/T = get_turf(src) - if(T) - if(stored_mob) - stored_mob.forceMove(get_turf(src)) - stored_mob = null - else if(fromtendril) - new /obj/effect/mob_spawn/corpse/human/charredskeleton(T) - else if(dwarf_mob) - new /obj/effect/mob_spawn/corpse/human/legioninfested/dwarf(T) - else - new /obj/effect/mob_spawn/corpse/human/legioninfested(T) - ..(gibbed) - -/mob/living/simple_animal/hostile/asteroid/hivelord/legion/tendril - fromtendril = TRUE - -//Legion skull -/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion - name = "legion" - desc = "One of many." - icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' - icon_state = "legion_head" - icon_living = "legion_head" - icon_aggro = "legion_head" - icon_dead = "legion_head" - icon_gib = "syndicate_gib" - friendly_verb_continuous = "buzzes near" - friendly_verb_simple = "buzz near" - vision_range = 10 - maxHealth = 1 - health = 5 - harm_intent_damage = 5 - melee_damage_lower = 12 - melee_damage_upper = 12 - attack_verb_continuous = "bites" - attack_verb_simple = "bite" - attack_vis_effect = ATTACK_EFFECT_BITE - speak_emote = list("echoes") - attack_sound = 'sound/weapons/pierce.ogg' - throw_message = "is shrugged off by" - del_on_death = TRUE - stat_attack = HARD_CRIT - robust_searching = 1 - clickbox_state = "sphere" - clickbox_max_scale = 2 - var/can_infest_dead = FALSE - -/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/Life(seconds_per_tick = SSMOBS_DT, times_fired) - . = ..() - if(stat == DEAD || !isturf(loc)) - return - for(var/mob/living/carbon/human/victim in range(src, 1)) //Only for corpse right next to/on same tile - switch(victim.stat) - if(UNCONSCIOUS, HARD_CRIT) - infest(victim) - return //This will qdelete the legion. - if(DEAD) - if(can_infest_dead) - infest(victim) - return //This will qdelete the legion. - -///Create a legion at the location of a corpse. Exists so that legion subtypes can override it with their own type of legion. -/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/proc/make_legion(mob/living/carbon/human/H) - if(HAS_TRAIT(H, TRAIT_DWARF)) //dwarf legions aren't just fluff! - return new /mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf(H.loc) - else - return new /mob/living/simple_animal/hostile/asteroid/hivelord/legion(H.loc) - -///Create a new legion using the supplied human H -/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/proc/infest(mob/living/carbon/human/H) - visible_message(span_warning("[name] burrows into the flesh of [H]!")) - var/mob/living/simple_animal/hostile/asteroid/hivelord/legion/L = make_legion(H) - visible_message(span_warning("[L] staggers to [L.p_their()] feet!")) - H.investigate_log("has been killed by hivelord infestation.", INVESTIGATE_DEATHS) - H.death() - H.adjustBruteLoss(1000) - L.stored_mob = H - H.forceMove(L) - qdel(src) - -//Advanced Legion is slightly tougher to kill and can raise corpses (revive other legions) -/mob/living/simple_animal/hostile/asteroid/hivelord/legion/advanced - stat_attack = DEAD - maxHealth = 120 - health = 120 - brood_type = /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/advanced - icon_state = "dwarf_legion" - icon_living = "dwarf_legion" - icon_aggro = "dwarf_legion" - icon_dead = "dwarf_legion" - -/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/advanced - stat_attack = DEAD - can_infest_dead = TRUE - -//Legion that spawns Legions -/mob/living/simple_animal/hostile/big_legion - name = "legion" - desc = "One of many." - icon = 'icons/mob/simple/lavaland/64x64megafauna.dmi' - icon_state = "legion" - icon_living = "legion" - icon_dead = "legion" - health_doll_icon = "legion" - health = 450 - maxHealth = 450 - melee_damage_lower = 20 - melee_damage_upper = 20 - anchored = FALSE - AIStatus = AI_ON - stop_automated_movement = FALSE - wander = TRUE - maxbodytemp = INFINITY - layer = MOB_LAYER - del_on_death = TRUE - sentience_type = SENTIENCE_BOSS - loot = list(/obj/item/organ/internal/monster_core/regenerative_core/legion = 3, /obj/effect/mob_spawn/corpse/human/legioninfested = 5) - move_to_delay = 14 - vision_range = 5 - aggro_vision_range = 9 - speed = 3 - faction = list(FACTION_MINING) - weather_immunities = list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE) - obj_damage = 30 - environment_smash = ENVIRONMENT_SMASH_STRUCTURES - // Purple, but bright cause we're gonna need to spot mobs on lavaland - lighting_cutoff_red = 35 - lighting_cutoff_green = 20 - lighting_cutoff_blue = 45 - - -/mob/living/simple_animal/hostile/big_legion/Initialize(mapload) - .=..() - AddComponent(\ - /datum/component/spawner,\ - spawn_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord/legion),\ - spawn_time = 20 SECONDS,\ - max_spawned = 3,\ - spawn_text = "peels itself off from",\ - faction = faction,\ - ) - -// Snow Legion -/mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow - name = "snow legion" - desc = "You can still see what was once a human under the shifting snowy mass, clearly decorated by a clown." - icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' - icon_state = "snowlegion" - icon_living = "snowlegion" - icon_aggro = "snowlegion_alive" - icon_dead = "snowlegion" - crusher_loot = /obj/item/crusher_trophy/legion_skull - loot = list(/obj/item/organ/internal/monster_core/regenerative_core/legion) - brood_type = /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/snow - weather_immunities = list(TRAIT_SNOWSTORM_IMMUNE) - -/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/snow/make_legion(mob/living/carbon/human/H) - return new /mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow(H.loc) - -// Snow Legion skull -/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/snow - name = "snow legion" - desc = "One of many." - icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' - icon_state = "snowlegion_head" - icon_living = "snowlegion_head" - icon_aggro = "snowlegion_head" - icon_dead = "snowlegion_head" - weather_immunities = list(TRAIT_SNOWSTORM_IMMUNE) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm deleted file mode 100644 index 9d84fe2e1fe8..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm +++ /dev/null @@ -1,85 +0,0 @@ -/mob/living/simple_animal/hostile/asteroid/ice_demon - name = "demonic watcher" - desc = "A creature formed entirely out of ice, bluespace energy emanates from inside of it." - icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' - icon_state = "ice_demon" - icon_living = "ice_demon" - icon_dead = "ice_demon_dead" - icon_gib = "syndicate_gib" - mob_biotypes = MOB_ORGANIC|MOB_BEAST - mouse_opacity = MOUSE_OPACITY_ICON - speak_emote = list("telepathically cries") - speed = 2 - move_to_delay = 2 - projectiletype = /obj/projectile/temp/ice_demon - projectilesound = 'sound/weapons/pierce.ogg' - ranged = TRUE - ranged_message = "manifests ice" - ranged_cooldown_time = 1.5 SECONDS - minimum_distance = 3 - retreat_distance = 3 - maxHealth = 150 - health = 150 - obj_damage = 40 - melee_damage_lower = 15 - melee_damage_upper = 15 - attack_verb_continuous = "slices" - attack_verb_simple = "slice" - attack_sound = 'sound/weapons/bladeslice.ogg' - attack_vis_effect = ATTACK_EFFECT_SLASH - vision_range = 9 - aggro_vision_range = 9 - move_force = MOVE_FORCE_VERY_STRONG - move_resist = MOVE_FORCE_VERY_STRONG - pull_force = MOVE_FORCE_VERY_STRONG - del_on_death = TRUE - loot = list() - crusher_loot = /obj/item/crusher_trophy/watcher_wing/ice_wing - death_message = "fades as the energies that tied it to this world dissipate." - death_sound = 'sound/magic/demon_dies.ogg' - stat_attack = HARD_CRIT - robust_searching = TRUE - footstep_type = FOOTSTEP_MOB_CLAW - /// Distance the demon will teleport from the target - var/teleport_distance = 3 - -/mob/living/simple_animal/hostile/asteroid/ice_demon/Initialize(mapload) - . = ..() - AddElement(/datum/element/simple_flying) - -/obj/projectile/temp/ice_demon - name = "ice blast" - icon_state = "ice_2" - damage = 5 - damage_type = BURN - armor_flag = ENERGY - speed = 1 - pixel_speed_multiplier = 0.25 - range = 200 - temperature = -75 - -/mob/living/simple_animal/hostile/asteroid/ice_demon/OpenFire() - ranged_cooldown = world.time + ranged_cooldown_time - // Sentient ice demons teleporting has been linked to server crashes - if(client) - return ..() - if(teleport_distance <= 0) - return ..() - var/list/possible_ends = view(teleport_distance, target.loc) - view(teleport_distance - 1, target.loc) - for(var/turf/closed/turf_to_remove in possible_ends) - possible_ends -= turf_to_remove - if(!possible_ends.len) - return ..() - var/turf/end = pick(possible_ends) - do_teleport(src, end, 0, channel=TELEPORT_CHANNEL_BLUESPACE, forced = TRUE) - SLEEP_CHECK_DEATH(8, src) - return ..() - -/mob/living/simple_animal/hostile/asteroid/ice_demon/death(gibbed) - move_force = MOVE_FORCE_DEFAULT - move_resist = MOVE_RESIST_DEFAULT - pull_force = PULL_FORCE_DEFAULT - new /obj/item/stack/ore/bluespace_crystal(loc, 3) - if(prob(5)) - new /obj/item/raw_anomaly_core/bluespace(loc) - return ..() diff --git a/code/modules/mob/living/simple_animal/hostile/nanotrasen.dm b/code/modules/mob/living/simple_animal/hostile/nanotrasen.dm deleted file mode 100644 index 2b81c6612cb9..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/nanotrasen.dm +++ /dev/null @@ -1,111 +0,0 @@ -/mob/living/simple_animal/hostile/nanotrasen - name = "\improper Nanotrasen Private Security Officer" - desc = "An officer part of Nanotrasen's private security force, he seems rather unpleased to meet you." - icon = 'icons/mob/simple/simple_human.dmi' - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - sentience_type = SENTIENCE_HUMANOID - speak_chance = 0 - turns_per_move = 5 - speed = 0 - stat_attack = HARD_CRIT - robust_searching = 1 - maxHealth = 100 - health = 100 - harm_intent_damage = 5 - melee_damage_lower = 10 - melee_damage_upper = 15 - attack_verb_continuous = "punches" - attack_verb_simple = "punch" - attack_sound = 'sound/weapons/punch1.ogg' - istate = ISTATE_HARM|ISTATE_BLOCKING - loot = list(/obj/effect/mob_spawn/corpse/human/nanotrasensoldier) - atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 7.5 - faction = list(ROLE_DEATHSQUAD) - check_friendly_fire = TRUE - status_flags = CANPUSH - del_on_death = TRUE - dodging = TRUE - footstep_type = FOOTSTEP_MOB_SHOE - /// Path of the mob spawner we base the mob's visuals off of. - var/mob_spawner = /obj/effect/mob_spawn/corpse/human/nanotrasensoldier - /// Path of the held item we give to the mob's visuals. - var/held_item - -/mob/living/simple_animal/hostile/nanotrasen/Initialize(mapload) - . = ..() - apply_dynamic_human_appearance(src, mob_spawn_path = mob_spawner, r_hand = held_item) - -/mob/living/simple_animal/hostile/nanotrasen/screaming/Aggro() - ..() - summon_backup(15) - say("411 in progress, requesting backup!") - -/mob/living/simple_animal/hostile/nanotrasen/ranged - icon_state = "nanotrasenranged" - icon_living = "nanotrasenranged" - ranged = 1 - retreat_distance = 3 - minimum_distance = 5 - casingtype = /obj/item/ammo_casing/c45 - projectilesound = 'sound/weapons/gun/pistol/shot_alt.ogg' - held_item = /obj/item/gun/ballistic/automatic/pistol/m1911 - -/mob/living/simple_animal/hostile/nanotrasen/ranged/smg - icon_state = "nanotrasenrangedsmg" - icon_living = "nanotrasenrangedsmg" - rapid = 3 - casingtype = /obj/item/ammo_casing/c46x30mm - projectilesound = 'sound/weapons/gun/smg/shot.ogg' - held_item = /obj/item/gun/ballistic/automatic/wt550 - - -/mob/living/simple_animal/hostile/retaliate/nanotrasenpeace - name = "\improper Nanotrasen Private Security Officer" - desc = "An officer part of Nanotrasen's private security force." - icon = 'icons/mob/simple/simple_human.dmi' - turns_per_move = 5 - speed = 0 - stat_attack = HARD_CRIT - robust_searching = 1 - vision_range = 3 - maxHealth = 100 - health = 100 - harm_intent_damage = 5 - melee_damage_lower = 10 - melee_damage_upper = 15 - attack_verb_continuous = "punches" - attack_verb_simple = "punch" - attack_sound = 'sound/weapons/punch1.ogg' - faction = list(FACTION_NANOTRASEN_PRIVATE) - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - sentience_type = SENTIENCE_HUMANOID - istate = ISTATE_HARM|ISTATE_BLOCKING - loot = list(/obj/effect/mob_spawn/corpse/human/nanotrasensoldier) - atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 7.5 - status_flags = CANPUSH - search_objects = 1 - /// Path of the held item we give to the mob's visuals. - var/held_item - -/mob/living/simple_animal/hostile/retaliate/nanotrasenpeace/Initialize(mapload) - . = ..() - apply_dynamic_human_appearance(src, mob_spawn_path = /obj/effect/mob_spawn/corpse/human/nanotrasensoldier, r_hand = held_item) - -/mob/living/simple_animal/hostile/retaliate/nanotrasenpeace/Aggro() - ..() - summon_backup(15) - say("411 in progress, requesting backup!") - -/mob/living/simple_animal/hostile/retaliate/nanotrasenpeace/ranged - vision_range = 9 - rapid = 3 - ranged = 1 - retreat_distance = 3 - minimum_distance = 5 - casingtype = /obj/item/ammo_casing/c46x30mm - projectilesound = 'sound/weapons/gun/smg/shot.ogg' - loot = list(/obj/item/gun/ballistic/automatic/wt550, - /obj/effect/mob_spawn/corpse/human/nanotrasensoldier) - held_item = /obj/item/gun/ballistic/automatic/wt550 diff --git a/code/modules/mob/living/simple_animal/hostile/ooze.dm b/code/modules/mob/living/simple_animal/hostile/ooze.dm index 3bcbf3b7d85d..695dcc9e4c4e 100644 --- a/code/modules/mob/living/simple_animal/hostile/ooze.dm +++ b/code/modules/mob/living/simple_animal/hostile/ooze.dm @@ -295,7 +295,7 @@ /mob/living/simple_animal/hostile/ooze/grapes/add_cell_sample() AddElement(/datum/element/swabable, CELL_LINE_TABLE_GRAPE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) -///Ability that allows the owner to fire healing globules at mobs, targetting specific limbs. +///Ability that allows the owner to fire healing globules at mobs, targeting specific limbs. /datum/action/cooldown/globules name = "Fire Mending globule" desc = "Fires a mending globule at someone, healing a specific limb of theirs." diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm index 237360468f8d..bf1c12d5da1e 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm @@ -37,7 +37,7 @@ continue if(isliving(A)) var/mob/living/M = A - if(faction_check_mob(M) && attack_same || !faction_check_mob(M)) + if(faction_check_atom(M) && attack_same || !faction_check_atom(M)) enemies |= WEAKREF(M) else if(ismecha(A)) var/obj/vehicle/sealed/mecha/M = A @@ -46,7 +46,7 @@ add_enemies(M.occupants) for(var/mob/living/simple_animal/hostile/retaliate/H in around) - if(faction_check_mob(H) && !attack_same && !H.attack_same) + if(faction_check_atom(H) && !attack_same && !H.attack_same) H.enemies |= enemies /mob/living/simple_animal/hostile/retaliate/adjustHealth(amount, updating_health = TRUE, forced = FALSE) diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/snake.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/snake.dm deleted file mode 100644 index e128349bc2e8..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/snake.dm +++ /dev/null @@ -1,76 +0,0 @@ - -/mob/living/simple_animal/hostile/retaliate/snake - name = "snake" - desc = "A slithery snake. These legless reptiles are the bane of mice and adventurers alike." - icon_state = "snake" - icon_living = "snake" - icon_dead = "snake_dead" - speak_emote = list("hisses") - health = 20 - maxHealth = 20 - attack_verb_continuous = "bites" - attack_verb_simple = "bite" - attack_sound = 'sound/weapons/bite.ogg' - attack_vis_effect = ATTACK_EFFECT_BITE - melee_damage_lower = 5 - melee_damage_upper = 6 - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "shoos" - response_disarm_simple = "shoo" - response_harm_continuous = "steps on" - response_harm_simple = "step on" - faction = list(FACTION_HOSTILE) - density = FALSE - pass_flags = PASSTABLE | PASSMOB - mob_size = MOB_SIZE_SMALL - mob_biotypes = MOB_ORGANIC|MOB_BEAST|MOB_REPTILE - gold_core_spawnable = FRIENDLY_SPAWN - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - -/mob/living/simple_animal/hostile/retaliate/snake/Initialize(mapload, special_reagent) - . = ..() - add_cell_sample() - ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) - if(!special_reagent) - special_reagent = /datum/reagent/toxin - AddElement(/datum/element/venomous, special_reagent, 4) - -/mob/living/simple_animal/hostile/retaliate/snake/add_cell_sample() - AddElement(/datum/element/swabable, CELL_LINE_TABLE_SNAKE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) - -/mob/living/simple_animal/hostile/retaliate/snake/ListTargets(atom/the_target) - var/atom/target_from = GET_TARGETS_FROM(src) - . = oview(vision_range, target_from) //get list of things in vision range - var/list/living_mobs = list() - var/list/mice = list() - for (var/HM in .) - //Yum a tasty mouse - if(ismouse(HM)) - mice += HM - if(isliving(HM)) - living_mobs += HM - - // if no tasty mice to chase, lets chase any living mob enemies in our vision range - if(length(mice)) - return mice - - var/list/actual_enemies = list() - for(var/datum/weakref/enemy as anything in enemies) - var/mob/flesh_and_blood = enemy.resolve() - if(!flesh_and_blood) - enemies -= enemy - continue - actual_enemies += flesh_and_blood - - //Filter living mobs (in range mobs) by those we consider enemies (retaliate behaviour) - return living_mobs & actual_enemies - -/mob/living/simple_animal/hostile/retaliate/snake/AttackingTarget() - if(ismouse(target)) - visible_message(span_notice("[name] consumes [target] in a single gulp!"), span_notice("You consume [target] in a single gulp!")) - QDEL_NULL(target) - adjustBruteLoss(-2) - else - return ..() diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/trader.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/trader.dm deleted file mode 100644 index e637638e7ebe..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/trader.dm +++ /dev/null @@ -1,501 +0,0 @@ -#define ITEM_REJECTED_PHRASE "ITEM_REJECTED_PHRASE" -#define ITEM_SELLING_CANCELED_PHRASE "ITEM_SELLING_CANCELED_PHRASE" -#define ITEM_SELLING_ACCEPTED_PHRASE "ITEM_SELLING_ACCEPTED_PHRASE" -#define INTERESTED_PHRASE "INTERESTED_PHRASE" -#define BUY_PHRASE "BUY_PHRASE" -#define NO_CASH_PHRASE "NO_CASH_PHRASE" -#define NO_STOCK_PHRASE "NO_STOCK_PHRASE" -#define NOT_WILLING_TO_BUY_PHRASE "NOT_WILLING_TO_BUY_PHRASE" -#define ITEM_IS_WORTHLESS_PHRASE "ITEM_IS_WORTHLESS_PHRASE" -#define TRADER_HAS_ENOUGH_ITEM_PHRASE "TRADER_HAS_ENOUGH_ITEM_PHRASE" -#define TRADER_LORE_PHRASE "TRADER_LORE_PHRASE" -#define TRADER_NOT_BUYING_ANYTHING "TRADER_NOT_BUYING_ANYTHING" -#define TRADER_NOT_SELLING_ANYTHING "TRADER_NOT_SELLING_ANYTHING" - -#define TRADER_PRODUCT_INFO_PRICE 1 -#define TRADER_PRODUCT_INFO_QUANTITY 2 -//Only valid for wanted_items -#define TRADER_PRODUCT_INFO_PRICE_MOD_DESCRIPTION 3 - -/** - * # Trader - * - * A mob that has some dialogue options with radials, allows for selling items and buying em' - * - */ -/mob/living/simple_animal/hostile/retaliate/trader - name = "Trader" - desc = "Come buy some!" - icon = 'icons/mob/simple/traders.dmi' - icon_state = "faceless" - maxHealth = 200 - health = 200 - melee_damage_lower = 10 - melee_damage_upper = 10 - attack_verb_continuous = "punches" - attack_verb_simple = "punch" - attack_sound = 'sound/weapons/punch1.ogg' - del_on_death = TRUE - loot = list(/obj/effect/mob_spawn/corpse/human) - atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 2.5 - casingtype = /obj/item/ammo_casing/shotgun/buckshot - wander = FALSE - ranged = TRUE - istate = ISTATE_HARM|ISTATE_BLOCKING - move_resist = MOVE_FORCE_STRONG - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - sentience_type = SENTIENCE_HUMANOID - speed = 0 - stat_attack = HARD_CRIT - robust_searching = TRUE - check_friendly_fire = TRUE - interaction_flags_atom = INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND|INTERACT_ATOM_ATTACK_HAND|INTERACT_ATOM_NO_FINGERPRINT_INTERACT - ///Sound used when item sold/bought - var/sell_sound = 'sound/effects/cashregister.ogg' - /** - * Format; list(TYPEPATH = list(PRICE, QUANTITY)) - * Associated list of items the NPC sells with how much they cost and the quantity available before a restock - * This list is filled by Initialize(), if you want to change the starting products, modify initial_products() - * * - */ - var/list/products - /** - * A list of wanted items that the trader would wish to buy, each typepath has a assigned value, quantity and additional flavor text - * - * CHILDREN OF TYPEPATHS INCLUDED IN WANTED_ITEMS WILL BE TREATED AS THE PARENT IF NO ENTRY EXISTS FOR THE CHILDREN - * - * As an additional note; if you include multiple children of a typepath; the typepath with the most children should be placed after all other typepaths - * Bad; list(/obj/item/milk = list(100, 1, ""), /obj/item/milk/small = list(50, 2, "")) - * Good; list(/obj/item/milk/small = list(50, 2, ""), /obj/item/milk = list(100, 1, "")) - * This is mainly because sell_item() uses a istype(item_being_sold, item_in_entry) to determine what parent should the child be automatically considered as - * If /obj/item/milk/small/spooky was being sold; /obj/item/milk/small would be the first to check against rather than /obj/item/milk - * - * Format; list(TYPEPATH = list(PRICE, QUANTITY, ADDITIONAL_DESCRIPTION)) - * Associated list of items able to be sold to the NPC with the money given for them. - * The price given should be the "base" price; any price manipulation based on variables should be done with apply_sell_price_mods() - * ADDITIONAL_DESCRIPTION is any additional text added to explain how the variables of the item effect the price; if it's stack based, it's final price depends how much is in the stack - * EX; /obj/item/stack/sheet/mineral/diamond = list(500, INFINITY, ", per 2000 cm3 sheet of diamond") - * This list is filled by Initialize(), if you want to change the starting wanted items, modify initial_wanteds() - */ - var/list/wanted_items - ///Associated list of defines matched with list of phrases; phrase to be said is dealt by return_trader_phrase() - var/list/say_phrases = list( - ITEM_REJECTED_PHRASE = list( - "Sorry, I'm not a fan of anything you're showing me. Give me something better and we'll talk." - ), - ITEM_SELLING_CANCELED_PHRASE = list( - "What a shame, tell me if you changed your mind." - ), - ITEM_SELLING_ACCEPTED_PHRASE = list( - "Pleasure doing business with you." - ), - INTERESTED_PHRASE = list( - "Hey, you've got an item that interests me, I'd like to buy it, I'll give you some cash for it, deal?" - ), - BUY_PHRASE = list( - "Pleasure doing business with you." - ), - NO_CASH_PHRASE = list( - "Sorry adventurer, I can't give credit! Come back when you're a little mmmmm... richer!" - ), - NO_STOCK_PHRASE = list( - "Sorry adventurer, but that item is not in stock at the moment." - ), - NOT_WILLING_TO_BUY_PHRASE = list( - "I don't want to buy that item for the time being, check back another time." - ), - ITEM_IS_WORTHLESS_PHRASE = list( - "This item seems to be worthless on a closer look, I won't buy this." - ), - TRADER_HAS_ENOUGH_ITEM_PHRASE = list( - "I already bought enough of this for the time being." - ), - TRADER_LORE_PHRASE = list( - "Hello! I am the test trader.", - "Oooooooo~!" - ), - TRADER_NOT_BUYING_ANYTHING = list( - "I'm currently buying nothing at the moment." - ), - TRADER_NOT_SELLING_ANYTHING = list( - "I'm currently selling nothing at the moment." - ), - ) - ///The name of the currency that is used when buying or selling items - var/currency_name = "credits" - -///Initializes the products and item demands of the trader -/mob/living/simple_animal/hostile/retaliate/trader/Initialize(mapload) - . = ..() - restock_products() - renew_item_demands() - -///Returns a list of the starting price/quanity/fluff text about the product listings; products = initial(products) doesn't work so this exists mainly for restock_products() -/mob/living/simple_animal/hostile/retaliate/trader/proc/initial_products() - return list(/obj/item/food/burger/ghost = list(200, INFINITY), - ) - -///Returns a list of the starting price/quanity/fluff text about the wanted items; wanted_items = initial(wanted_items) doesn't work so this exists mainly for renew_item_demands() -/mob/living/simple_animal/hostile/retaliate/trader/proc/initial_wanteds() - return list(/obj/item/ectoplasm = list(100, INFINITY, ""), - ) - -/** - * Depending on the passed parameter/override, returns a randomly picked string out of a list - * - * Do note when overriding this argument, you will need to ensure pick(the list) doesn't get supplied with a list of zero length - * Arguments: - * * say_text - (String) a define that matches the key of a entry in say_phrases - */ -/mob/living/simple_animal/hostile/retaliate/trader/proc/return_trader_phrase(say_text) - if(!length(say_phrases[say_text])) - return - return pick(say_phrases[say_text]) - //return (length(say_phrases[say_text]) ? pick(say_phrases[say_text]) : "") - -///Sets up the radials for the user and calls procs related to the actions the user wants to take -/mob/living/simple_animal/hostile/retaliate/trader/interact(mob/user) - if(user == target) - return FALSE - var/list/npc_options = list() - if(products.len) - npc_options["Buy"] = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_buy") - if(length(say_phrases[TRADER_LORE_PHRASE])) - npc_options["Talk"] = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_talk") - if(wanted_items.len) - npc_options["Sell"] = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_sell") - if(!npc_options.len) - return FALSE - var/npc_result = show_radial_menu(user, src, npc_options, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE) - switch(npc_result) - if("Buy") - buy_item(user) - if("Sell") - try_sell(user) - if("Talk") - discuss(user) - face_atom(user) - return TRUE - -/** - * Checks if the user is ok to use the radial - * - * Checks if the user is not a mob or is incapacitated or not adjacent to the source of the radial, in those cases returns FALSE, otherwise returns TRUE - * Arguments: - * * user - (Mob REF) The mob checking the menu - */ -/mob/living/simple_animal/hostile/retaliate/trader/proc/check_menu(mob/user) - if(!istype(user)) - return FALSE - if(user.incapacitated() || !user.Adjacent(src)) - return FALSE - return TRUE - -///Talk about what items are being sold/wanted by the trader and in what quantity or lore -/mob/living/simple_animal/hostile/retaliate/trader/proc/discuss(mob/user) - var/list/npc_options = list( - "Lore" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_lore"), - "Selling?" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_selling"), - "Buying?" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_buying"), - ) - var/pick = show_radial_menu(user, src, npc_options, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE) - switch(pick) - if("Lore") - say(return_trader_phrase(TRADER_LORE_PHRASE)) - if("Buying?") - trader_buys_what(user) - if("Selling?") - trader_sells_what(user) - -///Displays to the user what the trader is willing to buy and how much until a restock happens -/mob/living/simple_animal/hostile/retaliate/trader/proc/trader_buys_what(mob/user) - if(!wanted_items.len) - say(return_trader_phrase(TRADER_NOT_BUYING_ANYTHING)) - return - var/list/product_info - to_chat(user, span_green("I'm willing to buy the following; ")) - for(var/obj/item/thing as anything in wanted_items) - product_info = wanted_items[thing] - var/tern_op_result = (product_info[TRADER_PRODUCT_INFO_QUANTITY] == INFINITY ? "as many as I can." : "[product_info[TRADER_PRODUCT_INFO_QUANTITY]]") //Coder friendly string concat - if(product_info[TRADER_PRODUCT_INFO_QUANTITY] <= 0) //Zero demand - to_chat(user, span_notice("[span_red("(DOESN'T WANT MORE)")] [initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [currency_name][product_info[TRADER_PRODUCT_INFO_PRICE_MOD_DESCRIPTION]]; willing to buy [span_red("[tern_op_result]")] more.")) - else - to_chat(user, span_notice("[initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [currency_name][product_info[TRADER_PRODUCT_INFO_PRICE_MOD_DESCRIPTION]]; willing to buy [span_green("[tern_op_result]")]")) - -///Displays to the user what the trader is selling and how much is in stock -/mob/living/simple_animal/hostile/retaliate/trader/proc/trader_sells_what(mob/user) - if(!products.len) - say(return_trader_phrase(TRADER_NOT_SELLING_ANYTHING)) - return - var/list/product_info - to_chat(user, span_green("I'm currently selling the following; ")) - for(var/obj/item/thing as anything in products) - product_info = products[thing] - var/tern_op_result = (product_info[TRADER_PRODUCT_INFO_QUANTITY] == INFINITY ? "an infinite amount" : "[product_info[TRADER_PRODUCT_INFO_QUANTITY]]") //Coder friendly string concat - if(product_info[TRADER_PRODUCT_INFO_QUANTITY] <= 0) //Out of stock - to_chat(user, span_notice("[span_red("(OUT OF STOCK)")] [initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [currency_name]; [span_red("[tern_op_result]")] left in stock")) - else - to_chat(user, span_notice("[initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [currency_name]; [span_green("[tern_op_result]")] left in stock")) - -/** - * Generates a radial of the items the NPC sells and lets the user try to buy one - * Arguments: - * * user - (Mob REF) The mob trying to buy something - */ -/mob/living/simple_animal/hostile/retaliate/trader/proc/buy_item(mob/user) - if(!LAZYLEN(products)) - return - - var/list/display_names = list() - var/list/items = list() - var/list/product_info - for(var/obj/item/thing as anything in products) - display_names["[initial(thing.name)]"] = thing - var/image/item_image = image(icon = initial(thing.icon), icon_state = initial(thing.icon_state)) - product_info = products[thing] - if(product_info[TRADER_PRODUCT_INFO_QUANTITY] <= 0) //out of stock - item_image.overlays += image(icon = 'icons/hud/radial.dmi', icon_state = "radial_center") - items += list("[initial(thing.name)]" = item_image) - var/pick = show_radial_menu(user, src, items, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE) - if(!pick) - return - var/obj/item/item_to_buy = display_names[pick] - face_atom(user) - product_info = products[item_to_buy] - if(!product_info[TRADER_PRODUCT_INFO_QUANTITY]) - say("[initial(item_to_buy.name)] appears to be out of stock.") - return - say("It will cost you [product_info[TRADER_PRODUCT_INFO_PRICE]] [currency_name] to buy \the [initial(item_to_buy.name)]. Are you sure you want to buy it?") - var/list/npc_options = list( - "Yes" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_yes"), - "No" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_no") - ) - var/buyer_will_buy = show_radial_menu(user, src, npc_options, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE) - if(buyer_will_buy != "Yes") - return - face_atom(user) - if(!spend_buyer_offhand_money(user, product_info[TRADER_PRODUCT_INFO_PRICE])) - say(return_trader_phrase(NO_CASH_PHRASE)) - return - item_to_buy = new item_to_buy(get_turf(user)) - user.put_in_hands(item_to_buy) - playsound(src, sell_sound, 50, TRUE) - product_info[TRADER_PRODUCT_INFO_QUANTITY] -= 1 - say(return_trader_phrase(BUY_PHRASE)) - -///Calculates the value of money in the hand of the buyer and spends it if it's sufficient -/mob/living/simple_animal/hostile/retaliate/trader/proc/spend_buyer_offhand_money(mob/user, the_cost) - var/value = 0 - var/obj/item/holochip/cash = user.is_holding_item_of_type(/obj/item/holochip) - if(cash) - value += cash.credits - if((value >= the_cost) && cash) - return cash.spend(the_cost) - return FALSE //Purchase unsuccessful - -/** - * Tries to call sell_item on one of the user's held items, if fail gives a chat message - * - * Gets both items in the user's hands, and then tries to call sell_item on them, if both fail, he gives a chat message - * Arguments: - * * user - (Mob REF) The mob trying to sell something - */ -/mob/living/simple_animal/hostile/retaliate/trader/proc/try_sell(mob/user) - var/sold_item = FALSE - for(var/obj/item/an_item in user.held_items) - if(sell_item(user, an_item)) - sold_item = TRUE - break - if(!sold_item) - say(return_trader_phrase(ITEM_REJECTED_PHRASE)) - -/** - * Checks if an item is in the list of wanted items and if it is after a Yes/No radial returns generate_cash with the value of the item for the NPC - * Arguments: - * * user - (Mob REF) The mob trying to sell something - * * selling - (Item REF) The item being sold - */ -/mob/living/simple_animal/hostile/retaliate/trader/proc/sell_item(mob/user, obj/item/selling) - var/cost - if(!selling) - return FALSE - var/list/product_info - //Keep track of the typepath; rather mundane but it's required for correctly modifying the wanted_items - //should a product be sellable because even if it doesn't have a entry because it's a child of a parent that is present on the list - var/typepath_for_product_info - if(selling.type in wanted_items) - product_info = wanted_items[selling.type] - typepath_for_product_info = selling.type - else //Assume wanted_items is setup in the correct way; read wanted_items documentation for more info - for(var/typepath in wanted_items) - if(istype(selling, typepath)) - product_info = wanted_items[typepath] - typepath_for_product_info = typepath - break - - if(!product_info) //Nothing interesting to sell - return FALSE - if(product_info[TRADER_PRODUCT_INFO_QUANTITY] <= 0) - say(return_trader_phrase(TRADER_HAS_ENOUGH_ITEM_PHRASE)) - return FALSE - cost = apply_sell_price_mods(selling, product_info[TRADER_PRODUCT_INFO_PRICE]) - if(cost <= 0) - say(return_trader_phrase(ITEM_IS_WORTHLESS_PHRASE)) - return FALSE - say(return_trader_phrase(INTERESTED_PHRASE)) - say("You will receive [cost] [currency_name] for the [selling].") - var/list/npc_options = list( - "Yes" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_yes"), - "No" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_no"), - ) - face_atom(user) - var/npc_result = show_radial_menu(user, src, npc_options, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE) - if(npc_result != "Yes") - say(return_trader_phrase(ITEM_SELLING_CANCELED_PHRASE)) - return TRUE - say(return_trader_phrase(ITEM_SELLING_ACCEPTED_PHRASE)) - playsound(src, sell_sound, 50, TRUE) - log_econ("[selling] has been sold to [src] (typepath used for product info; [typepath_for_product_info]) by [user] for [cost] cash.") - exchange_sold_items(selling, cost, typepath_for_product_info) - generate_cash(cost, user) - return TRUE - -/** - * Handles modifying/deleting the items to ensure that a proper amount is converted into cash; put into it's own proc to make the children of this not override a 30+ line sell_item() - * - * Arguments: - * * selling - (Item REF) this is the item being sold - * * value_exchanged_for - (Number) the "value", useful for a scenario where you want to remove enough items equal to the value - * * original_typepath - (Typepath) For scenarios where a children of a parent is being sold but we want to modify the parent's product information - */ -/mob/living/simple_animal/hostile/retaliate/trader/proc/exchange_sold_items(obj/item/selling, value_exchanged_for, original_typepath) - var/list/product_info = wanted_items[original_typepath] - if(isstack(selling)) - var/obj/item/stack/the_stack = selling - var/actually_sold = min(the_stack.amount, product_info[TRADER_PRODUCT_INFO_QUANTITY]) - the_stack.use(actually_sold) - product_info[TRADER_PRODUCT_INFO_QUANTITY] -= (actually_sold) - else - qdel(selling) - product_info[TRADER_PRODUCT_INFO_QUANTITY] -= 1 - -/** - * Modifies the 'base' price of a item based on certain variables - * - * Arguments: - * * Reference to the item; this is the item being sold - * * Original cost; the original cost of the item, to be manipulated depending on the variables of the item, one example is using item.amount if it's a stack - */ -/mob/living/simple_animal/hostile/retaliate/trader/proc/apply_sell_price_mods(obj/item/selling, original_cost) - if(isstack(selling)) - var/obj/item/stack/stackoverflow = selling - original_cost *= stackoverflow.amount - return original_cost - -/** - * Creates an item equal to the value set by the proc and puts it in the user's hands if possible - * Arguments: - * * value - A number; The amount of cash that will be on the holochip - * * user - Reference to a mob; The mob we put the holochip in hands of - */ -/mob/living/simple_animal/hostile/retaliate/trader/proc/generate_cash(value, mob/user) - var/obj/item/holochip/chip = new /obj/item/holochip(get_turf(user), value) - user.put_in_hands(chip) - -///Sets quantity of all products to initial(quanity); this proc is currently not called anywhere on the base class of traders -/mob/living/simple_animal/hostile/retaliate/trader/proc/restock_products() - products = initial_products() - -///Sets quantity of all wanted_items to initial(quanity); this proc is currently not called anywhere on the base class of traders -/mob/living/simple_animal/hostile/retaliate/trader/proc/renew_item_demands() - wanted_items = initial_wanteds() - -/mob/living/simple_animal/hostile/retaliate/trader/mrbones - name = "Mr. Bones" - desc = "A skeleton merchant, he seems very humerus." - speak_emote = list("rattles") - speech_span = SPAN_SANS - sell_sound = 'sound/voice/hiss2.ogg' - mob_biotypes = MOB_UNDEAD|MOB_HUMANOID - icon_state = "mrbones" - gender = MALE - loot = list(/obj/effect/decal/remains/human) - - say_phrases = list( - ITEM_REJECTED_PHRASE = list( - "Sorry, I'm not a fan of anything you're showing me. Give me something better and we'll talk." - ), - ITEM_SELLING_CANCELED_PHRASE = list( - "What a shame, tell me if you changed your mind." - ), - ITEM_SELLING_ACCEPTED_PHRASE = list( - "Pleasure doing business with you." - ), - INTERESTED_PHRASE = list( - "Hey, you've got an item that interests me, I'd like to buy it, I'll give you some cash for it, deal?" - ), - BUY_PHRASE = list( - "Bone appetit!" - ), - NO_CASH_PHRASE = list( - "Sorry adventurer, I can't give credit! Come back when you're a little mmmmm... richer!" - ), - NO_STOCK_PHRASE = list( - "Sorry adventurer, but that item is not in stock at the moment." - ), - NOT_WILLING_TO_BUY_PHRASE = list( - "I don't want to buy that item for the time being, check back another time." - ), - ITEM_IS_WORTHLESS_PHRASE = list( - "This item seems to be worthless on a closer look, I won't buy this." - ), - TRADER_HAS_ENOUGH_ITEM_PHRASE = list( - "I already bought enough of this for the time being." - ), - TRADER_LORE_PHRASE = list( - "Hello, I am Mr. Bones!", - "The ride never ends!", - "I'd really like a refreshing carton of milk!", - "I'm willing to play big prices for BONES! Need materials to make merch, eh?", - "It's a beautiful day outside. Birds are singing, Flowers are blooming... On days like these, kids like you... Should be buying my wares!" - ), - TRADER_NOT_BUYING_ANYTHING = list( - "I'm currently buying nothing at the moment." - ), - TRADER_NOT_SELLING_ANYTHING = list( - "I'm currently selling nothing at the moment." - ), - ) - -/mob/living/simple_animal/hostile/retaliate/trader/mrbones/initial_products() - return list( - /obj/item/clothing/head/helmet/skull = list(150, INFINITY), - /obj/item/clothing/mask/bandana/skull/black = list(50, INFINITY), - /obj/item/food/cookie/sugar/spookyskull = list(10, INFINITY), - /obj/item/instrument/trombone/spectral = list(10000, INFINITY), - /obj/item/shovel/serrated = list(150, INFINITY), - ) - -/mob/living/simple_animal/hostile/retaliate/trader/mrbones/initial_wanteds() - return list( - /obj/item/reagent_containers/condiment/milk = list(1000, INFINITY, ""), - /obj/item/stack/sheet/bone = list(420, INFINITY, ", per sheet of bone"), - ) - -#undef ITEM_REJECTED_PHRASE -#undef ITEM_SELLING_CANCELED_PHRASE -#undef ITEM_SELLING_ACCEPTED_PHRASE -#undef INTERESTED_PHRASE -#undef BUY_PHRASE -#undef NO_CASH_PHRASE -#undef NO_STOCK_PHRASE -#undef NOT_WILLING_TO_BUY_PHRASE -#undef ITEM_IS_WORTHLESS_PHRASE -#undef TRADER_HAS_ENOUGH_ITEM_PHRASE -#undef TRADER_LORE_PHRASE -#undef TRADER_NOT_BUYING_ANYTHING -#undef TRADER_NOT_SELLING_ANYTHING -#undef TRADER_PRODUCT_INFO_PRICE -#undef TRADER_PRODUCT_INFO_QUANTITY -#undef TRADER_PRODUCT_INFO_PRICE_MOD_DESCRIPTION diff --git a/code/modules/mob/living/simple_animal/hostile/smspider.dm b/code/modules/mob/living/simple_animal/hostile/smspider.dm deleted file mode 100644 index 215164a9f6e3..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/smspider.dm +++ /dev/null @@ -1,65 +0,0 @@ -/mob/living/simple_animal/hostile/smspider - name = "supermatter spider" - desc= "A sliver of supermatter placed upon a robotically enhanced pedestal." - icon = 'icons/mob/simple/smspider.dmi' - icon_state = "smspider" - icon_living = "smspider" - icon_dead = "smspider_dead" - gender = NEUTER - mob_biotypes = MOB_BUG|MOB_ROBOTIC - turns_per_move = 2 - speak_emote = list("vibrates") - emote_see = list("vibrates") - emote_taunt = list("vibrates") - taunt_chance = 40 - istate = ISTATE_HARM|ISTATE_BLOCKING - maxHealth = 10 - health = 10 - minbodytemp = 0 - maxbodytemp = 1500 - healable = 0 - attack_verb_continuous = "slices" - attack_verb_simple = "slice" - attack_sound = 'sound/effects/supermatter.ogg' - attack_vis_effect = ATTACK_EFFECT_CLAW - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - robust_searching = 1 - faction = list(FACTION_HOSTILE) - // Gold, supermatter tinted - lighting_cutoff_red = 30 - lighting_cutoff_green = 30 - lighting_cutoff_blue = 10 - death_message = "falls to the ground, its shard dulling to a miserable grey!" - footstep_type = FOOTSTEP_MOB_CLAW - var/overcharged = FALSE // if true, spider will not die if it dusts a limb - -/mob/living/simple_animal/hostile/smspider/AttackingTarget() - . = ..() - if(isliving(target)) - playsound(get_turf(src), 'sound/effects/supermatter.ogg', 10, TRUE) - visible_message(span_danger("[src] knocks into [target], turning them to dust in a brilliant flash of light!")) - var/mob/living/victim = target - victim.investigate_log("has been dusted by [src].", INVESTIGATE_DEATHS) - victim.dust() - if(!overcharged) - death() - else if(!isturf(target)) - playsound(get_turf(src), 'sound/effects/supermatter.ogg', 10, TRUE) - visible_message(span_danger("[src] knocks into [target], turning it to dust in a brilliant flash of light!")) - qdel(target) - if(!overcharged) - death() - return FALSE - -/mob/living/simple_animal/hostile/smspider/Initialize(mapload) - . = ..() - AddComponent(/datum/component/swarming) - -/mob/living/simple_animal/hostile/smspider/overcharged - name = "overcharged supermatter spider" - desc = "A sliver of overcharged supermatter placed upon a robotically enhanced pedestal. This one seems especially dangerous." - icon_state = "smspideroc" - icon_living = "smspideroc" - maxHealth = 25 - health = 25 - overcharged = TRUE diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm deleted file mode 100644 index 234d2b691f62..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm +++ /dev/null @@ -1,385 +0,0 @@ -/// The darkness threshold for space dragon when choosing a color -#define DARKNESS_THRESHOLD 50 - -/** - * # Space Dragon - * - * A space-faring leviathan-esque monster which breathes fire and summons carp. Spawned during its respective midround antagonist event. - * - * A space-faring monstrosity who has the ability to breathe dangerous fire breath and uses its powerful wings to knock foes away. - * Normally spawned as an antagonist during the Space Dragon event, Space Dragon's main goal is to open three rifts from which to pull a great tide of carp onto the station. - * Space Dragon can summon only one rift at a time, and can do so anywhere a blob is allowed to spawn. In order to trigger his victory condition, Space Dragon must summon and defend three rifts while they charge. - * Space Dragon, when spawned, has five minutes to summon the first rift. Failing to do so will cause Space Dragon to return from whence he came. - * When the rift spawns, ghosts can interact with it to spawn in as space carp to help complete the mission. One carp is granted when the rift is first summoned, with an extra one every 30 seconds. - * Once the victory condition is met, all current rifts become invulnerable to damage, are allowed to spawn infinite sentient space carp, and Space Dragon gets unlimited rage. - * Alternatively, if the shuttle arrives while Space Dragon is still active, their victory condition will automatically be met and all the rifts will immediately become fully charged. - * If a charging rift is destroyed, Space Dragon will be incredibly slowed, and the endlag on his gust attack is greatly increased on each use. - * Space Dragon has the following abilities to assist him with his objective: - * - Can shoot fire in straight line, dealing 30 burn damage and setting those suseptible on fire. - * - Can use his wings to temporarily stun and knock back any nearby mobs. This attack has no cooldown, but instead has endlag after the attack where Space Dragon cannot act. This endlag's time decreases over time, but is added to every time he uses the move. - * - Can swallow mob corpses to heal for half their max health. Any corpses swallowed are stored within him, and will be regurgitated on death. - * - Can tear through any type of wall. This takes 4 seconds for most walls, and 12 seconds for reinforced walls. - */ -/mob/living/simple_animal/hostile/space_dragon - name = "Space Dragon" - desc = "A vile, leviathan-esque creature that flies in the most unnatural way. Looks slightly similar to a space carp." - gender = NEUTER - maxHealth = 320 - health = 320 - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0.5, OXY = 1) - istate = ISTATE_HARM|ISTATE_BLOCKING - speed = 0 - movement_type = FLYING - attack_verb_continuous = "chomps" - attack_verb_simple = "chomp" - attack_sound = 'sound/magic/demon_attack1.ogg' - attack_vis_effect = ATTACK_EFFECT_BITE - death_sound = 'sound/creatures/space_dragon_roar.ogg' - icon = 'icons/mob/nonhuman-player/spacedragon.dmi' - icon_state = "spacedragon" - icon_living = "spacedragon" - icon_dead = "spacedragon_dead" - health_doll_icon = "spacedragon" - obj_damage = 50 - environment_smash = ENVIRONMENT_SMASH_NONE - flags_1 = PREVENT_CONTENTS_EXPLOSION_1 - melee_damage_upper = 35 - melee_damage_lower = 35 - mob_size = MOB_SIZE_LARGE - armour_penetration = 30 - pixel_x = -16 - base_pixel_x = -16 - maptext_height = 64 - maptext_width = 64 - turns_per_move = 5 - ranged = TRUE - mouse_opacity = MOUSE_OPACITY_ICON - butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30) - death_message = "screeches as its wings turn to dust and it collapses on the floor, its life extinguished." - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxbodytemp = 1500 - faction = list(FACTION_CARP) - pressure_resistance = 200 - /// How much endlag using Wing Gust should apply. Each use of wing gust increments this, and it decreases over time. - var/tiredness = 0 - /// A multiplier to how much each use of wing gust should add to the tiredness variable. Set to 5 if the current rift is destroyed. - var/tiredness_mult = 1 - /// The distance Space Dragon's gust reaches - var/gust_distance = 4 - /// The amount of tiredness to add to Space Dragon per use of gust - var/gust_tiredness = 30 - /// Determines whether or not Space Dragon is in the middle of using wing gust. If set to true, prevents him from moving and doing certain actions. - var/using_special = FALSE - /// Determines whether or not Space Dragon is currently tearing through a wall. - var/tearing_wall = FALSE - /// The ability to make your sprite smaller - var/datum/action/small_sprite/space_dragon/small_sprite - /// The color of the space dragon. - var/chosen_color - /// Minimum devastation damage dealt coefficient based on max health - var/devastation_damage_min_percentage = 0.4 - /// Maximum devastation damage dealt coefficient based on max health - var/devastation_damage_max_percentage = 0.75 - -/mob/living/simple_animal/hostile/space_dragon/Initialize(mapload) - . = ..() - AddElement(/datum/element/simple_flying) - add_traits(list(TRAIT_SPACEWALK, TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_NO_FLOATING_ANIM, TRAIT_HEALS_FROM_CARP_RIFTS), INNATE_TRAIT) - AddElement(/datum/element/content_barfer) - small_sprite = new - small_sprite.Grant(src) - RegisterSignal(small_sprite, COMSIG_ACTION_TRIGGER, PROC_REF(add_dragon_overlay)) - -/mob/living/simple_animal/hostile/space_dragon/Login() - . = ..() - if(!chosen_color) - dragon_name() - color_selection() - -/mob/living/simple_animal/hostile/space_dragon/ex_act_devastate() - var/damage_coefficient = rand(devastation_damage_min_percentage, devastation_damage_max_percentage) - adjustBruteLoss(initial(maxHealth)*damage_coefficient) - -/mob/living/simple_animal/hostile/space_dragon/Life(seconds_per_tick = SSMOBS_DT, times_fired) - . = ..() - tiredness = max(tiredness - (0.5 * seconds_per_tick), 0) - for(var/mob/living/consumed_mob in src) - if(consumed_mob.stat == DEAD) - continue - playsound(src, 'sound/effects/splat.ogg', 50, TRUE) - visible_message(span_danger("[src] vomits up [consumed_mob]!")) - consumed_mob.forceMove(loc) - consumed_mob.Paralyze(50) - -/mob/living/simple_animal/hostile/space_dragon/AttackingTarget() - if(using_special) - return - if(target == src) - to_chat(src, span_warning("You almost bite yourself, but then decide against it.")) - return - if(iswallturf(target)) - if(tearing_wall) - return - tearing_wall = TRUE - var/turf/closed/wall/thewall = target - to_chat(src, span_warning("You begin tearing through the wall...")) - playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) - var/timetotear = 40 - if(istype(target, /turf/closed/wall/r_wall)) - timetotear = 120 - if(do_after(src, timetotear, target = thewall)) - if(isopenturf(thewall)) - return - thewall.dismantle_wall(1) - playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) - tearing_wall = FALSE - return - if(isliving(target)) //Swallows corpses like a snake to regain health. - var/mob/living/L = target - if(L.stat == DEAD) - to_chat(src, span_warning("You begin to swallow [L] whole...")) - if(do_after(src, 30, target = L)) - if(eat(L)) - adjustHealth(-L.maxHealth * 0.25) - return - . = ..() - if(ismecha(target)) - var/obj/vehicle/sealed/mecha/M = target - M.take_damage(50, BRUTE, MELEE, 1) - -/mob/living/simple_animal/hostile/space_dragon/ranged_secondary_attack(atom/target, modifiers) - if(using_special) - return - using_special = TRUE - icon_state = "spacedragon_gust" - add_dragon_overlay() - useGust(0) - -/mob/living/simple_animal/hostile/space_dragon/Move() - if(!using_special) - ..() - -/mob/living/simple_animal/hostile/space_dragon/OpenFire() - if(using_special) - return - ranged_cooldown = world.time + ranged_cooldown_time - fire_stream() - -/mob/living/simple_animal/hostile/space_dragon/death(gibbed) - . = ..() - add_dragon_overlay() - UnregisterSignal(small_sprite, COMSIG_ACTION_TRIGGER) - -/mob/living/simple_animal/hostile/space_dragon/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE) - var/was_dead = stat == DEAD - . = ..() - add_dragon_overlay() - - if (was_dead) - RegisterSignal(small_sprite, COMSIG_ACTION_TRIGGER, PROC_REF(add_dragon_overlay)) - -/** - * Allows space dragon to choose its own name. - * - * Prompts the space dragon to choose a name, which it will then apply to itself. - * If the name is invalid, will re-prompt the dragon until a proper name is chosen. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/dragon_name() - var/chosen_name = sanitize_name(reject_bad_text(tgui_input_text(src, "What would you like your name to be?", "Choose Your Name", real_name, MAX_NAME_LEN))) - if(!chosen_name) - to_chat(src, span_warning("Not a valid name, please try again.")) - dragon_name() - return - to_chat(src, span_notice("Your name is now [span_name("[chosen_name]")], the feared Space Dragon.")) - fully_replace_character_name(null, chosen_name) - -/** - * Allows space dragon to choose a color for itself. - * - * Prompts the space dragon to choose a color, from which it will then apply to itself. - * If an invalid color is given, will re-prompt the dragon until a proper color is chosen. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/color_selection() - chosen_color = input(src,"What would you like your color to be?","Choose Your Color", COLOR_WHITE) as color|null - if(!chosen_color) //redo proc until we get a color - to_chat(src, span_warning("Not a valid color, please try again.")) - color_selection() - return - var/temp_hsv = RGBtoHSV(chosen_color) - if(ReadHSV(temp_hsv)[3] < DARKNESS_THRESHOLD) - to_chat(src, span_danger("Invalid color. Your color is not bright enough.")) - color_selection() - return - add_atom_colour(chosen_color, FIXED_COLOUR_PRIORITY) - add_dragon_overlay() - -/** - * Adds the proper overlay to the space dragon. - * - * Clears the current overlay on space dragon and adds a proper one for whatever animation he's in. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/add_dragon_overlay() - cut_overlays() - if(!small_sprite.small) - return - if(stat == DEAD) - var/mutable_appearance/overlay = mutable_appearance(icon, "overlay_dead") - overlay.appearance_flags = RESET_COLOR - add_overlay(overlay) - return - if(!using_special) - var/mutable_appearance/overlay = mutable_appearance(icon, "overlay_base") - overlay.appearance_flags = RESET_COLOR - add_overlay(overlay) - return - if(using_special) - var/mutable_appearance/overlay = mutable_appearance(icon, "overlay_gust") - overlay.appearance_flags = RESET_COLOR - add_overlay(overlay) - -/** - * Determines a line of turfs from sources's position to the target with length range. - * - * Determines a line of turfs from the source's position to the target with length range. - * The line will extend on past the target if the range is large enough, and not reach the target if range is small enough. - * Arguments: - * * offset - whether or not to aim slightly to the left or right of the target - * * range - how many turfs should we go out for - * * atom/at - The target - */ -/mob/living/simple_animal/hostile/space_dragon/proc/line_target(offset, range, atom/at = target) - if(!at) - return - var/angle = ATAN2(at.x - src.x, at.y - src.y) + offset - var/turf/T = get_turf(src) - for(var/i in 1 to range) - var/turf/check = locate(src.x + cos(angle) * i, src.y + sin(angle) * i, src.z) - if(!check) - break - T = check - return (get_line(src, T) - get_turf(src)) - -/** - * Spawns fire at each position in a line from the source to the target. - * - * Spawns fire at each position in a line from the source to the target. - * Stops if it comes into contact with a solid wall, a window, or a door. - * Delays the spawning of each fire by 1.5 deciseconds. - * Arguments: - * * atom/at - The target - */ -/mob/living/simple_animal/hostile/space_dragon/proc/fire_stream(atom/at = target) - playsound(get_turf(src),'sound/magic/fireball.ogg', 200, TRUE) - var/range = 20 - var/list/turfs = list() - turfs = line_target(0, range, at) - var/delayFire = -1.0 - for(var/turf/T in turfs) - if(isclosedturf(T)) - return - for(var/obj/structure/window/W in T.contents) - return - for(var/obj/machinery/door/D in T.contents) - if(D.density) - return - delayFire += 1.5 - addtimer(CALLBACK(src, PROC_REF(dragon_fire_line), T), delayFire) - -/** - * What occurs on each tile to actually create the fire. - * - * Creates a fire on the given turf. - * It creates a hotspot on the given turf, damages any living mob with 30 burn damage, and damages mechs by 50. - * It can only hit any given target once. - * Arguments: - * * turf/T - The turf to trigger the effects on. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/dragon_fire_line(turf/T) - var/list/hit_list = list() - hit_list += src - new /obj/effect/hotspot(T) - T.hotspot_expose(700,50,1) - for(var/mob/living/L in T.contents) - if(L in hit_list) - continue - if(L.mind?.has_antag_datum(/datum/antagonist/space_carp)) - continue - hit_list += L - L.adjustFireLoss(30) - to_chat(L, span_userdanger("You're hit by [src]'s fire breath!")) - // deals damage to mechs - for(var/obj/vehicle/sealed/mecha/M in T.contents) - if(M in hit_list) - continue - hit_list += M - M.take_damage(50, BRUTE, MELEE, 1) - -/** - * Handles consuming and storing consumed things inside Space Dragon - * - * Plays a sound and then stores the consumed thing inside Space Dragon. - * Used in AttackingTarget(), paired with a heal should it succeed. - * Arguments: - * * atom/movable/A - The thing being consumed - */ -/mob/living/simple_animal/hostile/space_dragon/proc/eat(atom/movable/A) - if(A && A.loc != src) - playsound(src, 'sound/magic/demon_attack1.ogg', 100, TRUE) - visible_message(span_warning("[src] swallows [A] whole!")) - A.forceMove(src) - return TRUE - return FALSE - -/** - * Resets Space Dragon's status after using wing gust. - * - * Resets Space Dragon's status after using wing gust. - * If it isn't dead by the time it calls this method, reset the sprite back to the normal living sprite. - * Also sets the using_special variable to FALSE, allowing Space Dragon to move and attack freely again. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/reset_status() - if(stat != DEAD) - icon_state = "spacedragon" - using_special = FALSE - add_dragon_overlay() - -/** - * Handles wing gust from the windup all the way to the endlag at the end. - * - * Handles the wing gust attack from start to finish, based on the timer. - * When intially triggered, starts at 0. Until the timer reaches 10, increase Space Dragon's y position by 2 and call back to the function in 1.5 deciseconds. - * When the timer is at 10, trigger the attack. Change Space Dragon's sprite. reset his y position, and push all living creatures back in a 3 tile radius and stun them for 5 seconds. - * Stay in the ending state for how much our tiredness dictates and add to our tiredness. - * Arguments: - * * timer - The timer used for the windup. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/useGust(timer) - if(timer != 10) - pixel_y = pixel_y + 2; - addtimer(CALLBACK(src, PROC_REF(useGust), timer + 1), 1.5) - return - pixel_y = 0 - icon_state = "spacedragon_gust_2" - cut_overlays() - var/mutable_appearance/overlay = mutable_appearance(icon, "overlay_gust_2") - overlay.appearance_flags = RESET_COLOR - add_overlay(overlay) - playsound(src, 'sound/effects/gravhit.ogg', 100, TRUE) - var/gust_locs = spiral_range_turfs(gust_distance, get_turf(src)) - var/list/hit_things = list() - for(var/turf/T in gust_locs) - for(var/mob/living/L in T.contents) - if(L == src) - continue - hit_things += L - visible_message(span_boldwarning("[L] is knocked back by the gust!")) - to_chat(L, span_userdanger("You're knocked back by the gust!")) - var/dir_to_target = get_dir(get_turf(src), get_turf(L)) - var/throwtarget = get_edge_target_turf(target, dir_to_target) - L.safe_throw_at(throwtarget, 10, 1, src) - L.Paralyze(50) - addtimer(CALLBACK(src, PROC_REF(reset_status)), 4 + ((tiredness * tiredness_mult) / 10)) - tiredness = tiredness + (gust_tiredness * tiredness_mult) - -#undef DARKNESS_THRESHOLD diff --git a/code/modules/mob/living/simple_animal/hostile/wizard.dm b/code/modules/mob/living/simple_animal/hostile/wizard.dm deleted file mode 100644 index 25432e2f6dea..000000000000 --- a/code/modules/mob/living/simple_animal/hostile/wizard.dm +++ /dev/null @@ -1,83 +0,0 @@ -/mob/living/simple_animal/hostile/wizard - name = "Space Wizard" - desc = "EI NATH?" - icon = 'icons/mob/simple/simple_human.dmi' - icon_state = "wizard" - icon_living = "wizard" - icon_dead = "wizard_dead" - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - sentience_type = SENTIENCE_HUMANOID - speak_chance = 0 - turns_per_move = 3 - speed = 0 - maxHealth = 100 - health = 100 - harm_intent_damage = 5 - melee_damage_lower = 5 - melee_damage_upper = 5 - attack_verb_continuous = "punches" - attack_verb_simple = "punch" - attack_sound = 'sound/weapons/punch1.ogg' - istate = ISTATE_HARM|ISTATE_BLOCKING - atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 7.5 - faction = list(ROLE_WIZARD) - status_flags = CANPUSH - footstep_type = FOOTSTEP_MOB_SHOE - - retreat_distance = 3 //out of fireball range - minimum_distance = 3 - del_on_death = 1 - loot = list( - /obj/effect/mob_spawn/corpse/human/wizard, - /obj/item/staff, - ) - - var/next_cast = 0 - var/datum/action/cooldown/spell/pointed/projectile/fireball/fireball - var/datum/action/cooldown/spell/teleport/radius_turf/blink/blink - var/datum/action/cooldown/spell/aoe/magic_missile/magic_missile - -/mob/living/simple_animal/hostile/wizard/Initialize(mapload) - . = ..() - apply_dynamic_human_appearance(src, mob_spawn_path = /obj/effect/mob_spawn/corpse/human/wizard, r_hand = /obj/item/staff) - var/obj/item/implant/exile/exiled = new /obj/item/implant/exile(src) - exiled.implant(src) - - fireball = new(src) - fireball.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) - fireball.Grant(src) - - magic_missile = new(src) - magic_missile.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) - magic_missile.Grant(src) - - blink = new(src) - blink.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) - blink.outer_tele_radius = 3 - blink.Grant(src) - -/mob/living/simple_animal/hostile/wizard/Destroy() - QDEL_NULL(fireball) - QDEL_NULL(magic_missile) - QDEL_NULL(blink) - return ..() - -/mob/living/simple_animal/hostile/wizard/handle_automated_action() - . = ..() - if(target && next_cast < world.time) - if((get_dir(src, target) in list(SOUTH, EAST, WEST, NORTH)) && fireball.can_cast_spell(feedback = FALSE)) - setDir(get_dir(src, target)) - fireball.Trigger(null, target) - next_cast = world.time + 1 SECONDS - return - - if(magic_missile.IsAvailable()) - magic_missile.Trigger(null, target) - next_cast = world.time + 1 SECONDS - return - - if(blink.IsAvailable()) // Spam Blink when you can - blink.Trigger(null, src) - next_cast = world.time + 1 SECONDS - return diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm deleted file mode 100644 index ae67f37277f8..000000000000 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ /dev/null @@ -1,1033 +0,0 @@ -/* Parrots! - * Contains - * Defines - * Inventory (headset stuff) - * Attack responces - * AI - * Procs / Verbs (usable by players) - * Sub-types - * Hear & say (the things we do for gimmicks) - */ - -/* - * Defines - */ - -//Only a maximum of one action and one intent should be active at any given time. -//Actions -#define PARROT_PERCH (1<<0) //Sitting/sleeping, not moving -#define PARROT_SWOOP (1<<1) //Moving towards or away from a target -#define PARROT_WANDER (1<<2) //Moving without a specific target in mind - -//Intents -#define PARROT_STEAL (1<<3) //Flying towards a target to steal it/from it -#define PARROT_ATTACK (1<<4) //Flying towards a target to attack it -#define PARROT_RETURN (1<<5) //Flying towards its perch -#define PARROT_FLEE (1<<6) //Flying away from its attacker - - -/mob/living/simple_animal/parrot - name = "parrot" - desc = "The parrot squawks, \"They're a Parrot! BAWWK!\"" //' - icon = 'icons/mob/simple/animal.dmi' - icon_state = "parrot_fly" - icon_living = "parrot_fly" - icon_dead = "parrot_dead" - var/icon_sit = "parrot_sit" - density = FALSE - health = 80 - maxHealth = 80 - pass_flags = PASSTABLE | PASSMOB - - speak = list("Hi!","Hello!","Cracker?","BAWWWWK george mellons griffing me!") - speak_emote = list("squawks","says","yells") - emote_hear = list("squawks.","bawks!") - emote_see = list("flutters their wings.") - - speak_chance = 1 //1% (1 in 100) chance every tick; So about once per 150 seconds, assuming an average tick is 1.5s - turns_per_move = 5 - butcher_results = list(/obj/item/food/cracker = 1) - melee_damage_upper = 10 - melee_damage_lower = 5 - - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "gently moves aside" - response_disarm_simple = "gently move aside" - response_harm_continuous = "swats" - response_harm_simple = "swat" - stop_automated_movement = 1 - istate = ISTATE_HARM|ISTATE_BLOCKING //parrots now start "aggressive" since only player parrots will nuzzle. - attack_verb_continuous = "chomps" - attack_verb_simple = "chomp" - attack_vis_effect = ATTACK_EFFECT_BITE - friendly_verb_continuous = "grooms" - friendly_verb_simple = "groom" - mob_size = MOB_SIZE_SMALL - gold_core_spawnable = FRIENDLY_SPAWN - - var/parrot_damage_upper = 10 - var/parrot_state = PARROT_WANDER //Hunt for a perch when created - var/parrot_sleep_max = 25 //The time the parrot sits while perched before looking around. Mosly a way to avoid the parrot's AI in life() being run every single tick. - var/parrot_sleep_dur = 25 //Same as above, this is the var that physically counts down - var/parrot_dam_zone = list(BODY_ZONE_CHEST, BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_ARM, BODY_ZONE_R_LEG) //For humans, select a bodypart to attack - - var/parrot_speed = 5 //"Delay in world ticks between movement." according to byond. Yeah, that's BS but it does directly affect movement. Higher number = slower. - var/parrot_lastmove = null //Updates/Stores position of the parrot while it's moving - var/parrot_stuck = 0 //If parrot_lastmove hasn't changed, this will increment until it reaches parrot_stuck_threshold - var/parrot_stuck_threshold = 10 //if this == parrot_stuck, it'll force the parrot back to wandering - - var/list/speech_buffer = list() - var/speech_shuffle_rate = 20 - var/list/available_channels = list() - - //Headset for Poly to yell at engineers :) - var/obj/item/radio/headset/ears = null - - //Wheter the Parrot should come with a headset - var/spawn_headset = TRUE - - //The thing the parrot is currently interested in. This gets used for items the parrot wants to pick up, mobs it wants to steal from, - //mobs it wants to attack or mobs that have attacked it - var/atom/movable/parrot_interest = null - - //Parrots will generally sit on their perch unless something catches their eye. - //These vars store their preffered perch and if they dont have one, what they can use as a perch - var/obj/parrot_perch = null - var/obj/desired_perches = list(/obj/structure/frame/computer, - /obj/structure/displaycase, - /obj/structure/filingcabinet, - /obj/machinery/teleport, - /obj/machinery/dna_scannernew, - /obj/machinery/telecomms, - /obj/machinery/nuclearbomb, - /obj/machinery/recharge_station, - /obj/machinery/smartfridge, - /obj/machinery/computer, - /obj/machinery/suit_storage_unit, - ) - - //Parrots are kleptomaniacs. This variable ... stores the item a parrot is holding. - var/obj/item/held_item = null - - -/mob/living/simple_animal/parrot/Initialize(mapload) - . = ..() - parrot_sleep_dur = parrot_sleep_max //In case someone decides to change the max without changing the duration var - - add_verb(src, list(/mob/living/simple_animal/parrot/proc/steal_from_ground, \ - /mob/living/simple_animal/parrot/proc/steal_from_mob, \ - /mob/living/simple_animal/parrot/verb/drop_held_item_player, \ - /mob/living/simple_animal/parrot/proc/perch_player, \ - /mob/living/simple_animal/parrot/proc/toggle_mode, - /mob/living/simple_animal/parrot/proc/perch_mob_player)) - - AddElement(/datum/element/strippable, GLOB.strippable_parrot_items) - AddElement(/datum/element/simple_flying) - if(!spawn_headset) - return - if(!ears) - var/headset = pick(/obj/item/radio/headset/headset_sec, \ - /obj/item/radio/headset/headset_eng, \ - /obj/item/radio/headset/headset_med, \ - /obj/item/radio/headset/headset_sci, \ - /obj/item/radio/headset/headset_cargo) - ears = new headset(src) - -/mob/living/simple_animal/parrot/Destroy() - QDEL_NULL(ears) - return ..() - -/mob/living/simple_animal/parrot/examine(mob/user) - . = ..() - if(stat) - if(HAS_TRAIT(user, TRAIT_NAIVE)) - . += pick("It seems tired and shagged out after a long squawk.", "It seems to be pining for the fjords.", "It's resting. It's a beautiful bird. Lovely plumage.") - else - . += pick("This parrot is no more.","This is a late parrot.","This is an ex-parrot.") - -/mob/living/simple_animal/parrot/death(gibbed) - if(held_item) - held_item.forceMove(drop_location()) - held_item = null - SSmove_manager.stop_looping(src) - - if(buckled) - buckled.unbuckle_mob(src,force=1) - buckled = null - pixel_x = base_pixel_x - pixel_y = base_pixel_y - - return ..() - - -/mob/living/simple_animal/parrot/get_status_tab_items() - . = ..() - . += "Held Item: [held_item]" - -/mob/living/simple_animal/parrot/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range) - . = ..() - if(speaker != src && prob(50)) //Dont imitate ourselves - if(!radio_freq || prob(10)) - if(speech_buffer.len >= 500) - speech_buffer -= pick(speech_buffer) - speech_buffer |= html_decode(raw_message) - if(speaker == src && !client) //If a parrot squawks in the woods and no one is around to hear it, does it make a sound? This code says yes! - return message - -/mob/living/simple_animal/parrot/radio(message, list/message_mods = list(), list/spans, language) //literally copied from human/radio(), but there's no other way to do this. at least it's better than it used to be. - . = ..() - if(.) - return - - if(message_mods[MODE_HEADSET]) - if(ears) - ears.talk_into(src, message, , spans, language, message_mods) - return ITALICS | REDUCE_RANGE - else if(message_mods[RADIO_EXTENSION] == MODE_DEPARTMENT) - if(ears) - ears.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods) - return ITALICS | REDUCE_RANGE - else if(message_mods[RADIO_EXTENSION] in GLOB.radiochannels) - if(ears) - ears.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods) - return ITALICS | REDUCE_RANGE - - return FALSE - -GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list( - /datum/strippable_item/parrot_headset, -))) - -/datum/strippable_item/parrot_headset - key = STRIPPABLE_ITEM_PARROT_HEADSET - -/datum/strippable_item/parrot_headset/get_item(atom/source) - var/mob/living/simple_animal/parrot/parrot_source = source - return istype(parrot_source) ? parrot_source.ears : null - -/datum/strippable_item/parrot_headset/try_equip(atom/source, obj/item/equipping, mob/user) - . = ..() - if (!.) - return FALSE - - if (!istype(equipping, /obj/item/radio/headset)) - to_chat(user, span_warning("[equipping] won't fit!")) - return FALSE - - return TRUE - -// There is no delay for putting a headset on a parrot. -/datum/strippable_item/parrot_headset/start_equip(atom/source, obj/item/equipping, mob/user) - return TRUE - -/datum/strippable_item/parrot_headset/finish_equip(atom/source, obj/item/equipping, mob/user) - var/obj/item/radio/headset/radio = equipping - if (!istype(radio)) - return - - var/mob/living/simple_animal/parrot/parrot_source = source - if (!istype(parrot_source)) - return - - if (!user.transferItemToLoc(radio, source)) - return - - parrot_source.ears = radio - - to_chat(user, span_notice("You fit [radio] onto [source].")) - - parrot_source.available_channels.Cut() - - for (var/channel in radio.channels) - var/channel_to_add - - switch (channel) - if (RADIO_CHANNEL_ENGINEERING) - channel_to_add = RADIO_TOKEN_ENGINEERING - if (RADIO_CHANNEL_COMMAND) - channel_to_add = RADIO_TOKEN_COMMAND - if (RADIO_CHANNEL_SECURITY) - channel_to_add = RADIO_TOKEN_SECURITY - if (RADIO_CHANNEL_SCIENCE) - channel_to_add = RADIO_TOKEN_SCIENCE - if (RADIO_CHANNEL_MEDICAL) - channel_to_add = RADIO_TOKEN_MEDICAL - if (RADIO_CHANNEL_SUPPLY) - channel_to_add = RADIO_TOKEN_SUPPLY - if (RADIO_CHANNEL_SERVICE) - channel_to_add = RADIO_TOKEN_SERVICE - - if (channel_to_add) - parrot_source.available_channels += channel_to_add - - if (radio.translate_binary) - parrot_source.available_channels.Add(MODE_TOKEN_BINARY) - -/datum/strippable_item/parrot_headset/start_unequip(atom/source, mob/user) - . = ..() - if (!.) - return FALSE - - var/mob/living/simple_animal/parrot/parrot_source = source - if (!istype(parrot_source)) - return - - if (!parrot_source.stat) - parrot_source.say("[parrot_source.available_channels.len ? "[pick(parrot_source.available_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") - - return TRUE - -/datum/strippable_item/parrot_headset/finish_unequip(atom/source, mob/user) - var/mob/living/simple_animal/parrot/parrot_source = source - if (!istype(parrot_source)) - return - - parrot_source.ears.forceMove(parrot_source.drop_location()) - parrot_source.ears = null - -/* - * Attack responces - */ -//Humans, monkeys, aliens -/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/user, list/modifiers) - ..() - if(client) - return - if(!stat && (user.istate & ISTATE_HARM)) - - icon_state = icon_living //It is going to be flying regardless of whether it flees or attacks - - if(parrot_state == PARROT_PERCH) - parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched - - parrot_interest = user - parrot_state = PARROT_SWOOP //The parrot just got hit, it WILL move, now to pick a direction.. - - if(health > 30) //Let's get in there and squawk it up! - parrot_state |= PARROT_ATTACK - else - parrot_state |= PARROT_FLEE //Otherwise, fly like a bat out of hell! - drop_held_item(0) - if(stat != DEAD && !(user.istate & ISTATE_HARM)) - handle_automated_speech(1) //assured speak/emote - return - -/mob/living/simple_animal/parrot/attack_paw(mob/living/carbon/human/user, list/modifiers) - return attack_hand(modifiers) - -/mob/living/simple_animal/parrot/attack_alien(mob/living/carbon/alien/user, list/modifiers) - return attack_hand(user, modifiers) - -//Simple animals -/mob/living/simple_animal/parrot/attack_animal(mob/living/simple_animal/user, list/modifiers) - . = ..() //goodbye immortal parrots - - if(client) - return - - if(parrot_state == PARROT_PERCH) - parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched - - if(user.melee_damage_upper > 0 && !stat) - parrot_interest = user - parrot_state = PARROT_SWOOP | PARROT_ATTACK //Attack other animals regardless - icon_state = icon_living - -//Mobs with objects -/mob/living/simple_animal/parrot/attackby(obj/item/O, mob/living/user, params) - if(!stat && !client && !istype(O, /obj/item/stack/medical) && !istype(O, /obj/item/food/cracker)) - if(O.force) - if(parrot_state == PARROT_PERCH) - parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched - - parrot_interest = user - parrot_state = PARROT_SWOOP - if(health > 30) //Let's get in there and squawk it up! - parrot_state |= PARROT_ATTACK - else - parrot_state |= PARROT_FLEE - icon_state = icon_living - drop_held_item(0) - else if(istype(O, /obj/item/food/cracker)) //Poly wants a cracker. - qdel(O) - if(health < maxHealth) - adjustBruteLoss(-10) - speak_chance *= 1.27 // 20 crackers to go from 1% to 100% - speech_shuffle_rate += 10 - to_chat(user, span_notice("[src] eagerly devours the cracker.")) - ..() - return - -//Bullets -/mob/living/simple_animal/parrot/bullet_act(obj/projectile/Proj) - . = ..() - if(!stat && !client) - if(parrot_state == PARROT_PERCH) - parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched - - parrot_interest = null - parrot_state = PARROT_WANDER | PARROT_FLEE //Been shot and survived! RUN LIKE HELL! - //parrot_been_shot += 5 - icon_state = icon_living - drop_held_item(0) - -/mob/living/simple_animal/parrot/Process_Spacemove(movement_dir = 0, continuous_move = FALSE) - if(!stat) //Birds can fly, fun fact. No I don't care that space doesn't have air. Space parrots bitch - return TRUE - return ..() -/* - * AI - Not really intelligent, but I'm calling it AI anyway. - */ -/mob/living/simple_animal/parrot/Life(seconds_per_tick = SSMOBS_DT, times_fired) - ..() - - //Sprite update for when a parrot gets pulled - if(pulledby && !stat && parrot_state != PARROT_WANDER) - if(buckled) - buckled.unbuckle_mob(src, TRUE) - buckled = null - icon_state = icon_living - parrot_state = PARROT_WANDER - pixel_x = initial(pixel_x) - pixel_y = initial(pixel_y) - return - - -//-----SPEECH - /* Parrot speech mimickry! - Phrases that the parrot Hear()s get added to speach_buffer. - Every once in a while, the parrot picks one of the lines from the buffer and replaces an element of the 'speech' list. */ -/mob/living/simple_animal/parrot/handle_automated_speech() - ..() - if(speech_buffer.len && prob(speech_shuffle_rate)) //shuffle out a phrase and add in a new one - if(speak.len) - speak.Remove(pick(speak)) - - speak.Add(pick(speech_buffer)) - - -/mob/living/simple_animal/parrot/handle_automated_movement() - if(!isturf(src.loc) || !(mobility_flags & MOBILITY_MOVE) || buckled) - return //If it can't move, dont let it move. (The buckled check probably isn't necessary thanks to canmove) - - if(client && stat == CONSCIOUS && parrot_state != icon_living) - icon_state = icon_living - -//-----SLEEPING - if(parrot_state == PARROT_PERCH) - if(parrot_perch && parrot_perch.loc != src.loc) //Make sure someone hasn't moved our perch on us - if(parrot_perch in view(src)) - parrot_state = PARROT_SWOOP | PARROT_RETURN - icon_state = icon_living - return - else - parrot_state = PARROT_WANDER - icon_state = icon_living - return - - parrot_sleep_dur-- - if(parrot_sleep_dur) //Zzz - return - - else - //This way we only call the stuff below once every [sleep_max] ticks. - parrot_sleep_dur = parrot_sleep_max - - //Cycle through message modes for the headset - if(speak.len) - var/list/newspeak = list() - - if(available_channels.len && src.ears) - for(var/possible_phrase in speak) - - //50/50 chance to not use the radio at all - var/useradio = 0 - if(prob(50)) - useradio = 1 - - if((possible_phrase[1] in GLOB.department_radio_prefixes) && (copytext_char(possible_phrase, 2, 3) in GLOB.department_radio_keys)) - possible_phrase = "[useradio?pick(available_channels):""][copytext_char(possible_phrase, 3)]" //crop out the channel prefix - else - possible_phrase = "[useradio?pick(available_channels):""][possible_phrase]" - - newspeak.Add(possible_phrase) - - else //If we have no headset or channels to use, dont try to use any! - for(var/possible_phrase in speak) - if((possible_phrase[1] in GLOB.department_radio_prefixes) && (copytext_char(possible_phrase, 2, 3) in GLOB.department_radio_keys)) - possible_phrase = copytext_char(possible_phrase, 3) //crop out the channel prefix - newspeak.Add(possible_phrase) - speak = newspeak - - //Search for item to steal - parrot_interest = search_for_item() - if(parrot_interest) - manual_emote("looks in [parrot_interest]'s direction and takes flight.") - parrot_state = PARROT_SWOOP | PARROT_STEAL - icon_state = icon_living - return - -//-----WANDERING - This is basically a 'I dont know what to do yet' state - else if(parrot_state == PARROT_WANDER) - //Stop movement, we'll set it later - SSmove_manager.stop_looping(src) - parrot_interest = null - - //Wander around aimlessly. This will help keep the loops from searches down - //and possibly move the mob into a new are in view of something they can use - if(prob(90)) - step(src, pick(GLOB.cardinals)) - return - - if(!held_item && !parrot_perch) //If we've got nothing to do.. look for something to do. - var/atom/movable/AM = search_for_perch_and_item() //This handles checking through lists so we know it's either a perch or stealable item - if(AM) - if(isitem(AM) || isliving(AM)) //If stealable item - parrot_interest = AM - manual_emote("turns and flies towards [parrot_interest].") - parrot_state = PARROT_SWOOP | PARROT_STEAL - return - else //Else it's a perch - parrot_perch = AM - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - return - - if(parrot_interest && (parrot_interest in view(src))) - parrot_state = PARROT_SWOOP | PARROT_STEAL - return - - if(parrot_perch && (parrot_perch in view(src))) - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - - else //Have an item but no perch? Find one! - parrot_perch = search_for_perch() - if(parrot_perch) - parrot_state = PARROT_SWOOP | PARROT_RETURN - return -//-----STEALING - else if(parrot_state == (PARROT_SWOOP | PARROT_STEAL)) - SSmove_manager.stop_looping(src) - if(!parrot_interest || held_item) - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - - if(!(parrot_interest in view(src))) - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - - if(Adjacent(parrot_interest)) - - if(isliving(parrot_interest)) - steal_from_mob() - - else //This should ensure that we only grab the item we want, and make sure it's not already collected on our perch - if(!parrot_perch || parrot_interest.loc != parrot_perch.loc) - held_item = parrot_interest - parrot_interest.forceMove(src) - visible_message(span_notice("[src] grabs [held_item]!"), span_notice("You grab [held_item]!"), span_hear("You hear the sounds of wings flapping furiously.")) - - parrot_interest = null - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - - SSmove_manager.move_to(src, parrot_interest, 1, parrot_speed) - if(isStuck()) - return - - return - -//-----RETURNING TO PERCH - else if(parrot_state == (PARROT_SWOOP | PARROT_RETURN)) - SSmove_manager.stop_looping(src) - if(!parrot_perch || !isturf(parrot_perch.loc)) //Make sure the perch exists and somehow isn't inside of something else. - parrot_perch = null - parrot_state = PARROT_WANDER - return - - if(Adjacent(parrot_perch)) - forceMove(parrot_perch.loc) - drop_held_item() - parrot_state = PARROT_PERCH - icon_state = icon_sit - return - - SSmove_manager.move_to(src, parrot_perch, 1, parrot_speed) - if(isStuck()) - return - - return - -//-----FLEEING - else if(parrot_state == (PARROT_SWOOP | PARROT_FLEE)) - SSmove_manager.stop_looping(src) - if(!parrot_interest || !isliving(parrot_interest)) //Sanity - parrot_state = PARROT_WANDER - - SSmove_manager.move_away(src, parrot_interest, 1, parrot_speed) - if(isStuck()) - return - - return - -//-----ATTACKING - else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK)) - - //If we're attacking a nothing, an object, a turf or a ghost for some stupid reason, switch to wander - if(!parrot_interest || !isliving(parrot_interest)) - parrot_interest = null - parrot_state = PARROT_WANDER - return - - var/mob/living/L = parrot_interest - if(melee_damage_upper == 0) - melee_damage_upper = parrot_damage_upper - set_combat_mode(TRUE) - - //If the mob is close enough to interact with - if(Adjacent(parrot_interest)) - - //If the mob we've been chasing/attacking dies or falls into crit, check for loot! - if(L.stat) - parrot_interest = null - if(!held_item) - held_item = steal_from_ground() - if(!held_item) - held_item = steal_from_mob() //Apparently it's possible for dead mobs to hang onto items in certain circumstances. - if(parrot_perch in view(src)) //If we have a home nearby, go to it, otherwise find a new home - parrot_state = PARROT_SWOOP | PARROT_RETURN - else - parrot_state = PARROT_WANDER - return - - attack_verb_continuous = pick("claws at", "chomps") - attack_verb_simple = pick("claw at", "chomp") - L.attack_animal(src)//Time for the hurt to begin! - //Otherwise, fly towards the mob! - else - SSmove_manager.move_to(src, parrot_interest, 1, parrot_speed) - if(isStuck()) - return - - return -//-----STATE MISHAP - else //This should not happen. If it does lets reset everything and try again - SSmove_manager.stop_looping(src) - parrot_interest = null - parrot_perch = null - drop_held_item() - parrot_state = PARROT_WANDER - return - -/* - * Procs - */ - -/mob/living/simple_animal/parrot/proc/isStuck() - //Check to see if the parrot is stuck due to things like windows or doors or windowdoors - if(parrot_lastmove) - if(parrot_lastmove == src.loc) - if(parrot_stuck_threshold >= ++parrot_stuck) //If it has been stuck for a while, go back to wander. - parrot_state = PARROT_WANDER - parrot_stuck = 0 - parrot_lastmove = null - return TRUE - else - parrot_lastmove = null - else - parrot_lastmove = src.loc - return FALSE - -/mob/living/simple_animal/parrot/proc/search_for_item() - var/item - for(var/atom/movable/AM in view(src)) - //Skip items we already stole or are wearing or are too big - if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src) - continue - if(isitem(AM)) - var/obj/item/I = AM - if(I.w_class < WEIGHT_CLASS_SMALL) - item = I - else if(iscarbon(AM)) - var/mob/living/carbon/C = AM - for(var/obj/item/I in C.held_items) - if(I.w_class <= WEIGHT_CLASS_SMALL) - item = I - break - if(item) - if(!length(get_path_to(src, item))) // WHY DO WE DISREGARD THE PATH AHHHHHH - item = null - continue - return item - -/mob/living/simple_animal/parrot/proc/search_for_perch() - for(var/obj/O in view(src)) - for(var/path in desired_perches) - if(istype(O, path)) - return O - return null - -//This proc was made to save on doing two 'in view' loops seperatly -/mob/living/simple_animal/parrot/proc/search_for_perch_and_item() - for(var/atom/movable/AM in view(src)) - for(var/perch_path in desired_perches) - if(istype(AM, perch_path)) - return AM - - //Skip items we already stole or are wearing or are too big - if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src) - continue - - if(isitem(AM)) - var/obj/item/I = AM - if(I.w_class <= WEIGHT_CLASS_SMALL) - return I - - if(iscarbon(AM)) - var/mob/living/carbon/C = AM - for(var/obj/item/I in C.held_items) - if(I.w_class <= WEIGHT_CLASS_SMALL) - return C - return null - - -/* - * Verbs - These are actually procs, but can be used as verbs by player-controlled parrots. - */ -/mob/living/simple_animal/parrot/proc/steal_from_ground() - set name = "Steal from ground" - set category = "Parrot" - set desc = "Grabs a nearby item." - - if(stat) - return -1 - - if(held_item) - to_chat(src, span_warning("You are already holding [held_item]!")) - return 1 - - for(var/obj/item/I in view(1,src)) - //Make sure we're not already holding it and it's small enough - if(I.loc != src && I.w_class <= WEIGHT_CLASS_SMALL) - - //If we have a perch and the item is sitting on it, continue - if(!client && parrot_perch && I.loc == parrot_perch.loc) - continue - - held_item = I - I.forceMove(src) - visible_message(span_notice("[src] grabs [held_item]!"), span_notice("You grab [held_item]!"), span_hear("You hear the sounds of wings flapping furiously.")) - return held_item - - to_chat(src, span_warning("There is nothing of interest to take!")) - return 0 - -/mob/living/simple_animal/parrot/proc/steal_from_mob() - set name = "Steal from mob" - set category = "Parrot" - set desc = "Steals an item right out of a person's hand!" - - if(stat) - return -1 - - if(held_item) - to_chat(src, span_warning("You are already holding [held_item]!")) - return 1 - - var/obj/item/stolen_item = null - - for(var/mob/living/carbon/C in view(1,src)) - for(var/obj/item/I in C.held_items) - if(I.w_class <= WEIGHT_CLASS_SMALL) - stolen_item = I - break - - if(stolen_item) - C.transferItemToLoc(stolen_item, src, TRUE) - held_item = stolen_item - visible_message(span_notice("[src] grabs [held_item] out of [C]'s hand!"), span_notice("You snag [held_item] out of [C]'s hand!"), span_hear("You hear the sounds of wings flapping furiously.")) - return held_item - - to_chat(src, span_warning("There is nothing of interest to take!")) - return 0 - -/mob/living/simple_animal/parrot/verb/drop_held_item_player() - set name = "Drop held item" - set category = "Parrot" - set desc = "Drop the item you're holding." - - if(stat) - return - - src.drop_held_item() - - return - -/mob/living/simple_animal/parrot/proc/drop_held_item(drop_gently = 1) - set name = "Drop held item" - set category = "Parrot" - set desc = "Drop the item you're holding." - - if(stat) - return -1 - - if(!held_item) - if(src == usr) //So that other mobs won't make this message appear when they're bludgeoning you. - to_chat(src, span_warning("You have nothing to drop!")) - return 0 - - -//parrots will eat crackers instead of dropping them - if(istype(held_item, /obj/item/food/cracker) && (drop_gently)) - qdel(held_item) - held_item = null - if(health < maxHealth) - adjustBruteLoss(-10) - manual_emote("[src] eagerly downs the cracker.") - return 1 - - - if(!drop_gently) - if(isgrenade(held_item)) - var/obj/item/grenade/G = held_item - G.forceMove(drop_location()) - G.detonate() - to_chat(src, span_danger("You let go of [held_item]!")) - held_item = null - return 1 - - to_chat(src, span_notice("You drop [held_item].")) - - held_item.forceMove(drop_location()) - held_item = null - return 1 - -/mob/living/simple_animal/parrot/proc/perch_player() - set name = "Sit" - set category = "Parrot" - set desc = "Sit on a nice comfy perch." - - if(stat || !client) - return - - if(icon_state == icon_living) - for(var/atom/movable/AM in view(src,1)) - for(var/perch_path in desired_perches) - if(istype(AM, perch_path)) - src.forceMove(AM.loc) - icon_state = icon_sit - parrot_state = PARROT_PERCH - return - to_chat(src, span_warning("There is no perch nearby to sit on!")) - return - -/mob/living/simple_animal/parrot/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - . = ..() - if(. && !stat && client && parrot_state == PARROT_PERCH) - parrot_state = PARROT_WANDER - icon_state = icon_living - pixel_x = initial(pixel_x) - pixel_y = initial(pixel_y) - -/mob/living/simple_animal/parrot/proc/perch_mob_player() - set name = "Sit on Human's Shoulder" - set category = "Parrot" - set desc = "Sit on a nice comfy human being!" - - if(stat || !client) - return - - if(!buckled) - for(var/mob/living/carbon/human/H in view(src,1)) - if(H.has_buckled_mobs() && H.buckled_mobs.len >= H.max_buckled_mobs) //Already has a parrot, or is being eaten by a slime - continue - perch_on_human(H) - return - to_chat(src, span_warning("There is nobody nearby that you can sit on!")) - else - icon_state = icon_living - parrot_state = PARROT_WANDER - if(buckled) - to_chat(src, span_notice("You are no longer sitting on [buckled]'s shoulder.")) - buckled.unbuckle_mob(src, TRUE) - buckled = null - pixel_x = initial(pixel_x) - pixel_y = initial(pixel_y) - - - -/mob/living/simple_animal/parrot/proc/perch_on_human(mob/living/carbon/human/H) - if(!H) - return - forceMove(get_turf(H)) - if(H.buckle_mob(src, TRUE)) - pixel_y = 9 - pixel_x = pick(-8,8) //pick left or right shoulder - icon_state = icon_sit - parrot_state = PARROT_PERCH - to_chat(src, span_notice("You sit on [H]'s shoulder.")) - - -/mob/living/simple_animal/parrot/proc/toggle_mode() - set name = "Toggle mode" - set category = "Parrot" - set desc = "Time to bear those claws!" - - if(stat || !client) - return - - if((istate & ISTATE_HARM)) - melee_damage_upper = 0 - set_combat_mode(FALSE) - else - melee_damage_upper = parrot_damage_upper - set_combat_mode(TRUE) - to_chat(src, span_notice("You will now [(istate & ISTATE_HARM) ? "Harm" : "Help"] others.")) - return - -/mob/living/simple_animal/parrot/natural - spawn_headset = FALSE -/* - * Sub-types - */ -/mob/living/simple_animal/parrot/poly - name = "Poly" - desc = "Poly the Parrot. An expert on quantum cracker theory." - speak = list("Poly wanna cracker!", ":e Check the crystal, you chucklefucks!",":e Wire the solars, you lazy bums!",":e WHO TOOK THE DAMN MODSUITS?",":e OH GOD ITS ABOUT TO DELAMINATE CALL THE SHUTTLE") - gold_core_spawnable = NO_SPAWN - speak_chance = 3 - var/memory_saved = FALSE - var/rounds_survived = 0 - var/longest_survival = 0 - var/longest_deathstreak = 0 - -/mob/living/simple_animal/parrot/poly/Initialize(mapload) - ears = new /obj/item/radio/headset/headset_eng(src) - available_channels = list(":e") - Read_Memory() - if(rounds_survived == longest_survival) - speak += pick("...[longest_survival].", "The things I've seen!", "I have lived many lives!", "What are you before me?") - desc += " Old as sin, and just as loud. Claimed to be [rounds_survived]." - speak_chance = 20 //His hubris has made him more annoying/easier to justify killing - add_atom_colour("#EEEE22", FIXED_COLOUR_PRIORITY) - else if(rounds_survived == longest_deathstreak) - speak += pick("What are you waiting for!", "Violence breeds violence!", "Blood! Blood!", "Strike me down if you dare!") - desc += " The squawks of [-rounds_survived] dead parrots ring out in your ears..." - add_atom_colour("#BB7777", FIXED_COLOUR_PRIORITY) - else if(rounds_survived > 0) - speak += pick("...again?", "No, It was over!", "Let me out!", "It never ends!") - desc += " Over [rounds_survived] shifts without a \"terrible\" \"accident\"!" - else - speak += pick("...alive?", "This isn't parrot heaven!", "I live, I die, I live again!", "The void fades!") - - . = ..() - -/mob/living/simple_animal/parrot/poly/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved) - Write_Memory(FALSE) - memory_saved = TRUE - ..() - -/mob/living/simple_animal/parrot/poly/death(gibbed) - if(!memory_saved) - Write_Memory(TRUE) - if(rounds_survived == longest_survival || rounds_survived == longest_deathstreak || prob(0.666)) - var/mob/living/simple_animal/parrot/poly/ghost/G = new(loc) - if(mind) - mind.transfer_to(G) - else - G.key = key - ..(gibbed) - -/mob/living/simple_animal/parrot/poly/proc/Read_Memory() - if(fexists("data/npc_saves/Poly.sav")) //legacy compatability to convert old format to new - var/savefile/S = new /savefile("data/npc_saves/Poly.sav") - S["phrases"] >> speech_buffer - S["roundssurvived"] >> rounds_survived - S["longestsurvival"] >> longest_survival - S["longestdeathstreak"] >> longest_deathstreak - fdel("data/npc_saves/Poly.sav") - else - var/json_file = file("data/npc_saves/Poly.json") - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - speech_buffer = json["phrases"] - rounds_survived = json["roundssurvived"] - longest_survival = json["longestsurvival"] - longest_deathstreak = json["longestdeathstreak"] - if(!islist(speech_buffer)) - speech_buffer = list() - -/mob/living/simple_animal/parrot/poly/Write_Memory(dead, gibbed) - . = ..() - if(!.) - return - var/json_file = file("data/npc_saves/Poly.json") - var/list/file_data = list() - if(islist(speech_buffer)) - file_data["phrases"] = speech_buffer - if(dead) - file_data["roundssurvived"] = min(rounds_survived - 1, 0) - file_data["longestsurvival"] = longest_survival - if(rounds_survived - 1 < longest_deathstreak) - file_data["longestdeathstreak"] = rounds_survived - 1 - else - file_data["longestdeathstreak"] = longest_deathstreak - else - file_data["roundssurvived"] = max(rounds_survived, 0) + 1 - if(rounds_survived + 1 > longest_survival) - file_data["longestsurvival"] = rounds_survived + 1 - else - file_data["longestsurvival"] = longest_survival - file_data["longestdeathstreak"] = longest_deathstreak - fdel(json_file) - WRITE_FILE(json_file, json_encode(file_data)) - -/mob/living/simple_animal/parrot/poly/ghost - name = "The Ghost of Poly" - desc = "Doomed to squawk the Earth." - color = "#FFFFFF77" - speak_chance = 20 - status_flags = GODMODE - sentience_type = SENTIENCE_BOSS //This is so players can't mindswap into ghost poly to become a literal god - incorporeal_move = INCORPOREAL_MOVE_BASIC - butcher_results = list(/obj/item/ectoplasm = 1) - -/mob/living/simple_animal/parrot/poly/ghost/Initialize(mapload) - memory_saved = TRUE //At this point nothing is saved - . = ..() - -/mob/living/simple_animal/parrot/poly/ghost/handle_automated_speech() - if(ismob(loc)) - return - ..() - -/mob/living/simple_animal/parrot/poly/ghost/handle_automated_movement() - if(isliving(parrot_interest)) - if(!ishuman(parrot_interest)) - parrot_interest = null - else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK) && Adjacent(parrot_interest)) - SSmove_manager.move_to(src, parrot_interest, 0, parrot_speed) - Possess(parrot_interest) - ..() - -/mob/living/simple_animal/parrot/poly/ghost/proc/Possess(mob/living/carbon/human/H) - if(!ishuman(H)) - return - var/datum/disease/parrot_possession/P = new - P.parrot = src - forceMove(H) - H.ForceContractDisease(P, FALSE) - parrot_interest = null - H.visible_message(span_danger("[src] dive bombs into [H]'s chest and vanishes!"), span_userdanger("[src] dive bombs into your chest, vanishing! This can't be good!")) - -#undef PARROT_PERCH -#undef PARROT_SWOOP -#undef PARROT_WANDER -#undef PARROT_STEAL -#undef PARROT_ATTACK -#undef PARROT_RETURN -#undef PARROT_FLEE diff --git a/code/modules/mob/living/simple_animal/revenant.dm b/code/modules/mob/living/simple_animal/revenant.dm deleted file mode 100644 index 97ec1d93a695..000000000000 --- a/code/modules/mob/living/simple_animal/revenant.dm +++ /dev/null @@ -1,544 +0,0 @@ -//Revenants: based off of wraiths from Goon -//"Ghosts" that are invisible and move like ghosts, cannot take damage while invisible -//Can hear deadchat, but are NOT normal ghosts and do NOT have x-ray vision -//Admin-spawn or random event - -/mob/living/simple_animal/revenant - name = "revenant" - desc = "A malevolent spirit." - icon = 'icons/mob/simple/mob.dmi' - icon_state = "revenant_idle" - var/icon_idle = "revenant_idle" - var/icon_reveal = "revenant_revealed" - var/icon_stun = "revenant_stun" - var/icon_drain = "revenant_draining" - var/stasis = FALSE - mob_biotypes = MOB_SPIRIT - incorporeal_move = INCORPOREAL_MOVE_JAUNT - invisibility = INVISIBILITY_REVENANT - health = INFINITY //Revenants don't use health, they use essence instead - maxHealth = INFINITY - plane = GHOST_PLANE - healable = FALSE - sight = SEE_SELF - throwforce = 0 - - // Going for faint purple spoopy ghost - lighting_cutoff_red = 20 - lighting_cutoff_green = 15 - lighting_cutoff_blue = 35 - response_help_continuous = "passes through" - response_help_simple = "pass through" - response_disarm_continuous = "swings through" - response_disarm_simple = "swing through" - response_harm_continuous = "punches through" - response_harm_simple = "punch through" - unsuitable_atmos_damage = 0 - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) //I don't know how you'd apply those, but revenants no-sell them anyway. - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxbodytemp = INFINITY - harm_intent_damage = 0 - friendly_verb_continuous = "touches" - friendly_verb_simple = "touch" - status_flags = 0 - wander = FALSE - density = FALSE - move_resist = MOVE_FORCE_OVERPOWERING - mob_size = MOB_SIZE_TINY - pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - speed = 1 - unique_name = TRUE - hud_possible = list(ANTAG_HUD) - hud_type = /datum/hud/revenant - - var/essence = 75 //The resource, and health, of revenants. - var/essence_regen_cap = 75 //The regeneration cap of essence (go figure); regenerates every Life() tick up to this amount. - var/essence_regenerating = TRUE //If the revenant regenerates essence or not - var/essence_regen_amount = 2.5 //How much essence regenerates per second - var/essence_accumulated = 0 //How much essence the revenant has stolen - var/essence_excess = 0 //How much stolen essence avilable for unlocks - var/revealed = FALSE //If the revenant can take damage from normal sources. - var/unreveal_time = 0 //How long the revenant is revealed for, is about 2 seconds times this var. - var/unstun_time = 0 //How long the revenant is stunned for, is about 2 seconds times this var. - var/inhibited = FALSE //If the revenant's abilities are blocked by a chaplain's power. - var/essence_drained = 0 //How much essence the revenant will drain from the corpse it's feasting on. - var/draining = FALSE //If the revenant is draining someone. - var/list/drained_mobs = list() //Cannot harvest the same mob twice - var/perfectsouls = 0 //How many perfect, regen-cap increasing souls the revenant has. //TODO, add objective for getting a perfect soul(s?) - var/generated_objectives_and_spells = FALSE - -/mob/living/simple_animal/revenant/Initialize(mapload) - . = ..() - AddElement(/datum/element/simple_flying) - add_traits(list(TRAIT_SPACEWALK, TRAIT_SIXTHSENSE, TRAIT_FREE_HYPERSPACE_MOVEMENT), INNATE_TRAIT) - - // Starting spells - - var/datum/action/cooldown/spell/list_target/telepathy/revenant/telepathy = new(src) - telepathy.Grant(src) - - // Starting spells that start locked - var/datum/action/cooldown/spell/aoe/revenant/overload/lights_go_zap = new(src) - lights_go_zap.Grant(src) - - var/datum/action/cooldown/spell/aoe/revenant/defile/windows_go_smash = new(src) - windows_go_smash.Grant(src) - - var/datum/action/cooldown/spell/aoe/revenant/blight/botany_go_mad = new(src) - botany_go_mad.Grant(src) - - var/datum/action/cooldown/spell/aoe/revenant/malfunction/shuttle_go_emag = new(src) - shuttle_go_emag.Grant(src) - - var/datum/action/cooldown/spell/aoe/revenant/haunt_object/toolbox_go_bonk = new(src) - toolbox_go_bonk.Grant(src) - - RegisterSignal(src, COMSIG_LIVING_BANED, PROC_REF(on_baned)) - random_revenant_name() - -/mob/living/simple_animal/revenant/can_perform_action(atom/movable/target, action_bitflags) - return FALSE - -/mob/living/simple_animal/revenant/proc/random_revenant_name() - var/built_name = "" - built_name += pick(strings(REVENANT_NAME_FILE, "spirit_type")) - built_name += " of " - built_name += pick(strings(REVENANT_NAME_FILE, "adverb")) - built_name += pick(strings(REVENANT_NAME_FILE, "theme")) - name = built_name - -/mob/living/simple_animal/revenant/Login() - . = ..() - if(!. || !client) - return FALSE - to_chat(src, span_deadsay("You are a revenant.")) - to_chat(src, "Your formerly mundane spirit has been infused with alien energies and empowered into a revenant.") - to_chat(src, "You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds.") - to_chat(src, "You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable.") - to_chat(src, "To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities.") - to_chat(src, "You do not remember anything of your past lives, nor will you remember anything about this one after your death.") - to_chat(src, "Be sure to read the wiki page to learn more.") - if(!generated_objectives_and_spells) - generated_objectives_and_spells = TRUE - mind.set_assigned_role(SSjob.GetJobType(/datum/job/revenant)) - mind.special_role = ROLE_REVENANT - SEND_SOUND(src, sound('sound/effects/ghost.ogg')) - mind.add_antag_datum(/datum/antagonist/revenant) - -//Life, Stat, Hud Updates, and Say -/mob/living/simple_animal/revenant/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if(stasis) - return - if(revealed && essence <= 0) - death() - if(unreveal_time && world.time >= unreveal_time) - unreveal_time = 0 - revealed = FALSE - incorporeal_move = INCORPOREAL_MOVE_JAUNT - invisibility = INVISIBILITY_REVENANT - to_chat(src, span_revenboldnotice("You are once more concealed.")) - if(unstun_time && world.time >= unstun_time) - unstun_time = 0 - notransform = FALSE - to_chat(src, span_revenboldnotice("You can move again!")) - if(essence_regenerating && !inhibited && essence < essence_regen_cap) //While inhibited, essence will not regenerate - essence = min(essence + (essence_regen_amount * seconds_per_tick), essence_regen_cap) - update_mob_action_buttons() //because we update something required by our spells in life, we need to update our buttons - update_spooky_icon() - update_health_hud() - ..() - -/mob/living/simple_animal/revenant/get_status_tab_items() - . = ..() - . += "Current Essence: [essence >= essence_regen_cap ? essence : "[essence] / [essence_regen_cap]"]E" - . += "Total Essence Stolen: [essence_accumulated]SE" - . += "Unused Stolen Essence: [essence_excess]SE" - . += "Perfect Souls Stolen: [perfectsouls]" - -/mob/living/simple_animal/revenant/update_health_hud() - if(hud_used) - var/essencecolor = "#8F48C6" - if(essence > essence_regen_cap) - essencecolor = "#9A5ACB" //oh boy you've got a lot of essence - else if(!essence) - essencecolor = "#1D2953" //oh jeez you're dying - hud_used.healths.maptext = MAPTEXT("
[essence]E
") - -/mob/living/simple_animal/revenant/med_hud_set_health() - return //we use no hud - -/mob/living/simple_animal/revenant/med_hud_set_status() - return //we use no hud - -/mob/living/simple_animal/revenant/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) - if(!message) - return - if(sanitize) - message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)) - src.log_talk(message, LOG_SAY) - var/rendered = span_deadsay("UNDEAD: [src] says, \"[message]\"") - for(var/mob/M in GLOB.mob_list) - if(isrevenant(M)) - to_chat(M, rendered) - else if(isobserver(M)) - var/link = FOLLOW_LINK(M, src) - to_chat(M, "[link] [rendered]") - return - - -//Immunities - -/mob/living/simple_animal/revenant/ex_act(severity, target) - return FALSE //Immune to the effects of explosions. - -/mob/living/simple_animal/revenant/blob_act(obj/structure/blob/B) - return //blah blah blobs aren't in tune with the spirit world, or something. - -/mob/living/simple_animal/revenant/singularity_act() - return //don't walk into the singularity expecting to find corpses, okay? - -/mob/living/simple_animal/revenant/narsie_act() - return //most humans will now be either bones or harvesters, but we're still un-alive. - -/mob/living/simple_animal/revenant/bullet_act() - if(!revealed || stasis) - return BULLET_ACT_FORCE_PIERCE - return ..() - -//damage, gibbing, and dying -/mob/living/simple_animal/revenant/proc/on_baned(obj/item/weapon, mob/living/user) - SIGNAL_HANDLER - visible_message(span_warning("[src] violently flinches!"), \ - span_revendanger("As [weapon] passes through you, you feel your essence draining away!")) - inhibited = TRUE - update_mob_action_buttons() - addtimer(CALLBACK(src, PROC_REF(reset_inhibit)), 3 SECONDS) - -/mob/living/simple_animal/revenant/proc/reset_inhibit() - inhibited = FALSE - update_mob_action_buttons() - -/mob/living/simple_animal/revenant/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - if(!forced && !revealed) - return FALSE - . = amount - essence = max(0, essence-amount) - if(updating_health) - update_health_hud() - if(!essence) - death() - -/mob/living/simple_animal/revenant/dust(just_ash, drop_items, force) - death() - -/mob/living/simple_animal/revenant/gib() - death() - -/mob/living/simple_animal/revenant/death() - if(!revealed || stasis) //Revenants cannot die if they aren't revealed //or are already dead - return - stasis = TRUE - to_chat(src, span_revendanger("NO! No... it's too late, you can feel your essence [pick("breaking apart", "drifting away")]...")) - notransform = TRUE - revealed = TRUE - invisibility = 0 - playsound(src, 'sound/effects/screech.ogg', 100, TRUE) - visible_message(span_warning("[src] lets out a waning screech as violet mist swirls around its dissolving body!")) - icon_state = "revenant_draining" - for(var/i = alpha, i > 0, i -= 10) - stoplag() - alpha = i - visible_message(span_danger("[src]'s body breaks apart into a fine pile of blue dust.")) - var/reforming_essence = essence_regen_cap //retain the gained essence capacity - var/obj/item/ectoplasm/revenant/R = new(get_turf(src)) - R.essence = max(reforming_essence - 15 * perfectsouls, 75) //minus any perfect souls - R.old_key = client.key //If the essence reforms, the old revenant is put back in the body - R.revenant = src - invisibility = INVISIBILITY_ABSTRACT - revealed = FALSE - ghostize(0)//Don't re-enter invisible corpse - - -//reveal, stun, icon updates, cast checks, and essence changing -/mob/living/simple_animal/revenant/proc/reveal(time) - if(!src) - return - if(time <= 0) - return - revealed = TRUE - invisibility = 0 - incorporeal_move = FALSE - if(!unreveal_time) - to_chat(src, span_revendanger("You have been revealed!")) - unreveal_time = world.time + time - else - to_chat(src, span_revenwarning("You have been revealed!")) - unreveal_time = unreveal_time + time - update_spooky_icon() - orbiting?.end_orbit(src) - -/mob/living/simple_animal/revenant/proc/stun(time) - if(!src) - return - if(time <= 0) - return - notransform = TRUE - if(!unstun_time) - to_chat(src, span_revendanger("You cannot move!")) - unstun_time = world.time + time - else - to_chat(src, span_revenwarning("You cannot move!")) - unstun_time = unstun_time + time - update_spooky_icon() - orbiting?.end_orbit(src) - -/mob/living/simple_animal/revenant/proc/update_spooky_icon() - if(revealed) - if(notransform) - if(draining) - icon_state = icon_drain - else - icon_state = icon_stun - else - icon_state = icon_reveal - else - icon_state = icon_idle - -/mob/living/simple_animal/revenant/proc/castcheck(essence_cost) - if(!src) - return - var/turf/T = get_turf(src) - if(isclosedturf(T)) - to_chat(src, span_revenwarning("You cannot use abilities from inside of a wall.")) - return FALSE - for(var/obj/O in T) - if(O.density && !O.CanPass(src, get_dir(T, src))) - to_chat(src, span_revenwarning("You cannot use abilities inside of a dense object.")) - return FALSE - if(inhibited) - to_chat(src, span_revenwarning("Your powers have been suppressed by nulling energy!")) - return FALSE - if(!change_essence_amount(essence_cost, TRUE)) - to_chat(src, span_revenwarning("You lack the essence to use that ability.")) - return FALSE - return TRUE - -/mob/living/simple_animal/revenant/proc/unlock(essence_cost) - if(essence_excess < essence_cost) - return FALSE - essence_excess -= essence_cost - update_mob_action_buttons() - return TRUE - -/mob/living/simple_animal/revenant/proc/change_essence_amount(essence_amt, silent = FALSE, source = null) - if(!src) - return - if(essence + essence_amt < 0) - return - essence = max(0, essence+essence_amt) - update_health_hud() - if(essence_amt > 0) - essence_accumulated = max(0, essence_accumulated+essence_amt) - essence_excess = max(0, essence_excess+essence_amt) - update_mob_action_buttons() - if(!silent) - if(essence_amt > 0) - to_chat(src, span_revennotice("Gained [essence_amt]E[source ? " from [source]":""].")) - else - to_chat(src, span_revenminor("Lost [essence_amt]E[source ? " from [source]":""].")) - return 1 - -/mob/living/simple_animal/revenant/proc/death_reset() - revealed = FALSE - unreveal_time = 0 - notransform = 0 - unstun_time = 0 - inhibited = FALSE - draining = FALSE - incorporeal_move = INCORPOREAL_MOVE_JAUNT - invisibility = INVISIBILITY_REVENANT - alpha=255 - stasis = FALSE - -/mob/living/simple_animal/revenant/orbit(atom/target) - setDir(SOUTH) // reset dir so the right directional sprites show up - return ..() - -/mob/living/simple_animal/revenant/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - if(!orbiting) // only needed when orbiting - return ..() - if(incorporeal_move_check(src)) - return ..() - - // back back back it up, the orbitee went somewhere revenant cannot - orbiting?.end_orbit(src) - abstract_move(old_loc) // gross but maybe orbit component will be able to check pre move in the future - -/mob/living/simple_animal/revenant/stop_orbit(datum/component/orbiter/orbits) - // reset the simple_flying animation - animate(src, pixel_y = 2, time = 1 SECONDS, loop = -1, flags = ANIMATION_RELATIVE) - animate(pixel_y = -2, time = 1 SECONDS, flags = ANIMATION_RELATIVE) - return ..() - -/// Incorporeal move check: blocked by holy-watered tiles and salt piles. -/mob/living/simple_animal/revenant/proc/incorporeal_move_check(atom/destination) - var/turf/open/floor/stepTurf = get_turf(destination) - if(stepTurf) - var/obj/effect/decal/cleanable/food/salt/salt = locate() in stepTurf - if(salt) - to_chat(src, span_warning("[salt] bars your passage!")) - reveal(20) - stun(20) - return - if(stepTurf.turf_flags & NOJAUNT) - to_chat(src, span_warning("Some strange aura is blocking the way.")) - return - if(locate(/obj/effect/blessing) in stepTurf) - to_chat(src, span_warning("Holy energies block your path!")) - return - return TRUE - -//reforming -/obj/item/ectoplasm/revenant - name = "glimmering residue" - desc = "A pile of fine blue dust. Small tendrils of violet mist swirl around it." - icon = 'icons/effects/effects.dmi' - icon_state = "revenantEctoplasm" - w_class = WEIGHT_CLASS_SMALL - var/essence = 75 //the maximum essence of the reforming revenant - var/reforming = TRUE - var/inert = FALSE - var/old_key //key of the previous revenant, will have first pick on reform. - var/mob/living/simple_animal/revenant/revenant - -/obj/item/ectoplasm/revenant/Initialize(mapload) - . = ..() - addtimer(CALLBACK(src, PROC_REF(try_reform)), 600) - -/obj/item/ectoplasm/revenant/proc/scatter() - qdel(src) - -/obj/item/ectoplasm/revenant/proc/try_reform() - if(reforming) - reforming = FALSE - reform() - else - inert = TRUE - visible_message(span_warning("[src] settles down and seems lifeless.")) - -/obj/item/ectoplasm/revenant/attack_self(mob/user) - if(!reforming || inert) - return ..() - user.visible_message(span_notice("[user] scatters [src] in all directions."), \ - span_notice("You scatter [src] across the area. The particles slowly fade away.")) - user.dropItemToGround(src) - scatter() - -/obj/item/ectoplasm/revenant/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - ..() - if(inert) - return - visible_message(span_notice("[src] breaks into particles upon impact, which fade away to nothingness.")) - scatter() - -/obj/item/ectoplasm/revenant/examine(mob/user) - . = ..() - if(inert) - . += span_revennotice("It seems inert.") - else if(reforming) - . += span_revenwarning("It is shifting and distorted. It would be wise to destroy this.") - -/obj/item/ectoplasm/revenant/proc/reform() - if(QDELETED(src) || QDELETED(revenant) || inert) - return - var/key_of_revenant - message_admins("Revenant ectoplasm was left undestroyed for 1 minute and is reforming into a new revenant.") - forceMove(drop_location()) //In case it's in a backpack or someone's hand - revenant.forceMove(loc) - if(old_key) - for(var/mob/M in GLOB.dead_mob_list) - if(M.client && M.client.key == old_key) //Only recreates the mob if the mob the client is in is dead - key_of_revenant = old_key - break - if(!key_of_revenant) - message_admins("The new revenant's old client either could not be found or is in a new, living mob - grabbing a random candidate instead...") - var/list/candidates = poll_candidates_for_mob("Do you want to be [revenant.name] (reforming)?", ROLE_REVENANT, ROLE_REVENANT, 5 SECONDS, revenant) - if(!LAZYLEN(candidates)) - qdel(revenant) - message_admins("No candidates were found for the new revenant. Oh well!") - inert = TRUE - visible_message(span_revenwarning("[src] settles down and seems lifeless.")) - return - var/mob/dead/observer/C = pick(candidates) - key_of_revenant = C.key - if(!key_of_revenant) - qdel(revenant) - message_admins("No ckey was found for the new revenant. Oh well!") - inert = TRUE - visible_message(span_revenwarning("[src] settles down and seems lifeless.")) - return - - message_admins("[key_of_revenant] has been [old_key == key_of_revenant ? "re":""]made into a revenant by reforming ectoplasm.") - revenant.log_message("was [old_key == key_of_revenant ? "re":""]made as a revenant by reforming ectoplasm.", LOG_GAME) - visible_message(span_revenboldnotice("[src] suddenly rises into the air before fading away.")) - - revenant.essence = essence - revenant.essence_regen_cap = essence - revenant.death_reset() - revenant.key = key_of_revenant - revenant = null - qdel(src) - -/obj/item/ectoplasm/revenant/suicide_act(mob/living/user) - user.visible_message(span_suicide("[user] is inhaling [src]! It looks like [user.p_theyre()] trying to visit the shadow realm!")) - scatter() - return OXYLOSS - -/obj/item/ectoplasm/revenant/Destroy() - if(!QDELETED(revenant)) - qdel(revenant) - return ..() - -//objectives -/datum/objective/revenant - var/targetAmount = 100 - -/datum/objective/revenant/New() - targetAmount = rand(350,600) - explanation_text = "Absorb [targetAmount] points of essence from humans." - ..() - -/datum/objective/revenant/check_completion() - if(!isrevenant(owner.current)) - return FALSE - var/mob/living/simple_animal/revenant/R = owner.current - if(!R || R.stat == DEAD) - return FALSE - var/essence_stolen = R.essence_accumulated - if(essence_stolen < targetAmount) - return FALSE - return TRUE - -/datum/objective/revenant_fluff - -/datum/objective/revenant_fluff/New() - var/list/explanation_texts = list( - "Assist and exacerbate existing threats at critical moments.", \ - "Impersonate or be worshipped as a god.", \ - "Cause as much chaos and anger as you can without being killed.", \ - "Damage and render as much of the station rusted and unusable as possible.", \ - "Disable and cause malfunctions in as many machines as possible.", \ - "Ensure that any holy weapons are rendered unusable.", \ - "Heed and obey the requests of the dead, provided that carrying them out wouldn't be too inconvenient or self-destructive.", \ - "Make the crew as miserable as possible.", \ - "Make the clown as miserable as possible.", \ - "Make the captain as miserable as possible.", \ - "Prevent the use of energy weapons where possible.", - ) - explanation_text = pick(explanation_texts) - ..() - -/datum/objective/revenant_fluff/check_completion() - return TRUE - diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm deleted file mode 100644 index 87e6f536cb60..000000000000 --- a/code/modules/mob/living/simple_animal/shade.dm +++ /dev/null @@ -1,72 +0,0 @@ -/mob/living/simple_animal/shade - name = "Shade" - real_name = "Shade" - desc = "A bound spirit." - gender = PLURAL - icon = 'icons/mob/nonhuman-player/cult.dmi' - icon_state = "shade_cult" - icon_living = "shade_cult" - mob_biotypes = MOB_SPIRIT - maxHealth = 40 - health = 40 - healable = 0 - speak_emote = list("hisses") - emote_hear = list("wails.","screeches.") - response_help_continuous = "puts their hand through" - response_help_simple = "put your hand through" - response_disarm_continuous = "flails at" - response_disarm_simple = "flail at" - response_harm_continuous = "punches" - response_harm_simple = "punch" - speak_chance = 1 - melee_damage_lower = 5 - melee_damage_upper = 12 - attack_verb_continuous = "metaphysically strikes" - attack_verb_simple = "metaphysically strike" - minbodytemp = 0 - maxbodytemp = INFINITY - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - speed = -1 //they don't have to lug a body made of runed metal around - stop_automated_movement = 1 - faction = list(FACTION_CULT) - status_flags = CANPUSH - loot = list(/obj/item/ectoplasm) - del_on_death = TRUE - initial_language_holder = /datum/language_holder/construct - -/mob/living/simple_animal/shade/Initialize(mapload) - . = ..() - AddElement(/datum/element/simple_flying) - add_traits(list(TRAIT_HEALS_FROM_CULT_PYLONS, TRAIT_SPACEWALK, TRAIT_VENTCRAWLER_ALWAYS), INNATE_TRAIT) - -/mob/living/simple_animal/shade/death() - if(death_message == initial(death_message)) - death_message = "lets out a contented sigh as [p_their()] form unwinds." - ..() - -/mob/living/simple_animal/shade/can_suicide() - if(istype(loc, /obj/item/soulstone)) //do not suicide inside the soulstone - return FALSE - return ..() - -/mob/living/simple_animal/shade/attack_animal(mob/living/simple_animal/user, list/modifiers) - if(isconstruct(user)) - var/mob/living/simple_animal/hostile/construct/doll = user - if(!doll.can_repair) - return - if(health < maxHealth) - adjustHealth(-25) - Beam(user,icon_state="sendbeam", time = 4) - user.visible_message(span_danger("[user] heals \the [src]."), \ - span_cult("You heal [src], leaving [src] at [health]/[maxHealth] health.")) - else - to_chat(user, span_cult("You cannot heal [src], as [p_theyre()] unharmed!")) - else if(src != user) - return ..() - -/mob/living/simple_animal/shade/attackby(obj/item/item, mob/user, params) //Marker -Agouri - if(istype(item, /obj/item/soulstone)) - var/obj/item/soulstone/stone = item - stone.capture_shade(src, user) - else - . = ..() diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 09e3fe1f2546..bbe1275d90a7 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -136,10 +136,6 @@ ///Played when someone punches the creature. var/attacked_sound = SFX_PUNCH - ///If the creature has, and can use, hands. - var/dextrous = FALSE - var/dextrous_hud_type = /datum/hud/dextrous - ///The Status of our AI, can be set to AI_ON (On, usual processing), AI_IDLE (Will not process, but will return to AI_ON if an enemy comes near), AI_OFF (Off, Not processing ever), AI_Z_OFF (Temporarily off due to nonpresence of players). var/AIStatus = AI_ON ///once we have become sentient, we can never go back. @@ -180,9 +176,6 @@ if(!loc) stack_trace("Simple animal being instantiated in nullspace") update_simplemob_varspeed() - if(dextrous) - AddComponent(/datum/component/personal_crafting) - add_traits(list(TRAIT_ADVANCEDTOOLUSER, TRAIT_CAN_STRIP), ROUNDSTART_TRAIT) ADD_TRAIT(src, TRAIT_NOFIRE_SPREAD, ROUNDSTART_TRAIT) if(length(weather_immunities)) add_traits(weather_immunities, ROUNDSTART_TRAIT) @@ -448,23 +441,19 @@ /mob/living/simple_animal/death(gibbed) drop_loot() - if(dextrous) - drop_all_held_items() - if(del_on_death) ..() //Prevent infinite loops if the mob Destroy() is overridden in such //a manner as to cause a call to death() again //Pain del_on_death = FALSE qdel(src) - return - - health = 0 - icon_state = icon_dead - if(flip_on_death) - transform = transform.Turn(180) - //ADD_TRAIT(src, TRAIT_UNDENSE, BASIC_MOB_DEATH_TRAIT) - return ..() + else + health = 0 + icon_state = icon_dead + if(flip_on_death) + transform = transform.Turn(180) + ADD_TRAIT(src, TRAIT_UNDENSE, BASIC_MOB_DEATH_TRAIT) + ..() /mob/living/simple_animal/proc/CanAttack(atom/the_target) if(see_invisible < the_target.invisibility) @@ -488,7 +477,7 @@ if(!.) return icon_state = icon_living - set_density(initial(density)) + REMOVE_TRAIT(src, TRAIT_UNDENSE, BASIC_MOB_DEATH_TRAIT) /mob/living/simple_animal/proc/make_babies() // <3 <3 <3 if(gender != FEMALE || stat || next_scan_time > world.time || !childtype || !animal_species || !SSticker.IsRoundInProgress()) @@ -509,7 +498,7 @@ else if(!is_child && M.gender == MALE && !(M.flags_1 & HOLOGRAM_1)) //Better safe than sorry ;_; partner = M - else if(isliving(M) && !faction_check_mob(M)) //shyness check. we're not shy in front of things that share a faction with us. + else if(isliving(M) && !faction_check_atom(M)) //shyness check. we're not shy in front of things that share a faction with us. return //we never mate when not alone, so just abort early if(alone && partner && children < 3) @@ -558,49 +547,12 @@ /mob/living/simple_animal/get_idcard(hand_first) return (..() || access_card) -/mob/living/simple_animal/can_hold_items(obj/item/I) - return dextrous && ..() - -/mob/living/simple_animal/activate_hand(selhand) - if(!dextrous) - return ..() - if(!selhand) - selhand = (active_hand_index % held_items.len)+1 - if(istext(selhand)) - selhand = lowertext(selhand) - if(selhand == "right" || selhand == "r") - selhand = 2 - if(selhand == "left" || selhand == "l") - selhand = 1 - if(selhand != active_hand_index) - swap_hand(selhand) - else - mode() - -/mob/living/simple_animal/perform_hand_swap(hand_index) - . = ..() - if(!.) - return - if(!dextrous) - return - if(!hand_index) - hand_index = (active_hand_index % held_items.len)+1 - var/oindex = active_hand_index - active_hand_index = hand_index - if(hud_used) - var/atom/movable/screen/inventory/hand/H - H = hud_used.hand_slots["[hand_index]"] - if(H) - H.update_appearance() - H = hud_used.hand_slots["[oindex]"] - if(H) - H.update_appearance() - /mob/living/simple_animal/put_in_hands(obj/item/I, del_on_fail = FALSE, merge_stacks = TRUE, ignore_animation = TRUE) . = ..() update_held_items() /mob/living/simple_animal/update_held_items() + . = ..() if(!client || !hud_used || hud_used.hud_version == HUD_STYLE_NOHUD) return var/turf/our_turf = get_turf(src) diff --git a/code/modules/mob/living/simple_animal/slime/life.dm b/code/modules/mob/living/simple_animal/slime/life.dm index ca578f949dfe..6f9d42fed1a8 100644 --- a/code/modules/mob/living/simple_animal/slime/life.dm +++ b/code/modules/mob/living/simple_animal/slime/life.dm @@ -1,5 +1,5 @@ /mob/living/simple_animal/slime/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if (notransform) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return . = ..() if(!.) diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm index a84f4d22d0b2..c03105b7e62d 100644 --- a/code/modules/mob/living/simple_animal/slime/slime.dm +++ b/code/modules/mob/living/simple_animal/slime/slime.dm @@ -273,15 +273,6 @@ amount = -abs(amount) return ..() //Heals them -/mob/living/simple_animal/slime/bullet_act(obj/projectile/Proj, def_zone, piercing_hit = FALSE) - attacked += 10 - if((Proj.damage_type == BURN)) - adjustBruteLoss(-abs(Proj.damage)) //fire projectiles heals slimes. - Proj.on_hit(src, 0, piercing_hit) - else - . = ..(Proj) - . = . || BULLET_ACT_BLOCK - /mob/living/simple_animal/slime/emp_act(severity) . = ..() if(. & EMP_PROTECT_SELF) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 71e675ee5b97..6ae8c88d2acd 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -894,10 +894,45 @@ /// Performs the actual ritual of swapping hands, such as setting the held index variables /mob/proc/perform_hand_swap(held_index) PROTECTED_PROC(TRUE) + if (!HAS_TRAIT(src, TRAIT_CAN_HOLD_ITEMS)) + return FALSE + + if(!held_index) + held_index = (active_hand_index % held_items.len) + 1 + + if(!isnum(held_index)) + CRASH("You passed [held_index] into swap_hand instead of a number. WTF man") + + var/previous_index = active_hand_index + active_hand_index = held_index + if(hud_used) + var/atom/movable/screen/inventory/hand/held_location + held_location = hud_used.hand_slots["[previous_index]"] + if(!isnull(held_location)) + held_location.update_appearance() + held_location = hud_used.hand_slots["[held_index]"] + if(!isnull(held_location)) + held_location.update_appearance() return TRUE -/mob/proc/activate_hand(selhand) - return +/mob/proc/activate_hand(selected_hand) + if (!HAS_TRAIT(src, TRAIT_CAN_HOLD_ITEMS)) + return + + if(!selected_hand) + selected_hand = (active_hand_index % held_items.len)+1 + + if(istext(selected_hand)) + selected_hand = lowertext(selected_hand) + if(selected_hand == "right" || selected_hand == "r") + selected_hand = 2 + if(selected_hand == "left" || selected_hand == "l") + selected_hand = 1 + + if(selected_hand != active_hand_index) + swap_hand(selected_hand) + else + mode() /mob/proc/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) //For sec bot threat assessment return 0 @@ -1028,21 +1063,7 @@ ///Can this mob use storage /mob/proc/canUseStorage() return FALSE -/** - * Check if the other mob has any factions the same as us - * - * If exact match is set, then all our factions must match exactly - */ -/mob/proc/faction_check_mob(mob/target, exact_match) - if(exact_match) //if we need an exact match, we need to do some bullfuckery. - var/list/faction_src = faction.Copy() - var/list/faction_target = target.faction.Copy() - if(!("[REF(src)]" in faction_target)) //if they don't have our ref faction, remove it from our factions list. - faction_src -= "[REF(src)]" //if we don't do this, we'll never have an exact match. - if(!("[REF(target)]" in faction_src)) - faction_target -= "[REF(target)]" //same thing here. - return faction_check(faction_src, faction_target, TRUE) - return faction_check(faction, target.faction, FALSE) + /* * Compare two lists of factions, returning true if any match * diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 35b780f3f587..32c44d320b59 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -20,6 +20,8 @@ // we never want to hide a turf because it's not lit // We can rely on the lighting plane to handle that for us see_in_dark = 1e6 + // A list of factions that this mob is currently in, for hostile mob targeting, amongst other things + faction = list(FACTION_NEUTRAL) /// The current client inhabiting this mob. Managed by login/logout /// This exists so we can do cleanup in logout for occasions where a client was transfere rather then destroyed /// We need to do this because the mob on logout never actually has a reference to client @@ -85,14 +87,6 @@ /// Tick time the mob can next move var/next_move = null - /** - * Magic var that stops you moving and interacting with anything - * - * Set when you're being turned into something else and also used in a bunch of places - * it probably shouldn't really be - */ - var/notransform = null //Carbon - /// What is the mobs real name (name is overridden for disguises etc) var/real_name = null @@ -156,9 +150,6 @@ /// What job does this mob have var/job = null//Living - /// A list of factions that this mob is currently in, for hostile mob targetting, amongst other things - var/list/faction = list(FACTION_NEUTRAL) - /// Can this mob enter shuttles var/move_on_shuttle = 1 diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 66e2e18350b9..b14cd0d97295 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -518,3 +518,51 @@ "[key_name(src)] manually changed selected zone", data, ) + +/** + * Returns an associative list of the logs of a certain amount of lines spoken recently by this mob + * copy_amount - number of lines to return + * line_chance - chance to return a line, if you don't want just the most recent x lines + */ +/mob/proc/copy_recent_speech(copy_amount = LING_ABSORB_RECENT_SPEECH, line_chance = 100) + var/list/recent_speech = list() + var/list/say_log = list() + var/log_source = 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 = reverse_range(reversed.Copy()) + break + + for(var/spoken_memory in say_log) + if(recent_speech.len >= copy_amount) + break + if(!prob(line_chance)) + continue + recent_speech[spoken_memory] = splittext(say_log[spoken_memory], "\"", 1, 0, TRUE)[3] + + var/list/raw_lines = list() + for (var/key as anything in recent_speech) + raw_lines += recent_speech[key] + + return raw_lines + +/// Takes in an associated list (key `/datum/action` typepaths, value is the AI blackboard key) and handles granting the action and adding it to the mob's AI controller blackboard. +/// This is only useful in instances where you don't want to store the reference to the action on a variable on the mob. +/// You can set the value to null if you don't want to add it to the blackboard (like in player controlled instances). Is also safe with null AI controllers. +/// Assumes that the action will be initialized and held in the mob itself, which is typically standard. +/mob/proc/grant_actions_by_list(list/input) + if(length(input) <= 0) + return + + for(var/action in input) + var/datum/action/ability = new action(src) + ability.Grant(src) + + var/blackboard_key = input[action] + if(isnull(blackboard_key)) + continue + + ai_controller?.set_blackboard_key(blackboard_key, ability) diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 333eab324d52..67045a2a4a11 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -74,8 +74,8 @@ return FALSE if(!mob?.loc) return FALSE - if(mob.notransform) - return FALSE //This is sota the goto stop mobs from moving var + if(HAS_TRAIT(mob, TRAIT_NO_TRANSFORM)) + return FALSE //This is sorta the goto stop mobs from moving trait if(mob.control_object) return Move_object(direct) if(!isliving(mob)) @@ -255,9 +255,9 @@ if(salt) to_chat(L, span_warning("[salt] bars your passage!")) if(isrevenant(L)) - var/mob/living/simple_animal/revenant/R = L - R.reveal(20) - R.stun(20) + var/mob/living/basic/revenant/ghostie = L + ghostie.apply_status_effect(/datum/status_effect/revenant/revealed, 2 SECONDS) + ghostie.apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, 2 SECONDS) return if(stepTurf.turf_flags & NOJAUNT) to_chat(L, span_warning("Some strange aura is blocking the way.")) diff --git a/code/modules/mob/mob_transformation_simple.dm b/code/modules/mob/mob_transformation_simple.dm index 252d4362f967..27287834fe49 100644 --- a/code/modules/mob/mob_transformation_simple.dm +++ b/code/modules/mob/mob_transformation_simple.dm @@ -62,6 +62,7 @@ else desired_mob.key = key + SEND_SIGNAL(src, COMSIG_MOB_CHANGED_TYPE, desired_mob) if(delete_old_mob) QDEL_IN(src, 1) return desired_mob diff --git a/code/modules/mob/mob_update_icons.dm b/code/modules/mob/mob_update_icons.dm index 5ac1fd3105a8..bd17424d8482 100644 --- a/code/modules/mob/mob_update_icons.dm +++ b/code/modules/mob/mob_update_icons.dm @@ -29,7 +29,8 @@ ///Updates the held items overlay(s) & HUD element. /mob/proc/update_held_items() - return + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(src, COMSIG_MOB_UPDATE_HELD_ITEMS) ///Updates the mask overlay & HUD element. /mob/proc/update_worn_mask() diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index f80f072bcc4e..15e5c8fe525b 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -1,7 +1,11 @@ #define TRANSFORMATION_DURATION 22 +/// Will be removed once the transformation is complete. +#define TEMPORARY_TRANSFORMATION_TRAIT "temporary_transformation" +/// Considered "permanent" since we'll be deleting the old mob and the client will be inserted into a new one (without this trait) +#define PERMANENT_TRANSFORMATION_TRAIT "permanent_transformation" /mob/living/carbon/proc/monkeyize(instant = FALSE) - if (notransform || transformation_timer) + if (transformation_timer || HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return if(ismonkey(src)) @@ -12,7 +16,7 @@ return //Make mob invisible and spawn animation - notransform = TRUE + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, TEMPORARY_TRANSFORMATION_TRAIT) Paralyze(TRANSFORMATION_DURATION, ignore_canstun = TRUE) icon = null cut_overlays() @@ -23,8 +27,8 @@ /mob/living/carbon/proc/finish_monkeyize() transformation_timer = null - to_chat(src, "You are now a monkey.") - notransform = FALSE + to_chat(src, span_boldnotice("You are now a monkey.")) + REMOVE_TRAIT(src, TRAIT_NO_TRANSFORM, TEMPORARY_TRANSFORMATION_TRAIT) icon = initial(icon) invisibility = 0 set_species(/datum/species/monkey) @@ -37,7 +41,7 @@ //Could probably be merged with monkeyize but other transformations got their own procs, too /mob/living/carbon/proc/humanize(species = /datum/species/human, instant = FALSE) - if (notransform || transformation_timer) + if (transformation_timer || HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return if(!ismonkey(src)) @@ -48,7 +52,7 @@ return //Make mob invisible and spawn animation - notransform = TRUE + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, TEMPORARY_TRANSFORMATION_TRAIT) Paralyze(TRANSFORMATION_DURATION, ignore_canstun = TRUE) icon = null cut_overlays() @@ -59,8 +63,8 @@ /mob/living/carbon/proc/finish_humanize(species = /datum/species/human) transformation_timer = null - to_chat(src, "You are now a human.") - notransform = FALSE + to_chat(src, span_boldnotice("You are now a human.")) + REMOVE_TRAIT(src, TRAIT_NO_TRANSFORM, TEMPORARY_TRANSFORMATION_TRAIT) icon = initial(icon) invisibility = 0 set_species(species) @@ -102,10 +106,10 @@ qdel(src) -/mob/living/carbon/AIize(transfer_after = TRUE, client/preference_source) - if (notransform) +/mob/living/carbon/AIize(client/preference_source, transfer_after = TRUE) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return - notransform = TRUE + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT) Paralyze(1, ignore_canstun = TRUE) for(var/obj/item/W in src) dropItemToGround(W) @@ -114,8 +118,8 @@ invisibility = INVISIBILITY_MAXIMUM return ..() -/mob/living/carbon/human/AIize(transfer_after = TRUE, client/preference_source) - if (notransform) +/mob/living/carbon/human/AIize(client/preference_source, transfer_after = TRUE) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return for(var/t in bodyparts) qdel(t) @@ -123,9 +127,9 @@ return ..() /mob/proc/Robotize(delete_items = 0, transfer_after = TRUE) - if(notransform) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return - notransform = TRUE + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT) var/mob/living/silicon/robot/new_borg = new /mob/living/silicon/robot(loc) new_borg.gender = gender @@ -158,9 +162,9 @@ qdel(src) /mob/living/Robotize(delete_items = 0, transfer_after = TRUE) - if(notransform) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return - notransform = TRUE + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, TEMPORARY_TRANSFORMATION_TRAIT) Paralyze(1, ignore_canstun = TRUE) for(var/obj/item/W in src) @@ -172,7 +176,7 @@ icon = null invisibility = INVISIBILITY_MAXIMUM - notransform = FALSE + REMOVE_TRAIT(src, TRAIT_NO_TRANSFORM, TEMPORARY_TRANSFORMATION_TRAIT) return ..() /mob/living/silicon/robot/proc/replace_banned_cyborg() @@ -187,9 +191,9 @@ //human -> alien /mob/living/carbon/human/proc/Alienize() - if (notransform) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return - notransform = TRUE + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT) add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), TRAIT_GENERIC) for(var/obj/item/W in src) dropItemToGround(W) @@ -213,14 +217,14 @@ new_xeno.key = key update_atom_languages() - to_chat(new_xeno, "You are now an alien.") - . = new_xeno + to_chat(new_xeno, span_boldnotice("You are now an alien.")) qdel(src) + return new_xeno /mob/living/carbon/human/proc/slimeize(reproduce as num) - if (notransform) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return - notransform = TRUE + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT) add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), TRAIT_GENERIC) for(var/obj/item/W in src) dropItemToGround(W) @@ -245,9 +249,9 @@ new_slime.set_combat_mode(TRUE) new_slime.key = key - to_chat(new_slime, "You are now a slime. Skreee!") - . = new_slime + to_chat(new_slime, span_boldnotice("You are now a slime. Skreee!")) qdel(src) + return new_slime /mob/proc/become_overmind(starting_points = OVERMIND_STARTING_POINTS) var/mob/camera/blob/B = new /mob/camera/blob(get_turf(src), starting_points) @@ -257,9 +261,9 @@ /mob/living/carbon/human/proc/corgize() - if (notransform) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return - notransform = TRUE + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT) Paralyze(1, ignore_canstun = TRUE) for(var/obj/item/W in src) dropItemToGround(W) @@ -273,14 +277,14 @@ new_corgi.set_combat_mode(TRUE) new_corgi.key = key - to_chat(new_corgi, "You are now a Corgi. Yap Yap!") - . = new_corgi + to_chat(new_corgi, span_boldnotice("You are now a Corgi. Yap Yap!")) qdel(src) + return new_corgi /mob/living/carbon/proc/gorillize() - if(notransform) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return - notransform = TRUE + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT) Paralyze(1, ignore_canstun = TRUE) SSblackbox.record_feedback("amount", "gorillas_created", 1) @@ -293,15 +297,15 @@ regenerate_icons() icon = null invisibility = INVISIBILITY_MAXIMUM - var/mob/living/simple_animal/hostile/gorilla/new_gorilla = new (get_turf(src)) + var/mob/living/basic/gorilla/new_gorilla = new (get_turf(src)) new_gorilla.set_combat_mode(TRUE) if(mind) mind.transfer_to(new_gorilla) else new_gorilla.key = key - to_chat(new_gorilla, "You are now a gorilla. Ooga ooga!") - . = new_gorilla + to_chat(new_gorilla, span_boldnotice("You are now a gorilla. Ooga ooga!")) qdel(src) + return new_gorilla /mob/living/carbon/human/Animalize() @@ -313,9 +317,9 @@ to_chat(usr, span_danger("Sorry but this mob type is currently unavailable.")) return - if(notransform) + if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return - notransform = TRUE + ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT) Paralyze(1, ignore_canstun = TRUE) for(var/obj/item/W in src) @@ -334,8 +338,8 @@ new_mob.set_combat_mode(TRUE) to_chat(new_mob, span_boldnotice("You suddenly feel more... animalistic.")) - . = new_mob qdel(src) + return new_mob /mob/proc/Animalize() @@ -367,7 +371,7 @@ if(!MP) return FALSE //Sanity, this should never happen. - if(ispath(MP, /mob/living/simple_animal/hostile/construct)) + if(ispath(MP, /mob/living/basic/construct)) return FALSE //Verbs do not appear for players. //Good mobs! @@ -381,7 +385,7 @@ return TRUE if(ispath(MP, /mob/living/basic/mushroom)) return TRUE - if(ispath(MP, /mob/living/simple_animal/shade)) + if(ispath(MP, /mob/living/basic/shade)) return TRUE if(ispath(MP, /mob/living/basic/killer_tomato)) return TRUE @@ -389,10 +393,12 @@ return TRUE if(ispath(MP, /mob/living/basic/bear)) return TRUE - if(ispath(MP, /mob/living/simple_animal/parrot)) + if(ispath(MP, /mob/living/basic/parrot)) return TRUE //Parrots are no longer unfinished! -Nodrak //Not in here? Must be untested! return FALSE +#undef PERMANENT_TRANSFORMATION_TRAIT +#undef TEMPORARY_TRANSFORMATION_TRAIT #undef TRANSFORMATION_DURATION diff --git a/code/modules/mob_spawn/corpses/mining_corpses.dm b/code/modules/mob_spawn/corpses/mining_corpses.dm index c6e7525a92cc..373d326880b1 100644 --- a/code/modules/mob_spawn/corpses/mining_corpses.dm +++ b/code/modules/mob_spawn/corpses/mining_corpses.dm @@ -16,18 +16,21 @@ //Legion infested mobs -//dwarf type which spawns dwarfy versions -/obj/effect/mob_spawn/corpse/human/legioninfested/dwarf - -/obj/effect/mob_spawn/corpse/human/legioninfested/dwarf/special(mob/living/carbon/human/spawned_human) - . = ..() - spawned_human.dna.add_mutation(/datum/mutation/human/dwarfism) - -//main type, rolls a pool of legion victims +/// Mob spawner used by Legion to spawn costumed bodies /obj/effect/mob_spawn/corpse/human/legioninfested brute_damage = 1000 /obj/effect/mob_spawn/corpse/human/legioninfested/Initialize(mapload) + outfit = select_outfit() + return ..() + +/obj/effect/mob_spawn/corpse/human/legioninfested/special(mob/living/carbon/human/spawned_human) + . = ..() + var/obj/item/organ/internal/legion_tumour/cancer = new() + cancer.Insert(spawned_human, special = TRUE, drop_if_replaced = FALSE) + +/// Returns the outfit worn by our corpse +/obj/effect/mob_spawn/corpse/human/legioninfested/proc/select_outfit() var/corpse_theme = pick_weight(list( "Miner" = 66, "Ashwalker" = 10, @@ -40,24 +43,91 @@ "Shadow", )) = 4, )) + switch(corpse_theme) if("Miner") - outfit = /datum/outfit/consumed_miner + return /datum/outfit/consumed_miner if("Ashwalker") - outfit = /datum/outfit/consumed_ashwalker + return /datum/outfit/consumed_ashwalker + if("Golem") + return /datum/outfit/consumed_golem if("Clown") - outfit = /datum/outfit/consumed_clown + return /datum/outfit/consumed_clown if("Cultist") - outfit = /datum/outfit/consumed_cultist + return /datum/outfit/consumed_cultist if("Dame") - outfit = /datum/outfit/consumed_dame + return /datum/outfit/consumed_dame + if("Operative") + return /datum/outfit/syndicatecommandocorpse/lessenedgear + if("Shadow") + return /datum/outfit/consumed_shadowperson + +/// Corpse spawner used by dwarf legions to make small corpses +/obj/effect/mob_spawn/corpse/human/legioninfested/dwarf + +/obj/effect/mob_spawn/corpse/human/legioninfested/dwarf/special(mob/living/carbon/human/spawned_human) + . = ..() + spawned_human.dna.add_mutation(/datum/mutation/human/dwarfism) + +/// Corpse spawner used by snow legions with alternate costumes +/obj/effect/mob_spawn/corpse/human/legioninfested/snow + +/obj/effect/mob_spawn/corpse/human/legioninfested/snow/select_outfit() + var/corpse_theme = pick_weight(list( + "Miner" = 64, + "Clown" = 5, + "Golem" = 15, + "Settler" = 10, + pick(list( + "Cultist", + "Heremoth", + "Operative", + "Shadow", + )) = 4, + )) + + switch(corpse_theme) + if("Miner") + return /datum/outfit/consumed_miner + if("Settler") + return /datum/outfit/consumed_ice_settler + if("Heremoth") + return /datum/outfit/consumed_heremoth + if("Clown") + return /datum/outfit/consumed_clown + if("Cultist") + return /datum/outfit/consumed_cultist if("Golem") - outfit = /datum/outfit/consumed_golem + return /datum/outfit/consumed_golem if("Operative") - outfit = /datum/outfit/syndicatecommandocorpse + return /datum/outfit/syndicatecommandocorpse/lessenedgear if("Shadow") - outfit = /datum/outfit/consumed_shadowperson + return /datum/outfit/consumed_shadowperson + +/// Creates a dead legion-infested skeleton +/obj/effect/mob_spawn/corpse/human/legioninfested/skeleton + name = "legion-infested skeleton" + mob_name = "skeleton" + mob_species = /datum/species/skeleton + +/obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/select_outfit() + return null + +/obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/special(mob/living/carbon/human/spawned_human) . = ..() + spawned_human.gender = NEUTER + +/// Creates a dead and burned legion-infested skeleton +/obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/charred + name = "charred legion-infested skeleton" + mob_name = "charred skeleton" + brute_damage = 0 + burn_damage = 1000 + +/obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/charred/special(mob/living/carbon/human/spawned_human) + . = ..() + spawned_human.color = "#454545" + /datum/outfit/consumed_miner name = "Legion-Consumed Miner" @@ -283,3 +353,53 @@ /obj/item/stack/sheet/runed_metal = 15, ) r_pocket = /obj/item/clothing/glasses/hud/health/night/cultblind + +/datum/outfit/consumed_heremoth + name = "Legion-Consumed Tribal Mothman" + suit = /obj/item/clothing/suit/hooded/cultrobes/eldritch + head = /obj/item/clothing/head/hooded/cult_hoodie/eldritch + +/datum/outfit/consumed_heremoth/pre_equip(mob/living/carbon/human/moth, visualsOnly = FALSE) + if(!visualsOnly) + moth.set_species(/datum/species/moth) + if(prob(70)) + glasses = /obj/item/clothing/glasses/blindfold + if(prob(90)) + back = /obj/item/storage/backpack/cultpack + backpack_contents = list() + var/backpack_loot = pick(list( + /obj/item/flashlight/lantern = 1, + /obj/item/toy/plush/moth = 1, + /obj/item/toy/eldritch_book = 2, + /obj/item/knife/combat/survival = 2, + )) + backpack_contents += backpack_loot + +/datum/outfit/consumed_ice_settler + name = "Legion-Consumed Settler" + suit = /obj/item/clothing/suit/hooded/wintercoat + shoes = /obj/item/clothing/shoes/winterboots + mask = /obj/item/clothing/mask/breath + +/datum/outfit/consumed_ice_settler/pre_equip(mob/living/carbon/human/ice_settler, visualsOnly = FALSE) + if(prob(40)) + r_pocket = pick_weight(list( + /obj/item/coin/silver = 5, + /obj/item/fishing_hook = 2, + /obj/item/coin/gold = 2, + /obj/item/fishing_hook/shiny = 1, + )) + if(prob(30)) + back = pick_weight(list( + /obj/item/pickaxe = 4, + /obj/item/tank/internals/oxygen = 6, + )) + else + back = /obj/item/storage/backpack/satchel/explorer + backpack_contents = list() + var/backpack_loot = pick(list( + /obj/item/food/fishmeat = 89, + /obj/item/food/fishmeat/carp = 10, + /obj/item/skeleton_key = 1, + )) + backpack_contents += backpack_loot diff --git a/code/modules/mob_spawn/corpses/mob_corpses.dm b/code/modules/mob_spawn/corpses/mob_corpses.dm index 9d0605948a2c..0ad9d0bcd8ce 100644 --- a/code/modules/mob_spawn/corpses/mob_corpses.dm +++ b/code/modules/mob_spawn/corpses/mob_corpses.dm @@ -209,6 +209,21 @@ facial_haircolor = COLOR_WHITE skin_tone = "caucasian1" +/obj/effect/mob_spawn/corpse/human/wizard/red + outfit = /datum/outfit/wizardcorpse/red + +/obj/effect/mob_spawn/corpse/human/wizard/yellow + outfit = /datum/outfit/wizardcorpse/yellow + +/obj/effect/mob_spawn/corpse/human/wizard/black + outfit = /datum/outfit/wizardcorpse/black + +/obj/effect/mob_spawn/corpse/human/wizard/marisa + outfit = /datum/outfit/wizardcorpse/marisa + +/obj/effect/mob_spawn/corpse/human/wizard/tape + outfit = /datum/outfit/wizardcorpse/tape + /datum/outfit/wizardcorpse name = "Space Wizard Corpse" uniform = /obj/item/clothing/under/color/lightpurple @@ -216,6 +231,27 @@ shoes = /obj/item/clothing/shoes/sandal/magic head = /obj/item/clothing/head/wizard +/datum/outfit/wizardcorpse/red + suit = /obj/item/clothing/suit/wizrobe/red + head = /obj/item/clothing/head/wizard/red + +/datum/outfit/wizardcorpse/yellow + suit = /obj/item/clothing/suit/wizrobe/yellow + head = /obj/item/clothing/head/wizard/yellow + +/datum/outfit/wizardcorpse/black + suit = /obj/item/clothing/suit/wizrobe/black + head = /obj/item/clothing/head/wizard/black + +/datum/outfit/wizardcorpse/marisa + suit = /obj/item/clothing/suit/wizrobe/marisa + head = /obj/item/clothing/head/wizard/marisa + shoes = /obj/item/clothing/shoes/sneakers/marisa + +/datum/outfit/wizardcorpse/tape + suit = /obj/item/clothing/suit/wizrobe/tape + head = /obj/item/clothing/head/wizard/tape + /obj/effect/mob_spawn/corpse/human/wizard/dark name = "Dark Wizard Corpse" outfit = /datum/outfit/wizardcorpse/dark diff --git a/code/modules/mob_spawn/ghost_roles/space_roles.dm b/code/modules/mob_spawn/ghost_roles/space_roles.dm index 3567857ed6a7..cc97da88536e 100644 --- a/code/modules/mob_spawn/ghost_roles/space_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/space_roles.dm @@ -97,7 +97,7 @@ /obj/effect/mob_spawn/ghost_role/human/lavaland_syndicate/comms/space/Initialize(mapload) . = ..() if(prob(85)) //only has a 15% chance of existing, otherwise it'll just be a NPC syndie. - new /mob/living/basic/syndicate/ranged(get_turf(src)) + new /mob/living/basic/trooper/syndicate/ranged(get_turf(src)) return INITIALIZE_HINT_QDEL ///battlecruiser stuff diff --git a/code/modules/mob_spawn/ghost_roles/venus_human_trap.dm b/code/modules/mob_spawn/ghost_roles/venus_human_trap.dm index 800a6089aa37..9ffca695497f 100644 --- a/code/modules/mob_spawn/ghost_roles/venus_human_trap.dm +++ b/code/modules/mob_spawn/ghost_roles/venus_human_trap.dm @@ -4,7 +4,7 @@ desc = "A large pulsating plant..." icon = 'icons/effects/spacevines.dmi' icon_state = "bud0" - mob_type = /mob/living/simple_animal/hostile/venus_human_trap + mob_type = /mob/living/basic/venus_human_trap density = FALSE prompt_name = "venus human trap" you_are_text = "You are a venus human trap." @@ -23,7 +23,7 @@ flower_bud = null return ..() -/obj/effect/mob_spawn/ghost_role/venus_human_trap/equip(mob/living/simple_animal/hostile/venus_human_trap/spawned_human_trap) +/obj/effect/mob_spawn/ghost_role/venus_human_trap/equip(mob/living/basic/venus_human_trap/spawned_human_trap) if(spawned_human_trap && flower_bud) if(flower_bud.trait_flags & SPACEVINE_HEAT_RESISTANT) spawned_human_trap.unsuitable_heat_damage = 0 diff --git a/code/modules/mob_spawn/mob_spawn.dm b/code/modules/mob_spawn/mob_spawn.dm index 0548f0ee937b..806ec6cb7d80 100644 --- a/code/modules/mob_spawn/mob_spawn.dm +++ b/code/modules/mob_spawn/mob_spawn.dm @@ -11,8 +11,6 @@ var/mob_name ///the type of the mob, you best inherit this var/mob_type = /mob/living/basic/cockroach - ///Lazy string list of factions that the spawned mob will be in upon spawn - var/list/faction ////Human specific stuff. Don't set these if you aren't using a human, the unit tests will put a stop to your sinful hand. diff --git a/code/modules/mod/modules/modules_engineering.dm b/code/modules/mod/modules/modules_engineering.dm index 4531b84f15a7..e41b35aa51ee 100644 --- a/code/modules/mod/modules/modules_engineering.dm +++ b/code/modules/mod/modules/modules_engineering.dm @@ -126,7 +126,7 @@ line = firer.Beam(src, "line", 'icons/obj/clothing/modsuit/mod_modules.dmi', emissive = FALSE) ..() -/obj/projectile/tether/on_hit(atom/target) +/obj/projectile/tether/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(firer) firer.throw_at(target, 10, 1, firer, FALSE, FALSE, null, MOVE_FORCE_NORMAL, TRUE) diff --git a/code/modules/mod/modules/modules_medical.dm b/code/modules/mod/modules/modules_medical.dm index 1dd924189213..46f698e0d5fb 100644 --- a/code/modules/mod/modules/modules_medical.dm +++ b/code/modules/mod/modules/modules_medical.dm @@ -181,7 +181,7 @@ organ = null return ..() -/obj/projectile/organ/on_hit(atom/target) +/obj/projectile/organ/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(!ishuman(target)) organ.forceMove(drop_location()) diff --git a/code/modules/mod/modules/modules_ninja.dm b/code/modules/mod/modules/modules_ninja.dm index 53426c67e5ad..4f8c1d94a9c7 100644 --- a/code/modules/mod/modules/modules_ninja.dm +++ b/code/modules/mod/modules/modules_ninja.dm @@ -340,6 +340,8 @@ use_power_cost = DEFAULT_CHARGE_DRAIN * 6 incompatible_modules = list(/obj/item/mod/module/energy_net) cooldown_time = 1.5 SECONDS + /// List of all energy nets this module made. + var/list/energy_nets = list() /obj/item/mod/module/energy_net/on_select_use(atom/target) . = ..() @@ -367,6 +369,55 @@ net.buckle_mob(living_target, force = TRUE) drain_power(use_power_cost) +/obj/item/mod/module/energy_net/proc/add_net(obj/structure/energy_net/net) + energy_nets += net + RegisterSignal(net, COMSIG_PARENT_QDELETING, PROC_REF(remove_net)) + +/obj/item/mod/module/energy_net/proc/remove_net(obj/structure/energy_net/net) + SIGNAL_HANDLER + energy_nets -= net + +/obj/projectile/energy_net + name = "energy net" + icon_state = "net_projectile" + icon = 'icons/obj/clothing/modsuit/mod_modules.dmi' + damage = 0 + range = 9 + hitsound = 'sound/items/fultext_deploy.ogg' + hitsound_wall = 'sound/items/fultext_deploy.ogg' + /// Reference to the beam following the projectile. + var/line + /// Reference to the energy net module. + var/datum/weakref/net_module + +/obj/projectile/energy_net/Initialize(mapload, net_module) + . = ..() + src.net_module = WEAKREF(net_module) + +/obj/projectile/energy_net/fire(setAngle) + if(firer) + line = firer.Beam(src, "net_beam", 'icons/obj/clothing/modsuit/mod_modules.dmi') + return ..() + +/obj/projectile/energy_net/on_hit(mob/living/target, blocked = 0, pierce_hit) + . = ..() + if(!istype(target)) + return + if(locate(/obj/structure/energy_net) in get_turf(target)) + return + var/obj/structure/energy_net/net = new /obj/structure/energy_net(target.drop_location()) + var/obj/item/mod/module/energy_net/module = net_module?.resolve() + if(module) + module.add_net(net) + firer?.visible_message(span_danger("[firer] caught [target] with an energy net!"), span_notice("You caught [target] with an energy net!")) + if(target.buckled) + target.buckled.unbuckle_mob(target, force = TRUE) + net.buckle_mob(target, force = TRUE) + +/obj/projectile/energy_net/Destroy() + QDEL_NULL(line) + return ..() + ///Adrenaline Boost - Stops all stuns the ninja is affected with, increases his speed. /obj/item/mod/module/adrenaline_boost name = "MOD adrenaline boost module" diff --git a/code/modules/mod/modules/modules_timeline.dm b/code/modules/mod/modules/modules_timeline.dm index 4fa348659dad..fdf05b80a8a3 100644 --- a/code/modules/mod/modules/modules_timeline.dm +++ b/code/modules/mod/modules/modules_timeline.dm @@ -297,11 +297,14 @@ ///Reference to the tem... given by the tem! weakref because back in the day we didn't know about harddels- or maybe we didn't care. var/datum/weakref/tem_weakref -/obj/projectile/energy/chrono_beam/on_hit(atom/target) +/obj/projectile/energy/chrono_beam/on_hit(atom/target, blocked = 0, pierce_hit) var/obj/item/mod/module/tem/tem = tem_weakref.resolve() if(target && tem && isliving(target)) var/obj/structure/chrono_field/field = new(target.loc, target, tem) tem.field_connect(field) + return BULLET_ACT_HIT + + return ..() /obj/structure/chrono_field name = "eradication field" @@ -403,9 +406,10 @@ var/obj/item/mod/module/tem/linked_tem = beam.tem_weakref.resolve() if(linked_tem && istype(linked_tem)) linked_tem.field_connect(src) - else return BULLET_ACT_HIT + return ..() + /obj/structure/chrono_field/assume_air() return FALSE diff --git a/code/modules/modular_computers/file_system/programs/robocontrol.dm b/code/modules/modular_computers/file_system/programs/robocontrol.dm index 9d1f1542ae7e..baf6cafd7a2c 100644 --- a/code/modules/modular_computers/file_system/programs/robocontrol.dm +++ b/code/modules/modular_computers/file_system/programs/robocontrol.dm @@ -62,7 +62,7 @@ newbot["mule_check"] = TRUE botlist += list(newbot) - for(var/mob/living/simple_animal/drone/all_drones as anything in GLOB.drones_list) + for(var/mob/living/basic/drone/all_drones as anything in GLOB.drones_list) if(all_drones.hacked) continue if(!is_valid_z_level(current_turf, get_turf(all_drones))) diff --git a/code/modules/movespeed/modifiers/status_effects.dm b/code/modules/movespeed/modifiers/status_effects.dm index e8aad88c50d3..8a88d45a8179 100644 --- a/code/modules/movespeed/modifiers/status_effects.dm +++ b/code/modules/movespeed/modifiers/status_effects.dm @@ -37,3 +37,22 @@ /datum/movespeed_modifier/status_effect/tired_post_charge multiplicative_slowdown = 3 + +/// Get slower the more gold is in your system. +/datum/movespeed_modifier/status_effect/midas_blight + id = "midas_blight" + +/datum/movespeed_modifier/status_effect/midas_blight/soft + multiplicative_slowdown = 0.25 + +/datum/movespeed_modifier/status_effect/midas_blight/medium + multiplicative_slowdown = 0.75 + +/datum/movespeed_modifier/status_effect/midas_blight/hard + multiplicative_slowdown = 1.5 + +/datum/movespeed_modifier/status_effect/midas_blight/gold + multiplicative_slowdown = 2 + +/datum/movespeed_modifier/status_effect/guardian_shield + multiplicative_slowdown = 1 diff --git a/code/modules/pai/card.dm b/code/modules/pai/card.dm index a184c5da8032..ea82c35dc09f 100644 --- a/code/modules/pai/card.dm +++ b/code/modules/pai/card.dm @@ -110,6 +110,7 @@ name = pai.name, transmit = pai.can_transmit, receive = pai.can_receive, + range = pai.leash.distance, ) return data diff --git a/code/modules/pai/defense.dm b/code/modules/pai/defense.dm index 387f4026227a..f181e2c90101 100644 --- a/code/modules/pai/defense.dm +++ b/code/modules/pai/defense.dm @@ -55,11 +55,11 @@ if(user.put_in_hands(card)) user.visible_message(span_notice("[user] promptly scoops up [user.p_their()] pAI's card.")) -/mob/living/silicon/pai/bullet_act(obj/projectile/Proj) - if(Proj.stun) +/mob/living/silicon/pai/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE) + . = ..() + if(. == BULLET_ACT_HIT && (hitting_projectile.stun || hitting_projectile.paralyze)) fold_in(force = TRUE) - src.visible_message(span_warning("The electrically-charged projectile disrupts [src]'s holomatrix, forcing [src] to fold in!")) - . = ..(Proj) + visible_message(span_warning("The electrically-charged projectile disrupts [src]'s holomatrix, forcing [p_them()] to fold in!")) /mob/living/silicon/pai/ignite_mob(silent) return FALSE @@ -72,18 +72,17 @@ to_chat(src, span_userdanger("The impact degrades your holochassis!")) return amount -/mob/living/silicon/pai/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) - return take_holo_damage(amount) - -/mob/living/silicon/pai/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) - return take_holo_damage(amount) +/// Called when we take burn or brute damage, pass it to the shell instead +/mob/living/silicon/pai/proc/on_shell_damaged(datum/hurt, type, amount, forced) + SIGNAL_HANDLER + take_holo_damage(amount) + return COMPONENT_IGNORE_CHANGE -/mob/living/silicon/pai/pre_stamina_change(diff as num, forced) - if(forced) - take_holo_damage(diff) - else - take_holo_damage(diff * 0.25) - return 0 +/// Called when we take stamina damage, pass it to the shell instead +/mob/living/silicon/pai/proc/on_shell_weakened(datum/hurt, type, amount, forced) + SIGNAL_HANDLER + take_holo_damage(amount * ((forced) ? 1 : 0.25)) + return COMPONENT_IGNORE_CHANGE /mob/living/silicon/pai/getBruteLoss() return HOLOCHASSIS_MAX_HEALTH - holochassis_health diff --git a/code/modules/pai/pai.dm b/code/modules/pai/pai.dm index dffa39483f0c..8659f505337f 100644 --- a/code/modules/pai/pai.dm +++ b/code/modules/pai/pai.dm @@ -65,6 +65,8 @@ var/ram = 100 /// Toggles whether the Security HUD is active or not var/secHUD = FALSE + /// The current leash to the owner + var/datum/component/leash/leash // Onboard Items /// Atmospheric analyzer @@ -225,14 +227,17 @@ var/newcardloc = pai_card pai_card = new(newcardloc) pai_card.set_personality(src) - forceMove(pai_card) card = pai_card + forceMove(pai_card) + leash = AddComponent(/datum/component/leash, pai_card, HOLOFORM_DEFAULT_RANGE, force_teleport_out_effect = /obj/effect/temp_visual/guardian/phase/out) addtimer(VARSET_WEAK_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_INIT_TIME) if(!holoform) add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), PAI_FOLDED) desc = "A pAI hard-light holographics emitter. This one appears in the form of a [chassis]." RegisterSignal(src, COMSIG_LIVING_CULT_SACRIFICED, PROC_REF(on_cult_sacrificed)) + RegisterSignals(src, list(COMSIG_LIVING_ADJUST_BRUTE_DAMAGE, COMSIG_LIVING_ADJUST_BURN_DAMAGE), PROC_REF(on_shell_damaged)) + RegisterSignal(src, COMSIG_LIVING_ADJUST_STAMINA_DAMAGE, PROC_REF(on_shell_weakened)) /mob/living/silicon/pai/make_laws() laws = new /datum/ai_laws/pai() @@ -434,3 +439,10 @@ for(var/mob/living/cultist as anything in invokers) to_chat(cultist, span_cultitalic("You don't think this is what Nar'Sie had in mind when She asked for blood sacrifices...")) return STOP_SACRIFICE + +/// Updates the distance we can be from our pai card +/mob/living/silicon/pai/proc/increment_range(increment_amount) + var/new_distance = leash.distance + increment_amount + if (new_distance < HOLOFORM_MIN_RANGE || new_distance > HOLOFORM_MAX_RANGE) + return + leash.set_distance(new_distance) diff --git a/code/modules/pai/shell.dm b/code/modules/pai/shell.dm index 711e19ead51b..665727705eba 100644 --- a/code/modules/pai/shell.dm +++ b/code/modules/pai/shell.dm @@ -93,7 +93,7 @@ card.forceMove(target) forceMove(card) add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), PAI_FOLDED) - set_density(FALSE) + ADD_TRAIT(src, TRAIT_UNDENSE, PAI_FOLDED) set_light_on(FALSE) holoform = FALSE set_resting(resting) @@ -124,7 +124,7 @@ addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_COOLDOWN) REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, PAI_FOLDED) REMOVE_TRAIT(src, TRAIT_HANDS_BLOCKED, PAI_FOLDED) - set_density(TRUE) + REMOVE_TRAIT(src, TRAIT_UNDENSE, PAI_FOLDED) if(istype(card.loc, /obj/item/modular_computer)) var/obj/item/modular_computer/pc = card.loc pc.inserted_pai = null diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index bf9b9c5a7e86..e98ec70669f8 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -33,6 +33,7 @@ pickup_sound = 'sound/items/handling/paper_pickup.ogg' grind_results = list(/datum/reagent/cellulose = 3) color = COLOR_WHITE + item_flags = SKIP_FANTASY_ON_SPAWN /// Lazylist of raw, unsanitised, unparsed text inputs that have been made to the paper. var/list/datum/paper_input/raw_text_inputs diff --git a/code/modules/paperwork/paper_cutter.dm b/code/modules/paperwork/paper_cutter.dm index 4c124ca3ad90..74c5c3d063b3 100644 --- a/code/modules/paperwork/paper_cutter.dm +++ b/code/modules/paperwork/paper_cutter.dm @@ -139,8 +139,9 @@ to_chat(user, span_userdanger("You neatly cut [stored_paper][clumsy ? "... and your finger in the process!" : "."]")) if(clumsy) var/obj/item/bodypart/finger = user.get_active_hand() - var/datum/wound/slash/moderate/papercut = new - papercut.apply_wound(finger) + if (iscarbon(user)) + var/mob/living/carbon/carbon_user = user + carbon_user.cause_wound_of_type_and_severity(WOUND_SLASH, finger, WOUND_SEVERITY_MODERATE, wound_source = "paper cut") stored_paper = null qdel(stored_paper) new /obj/item/paper/paperslip(get_turf(src)) diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm index ca409d31e185..7997d7c994e7 100644 --- a/code/modules/paperwork/paperplane.dm +++ b/code/modules/paperwork/paperplane.dm @@ -98,7 +98,7 @@ return ..() -/obj/item/paperplane/throw_at(atom/target, range, speed, mob/thrower, spin=FALSE, diagonals_first = FALSE, datum/callback/callback, quickstart = TRUE) +/obj/item/paperplane/throw_at(atom/target, range, speed, mob/thrower, spin=FALSE, diagonals_first = FALSE, datum/callback/callback, gentle, quickstart = TRUE) . = ..(target, range, speed, thrower, FALSE, diagonals_first, callback, quickstart = quickstart) /obj/item/paperplane/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) diff --git a/code/modules/photography/camera/other.dm b/code/modules/photography/camera/other.dm index b10af32bfd88..8a86d7f59deb 100644 --- a/code/modules/photography/camera/other.dm +++ b/code/modules/photography/camera/other.dm @@ -3,6 +3,7 @@ desc = "A polaroid camera, some say it can see ghosts!" see_ghosts = CAMERA_SEE_GHOSTS_BASIC + /obj/item/camera/spooky/badmin desc = "A polaroid camera, some say it can see ghosts! It seems to have an extra magnifier on the end." see_ghosts = CAMERA_SEE_GHOSTS_ORBIT diff --git a/code/modules/power/apc/apc_attack.dm b/code/modules/power/apc/apc_attack.dm index 7a82785da4e7..3fffafe82b04 100644 --- a/code/modules/power/apc/apc_attack.dm +++ b/code/modules/power/apc/apc_attack.dm @@ -261,7 +261,7 @@ /obj/machinery/power/apc/blob_act(obj/structure/blob/B) set_broken() -/obj/machinery/power/apc/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE) +/obj/machinery/power/apc/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armor_penetration = 0) // APC being at 0 integrity doesnt delete it outright. Combined with take_damage this might cause runtimes. if(machine_stat & BROKEN && atom_integrity <= 0) if(sound_effect) diff --git a/code/modules/power/lighting/light.dm b/code/modules/power/lighting/light.dm index 0cac3acbfed9..da375a77e61a 100644 --- a/code/modules/power/lighting/light.dm +++ b/code/modules/power/lighting/light.dm @@ -431,7 +431,7 @@ if(prob(12)) electrocute_mob(user, get_area(src), src, 0.3, TRUE) -/obj/machinery/light/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) +/obj/machinery/light/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0) . = ..() if(. && !QDELETED(src)) if(prob(damage_amount * 5)) diff --git a/code/modules/power/singularity/narsie.dm b/code/modules/power/singularity/narsie.dm index 9b5fda50c127..14f31bc1df69 100644 --- a/code/modules/power/singularity/narsie.dm +++ b/code/modules/power/singularity/narsie.dm @@ -115,7 +115,7 @@ return ..() /obj/narsie/attack_ghost(mob/user) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, user, cultoverride = TRUE, loc_override = loc) + makeNewConstruct(/mob/living/basic/construct/harvester, user, cultoverride = TRUE, loc_override = loc) /obj/narsie/process() var/datum/component/singularity/singularity_component = singularity.resolve() diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index dd8a3ab5b77a..d89add9ad38e 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -191,7 +191,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) RegisterSignal(src, COMSIG_ATOM_BSA_BEAM, PROC_REF(force_delam)) RegisterSignal(src, COMSIG_ATOM_TIMESTOP_FREEZE, PROC_REF(time_frozen)) RegisterSignal(src, COMSIG_ATOM_TIMESTOP_UNFREEZE, PROC_REF(time_unfrozen)) - + RegisterSignal(src, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(eat_bullets)) var/static/list/loc_connections = list( COMSIG_TURF_INDUSTRIAL_LIFT_ENTER = PROC_REF(tram_contents_consume), ) diff --git a/code/modules/power/supermatter/supermatter_delamination/cascade_delam_objects.dm b/code/modules/power/supermatter/supermatter_delamination/cascade_delam_objects.dm index a45c26c2f6d9..62fe18b9c76c 100644 --- a/code/modules/power/supermatter/supermatter_delamination/cascade_delam_objects.dm +++ b/code/modules/power/supermatter/supermatter_delamination/cascade_delam_objects.dm @@ -36,6 +36,9 @@ if(our_turf) our_turf.opacity = FALSE + // Ideally this'd be part of the SM component, but the SM itself snowflakes bullets (emitters are bullets). + RegisterSignal(src, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(eat_bullets)) + /obj/crystal_mass/process() if(!COOLDOWN_FINISHED(src, sm_wall_cooldown)) @@ -70,9 +73,18 @@ new /obj/crystal_mass(next_turf, get_dir(next_turf, src)) -/obj/crystal_mass/bullet_act(obj/projectile/projectile) - visible_message(span_notice("[src] is unscathed!")) - return BULLET_ACT_HIT +/obj/crystal_mass/proc/eat_bullets(datum/source, obj/projectile/hitting_projectile) + SIGNAL_HANDLER + + visible_message( + span_warning("[hitting_projectile] flies into [src] with a loud crack, before rapidly flashing into ash."), + null, + span_hear("You hear a loud crack as you are washed with a wave of heat."), + ) + + playsound(src, 'sound/effects/supermatter.ogg', 50, TRUE) + qdel(hitting_projectile) + return COMPONENT_BULLET_BLOCKED /obj/crystal_mass/singularity_act() return @@ -166,4 +178,3 @@ span_hear("You hear a loud crack as a small distortion passes through you.")) qdel(consumed_object) - diff --git a/code/modules/power/supermatter/supermatter_hit_procs.dm b/code/modules/power/supermatter/supermatter_hit_procs.dm index 5c68669e6e24..aec732c323b7 100644 --- a/code/modules/power/supermatter/supermatter_hit_procs.dm +++ b/code/modules/power/supermatter/supermatter_hit_procs.dm @@ -5,16 +5,20 @@ for(var/atom/thing_to_consume as anything in tram_contents) Bumped(thing_to_consume) -/obj/machinery/power/supermatter_crystal/bullet_act(obj/projectile/projectile) +/obj/machinery/power/supermatter_crystal/proc/eat_bullets(datum/source, obj/projectile/projectile) + SIGNAL_HANDLER + var/turf/local_turf = loc + if(!istype(local_turf)) + return NONE + var/kiss_power = 0 switch(projectile.type) if(/obj/projectile/kiss) kiss_power = 60 if(/obj/projectile/kiss/death) kiss_power = 20000 - if(!istype(local_turf)) - return FALSE + if(!istype(projectile.firer, /obj/machinery/power/emitter)) investigate_log("has been hit by [projectile] fired by [key_name(projectile.firer)]", INVESTIGATE_ENGINE) if(projectile.armor_flag != BULLET || kiss_power) @@ -29,7 +33,10 @@ var/damage_to_be = damage + external_damage_immediate * clamp((emergency_point - damage) / emergency_point, 0, 1) if(damage_to_be > danger_point) visible_message(span_notice("[src] compresses under stress, resisting further impacts!")) - return BULLET_ACT_HIT + + playsound(src, 'sound/effects/supermatter.ogg', 50, TRUE) + qdel(projectile) + return COMPONENT_BULLET_BLOCKED /obj/machinery/power/supermatter_crystal/singularity_act() var/gain = 100 diff --git a/code/modules/procedural_mapping/mapGenerators/asteroid.dm b/code/modules/procedural_mapping/mapGenerators/asteroid.dm index ab2bc6f2ca42..bf6c84ebf883 100644 --- a/code/modules/procedural_mapping/mapGenerators/asteroid.dm +++ b/code/modules/procedural_mapping/mapGenerators/asteroid.dm @@ -22,7 +22,7 @@ spawnableAtoms = list( /mob/living/basic/mining/basilisk = 10, /mob/living/basic/mining/goliath/ancient = 10, - /mob/living/simple_animal/hostile/asteroid/hivelord = 10, + /mob/living/basic/mining/hivelord = 10, ) diff --git a/code/modules/procedural_mapping/mapGenerators/syndicate.dm b/code/modules/procedural_mapping/mapGenerators/syndicate.dm index b9dc00e13642..74d2d153d06a 100644 --- a/code/modules/procedural_mapping/mapGenerators/syndicate.dm +++ b/code/modules/procedural_mapping/mapGenerators/syndicate.dm @@ -17,10 +17,12 @@ /obj/structure/closet/syndicate = 25, /obj/machinery/suit_storage_unit/syndicate = 15) /datum/map_generator_module/splatter_layer/syndie_mobs - spawnableAtoms = list(/mob/living/basic/syndicate = 30, \ - /mob/living/basic/syndicate/melee = 20, \ - /mob/living/basic/syndicate/ranged = 20, \ - /mob/living/basic/viscerator = 30) + spawnableAtoms = list( + /mob/living/basic/trooper/syndicate = 30, + /mob/living/basic/trooper/syndicate/melee = 20, + /mob/living/basic/trooper/syndicate/ranged = 20, + /mob/living/basic/viscerator = 30 + ) spawnableTurfs = list() // Generators diff --git a/code/modules/projectiles/ammunition/ballistic/rifle.dm b/code/modules/projectiles/ammunition/ballistic/rifle.dm index 38515fb78207..9d785ba091f2 100644 --- a/code/modules/projectiles/ammunition/ballistic/rifle.dm +++ b/code/modules/projectiles/ammunition/ballistic/rifle.dm @@ -39,3 +39,18 @@ caliber = CALIBER_40MM icon_state = "40mmHE" projectile_type = /obj/projectile/bullet/a40mm + + +/obj/item/ammo_casing/a223 + name = ".223 bullet casing" + desc = "A .223 bullet casing." + caliber = CALIBER_A223 + projectile_type = /obj/projectile/bullet/a223 + +/obj/item/ammo_casing/a223/phasic + name = ".223 phasic bullet casing" + desc = "A .223 phasic bullet casing." + projectile_type = /obj/projectile/bullet/a223/phasic + +/obj/item/ammo_casing/a223/weak + projectile_type = /obj/projectile/bullet/a223/weak diff --git a/code/modules/projectiles/ammunition/energy/special.dm b/code/modules/projectiles/ammunition/energy/special.dm index bedfab7b2357..47f36877f053 100644 --- a/code/modules/projectiles/ammunition/energy/special.dm +++ b/code/modules/projectiles/ammunition/energy/special.dm @@ -20,15 +20,15 @@ harmful = FALSE /obj/item/ammo_casing/energy/flora/yield - projectile_type = /obj/projectile/energy/florayield + projectile_type = /obj/projectile/energy/flora/yield select_name = "yield" /obj/item/ammo_casing/energy/flora/mut - projectile_type = /obj/projectile/energy/floramut + projectile_type = /obj/projectile/energy/flora/mut select_name = "mutation" /obj/item/ammo_casing/energy/flora/revolution - projectile_type = /obj/projectile/energy/florarevolution + projectile_type = /obj/projectile/energy/flora/evolution select_name = "revolution" e_cost = 250 diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index f7ea7e9c80f6..c77a4a8c4e6e 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -87,6 +87,16 @@ QDEL_NULL(suppressed) return ..() +/obj/item/gun/apply_fantasy_bonuses(bonus) + . = ..() + fire_delay = modify_fantasy_variable("fire_delay", fire_delay, -bonus, 0) + projectile_damage_multiplier = modify_fantasy_variable("projectile_damage_multiplier", projectile_damage_multiplier, bonus/10, 0.1) + +/obj/item/gun/remove_fantasy_bonuses(bonus) + fire_delay = reset_fantasy_variable("fire_delay", fire_delay) + projectile_damage_multiplier = reset_fantasy_variable("projectile_damage_multiplier", projectile_damage_multiplier) + return ..() + /// Handles adding [the seclite mount component][/datum/component/seclite_attachable] to the gun. /// If the gun shouldn't have a seclight mount, override this with a return. /// Or, if a child of a gun with a seclite mount has slightly different behavior or icons, extend this. diff --git a/code/modules/projectiles/guns/ballistic/launchers.dm b/code/modules/projectiles/guns/ballistic/launchers.dm index 60225e8db121..54ef35b05627 100644 --- a/code/modules/projectiles/guns/ballistic/launchers.dm +++ b/code/modules/projectiles/guns/ballistic/launchers.dm @@ -89,13 +89,13 @@ user.visible_message(span_warning("[user] aims [src] at the ground! It looks like [user.p_theyre()] performing a sick rocket jump!"), \ span_userdanger("You aim [src] at the ground to perform a bisnasty rocket jump...")) if(can_shoot()) - user.notransform = TRUE + ADD_TRAIT(user, TRAIT_NO_TRANSFORM, REF(src)) playsound(src, 'sound/vehicles/rocketlaunch.ogg', 80, TRUE, 5) animate(user, pixel_z = 300, time = 30, easing = LINEAR_EASING) sleep(7 SECONDS) animate(user, pixel_z = 0, time = 5, easing = LINEAR_EASING) sleep(0.5 SECONDS) - user.notransform = FALSE + REMOVE_TRAIT(user, TRAIT_NO_TRANSFORM, REF(src)) process_fire(user, user, TRUE) if(!QDELETED(user)) //if they weren't gibbed by the explosion, take care of them for good. user.gib() diff --git a/code/modules/projectiles/guns/energy/beam_rifle.dm b/code/modules/projectiles/guns/energy/beam_rifle.dm index 6fb7dc523d2c..b7dd4c657da9 100644 --- a/code/modules/projectiles/guns/energy/beam_rifle.dm +++ b/code/modules/projectiles/guns/energy/beam_rifle.dm @@ -74,6 +74,18 @@ var/mob/listeningTo +/obj/item/gun/energy/beam_rifle/apply_fantasy_bonuses(bonus) + . = ..() + delay = modify_fantasy_variable("delay", delay, -bonus * 2) + aiming_time = modify_fantasy_variable("aiming_time", aiming_time, -bonus * 2) + recoil = modify_fantasy_variable("aiming_time", aiming_time, round(-bonus / 2)) + +/obj/item/gun/energy/beam_rifle/remove_fantasy_bonuses(bonus) + delay = reset_fantasy_variable("delay", delay) + aiming_time = reset_fantasy_variable("aiming_time", aiming_time) + recoil = reset_fantasy_variable("recoil", recoil) + return ..() + /obj/item/gun/energy/beam_rifle/debug delay = 0 cell_type = /obj/item/stock_parts/cell/infinite @@ -496,8 +508,8 @@ if(!QDELETED(target)) handle_impact(target) -/obj/projectile/beam/beam_rifle/on_hit(atom/target, blocked = FALSE, piercing_hit = FALSE) - handle_hit(target, piercing_hit) +/obj/projectile/beam/beam_rifle/on_hit(atom/target, blocked = 0, pierce_hit) + handle_hit(target, pierce_hit) return ..() /obj/projectile/beam/beam_rifle/is_hostile_projectile() @@ -543,7 +555,8 @@ /obj/projectile/beam/beam_rifle/hitscan/aiming_beam/prehit_pierce(atom/target) return PROJECTILE_DELETE_WITHOUT_HITTING -/obj/projectile/beam/beam_rifle/hitscan/aiming_beam/on_hit() +/obj/projectile/beam/beam_rifle/hitscan/aiming_beam/on_hit(atom/target, blocked = 0, pierce_hit) + SHOULD_CALL_PARENT(FALSE) // This is some snowflake stuff so whatever qdel(src) return BULLET_ACT_BLOCK diff --git a/code/modules/projectiles/guns/energy/dueling.dm b/code/modules/projectiles/guns/energy/dueling.dm index 054c11f3df66..bc2e7eadebcd 100644 --- a/code/modules/projectiles/guns/energy/dueling.dm +++ b/code/modules/projectiles/guns/energy/dueling.dm @@ -326,7 +326,7 @@ if(DUEL_SETTING_C) color = "blue" -/obj/projectile/energy/duel/on_hit(atom/target, blocked) +/obj/projectile/energy/duel/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() var/turf/T = get_turf(target) var/obj/effect/temp_visual/dueling_chaff/C = locate() in T diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm index 702b86c5475e..0520750627e4 100644 --- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm +++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm @@ -16,6 +16,14 @@ var/list/modkits = list() gun_flags = NOT_A_REAL_GUN +/obj/item/gun/energy/recharge/kinetic_accelerator/apply_fantasy_bonuses(bonus) + . = ..() + max_mod_capacity = modify_fantasy_variable("max_mod_capacity", max_mod_capacity, bonus * 10) + +/obj/item/gun/energy/recharge/kinetic_accelerator/remove_fantasy_bonuses(bonus) + max_mod_capacity = reset_fantasy_variable("max_mod_capacity", max_mod_capacity) + return ..() + /obj/item/gun/energy/recharge/kinetic_accelerator/Initialize(mapload) . = ..() @@ -188,7 +196,7 @@ strike_thing() ..() -/obj/projectile/kinetic/on_hit(atom/target) +/obj/projectile/kinetic/on_hit(atom/target, blocked = 0, pierce_hit) strike_thing(target) . = ..() @@ -255,10 +263,10 @@ /obj/item/borg/upgrade/modkit/proc/install(obj/item/gun/energy/recharge/kinetic_accelerator/KA, mob/user, transfer_to_loc = TRUE) . = TRUE if(minebot_upgrade) - if(minebot_exclusive && !istype(KA.loc, /mob/living/simple_animal/hostile/mining_drone)) + if(minebot_exclusive && !istype(KA.loc, /mob/living/basic/mining_drone)) to_chat(user, span_notice("The modkit you're trying to install is only rated for minebot use.")) return FALSE - else if(istype(KA.loc, /mob/living/simple_animal/hostile/mining_drone)) + else if(istype(KA.loc, /mob/living/basic/mining_drone)) to_chat(user, span_notice("The modkit you're trying to install is not rated for minebot use.")) return FALSE if(denied_type) @@ -586,5 +594,12 @@ name = "adjustable tracer bolts" desc = "Causes kinetic accelerator bolts to have an adjustable-colored tracer trail and explosion. Use in-hand to change color." -/obj/item/borg/upgrade/modkit/tracer/adjustable/attack_self(mob/user) - bolt_color = input(user,"","Choose Color",bolt_color) as color|null +/obj/item/borg/upgrade/modkit/tracer/adjustable/interact(mob/user) + ..() + choose_bolt_color(user) + +/obj/item/borg/upgrade/modkit/tracer/adjustable/proc/choose_bolt_color(mob/user) + set waitfor = FALSE + + var/new_color = input(user,"","Choose Color",bolt_color) as color|null + bolt_color = new_color || bolt_color diff --git a/code/modules/projectiles/guns/energy/recharge.dm b/code/modules/projectiles/guns/energy/recharge.dm index 3e84b8761af6..e1ca0c34b3fd 100644 --- a/code/modules/projectiles/guns/energy/recharge.dm +++ b/code/modules/projectiles/guns/energy/recharge.dm @@ -18,6 +18,14 @@ /// Do we recharge slower with more of our type? var/unique_frequency = FALSE +/obj/item/gun/energy/recharge/apply_fantasy_bonuses(bonus) + . = ..() + recharge_time = modify_fantasy_variable("recharge_time", recharge_time, -bonus, minimum = 0.2 SECONDS) + +/obj/item/gun/energy/recharge/remove_fantasy_bonuses(bonus) + recharge_time = reset_fantasy_variable("recharge_time", recharge_time) + return ..() + /obj/item/gun/energy/recharge/Initialize(mapload) . = ..() if(!holds_charge) diff --git a/code/modules/projectiles/guns/magic.dm b/code/modules/projectiles/guns/magic.dm index 762e95544c52..94c1e7502a8e 100644 --- a/code/modules/projectiles/guns/magic.dm +++ b/code/modules/projectiles/guns/magic.dm @@ -29,6 +29,17 @@ . = ..() RegisterSignal(src, COMSIG_ITEM_MAGICALLY_CHARGED, PROC_REF(on_magic_charge)) +/obj/item/gun/magic/apply_fantasy_bonuses(bonus) + . = ..() + recharge_rate = modify_fantasy_variable("recharge_rate", recharge_rate, -bonus, minimum = 1) + max_charges = modify_fantasy_variable("max_charges", max_charges, bonus) + charges = modify_fantasy_variable("charges", charges, bonus) + +/obj/item/gun/magic/remove_fantasy_bonuses(bonus) + recharge_rate = reset_fantasy_variable("recharge_rate", recharge_rate) + max_charges = reset_fantasy_variable("max_charges", max_charges) + charges = reset_fantasy_variable("charges", charges) + return ..() /obj/item/gun/magic/fire_sounds() var/frequency_to_use = sin((90/max_charges) * charges) diff --git a/code/modules/projectiles/guns/special/chem_gun.dm b/code/modules/projectiles/guns/special/chem_gun.dm index cf756f7649ba..46ebe99d6e26 100644 --- a/code/modules/projectiles/guns/special/chem_gun.dm +++ b/code/modules/projectiles/guns/special/chem_gun.dm @@ -17,6 +17,17 @@ var/max_syringes = 4 var/last_synth = 0 +/obj/item/gun/chem/apply_fantasy_bonuses(bonus) + . = ..() + max_syringes = modify_fantasy_variable("max_syringes", max_syringes, bonus, minimum = 1) + time_per_syringe = modify_fantasy_variable("time_per_syringe", time_per_syringe, -bonus * 10) + +/obj/item/gun/chem/remove_fantasy_bonuses(bonus) + max_syringes = reset_fantasy_variable("max_syringes", max_syringes) + time_per_syringe = reset_fantasy_variable("time_per_syringe", time_per_syringe) + return ..() + + /obj/item/gun/chem/Initialize(mapload) . = ..() chambered = new /obj/item/ammo_casing/chemgun(src) diff --git a/code/modules/projectiles/guns/special/grenade_launcher.dm b/code/modules/projectiles/guns/special/grenade_launcher.dm index 7aa6bc9a46c7..34861e981cb4 100644 --- a/code/modules/projectiles/guns/special/grenade_launcher.dm +++ b/code/modules/projectiles/guns/special/grenade_launcher.dm @@ -16,6 +16,14 @@ . = ..() . += "[grenades.len] / [max_grenades] grenades loaded." +/obj/item/gun/grenadelauncher/apply_fantasy_bonuses(bonus) + . = ..() + max_grenades = modify_fantasy_variable("max_syringes", max_grenades, bonus, minimum = 1) + +/obj/item/gun/grenadelauncher/remove_fantasy_bonuses(bonus) + max_grenades = reset_fantasy_variable("max_syringes", max_grenades) + return ..() + /obj/item/gun/grenadelauncher/attackby(obj/item/I, mob/user, params) if(istype(I, /obj/item/grenade/c4)) diff --git a/code/modules/projectiles/guns/special/meat_hook.dm b/code/modules/projectiles/guns/special/meat_hook.dm index ac9e5361999e..e43fe21e70d1 100644 --- a/code/modules/projectiles/guns/special/meat_hook.dm +++ b/code/modules/projectiles/guns/special/meat_hook.dm @@ -50,7 +50,7 @@ ..() //TODO: root the firer until the chain returns -/obj/projectile/hook/on_hit(atom/target) +/obj/projectile/hook/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(ismovable(target)) var/atom/movable/A = target diff --git a/code/modules/projectiles/guns/special/syringe_gun.dm b/code/modules/projectiles/guns/special/syringe_gun.dm index 70e151fb6e06..31f6d2081f5f 100644 --- a/code/modules/projectiles/guns/special/syringe_gun.dm +++ b/code/modules/projectiles/guns/special/syringe_gun.dm @@ -30,6 +30,14 @@ chambered = new /obj/item/ammo_casing/syringegun(src) recharge_newshot() +/obj/item/gun/syringe/apply_fantasy_bonuses(bonus) + . = ..() + max_syringes = modify_fantasy_variable("max_syringes", max_syringes, bonus, minimum = 1) + +/obj/item/gun/syringe/remove_fantasy_bonuses(bonus) + max_syringes = reset_fantasy_variable("max_syringes", max_syringes) + return ..() + /obj/item/gun/syringe/handle_atom_del(atom/A) . = ..() if(A in syringes) diff --git a/code/modules/projectiles/pins.dm b/code/modules/projectiles/pins.dm index 32e3b4c8e2c8..c5a6996759a1 100644 --- a/code/modules/projectiles/pins.dm +++ b/code/modules/projectiles/pins.dm @@ -30,19 +30,19 @@ if(proximity_flag) if(isgun(target)) . |= AFTERATTACK_PROCESSED_ITEM - var/obj/item/gun/targetted_gun = target - var/obj/item/firing_pin/old_pin = targetted_gun.pin + var/obj/item/gun/targeted_gun = target + var/obj/item/firing_pin/old_pin = targeted_gun.pin if(old_pin?.pin_removable && (force_replace || old_pin.pin_hot_swappable)) if(Adjacent(user)) user.put_in_hands(old_pin) else - old_pin.forceMove(targetted_gun.drop_location()) + old_pin.forceMove(targeted_gun.drop_location()) old_pin.gun_remove(user) - if(!targetted_gun.pin) + if(!targeted_gun.pin) if(!user.temporarilyRemoveItemFromInventory(src)) return . - if(gun_insert(user, targetted_gun)) + if(gun_insert(user, targeted_gun)) if(old_pin) balloon_alert(user, "swapped firing pin") else @@ -223,12 +223,12 @@ color = "#FFD700" fail_message = "" ///list of account IDs which have accepted the license prompt. If this is the multi-payment pin, then this means they accepted the waiver that each shot will cost them money - var/list/gun_owners = list() + var/list/gun_owners = list() ///how much gets paid out to license yourself to the gun - var/payment_amount + var/payment_amount var/datum/bank_account/pin_owner ///if true, user has to pay everytime they fire the gun - var/multi_payment = FALSE + var/multi_payment = FALSE var/owned = FALSE ///purchase prompt to prevent spamming it, set to the user who opens to prompt to prevent locking the gun up for other users. var/active_prompt_user @@ -321,10 +321,10 @@ pin_owner.adjust_money(payment_amount, "Firing Pin: Gun License Bought") gun_owners += credit_card_details to_chat(user, span_notice("Gun license purchased, have a secure day!")) - - else + + else to_chat(user, span_warning("ERROR: User balance insufficent for successful transaction!")) - + if("No", null) to_chat(user, span_warning("ERROR: User has declined to purchase gun license!")) active_prompt_user = null diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index c0673b5d84db..ad0031678666 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -157,13 +157,15 @@ var/decayedRange //stores original range var/reflect_range_decrease = 5 //amount of original range that falls off when reflecting, so it doesn't go forever var/reflectable = NONE // Can it be reflected or not? + // Status effects applied on hit - var/stun = 0 - var/knockdown = 0 - var/paralyze = 0 - var/immobilize = 0 - var/unconscious = 0 - var/eyeblur = 0 + var/stun = 0 SECONDS + var/knockdown = 0 SECONDS + var/paralyze = 0 SECONDS + var/immobilize = 0 SECONDS + var/unconscious = 0 SECONDS + /// Seconds of blurry eyes applied on projectile hit + var/eyeblur = 0 SECONDS /// Drowsiness applied on projectile hit var/drowsy = 0 SECONDS /// Jittering applied on projectile hit @@ -241,14 +243,22 @@ /** * Called when the projectile hits something * - * @params - * target - thing hit - * blocked - percentage of hit blocked - * pierce_hit - are we piercing through or regular hitting + * By default parent call will always return [BULLET_ACT_HIT] (unless qdeleted) + * so it is save to assume a successful hit in children (though not necessarily successfully damaged - it could've been blocked) + * + * Arguments + * * target - thing hit + * * blocked - percentage of hit blocked (0 to 100) + * * pierce_hit - boolean, are we piercing through or regular hitting + * + * Returns + * * Returns [BULLET_ACT_HIT] if we hit something. Default return value. + * * Returns [BULLET_ACT_BLOCK] if we were hit but sustained no effects (blocked it). Note, Being "blocked" =/= "blocked is 100". + * * Returns [BULLET_ACT_FORCE_PIERCE] to have the projectile keep going instead of "hitting", as if we were not hit at all. */ -/obj/projectile/proc/on_hit(atom/target, blocked = FALSE, pierce_hit) - if(fired_from) - SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle) +/obj/projectile/proc/on_hit(atom/target, blocked = 0, pierce_hit) + SHOULD_CALL_PARENT(TRUE) + // i know that this is probably more with wands and gun mods in mind, but it's a bit silly that the projectile on_hit signal doesn't ping the projectile itself. // maybe we care what the projectile thinks! See about combining these via args some time when it's not 5AM var/obj/item/bodypart/hit_limb @@ -258,8 +268,8 @@ SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle, hit_limb) if(QDELETED(src)) // in case one of the above signals deleted the projectile for whatever reason - return - var/turf/target_loca = get_turf(target) + return BULLET_ACT_BLOCK + var/turf/target_turf = get_turf(target) var/hitx var/hity @@ -270,10 +280,10 @@ hitx = target.pixel_x + rand(-8, 8) hity = target.pixel_y + rand(-8, 8) - if(damage > 0 && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_loca) && prob(75)) - var/turf/closed/wall/W = target_loca + if(damage > 0 && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_turf) && prob(75)) + var/turf/closed/wall/W = target_turf if(impact_effect_type && !hitscan) - new impact_effect_type(target_loca, hitx, hity) + new impact_effect_type(target_turf, hitx, hity) W.add_dent(WALL_DENT_SHOT, hitx, hity) @@ -281,7 +291,7 @@ if(!isliving(target)) if(impact_effect_type && !hitscan) - new impact_effect_type(target_loca, hitx, hity) + new impact_effect_type(target_turf, hitx, hity) if(isturf(target) && hitsound_wall) var/volume = clamp(vol_by_damage() + 20, 0, 100) if(suppressed) @@ -295,15 +305,15 @@ if(damage && L.blood_volume && damage_type == BRUTE) var/splatter_dir = dir if(starting) - splatter_dir = get_dir(starting, target_loca) + splatter_dir = get_dir(starting, target_turf) if(isalien(L)) - new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir) + new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_turf, splatter_dir) else - new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir) + new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_turf, splatter_dir) if(prob(33)) - L.add_splatter_floor(target_loca) + L.add_splatter_floor(target_turf) else if(impact_effect_type && !hitscan) - new impact_effect_type(target_loca, hitx, hity) + new impact_effect_type(target_turf, hitx, hity) var/organ_hit_text = "" var/limb_hit = hit_limb @@ -320,9 +330,6 @@ playsound(src, hitsound, volume, TRUE, -1) L.visible_message(span_danger("[L] is hit by \a [src][organ_hit_text]!"), \ span_userdanger("You're hit by \a [src][organ_hit_text]!"), null, COMBAT_MESSAGE_RANGE) - if(L.is_blind()) - to_chat(L, span_userdanger("You feel something hit you[organ_hit_text]!")) - L.on_hit(src) var/reagent_note if(reagents?.reagent_list) @@ -1137,6 +1144,29 @@ return FALSE +///Checks if the projectile can embed into someone +/obj/projectile/proc/can_embed_into(atom/hit) + return embedding && shrapnel_type && iscarbon(hit) && !HAS_TRAIT(hit, TRAIT_PIERCEIMMUNE) + +/// Reflects the projectile off of something +/obj/projectile/proc/reflect(atom/hit_atom) + if(!starting) + return + var/new_x = starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/new_y = starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/turf/current_tile = get_turf(hit_atom) + + // redirect the projectile + original = locate(new_x, new_y, z) + starting = current_tile + firer = hit_atom + yo = new_y - current_tile.y + xo = new_x - current_tile.x + var/new_angle_s = Angle + rand(120,240) + while(new_angle_s > 180) // Translate to regular projectile degrees + new_angle_s -= 360 + set_angle(new_angle_s) + #undef MOVES_HITSCAN #undef MUZZLE_EFFECT_PIXEL_INCREMENT diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index a161c295f1d9..e467a745f805 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -7,7 +7,7 @@ hitsound = 'sound/weapons/sear.ogg' hitsound_wall = 'sound/weapons/effects/searwall.ogg' armor_flag = LASER - eyeblur = 2 + eyeblur = 4 SECONDS impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser light_system = MOVABLE_LIGHT light_outer_range = 1 @@ -46,7 +46,7 @@ muzzle_type = /obj/effect/projectile/muzzle/heavy_laser impact_type = /obj/effect/projectile/impact/heavy_laser -/obj/projectile/beam/laser/on_hit(atom/target, blocked = FALSE) +/obj/projectile/beam/laser/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) var/mob/living/carbon/M = target @@ -106,8 +106,6 @@ stamina = 45 paralyze_timer = 5 SECONDS armor_flag = ENERGY - hitsound = 'sound/weapons/tap.ogg' - eyeblur = 0 impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser light_color = LIGHT_COLOR_BLUE tracer_type = /obj/effect/projectile/tracer/disabler @@ -137,7 +135,7 @@ impact_type = /obj/effect/projectile/impact/pulse wound_bonus = 10 -/obj/projectile/beam/pulse/on_hit(atom/target, blocked = FALSE) +/obj/projectile/beam/pulse/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if (!QDELETED(target) && (isturf(target) || isstructure(target))) if(isobj(target)) @@ -154,7 +152,7 @@ projectile_piercing = ALL var/pierce_hits = 2 -/obj/projectile/beam/pulse/heavy/on_hit(atom/target, blocked = FALSE) +/obj/projectile/beam/pulse/heavy/on_hit(atom/target, blocked = 0, pierce_hit) if(pierce_hits <= 0) projectile_piercing = NONE pierce_hits -= 1 @@ -198,7 +196,7 @@ impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser light_color = LIGHT_COLOR_BLUE -/obj/projectile/beam/lasertag/on_hit(atom/target, blocked = FALSE) +/obj/projectile/beam/lasertag/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(ishuman(target)) var/mob/living/carbon/human/M = target @@ -240,7 +238,7 @@ light_color = LIGHT_COLOR_BLUE var/shrink_time = 90 -/obj/projectile/beam/shrink/on_hit(atom/target, blocked = FALSE) +/obj/projectile/beam/shrink/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isopenturf(target) || isindestructiblewall(target))//shrunk floors wouldnt do anything except look weird, i-walls shouldn't be bypassable return diff --git a/code/modules/projectiles/projectile/bullets/_incendiary.dm b/code/modules/projectiles/projectile/bullets/_incendiary.dm index 857b6f037349..f9f7996c17d6 100644 --- a/code/modules/projectiles/projectile/bullets/_incendiary.dm +++ b/code/modules/projectiles/projectile/bullets/_incendiary.dm @@ -5,7 +5,7 @@ /// If TRUE, leaves a trail of hotspots as it flies, very very chaotic var/leaves_fire_trail = TRUE -/obj/projectile/bullet/incendiary/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/incendiary/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) var/mob/living/carbon/M = target @@ -41,7 +41,7 @@ wound_falloff_tile = -4 fire_stacks = 3 -/obj/projectile/bullet/incendiary/fire/on_hit(atom/target, blocked) +/obj/projectile/bullet/incendiary/fire/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() var/turf/location = get_turf(target) if(isopenturf(location)) diff --git a/code/modules/projectiles/projectile/bullets/cannonball.dm b/code/modules/projectiles/projectile/bullets/cannonball.dm index 11ffa603cbf7..2f57a3dcc99b 100644 --- a/code/modules/projectiles/projectile/bullets/cannonball.dm +++ b/code/modules/projectiles/projectile/bullets/cannonball.dm @@ -22,7 +22,7 @@ /// How much our object damage decreases on hit, similar to normal damage. var/object_damage_decrease_on_hit = 0 -/obj/projectile/bullet/cannonball/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/cannonball/on_hit(atom/target, blocked = 0, pierce_hit) damage -= damage_decrease_on_hit if(object_damage_decreases) object_damage -= min(damage, object_damage_decrease_on_hit) @@ -46,7 +46,7 @@ projectile_piercing = NONE damage = 40 //set to 30 before first mob impact, but they're gonna be gibbed by the explosion -/obj/projectile/bullet/cannonball/explosive/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/cannonball/explosive/on_hit(atom/target, blocked = 0, pierce_hit) explosion(target, devastation_range = 2, heavy_impact_range = 3, light_impact_range = 4, explosion_cause = src) . = ..() @@ -56,7 +56,7 @@ projectile_piercing = NONE damage = 15 //very low -/obj/projectile/bullet/cannonball/emp/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/cannonball/emp/on_hit(atom/target, blocked = 0, pierce_hit) empulse(src, 4, 10) . = ..() @@ -65,7 +65,7 @@ icon_state = "biggest_one" damage = 70 //low pierce -/obj/projectile/bullet/cannonball/biggest_one/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/cannonball/biggest_one/on_hit(atom/target, blocked = 0, pierce_hit) if(projectile_piercing == NONE) explosion(target, devastation_range = GLOB.MAX_EX_DEVESTATION_RANGE, heavy_impact_range = GLOB.MAX_EX_HEAVY_RANGE, light_impact_range = GLOB.MAX_EX_LIGHT_RANGE, flash_range = GLOB.MAX_EX_FLASH_RANGE, explosion_cause = src) . = ..() diff --git a/code/modules/projectiles/projectile/bullets/dart_syringe.dm b/code/modules/projectiles/projectile/bullets/dart_syringe.dm index 1f853127858a..405552a8909c 100644 --- a/code/modules/projectiles/projectile/bullets/dart_syringe.dm +++ b/code/modules/projectiles/projectile/bullets/dart_syringe.dm @@ -10,7 +10,7 @@ . = ..() create_reagents(50, NO_REACT) -/obj/projectile/bullet/dart/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/dart/on_hit(atom/target, blocked = 0, pierce_hit) if(iscarbon(target)) var/mob/living/carbon/M = target if(blocked != 100) // not completely blocked diff --git a/code/modules/projectiles/projectile/bullets/dnainjector.dm b/code/modules/projectiles/projectile/bullets/dnainjector.dm index 139f20c339ca..fdb051e7f800 100644 --- a/code/modules/projectiles/projectile/bullets/dnainjector.dm +++ b/code/modules/projectiles/projectile/bullets/dnainjector.dm @@ -7,7 +7,7 @@ embedding = null shrapnel_type = null -/obj/projectile/bullet/dnainjector/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/dnainjector/on_hit(atom/target, blocked = 0, pierce_hit) if(iscarbon(target)) var/mob/living/carbon/M = target if(blocked != 100) diff --git a/code/modules/projectiles/projectile/bullets/grenade.dm b/code/modules/projectiles/projectile/bullets/grenade.dm index b1d7278228f7..a99a7b57ff3e 100644 --- a/code/modules/projectiles/projectile/bullets/grenade.dm +++ b/code/modules/projectiles/projectile/bullets/grenade.dm @@ -8,7 +8,7 @@ embedding = null shrapnel_type = null -/obj/projectile/bullet/a40mm/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/a40mm/on_hit(atom/target, blocked = 0, pierce_hit) ..() explosion(target, devastation_range = -1, light_impact_range = 2, flame_range = 3, flash_range = 1, adminlog = FALSE, explosion_cause = src) return BULLET_ACT_HIT diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm index dbfdbccffe28..7436ad2fa482 100644 --- a/code/modules/projectiles/projectile/bullets/revolver.dm +++ b/code/modules/projectiles/projectile/bullets/revolver.dm @@ -73,7 +73,7 @@ damage = 10 ricochets_max = 0 -/obj/projectile/bullet/c38/trac/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/c38/trac/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() var/mob/living/carbon/M = target if(!istype(M)) @@ -91,7 +91,7 @@ damage = 20 ricochets_max = 0 -/obj/projectile/bullet/c38/hotshot/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/c38/hotshot/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) var/mob/living/carbon/M = target @@ -104,7 +104,7 @@ var/temperature = 100 ricochets_max = 0 -/obj/projectile/bullet/c38/iceblox/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/c38/iceblox/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) var/mob/living/M = target diff --git a/code/modules/projectiles/projectile/bullets/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm index 712e4dcc0f22..c0fa5e2766c7 100644 --- a/code/modules/projectiles/projectile/bullets/rifle.dm +++ b/code/modules/projectiles/projectile/bullets/rifle.dm @@ -46,3 +46,19 @@ bare_wound_bonus = 80 embedding = list(embed_chance=100, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10) wound_falloff_tile = -5 + +/obj/projectile/bullet/a223 + name = ".223 bullet" + damage = 35 + armour_penetration = 30 + wound_bonus = -40 + +/obj/projectile/bullet/a223/weak //centcom + damage = 20 + +/obj/projectile/bullet/a223/phasic + name = ".223 phasic bullet" + icon_state = "gaussphase" + damage = 30 + armour_penetration = 100 + projectile_phasing = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF | PASSMACHINE | PASSSTRUCTURE | PASSDOORS diff --git a/code/modules/projectiles/projectile/bullets/shotgun.dm b/code/modules/projectiles/projectile/bullets/shotgun.dm index cde530c6ed51..7c2f193df4c3 100644 --- a/code/modules/projectiles/projectile/bullets/shotgun.dm +++ b/code/modules/projectiles/projectile/bullets/shotgun.dm @@ -55,7 +55,7 @@ damage = 15 paralyze = 10 -/obj/projectile/bullet/shotgun_frag12/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/shotgun_frag12/on_hit(atom/target, blocked = 0, pierce_hit) ..() explosion(target, devastation_range = -1, light_impact_range = 1, explosion_cause = src) return BULLET_ACT_HIT diff --git a/code/modules/projectiles/projectile/bullets/sniper.dm b/code/modules/projectiles/projectile/bullets/sniper.dm index 8402e41fe367..a43426708644 100644 --- a/code/modules/projectiles/projectile/bullets/sniper.dm +++ b/code/modules/projectiles/projectile/bullets/sniper.dm @@ -14,7 +14,7 @@ ///Determines how much additional damage the round does to mechs. var/mecha_damage = 10 -/obj/projectile/bullet/p50/on_hit(atom/target, blocked = 0) +/obj/projectile/bullet/p50/on_hit(atom/target, blocked = 0, pierce_hit) if(isobj(target) && (blocked != 100)) var/obj/thing_to_break = target var/damage_to_deal = object_damage @@ -41,7 +41,7 @@ mecha_damage = 100 var/emp_radius = 2 -/obj/projectile/bullet/p50/disruptor/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/p50/disruptor/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if((blocked != 100) && isliving(target)) var/mob/living/living_guy = target @@ -60,7 +60,7 @@ object_damage = 30 mecha_damage = 0 -/obj/projectile/bullet/p50/incendiary/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/p50/incendiary/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) var/mob/living/carbon/poor_burning_dork = target diff --git a/code/modules/projectiles/projectile/bullets/special.dm b/code/modules/projectiles/projectile/bullets/special.dm index 59bb27e34eb4..49e8a03022c0 100644 --- a/code/modules/projectiles/projectile/bullets/special.dm +++ b/code/modules/projectiles/projectile/bullets/special.dm @@ -16,7 +16,7 @@ . = ..() SpinAnimation() -/obj/projectile/bullet/honker/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/honker/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() var/mob/M = target if(istype(M)) @@ -30,7 +30,7 @@ /obj/projectile/bullet/mime damage = 40 -/obj/projectile/bullet/mime/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/mime/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(!isliving(target)) return diff --git a/code/modules/projectiles/projectile/energy/ebow.dm b/code/modules/projectiles/projectile/energy/ebow.dm index 73faaffc22f5..e1da23495f4a 100644 --- a/code/modules/projectiles/projectile/energy/ebow.dm +++ b/code/modules/projectiles/projectile/energy/ebow.dm @@ -4,7 +4,7 @@ damage = 15 damage_type = TOX stamina = 60 - eyeblur = 10 + eyeblur = 20 SECONDS knockdown = 10 slur = 10 SECONDS diff --git a/code/modules/projectiles/projectile/energy/net_snare.dm b/code/modules/projectiles/projectile/energy/net_snare.dm index a3b0941964b4..74a0e3eaec75 100644 --- a/code/modules/projectiles/projectile/energy/net_snare.dm +++ b/code/modules/projectiles/projectile/energy/net_snare.dm @@ -10,7 +10,7 @@ . = ..() SpinAnimation() -/obj/projectile/energy/net/on_hit(atom/target, blocked = FALSE) +/obj/projectile/energy/net/on_hit(atom/target, blocked = 0, pierce_hit) if(isliving(target)) var/turf/Tloc = get_turf(target) if(!locate(/obj/effect/nettingportal) in Tloc) @@ -64,7 +64,7 @@ hitsound = 'sound/weapons/taserhit.ogg' range = 4 -/obj/projectile/energy/trap/on_hit(atom/target, blocked = FALSE) +/obj/projectile/energy/trap/on_hit(atom/target, blocked = 0, pierce_hit) if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - drop a trap new/obj/item/restraints/legcuffs/beartrap/energy(get_turf(loc)) else if(iscarbon(target)) @@ -82,7 +82,7 @@ hitsound = 'sound/weapons/taserhit.ogg' range = 10 -/obj/projectile/energy/trap/cyborg/on_hit(atom/target, blocked = FALSE) +/obj/projectile/energy/trap/cyborg/on_hit(atom/target, blocked = 0, pierce_hit) if(!ismob(target) || blocked >= 100) do_sparks(1, TRUE, src) qdel(src) diff --git a/code/modules/projectiles/projectile/energy/stun.dm b/code/modules/projectiles/projectile/energy/stun.dm index 03cf5f85d84d..7f36bf437ed6 100644 --- a/code/modules/projectiles/projectile/energy/stun.dm +++ b/code/modules/projectiles/projectile/energy/stun.dm @@ -11,7 +11,7 @@ muzzle_type = /obj/effect/projectile/muzzle/stun impact_type = /obj/effect/projectile/impact/stun -/obj/projectile/energy/electrode/on_hit(atom/target, blocked = FALSE) +/obj/projectile/energy/electrode/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - burst into sparks! do_sparks(1, TRUE, src) diff --git a/code/modules/projectiles/projectile/energy/tesla.dm b/code/modules/projectiles/projectile/energy/tesla.dm index 9afb816088ff..4a9a9a1068b4 100644 --- a/code/modules/projectiles/projectile/energy/tesla.dm +++ b/code/modules/projectiles/projectile/energy/tesla.dm @@ -7,7 +7,7 @@ var/zap_range = 3 var/power = 10000 -/obj/projectile/energy/tesla/on_hit(atom/target) +/obj/projectile/energy/tesla/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() tesla_zap(src, zap_range, power, zap_flags) qdel(src) @@ -32,7 +32,7 @@ speed = 1.5 var/shock_damage = 5 -/obj/projectile/energy/tesla_cannon/on_hit(atom/target) +/obj/projectile/energy/tesla_cannon/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) var/mob/living/victim = target diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index 9957c802c79d..fa6e255a99ed 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -31,7 +31,7 @@ name = "bolt of death" icon_state = "pulse1_bl" -/obj/projectile/magic/death/on_hit(atom/target) +/obj/projectile/magic/death/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) @@ -57,7 +57,7 @@ name = "bolt of resurrection" icon_state = "ion" -/obj/projectile/magic/resurrection/on_hit(atom/target) +/obj/projectile/magic/resurrection/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) @@ -85,7 +85,7 @@ var/inner_tele_radius = 0 var/outer_tele_radius = 6 -/obj/projectile/magic/teleport/on_hit(mob/target) +/obj/projectile/magic/teleport/on_hit(mob/target, blocked = 0, pierce_hit) . = ..() var/teleammount = 0 var/teleloc = target @@ -104,7 +104,7 @@ name = "bolt of safety" icon_state = "bluespace" -/obj/projectile/magic/safety/on_hit(atom/target) +/obj/projectile/magic/safety/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isturf(target)) return BULLET_ACT_HIT @@ -123,7 +123,7 @@ icon_state = "energy" var/list/door_types = list(/obj/structure/mineral_door/wood, /obj/structure/mineral_door/iron, /obj/structure/mineral_door/silver, /obj/structure/mineral_door/gold, /obj/structure/mineral_door/uranium, /obj/structure/mineral_door/sandstone, /obj/structure/mineral_door/transparent/plasma, /obj/structure/mineral_door/transparent/diamond) -/obj/projectile/magic/door/on_hit(atom/target) +/obj/projectile/magic/door/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(istype(target, /obj/machinery/door)) OpenDoor(target) @@ -153,7 +153,7 @@ /// If set, this projectile will only pass certain changeflags to wabbajack var/set_wabbajack_changeflags -/obj/projectile/magic/change/on_hit(atom/target) +/obj/projectile/magic/change/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) @@ -171,7 +171,7 @@ icon_state = "red_1" damage_type = BURN -/obj/projectile/magic/animate/on_hit(atom/target, blocked = FALSE) +/obj/projectile/magic/animate/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() target.animate_atom_living(firer) @@ -251,7 +251,7 @@ target.forceMove(src) return PROJECTILE_PIERCE_PHASE -/obj/projectile/magic/locker/on_hit(target) +/obj/projectile/magic/locker/on_hit(atom/target, blocked = 0, pierce_hit) if(created) return ..() if(LAZYLEN(contents)) @@ -313,7 +313,7 @@ name = "bolt of flying" icon_state = "flight" -/obj/projectile/magic/flying/on_hit(mob/living/target) +/obj/projectile/magic/flying/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) var/atom/throw_target = get_edge_target_turf(target, angle2dir(Angle)) @@ -323,7 +323,7 @@ name = "bolt of bounty" icon_state = "bounty" -/obj/projectile/magic/bounty/on_hit(mob/living/target) +/obj/projectile/magic/bounty/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) target.apply_status_effect(/datum/status_effect/bounty, firer) @@ -332,16 +332,16 @@ name = "bolt of antimagic" icon_state = "antimagic" -/obj/projectile/magic/antimagic/on_hit(mob/living/target) +/obj/projectile/magic/antimagic/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() - if(isliving(target)) + if(istype(target)) target.apply_status_effect(/datum/status_effect/song/antimagic) /obj/projectile/magic/fetch name = "bolt of fetching" icon_state = "fetch" -/obj/projectile/magic/fetch/on_hit(mob/living/target) +/obj/projectile/magic/fetch/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) var/atom/throw_target = get_edge_target_turf(target, get_dir(target, firer)) @@ -351,7 +351,7 @@ name = "bolt of babel" icon_state = "babel" -/obj/projectile/magic/babel/on_hit(mob/living/carbon/target) +/obj/projectile/magic/babel/on_hit(mob/living/carbon/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) if(curse_of_babel(target)) @@ -361,7 +361,7 @@ name = "bolt of necropotence" icon_state = "necropotence" -/obj/projectile/magic/necropotence/on_hit(mob/living/target) +/obj/projectile/magic/necropotence/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() if(!isliving(target)) return @@ -378,7 +378,7 @@ name = "bolt of possession" icon_state = "wipe" -/obj/projectile/magic/wipe/on_hit(mob/living/carbon/target) +/obj/projectile/magic/wipe/on_hit(mob/living/carbon/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) for(var/x in target.get_traumas())//checks to see if the victim is already going through possession @@ -494,7 +494,7 @@ chain = firer.Beam(src, icon_state = "lightning[rand(1, 12)]") return ..() -/obj/projectile/magic/aoe/lightning/on_hit(target) +/obj/projectile/magic/aoe/lightning/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() tesla_zap(src, zap_range, zap_power, zap_flags) @@ -522,7 +522,7 @@ /// Flash radius of the fireball var/exp_flash = 3 -/obj/projectile/magic/fireball/on_hit(atom/target, blocked = FALSE, pierce_hit) +/obj/projectile/magic/fireball/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) var/mob/living/mob_target = target @@ -577,7 +577,7 @@ speed = 1 pixel_speed_multiplier = 1/7 -/obj/projectile/magic/aoe/juggernaut/on_hit(atom/target, blocked) +/obj/projectile/magic/aoe/juggernaut/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() var/turf/target_turf = get_turf(src) playsound(target_turf, 'sound/weapons/resonator_blast.ogg', 100, FALSE) diff --git a/code/modules/projectiles/projectile/reusable/_reusable.dm b/code/modules/projectiles/projectile/reusable/_reusable.dm index ab7d89573eeb..73825c9b1395 100644 --- a/code/modules/projectiles/projectile/reusable/_reusable.dm +++ b/code/modules/projectiles/projectile/reusable/_reusable.dm @@ -7,7 +7,7 @@ var/ammo_type = /obj/item/ammo_casing/caseless var/dropped = FALSE -/obj/projectile/bullet/reusable/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/reusable/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() handle_drop() diff --git a/code/modules/projectiles/projectile/special/floral.dm b/code/modules/projectiles/projectile/special/floral.dm index 0fef1ef7443d..608679bf6da2 100644 --- a/code/modules/projectiles/projectile/special/floral.dm +++ b/code/modules/projectiles/projectile/special/floral.dm @@ -1,59 +1,67 @@ -/obj/projectile/energy/floramut - name = "alpha somatoray" - icon_state = "energy" +/obj/projectile/energy/flora damage = 0 damage_type = TOX armor_flag = ENERGY -/obj/projectile/energy/floramut/on_hit(atom/target, blocked = FALSE) +/obj/projectile/energy/flora/on_hit(atom/target, blocked, pierce_hit) + if(!isliving(target)) + return ..() + + var/mob/living/hit_plant = target + if(!(hit_plant.mob_biotypes & MOB_PLANT)) + hit_plant.show_message(span_notice("The radiation beam dissipates harmlessly through your body.")) + return BULLET_ACT_BLOCK + . = ..() - if(isliving(target)) - var/mob/living/L = target - if(L.mob_biotypes & MOB_PLANT) - if(prob(15)) - L.adjustToxLoss(rand(3, 6)) - L.Paralyze(100) - L.visible_message(span_warning("[L] writhes in pain as [L.p_their()] vacuoles boil."), span_userdanger("You writhe in pain as your vacuoles boil!"), span_hear("You hear the crunching of leaves.")) - if(iscarbon(L) && L.has_dna()) - var/mob/living/carbon/C = L - if(prob(80)) - C.easy_random_mutate(NEGATIVE + MINOR_NEGATIVE) - else - C.easy_random_mutate(POSITIVE) - C.random_mutate_unique_identity() - C.random_mutate_unique_features() - C.domutcheck() - else - L.adjustFireLoss(rand(5, 15)) - L.show_message(span_userdanger("The radiation beam singes you!")) - -/obj/projectile/energy/florayield + if(. == BULLET_ACT_HIT && blocked < 100) + on_hit_plant_effect(target) + + return . + +/// Called when we hit a mob with plant biotype +/obj/projectile/energy/flora/proc/on_hit_plant_effect(mob/living/hit_plant) + return + +/obj/projectile/energy/flora/mut + name = "alpha somatoray" + icon_state = "energy" + +/obj/projectile/energy/flora/mut/on_hit_plant_effect(mob/living/hit_plant) + if(prob(85)) + hit_plant.adjustFireLoss(rand(5, 15)) + hit_plant.show_message(span_userdanger("The radiation beam singes you!")) + return + + hit_plant.adjustToxLoss(rand(3, 6)) + hit_plant.Paralyze(10 SECONDS) + hit_plant.visible_message( + span_warning("[hit_plant] writhes in pain as [hit_plant.p_their()] vacuoles boil."), + span_userdanger("You writhe in pain as your vacuoles boil!"), + span_hear("You hear the crunching of leaves."), + ) + if(iscarbon(hit_plant) && hit_plant.has_dna()) + var/mob/living/carbon/carbon_plant = hit_plant + if(prob(80)) + carbon_plant.easy_random_mutate(NEGATIVE + MINOR_NEGATIVE) + else + carbon_plant.easy_random_mutate(POSITIVE) + carbon_plant.random_mutate_unique_identity() + carbon_plant.random_mutate_unique_features() + carbon_plant.domutcheck() + +/obj/projectile/energy/flora/yield name = "beta somatoray" icon_state = "energy2" - damage = 0 - damage_type = TOX - armor_flag = ENERGY -/obj/projectile/energy/florayield/on_hit(atom/target, blocked = FALSE) - . = ..() - if(isliving(target)) - var/mob/living/L = target - if(L.mob_biotypes & MOB_PLANT) - L.set_nutrition(min(L.nutrition + 30, NUTRITION_LEVEL_FULL)) +/obj/projectile/energy/flora/yield/on_hit_plant_effect(mob/living/hit_plant) + hit_plant.set_nutrition(min(hit_plant.nutrition + 30, NUTRITION_LEVEL_FULL)) -/obj/projectile/energy/florarevolution +/obj/projectile/energy/flora/evolution name = "gamma somatoray" icon_state = "energy3" - damage = 0 - damage_type = TOX - armor_flag = ENERGY -/obj/projectile/energy/florarevolution/on_hit(atom/target, blocked = FALSE) - . = ..() - if(isliving(target)) - var/mob/living/L = target - if(L.mob_biotypes & MOB_PLANT) - L.show_message(span_notice("The radiation beam leaves you feeling disoriented!")) - L.set_dizzy_if_lower(30 SECONDS) - L.emote("flip") - L.emote("spin") +/obj/projectile/energy/flora/evolution/on_hit_plant_effect(mob/living/hit_plant) + hit_plant.show_message(span_notice("The radiation beam leaves you feeling disoriented!")) + hit_plant.set_dizzy_if_lower(30 SECONDS) + hit_plant.emote("flip") + hit_plant.emote("spin") diff --git a/code/modules/projectiles/projectile/special/gravity.dm b/code/modules/projectiles/projectile/special/gravity.dm index 2a0df1b510b5..1a23b653a051 100644 --- a/code/modules/projectiles/projectile/special/gravity.dm +++ b/code/modules/projectiles/projectile/special/gravity.dm @@ -16,7 +16,7 @@ if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items power = min(C.gun?.power, 15) -/obj/projectile/gravityrepulse/on_hit() +/obj/projectile/gravityrepulse/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() T = get_turf(src) for(var/atom/movable/A in range(T, power)) @@ -50,7 +50,7 @@ if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items power = min(C.gun?.power, 15) -/obj/projectile/gravityattract/on_hit() +/obj/projectile/gravityattract/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() T = get_turf(src) for(var/atom/movable/A in range(T, power)) @@ -83,7 +83,7 @@ if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items power = min(C.gun?.power, 15) -/obj/projectile/gravitychaos/on_hit() +/obj/projectile/gravitychaos/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() T = get_turf(src) for(var/atom/movable/A in range(T, power)) diff --git a/code/modules/projectiles/projectile/special/ion.dm b/code/modules/projectiles/projectile/special/ion.dm index 6dc0246d35bb..9d25f1504cde 100644 --- a/code/modules/projectiles/projectile/special/ion.dm +++ b/code/modules/projectiles/projectile/special/ion.dm @@ -7,7 +7,7 @@ impact_effect_type = /obj/effect/temp_visual/impact_effect/ion var/emp_radius = 1 -/obj/projectile/ion/on_hit(atom/target, blocked = FALSE) +/obj/projectile/ion/on_hit(atom/target, blocked = 0, pierce_hit) ..() empulse(target, emp_radius, emp_radius) return BULLET_ACT_HIT diff --git a/code/modules/projectiles/projectile/special/meteor.dm b/code/modules/projectiles/projectile/special/meteor.dm index a0020a573d37..7cecbecc6aa3 100644 --- a/code/modules/projectiles/projectile/special/meteor.dm +++ b/code/modules/projectiles/projectile/special/meteor.dm @@ -9,7 +9,7 @@ damage_type = BRUTE armor_flag = BULLET -/obj/projectile/meteor/on_hit(atom/target, blocked = FALSE) +/obj/projectile/meteor/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(. == BULLET_ACT_HIT && isliving(target)) explosion(target, devastation_range = -1, light_impact_range = 2, flame_range = 0, flash_range = 1, adminlog = FALSE) diff --git a/code/modules/projectiles/projectile/special/mindflayer.dm b/code/modules/projectiles/projectile/special/mindflayer.dm index 54889bbced1c..9f15e9389d59 100644 --- a/code/modules/projectiles/projectile/special/mindflayer.dm +++ b/code/modules/projectiles/projectile/special/mindflayer.dm @@ -1,7 +1,7 @@ /obj/projectile/beam/mindflayer name = "flayer ray" -/obj/projectile/beam/mindflayer/on_hit(atom/target, blocked = FALSE) +/obj/projectile/beam/mindflayer/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(ishuman(target)) var/mob/living/carbon/human/human_hit = target diff --git a/code/modules/projectiles/projectile/special/neurotoxin.dm b/code/modules/projectiles/projectile/special/neurotoxin.dm index aa59cb114c8f..6cfe13bd75dd 100644 --- a/code/modules/projectiles/projectile/special/neurotoxin.dm +++ b/code/modules/projectiles/projectile/special/neurotoxin.dm @@ -7,7 +7,7 @@ armor_flag = BIO impact_effect_type = /obj/effect/temp_visual/impact_effect/neurotoxin -/obj/projectile/neurotoxin/on_hit(atom/target, blocked = FALSE) +/obj/projectile/neurotoxin/on_hit(atom/target, blocked = 0, pierce_hit) if(isalien(target)) knockdown = 0 SECONDS //monkestation edit: from paralyze to knockdown damage = 0 diff --git a/code/modules/projectiles/projectile/special/plasma.dm b/code/modules/projectiles/projectile/special/plasma.dm index cf8778fe4deb..5564ba14dab0 100644 --- a/code/modules/projectiles/projectile/special/plasma.dm +++ b/code/modules/projectiles/projectile/special/plasma.dm @@ -11,7 +11,7 @@ muzzle_type = /obj/effect/projectile/muzzle/plasma_cutter impact_type = /obj/effect/projectile/impact/plasma_cutter -/obj/projectile/plasma/on_hit(atom/target) +/obj/projectile/plasma/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(ismineralturf(target)) var/turf/closed/mineral/M = target diff --git a/code/modules/projectiles/projectile/special/rocket.dm b/code/modules/projectiles/projectile/special/rocket.dm index 1c8c8d4267e1..06a3135197f3 100644 --- a/code/modules/projectiles/projectile/special/rocket.dm +++ b/code/modules/projectiles/projectile/special/rocket.dm @@ -5,7 +5,7 @@ embedding = null shrapnel_type = null -/obj/projectile/bullet/gyro/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/gyro/on_hit(atom/target, blocked = 0, pierce_hit) ..() explosion(target, devastation_range = -1, light_impact_range = 2, explosion_cause = src) return BULLET_ACT_HIT @@ -22,7 +22,7 @@ /// Whether the rocket is capable of instantly killing a living target var/random_crits_enabled = TRUE // Worst thing Valve ever added -/obj/projectile/bullet/rocket/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/rocket/on_hit(atom/target, blocked = 0, pierce_hit) if(isliving(target) && prob(1) && random_crits_enabled) var/mob/living/gibbed_dude = target if(gibbed_dude.stat < HARD_CRIT) diff --git a/code/modules/projectiles/projectile/special/temperature.dm b/code/modules/projectiles/projectile/special/temperature.dm index 7eae3edfa203..10c652b77b51 100644 --- a/code/modules/projectiles/projectile/special/temperature.dm +++ b/code/modules/projectiles/projectile/special/temperature.dm @@ -9,7 +9,7 @@ /obj/projectile/temp/is_hostile_projectile() return temperature != 0 // our damage is done by cooling or heating (casting to boolean here) -/obj/projectile/temp/on_hit(atom/target, blocked = 0) +/obj/projectile/temp/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) var/mob/living/carbon/hit_mob = target diff --git a/code/modules/projectiles/projectile/special/wormhole.dm b/code/modules/projectiles/projectile/special/wormhole.dm index 26873daac871..90eadd0bb097 100644 --- a/code/modules/projectiles/projectile/special/wormhole.dm +++ b/code/modules/projectiles/projectile/special/wormhole.dm @@ -22,9 +22,11 @@ gun = casing.gun -/obj/projectile/beam/wormhole/on_hit(atom/target) +/obj/projectile/beam/wormhole/on_hit(atom/target, blocked = 0, pierce_hit) var/obj/item/gun/energy/wormhole_projector/projector = gun.resolve() if(!projector) qdel(src) - return + return BULLET_ACT_BLOCK + + . = ..() projector.create_portal(src, get_turf(src)) diff --git a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm index 054d432d8f34..18d0e6f8ba3e 100644 --- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm +++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm @@ -125,16 +125,16 @@ ///special size for anti cult effect var/effective_size = round(created_volume/48) playsound(T, 'sound/effects/pray.ogg', 80, FALSE, effective_size) - for(var/mob/living/simple_animal/revenant/R in get_hearers_in_view(7,T)) + for(var/mob/living/basic/revenant/ghostie in get_hearers_in_view(7,T)) var/deity if(GLOB.deity) deity = GLOB.deity else deity = "Christ" - to_chat(R, span_userdanger("The power of [deity] compels you!")) - R.stun(20) - R.reveal(100) - R.adjustHealth(50) + to_chat(ghostie, span_userdanger("The power of [deity] compels you!")) + ghostie.apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, 2 SECONDS) + ghostie.apply_status_effect(/datum/status_effect/revenant/revealed, 2 SECONDS) + ghostie.adjust_health(50) for(var/mob/living/carbon/C in get_hearers_in_view(effective_size,T)) if(IS_CULTIST(C)) to_chat(C, span_userdanger("The divine explosion sears you!")) diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm index 692dade04b63..d33eb213f792 100644 --- a/code/modules/reagents/reagent_containers.dm +++ b/code/modules/reagents/reagent_containers.dm @@ -38,6 +38,18 @@ /// The icon file to take fill icon appearances from var/fill_icon = 'icons/obj/reagentfillings.dmi' +/obj/item/reagent_containers/apply_fantasy_bonuses(bonus) + . = ..() + if(reagents) + reagents.maximum_volume = modify_fantasy_variable("maximum_volume", reagents.maximum_volume, bonus * 10, minimum = 5) + volume = modify_fantasy_variable("maximum_volume_beaker", volume, bonus * 10, minimum = 5) + +/obj/item/reagent_containers/remove_fantasy_bonuses(bonus) + if(reagents) + reagents.maximum_volume = reset_fantasy_variable("maximum_volume", reagents.maximum_volume) + volume = reset_fantasy_variable("maximum_volume_beaker", volume) + return ..() + /obj/item/reagent_containers/Initialize(mapload, vol) . = ..() if(isnum(vol) && vol > 0) diff --git a/code/modules/recycling/conveyor.dm b/code/modules/recycling/conveyor.dm index a667ae731aa2..fbd33d089433 100644 --- a/code/modules/recycling/conveyor.dm +++ b/code/modules/recycling/conveyor.dm @@ -37,6 +37,10 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) //Direction -> if we have a conveyor belt in that direction var/list/neighbors +/obj/machinery/conveyor/Initialize(mapload) + . = ..() + AddElement(/datum/element/footstep_override, priority = STEP_SOUND_CONVEYOR_PRIORITY) + /obj/machinery/conveyor/examine(mob/user) . = ..() if(inverted) diff --git a/code/modules/religion/burdened/psyker.dm b/code/modules/religion/burdened/psyker.dm index 537d163161d0..9a8a9d2100f5 100644 --- a/code/modules/religion/burdened/psyker.dm +++ b/code/modules/religion/burdened/psyker.dm @@ -185,7 +185,8 @@ /obj/item/gun/ballistic/revolver/chaplain/Initialize(mapload) . = ..() - AddComponent(/datum/component/anti_magic, MAGIC_RESISTANCE_HOLY) + AddComponent(/datum/component/anti_magic, MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY) + AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25) name = pick(possible_names) desc = possible_names[name] diff --git a/code/modules/religion/festival/instrument_rites.dm b/code/modules/religion/festival/instrument_rites.dm index 83b064a63984..a1c94c92425d 100644 --- a/code/modules/religion/festival/instrument_rites.dm +++ b/code/modules/religion/festival/instrument_rites.dm @@ -90,7 +90,7 @@ /datum/religion_rites/song_tuner/pain/finish_effect(mob/living/carbon/human/listener, atom/song_source) var/obj/item/bodypart/sliced_limb = pick(listener.bodyparts) - sliced_limb.force_wound_upwards(/datum/wound/slash/moderate/many_cuts) + sliced_limb.force_wound_upwards(/datum/wound/slash/flesh/moderate/many_cuts) /datum/religion_rites/song_tuner/lullaby name = "Spiritual Lullaby" diff --git a/code/modules/religion/religion_sects.dm b/code/modules/religion/religion_sects.dm index 8817de3f23b1..959900054d6c 100644 --- a/code/modules/religion/religion_sects.dm +++ b/code/modules/religion/religion_sects.dm @@ -161,7 +161,7 @@ eth_stomach.adjust_charge(60) did_we_charge = TRUE - //if we're not targetting a robot part we stop early + //if we're not targeting a robot part we stop early var/obj/item/bodypart/bodypart = blessed.get_bodypart(chap.zone_selected) if(!IS_ORGANIC_LIMB(bodypart)) if(!did_we_charge) diff --git a/code/modules/religion/sparring/sparring_datum.dm b/code/modules/religion/sparring/sparring_datum.dm index 00aac29a3557..39409935cd8a 100644 --- a/code/modules/religion/sparring/sparring_datum.dm +++ b/code/modules/religion/sparring/sparring_datum.dm @@ -216,7 +216,7 @@ var/mob/living/carbon/human/branded = interfering to_chat(interfering, span_warning("[GLOB.deity] brands your flesh for interfering with [chaplain]'s sparring match!!")) var/obj/item/bodypart/branded_limb = pick(branded.bodyparts) - branded_limb.force_wound_upwards(/datum/wound/burn/severe/brand) + branded_limb.force_wound_upwards(/datum/wound/burn/flesh/severe/brand, wound_source = "divine intervention") branded.emote("scream") flubs-- diff --git a/code/modules/research/designs/autolathe/service_designs.dm b/code/modules/research/designs/autolathe/service_designs.dm index ee90d6a73198..f1f590eb93e8 100644 --- a/code/modules/research/designs/autolathe/service_designs.dm +++ b/code/modules/research/designs/autolathe/service_designs.dm @@ -142,6 +142,18 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SERVICE +/datum/design/tongs + name = "Tongs" + id = "tongs" + build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = 1000) + build_path = /obj/item/kitchen/tongs + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_KITCHEN, + ) + departmental_flags = DEPARTMENT_BITFLAG_SERVICE + /datum/design/tray name = "Serving Tray" id = "servingtray" diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm index 98abd12ee750..249e5ff609da 100644 --- a/code/modules/research/experimentor.dm +++ b/code/modules/research/experimentor.dm @@ -646,11 +646,10 @@ /mob/living/basic/crab, /mob/living/basic/lizard, /mob/living/basic/mouse, + /mob/living/basic/parrot, /mob/living/basic/pet/dog/corgi, /mob/living/basic/pet/dog/pug, /mob/living/basic/pet/fox, - /mob/living/basic/crab, - /mob/living/simple_animal/parrot/natural, /mob/living/simple_animal/pet/cat, ) for(var/counter in 1 to rand(1, 25)) diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm index 9ac8341b478c..2dab4b816059 100644 --- a/code/modules/research/server.dm +++ b/code/modules/research/server.dm @@ -148,7 +148,7 @@ if(HDD_OVERLOADED) . += "The front panel is dangling open. The hdd inside is destroyed and the wires are all burned." -/obj/machinery/rnd/server/master/tool_act(mob/living/user, obj/item/tool, tool_type) +/obj/machinery/rnd/server/master/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking) // Only antags are given the training and knowledge to disassemble this thing. if(is_special_character(user)) return ..() diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index d01099668af0..df32e10abda9 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -119,6 +119,7 @@ "titaniumglass", "toner_large", "toner", + "tongs", "toy_armblade", "toy_balloon", "toygun", diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm index c3ad4ed3e1a6..a68a6fafae35 100644 --- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm +++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm @@ -989,7 +989,7 @@ healing_types += CLONE if(length(healing_types)) - owner.apply_damage_type(-heal_amount, damagetype = pick(healing_types)) + owner.heal_damage_type(heal_amount, damagetype = pick(healing_types)) owner.adjust_nutrition(3) drained.adjustCloneLoss(heal_amount * DRAIN_DAMAGE_MULTIPLIER) diff --git a/code/modules/research/xenobiology/crossbreeding/_weapons.dm b/code/modules/research/xenobiology/crossbreeding/_weapons.dm index 4e2adcb958be..1bbdd4b14820 100644 --- a/code/modules/research/xenobiology/crossbreeding/_weapons.dm +++ b/code/modules/research/xenobiology/crossbreeding/_weapons.dm @@ -127,7 +127,7 @@ Slimecrossing Weapons icon_state = "pulse0_bl" hitsound = 'sound/effects/splat.ogg' -/obj/projectile/magic/bloodchill/on_hit(mob/living/target) +/obj/projectile/magic/bloodchill/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) target.apply_status_effect(/datum/status_effect/bloodchill) diff --git a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm index a23a7d75ec3c..cb903c84d625 100644 --- a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm +++ b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm @@ -226,7 +226,7 @@ /datum/reagent/consumable/corn_syrup = -6, /datum/reagent/sulfur = -3) //sulfur repels snakes according to professor google. - resulting_atoms = list(/mob/living/simple_animal/hostile/retaliate/snake = 1) + resulting_atoms = list(/mob/living/basic/snake = 1) /////////////////////////////////////////// @@ -265,7 +265,7 @@ /datum/reagent/napalm = -4) virus_suspectibility = 0 - resulting_atoms = list(/mob/living/simple_animal/hostile/blob/blobspore/independent = 2) //These are useless so we might as well spawn 2. + resulting_atoms = list(/mob/living/basic/blob_minion/spore = 2) //These are useless so we might as well spawn 2. /datum/micro_organism/cell_line/blobbernaut desc = "Blobular myocytes" @@ -284,7 +284,7 @@ suppressive_reagents = list(/datum/reagent/consumable/tinlux = -6) virus_suspectibility = 0 - resulting_atoms = list(/mob/living/simple_animal/hostile/blob/blobbernaut/independent = 1) + resulting_atoms = list(/mob/living/basic/blob_minion/blobbernaut = 1) /datum/micro_organism/cell_line/gelatinous_cube desc = "Cubic ooze particles" @@ -482,7 +482,7 @@ /datum/micro_organism/cell_line/clown/fuck_up_growing(obj/machinery/plumbing/growing_vat/vat) vat.visible_message(span_warning("The biological sample in [vat] seems to have created something horrific!")) - var/mob/selected_mob = pick(list(/mob/living/simple_animal/hostile/retaliate/clown/mutant/slow, /mob/living/simple_animal/hostile/retaliate/clown/fleshclown)) + var/mob/selected_mob = pick(list(/mob/living/basic/clown/mutant/slow, /mob/living/basic/clown/fleshclown)) new selected_mob(get_turf(vat)) if(SEND_SIGNAL(vat.biological_sample, COMSIG_SAMPLE_GROWTH_COMPLETED) & SPARE_SAMPLE) @@ -510,7 +510,7 @@ /datum/reagent/consumable/nothing = -2, /datum/reagent/fuel/oil = -1) - resulting_atoms = list(/mob/living/simple_animal/hostile/retaliate/clown/banana = 1) + resulting_atoms = list(/mob/living/basic/clown/banana = 1) /datum/micro_organism/cell_line/clown/glutton desc = "hyperadipogenic clown stem cells" @@ -537,7 +537,7 @@ /datum/reagent/consumable/nothing = -2, /datum/reagent/toxin/bad_food = -1) - resulting_atoms = list(/mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton = 1) + resulting_atoms = list(/mob/living/basic/clown/mutant/glutton = 1) /datum/micro_organism/cell_line/clown/longclown desc = "long clown bits" @@ -560,7 +560,7 @@ /datum/reagent/consumable/nothing = -2, /datum/reagent/sulfur = -1) - resulting_atoms = list(/mob/living/simple_animal/hostile/retaliate/clown/longface = 1) + resulting_atoms = list(/mob/living/basic/clown/longface = 1) /datum/micro_organism/cell_line/frog desc = "anura amphibian cells" diff --git a/code/modules/security_levels/keycard_authentication.dm b/code/modules/security_levels/keycard_authentication.dm index cdf14a69c006..1512055d0c97 100644 --- a/code/modules/security_levels/keycard_authentication.dm +++ b/code/modules/security_levels/keycard_authentication.dm @@ -55,11 +55,10 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/keycard_auth, 26) /obj/machinery/keycard_auth/ui_status(mob/user) if(isdrone(user)) return UI_CLOSE - if(!isanimal(user)) + if(!isanimal_or_basicmob(user)) return ..() - var/mob/living/simple_animal/A = user - if(!A.dextrous) - to_chat(user, span_warning("You are too primitive to use this device!")) + if(!HAS_TRAIT(user, TRAIT_CAN_HOLD_ITEMS)) + balloon_alert(user, "no hands!") return UI_CLOSE return ..() diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index ad97d57fc6c3..47e62be1ebaa 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -70,7 +70,7 @@ /obj/docking_port/has_gravity(turf/current_turf) return TRUE -/obj/docking_port/take_damage() +/obj/docking_port/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0) return /obj/docking_port/singularity_pull() @@ -797,7 +797,7 @@ if(sunset_mobs.mind && !istype(get_area(sunset_mobs), /area/shuttle/escape/brig)) sunset_mobs.mind.force_escaped = TRUE // Ghostize them and put them in nullspace stasis (for stat & possession checks) - sunset_mobs.notransform = TRUE + ADD_TRAIT(sunset_mobs, TRAIT_NO_TRANSFORM, REF(src)) sunset_mobs.ghostize(FALSE) sunset_mobs.moveToNullspace() diff --git a/code/modules/shuttle/special.dm b/code/modules/shuttle/special.dm index 3a49beab926c..b801c171feaa 100644 --- a/code/modules/shuttle/special.dm +++ b/code/modules/shuttle/special.dm @@ -154,7 +154,7 @@ // Bar staff, GODMODE mobs(as long as they stay in the shuttle) that just want to make sure people have drinks // and a good time. -/mob/living/simple_animal/drone/snowflake/bardrone +/mob/living/basic/drone/snowflake/bardrone name = "Bardrone" desc = "A barkeeping drone, a robot built to tend bars." hacked = TRUE @@ -166,12 +166,9 @@ initial_language_holder = /datum/language_holder/universal default_storage = null -/mob/living/simple_animal/drone/snowflake/bardrone/Initialize(mapload) +/mob/living/basic/drone/snowflake/bardrone/Initialize(mapload) . = ..() - access_card.add_access(list(ACCESS_CENT_BAR)) - become_area_sensitive(ROUNDSTART_TRAIT) - RegisterSignal(src, COMSIG_ENTER_AREA, PROC_REF(check_barstaff_godmode)) - check_barstaff_godmode() + AddComponentFrom(ROUNDSTART_TRAIT, /datum/component/area_based_godmode, area_type = /area/shuttle/escape, allow_area_subtypes = TRUE) /mob/living/simple_animal/hostile/alien/maid/barmaid gold_core_spawnable = NO_SPAWN @@ -192,22 +189,12 @@ access_card.add_access(cap_trim.access + cap_trim.wildcard_access + list(ACCESS_CENT_BAR)) ADD_TRAIT(access_card, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - become_area_sensitive(ROUNDSTART_TRAIT) - RegisterSignal(src, COMSIG_ENTER_AREA, PROC_REF(check_barstaff_godmode)) - check_barstaff_godmode() + AddComponentFrom(ROUNDSTART_TRAIT, /datum/component/area_based_godmode, area_type = /area/shuttle/escape, allow_area_subtypes = TRUE) /mob/living/simple_animal/hostile/alien/maid/barmaid/Destroy() qdel(access_card) . = ..() -/mob/living/simple_animal/proc/check_barstaff_godmode() - SIGNAL_HANDLER - - if(istype(get_area(loc), /area/shuttle/escape)) - status_flags |= GODMODE - else - status_flags &= ~GODMODE - // Bar table, a wooden table that kicks you in a direction if you're not // barstaff (defined as someone who was a roundstart bartender or someone // with CENTCOM_BARSTAFF) @@ -242,6 +229,9 @@ if(is_bartender_job(human_user.mind?.assigned_role)) return TRUE + if(istype(user, /mob/living/basic/drone/snowflake/bardrone)) + return TRUE + var/obj/item/card/id/ID = user.get_idcard(FALSE) if(ID && (ACCESS_CENT_BAR in ID.access)) return TRUE diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm index 85bcc9472474..683bdc2d647c 100644 --- a/code/modules/shuttle/supply.dm +++ b/code/modules/shuttle/supply.dm @@ -1,37 +1,38 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( /mob/living, - /obj/structure/blob, - /obj/effect/rune, - /obj/item/disk/nuclear, - /obj/machinery/nuclearbomb, - /obj/item/beacon, - /obj/narsie, - /obj/tear_in_reality, - /obj/machinery/teleport/station, - /obj/machinery/teleport/hub, - /obj/machinery/quantumpad, - /obj/effect/mob_spawn, + /obj/docking_port, /obj/effect/hierophant, - /obj/structure/receiving_pad, - /obj/item/warp_cube, - /obj/machinery/rnd/production, //print tracking beacons, send shuttle - /obj/machinery/autolathe, //same - /obj/projectile/beam/wormhole, + /obj/effect/mob_spawn, /obj/effect/portal, - /obj/item/shared_storage, - /obj/structure/extraction_point, - /obj/machinery/syndicatebomb, + /obj/effect/rune, + /obj/item/beacon, + /obj/item/disk/nuclear, + /obj/item/gps, /obj/item/hilbertshotel, - /obj/item/swapper, - /obj/docking_port, - /obj/machinery/launchpad, - /obj/machinery/disposal, - /obj/structure/disposalpipe, /obj/item/mail, + /obj/item/shared_storage, + /obj/item/swapper, + /obj/item/warp_cube, + /obj/machinery/autolathe, // In case you manage to get it to print a beacon while in transit /obj/machinery/camera, - /obj/item/gps, + /obj/machinery/disposal, + /obj/machinery/exodrone_launcher, + /obj/machinery/fax, + /obj/machinery/launchpad, + /obj/machinery/nuclearbomb, + /obj/machinery/quantumpad, + /obj/machinery/rnd/production, + /obj/machinery/syndicatebomb, + /obj/machinery/teleport/hub, + /obj/machinery/teleport/station, + /obj/narsie, + /obj/projectile/beam/wormhole, + /obj/structure/blob, /obj/structure/checkoutmachine, - /obj/machinery/fax + /obj/structure/disposalpipe, + /obj/structure/extraction_point, + /obj/structure/guardian_beacon, + /obj/tear_in_reality, ))) /// How many goody orders we can fit in a lockbox before we upgrade to a crate diff --git a/code/modules/spells/spell_types/conjure/simian.dm b/code/modules/spells/spell_types/conjure/simian.dm index 556a78e50127..aa9aabc68100 100644 --- a/code/modules/spells/spell_types/conjure/simian.dm +++ b/code/modules/spells/spell_types/conjure/simian.dm @@ -14,14 +14,18 @@ invocation_type = INVOCATION_SHOUT summon_radius = 2 - summon_type = list(/mob/living/carbon/human/species/monkey/angry, /mob/living/carbon/human/species/monkey/angry, /mob/living/simple_animal/hostile/gorilla/lesser) + summon_type = list( + /mob/living/basic/gorilla/lesser, + /mob/living/carbon/human/species/monkey/angry, + /mob/living/carbon/human/species/monkey/angry, // Listed twice so it's twice as likely, this class doesn't use pick weight + ) summon_amount = 4 /datum/action/cooldown/spell/conjure/simian/level_spell(bypass_cap) . = ..() summon_amount++ // MORE, MOOOOORE if(spell_level == spell_max_level) // We reward the faithful. - summon_type = list(/mob/living/carbon/human/species/monkey/angry, /mob/living/simple_animal/hostile/gorilla) + summon_type = list(/mob/living/carbon/human/species/monkey/angry, /mob/living/basic/gorilla) spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC // Max level lets you cast it naked, for monkey larp. to_chat(owner, span_notice("Your simian power has reached maximum capacity! You can now cast this spell naked, and you will create adult Gorillas with each cast.")) diff --git a/code/modules/spells/spell_types/conjure_item/lighting_packet.dm b/code/modules/spells/spell_types/conjure_item/lighting_packet.dm index 5901c268c2c8..2badfdd46dff 100644 --- a/code/modules/spells/spell_types/conjure_item/lighting_packet.dm +++ b/code/modules/spells/spell_types/conjure_item/lighting_packet.dm @@ -32,7 +32,7 @@ hit_living.electrocute_act(80, src, flags = SHOCK_ILLUSION | SHOCK_NOGLOVES) qdel(src) -/obj/item/spellpacket/lightningbolt/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY, quickstart = TRUE) +/obj/item/spellpacket/lightningbolt/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY, gentle, quickstart = TRUE) . = ..() if(ishuman(thrower)) var/mob/living/carbon/human/human_thrower = thrower diff --git a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm index ce514b004a50..e17d64fb8734 100644 --- a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm +++ b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm @@ -83,10 +83,10 @@ var/turf/jaunt_turf = get_turf(blood) // Begin the jaunt - jaunter.notransform = TRUE + ADD_TRAIT(jaunter, TRAIT_NO_TRANSFORM, REF(src)) var/obj/effect/dummy/phased_mob/holder = enter_jaunt(jaunter, jaunt_turf) if(!holder) - jaunter.notransform = FALSE + REMOVE_TRAIT(jaunter, TRAIT_NO_TRANSFORM, REF(src)) return FALSE RegisterSignal(holder, COMSIG_MOVABLE_MOVED, PROC_REF(update_status_on_signal)) @@ -104,7 +104,7 @@ playsound(jaunt_turf, 'sound/magic/enter_blood.ogg', 50, TRUE, -1) jaunter.extinguish_mob() - jaunter.notransform = FALSE + REMOVE_TRAIT(jaunter, TRAIT_NO_TRANSFORM, REF(src)) return TRUE /** @@ -113,7 +113,7 @@ */ /datum/action/cooldown/spell/jaunt/bloodcrawl/proc/try_exit_jaunt(obj/effect/decal/cleanable/blood, mob/living/jaunter, forced = FALSE) if(!forced) - if(jaunter.notransform) + if(HAS_TRAIT(jaunter, TRAIT_NO_TRANSFORM)) to_chat(jaunter, span_warning("You cannot exit yet!!")) return FALSE @@ -196,9 +196,9 @@ blind_message = span_notice("You hear a splash."), ) - jaunter.notransform = TRUE + ADD_TRAIT(jaunter, TRAIT_NO_TRANSFORM, REF(src)) consume_victim(victim, jaunter) - jaunter.notransform = FALSE + REMOVE_TRAIT(jaunter, TRAIT_NO_TRANSFORM, REF(src)) return TRUE diff --git a/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm b/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm index b4414c99796d..43e1f9036ee4 100644 --- a/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm +++ b/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm @@ -49,9 +49,9 @@ /datum/action/cooldown/spell/jaunt/ethereal_jaunt/proc/do_jaunt(mob/living/cast_on) // Makes sure they don't die or get jostled or something during the jaunt entry // Honestly probably not necessary anymore, but better safe than sorry - cast_on.notransform = TRUE + ADD_TRAIT(cast_on, TRAIT_NO_TRANSFORM, REF(src)) var/obj/effect/dummy/phased_mob/holder = enter_jaunt(cast_on) - cast_on.notransform = FALSE + REMOVE_TRAIT(cast_on, TRAIT_NO_TRANSFORM, REF(src)) if(!holder) CRASH("[type] attempted do_jaunt but failed to create a jaunt holder via enter_jaunt.") @@ -167,9 +167,10 @@ /datum/action/cooldown/spell/jaunt/ethereal_jaunt/proc/end_jaunt(mob/living/cast_on, obj/effect/dummy/phased_mob/spell_jaunt/holder, turf/final_point) if(QDELETED(cast_on) || QDELETED(holder) || QDELETED(src)) return - cast_on.notransform = TRUE + + ADD_TRAIT(cast_on, TRAIT_NO_TRANSFORM, REF(src)) exit_jaunt(cast_on) - cast_on.notransform = FALSE + REMOVE_TRAIT(cast_on, TRAIT_NO_TRANSFORM, REF(src)) REMOVE_TRAIT(cast_on, TRAIT_IMMOBILIZED, REF(src)) diff --git a/code/modules/spells/spell_types/pointed/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm index b18669d6f9ef..976e18e2731e 100644 --- a/code/modules/spells/spell_types/pointed/_pointed.dm +++ b/code/modules/spells/spell_types/pointed/_pointed.dm @@ -64,17 +64,17 @@ build_all_button_icons() return TRUE -/datum/action/cooldown/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/click_target) +/datum/action/cooldown/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/target) var/atom/aim_assist_target - if(aim_assist && isturf(click_target)) + if(aim_assist && isturf(target)) // Find any human in the list. We aren't picky, it's aim assist after all - aim_assist_target = locate(/mob/living/carbon/human) in click_target + aim_assist_target = locate(/mob/living/carbon/human) in target if(!aim_assist_target) // If we didn't find a human, we settle for any living at all - aim_assist_target = locate(/mob/living) in click_target + aim_assist_target = locate(/mob/living) in target - return ..(caller, params, aim_assist_target || click_target) + return ..(caller, params, aim_assist_target || target) /datum/action/cooldown/spell/pointed/is_valid_target(atom/cast_on) if(cast_on == owner) diff --git a/code/modules/spells/spell_types/pointed/mind_transfer.dm b/code/modules/spells/spell_types/pointed/mind_transfer.dm index 6c73f664fbfd..f0e0a9adb2c5 100644 --- a/code/modules/spells/spell_types/pointed/mind_transfer.dm +++ b/code/modules/spells/spell_types/pointed/mind_transfer.dm @@ -59,7 +59,7 @@ to_chat(owner, span_warning("This creature is too [pick("powerful", "strange", "arcane", "obscene")] to control!")) return FALSE if(isguardian(cast_on)) - var/mob/living/simple_animal/hostile/guardian/stand = cast_on + var/mob/living/basic/guardian/stand = cast_on if(stand.summoner && stand.summoner == owner) to_chat(owner, span_warning("Swapping minds with your own guardian would just put you back into your own head!")) return FALSE @@ -86,7 +86,7 @@ var/mob/living/to_swap = cast_on if(isguardian(cast_on)) - var/mob/living/simple_animal/hostile/guardian/stand = cast_on + var/mob/living/basic/guardian/stand = cast_on if(stand.summoner) to_swap = stand.summoner diff --git a/code/modules/spells/spell_types/pointed/swap.dm b/code/modules/spells/spell_types/pointed/swap.dm index 55e5a67c6250..904c2d36c6ee 100644 --- a/code/modules/spells/spell_types/pointed/swap.dm +++ b/code/modules/spells/spell_types/pointed/swap.dm @@ -35,27 +35,27 @@ return FALSE return TRUE -/datum/action/cooldown/spell/pointed/swap/InterceptClickOn(mob/living/caller, params, atom/click_target) +/datum/action/cooldown/spell/pointed/swap/InterceptClickOn(mob/living/caller, params, atom/target) if((caller.istate & ISTATE_SECONDARY)) if(!IsAvailable(feedback = TRUE)) return FALSE if(!target) return FALSE - if(!isliving(click_target) || isturf(click_target)) + if(!isliving(target) || isturf(target)) // Find any living being in the list. We aren't picky, it's aim assist after all - click_target = locate(/mob/living) in click_target - if(!click_target) + target = locate(/mob/living) in target + if(!target) to_chat(owner, span_warning("You can only select living beings as secondary target!")) return FALSE - if(click_target == owner) + if(target == owner) if(!isnull(second_target)) to_chat(owner, span_notice("You cancel your secondary swap target!")) second_target = null else to_chat(owner, span_warning("You have no secondary swap target!")) return FALSE - second_target = click_target - to_chat(owner, span_notice("You select [click_target.name] as a secondary swap target!")) + second_target = target + to_chat(owner, span_notice("You select [target.name] as a secondary swap target!")) return FALSE return ..() diff --git a/code/modules/spells/spell_types/self/rod_form.dm b/code/modules/spells/spell_types/self/rod_form.dm index fd9a52be412f..5336036cd2c5 100644 --- a/code/modules/spells/spell_types/self/rod_form.dm +++ b/code/modules/spells/spell_types/self/rod_form.dm @@ -138,9 +138,8 @@ our_wizard = WEAKREF(wizard) wizard.forceMove(src) - wizard.notransform = TRUE wizard.status_flags |= GODMODE - ADD_TRAIT(wizard, TRAIT_MAGICALLY_PHASED, REF(src)) + wizard.add_traits(list(TRAIT_MAGICALLY_PHASED, TRAIT_NO_TRANSFORM), REF(src)) /** * Eject our current wizard, removing them from the rod @@ -152,9 +151,8 @@ return wizard.status_flags &= ~GODMODE - wizard.notransform = FALSE + wizard.remove_traits(list(TRAIT_MAGICALLY_PHASED, TRAIT_NO_TRANSFORM), REF(src)) wizard.forceMove(get_turf(src)) our_wizard = null - REMOVE_TRAIT(wizard, TRAIT_MAGICALLY_PHASED, REF(src)) #undef BASE_WIZ_ROD_RANGE diff --git a/code/modules/spells/spell_types/shapeshift/_shape_status.dm b/code/modules/spells/spell_types/shapeshift/_shape_status.dm index 620a91bf58da..24d41b859a31 100644 --- a/code/modules/spells/spell_types/shapeshift/_shape_status.dm +++ b/code/modules/spells/spell_types/shapeshift/_shape_status.dm @@ -32,7 +32,7 @@ /datum/status_effect/shapechange_mob/on_apply() caster_mob.mind?.transfer_to(owner) caster_mob.forceMove(owner) - caster_mob.notransform = TRUE + ADD_TRAIT(caster_mob, TRAIT_NO_TRANSFORM, REF(src)) caster_mob.apply_status_effect(/datum/status_effect/grouped/stasis, STASIS_SHAPECHANGE_EFFECT) RegisterSignal(owner, COMSIG_LIVING_PRE_WABBAJACKED, PROC_REF(on_wabbajacked)) @@ -77,7 +77,7 @@ UnregisterSignal(caster_mob, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH)) caster_mob.forceMove(owner.loc) - caster_mob.notransform = FALSE + REMOVE_TRAIT(caster_mob, TRAIT_NO_TRANSFORM, REF(src)) caster_mob.remove_status_effect(/datum/status_effect/grouped/stasis, STASIS_SHAPECHANGE_EFFECT) owner.mind?.transfer_to(caster_mob) diff --git a/code/modules/spells/spell_types/shapeshift/shapechange.dm b/code/modules/spells/spell_types/shapeshift/shapechange.dm index bfe3ee0d91c9..d7ff71e0425d 100644 --- a/code/modules/spells/spell_types/shapeshift/shapechange.dm +++ b/code/modules/spells/spell_types/shapeshift/shapechange.dm @@ -11,10 +11,10 @@ spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC possible_shapes = list( + /mob/living/basic/carp/magic/chaos, + /mob/living/basic/construct/juggernaut/mystic, /mob/living/basic/mouse, /mob/living/basic/pet/dog/corgi, - /mob/living/basic/carp/magic/chaos, - /mob/living/simple_animal/bot/secbot/ed209, /mob/living/basic/spider/giant/viper/wizard, - /mob/living/simple_animal/hostile/construct/juggernaut/mystic, + /mob/living/simple_animal/bot/secbot/ed209, ) diff --git a/code/modules/spells/spell_types/touch/scream_for_me.dm b/code/modules/spells/spell_types/touch/scream_for_me.dm index 941885e75776..231b6927e504 100644 --- a/code/modules/spells/spell_types/touch/scream_for_me.dm +++ b/code/modules/spells/spell_types/touch/scream_for_me.dm @@ -21,7 +21,7 @@ span_userdanger("The spell bounces from [victim]'s skin back into your arm!"), ) var/obj/item/bodypart/to_wound = caster.get_holding_bodypart_of_item(hand) - to_wound.force_wound_upwards(/datum/wound/slash/critical) + caster.cause_wound_of_type_and_severity(WOUND_SLASH, to_wound, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_CRITICAL) /datum/action/cooldown/spell/touch/scream_for_me/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/victim, mob/living/carbon/caster) if(!ishuman(victim)) @@ -29,7 +29,7 @@ var/mob/living/carbon/human/human_victim = victim human_victim.emote("scream") for(var/obj/item/bodypart/to_wound as anything in human_victim.bodyparts) - to_wound.force_wound_upwards(/datum/wound/slash/critical) + human_victim.cause_wound_of_type_and_severity(WOUND_SLASH, to_wound, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_CRITICAL) return TRUE /obj/item/melee/touch_attack/scream_for_me diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index ca2cff40edb5..1ce9e26203d8 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -1,3 +1,6 @@ +#define AUGGED_LIMB_EMP_BRUTE_DAMAGE 3 +#define AUGGED_LIMB_EMP_BURN_DAMAGE 2 + /obj/item/bodypart name = "limb" desc = "Why is it detached..." @@ -22,14 +25,15 @@ /// DO NOT MODIFY DIRECTLY. Use set_owner() var/mob/living/carbon/owner + /// If this limb can be scarred. + var/scarrable = TRUE + /** * A bitfield of biological states, exclusively used to determine which wounds this limb will get, * as well as how easily it will happen. - * Set to BIO_FLESH_BONE because most species have both flesh and bone in their limbs. - * - * This currently has absolutely no meaning for robotic limbs. + * Set to BIO_STANDARD_UNJOINTED because most species have both flesh bone and blood in their limbs. */ - var/biological_state = BIO_FLESH_BONE + var/biological_state = BIO_STANDARD_UNJOINTED ///A bitfield of bodytypes for clothing, surgery, and misc information var/bodytype = BODYTYPE_HUMANOID | BODYTYPE_ORGANIC ///Defines when a bodypart should not be changed. Example: BP_BLOCK_CHANGE_SPECIES prevents the limb from being overwritten on species gain @@ -88,11 +92,11 @@ ///Gradually increases while burning when at full damage, destroys the limb when at 100 var/cremation_progress = 0 - // Damage reduction variables for damage handled on the limb level. Handled after worn armor. - ///Amount subtracted from brute damage inflicted on the limb. - var/brute_reduction = 0 - ///Amount subtracted from burn damage inflicted on the limb. - var/burn_reduction = 0 + //Multiplicative damage modifiers + /// Brute damage gets multiplied by this on receive_damage() + var/brute_modifier = 1 + /// Burn damage gets multiplied by this on receive_damage() + var/burn_modifier = 1 //Coloring and proper item icon update var/skin_tone = "" @@ -178,6 +182,35 @@ var/list/bodypart_traits = list() /// The name of the trait source that the organ gives. Should not be altered during the events of gameplay, and will cause problems if it is. var/bodypart_trait_source = BODYPART_TRAIT + /// List of the above datums which have actually been instantiated, managed automatically + var/list/feature_offsets = list() + + /// In the case we dont have dismemberable features, or literally cant get wounds, we will use this percent to determine when we can be dismembered. + /// Compared to our ABSOLUTE maximum. Stored in decimal; 0.8 = 80%. + var/hp_percent_to_dismemberable = 0.8 + /// If true, we will use [hp_percent_to_dismemberable] even if we are dismemberable via wounds. Useful for things with extreme wound resistance. + var/use_alternate_dismemberment_calc_even_if_mangleable = FALSE + /// If false, no wound that can be applied to us can mangle our exterior. Used for determining if we should use [hp_percent_to_dismemberable] instead of normal dismemberment. + var/any_existing_wound_can_mangle_our_exterior + /// If false, no wound that can be applied to us can mangle our interior. Used for determining if we should use [hp_percent_to_dismemberable] instead of normal dismemberment. + var/any_existing_wound_can_mangle_our_interior + +/obj/item/bodypart/apply_fantasy_bonuses(bonus) + . = ..() + unarmed_damage_low = modify_fantasy_variable("unarmed_damage_low", unarmed_damage_low, bonus, minimum = 1) + unarmed_damage_high = modify_fantasy_variable("unarmed_damage_high", unarmed_damage_high, bonus, minimum = 1) + brute_modifier = modify_fantasy_variable("brute_modifier", brute_modifier, bonus * 0.02, minimum = 0.7) + burn_modifier = modify_fantasy_variable("burn_modifier", burn_modifier, bonus * 0.02, minimum = 0.7) + wound_resistance = modify_fantasy_variable("wound_resistance", wound_resistance, bonus) + +/obj/item/bodypart/remove_fantasy_bonuses(bonus) + unarmed_damage_low = reset_fantasy_variable("unarmed_damage_low", unarmed_damage_low) + unarmed_damage_high = reset_fantasy_variable("unarmed_damage_high", unarmed_damage_high) + brute_modifier = reset_fantasy_variable("brute_modifier", brute_modifier) + burn_modifier = reset_fantasy_variable("burn_modifier", burn_modifier) + wound_resistance = reset_fantasy_variable("wound_resistance", wound_resistance) + return ..() + /obj/item/bodypart/Initialize(mapload) . = ..() @@ -403,7 +436,7 @@ //Applies brute and burn damage to the organ. Returns 1 if the damage-icon states changed at all. //Damage will not exceed max_damage using this proc //Cannot apply negative damage -/obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, blocked = 0, updating_health = TRUE, required_bodytype = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null) +/obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, blocked = 0, updating_health = TRUE, forced = FALSE, required_bodytype = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null, damage_source) SHOULD_CALL_PARENT(TRUE) var/area/target_area = get_area(src.owner) if(target_area) @@ -413,16 +446,18 @@ var/hit_percent = (100-blocked)/100 if((!brute && !burn) || hit_percent <= 0) return FALSE - if(owner && (owner.status_flags & GODMODE)) - return FALSE //godmode - if(required_bodytype && !(bodytype & required_bodytype)) - return FALSE + if (!forced) + if(!isnull(owner)) + if (owner.status_flags & GODMODE) + return FALSE + if (SEND_SIGNAL(owner, COMSIG_CARBON_LIMB_DAMAGED, src, brute, burn) & COMPONENT_PREVENT_LIMB_DAMAGE) + return FALSE + if(required_bodytype && !(bodytype & required_bodytype)) + return FALSE var/dmg_multi = CONFIG_GET(number/damage_multiplier) * hit_percent - brute = round(max(brute * dmg_multi, 0),DAMAGE_PRECISION) - burn = round(max(burn * dmg_multi, 0),DAMAGE_PRECISION) - brute = max(0, brute - brute_reduction) - burn = max(0, burn - burn_reduction) + brute = round(max(brute * dmg_multi * brute_modifier, 0), DAMAGE_PRECISION) + burn = round(max(burn * dmg_multi * burn_modifier, 0), DAMAGE_PRECISION) if(!brute && !burn) return FALSE @@ -447,43 +482,42 @@ else if (sharpness & SHARP_POINTY) wounding_type = WOUND_PIERCE - if(owner) + if(owner) // i tried to modularize the below, but the modifications to wounding_dmg and wounding_type cant be extracted to a proc var/mangled_state = get_mangled_state() var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%) - //Handling for bone only/flesh only(none right now)/flesh and bone targets - switch(biological_state) - // if we're fleshless, all cutting attacks go straight to the bone - if(BIO_BONE, BIO_INORGANIC) - if(wounding_type == WOUND_SLASH) - wounding_type = WOUND_BLUNT - wounding_dmg *= (easy_dismember ? 1 : 0.6) - else if(wounding_type == WOUND_PIERCE) - wounding_type = WOUND_BLUNT - wounding_dmg *= (easy_dismember ? 1 : 0.75) - if(((mangled_state & BODYPART_MANGLED_BONE) || biological_state == BIO_INORGANIC) && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)) - return - // note that there's no handling for BIO_FLESH since we don't have any that are that right now (slimepeople maybe someday) - // standard humanoids - if(BIO_FLESH_BONE) - // if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate - // So a big sharp weapon is still all you need to destroy a limb - if((mangled_state & BODYPART_MANGLED_FLESH) && !(mangled_state & BODYPART_MANGLED_BONE) && sharpness) - playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100) - if(wounding_type == WOUND_SLASH && !easy_dismember) - wounding_dmg *= 0.6 // edged weapons pass along 60% of their wounding damage to the bone since the power is spread out over a larger area - if(wounding_type == WOUND_PIERCE && !easy_dismember) - wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated - wounding_type = WOUND_BLUNT - else if((mangled_state & BODYPART_MANGLED_FLESH) && (mangled_state & BODYPART_MANGLED_BONE) && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)) - return + var/bio_status = get_bio_state_status() + var/has_exterior = ((bio_status & ANATOMY_EXTERIOR)) + var/has_interior = ((bio_status & ANATOMY_INTERIOR)) + + var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR))) + + // if we're bone only, all cutting attacks go straight to the bone + if(!has_exterior && has_interior) + if(wounding_type == WOUND_SLASH) + wounding_type = WOUND_BLUNT + wounding_dmg *= (easy_dismember ? 1 : 0.6) + else if(wounding_type == WOUND_PIERCE) + wounding_type = WOUND_BLUNT + wounding_dmg *= (easy_dismember ? 1 : 0.75) + else + // if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate + // So a big sharp weapon is still all you need to destroy a limb + if(has_interior && exterior_ready_to_dismember && !(mangled_state & BODYPART_MANGLED_INTERIOR) && sharpness) + if(wounding_type == WOUND_SLASH && !easy_dismember) + wounding_dmg *= 0.6 // edged weapons pass along 60% of their wounding damage to the bone since the power is spread out over a larger area + if(wounding_type == WOUND_PIERCE && !easy_dismember) + wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated + wounding_type = WOUND_BLUNT + if ((dismemberable_by_wound() || dismemberable_by_total_damage()) && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)) + return // now we have our wounding_type and are ready to carry on with wounds and dealing the actual damage if(wounding_dmg >= WOUND_MINIMUM_DAMAGE && wound_bonus != CANT_WOUND) check_wounding(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus, attack_direction) for(var/datum/wound/iter_wound as anything in wounds) - iter_wound.receive_damage(wounding_type, wounding_dmg, wound_bonus) + iter_wound.receive_damage(wounding_type, wounding_dmg, wound_bonus, damage_source) /* // END WOUND HANDLING @@ -510,6 +544,83 @@ owner.updatehealth() return update_bodypart_damage_state() || . +/// Returns a bitflag using ANATOMY_EXTERIOR or ANATOMY_INTERIOR. Used to determine if we as a whole have a interior or exterior biostate, or both. +/obj/item/bodypart/proc/get_bio_state_status() + SHOULD_BE_PURE(TRUE) + + var/bio_status = NONE + + for (var/state as anything in GLOB.bio_state_anatomy) + var/flag = text2num(state) + if (!(biological_state & flag)) + continue + + var/value = GLOB.bio_state_anatomy[state] + if (value & ANATOMY_EXTERIOR) + bio_status |= ANATOMY_EXTERIOR + if (value & ANATOMY_INTERIOR) + bio_status |= ANATOMY_INTERIOR + + if ((bio_status & ANATOMY_EXTERIOR_AND_INTERIOR) == ANATOMY_EXTERIOR_AND_INTERIOR) + break + + return bio_status + +/// Returns if our current mangling status allows us to be dismembered. Requires both no exterior/mangled exterior and no interior/mangled interior. +/obj/item/bodypart/proc/dismemberable_by_wound() + SHOULD_BE_PURE(TRUE) + + var/mangled_state = get_mangled_state() + + var/bio_status = get_bio_state_status() + + var/has_exterior = ((bio_status & ANATOMY_EXTERIOR)) + var/has_interior = ((bio_status & ANATOMY_INTERIOR)) + + var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR))) + var/interior_ready_to_dismember = (!has_interior || ((mangled_state & BODYPART_MANGLED_INTERIOR))) + + return (exterior_ready_to_dismember && interior_ready_to_dismember) + +/// Returns TRUE if our total percent damage is more or equal to our dismemberable percentage, but FALSE if a wound can cause us to be dismembered. +/obj/item/bodypart/proc/dismemberable_by_total_damage() + + update_wound_theory() + + var/bio_status = get_bio_state_status() + + var/has_interior = ((bio_status & ANATOMY_INTERIOR)) + var/can_theoretically_be_dismembered_by_wound = (any_existing_wound_can_mangle_our_interior || (any_existing_wound_can_mangle_our_exterior && has_interior)) + + var/wound_dismemberable = dismemberable_by_wound() + var/ready_to_use_alternate_formula = (use_alternate_dismemberment_calc_even_if_mangleable || (!wound_dismemberable && !can_theoretically_be_dismembered_by_wound)) + + if (ready_to_use_alternate_formula) + var/percent_to_total_max = (get_damage() / max_damage) + if (percent_to_total_max >= hp_percent_to_dismemberable) + return TRUE + + return FALSE + +/// Updates our "can be theoretically dismembered by wounds" variables by iterating through all wound static data. +/obj/item/bodypart/proc/update_wound_theory() + // We put this here so we dont increase init time by doing this all at once on initialization + // Effectively, we "lazy load" + if (isnull(any_existing_wound_can_mangle_our_interior) || isnull(any_existing_wound_can_mangle_our_exterior)) + any_existing_wound_can_mangle_our_interior = FALSE + any_existing_wound_can_mangle_our_exterior = FALSE + for (var/datum/wound/wound_type as anything in GLOB.all_wound_pregen_data) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound_type] + if (!pregen_data.can_be_applied_to(src, random_roll = TRUE)) // we only consider randoms because non-randoms are usually really specific + continue + if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_EXTERIOR) + any_existing_wound_can_mangle_our_exterior = TRUE + if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_INTERIOR) + any_existing_wound_can_mangle_our_interior = TRUE + + if (any_existing_wound_can_mangle_our_interior && any_existing_wound_can_mangle_our_exterior) + break + //Heals brute and burn damage for the organ. Returns 1 if the damage-icon states changed at all. //Damage cannot go below zero. //Cannot remove negative damage (i.e. apply damage) @@ -987,7 +1098,7 @@ if(!owner) return - if(HAS_TRAIT(owner, TRAIT_NOBLOOD) || (!IS_ORGANIC_LIMB(src) && !HAS_TRAIT(src.owner, TRAIT_ROBOT_CAN_BLEED))) + if(!can_bleed()) if(cached_bleed_rate != old_bleed_rate) update_part_wound_overlay() return @@ -1002,9 +1113,6 @@ for(var/datum/wound/iter_wound as anything in wounds) cached_bleed_rate += iter_wound.blood_flow - if(!cached_bleed_rate) - QDEL_NULL(grasped_by) - // Our bleed overlay is based directly off bleed_rate, so go aheead and update that would you? if(cached_bleed_rate != old_bleed_rate) update_part_wound_overlay() @@ -1028,7 +1136,7 @@ /obj/item/bodypart/proc/update_part_wound_overlay() if(!owner) return FALSE - if(HAS_TRAIT(owner, TRAIT_NOBLOOD) || !IS_ORGANIC_LIMB(src)) + if(!can_bleed()) if(bleed_overlay_icon) bleed_overlay_icon = null owner.update_wound_overlays() @@ -1061,6 +1169,11 @@ #undef BLEED_OVERLAY_MED #undef BLEED_OVERLAY_GUSH +/obj/item/bodypart/proc/can_bleed() + SHOULD_BE_PURE(TRUE) + + return ((biological_state & BIO_BLOODED) && (!owner || !HAS_TRAIT(owner, TRAIT_NOBLOOD))) + /** * apply_gauze() is used to- well, apply gauze to a bodypart * @@ -1146,3 +1259,47 @@ owner.update_body_parts() else update_icon_dropped() + +/obj/item/bodypart/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_WIRES || !IS_ROBOTIC_LIMB(src)) + return FALSE + owner.visible_message(span_danger("[owner]'s [src.name] seems to malfunction!")) + + // with defines at the time of writing, this is 3 brute and 2 burn + // 3 + 2 = 5, with 6 limbs thats 30, on a heavy 60 + // 60 * 0.8 = 48 + var/time_needed = 10 SECONDS + var/brute_damage = AUGGED_LIMB_EMP_BRUTE_DAMAGE + var/burn_damage = AUGGED_LIMB_EMP_BURN_DAMAGE + if(severity == EMP_HEAVY) + time_needed *= 2 + brute_damage *= 2 + burn_damage *= 2 + + receive_damage(brute_damage, burn_damage) + do_sparks(number = 1, cardinal_only = FALSE, source = owner) + ADD_TRAIT(src, TRAIT_PARALYSIS, EMP_TRAIT) + addtimer(CALLBACK(src, PROC_REF(un_paralyze)), time_needed) + return TRUE + +/obj/item/bodypart/proc/un_paralyze() + REMOVE_TRAITS_IN(src, EMP_TRAIT) + +/// Returns the generic description of our BIO_EXTERNAL feature(s), prioritizing certain ones over others. Returns error on failure. +/obj/item/bodypart/proc/get_external_description() + if (biological_state & BIO_FLESH) + return "flesh" + if (biological_state & BIO_WIRED) + return "wiring" + + return "error" + +/// Returns the generic description of our BIO_INTERNAL feature(s), prioritizing certain ones over others. Returns error on failure. +/obj/item/bodypart/proc/get_internal_description() + if (biological_state & BIO_BONE) + return "bone" + if (biological_state & BIO_METAL) + return "metal" + + return "error" diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index c64204193ea0..58eb7c8fbc0d 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -1,11 +1,11 @@ /obj/item/bodypart/proc/can_dismember(obj/item/item) - if(bodypart_flags & BODYPART_UNREMOVABLE) + if(bodypart_flags & BODYPART_UNREMOVABLE || (owner && HAS_TRAIT(owner, TRAIT_NODISMEMBER))) return FALSE return TRUE ///Remove target limb from it's owner, with side effects. -/obj/item/bodypart/proc/dismember(dam_type = BRUTE, silent=TRUE) +/obj/item/bodypart/proc/dismember(dam_type = BRUTE, silent=TRUE, wounding_type) if(!owner || (bodypart_flags & BODYPART_UNREMOVABLE)) return FALSE var/mob/living/carbon/limb_owner = owner @@ -22,11 +22,15 @@ playsound(get_turf(limb_owner), 'sound/effects/dismember.ogg', 80, TRUE) limb_owner.add_mood_event("dismembered", /datum/mood_event/dismembered) limb_owner.add_mob_memory(/datum/memory/was_dismembered, lost_limb = src) + + if (wounding_type) + LAZYSET(limb_owner.body_zone_dismembered_by, body_zone, wounding_type) + drop_limb() limb_owner.update_equipment_speed_mods() // Update in case speed affecting item unequipped by dismemberment var/turf/owner_location = limb_owner.loc - if(istype(owner_location)) + if(wounding_type != WOUND_BURN && istype(owner_location) && can_bleed()) limb_owner.add_splatter_floor(owner_location) if(QDELETED(src)) //Could have dropped into lava/explosion/chasm/whatever @@ -34,8 +38,9 @@ if(dam_type == BURN) burn() return TRUE - add_mob_blood(limb_owner) - limb_owner.bleed(rand(20, 40)) + if (can_bleed()) + add_mob_blood(limb_owner) + limb_owner.bleed(rand(20, 40)) var/direction = pick(GLOB.cardinals) var/t_range = rand(2,max(throw_range/2, 2)) var/turf/target_turf = get_turf(src) @@ -47,10 +52,10 @@ if(new_turf.density) break throw_at(target_turf, throw_range, throw_speed) - return TRUE + return TRUE -/obj/item/bodypart/chest/dismember() +/obj/item/bodypart/chest/dismember(dam_type = BRUTE, silent=TRUE, wounding_type) if(!owner) return FALSE var/mob/living/carbon/chest_owner = owner @@ -59,7 +64,7 @@ if(HAS_TRAIT(chest_owner, TRAIT_NODISMEMBER)) return FALSE . = list() - if(isturf(chest_owner.loc)) + if(wounding_type != WOUND_BURN && isturf(chest_owner.loc) && can_bleed()) chest_owner.add_splatter_floor(chest_owner.loc) playsound(get_turf(chest_owner), 'sound/misc/splort.ogg', 80, TRUE) for(var/obj/item/organ/organ as anything in chest_owner.organs) @@ -81,8 +86,6 @@ . += cavity_item cavity_item = null - - ///limb removal. The "special" argument is used for swapping a limb with a new one without the effects of losing a limb kicking in. /obj/item/bodypart/proc/drop_limb(special, dismembered) if(!owner) @@ -160,16 +163,16 @@ * Dismemberment for flesh and bone requires the victim to have the skin on their bodypart destroyed (either a critical cut or piercing wound), and at least a hairline fracture * (severe bone), at which point we can start rolling for dismembering. The attack must also deal at least 10 damage, and must be a brute attack of some kind (sorry for now, cakehat, maybe later) * - * Returns: BODYPART_MANGLED_NONE if we're fine, BODYPART_MANGLED_FLESH if our skin is broken, BODYPART_MANGLED_BONE if our bone is broken, or BODYPART_MANGLED_BOTH if both are broken and we're up for dismembering + * Returns: BODYPART_MANGLED_NONE if we're fine, BODYPART_MANGLED_EXTERIOR if our skin is broken, BODYPART_MANGLED_INTERIOR if our bone is broken, or BODYPART_MANGLED_BOTH if both are broken and we're up for dismembering */ /obj/item/bodypart/proc/get_mangled_state() . = BODYPART_MANGLED_NONE for(var/datum/wound/iter_wound as anything in wounds) - if((iter_wound.wound_flags & MANGLES_BONE)) - . |= BODYPART_MANGLED_BONE - if((iter_wound.wound_flags & MANGLES_FLESH)) - . |= BODYPART_MANGLED_FLESH + if((iter_wound.wound_flags & MANGLES_INTERIOR)) + . |= BODYPART_MANGLED_INTERIOR + if((iter_wound.wound_flags & MANGLES_EXTERIOR)) + . |= BODYPART_MANGLED_EXTERIOR /** * try_dismember() is used, once we've confirmed that a flesh and bone bodypart has both the skin and bone mangled, to actually roll for it @@ -185,14 +188,17 @@ * * bare_wound_bonus: ditto above */ /obj/item/bodypart/proc/try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus) + if (!can_dismember()) + return + if(wounding_dmg < DISMEMBER_MINIMUM_DAMAGE) return var/base_chance = wounding_dmg base_chance += (get_damage() / max_damage * 50) // how much damage we dealt with this blow, + 50% of the damage percentage we already had on this bodypart - if(locate(/datum/wound/blunt/critical) in wounds) // we only require a severe bone break, but if there's a critical bone break, we'll add 15% more - base_chance += 15 + for (var/datum/wound/iterated_wound as anything in wounds) + base_chance += iterated_wound.get_dismember_chance_bonus(base_chance) if(prob(base_chance)) var/datum/wound/loss/dismembering = new @@ -340,6 +346,8 @@ hand.update_appearance() new_limb_owner.update_worn_gloves() + LAZYREMOVE(new_limb_owner.body_zone_dismembered_by, body_zone) + if(special) //non conventional limb attachment for(var/datum/surgery/attach_surgery as anything in new_limb_owner.surgeries) //if we had an ongoing surgery to attach a new limb, we stop it. var/surgery_zone = check_zone(attach_surgery.location) @@ -443,12 +451,15 @@ /mob/living/carbon/proc/regenerate_limbs(list/excluded_zones = list()) SEND_SIGNAL(src, COMSIG_CARBON_REGENERATE_LIMBS, excluded_zones) var/list/zone_list = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) + + var/list/dismembered_by_copy = body_zone_dismembered_by?.Copy() + if(length(excluded_zones)) zone_list -= excluded_zones for(var/limb_zone in zone_list) - regenerate_limb(limb_zone) + regenerate_limb(limb_zone, dismembered_by_copy) -/mob/living/carbon/proc/regenerate_limb(limb_zone) +/mob/living/carbon/proc/regenerate_limb(limb_zone, list/dismembered_by_copy = body_zone_dismembered_by?.Copy()) var/obj/item/bodypart/limb if(get_bodypart(limb_zone)) return FALSE @@ -458,9 +469,16 @@ qdel(limb) return FALSE limb.update_limb(is_creating = TRUE) - var/datum/scar/scaries = new - var/datum/wound/loss/phantom_loss = new // stolen valor, really - scaries.generate(limb, phantom_loss) + if (LAZYLEN(dismembered_by_copy)) + var/datum/scar/scaries = new + var/datum/wound/loss/phantom_loss = new // stolen valor, really + phantom_loss.loss_wounding_type = dismembered_by_copy?[limb_zone] + if (phantom_loss.loss_wounding_type) + scaries.generate(limb, phantom_loss) + LAZYREMOVE(dismembered_by_copy, limb_zone) // in case we're using a passed list + else + qdel(scaries) + qdel(phantom_loss) //Copied from /datum/species/proc/on_species_gain() for(var/obj/item/organ/external/organ_path as anything in dna.species.external_organs) diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index 57001668ec8b..5aeb7363ddf3 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -174,15 +174,12 @@ /obj/item/bodypart/head/update_limb(dropping_limb, is_creating) . = ..() - - real_name = owner.real_name - if(HAS_TRAIT(owner, TRAIT_HUSK)) - real_name = "Unknown" - hair_style = "Bald" - facial_hairstyle = "Shaved" - lip_style = null - stored_lipstick_trait = null - update_hair_and_lips() + if(!isnull(owner)) + if(HAS_TRAIT(owner, TRAIT_HUSK)) + real_name = "Unknown" + else + real_name = owner.real_name + update_hair_and_lips(dropping_limb, is_creating) ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm index 89c7634cb5a9..dddf77755807 100644 --- a/code/modules/surgery/bodyparts/parts.dm +++ b/code/modules/surgery/bodyparts/parts.dm @@ -31,7 +31,7 @@ if(cavity_item) cavity_item.forceMove(drop_location()) cavity_item = null - ..() + return ..() /obj/item/bodypart/chest/monkey icon = 'icons/mob/species/monkey/bodyparts.dmi' @@ -88,6 +88,11 @@ unarmed_stun_threshold = 10 body_zone = BODY_ZONE_L_ARM + biological_state = BIO_STANDARD_JOINTED + +/obj/item/bodypart/arm/Destroy() + return ..() + /obj/item/bodypart/arm/left name = "left arm" desc = "Did you know that the word 'sinister' stems originally from the \ @@ -312,6 +317,11 @@ unarmed_damage_high = 15 unarmed_stun_threshold = 10 + biological_state = BIO_STANDARD_JOINTED + +/obj/item/bodypart/leg/Destroy() + return ..() + /obj/item/bodypart/leg/left name = "left leg" desc = "Some athletes prefer to tie their left shoelaces first for good \ diff --git a/code/modules/surgery/bodyparts/robot_bodyparts.dm b/code/modules/surgery/bodyparts/robot_bodyparts.dm index e4663d271ed9..f0d40f495c75 100644 --- a/code/modules/surgery/bodyparts/robot_bodyparts.dm +++ b/code/modules/surgery/bodyparts/robot_bodyparts.dm @@ -26,8 +26,8 @@ change_exempt_flags = BP_BLOCK_CHANGE_SPECIES dmg_overlay_type = "robotic" - brute_reduction = 5 - burn_reduction = 4 + brute_modifier = 0.8 + burn_modifier = 0.8 light_brute_msg = ROBOTIC_LIGHT_BRUTE_MSG medium_brute_msg = ROBOTIC_MEDIUM_BRUTE_MSG @@ -37,6 +37,8 @@ medium_burn_msg = ROBOTIC_MEDIUM_BURN_MSG heavy_burn_msg = ROBOTIC_HEAVY_BURN_MSG + biological_state = (BIO_ROBOTIC|BIO_JOINTED) + damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) disabling_threshold_percentage = 1 @@ -56,8 +58,8 @@ change_exempt_flags = BP_BLOCK_CHANGE_SPECIES dmg_overlay_type = "robotic" - brute_reduction = 5 - burn_reduction = 4 + brute_modifier = 0.8 + burn_modifier = 0.8 disabling_threshold_percentage = 1 light_brute_msg = ROBOTIC_LIGHT_BRUTE_MSG @@ -68,6 +70,8 @@ medium_burn_msg = ROBOTIC_MEDIUM_BURN_MSG heavy_burn_msg = ROBOTIC_HEAVY_BURN_MSG + biological_state = (BIO_ROBOTIC|BIO_JOINTED) + damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) /obj/item/bodypart/leg/left/robot @@ -86,8 +90,8 @@ change_exempt_flags = BP_BLOCK_CHANGE_SPECIES dmg_overlay_type = "robotic" - brute_reduction = 5 - burn_reduction = 4 + brute_modifier = 0.8 + burn_modifier = 0.8 disabling_threshold_percentage = 1 light_brute_msg = ROBOTIC_LIGHT_BRUTE_MSG @@ -98,6 +102,8 @@ medium_burn_msg = ROBOTIC_MEDIUM_BURN_MSG heavy_burn_msg = ROBOTIC_HEAVY_BURN_MSG + biological_state = (BIO_ROBOTIC|BIO_JOINTED) + damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) /obj/item/bodypart/leg/right/robot @@ -116,8 +122,8 @@ change_exempt_flags = BP_BLOCK_CHANGE_SPECIES dmg_overlay_type = "robotic" - brute_reduction = 5 - burn_reduction = 4 + brute_modifier = 0.8 + burn_modifier = 0.8 disabling_threshold_percentage = 1 light_brute_msg = ROBOTIC_LIGHT_BRUTE_MSG @@ -128,6 +134,8 @@ medium_burn_msg = ROBOTIC_MEDIUM_BURN_MSG heavy_burn_msg = ROBOTIC_HEAVY_BURN_MSG + biological_state = (BIO_ROBOTIC|BIO_JOINTED) + damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) /obj/item/bodypart/chest/robot @@ -145,8 +153,8 @@ change_exempt_flags = BP_BLOCK_CHANGE_SPECIES dmg_overlay_type = "robotic" - brute_reduction = 5 - burn_reduction = 4 + brute_modifier = 0.8 + burn_modifier = 0.8 light_brute_msg = ROBOTIC_LIGHT_BRUTE_MSG medium_brute_msg = ROBOTIC_MEDIUM_BRUTE_MSG @@ -156,6 +164,8 @@ medium_burn_msg = ROBOTIC_MEDIUM_BURN_MSG heavy_burn_msg = ROBOTIC_HEAVY_BURN_MSG + biological_state = (BIO_ROBOTIC) + damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) var/wired = FALSE @@ -257,8 +267,8 @@ change_exempt_flags = BP_BLOCK_CHANGE_SPECIES dmg_overlay_type = "robotic" - brute_reduction = 5 - burn_reduction = 4 + brute_modifier = 0.8 + burn_modifier = 0.8 light_brute_msg = ROBOTIC_LIGHT_BRUTE_MSG medium_brute_msg = ROBOTIC_MEDIUM_BRUTE_MSG @@ -268,6 +278,8 @@ medium_burn_msg = ROBOTIC_MEDIUM_BURN_MSG heavy_burn_msg = ROBOTIC_HEAVY_BURN_MSG + biological_state = (BIO_ROBOTIC) + damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) var/obj/item/assembly/flash/handheld/flash1 = null @@ -347,43 +359,56 @@ +// Prosthetics - Cheap, mediocre, and worse than organic limbs +// The fact they dont have a internal biotype means theyre a lot weaker defensively, +// since they skip slash and go right to blunt +// They are VERY easy to delimb as a result +// HP is also reduced just in case this isnt enough /obj/item/bodypart/arm/left/robot/surplus name = "surplus prosthetic left arm" desc = "A skeletal, robotic limb. Outdated and fragile, but it's still better than nothing." icon_static = 'icons/mob/augmentation/surplus_augments.dmi' icon = 'icons/mob/augmentation/surplus_augments.dmi' - brute_reduction = 0 - burn_reduction = 0 + burn_modifier = 1 + brute_modifier = 1 max_damage = 20 + biological_state = (BIO_METAL|BIO_JOINTED) + /obj/item/bodypart/arm/right/robot/surplus name = "surplus prosthetic right arm" desc = "A skeletal, robotic limb. Outdated and fragile, but it's still better than nothing." icon_static = 'icons/mob/augmentation/surplus_augments.dmi' icon = 'icons/mob/augmentation/surplus_augments.dmi' - brute_reduction = 0 - burn_reduction = 0 + burn_modifier = 1 + brute_modifier = 1 max_damage = 20 + biological_state = (BIO_METAL|BIO_JOINTED) + /obj/item/bodypart/leg/left/robot/surplus name = "surplus prosthetic left leg" desc = "A skeletal, robotic limb. Outdated and fragile, but it's still better than nothing." icon_static = 'icons/mob/augmentation/surplus_augments.dmi' icon = 'icons/mob/augmentation/surplus_augments.dmi' - brute_reduction = 0 - burn_reduction = 0 + brute_modifier = 1 + burn_modifier = 1 max_damage = 20 + biological_state = (BIO_METAL|BIO_JOINTED) + /obj/item/bodypart/leg/right/robot/surplus name = "surplus prosthetic right leg" desc = "A skeletal, robotic limb. Outdated and fragile, but it's still better than nothing." icon_static = 'icons/mob/augmentation/surplus_augments.dmi' icon = 'icons/mob/augmentation/surplus_augments.dmi' - brute_reduction = 0 - burn_reduction = 0 + brute_modifier = 1 + burn_modifier = 1 max_damage = 20 + biological_state = (BIO_METAL|BIO_JOINTED) + #undef ROBOTIC_LIGHT_BRUTE_MSG #undef ROBOTIC_MEDIUM_BRUTE_MSG #undef ROBOTIC_HEAVY_BRUTE_MSG diff --git a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm index 491daa46cba8..0d156b2a707c 100644 --- a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm @@ -28,124 +28,118 @@ ///ABDUCTOR /obj/item/bodypart/head/abductor - biological_state = BIO_INORGANIC //i have no fucking clue why these mfs get no wounds but SURE limb_id = SPECIES_ABDUCTOR is_dimorphic = FALSE should_draw_greyscale = FALSE /obj/item/bodypart/chest/abductor - biological_state = BIO_INORGANIC limb_id = SPECIES_ABDUCTOR is_dimorphic = FALSE should_draw_greyscale = FALSE /obj/item/bodypart/arm/left/abductor - biological_state = BIO_INORGANIC limb_id = SPECIES_ABDUCTOR should_draw_greyscale = FALSE bodypart_traits = list(TRAIT_CHUNKYFINGERS) /obj/item/bodypart/arm/right/abductor - biological_state = BIO_INORGANIC limb_id = SPECIES_ABDUCTOR should_draw_greyscale = FALSE bodypart_traits = list(TRAIT_CHUNKYFINGERS) /obj/item/bodypart/leg/left/abductor - biological_state = BIO_INORGANIC limb_id = SPECIES_ABDUCTOR should_draw_greyscale = FALSE /obj/item/bodypart/leg/right/abductor - biological_state = BIO_INORGANIC limb_id = SPECIES_ABDUCTOR should_draw_greyscale = FALSE ///JELLY /obj/item/bodypart/head/jelly - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_JELLYPERSON is_dimorphic = TRUE dmg_overlay_type = null /obj/item/bodypart/chest/jelly - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_JELLYPERSON is_dimorphic = TRUE dmg_overlay_type = null /obj/item/bodypart/arm/left/jelly - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null /obj/item/bodypart/arm/right/jelly - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null /obj/item/bodypart/leg/left/jelly - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null /obj/item/bodypart/leg/right/jelly - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null ///SLIME /obj/item/bodypart/head/slime - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_SLIMEPERSON is_dimorphic = FALSE /obj/item/bodypart/chest/slime - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_SLIMEPERSON is_dimorphic = TRUE /obj/item/bodypart/arm/left/slime - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) limb_id = SPECIES_SLIMEPERSON /obj/item/bodypart/arm/right/slime - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) limb_id = SPECIES_SLIMEPERSON /obj/item/bodypart/leg/left/slime - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_SLIMEPERSON /obj/item/bodypart/leg/right/slime - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) limb_id = SPECIES_SLIMEPERSON ///LUMINESCENT /obj/item/bodypart/head/luminescent - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_LUMINESCENT is_dimorphic = TRUE /obj/item/bodypart/chest/luminescent - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_LUMINESCENT is_dimorphic = TRUE /obj/item/bodypart/arm/left/luminescent - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) limb_id = SPECIES_LUMINESCENT /obj/item/bodypart/arm/right/luminescent - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) limb_id = SPECIES_LUMINESCENT /obj/item/bodypart/leg/left/luminescent - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) limb_id = SPECIES_LUMINESCENT /obj/item/bodypart/leg/right/luminescent - biological_state = BIO_INORGANIC + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) limb_id = SPECIES_LUMINESCENT ///ZOMBIE @@ -233,34 +227,28 @@ ///SHADOW /obj/item/bodypart/head/shadow - biological_state = BIO_INORGANIC limb_id = SPECIES_SHADOW is_dimorphic = FALSE should_draw_greyscale = FALSE /obj/item/bodypart/chest/shadow - biological_state = BIO_INORGANIC limb_id = SPECIES_SHADOW is_dimorphic = FALSE should_draw_greyscale = FALSE /obj/item/bodypart/arm/left/shadow - biological_state = BIO_INORGANIC limb_id = SPECIES_SHADOW should_draw_greyscale = FALSE /obj/item/bodypart/arm/right/shadow - biological_state = BIO_INORGANIC limb_id = SPECIES_SHADOW should_draw_greyscale = FALSE /obj/item/bodypart/leg/left/shadow - biological_state = BIO_INORGANIC limb_id = SPECIES_SHADOW should_draw_greyscale = FALSE /obj/item/bodypart/leg/right/shadow - biological_state = BIO_INORGANIC limb_id = SPECIES_SHADOW should_draw_greyscale = FALSE @@ -286,25 +274,25 @@ dmg_overlay_type = null /obj/item/bodypart/arm/left/skeleton - biological_state = BIO_BONE + biological_state = (BIO_BONE|BIO_JOINTED) limb_id = SPECIES_SKELETON should_draw_greyscale = FALSE dmg_overlay_type = null /obj/item/bodypart/arm/right/skeleton - biological_state = BIO_BONE + biological_state = (BIO_BONE|BIO_JOINTED) limb_id = SPECIES_SKELETON should_draw_greyscale = FALSE dmg_overlay_type = null /obj/item/bodypart/leg/left/skeleton - biological_state = BIO_BONE + biological_state = (BIO_BONE|BIO_JOINTED) limb_id = SPECIES_SKELETON should_draw_greyscale = FALSE dmg_overlay_type = null /obj/item/bodypart/leg/right/skeleton - biological_state = BIO_BONE + biological_state = (BIO_BONE|BIO_JOINTED) limb_id = SPECIES_SKELETON should_draw_greyscale = FALSE dmg_overlay_type = null @@ -345,20 +333,24 @@ ///GOLEMS (i hate xenobio SO FUCKING MUCH) (from 2022: Yeah I fucking feel your pain brother) /obj/item/bodypart/head/golem - biological_state = BIO_INORGANIC + biological_state = BIO_BONE + bodytype = BODYTYPE_ORGANIC limb_id = SPECIES_GOLEM is_dimorphic = FALSE dmg_overlay_type = null /obj/item/bodypart/chest/golem - biological_state = BIO_INORGANIC + biological_state = BIO_BONE + acceptable_bodytype = BODYTYPE_ORGANIC + bodytype = BODYTYPE_ORGANIC limb_id = SPECIES_GOLEM is_dimorphic = TRUE dmg_overlay_type = null bodypart_traits = list(TRAIT_NO_JUMPSUIT) /obj/item/bodypart/arm/left/golem - biological_state = BIO_INORGANIC + biological_state = (BIO_BONE|BIO_JOINTED) + bodytype = BODYTYPE_ORGANIC limb_id = SPECIES_GOLEM dmg_overlay_type = null bodypart_traits = list(TRAIT_CHUNKYFINGERS) @@ -378,7 +370,8 @@ old_owner.RemoveComponentSource(REF(src), /datum/component/shovel_hands) /obj/item/bodypart/arm/right/golem - biological_state = BIO_INORGANIC + biological_state = (BIO_BONE|BIO_JOINTED) + bodytype = BODYTYPE_ORGANIC limb_id = SPECIES_GOLEM dmg_overlay_type = null bodypart_traits = list(TRAIT_CHUNKYFINGERS) @@ -398,7 +391,8 @@ old_owner.RemoveComponentSource(REF(src), /datum/component/shovel_hands) /obj/item/bodypart/leg/left/golem - biological_state = BIO_INORGANIC + biological_state = (BIO_BONE|BIO_JOINTED) + bodytype = BODYTYPE_ORGANIC limb_id = SPECIES_GOLEM dmg_overlay_type = null unarmed_damage_low = 7 @@ -406,7 +400,8 @@ unarmed_stun_threshold = 11 /obj/item/bodypart/leg/right/golem - biological_state = BIO_INORGANIC + biological_state = (BIO_BONE|BIO_JOINTED) + bodytype = BODYTYPE_ORGANIC limb_id = SPECIES_GOLEM dmg_overlay_type = null unarmed_damage_low = 7 diff --git a/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm index 8adc098844d6..3756cee1e8b3 100644 --- a/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm @@ -22,7 +22,7 @@ icon = 'icons/mob/species/plasmaman/bodyparts.dmi' icon_state = "plasmaman_l_arm" icon_static = 'icons/mob/species/plasmaman/bodyparts.dmi' - biological_state = BIO_BONE + biological_state = (BIO_BONE|BIO_JOINTED) limb_id = SPECIES_PLASMAMAN should_draw_greyscale = FALSE dmg_overlay_type = null @@ -31,7 +31,7 @@ icon = 'icons/mob/species/plasmaman/bodyparts.dmi' icon_state = "plasmaman_r_arm" icon_static = 'icons/mob/species/plasmaman/bodyparts.dmi' - biological_state = BIO_BONE + biological_state = (BIO_BONE|BIO_JOINTED) limb_id = SPECIES_PLASMAMAN should_draw_greyscale = FALSE dmg_overlay_type = null @@ -40,7 +40,7 @@ icon = 'icons/mob/species/plasmaman/bodyparts.dmi' icon_state = "plasmaman_l_leg" icon_static = 'icons/mob/species/plasmaman/bodyparts.dmi' - biological_state = BIO_BONE + biological_state = (BIO_BONE|BIO_JOINTED) limb_id = SPECIES_PLASMAMAN should_draw_greyscale = FALSE dmg_overlay_type = null @@ -49,7 +49,7 @@ icon = 'icons/mob/species/plasmaman/bodyparts.dmi' icon_state = "plasmaman_r_leg" icon_static = 'icons/mob/species/plasmaman/bodyparts.dmi' - biological_state = BIO_BONE + biological_state = (BIO_BONE|BIO_JOINTED) limb_id = SPECIES_PLASMAMAN should_draw_greyscale = FALSE dmg_overlay_type = null diff --git a/code/modules/surgery/bodyparts/wounds.dm b/code/modules/surgery/bodyparts/wounds.dm index 11ff3ff248e7..3d2a6c4056eb 100644 --- a/code/modules/surgery/bodyparts/wounds.dm +++ b/code/modules/surgery/bodyparts/wounds.dm @@ -1,47 +1,40 @@ /// Allows us to roll for and apply a wound without actually dealing damage. Used for aggregate wounding power with pellet clouds -/obj/item/bodypart/proc/painless_wound_roll(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus, sharpness=NONE) +/obj/item/bodypart/proc/painless_wound_roll(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus, sharpness=NONE) SHOULD_CALL_PARENT(TRUE) - if(!owner || phantom_wounding_dmg <= WOUND_MINIMUM_DAMAGE || wound_bonus == CANT_WOUND) + if(!owner || wounding_dmg <= WOUND_MINIMUM_DAMAGE || wound_bonus == CANT_WOUND || (owner.status_flags & GODMODE)) return var/mangled_state = get_mangled_state() var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%) - if(wounding_type == WOUND_BLUNT && sharpness) - if(sharpness & SHARP_EDGED) - wounding_type = WOUND_SLASH - else if (sharpness & SHARP_POINTY) - wounding_type = WOUND_PIERCE + var/bio_status = get_bio_state_status() - //Handling for bone only/flesh only(none right now)/flesh and bone targets - switch(biological_state) - // if we're bone only, all cutting attacks go straight to the bone - if(BIO_BONE) - if(wounding_type == WOUND_SLASH) - wounding_type = WOUND_BLUNT - phantom_wounding_dmg *= (easy_dismember ? 1 : 0.6) - else if(wounding_type == WOUND_PIERCE) - wounding_type = WOUND_BLUNT - phantom_wounding_dmg *= (easy_dismember ? 1 : 0.75) - if((mangled_state & BODYPART_MANGLED_BONE) && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus)) - return - // note that there's no handling for BIO_FLESH since we don't have any that are that right now (slimepeople maybe someday) - // standard humanoids - if(BIO_FLESH_BONE) - // if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate - // So a big sharp weapon is still all you need to destroy a limb - if((mangled_state & BODYPART_MANGLED_FLESH) && !(mangled_state & BODYPART_MANGLED_BONE) && sharpness) - playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100) - if(wounding_type == WOUND_SLASH && !easy_dismember) - phantom_wounding_dmg *= 0.6 // edged weapons pass along 60% of their wounding damage to the bone since the power is spread out over a larger area - if(wounding_type == WOUND_PIERCE && !easy_dismember) - phantom_wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated - wounding_type = WOUND_BLUNT - else if((mangled_state & BODYPART_MANGLED_FLESH) && (mangled_state & BODYPART_MANGLED_BONE) && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus)) - return - - check_wounding(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus) + var/has_exterior = ((bio_status & ANATOMY_EXTERIOR)) + var/has_interior = ((bio_status & ANATOMY_INTERIOR)) + + var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR))) + + // if we're bone only, all cutting attacks go straight to the bone + if(!has_exterior && has_interior) + if(wounding_type == WOUND_SLASH) + wounding_type = WOUND_BLUNT + wounding_dmg *= (easy_dismember ? 1 : 0.6) + else if(wounding_type == WOUND_PIERCE) + wounding_type = WOUND_BLUNT + wounding_dmg *= (easy_dismember ? 1 : 0.75) + else + // if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate + // So a big sharp weapon is still all you need to destroy a limb + if(has_interior && exterior_ready_to_dismember && !(mangled_state & BODYPART_MANGLED_INTERIOR) && sharpness) + if(wounding_type == WOUND_SLASH && !easy_dismember) + wounding_dmg *= 0.6 // edged weapons pass along 60% of their wounding damage to the bone since the power is spread out over a larger area + if(wounding_type == WOUND_PIERCE && !easy_dismember) + wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated + wounding_type = WOUND_BLUNT + if ((dismemberable_by_wound() || dismemberable_by_total_damage()) && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)) + return + return check_wounding(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus) /** * check_wounding() is where we handle rolling for, selecting, and applying a wound if we meet the criteria @@ -55,7 +48,7 @@ * * wound_bonus- The wound_bonus of an attack * * bare_wound_bonus- The bare_wound_bonus of an attack */ -/obj/item/bodypart/proc/check_wounding(woundtype, damage, wound_bonus, bare_wound_bonus, attack_direction) +/obj/item/bodypart/proc/check_wounding(woundtype, damage, wound_bonus, bare_wound_bonus, attack_direction, damage_source) SHOULD_CALL_PARENT(TRUE) RETURN_TYPE(/datum/wound) @@ -77,12 +70,18 @@ var/base_roll = rand(1, round(damage ** WOUND_DAMAGE_EXPONENT)) var/injury_roll = base_roll injury_roll += check_woundings_mods(woundtype, damage, wound_bonus, bare_wound_bonus) + var/list/series_wounding_mods = check_series_wounding_mods() + if(injury_roll > WOUND_DISMEMBER_OUTRIGHT_THRESH && prob(get_damage() / max_damage * 100)) var/datum/wound/loss/dismembering = new dismembering.apply_dismember(src, woundtype, outright = TRUE, attack_direction = attack_direction) return - var/list/wounds_checking = GLOB.global_wound_types[woundtype] + var/list/datum/wound/possible_wounds = list() + for (var/datum/wound/type as anything in GLOB.all_wound_pregen_data) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[type] + if (pregen_data.can_be_applied_to(src, list(woundtype), random_roll = TRUE)) + possible_wounds[type] = pregen_data.get_weight(src, woundtype, damage, attack_direction, damage_source) // quick re-check to see if bare_wound_bonus applies, for the benefit of log_wound(), see about getting the check from check_woundings_mods() somehow if(ishuman(owner)) var/mob/living/carbon/human/human_wearer = owner @@ -93,39 +92,137 @@ bare_wound_bonus = 0 break - //cycle through the wounds of the relevant category from the most severe down - for(var/datum/wound/possible_wound as anything in wounds_checking) + for (var/datum/wound/iterated_path as anything in possible_wounds) + for (var/datum/wound/existing_wound as anything in wounds) + if (iterated_path == existing_wound.type) + possible_wounds -= iterated_path + break // breaks out of the nested loop + + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[iterated_path] + var/specific_injury_roll = (injury_roll + series_wounding_mods[pregen_data.wound_series]) + if (pregen_data.get_threshold_for(src, attack_direction, damage_source) > specific_injury_roll) + possible_wounds -= iterated_path + continue + + if (pregen_data.compete_for_wounding) + for (var/datum/wound/other_path as anything in possible_wounds) + if (other_path == iterated_path) + continue + if (initial(iterated_path.severity) == initial(other_path.severity) && pregen_data.overpower_wounds_of_even_severity) + possible_wounds -= other_path + continue + else if (pregen_data.competition_mode == WOUND_COMPETITION_OVERPOWER_LESSERS) + if (initial(iterated_path.severity) > initial(other_path.severity)) + possible_wounds -= other_path + continue + else if (pregen_data.competition_mode == WOUND_COMPETITION_OVERPOWER_GREATERS) + if (initial(iterated_path.severity) < initial(other_path.severity)) + possible_wounds -= other_path + continue + + while (length(possible_wounds)) + var/datum/wound/possible_wound = pick_weight(possible_wounds) + var/datum/wound_pregen_data/possible_pregen_data = GLOB.all_wound_pregen_data[possible_wound] + possible_wounds -= possible_wound + var/datum/wound/replaced_wound for(var/datum/wound/existing_wound as anything in wounds) - if(existing_wound.type in wounds_checking) + var/datum/wound_pregen_data/existing_pregen_data = GLOB.all_wound_pregen_data[existing_wound.type] + if(existing_pregen_data.wound_series == possible_pregen_data.wound_series) if(existing_wound.severity >= initial(possible_wound.severity)) - return + continue else replaced_wound = existing_wound + // if we get through this whole loop without continuing, we found our winner - if(initial(possible_wound.threshold_minimum) < injury_roll) - var/datum/wound/new_wound - if(replaced_wound) - new_wound = replaced_wound.replace_wound(possible_wound, attack_direction = attack_direction) - else - new_wound = new possible_wound - new_wound.apply_wound(src, attack_direction = attack_direction) - log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll) // dismembering wounds are logged in the apply_wound() for loss wounds since they delete themselves immediately, these will be immediately returned - return new_wound + var/datum/wound/new_wound = new possible_wound + if(replaced_wound) + new_wound = replaced_wound.replace_wound(new_wound, attack_direction = attack_direction) + else + new_wound.apply_wound(src, attack_direction = attack_direction, wound_source = damage_source) + log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll) // dismembering wounds are logged in the apply_wound() for loss wounds since they delete themselves immediately, these will be immediately returned + return new_wound // try forcing a specific wound, but only if there isn't already a wound of that severity or greater for that type on this bodypart -/obj/item/bodypart/proc/force_wound_upwards(specific_woundtype, smited = FALSE) +/obj/item/bodypart/proc/force_wound_upwards(datum/wound/potential_wound, smited = FALSE, wound_source) SHOULD_NOT_OVERRIDE(TRUE) - var/datum/wound/potential_wound = specific_woundtype + if (isnull(potential_wound)) + return + + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[potential_wound] for(var/datum/wound/existing_wound as anything in wounds) - if(existing_wound.wound_type == initial(potential_wound.wound_type)) + var/datum/wound_pregen_data/existing_pregen_data = existing_wound.get_pregen_data() + if (existing_pregen_data.wound_series == pregen_data.wound_series) if(existing_wound.severity < initial(potential_wound.severity)) // we only try if the existing one is inferior to the one we're trying to force - existing_wound.replace_wound(potential_wound, smited) + existing_wound.replace_wound(new potential_wound, smited) return var/datum/wound/new_wound = new potential_wound - new_wound.apply_wound(src, smited = smited) + new_wound.apply_wound(src, smited = smited, wound_source = wound_source) + return new_wound + +/** + * A simple proc to force a type of wound onto this mob. If you just want to force a specific mainline (fractures, bleeding, etc.) wound, you only need to care about the first 3 args. + * + * Args: + * * wounding_type: The wounding_type, e.g. WOUND_BLUNT, WOUND_SLASH to force onto the mob. Can be a list. + * * obj/item/bodypart/limb: The limb we wil be applying the wound to. If null, a random bodypart will be picked. + * * min_severity: The minimum severity that will be considered. + * * max_severity: The maximum severity that will be considered. + * * severity_pick_mode: The "pick mode" to be used. See get_corresponding_wound_type's documentation + * * wound_source: The source of the wound to be applied. Nullable. + * + * For the rest of the args, refer to get_corresponding_wound_type(). + * + * Returns: + * A new wound instance if the application was successful, null otherwise. +*/ +/mob/living/carbon/proc/cause_wound_of_type_and_severity(wounding_type, obj/item/bodypart/limb, min_severity, max_severity = min_severity, severity_pick_mode = WOUND_PICK_HIGHEST_SEVERITY, wound_source) + if (isnull(limb)) + limb = pick(bodyparts) + + var/list/type_list = wounding_type + if (!islist(type_list)) + type_list = list(type_list) + + var/datum/wound/corresponding_typepath = get_corresponding_wound_type(type_list, limb, min_severity, max_severity, severity_pick_mode) + if (corresponding_typepath) + return limb.force_wound_upwards(corresponding_typepath, wound_source = wound_source) + +/// Limb is nullable, but picks a random one. Defers to limb.get_wound_threshold_of_wound_type, see it for documentation. +/mob/living/carbon/proc/get_wound_threshold_of_wound_type(wounding_type, severity, default, obj/item/bodypart/limb, wound_source) + if (isnull(limb)) + limb = pick(bodyparts) + + if (!limb) + return default + + return limb.get_wound_threshold_of_wound_type(wounding_type, severity, default, wound_source) + +/** + * A simple proc that gets the best wound to fit the criteria laid out, then returns its wound threshold. + * + * Args: + * * wounding_type: The wounding_type, e.g. WOUND_BLUNT, WOUND_SLASH to force onto the mob. Can be a list of wounding_types. + * * severity: The severity that will be considered. + * * return_value_if_no_wound: If no wound is found, we will return this instead. (It is reccomended to use named args for this one, as its unclear what it is without) + * * wound_source: The theoretical source of the wound. Nullable. + * + * Returns: + * return_value_if_no_wound if no wound is found - if one IS found, the wound threshold for that wound. + */ +/obj/item/bodypart/proc/get_wound_threshold_of_wound_type(wounding_type, severity, return_value_if_no_wound, wound_source) + var/list/type_list = wounding_type + if (!islist(type_list)) + type_list = list(type_list) + + var/datum/wound/wound_path = get_corresponding_wound_type(type_list, src, severity, duplicates_allowed = TRUE, care_about_existing_wounds = FALSE) + if (wound_path) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound_path] + return pregen_data.get_threshold_for(src, damage_source = wound_source) + + return return_value_if_no_wound /** * check_wounding_mods() is where we handle the various modifiers of a wound roll @@ -171,7 +268,20 @@ return injury_mod - /// Get whatever wound of the given type is currently attached to this limb, if any +/// Should return an assoc list of (wound_series -> penalty). Will be used in determining series-specific penalties for wounding. +/obj/item/bodypart/proc/check_series_wounding_mods() + RETURN_TYPE(/list) + + var/list/series_mods = list() + + for (var/datum/wound/iterated_wound as anything in wounds) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[iterated_wound.type] + + series_mods[pregen_data.wound_series] += iterated_wound.series_threshold_penalty + + return series_mods + +/// Get whatever wound of the given type is currently attached to this limb, if any /obj/item/bodypart/proc/get_wound_type(checking_type) RETURN_TYPE(checking_type) SHOULD_NOT_OVERRIDE(TRUE) @@ -196,10 +306,6 @@ var/dam_mul = 1 //initial(wound_damage_multiplier) - // we can (normally) only have one wound per type, but remember there's multiple types (smites like :B:loodless can generate multiple cuts on a limb) - for(var/datum/wound/iter_wound as anything in wounds) - dam_mul *= iter_wound.damage_mulitplier_penalty - if(!LAZYLEN(wounds) && current_gauze && !replaced) // no more wounds = no need for the gauze anymore owner.visible_message(span_notice("\The [current_gauze.name] on [owner]'s [name] falls away."), span_notice("The [current_gauze.name] on your [parse_zone(body_zone)] falls away.")) QDEL_NULL(current_gauze) diff --git a/code/modules/surgery/bone_mending.dm b/code/modules/surgery/bone_mending.dm index f500319a49ee..6a34a3e13145 100644 --- a/code/modules/surgery/bone_mending.dm +++ b/code/modules/surgery/bone_mending.dm @@ -4,8 +4,8 @@ ///// Repair Hairline Fracture (Severe) /datum/surgery/repair_bone_hairline name = "Repair bone fracture (hairline)" - surgery_flags = SURGERY_SELF_OPERABLE | SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB - targetable_wound = /datum/wound/blunt/severe + surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB + targetable_wound = /datum/wound/blunt/bone/severe possible_locs = list( BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, @@ -27,11 +27,12 @@ return(targeted_bodypart.get_wound_type(targetable_wound)) +///// Repair Compound Fracture (Critical) ///// Repair Compound Fracture (Critical) /datum/surgery/repair_bone_compound name = "Repair Compound Fracture" - surgery_flags = SURGERY_SELF_OPERABLE | SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB - targetable_wound = /datum/wound/blunt/critical + surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB + targetable_wound = /datum/wound/blunt/bone/critical possible_locs = list( BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, diff --git a/code/modules/surgery/burn_dressing.dm b/code/modules/surgery/burn_dressing.dm index b3047d75065d..817772ca8b65 100644 --- a/code/modules/surgery/burn_dressing.dm +++ b/code/modules/surgery/burn_dressing.dm @@ -24,7 +24,7 @@ return FALSE if(..()) var/obj/item/bodypart/targeted_bodypart = target.get_bodypart(user.zone_selected) - var/datum/wound/burn/burn_wound = targeted_bodypart.get_wound_type(targetable_wound) + var/datum/wound/burn/flesh/burn_wound = targeted_bodypart.get_wound_type(targetable_wound) return(burn_wound && burn_wound.infestation > 0) //SURGERY STEPS @@ -48,7 +48,7 @@ var/infestation_removed = 4 /// To give the surgeon a heads up how much work they have ahead of them -/datum/surgery_step/debride/proc/get_progress(mob/user, mob/living/carbon/target, datum/wound/burn/burn_wound) +/datum/surgery_step/debride/proc/get_progress(mob/user, mob/living/carbon/target, datum/wound/burn/flesh/burn_wound) if(!burn_wound?.infestation || !infestation_removed) return var/estimated_remaining_steps = burn_wound.infestation / infestation_removed @@ -68,7 +68,7 @@ /datum/surgery_step/debride/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) if(surgery.operated_wound) - var/datum/wound/burn/burn_wound = surgery.operated_wound + var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound if(burn_wound.infestation <= 0) to_chat(user, span_notice("[target]'s [parse_zone(user.zone_selected)] has no infected flesh to remove!")) surgery.status++ @@ -86,7 +86,7 @@ user.visible_message(span_notice("[user] looks for [target]'s [parse_zone(user.zone_selected)]."), span_notice("You look for [target]'s [parse_zone(user.zone_selected)]...")) /datum/surgery_step/debride/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/datum/wound/burn/burn_wound = surgery.operated_wound + var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound if(burn_wound) var/progress_text = get_progress(user, target, burn_wound) display_results( @@ -120,7 +120,7 @@ /datum/surgery_step/debride/initiate(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) if(!..()) return - var/datum/wound/burn/burn_wound = surgery.operated_wound + var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound while(burn_wound && burn_wound.infestation > 0.25) if(!..()) break @@ -139,7 +139,7 @@ /datum/surgery_step/dress/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/datum/wound/burn/burn_wound = surgery.operated_wound + var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound if(burn_wound) display_results( user, @@ -153,7 +153,7 @@ user.visible_message(span_notice("[user] looks for [target]'s [parse_zone(user.zone_selected)]."), span_notice("You look for [target]'s [parse_zone(user.zone_selected)]...")) /datum/surgery_step/dress/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/datum/wound/burn/burn_wound = surgery.operated_wound + var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound if(burn_wound) display_results( user, diff --git a/code/modules/surgery/implant_removal.dm b/code/modules/surgery/implant_removal.dm index 9370a4f719c9..7f43d56ffb1b 100644 --- a/code/modules/surgery/implant_removal.dm +++ b/code/modules/surgery/implant_removal.dm @@ -1,5 +1,6 @@ /datum/surgery/implant_removal - name = "Implant removal" + name = "Implant Removal" + target_mobtypes = list(/mob/living) possible_locs = list(BODY_ZONE_CHEST) steps = list( /datum/surgery_step/incise, @@ -20,7 +21,7 @@ success_sound = 'sound/surgery/hemostat1.ogg' var/obj/item/implant/implant -/datum/surgery_step/extract_implant/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) +/datum/surgery_step/extract_implant/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) for(var/obj/item/object in target.implants) implant = object break @@ -42,7 +43,7 @@ span_notice("[user] looks for something in [target]'s [target_zone]."), ) -/datum/surgery_step/extract_implant/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) +/datum/surgery_step/extract_implant/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) if(implant) display_results( user, @@ -79,8 +80,9 @@ return ..() /datum/surgery/implant_removal/mechanic - name = "implant removal" + name = "Implant Removal" requires_bodypart_type = BODYTYPE_ROBOTIC + target_mobtypes = list(/mob/living/carbon/human) // Simpler mobs don't have bodypart types steps = list( /datum/surgery_step/mechanic_open, /datum/surgery_step/open_hatch, diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index 4f5ad281ea1c..0363a9aa7e23 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -45,6 +45,8 @@ var/list/organ_traits /// Status Effects that are given to the holder of the organ. var/list/organ_effects + /// String displayed when the organ has decayed. + var/failing_desc = "has decayed for too long, and has turned a sickly color. It probably won't work without repairs." // Players can look at prefs before atoms SS init, and without this // they would not be able to see external organs, such as moth wings. diff --git a/code/modules/surgery/organs/liver.dm b/code/modules/surgery/organs/liver.dm old mode 100755 new mode 100644 diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index 8dd9af374c5f..2571034a06b6 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -265,7 +265,7 @@ return var/ratio = (breath.gases[/datum/gas/oxygen][MOLES] / safe_oxygen_max) * 10 - breather.apply_damage_type(clamp(ratio, oxy_breath_dam_min, oxy_breath_dam_max), oxy_damage_type) + breather.apply_damage(clamp(ratio, oxy_breath_dam_min, oxy_breath_dam_max), oxy_damage_type, spread_damage = TRUE) breather.throw_alert(ALERT_TOO_MUCH_OXYGEN, /atom/movable/screen/alert/too_much_oxy) /// Handles NOT having too much o2. only relevant if safe_oxygen_max has a value @@ -321,10 +321,10 @@ breather.throw_alert(ALERT_TOO_MUCH_CO2, /atom/movable/screen/alert/too_much_co2) breather.Unconscious(6 SECONDS) // Lets hurt em a little, let them know we mean business. - breather.apply_damage_type(3, co2_damage_type) + breather.apply_damage(3, co2_damage_type, spread_damage = TRUE) // They've been in here 30s now, start to kill them for their own good! if((world.time - breather.co2overloadtime) > 30 SECONDS) - breather.apply_damage_type(8, co2_damage_type) + breather.apply_damage(8, co2_damage_type, spread_damage = TRUE) /// Handles NOT having too much co2. only relevant if safe_co2_max has a value /obj/item/organ/internal/lungs/proc/safe_co2(mob/living/carbon/breather, datum/gas_mixture/breath, old_co2_pp) @@ -365,7 +365,7 @@ breather.throw_alert(ALERT_TOO_MUCH_PLASMA, /atom/movable/screen/alert/too_much_plas) var/ratio = (breath.gases[/datum/gas/plasma][MOLES] / safe_plasma_max) * 10 - breather.apply_damage_type(clamp(ratio, plas_breath_dam_min, plas_breath_dam_max), plas_damage_type) + breather.apply_damage(clamp(ratio, plas_breath_dam_min, plas_breath_dam_max), plas_damage_type, spread_damage = TRUE) /// Resets plasma side effects /obj/item/organ/internal/lungs/proc/safe_plasma(mob/living/carbon/breather, datum/gas_mixture/breath, old_plasma_pp) @@ -754,11 +754,11 @@ if(!HAS_TRAIT(breather, TRAIT_RESISTCOLD)) // COLD DAMAGE var/cold_modifier = breather.dna.species.coldmod if(breath_temperature < cold_level_3_threshold) - breather.apply_damage_type(cold_level_3_damage*cold_modifier, cold_damage_type) + breather.apply_damage(cold_level_3_damage*cold_modifier, cold_damage_type, spread_damage = TRUE) if(breath_temperature > cold_level_3_threshold && breath_temperature < cold_level_2_threshold) - breather.apply_damage_type(cold_level_2_damage*cold_modifier, cold_damage_type) + breather.apply_damage(cold_level_2_damage*cold_modifier, cold_damage_type, spread_damage = TRUE) if(breath_temperature > cold_level_2_threshold && breath_temperature < cold_level_1_threshold) - breather.apply_damage_type(cold_level_1_damage*cold_modifier, cold_damage_type) + breather.apply_damage(cold_level_1_damage*cold_modifier, cold_damage_type, spread_damage = TRUE) if(breath_temperature < cold_level_1_threshold) if(prob(20)) to_chat(breather, span_warning("You feel [cold_message] in your [name]!")) @@ -766,11 +766,11 @@ if(!HAS_TRAIT(breather, TRAIT_RESISTHEAT)) // HEAT DAMAGE var/heat_modifier = breather.dna.species.heatmod if(breath_temperature > heat_level_1_threshold && breath_temperature < heat_level_2_threshold) - breather.apply_damage_type(heat_level_1_damage*heat_modifier, heat_damage_type) + breather.apply_damage(heat_level_1_damage*heat_modifier, heat_damage_type, spread_damage = TRUE) if(breath_temperature > heat_level_2_threshold && breath_temperature < heat_level_3_threshold) - breather.apply_damage_type(heat_level_2_damage*heat_modifier, heat_damage_type) + breather.apply_damage(heat_level_2_damage*heat_modifier, heat_damage_type, spread_damage = TRUE) if(breath_temperature > heat_level_3_threshold) - breather.apply_damage_type(heat_level_3_damage*heat_modifier, heat_damage_type) + breather.apply_damage(heat_level_3_damage*heat_modifier, heat_damage_type, spread_damage = TRUE) if(breath_temperature > heat_level_1_threshold) if(prob(20)) to_chat(breather, span_warning("You feel [hot_message] in your [name]!")) @@ -819,6 +819,7 @@ /obj/item/organ/internal/lungs/cybernetic name = "basic cybernetic lungs" desc = "A basic cybernetic version of the lungs found in traditional humanoid entities." + failing_desc = "seems to be broken." icon_state = "lungs-c" organ_flags = ORGAN_SYNTHETIC maxHealth = STANDARD_ORGAN_THRESHOLD * 0.5 diff --git a/code/modules/surgery/repair_puncture.dm b/code/modules/surgery/repair_puncture.dm index 9c395d9b40f4..c61eeeaa8bdd 100644 --- a/code/modules/surgery/repair_puncture.dm +++ b/code/modules/surgery/repair_puncture.dm @@ -32,7 +32,7 @@ . = ..() if(.) var/obj/item/bodypart/targeted_bodypart = target.get_bodypart(user.zone_selected) - var/datum/wound/burn/pierce_wound = targeted_bodypart.get_wound_type(targetable_wound) + var/datum/wound/burn/flesh/pierce_wound = targeted_bodypart.get_wound_type(targetable_wound) return(pierce_wound && pierce_wound.blood_flow > 0) //SURGERY STEPS @@ -47,7 +47,7 @@ time = 3 SECONDS /datum/surgery_step/repair_innards/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/datum/wound/pierce/pierce_wound = surgery.operated_wound + var/datum/wound/pierce/bleed/pierce_wound = surgery.operated_wound if(!pierce_wound) user.visible_message(span_notice("[user] looks for [target]'s [parse_zone(user.zone_selected)]."), span_notice("You look for [target]'s [parse_zone(user.zone_selected)]...")) return @@ -67,7 +67,7 @@ display_pain(target, "You feel a horrible stabbing pain in your [parse_zone(user.zone_selected)]!") /datum/surgery_step/repair_innards/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/datum/wound/pierce/pierce_wound = surgery.operated_wound + var/datum/wound/pierce/bleed/pierce_wound = surgery.operated_wound if(!pierce_wound) to_chat(user, span_warning("[target] has no puncture wound there!")) return ..() @@ -112,7 +112,7 @@ return TRUE /datum/surgery_step/seal_veins/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/datum/wound/pierce/pierce_wound = surgery.operated_wound + var/datum/wound/pierce/bleed/pierce_wound = surgery.operated_wound if(!pierce_wound) user.visible_message(span_notice("[user] looks for [target]'s [parse_zone(user.zone_selected)]."), span_notice("You look for [target]'s [parse_zone(user.zone_selected)]...")) return @@ -126,7 +126,7 @@ display_pain(target, "You're being burned inside your [parse_zone(user.zone_selected)]!") /datum/surgery_step/seal_veins/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/datum/wound/pierce/pierce_wound = surgery.operated_wound + var/datum/wound/pierce/bleed/pierce_wound = surgery.operated_wound if(!pierce_wound) to_chat(user, span_warning("[target] has no puncture there!")) return ..() diff --git a/code/modules/tgui/states/notcontained.dm b/code/modules/tgui/states/notcontained.dm index 018e0fa0304c..b144a375757d 100644 --- a/code/modules/tgui/states/notcontained.dm +++ b/code/modules/tgui/states/notcontained.dm @@ -28,5 +28,5 @@ GLOBAL_DATUM_INIT(notcontained_state, /datum/ui_state/notcontained_state, new) /mob/living/silicon/notcontained_can_use_topic(src_object) return default_can_use_topic(src_object) // Silicons use default bevhavior. -/mob/living/simple_animal/drone/notcontained_can_use_topic(src_object) +/mob/living/basic/drone/notcontained_can_use_topic(src_object) return default_can_use_topic(src_object) // Drones use default bevhavior. diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 6211f8dcc5d7..79ac97f3dd4b 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -133,6 +133,7 @@ #include "json_savefile_importing.dm" #include "keybinding_init.dm" #include "knockoff_component.dm" +#include "leash.dm" #include "lesserform.dm" #include "limbsanity.dm" #include "lungs.dm" @@ -145,6 +146,7 @@ #include "merge_type.dm" #include "mindbound_actions.dm" #include "missing_icons.dm" +#include "mob_chains.dm" #include "mob_faction.dm" #include "mob_spawn.dm" #include "modsuit.dm" @@ -160,7 +162,6 @@ #include "orderable_items.dm" #include "organs.dm" #include "organ_set_bonus.dm" -#include "outfit_sanity.dm" #include "paintings.dm" #include "pills.dm" #include "plane_double_transform.dm" diff --git a/code/modules/unit_tests/dragon_expiration.dm b/code/modules/unit_tests/dragon_expiration.dm index 7b36b5762911..45262dc9d609 100644 --- a/code/modules/unit_tests/dragon_expiration.dm +++ b/code/modules/unit_tests/dragon_expiration.dm @@ -2,9 +2,12 @@ /datum/unit_test/contents_barfer /datum/unit_test/contents_barfer/Run() - var/mob/living/simple_animal/hostile/space_dragon/dragon_time = allocate(/mob/living/simple_animal/hostile/space_dragon) + var/mob/living/basic/space_dragon/dragon_time = allocate(/mob/living/basic/space_dragon) var/mob/living/carbon/human/to_be_consumed = allocate(/mob/living/carbon/human/consistent) + to_be_consumed.adjust_fire_stacks(5) + to_be_consumed.ignite_mob() TEST_ASSERT(dragon_time.eat(to_be_consumed), "The space dragon failed to consume the dummy!") + TEST_ASSERT(!to_be_consumed.has_status_effect(/datum/status_effect/fire_handler/fire_stacks), "The space dragon failed to extinguish the dummy!") TEST_ASSERT_EQUAL(to_be_consumed.loc, dragon_time, "The dummy's location, after being successfuly consumed, was not within the space dragon's contents!") dragon_time.death() TEST_ASSERT(isturf(to_be_consumed.loc), "After dying, the space dragon did not eject the consumed dummy content barfer element.") @@ -13,7 +16,7 @@ /datum/unit_test/space_dragon_expiration /datum/unit_test/space_dragon_expiration/Run() - var/mob/living/simple_animal/hostile/space_dragon/dragon_time = allocate(/mob/living/simple_animal/hostile/space_dragon) + var/mob/living/basic/space_dragon/dragon_time = allocate(/mob/living/basic/space_dragon) var/mob/living/carbon/human/to_be_consumed = allocate(/mob/living/carbon/human/consistent) dragon_time.mind_initialize() diff --git a/code/modules/unit_tests/leash.dm b/code/modules/unit_tests/leash.dm new file mode 100644 index 000000000000..2372ebca7df5 --- /dev/null +++ b/code/modules/unit_tests/leash.dm @@ -0,0 +1,102 @@ +/datum/unit_test/leash + abstract_type = /datum/unit_test/leash + + var/atom/movable/owner + var/atom/movable/pet + + var/max_distance = 3 + + var/forcibly_teleported = FALSE + var/datum/leash_wait/leash_wait + +/datum/unit_test/leash/New() + . = ..() + + owner = allocate(/obj/item/pen) + pet = allocate(/obj/item/pen) + + pet.AddComponent(/datum/component/leash, owner, max_distance) + + RegisterSignal(pet, COMSIG_LEASH_FORCE_TELEPORT, PROC_REF(on_leash_force_teleport)) + RegisterSignal(pet, COMSIG_LEASH_PATH_STARTED, PROC_REF(on_leash_path_started)) + RegisterSignal(pet, COMSIG_LEASH_PATH_COMPLETE, PROC_REF(on_leash_path_complete)) + +/datum/unit_test/leash/Destroy() + QDEL_NULL(owner) + QDEL_NULL(pet) + + return ..() + +/datum/unit_test/leash/proc/on_leash_force_teleport() + SIGNAL_HANDLER + forcibly_teleported = TRUE + +/datum/unit_test/leash/proc/on_leash_path_complete() + SIGNAL_HANDLER + leash_wait?.completed() + +/datum/unit_test/leash/proc/on_leash_path_started() + SIGNAL_HANDLER + leash_wait?.started() + +/datum/unit_test/leash/proc/move_away(atom/movable/mover, distance) + RETURN_TYPE(/datum/leash_wait) + leash_wait = new + + for (var/_ in 1 to distance) + mover.Move(get_step(mover, EAST)) + + return leash_wait + +/datum/leash_wait + var/completed = FALSE + var/started = FALSE + + var/timed_out = FALSE + +/datum/leash_wait/New() + addtimer(VARSET_CALLBACK(src, timed_out, TRUE), 1 SECONDS) + +/datum/leash_wait/proc/completed() + completed = TRUE + +/datum/leash_wait/proc/started() + started = TRUE + +/datum/leash_wait/proc/assert_unmoved() + ASSERT(!started, "Leash started to move when it should not have") + +/datum/leash_wait/proc/wait() + ASSERT(started, "Leash doesn't plan on moving") + + UNTIL(completed || timed_out) + ASSERT(!timed_out, "Waiting for leash movement timed out, it didn't want to move") + +/// Validates the leash component will keep its parent within range without teleporting +/// when possible. +/datum/unit_test/leash/no_teleport + +/datum/unit_test/leash/no_teleport/Run() + move_away(owner, 1).assert_unmoved() + TEST_ASSERT_EQUAL(get_dist(owner, pet), 1, "Pet should not have moved") + + move_away(owner, max_distance).wait() // max_distance + 1 = we move closer, but don't teleport + TEST_ASSERT_EQUAL(get_dist(owner, pet), max_distance, "Pet should have stayed directly outside range of owner") + + TEST_ASSERT(!forcibly_teleported, "Pet should not have been forcibly teleported") + +/// Validates that the leash component will forcibly teleport when necessary +/datum/unit_test/leash/will_teleport + +/datum/unit_test/leash/will_teleport/Run() + leash_wait = new + owner.forceMove(locate(1, 1, 1)) + leash_wait.wait() + TEST_ASSERT(forcibly_teleported, "Pet should have been forcibly teleported, since they are too far away with no valid path") + +/// Validates that the leashed object cannot move outside of the max distance from owner +/datum/unit_test/leash/limit_range + +/datum/unit_test/leash/limit_range/Run() + move_away(pet, max_distance + 1) + TEST_ASSERT_EQUAL(get_dist(owner, pet), max_distance, "Pet should not have moved farther than max_distance") diff --git a/code/modules/unit_tests/medical_wounds.dm b/code/modules/unit_tests/medical_wounds.dm index 56c7a43104f1..161492a726a9 100644 --- a/code/modules/unit_tests/medical_wounds.dm +++ b/code/modules/unit_tests/medical_wounds.dm @@ -12,17 +12,18 @@ var/i = 1 var/list/iter_test_wound_list - for(iter_test_wound_list in list(list(/datum/wound/blunt/moderate, /datum/wound/blunt/severe, /datum/wound/blunt/critical),\ - list(/datum/wound/slash/moderate, /datum/wound/slash/severe, /datum/wound/slash/critical),\ - list(/datum/wound/pierce/moderate, /datum/wound/pierce/severe, /datum/wound/pierce/critical),\ - list(/datum/wound/burn/moderate, /datum/wound/burn/severe, /datum/wound/burn/critical))) + for(iter_test_wound_list in list(list(/datum/wound/blunt/bone/moderate, /datum/wound/blunt/bone/severe, /datum/wound/blunt/bone/critical),\ + list(/datum/wound/slash/flesh/moderate, /datum/wound/slash/flesh/severe, /datum/wound/slash/flesh/critical),\ + list(/datum/wound/pierce/bleed/moderate, /datum/wound/pierce/bleed/severe, /datum/wound/pierce/bleed/critical),\ + list(/datum/wound/burn/flesh/moderate, /datum/wound/burn/flesh/severe, /datum/wound/burn/flesh/critical))) TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test") var/datum/wound/iter_test_wound + var/datum/wound_pregen_data/iter_pregen_data = GLOB.all_wound_pregen_data[iter_test_wound] var/threshold_penalty = 0 for(iter_test_wound in iter_test_wound_list) - var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty + var/threshold = iter_pregen_data.threshold_minimum - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty if(dam_types[i] == BRUTE) tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i]) else if(dam_types[i] == BURN) @@ -52,30 +53,33 @@ var/list/iter_test_wound_list tested_part.biological_state &= ~BIO_FLESH // take away the base limb's flesh (ouchie!) ((not actually ouchie, this just affects their wounds and dismemberment handling)) - for(iter_test_wound_list in list(list(/datum/wound/blunt/moderate, /datum/wound/blunt/severe, /datum/wound/blunt/critical),\ - list(/datum/wound/slash/moderate, /datum/wound/slash/severe, /datum/wound/slash/critical),\ - list(/datum/wound/pierce/moderate, /datum/wound/pierce/severe, /datum/wound/pierce/critical),\ - list(/datum/wound/burn/moderate, /datum/wound/burn/severe, /datum/wound/burn/critical))) + for(iter_test_wound_list in list(list(/datum/wound/blunt/bone/moderate, /datum/wound/blunt/bone/severe, /datum/wound/blunt/bone/critical),\ + list(/datum/wound/slash/flesh/moderate, /datum/wound/slash/flesh/severe, /datum/wound/slash/flesh/critical),\ + list(/datum/wound/pierce/bleed/moderate, /datum/wound/pierce/bleed/severe, /datum/wound/pierce/bleed/critical),\ + list(/datum/wound/burn/flesh/moderate, /datum/wound/burn/flesh/severe, /datum/wound/burn/flesh/critical))) TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test") var/datum/wound/iter_test_wound + var/datum/wound_pregen_data/iter_pregen_data = GLOB.all_wound_pregen_data[iter_test_wound] var/threshold_penalty = 0 for(iter_test_wound in iter_test_wound_list) - var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty + var/threshold = iter_pregen_data.threshold_minimum - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty if(dam_types[i] == BRUTE) tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i]) else if(dam_types[i] == BURN) tested_part.receive_damage(0, WOUND_MINIMUM_DAMAGE, wound_bonus = threshold, sharpness=sharps[i]) // so if we just tried to deal a flesh wound, make sure we didn't actually suffer it. We may have suffered a bone wound instead, but we just want to make sure we don't have a flesh wound - if(initial(iter_test_wound.wound_flags) & FLESH_WOUND) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[iter_test_wound] + if (pregen_data.required_limb_biostate & BIO_FLESH) if(!length(victim.all_wounds)) // not having a wound is good news continue else // we have to check that it's actually a bone wound and not the intended wound type TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]") var/datum/wound/actual_wound = victim.all_wounds[1] - TEST_ASSERT((actual_wound.wound_flags & ~FLESH_WOUND), "Limb has flesh wound despite no BIO_FLESH biological_state, expected either no wound or bone wound. Offending wound: [actual_wound]") + var/datum/wound_pregen_data/actual_pregen_data = GLOB.all_wound_pregen_data[actual_wound.type] + TEST_ASSERT((actual_pregen_data.required_limb_biostate & ~BIO_FLESH), "Limb has flesh wound despite no BIO_FLESH biological_state, expected either no wound or bone wound. Offending wound: [actual_wound]") threshold_penalty = actual_wound.threshold_penalty else // otherwise if it's a bone wound, check that we have it per usual TEST_ASSERT(length(victim.all_wounds), "Patient has no wounds when one wound is expected. Severity: [initial(iter_test_wound.severity)]") diff --git a/code/modules/unit_tests/mob_chains.dm b/code/modules/unit_tests/mob_chains.dm new file mode 100644 index 000000000000..503c80cf91bf --- /dev/null +++ b/code/modules/unit_tests/mob_chains.dm @@ -0,0 +1,31 @@ +/// Checks if mobs who are linked together with the mob chain component react as expected +/datum/unit_test/mob_chains + +/datum/unit_test/mob_chains/Run() + var/mob/living/centipede_head = allocate(/mob/living/basic/pet/dog) + var/list/segments = list(centipede_head) + centipede_head.AddComponent(/datum/component/mob_chain) + var/mob/living/centipede_tail = centipede_head + for (var/i in 1 to 2) + var/mob/living/new_segment = allocate(/mob/living/basic/pet/dog) + new_segment.AddComponent(/datum/component/mob_chain, front = centipede_tail) + segments += new_segment + centipede_tail = new_segment + + var/test_damage = 15 + centipede_head.apply_damage(test_damage, BRUTE) + TEST_ASSERT_EQUAL(centipede_head.bruteloss, 0, "Centipede head took damage which should have been passed to its tail.") + TEST_ASSERT_EQUAL(centipede_tail.bruteloss, test_damage, "Centipede tail did not take damage which should have originated from its head.") + + var/expected_damage = 5 + for (var/mob/living/segment as anything in segments) + segment.istate |= ISTATE_HARM + segment.melee_damage_lower = expected_damage + segment.melee_damage_upper = expected_damage + + var/mob/living/victim = allocate(/mob/living/basic/pet/dog) + centipede_head.ClickOn(victim) + TEST_ASSERT_EQUAL(victim.bruteloss, expected_damage * 3, "Centipede failed to do damage with all of its segments.") + + centipede_head.death() + TEST_ASSERT_EQUAL(centipede_tail.stat, DEAD, "Centipede tail failed to die with head.") diff --git a/code/modules/unit_tests/modify_fantasy_variable.dm b/code/modules/unit_tests/modify_fantasy_variable.dm new file mode 100644 index 000000000000..d78c1d1dac74 --- /dev/null +++ b/code/modules/unit_tests/modify_fantasy_variable.dm @@ -0,0 +1,21 @@ +// Unit test to make sure that there are no duplicate keys when modify_fantasy_variable is called when applying fantasy bonuses. +// Also to make sure the fantasy_modifications list is null when fantasy bonuses are removed. +/datum/unit_test/modify_fantasy_variable/Run() + + for(var/obj/item/path as anything in subtypesof(/obj/item)) + var/obj/item/object = allocate(path) + // Try positive + object.apply_fantasy_bonuses(bonus = 5) + object.remove_fantasy_bonuses(bonus = 5) + TEST_ASSERT_NULL(object.fantasy_modifications) + // Then negative + object.apply_fantasy_bonuses(bonus = -5) + object.remove_fantasy_bonuses(bonus = -5) + TEST_ASSERT_NULL(object.fantasy_modifications) + // Now try the extremes of each + object.apply_fantasy_bonuses(bonus = 500) + object.remove_fantasy_bonuses(bonus = 500) + TEST_ASSERT_NULL(object.fantasy_modifications) + object.apply_fantasy_bonuses(bonus = -500) + object.remove_fantasy_bonuses(bonus = -500) + TEST_ASSERT_NULL(object.fantasy_modifications) diff --git a/code/modules/unit_tests/spell_shapeshift.dm b/code/modules/unit_tests/spell_shapeshift.dm index 4e2ce6590037..d99c7f576f0d 100644 --- a/code/modules/unit_tests/spell_shapeshift.dm +++ b/code/modules/unit_tests/spell_shapeshift.dm @@ -87,7 +87,7 @@ shift.shapeshift_type = shift.possible_shapes[1] shift.Grant(dummy) - var/mob/living/simple_animal/hostile/guardian/test_stand = allocate(/mob/living/simple_animal/hostile/guardian) + var/mob/living/basic/guardian/test_stand = allocate(/mob/living/basic/guardian) test_stand.set_summoner(dummy) // The stand's summoner is dummy. diff --git a/code/modules/uplink/uplink_items/badass.dm b/code/modules/uplink/uplink_items/badass.dm index 1fb7fab4964e..c1f4ac3a695e 100644 --- a/code/modules/uplink/uplink_items/badass.dm +++ b/code/modules/uplink/uplink_items/badass.dm @@ -82,3 +82,16 @@ desc = "Contains 8 random stickers precisely engineered to resemble suspicious objects, which may or may not be useful for fooling crew." item = /obj/item/storage/box/syndie_kit/stickers cost = 1 + +/datum/uplink_item/badass/demotivational_posters + name = "Syndicate Demotivational Poster Pack" + desc = "Contains a selection of demotivational posters to minimise productivity and maximise apathy in the workplace." + item = /obj/item/storage/box/syndie_kit/poster_box + cost = 1 + +/datum/uplink_item/badass/syndie_spraycan + name = "Syndicate Spraycan" + desc = "A stylish Syndicate spraycan. \ + Contains enough special solution to spray a single super-size seditious symbol, subjecting station staff to slippery suffering." + item = /obj/item/traitor_spraycan + cost = 1 diff --git a/code/modules/uplink/uplink_items/dangerous.dm b/code/modules/uplink/uplink_items/dangerous.dm index 0c14c302b5ef..59ab6ae48917 100644 --- a/code/modules/uplink/uplink_items/dangerous.dm +++ b/code/modules/uplink/uplink_items/dangerous.dm @@ -84,7 +84,7 @@ desc = "Though capable of near sorcerous feats via use of hardlight holograms and nanomachines, they require an \ organic host as a home base and source of fuel. Holoparasites come in various types and share damage with their host." progression_minimum = 30 MINUTES - item = /obj/item/guardiancreator/tech/choose/traitor + item = /obj/item/guardian_creator/tech cost = 18 surplus = 0 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) diff --git a/code/modules/uplink/uplink_items/job.dm b/code/modules/uplink/uplink_items/job.dm index b754ed2ff94f..25d76c8df10f 100644 --- a/code/modules/uplink/uplink_items/job.dm +++ b/code/modules/uplink/uplink_items/job.dm @@ -311,3 +311,10 @@ cost = 14 //High cost because of the potential for extreme damage in the hands of a skilled scientist. restricted_roles = list(JOB_RESEARCH_DIRECTOR, JOB_SCIENTIST) surplus = 5 + +/datum/uplink_item/role_restricted/evil_seedling + name = "Evil Seedling" + desc = "A rare seed we have recovered that grows into a dangerous species that will aid you with your tasks!" + item = /obj/item/seeds/seedling/evil + cost = 8 + restricted_roles = list(JOB_BOTANIST) diff --git a/code/modules/vehicles/atv.dm b/code/modules/vehicles/atv.dm index d2d5b3a6af30..abebe8264925 100644 --- a/code/modules/vehicles/atv.dm +++ b/code/modules/vehicles/atv.dm @@ -117,12 +117,11 @@ smoke.start() /obj/vehicle/ridden/atv/bullet_act(obj/projectile/P) - if(prob(50) || !buckled_mobs) + if(prob(50) || !LAZYLEN(buckled_mobs)) return ..() - for(var/m in buckled_mobs) - var/mob/buckled_mob = m + for(var/mob/buckled_mob as anything in buckled_mobs) buckled_mob.bullet_act(P) - return TRUE + return BULLET_ACT_HIT /obj/vehicle/ridden/atv/atom_destruction() explosion(src, devastation_range = -1, light_impact_range = 2, flame_range = 3, flash_range = 4) diff --git a/code/modules/vehicles/cars/clowncar.dm b/code/modules/vehicles/cars/clowncar.dm index b90cb6267468..edf25bb96982 100644 --- a/code/modules/vehicles/cars/clowncar.dm +++ b/code/modules/vehicles/cars/clowncar.dm @@ -119,11 +119,11 @@ if(prob(35)) //Note: The randomstep on dump_mobs throws occupants into each other and often causes wounds regardless. continue for(var/obj/item/bodypart/head/head_to_wound as anything in carbon_occupant.bodyparts) - var/type_wound = pick(list( - /datum/wound/blunt/moderate, - /datum/wound/blunt/severe, - )) - head_to_wound.force_wound_upwards(type_wound) + var/pick_mode = text2num(pick(list( + "[WOUND_PICK_LOWEST_SEVERITY]", + "[WOUND_PICK_HIGHEST_SEVERITY]" + ))) + carbon_occupant.cause_wound_of_type_and_severity(WOUND_BLUNT, head_to_wound, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, pick_mode) carbon_occupant.playsound_local(src, 'sound/weapons/flash_ring.ogg', 50) carbon_occupant.set_eye_blur_if_lower(rand(10 SECONDS, 20 SECONDS)) diff --git a/code/modules/vehicles/mecha/combat/durand.dm b/code/modules/vehicles/mecha/combat/durand.dm index 8d3af6041c22..4de7cd72f65b 100644 --- a/code/modules/vehicles/mecha/combat/durand.dm +++ b/code/modules/vehicles/mecha/combat/durand.dm @@ -261,7 +261,7 @@ own integrity back to max. Shield is automatically dropped if we run out of powe setDir(newdir) -/obj/durand_shield/take_damage() +/obj/durand_shield/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0) if(!chassis) qdel(src) return diff --git a/code/modules/vehicles/mecha/combat/savannah_ivanov.dm b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm index 08a996fbd88f..8ea4ac076358 100644 --- a/code/modules/vehicles/mecha/combat/savannah_ivanov.dm +++ b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm @@ -292,7 +292,7 @@ /** * ## end_missile_targeting * - * Called by the ivanov strike datum action or other actions that would end targetting + * Called by the ivanov strike datum action or other actions that would end targeting * Unhooks signals into clicking to call drop_missile plus other flavor like the overlay */ /datum/action/vehicle/sealed/mecha/ivanov_strike/proc/end_missile_targeting() diff --git a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm index e925710397ff..4c766d669d44 100644 --- a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm +++ b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm @@ -111,7 +111,7 @@ * Cooldown proc variant for using do_afters between activations instead of timers * Example of usage is mech drills, rcds * arguments: - * * target: targetted atom for action activation + * * target: targeted atom for action activation * * user: occupant to display do after for * * interaction_key: interaction key to pass to [/proc/do_after] */ diff --git a/code/modules/vehicles/mecha/mecha_construction_paths.dm b/code/modules/vehicles/mecha/mecha_construction_paths.dm index 7df97650d0ea..bab8f3fe3536 100644 --- a/code/modules/vehicles/mecha/mecha_construction_paths.dm +++ b/code/modules/vehicles/mecha/mecha_construction_paths.dm @@ -493,7 +493,7 @@ list( "key" = /obj/item/circuitboard/mecha/honker/targeting, "action" = ITEM_DELETE, - "desc" = "Prank targetting board can be added!", + "desc" = "Prank targeting board can be added!", "forward_message" = "added prank" ), list( diff --git a/code/modules/vehicles/mecha/mecha_damage.dm b/code/modules/vehicles/mecha/mecha_damage.dm index bc5d95d552c9..85d93e0ac50f 100644 --- a/code/modules/vehicles/mecha/mecha_damage.dm +++ b/code/modules/vehicles/mecha/mecha_damage.dm @@ -9,7 +9,7 @@ * Pretty simple, adds armor, you can choose against what * ## Internal damage * When taking damage will force you to take some time to repair, encourages improvising in a fight - * Targetting different def zones will damage them to encurage a more strategic approach to fights + * Targeting different def zones will damage them to encurage a more strategic approach to fights * where they target the "dangerous" modules */ diff --git a/code/modules/vehicles/mecha/mecha_defense.dm b/code/modules/vehicles/mecha/mecha_defense.dm index bf48b1b8267c..7db061b0383d 100644 --- a/code/modules/vehicles/mecha/mecha_defense.dm +++ b/code/modules/vehicles/mecha/mecha_defense.dm @@ -9,11 +9,11 @@ * Pretty simple, adds armor, you can choose against what * ## Internal damage * When taking damage will force you to take some time to repair, encourages improvising in a fight - * Targetting different def zones will damage them to encurage a more strategic approach to fights + * Targeting different def zones will damage them to encurage a more strategic approach to fights * where they target the "dangerous" modules */ -/// tries to damage mech equipment depending on damage and where is being targetted +/// tries to damage mech equipment depending on damage and where is being targeted /obj/vehicle/sealed/mecha/proc/try_damage_component(damage, def_zone) if(damage < component_damage_threshold) return @@ -114,10 +114,19 @@ return ..() /obj/vehicle/sealed/mecha/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit) //wrapper - if(!enclosed && LAZYLEN(occupants) && !(mecha_flags & SILICON_PILOT) && (hitting_projectile.def_zone == BODY_ZONE_HEAD || hitting_projectile.def_zone == BODY_ZONE_CHEST)) //allows bullets to hit the pilot of open-canopy mechs + . = ..() + if(. != BULLET_ACT_HIT) + return . + + //allows bullets to hit the pilot of open-canopy mechs + if(!enclosed \ + && LAZYLEN(occupants) \ + && !(mecha_flags & SILICON_PILOT) \ + && (def_zone == BODY_ZONE_HEAD || def_zone == BODY_ZONE_CHEST)) for(var/mob/living/hitmob as anything in occupants) hitmob.bullet_act(hitting_projectile, def_zone, piercing_hit) //If the sides are open, the occupant can be hit return BULLET_ACT_HIT + log_message("Hit by projectile. Type: [hitting_projectile]([hitting_projectile.damage_type]).", LOG_MECHA, color="red") // yes we *have* to run the armor calc proc here I love tg projectile code too try_damage_component(run_atom_armor( @@ -126,8 +135,7 @@ damage_flag = hitting_projectile.armor_flag, attack_dir = REVERSE_DIR(hitting_projectile.dir), armour_penetration = hitting_projectile.armour_penetration, - ), hitting_projectile.def_zone) - return ..() + ), def_zone) /obj/vehicle/sealed/mecha/ex_act(severity, target) log_message("Affected by explosion of severity: [severity].", LOG_MECHA, color="red") diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 270ced11cec0..810af6e0d1a4 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -74,9 +74,14 @@ ///The ref of the last mob to shop with us var/last_shopper var/tilted = FALSE + /// If tilted, this variable should always be the rotation that was applied when we were tilted. Stored for the purposes of unapplying it. + var/tilted_rotation = 0 var/tiltable = TRUE var/squish_damage = 75 - var/forcecrit = 0 + /// The chance, in percent, of this vendor performing a critical hit on anything it crushes via [tilt]. + var/crit_chance = 15 + /// If set to a critical define in crushing.dm, anything this vendor crushes will always be hit with that effect. + var/forcecrit = null var/num_shards = 7 var/list/pinned_mobs = list() ///Icon for the maintenance panel overlay @@ -629,10 +634,8 @@ freebie(user, 1) if(26 to 75) return - if(76 to 90) + if(76 to 100) tilt(user) - if(91 to 100) - tilt(user, crit=TRUE) /obj/machinery/vending/proc/freebie(mob/fatty, freebies) visible_message(span_notice("[src] yields [freebies > 1 ? "several free goodies" : "a free goody"]!")) @@ -655,119 +658,324 @@ R.amount-- break -///Tilts ontop of the atom supplied, if crit is true some extra shit can happen. Returns TRUE if it dealt damage to something. -/obj/machinery/vending/proc/tilt(atom/fatty, crit=FALSE) +/// Tilts ontop of the atom supplied, if crit is true some extra shit can happen. See [fall_and_crush] for return values. +/obj/machinery/vending/proc/tilt(atom/fatty, local_crit_chance = crit_chance, forced_crit = forcecrit) if(QDELETED(src) || !has_gravity(src)) return - visible_message(span_danger("[src] tips over!")) - tilted = TRUE - layer = ABOVE_MOB_LAYER - SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER) - var/crit_case - if(crit) - crit_case = rand(1,6) + . = NONE - if(forcecrit) - crit_case = forcecrit + var/picked_rotation = pick(90, 270) + if(Adjacent(fatty)) + . = fall_and_crush(get_turf(fatty), squish_damage, local_crit_chance, forced_crit, 6 SECONDS, rotation = picked_rotation) - . = FALSE + if (. & SUCCESSFULLY_FELL_OVER) + visible_message(span_danger("[src] tips over!")) + tilted = TRUE + tilted_rotation = picked_rotation + layer = ABOVE_MOB_LAYER + SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER) - if(Adjacent(fatty)) - for(var/mob/living/L in get_turf(fatty)) - var/was_alive = (L.stat != DEAD) - var/mob/living/carbon/C = L - - SEND_SIGNAL(L, COMSIG_ON_VENDOR_CRUSH) - - - if(istype(C)) - var/crit_rebate = 0 // lessen the normal damage we deal for some of the crits - - if(crit_case < 5) // the body/head asplode case has its own description - C.visible_message(span_danger("[C] is crushed by [src]!"), \ - span_userdanger("You are crushed by [src]!")) - - switch(crit_case) // only carbons can have the fun crits - if(1) // shatter their legs and bleed 'em - crit_rebate = 60 - C.bleed(150) - var/obj/item/bodypart/leg/left/l = C.get_bodypart(BODY_ZONE_L_LEG) - if(l) - l.receive_damage(brute=200) - var/obj/item/bodypart/leg/right/r = C.get_bodypart(BODY_ZONE_R_LEG) - if(r) - r.receive_damage(brute=200) - if(l || r) - C.visible_message(span_danger("[C]'s legs shatter with a sickening crunch!"), \ - span_userdanger("Your legs shatter with a sickening crunch!")) - if(2) // pin them beneath the machine until someone untilts it - forceMove(get_turf(C)) - buckle_mob(C, force=TRUE) - C.visible_message(span_danger("[C] is pinned underneath [src]!"), \ - span_userdanger("You are pinned down by [src]!")) - if(3) // glass candy - crit_rebate = 50 - for(var/i in 1 to num_shards) - var/obj/item/shard/shard = new /obj/item/shard(get_turf(C)) - shard.embedding = list(embed_chance = 100, ignore_throwspeed_threshold = TRUE, impact_pain_mult=1, pain_chance=5) - shard.updateEmbedding() - C.hitby(shard, skipcatch = TRUE, hitpush = FALSE) - shard.embedding = list() - shard.updateEmbedding() - if(4) // paralyze this binch - // the new paraplegic gets like 4 lines of losing their legs so skip them - visible_message(span_danger("[C]'s spinal cord is obliterated with a sickening crunch!"), ignored_mobs = list(C)) - C.gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic) - if(5) // limb squish! - for(var/i in C.bodyparts) - var/obj/item/bodypart/squish_part = i - if(IS_ORGANIC_LIMB(squish_part)) - var/type_wound = pick(list(/datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/moderate)) - squish_part.force_wound_upwards(type_wound) - else - squish_part.receive_damage(brute=30) - C.visible_message(span_danger("[C]'s body is maimed underneath the mass of [src]!"), \ - span_userdanger("Your body is maimed underneath the mass of [src]!")) - if(6) // skull squish! - var/obj/item/bodypart/head/O = C.get_bodypart(BODY_ZONE_HEAD) - if(O) - if(O.dismember()) - C.visible_message(span_danger("[O] explodes in a shower of gore beneath [src]!"), \ - span_userdanger("Oh f-")) - O.drop_organs() - qdel(O) - new /obj/effect/gibspawner/human/bodypartless(get_turf(C)) - - if(prob(30)) - C.apply_damage(max(0, squish_damage - crit_rebate), forced=TRUE, spread_damage=TRUE) // the 30% chance to spread the damage means you escape breaking any bones + if(get_turf(fatty) != get_turf(src)) + throw_at(get_turf(fatty), 1, 1, spin = FALSE, quickstart = FALSE) + +/** + * Exists for the purposes of custom behavior. + * Called directly after [crushed] is crushed. + * + * Args: + * * mob/living/crushed: The mob that was crushed. + * * was_alive: Boolean. True if the mob was alive before the crushing. + */ +/atom/movable/proc/post_crush_living(mob/living/crushed, was_alive) + return + +/** + * Exists for the purposes of custom behavior. + * Called directly after src actually rotates and falls over. + */ +/atom/movable/proc/post_tilt() + return + +/obj/machinery/vending/post_crush_living(mob/living/crushed, was_alive) + + if(was_alive && crushed.stat == DEAD && crushed.client) + crushed.client.give_award(/datum/award/achievement/misc/vendor_squish, crushed) // good job losing a fight with an inanimate object idiot + + add_memory_in_range(crushed, 7, /datum/memory/witness_vendor_crush, protagonist = crushed, antagonist = src) + + return ..() + + +/obj/machinery/vending/apply_crit_crush(crit_case, atom_target) + . = ..() + + if (.) + return TRUE + + switch (crit_case) + if (VENDOR_CRUSH_CRIT_GLASSCANDY) + if (!iscarbon(atom_target)) + return FALSE + var/mob/living/carbon/carbon_target = atom_target + for(var/i in 1 to num_shards) + var/obj/item/shard/shard = new /obj/item/shard(get_turf(carbon_target)) + shard.embedding = list(embed_chance = 100, ignore_throwspeed_threshold = TRUE, impact_pain_mult = 1, pain_chance = 5) + shard.updateEmbedding() + carbon_target.hitby(shard, skipcatch = TRUE, hitpush = FALSE) + shard.embedding = list() + shard.updateEmbedding() + return TRUE + if (VENDOR_CRUSH_CRIT_PIN) // pin them beneath the machine until someone untilts it + if (!isliving(atom_target)) + return FALSE + var/mob/living/living_target = atom_target + forceMove(get_turf(living_target)) + buckle_mob(living_target, force=TRUE) + living_target.visible_message(span_danger("[living_target] is pinned underneath [src]!"), span_userdanger("You are pinned down by [src]!")) + return TRUE + + return FALSE + +/** + * Causes src to fall onto [target], crushing everything on it (including itself) with [damage] + * and a small chance to do a spectacular effect per entity (if a chance above 0 is provided). + * + * Args: + * * turf/target: The turf to fall onto. Cannot be null. + * * damage: The raw numerical damage to do by default. + * * chance_to_crit: The percent chance of a critical hit occuring. Default: 0 + * * forced_crit_case: If given a value from crushing.dm, [target] and it's contents will always be hit with that specific critical hit. Default: null + * * paralyze_time: The time, in deciseconds, a given mob/living will be paralyzed for if crushed. + * * crush_dir: The direction the crush is coming from. Default: dir of src to [target]. + * * damage_type: The type of damage to do. Default: BRUTE + * * damage_flag: The attack flag for armor purposes. Default: MELEE + * * rotation: The angle of which to rotate src's transform by on a successful tilt. Default: 90. + * + * Returns: A collection of bitflags defined in crushing.dm. Read that file's documentation for info. + */ +/atom/movable/proc/fall_and_crush(turf/target, damage, chance_to_crit = 0, forced_crit_case = null, paralyze_time, crush_dir = get_dir(get_turf(src), target), damage_type = BRUTE, damage_flag = MELEE, rotation = 90) + + ASSERT(!isnull(target)) + + var/flags_to_return = NONE + + if (!target.is_blocked_turf(TRUE, src, list(src))) + for(var/atom/atom_target in (target.contents) + target) + if (isarea(atom_target)) + continue + + if (SEND_SIGNAL(atom_target, COMSIG_PRE_TILT_AND_CRUSH, src) & COMPONENT_IMMUNE_TO_TILT_AND_CRUSH) + continue + + var/crit_case = forced_crit_case + if (isnull(crit_case) && chance_to_crit > 0) + if (prob(chance_to_crit)) + crit_case = pick_weight(get_crit_crush_chances()) + var/crit_rebate_mult = 1 // lessen the normal damage we deal for some of the crits + + if (!isnull(crit_case)) + crit_rebate_mult = fall_and_crush_crit_rebate_table(crit_case) + apply_crit_crush(crit_case, atom_target) + + var/adjusted_damage = damage * crit_rebate_mult + var/crushed + if (isliving(atom_target)) + crushed = TRUE + var/mob/living/carbon/living_target = atom_target + var/was_alive = (living_target.stat != DEAD) + var/blocked = living_target.run_armor_check(attack_flag = damage_flag) + if (iscarbon(living_target)) + var/mob/living/carbon/carbon_target = living_target + if(prob(30)) + carbon_target.apply_damage(max(0, adjusted_damage), damage_type, blocked = blocked, forced = TRUE, spread_damage = TRUE, attack_direction = crush_dir) // the 30% chance to spread the damage means you escape breaking any bones + else + var/brute = (damage_type == BRUTE ? damage : 0) * 0.5 + var/burn = (damage_type == BURN ? damage : 0) * 0.5 + carbon_target.take_bodypart_damage(brute, burn, check_armor = TRUE, wound_bonus = 5) // otherwise, deal it to 2 random limbs (or the same one) which will likely shatter something + carbon_target.take_bodypart_damage(brute, burn, check_armor = TRUE, wound_bonus = 5) + carbon_target.AddElement(/datum/element/squish, 80 SECONDS) else - C.take_bodypart_damage((squish_damage - crit_rebate)*0.5, wound_bonus = 5) // otherwise, deal it to 2 random limbs (or the same one) which will likely shatter something - C.take_bodypart_damage((squish_damage - crit_rebate)*0.5, wound_bonus = 5) - C.AddElement(/datum/element/squish, 80 SECONDS) - else - L.visible_message(span_danger("[L] is crushed by [src]!"), \ - span_userdanger("You are crushed by [src]!")) - L.apply_damage(squish_damage, forced=TRUE) - if(crit_case) - L.apply_damage(squish_damage, forced=TRUE) - if(was_alive && L.stat == DEAD && L.client) - L.client.give_award(/datum/award/achievement/misc/vendor_squish, L) // good job losing a fight with an inanimate object idiot - - L.Paralyze(60) - L.emote("scream") - . = TRUE - playsound(L, 'sound/effects/blobattack.ogg', 40, TRUE) - playsound(L, 'sound/effects/splat.ogg', 50, TRUE) - add_memory_in_range(L, 7, /datum/memory/witness_vendor_crush, protagonist = L, antagonist = src) - - var/matrix/M = matrix() - M.Turn(pick(90, 270)) - transform = M + living_target.apply_damage(adjusted_damage, damage_type, blocked = blocked, forced = TRUE, attack_direction = crush_dir) + + living_target.Paralyze(paralyze_time) + living_target.emote("scream") + playsound(living_target, 'sound/effects/blobattack.ogg', 40, TRUE) + playsound(living_target, 'sound/effects/splat.ogg', 50, TRUE) + post_crush_living(living_target, was_alive) + flags_to_return |= (SUCCESSFULLY_CRUSHED_MOB|SUCCESSFULLY_CRUSHED_ATOM) + + else if (atom_target.uses_integrity && !(atom_target.invisibility > SEE_INVISIBLE_LIVING) && !(is_type_in_typecache(atom_target, GLOB.WALLITEMS_INTERIOR) || is_type_in_typecache(atom_target, GLOB.WALLITEMS_EXTERIOR))) + atom_target.take_damage(adjusted_damage, damage_type, damage_flag, FALSE, crush_dir) + crushed = TRUE + flags_to_return |= SUCCESSFULLY_CRUSHED_ATOM + + if (crushed) + atom_target.visible_message(span_danger("[atom_target] is crushed by [src]!"), span_userdanger("You are crushed by [src]!")) + SEND_SIGNAL(atom_target, COMSIG_POST_TILT_AND_CRUSH, src) + + var/matrix/to_turn = turn(transform, rotation) + animate(src, transform = to_turn, 0.2 SECONDS) + playsound(src, 'sound/effects/bang.ogg', 40) + + visible_message(span_danger("[src] tips over, slamming hard onto [target]!")) + flags_to_return |= SUCCESSFULLY_FELL_OVER + post_tilt() + else + visible_message(span_danger("[src] rebounds comically as it fails to slam onto [target]!")) - if(get_turf(fatty) != get_turf(src)) - throw_at(get_turf(fatty), 1, 1, spin=FALSE, quickstart=FALSE) + Move(target, crush_dir) // we still TRY to move onto it for shit like teleporters + return flags_to_return + + +/obj/machinery/vending/post_crush_living(mob/living/crushed, was_alive) + if(was_alive && crushed.stat == DEAD && crushed.client) + crushed.client.give_award(/datum/award/achievement/misc/vendor_squish, crushed) // good job losing a fight with an inanimate object idiot + + add_memory_in_range(crushed, 7, /datum/memory/witness_vendor_crush, protagonist = crushed, antagonist = src) + + return ..() + +/** + * Allows damage to be reduced on certain crit cases. + * Args: + * * crit_case: The critical case chosen. + */ +/atom/movable/proc/fall_and_crush_crit_rebate_table(crit_case) + + ASSERT(!isnull(crit_case)) + + switch(crit_case) + if (CRUSH_CRIT_SHATTER_LEGS) + return 0.2 + else + return 1 + +/obj/machinery/vending/fall_and_crush_crit_rebate_table(crit_case) + + if (crit_case == VENDOR_CRUSH_CRIT_GLASSCANDY) + return 0.33 + + return ..() + +/** + * Returns a assoc list of (critcase -> num), where critcase is a critical define in crushing.dm and num is a weight. + * Use with pickweight to acquire a random critcase. + */ +/atom/movable/proc/get_crit_crush_chances() + RETURN_TYPE(/list) + + var/list/weighted_crits = list() + + weighted_crits[CRUSH_CRIT_SHATTER_LEGS] = 100 + weighted_crits[CRUSH_CRIT_PARAPALEGIC] = 80 + weighted_crits[CRUSH_CRIT_HEADGIB] = 20 + weighted_crits[CRUSH_CRIT_SQUISH_LIMB] = 100 + + return weighted_crits + +/obj/machinery/vending/get_crit_crush_chances() + var/list/weighted_crits = ..() + + weighted_crits[VENDOR_CRUSH_CRIT_GLASSCANDY] = 100 + weighted_crits[VENDOR_CRUSH_CRIT_PIN] = 100 + + return weighted_crits + +/** + * Should be where critcase effects are actually implemented. Use this to apply critcases. + * Args: + * * crit_case: The chosen critcase, defined in crushing.dm. + * * atom/atom_target: The target to apply the critical hit to. Cannot be null. Can be anything except /area. + * + * Returns: + * TRUE if a crit case is successfully applied, FALSE otherwise. + */ +/atom/movable/proc/apply_crit_crush(crit_case, atom/atom_target) + switch (crit_case) + if(CRUSH_CRIT_SHATTER_LEGS) // shatter their legs and bleed 'em + if (!iscarbon(atom_target)) + return FALSE + var/mob/living/carbon/carbon_target = atom_target + carbon_target.bleed(150) + var/obj/item/bodypart/leg/left/left_leg = carbon_target.get_bodypart(BODY_ZONE_L_LEG) + if(left_leg) + left_leg.receive_damage(brute = 200) + var/obj/item/bodypart/leg/right/right_leg = carbon_target.get_bodypart(BODY_ZONE_R_LEG) + if(right_leg) + right_leg.receive_damage(brute = 200) + if(left_leg || right_leg) + carbon_target.visible_message(span_danger("[carbon_target]'s legs shatter with a sickening crunch!"), span_userdanger("Your legs shatter with a sickening crunch!")) + return TRUE + if(CRUSH_CRIT_PARAPALEGIC) // paralyze this binch + // the new paraplegic gets like 4 lines of losing their legs so skip them + if (!iscarbon(atom_target)) + return FALSE + var/mob/living/carbon/carbon_target = atom_target + visible_message(span_danger("[carbon_target]'s spinal cord is obliterated with a sickening crunch!"), ignored_mobs = list(carbon_target)) + carbon_target.gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic) + return TRUE + if(CRUSH_CRIT_SQUISH_LIMB) // limb squish! + if (!iscarbon(atom_target)) + return FALSE + var/mob/living/carbon/carbon_target = atom_target + for(var/obj/item/bodypart/squish_part in carbon_target.bodyparts) + var/severity = pick(WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, WOUND_SEVERITY_CRITICAL) + if (!carbon_target.cause_wound_of_type_and_severity(WOUND_BLUNT, squish_part, severity, wound_source = "crushed by [src]")) + squish_part.receive_damage(brute = 30) + carbon_target.visible_message(span_danger("[carbon_target]'s body is maimed underneath the mass of [src]!"), span_userdanger("Your body is maimed underneath the mass of [src]!")) + return TRUE + if(CRUSH_CRIT_HEADGIB) // skull squish! + if (!iscarbon(atom_target)) + return FALSE + var/mob/living/carbon/carbon_target = atom_target + var/obj/item/bodypart/head/carbon_head = carbon_target.get_bodypart(BODY_ZONE_HEAD) + if(carbon_head) + if(carbon_head.dismember()) + carbon_target.visible_message(span_danger("[carbon_head] explodes in a shower of gore beneath [src]!"), span_userdanger("Oh f-")) + carbon_head.drop_organs() + qdel(carbon_head) + new /obj/effect/gibspawner/human/bodypartless(get_turf(carbon_target)) + return TRUE + + return FALSE + +/obj/machinery/vending/apply_crit_crush(crit_case, atom_target) + . = ..() + + if (.) + return TRUE + + switch (crit_case) + if (VENDOR_CRUSH_CRIT_GLASSCANDY) + if (!iscarbon(atom_target)) + return FALSE + var/mob/living/carbon/carbon_target = atom_target + for(var/i in 1 to num_shards) + var/obj/item/shard/shard = new /obj/item/shard(get_turf(carbon_target)) + shard.embedding = list(embed_chance = 100, ignore_throwspeed_threshold = TRUE, impact_pain_mult = 1, pain_chance = 5) + shard.updateEmbedding() + carbon_target.hitby(shard, skipcatch = TRUE, hitpush = FALSE) + shard.embedding = list() + shard.updateEmbedding() + return TRUE + if (VENDOR_CRUSH_CRIT_PIN) // pin them beneath the machine until someone untilts it + if (!isliving(atom_target)) + return FALSE + var/mob/living/living_target = atom_target + forceMove(get_turf(living_target)) + buckle_mob(living_target, force=TRUE) + living_target.visible_message(span_danger("[living_target] is pinned underneath [src]!"), span_userdanger("You are pinned down by [src]!")) + return TRUE + + return FALSE + +/** + * Rights the vendor up, unpinning mobs under it, if any. + * Arguments: + * user - mob that has untilted the vendor + */ /obj/machinery/vending/proc/untilt(mob/user) if(user) user.visible_message(span_notice("[user] rights [src]."), \ @@ -779,9 +987,9 @@ layer = initial(layer) SET_PLANE_IMPLICIT(src, initial(plane)) - var/matrix/M = matrix() - M.Turn(0) - transform = M + var/matrix/to_turn = turn(transform, -tilted_rotation) + animate(src, transform = to_turn, 0.2 SECONDS) + tilted_rotation = 0 /obj/machinery/vending/proc/loadingAttempt(obj/item/I, mob/user) . = TRUE diff --git a/code/modules/vending/drinnerware.dm b/code/modules/vending/drinnerware.dm index e7492f43d94d..006ecb1eb672 100644 --- a/code/modules/vending/drinnerware.dm +++ b/code/modules/vending/drinnerware.dm @@ -4,27 +4,61 @@ product_ads = "Mm, food stuffs!;Food and food accessories.;Get your plates!;You like forks?;I like forks.;Woo, utensils.;You don't really need these..." icon_state = "dinnerware" panel_type = "panel4" - products = list( - /obj/item/storage/bag/tray = 8, - /obj/item/reagent_containers/cup/bowl = 30, - /obj/item/reagent_containers/cup/soup_pot = 3, - /obj/item/kitchen/fork = 6, - /obj/item/kitchen/spoon = 10, - /obj/item/kitchen/spoon/soup_ladle = 3, - /obj/item/reagent_containers/cup/glass/drinkingglass = 8, - /obj/item/reagent_containers/condiment/pack/ketchup = 5, - /obj/item/reagent_containers/condiment/pack/hotsauce = 5, - /obj/item/reagent_containers/condiment/pack/astrotame = 5, - /obj/item/reagent_containers/condiment/saltshaker = 5, - /obj/item/reagent_containers/condiment/peppermill = 5, - /obj/item/clothing/suit/apron/chef = 2, - /obj/item/kitchen/rollingpin = 2, - /obj/item/knife/kitchen = 2, - /obj/item/book/granter/crafting_recipe/cooking_sweets_101 = 2, - /obj/item/skillchip/chefs_kiss = 2, - /obj/item/plate/small = 5, - /obj/item/plate = 10, - /obj/item/plate/large = 5, + product_categories = list( + list( + "name" = "Kitchen Utensils", + "icon" = FA_ICON_KITCHEN_SET, + "products" = list( + /obj/item/storage/bag/tray = 8, + /obj/item/reagent_containers/cup/soup_pot = 3, + /obj/item/kitchen/spoon/soup_ladle = 3, + /obj/item/clothing/suit/apron/chef = 2, + /obj/item/kitchen/rollingpin = 2, + /obj/item/kitchen/tongs = 2, + /obj/item/knife/kitchen = 2, + ), + ), + list( + "name" = "Eating Utensils", + "icon" = FA_ICON_UTENSILS, + "products" = list( + /obj/item/kitchen/fork = 6, + /obj/item/kitchen/spoon = 10, + ), + ), + list( + "name" = "Dinnerware", + "icon" = FA_ICON_PLATE_WHEAT, + "products" = list( + /obj/item/plate/small = 5, + /obj/item/plate = 10, + /obj/item/plate/large = 5, + /obj/item/reagent_containers/cup/bowl = 30, + /obj/item/reagent_containers/cup/glass/drinkingglass = 8, + ), + ), + list( + "name" = "Condiments", + "icon" = FA_ICON_BOTTLE_DROPLET, + "products" = list( + /obj/item/reagent_containers/condiment/pack/ketchup = 5, + /obj/item/reagent_containers/condiment/pack/hotsauce = 5, + /obj/item/reagent_containers/condiment/pack/astrotame = 5, + /obj/item/reagent_containers/condiment/saltshaker = 5, + /obj/item/reagent_containers/condiment/peppermill = 5, + ), + ), + list( + "name" = "Recipes", + "icon" = FA_ICON_BOOK_OPEN_READER, + "products" = list( + /obj/item/book/granter/crafting_recipe/cooking_sweets_101 = 2, + ), + ), + ) + + premium = list( + /obj/item/skillchip/chefs_kiss = 2 ) contraband = list( /obj/item/kitchen/rollingpin/illegal = 2, diff --git a/config/lavaruinblacklist.txt b/config/lavaruinblacklist.txt index c75ac5573421..91edbecce1fd 100644 --- a/config/lavaruinblacklist.txt +++ b/config/lavaruinblacklist.txt @@ -23,12 +23,21 @@ #_maps/RandomRuins/LavaRuins/lavaland_surface_sloth.dmm ##MISC +#_maps/RandomRuins/AnywhereRuins/fountain_hall.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_automated_trade_outpost.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_elephant_graveyard.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_gaia.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_library.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_phonebooth.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_tomb.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_ufo_crash.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_ww_vault.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_automated_trade_outpost.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_xeno_nest.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_wwiioutpost.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_tomb.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm @@ -39,3 +48,4 @@ #_maps/RandomRuins/LavaRuins/lavaland_surface_elephant_graveyard.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_library.dmm #_maps/RandomRuins/AnywhereRuins/fountain_hall.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_xeno_nest.dmm diff --git a/html/changelogs/archive/2018-12.yml b/html/changelogs/archive/2018-12.yml index 9f2eeb1a5f38..097b14839086 100644 --- a/html/changelogs/archive/2018-12.yml +++ b/html/changelogs/archive/2018-12.yml @@ -292,7 +292,7 @@ YoYoBatty: - rscadd: Blurry eyes now actually blur instead of an overlay. anconfuzedrock: - - tweak: slapping now doesn't require a specific intent or limb to be targetted. + - tweak: slapping now doesn't require a specific intent or limb to be targeted. coiax: - rscadd: Robotics can print cybernetic hearts, lungs and livers at their exofabricators (along with their upgraded versions). diff --git a/html/changelogs/archive/2021-02.yml b/html/changelogs/archive/2021-02.yml index 596ac007c1b5..8be6bee8841e 100644 --- a/html/changelogs/archive/2021-02.yml +++ b/html/changelogs/archive/2021-02.yml @@ -695,7 +695,7 @@ multiple targets. - balance: 'The immovable rod deals a LOT more damage to non-mobs that it passes through. feature: The immovable rod can now traverse z-level to chase its targets - down when used as both a targetted smite and as an option from the admin event + down when used as both a targeted smite and as an option from the admin event menu. feature: The immovable rod will subtly grow as it consumes the souls of sentients it hits.' - bugfix: The immovable rod no longer stalls on Icebox and will now spawn on the diff --git a/icons/mob/actions/actions_AI.dmi b/icons/mob/actions/actions_AI.dmi index 9f900bb9b4d9..ea940fc92af9 100644 Binary files a/icons/mob/actions/actions_AI.dmi and b/icons/mob/actions/actions_AI.dmi differ diff --git a/icons/mob/actions/actions_trader.dmi b/icons/mob/actions/actions_trader.dmi new file mode 100644 index 000000000000..59330b4aac83 Binary files /dev/null and b/icons/mob/actions/actions_trader.dmi differ diff --git a/icons/mob/simple/jungle/mook.dmi b/icons/mob/simple/jungle/mook.dmi index c9265b22a0ad..fbc38d29d99d 100644 Binary files a/icons/mob/simple/jungle/mook.dmi and b/icons/mob/simple/jungle/mook.dmi differ diff --git a/icons/mob/simple/jungle/seedling.dmi b/icons/mob/simple/jungle/seedling.dmi index 01e91c6c292c..c4a76ebb2d1c 100644 Binary files a/icons/mob/simple/jungle/seedling.dmi and b/icons/mob/simple/jungle/seedling.dmi differ diff --git a/icons/mob/simple/lavaland/lavaland_monsters.dmi b/icons/mob/simple/lavaland/lavaland_monsters.dmi index 1d546bd17d45..62f6f6a3fa1a 100644 Binary files a/icons/mob/simple/lavaland/lavaland_monsters.dmi and b/icons/mob/simple/lavaland/lavaland_monsters.dmi differ diff --git a/icons/mob/simple/traders.dmi b/icons/mob/simple/traders.dmi deleted file mode 100644 index 2f2b828bed91..000000000000 Binary files a/icons/mob/simple/traders.dmi and /dev/null differ diff --git a/icons/mob/spacevines.dmi b/icons/mob/spacevines.dmi new file mode 100644 index 000000000000..5839307bcfcd Binary files /dev/null and b/icons/mob/spacevines.dmi differ diff --git a/icons/mob/telegraphing/telegraph.dmi b/icons/mob/telegraphing/telegraph.dmi index d5e03419cd89..de525ead4ee9 100644 Binary files a/icons/mob/telegraphing/telegraph.dmi and b/icons/mob/telegraphing/telegraph.dmi differ diff --git a/icons/obj/anomaly.dmi b/icons/obj/anomaly.dmi new file mode 100644 index 000000000000..ad712e290fb7 Binary files /dev/null and b/icons/obj/anomaly.dmi differ diff --git a/icons/obj/clothing/modsuit/mod_modules.dmi b/icons/obj/clothing/modsuit/mod_modules.dmi index 037e7fe69634..f1d19c29da11 100644 Binary files a/icons/obj/clothing/modsuit/mod_modules.dmi and b/icons/obj/clothing/modsuit/mod_modules.dmi differ diff --git a/icons/obj/hydroponics/growing_fruits.dmi b/icons/obj/hydroponics/growing_fruits.dmi index d52fa6568bdb..73c22bc11c1d 100644 Binary files a/icons/obj/hydroponics/growing_fruits.dmi and b/icons/obj/hydroponics/growing_fruits.dmi differ diff --git a/icons/obj/medical/organs/mining_organs.dmi b/icons/obj/medical/organs/mining_organs.dmi index f3fc298284ba..172f94001ffa 100644 Binary files a/icons/obj/medical/organs/mining_organs.dmi and b/icons/obj/medical/organs/mining_organs.dmi differ diff --git a/icons/obj/mining_zones/artefacts.dmi b/icons/obj/mining_zones/artefacts.dmi new file mode 100644 index 000000000000..d4c603834d21 Binary files /dev/null and b/icons/obj/mining_zones/artefacts.dmi differ diff --git a/icons/obj/service/hydroponics/growing_fruits.dmi b/icons/obj/service/hydroponics/growing_fruits.dmi new file mode 100644 index 000000000000..c0f547322c7c Binary files /dev/null and b/icons/obj/service/hydroponics/growing_fruits.dmi differ diff --git a/icons/obj/service/hydroponics/seeds.dmi b/icons/obj/service/hydroponics/seeds.dmi new file mode 100644 index 000000000000..ffef219193a7 Binary files /dev/null and b/icons/obj/service/hydroponics/seeds.dmi differ diff --git a/icons/obj/service/kitchen.dmi b/icons/obj/service/kitchen.dmi new file mode 100644 index 000000000000..aeafe2591e9b Binary files /dev/null and b/icons/obj/service/kitchen.dmi differ diff --git a/icons/obj/structures.dmi b/icons/obj/structures.dmi index a931c282f4dc..4b19402cfd59 100644 Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ diff --git a/icons/obj/trader_signs.dmi b/icons/obj/trader_signs.dmi new file mode 100644 index 000000000000..a6789e0bb4c5 Binary files /dev/null and b/icons/obj/trader_signs.dmi differ diff --git a/icons/testing/turf_analysis.dmi b/icons/testing/turf_analysis.dmi index e3aac1ebd05b..864e6c14643d 100644 Binary files a/icons/testing/turf_analysis.dmi and b/icons/testing/turf_analysis.dmi differ diff --git a/monkestation/_maps/RandomBars/Tram/tram_bar_beachside.dmm b/monkestation/_maps/RandomBars/Tram/tram_bar_beachside.dmm index 03ffb3d85c1e..a94346f91a0d 100644 --- a/monkestation/_maps/RandomBars/Tram/tram_bar_beachside.dmm +++ b/monkestation/_maps/RandomBars/Tram/tram_bar_beachside.dmm @@ -274,7 +274,7 @@ /turf/open/water/beach/biodome/sinking, /area/station/commons/lounge) "js" = ( -/mob/living/simple_animal/parrot, +/mob/living/basic/parrot, /obj/structure/fluff/beach_umbrella/security, /turf/open/misc/beach/sand, /area/station/commons/lounge) diff --git a/monkestation/code/datums/components/turf_healing.dm b/monkestation/code/datums/components/turf_healing.dm index 107d2f3e187f..2acaacb2de74 100644 --- a/monkestation/code/datums/components/turf_healing.dm +++ b/monkestation/code/datums/components/turf_healing.dm @@ -33,4 +33,4 @@ if(entry == STAMINA) healed_mob.stamina.adjust(healing_types[entry] * seconds_per_tick * 0.5) continue - healed_mob.apply_damage_type((-healing_types[entry] * seconds_per_tick * 0.5), entry) + healed_mob.heal_damage_type((healing_types[entry] * seconds_per_tick * 0.5), entry) diff --git a/monkestation/code/datums/diseases/advance/symptoms/clockwork.dm b/monkestation/code/datums/diseases/advance/symptoms/clockwork.dm index 5e3cf22383f4..11a159fbef40 100644 --- a/monkestation/code/datums/diseases/advance/symptoms/clockwork.dm +++ b/monkestation/code/datums/diseases/advance/symptoms/clockwork.dm @@ -145,16 +145,16 @@ if(replacebody) for(var/obj/item/bodypart/Oldlimb in Host.bodyparts) if(!IS_ORGANIC_LIMB(Oldlimb)) - if(robustbits && Oldlimb.brute_reduction < 3 || Oldlimb.burn_reduction < 2) - Oldlimb.burn_reduction = max(4, Oldlimb.burn_reduction) - Oldlimb.brute_reduction = max(5, Oldlimb.brute_reduction) + if(robustbits && Oldlimb.brute_modifier < 3 || Oldlimb.burn_modifier < 2) + Oldlimb.burn_modifier = max(4, Oldlimb.burn_modifier) + Oldlimb.brute_modifier = max(5, Oldlimb.brute_modifier) continue switch(Oldlimb.body_zone) if(BODY_ZONE_HEAD)//i wish i knew how to transfer external organs from old limb to new limb, but i dont. var/obj/item/bodypart/head/robot/clockwork/newlimb = new() if(robustbits) - newlimb.brute_reduction = 5 - newlimb.burn_reduction = 4 + newlimb.brute_modifier = 5 + newlimb.burn_modifier = 4 newlimb.replace_limb(Host, TRUE) Host.visible_message("Your head feels numb, and cold.
") qdel(Oldlimb) @@ -162,8 +162,8 @@ if(BODY_ZONE_CHEST)//i wish i knew how to transfer external organs from old limb to new limb, but i dont. var/obj/item/bodypart/chest/robot/clockwork/newlimb = new() if(robustbits) - newlimb.brute_reduction = 5 - newlimb.burn_reduction = 4 + newlimb.brute_modifier = 5 + newlimb.burn_modifier = 4 newlimb.replace_limb(Host, TRUE) Host.visible_message("Your [Oldlimb] feels numb, and cold.
") qdel(Oldlimb) @@ -171,8 +171,8 @@ if(BODY_ZONE_L_ARM) var/obj/item/bodypart/arm/left/robot/clockwork/newlimb = new() if(robustbits) - newlimb.brute_reduction = 5 - newlimb.burn_reduction = 4 + newlimb.brute_modifier = 5 + newlimb.burn_modifier = 4 newlimb.replace_limb(Host, TRUE) Host.visible_message("Your [Oldlimb] feels numb, and cold.
") qdel(Oldlimb) @@ -180,8 +180,8 @@ if(BODY_ZONE_R_ARM) var/obj/item/bodypart/arm/right/robot/clockwork/newlimb = new() if(robustbits) - newlimb.brute_reduction = 5 - newlimb.burn_reduction = 4 + newlimb.brute_modifier = 5 + newlimb.burn_modifier = 4 newlimb.replace_limb(Host, TRUE) Host.visible_message("Your [Oldlimb] feels numb, and cold.") qdel(Oldlimb) @@ -189,8 +189,8 @@ if(BODY_ZONE_L_LEG) var/obj/item/bodypart/leg/left/robot/clockwork/newlimb = new() if(robustbits) - newlimb.brute_reduction = 5 - newlimb.burn_reduction = 4 + newlimb.brute_modifier = 5 + newlimb.burn_modifier = 4 newlimb.replace_limb(Host, TRUE) Host.visible_message("Your [Oldlimb] feels numb, and cold.") qdel(Oldlimb) @@ -198,8 +198,8 @@ if(BODY_ZONE_R_LEG) var/obj/item/bodypart/leg/right/robot/clockwork/newlimb = new() if(robustbits) - newlimb.brute_reduction = 5 - newlimb.burn_reduction = 4 + newlimb.brute_modifier = 5 + newlimb.burn_modifier = 4 newlimb.replace_limb(Host, TRUE) Host.visible_message("Your [Oldlimb] feels numb, and cold.") qdel(Oldlimb) @@ -215,8 +215,8 @@ Host.dna.species.regenerate_organs(Host, replace_current = TRUE) for(var/obj/item/bodypart/Oldlimb in Host.bodyparts) if(!IS_ORGANIC_LIMB(Oldlimb)) - Oldlimb.burn_reduction = initial(Oldlimb.burn_reduction) - Oldlimb.brute_reduction = initial(Oldlimb.brute_reduction) + Oldlimb.burn_modifier = initial(Oldlimb.burn_modifier) + Oldlimb.brute_modifier = initial(Oldlimb.brute_modifier) /datum/symptom/robotic_adaptation/OnRemove(datum/disease/advance/advanced_disease) advanced_disease.infectable_biotypes -= MOB_ROBOTIC diff --git a/monkestation/code/datums/stamina_container.dm b/monkestation/code/datums/stamina_container.dm index ec338c43e9f3..62b3afa7b4ca 100644 --- a/monkestation/code/datums/stamina_container.dm +++ b/monkestation/code/datums/stamina_container.dm @@ -81,3 +81,4 @@ update() if((amt < 0) && is_regenerating) pause(STAMINA_REGEN_TIME) + return amt diff --git a/monkestation/code/datums/status_effects/food_buffs.dm b/monkestation/code/datums/status_effects/food_buffs.dm index ebf4c084f6a5..ce177b143cdc 100644 --- a/monkestation/code/datums/status_effects/food_buffs.dm +++ b/monkestation/code/datums/status_effects/food_buffs.dm @@ -67,7 +67,7 @@ if(ishuman(owner)) var/mob/living/carbon/user = owner for(var/obj/item/bodypart/limbs in user.bodyparts) - limbs.brute_reduction += 3 + limbs.brute_modifier -= 0.1 return ..() /datum/status_effect/food/resistance/on_remove() @@ -75,7 +75,7 @@ if(ishuman(owner)) var/mob/living/carbon/user = owner for(var/obj/item/bodypart/limbs in user.bodyparts) - limbs.brute_reduction -= 3 + limbs.brute_modifier += 0.1 #define DURATION_LOSS 250 diff --git a/monkestation/code/game/machinery/prize_vendor.dm b/monkestation/code/game/machinery/prize_vendor.dm index ee326f17b647..bee4c1113541 100644 --- a/monkestation/code/game/machinery/prize_vendor.dm +++ b/monkestation/code/game/machinery/prize_vendor.dm @@ -143,8 +143,8 @@ /obj/machinery/prize_vendor/pets name = "Pet Prize Vendor" desc = "Friend dispenser." - dispense_list_override = list(/mob/living/simple_animal/parrot/natural = 3, - /mob/living/simple_animal/sloth = 3, + dispense_list_override = list(/mob/living/basic/parrot = 3, + /mob/living/basic/sloth = 3, /mob/living/simple_animal/pet/cat = 3, /mob/living/basic/pet/fox = 3, /mob/living/simple_animal/pet/gondola = 1, diff --git a/monkestation/code/game/objects/items/choice_beacon.dm b/monkestation/code/game/objects/items/choice_beacon.dm index bfc1c7db68e1..fe00d08e0b2c 100644 --- a/monkestation/code/game/objects/items/choice_beacon.dm +++ b/monkestation/code/game/objects/items/choice_beacon.dm @@ -10,7 +10,7 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targetting/fetch, + /datum/pet_command/point_targeting/fetch, /datum/pet_command/play_dead, ) @@ -25,7 +25,7 @@ /mob/living/basic/axolotl, /mob/living/basic/mouse, /mob/living/basic/mouse/rat, - /mob/living/simple_animal/parrot, + /mob/living/basic/parrot, /mob/living/basic/butterfly, /mob/living/basic/bee/friendly, /mob/living/basic/crab, diff --git a/monkestation/code/game/objects/items/effects/honk_platinum.dm b/monkestation/code/game/objects/items/effects/honk_platinum.dm index c4deebd28552..2cfaf15bdc81 100644 --- a/monkestation/code/game/objects/items/effects/honk_platinum.dm +++ b/monkestation/code/game/objects/items/effects/honk_platinum.dm @@ -4,12 +4,13 @@ /obj/item/effect_granter/honk_platinum/grant_effect(mob/living/carbon/granter) - var/mob/living/simple_animal/parrot/honk_platinum/new_honk = new(granter.loc) + var/mob/living/basic/parrot/honk_platinum/new_honk = new(granter.loc) new_honk.mind_initialize() var/datum/mind/granters_mind = granter.mind granters_mind.transfer_to(new_honk) + new_honk.adjust_hand_count(2) qdel(granter) . = ..() diff --git a/monkestation/code/game/objects/items/guns/SRN.dm b/monkestation/code/game/objects/items/guns/SRN.dm index ddd8f996a809..63e66f9cf9f4 100644 --- a/monkestation/code/game/objects/items/guns/SRN.dm +++ b/monkestation/code/game/objects/items/guns/SRN.dm @@ -61,7 +61,7 @@ damage = 10 ricochets_max = 0 //it's a MISSILE -/obj/projectile/bullet/SRN_rocket/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/SRN_rocket/on_hit(atom/target, blocked = 0, pierce_hit) ..() if(ishuman(target)) var/mob/living/carbon/human/M = target diff --git a/monkestation/code/game/objects/items/mothlet_grenade.dm b/monkestation/code/game/objects/items/mothlet_grenade.dm index f642e33d3678..6a51961b8cdc 100644 --- a/monkestation/code/game/objects/items/mothlet_grenade.dm +++ b/monkestation/code/game/objects/items/mothlet_grenade.dm @@ -26,9 +26,9 @@ embedding = list(embed_chance=0, ignore_throwspeed_threshold=TRUE, fall_chance=1) -/obj/projectile/bullet/shrapnel/mothlet/on_hit(owner) +/obj/projectile/bullet/shrapnel/mothlet/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() - if(iscarbon(owner) && prob(50)) - var/mob/living/carbon/carbon_owner = owner + if(iscarbon(target) && prob(50)) + var/mob/living/carbon/carbon_owner = target carbon_owner.unequip_everything() diff --git a/monkestation/code/modules/antagonists/clock_cult/mechas/mecha_effects.dm b/monkestation/code/modules/antagonists/clock_cult/mechas/mecha_effects.dm index f9ec021a6e6a..7e09522241e2 100644 --- a/monkestation/code/modules/antagonists/clock_cult/mechas/mecha_effects.dm +++ b/monkestation/code/modules/antagonists/clock_cult/mechas/mecha_effects.dm @@ -38,7 +38,7 @@ else marked_mob.Paralyze(0.5 SECONDS) marked_mob.Knockdown(3 SECONDS) - marked_mob.apply_damage_type(30, BURN) + marked_mob.apply_damage(30, BURN) marked_mob.visible_message(span_warning("[marked_mob] is hit by a judicial explosion!"), span_warning("You feel the ground beneath you heat up!")) sleep(0.3 SECONDS) diff --git a/monkestation/code/modules/antagonists/clock_cult/mobs/clockwork_marauder.dm b/monkestation/code/modules/antagonists/clock_cult/mobs/clockwork_marauder.dm index 7a4203c2912b..7a570ecf21f9 100644 --- a/monkestation/code/modules/antagonists/clock_cult/mobs/clockwork_marauder.dm +++ b/monkestation/code/modules/antagonists/clock_cult/mobs/clockwork_marauder.dm @@ -119,7 +119,7 @@ GLOBAL_LIST_EMPTY(clockwork_marauders) /datum/ai_controller/basic_controller/clockwork_marauder blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/clockwork_marauder() + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance @@ -137,10 +137,6 @@ GLOBAL_LIST_EMPTY(clockwork_marauders) action_cooldown = 1.2 SECONDS -/datum/targetting_datum/basic/clockwork_marauder - stat_attack = HARD_CRIT - - /obj/item/nullrod/Initialize(mapload) . = ..() AddElement(/datum/element/bane, /mob/living/basic/clockwork_marauder, 1, 15, FALSE) diff --git a/monkestation/code/modules/antagonists/clock_cult/mobs/cogscarab.dm b/monkestation/code/modules/antagonists/clock_cult/mobs/cogscarab.dm index 530238aaab16..d15c714cd5a4 100644 --- a/monkestation/code/modules/antagonists/clock_cult/mobs/cogscarab.dm +++ b/monkestation/code/modules/antagonists/clock_cult/mobs/cogscarab.dm @@ -4,7 +4,7 @@ GLOBAL_LIST_EMPTY(cogscarabs) //====Cogscarab==== -/mob/living/simple_animal/drone/cogscarab +/mob/living/basic/drone/cogscarab name = "Cogscarab" desc = "A mechanical device, filled with twisting cogs and mechanical parts, built to maintain Reebe." icon_state = "drone_clock" @@ -31,7 +31,7 @@ GLOBAL_LIST_EMPTY(cogscarabs) var/stay_on_reebe = TRUE //No you can't go wielding guns like that. -/mob/living/simple_animal/drone/cogscarab/Initialize(mapload) +/mob/living/basic/drone/cogscarab/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_NOGUNS, "cogscarab") GLOB.cogscarabs += src @@ -40,16 +40,16 @@ GLOBAL_LIST_EMPTY(cogscarabs) /datum/actionspeed_modifier/cogscarab multiplicative_slowdown = 0.6 -/mob/living/simple_animal/drone/cogscarab/death(gibbed) +/mob/living/basic/drone/cogscarab/death(gibbed) GLOB.cogscarabs -= src . = ..() -/mob/living/simple_animal/drone/cogscarab/Life(seconds, times_fired) +/mob/living/basic/drone/cogscarab/Life(seconds, times_fired) if(!on_reebe(src) && !GLOB.ratvar_risen && GLOB.abscond_markers && stay_on_reebe) try_servant_warp(src, get_turf(pick(GLOB.abscond_markers))) . = ..() -/mob/living/simple_animal/drone/cogscarab/Destroy() +/mob/living/basic/drone/cogscarab/Destroy() GLOB.cogscarabs -= src return ..() @@ -61,7 +61,7 @@ GLOBAL_LIST_EMPTY(cogscarabs) icon = 'monkestation/icons/obj/clock_cult/clockwork_objects.dmi' icon_state = "cogscarab_shell" mob_name = "cogscarab" - mob_type = /mob/living/simple_animal/drone/cogscarab + mob_type = /mob/living/basic/drone/cogscarab role_ban = ROLE_CLOCK_CULTIST prompt_name = "a cogscarab" you_are_text = "You are a cogscarab!" diff --git a/monkestation/code/modules/antagonists/clock_cult/ratvar.dm b/monkestation/code/modules/antagonists/clock_cult/ratvar.dm index c68e0c0ef4c9..15ef901a7840 100644 --- a/monkestation/code/modules/antagonists/clock_cult/ratvar.dm +++ b/monkestation/code/modules/antagonists/clock_cult/ratvar.dm @@ -109,7 +109,7 @@ GLOBAL_DATUM(cult_ratvar, /obj/ratvar) if(!user.mind) //this should not happen but just to be safe return . = ..() - var/mob/living/simple_animal/drone/created_drone = new /mob/living/simple_animal/drone/cogscarab(get_turf(src)) + var/mob/living/basic/drone/created_drone = new /mob/living/basic/drone/cogscarab(get_turf(src)) created_drone.flags_1 |= (flags_1 & ADMIN_SPAWNED_1) user.mind.transfer_to(created_drone, TRUE) diff --git a/monkestation/code/modules/antagonists/clock_cult/scriptures/_scripture.dm b/monkestation/code/modules/antagonists/clock_cult/scriptures/_scripture.dm index 4265294de515..881d3420c5bf 100644 --- a/monkestation/code/modules/antagonists/clock_cult/scriptures/_scripture.dm +++ b/monkestation/code/modules/antagonists/clock_cult/scriptures/_scripture.dm @@ -402,10 +402,8 @@ GLOBAL_LIST_EMPTY(clock_scriptures_by_type) return ..() -/datum/action/cooldown/spell/pointed/slab/InterceptClickOn(mob/living/caller, params, atom/clicked_atom) - parent_scripture?.click_on(clicked_atom) - - +/datum/action/cooldown/spell/pointed/slab/InterceptClickOn(mob/living/caller, params, atom/target) + parent_scripture?.click_on(target) /// Generate all scriptures in a global assoc of name:ref. Only needs to be done once /proc/generate_clockcult_scriptures() diff --git a/monkestation/code/modules/antagonists/clock_cult/structures/sigil/sigil_submission.dm b/monkestation/code/modules/antagonists/clock_cult/structures/sigil/sigil_submission.dm index 8453567642d6..861d0bb629cc 100644 --- a/monkestation/code/modules/antagonists/clock_cult/structures/sigil/sigil_submission.dm +++ b/monkestation/code/modules/antagonists/clock_cult/structures/sigil/sigil_submission.dm @@ -32,7 +32,7 @@ GLOB.main_clock_cult?.check_member_distribution() if(isdrone(converted_mob) && (GLOB.cogscarabs.len < MAXIMUM_COGSCARABS)) - var/mob/living/simple_animal/drone/cogscarab/cogger = new /mob/living/simple_animal/drone/cogscarab(get_turf(src)) + var/mob/living/basic/drone/cogscarab/cogger = new /mob/living/basic/drone/cogscarab(get_turf(src)) cogger.key = converted_mob.key cogger.mind?.add_antag_datum(/datum/antagonist/clock_cultist) cogger.visible_message("A light envelops \the [converted_mob]! As the light fades you see it has become a cogscarab!", diff --git a/monkestation/code/modules/antagonists/clock_cult/structures/traps/trap.dm b/monkestation/code/modules/antagonists/clock_cult/structures/traps/trap.dm index add2ffa96064..0f8060466485 100644 --- a/monkestation/code/modules/antagonists/clock_cult/structures/traps/trap.dm +++ b/monkestation/code/modules/antagonists/clock_cult/structures/traps/trap.dm @@ -117,7 +117,7 @@ outputs |= input.parent -/// Adds this as an output to the targetted component's `outputs` list +/// Adds this as an output to the targeted component's `outputs` list /datum/component/clockwork_trap/proc/add_output(datum/component/clockwork_trap/output) output.outputs |= parent diff --git a/monkestation/code/modules/antagonists/slasher/abilities/incorporealize.dm b/monkestation/code/modules/antagonists/slasher/abilities/incorporealize.dm index 748772db16ac..450051f08fe8 100644 --- a/monkestation/code/modules/antagonists/slasher/abilities/incorporealize.dm +++ b/monkestation/code/modules/antagonists/slasher/abilities/incorporealize.dm @@ -63,7 +63,7 @@ RegisterSignal(jaunt, COMSIG_MOB_EJECTED_FROM_JAUNT, PROC_REF(on_jaunt_exited)) jaunter.add_traits(list(TRAIT_MAGICALLY_PHASED, TRAIT_RUNECHAT_HIDDEN), REF(src)) jaunter.drop_all_held_items() - jaunter.notransform = TRUE + ADD_TRAIT(jaunter, TRAIT_NO_TRANSFORM, INNATE_TRAIT) // Give them some bloody hands to prevent them from doing things var/obj/item/bloodcrawl/left_hand = new(jaunter) @@ -75,7 +75,7 @@ // Make sure they wont be burning for 20 seconds jaunter.extinguish_mob() - jaunter.notransform = FALSE + REMOVE_TRAIT(jaunter, TRAIT_NO_TRANSFORM, INNATE_TRAIT) slasherdatum.corporeal = FALSE diff --git a/monkestation/code/modules/bloodsuckers/bloodsucker/bloodsucker_guardian.dm b/monkestation/code/modules/bloodsuckers/bloodsucker/bloodsucker_guardian.dm index 299b5a5e780d..97953c5a1d26 100644 --- a/monkestation/code/modules/bloodsuckers/bloodsucker/bloodsucker_guardian.dm +++ b/monkestation/code/modules/bloodsuckers/bloodsucker/bloodsucker_guardian.dm @@ -1,18 +1,17 @@ ///Bloodsuckers spawning a Guardian will get the Bloodsucker one instead. -/obj/item/guardiancreator/spawn_guardian(mob/living/user, mob/dead/candidate) +/obj/item/guardian_creator/spawn_guardian(mob/living/user, mob/dead/candidate) var/list/guardians = user.get_all_linked_holoparasites() - if(length(guardians) && !allowmultiple) + if(length(guardians)) to_chat(user, span_holoparasite("You already have a [mob_name]!")) used = FALSE return if(IS_BLOODSUCKER(user)) - var/mob/living/simple_animal/hostile/guardian/standard/timestop/bloodsucker_guardian = new(user, GUARDIAN_THEME_MAGIC) + var/mob/living/basic/guardian/standard/bloodsucker_guardian = new(user, GUARDIAN_THEME_MAGIC) bloodsucker_guardian.set_summoner(user, different_person = TRUE) bloodsucker_guardian.key = candidate.key user.log_message("has summoned [key_name(bloodsucker_guardian)], a [bloodsucker_guardian.creator_name] holoparasite.", LOG_GAME) bloodsucker_guardian.log_message("was summoned as a [bloodsucker_guardian.creator_name] holoparasite.", LOG_GAME) - to_chat(user, bloodsucker_guardian.magic_fluff_string) to_chat(user, replacetext(success_message, "%GUARDIAN", mob_name)) bloodsucker_guardian.client?.init_verbs() return @@ -23,7 +22,7 @@ /** * The Guardian itself */ -/mob/living/simple_animal/hostile/guardian/standard/timestop +/mob/living/basic/guardian/standard // Like Bloodsuckers do, you will take more damage to Burn and less to Brute damage_coeff = list(BRUTE = 0.5, BURN = 2.5, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) @@ -31,16 +30,9 @@ creator_desc = "Devastating close combat attacks and high damage resistance. Can smash through weak walls and stop time." creator_icon = "standard" - //None of these shouldn't appear in game outside of admin intervention - playstyle_string = span_holoparasite("As a time manipulation type you can stop time and you have a damage multiplier instead of armor as-well as powerful melee attacks capable of smashing through walls.") - magic_fluff_string = span_holoparasite("...And draw... The World, through sheer luck or perhaps destiny, maybe even your own physiology. Manipulator of time, a guardian powerful enough to control THE WORLD!") - tech_fluff_string = span_holoparasite("ERROR... T$M3 M4N!PULA%I0N modules loaded. Holoparasite swarm online.") - carp_fluff_string = span_holoparasite("CARP CARP CARP! You caught one! It's imbued with the power of Carp'Sie herself. Time to rule THE WORLD!.") - miner_fluff_string = span_holoparasite("You encounter... The World, the controller of time and space.") - -/mob/living/simple_animal/hostile/guardian/standard/timestop/Initialize(mapload, theme) +/mob/living/basic/guardian/standard/Initialize(mapload, theme) //Wizard Holoparasite theme, just to be more visibly stronger than regular ones - theme = GUARDIAN_THEME_MAGIC + theme = GLOB.guardian_themes[GUARDIAN_THEME_TECH] . = ..() var/datum/action/cooldown/spell/timestop/guardian/timestop_ability = new() timestop_ability.Grant(src) @@ -56,12 +48,12 @@ /datum/action/cooldown/spell/timestop/guardian/Grant(mob/grant_to) . = ..() - var/mob/living/simple_animal/hostile/guardian/standard/timestop/bloodsucker_guardian = owner + var/mob/living/basic/guardian/standard/bloodsucker_guardian = owner if(bloodsucker_guardian && istype(bloodsucker_guardian) && bloodsucker_guardian.summoner) ADD_TRAIT(bloodsucker_guardian.summoner, TRAIT_TIME_STOP_IMMUNE, REF(src)) /datum/action/cooldown/spell/timestop/guardian/Remove(mob/remove_from) - var/mob/living/simple_animal/hostile/guardian/standard/timestop/bloodsucker_guardian = owner + var/mob/living/basic/guardian/standard/bloodsucker_guardian = owner if(bloodsucker_guardian && istype(bloodsucker_guardian) && bloodsucker_guardian.summoner) REMOVE_TRAIT(bloodsucker_guardian.summoner, TRAIT_TIME_STOP_IMMUNE, REF(src)) return ..() diff --git a/monkestation/code/modules/bloodsuckers/monster_hunters/hunter_weapons.dm b/monkestation/code/modules/bloodsuckers/monster_hunters/hunter_weapons.dm index 29b28aab183d..463cf9d9d9ec 100644 --- a/monkestation/code/modules/bloodsuckers/monster_hunters/hunter_weapons.dm +++ b/monkestation/code/modules/bloodsuckers/monster_hunters/hunter_weapons.dm @@ -276,7 +276,7 @@ damage = 3 ricochets_max = 4 -/obj/projectile/bullet/bloodsilver/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/bloodsilver/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(!iscarbon(target)) return diff --git a/monkestation/code/modules/bloodsuckers/monster_hunters/monsters/monster_controllers/killer_rabbit.dm b/monkestation/code/modules/bloodsuckers/monster_hunters/monsters/monster_controllers/killer_rabbit.dm index 9aac3a6402b9..ba5a2d86937f 100644 --- a/monkestation/code/modules/bloodsuckers/monster_hunters/monsters/monster_controllers/killer_rabbit.dm +++ b/monkestation/code/modules/bloodsuckers/monster_hunters/monsters/monster_controllers/killer_rabbit.dm @@ -3,7 +3,7 @@ */ /datum/ai_controller/basic_controller/killer_rabbit blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance diff --git a/monkestation/code/modules/bloodsuckers/monster_hunters/monsters/monster_controllers/red_rabbit.dm b/monkestation/code/modules/bloodsuckers/monster_hunters/monsters/monster_controllers/red_rabbit.dm index bd4f3d4bfcec..2adbe7403318 100644 --- a/monkestation/code/modules/bloodsuckers/monster_hunters/monsters/monster_controllers/red_rabbit.dm +++ b/monkestation/code/modules/bloodsuckers/monster_hunters/monsters/monster_controllers/red_rabbit.dm @@ -3,7 +3,7 @@ */ /datum/ai_controller/basic_controller/red_rabbit blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance diff --git a/monkestation/code/modules/bloodsuckers/powers/targeted/lunge.dm b/monkestation/code/modules/bloodsuckers/powers/targeted/lunge.dm index f86f9e25d373..d43420807c48 100644 --- a/monkestation/code/modules/bloodsuckers/powers/targeted/lunge.dm +++ b/monkestation/code/modules/bloodsuckers/powers/targeted/lunge.dm @@ -146,7 +146,7 @@ owner.balloon_alert(owner, "you lunge at [target]!") if(target.stat == DEAD) var/obj/item/bodypart/chest = target.get_bodypart(BODY_ZONE_CHEST) - var/datum/wound/slash/moderate/crit_wound = new + var/datum/wound/slash/flesh/moderate/crit_wound = new crit_wound.apply_wound(chest) owner.visible_message( span_warning("[owner] tears into [target]'s chest!"), diff --git a/monkestation/code/modules/bloodsuckers/powers/targeted/mesmerize.dm b/monkestation/code/modules/bloodsuckers/powers/targeted/mesmerize.dm index a2ef6ab31e91..2c3f038bdf85 100644 --- a/monkestation/code/modules/bloodsuckers/powers/targeted/mesmerize.dm +++ b/monkestation/code/modules/bloodsuckers/powers/targeted/mesmerize.dm @@ -123,7 +123,7 @@ mesmerized_target.adjust_silence(power_time) //mesmerized_target.silent += power_time / 10 // Silent isn't based on ticks. mesmerized_target.next_move = world.time + power_time // <--- Use direct change instead. We want an unmodified delay to their next move // mesmerized_target.changeNext_move(power_time) // check click.dm - mesmerized_target.notransform = TRUE // <--- Fuck it. We tried using next_move, but they could STILL resist. We're just doing a hard freeze. + ADD_TRAIT(mesmerized_target, TRAIT_NO_TRANSFORM, BLOODSUCKER_TRAIT)// <--- Fuck it. We tried using next_move, but they could STILL resist. We're just doing a hard freeze. addtimer(CALLBACK(src, PROC_REF(end_mesmerize), user, mesmerized_target), power_time) power_activated_sucessfully() // PAY COST! BEGIN COOLDOWN! @@ -132,7 +132,7 @@ . = ..() /datum/action/cooldown/bloodsucker/targeted/mesmerize/proc/end_mesmerize(mob/living/user, mob/living/target) - target.notransform = FALSE + REMOVE_TRAIT(target, TRAIT_NO_TRANSFORM, BLOODSUCKER_TRAIT) REMOVE_TRAIT(target, TRAIT_MUTE, BLOODSUCKER_TRAIT) // They Woke Up! (Notice if within view) if(istype(user) && target.stat == CONSCIOUS && (target in view(6, get_turf(user)))) diff --git a/monkestation/code/modules/bloodsuckers/powers/targeted/trespass.dm b/monkestation/code/modules/bloodsuckers/powers/targeted/trespass.dm index 243e2b229b44..2671c281a987 100644 --- a/monkestation/code/modules/bloodsuckers/powers/targeted/trespass.dm +++ b/monkestation/code/modules/bloodsuckers/powers/targeted/trespass.dm @@ -19,7 +19,7 @@ . = ..() if(!.) return FALSE - if(user.notransform || !get_turf(user)) + if(HAS_TRAIT(user, TRAIT_NO_TRANSFORM) || !get_turf(user)) return FALSE return TRUE diff --git a/monkestation/code/modules/bloodsuckers/powers/tremere/auspex.dm b/monkestation/code/modules/bloodsuckers/powers/tremere/auspex.dm index 12a49f8890a7..f3c53394d27a 100644 --- a/monkestation/code/modules/bloodsuckers/powers/tremere/auspex.dm +++ b/monkestation/code/modules/bloodsuckers/powers/tremere/auspex.dm @@ -110,7 +110,7 @@ continue if(level_current >= 4) var/obj/item/bodypart/bodypart = pick(living_mob.bodyparts) - bodypart.force_wound_upwards(/datum/wound/slash/critical) + living_mob.cause_wound_of_type_and_severity(WOUND_SLASH, bodypart, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_CRITICAL) living_mob.adjustBruteLoss(15) if(level_current >= 5) living_mob.Knockdown(10 SECONDS, ignore_canstun = TRUE) diff --git a/monkestation/code/modules/bloodsuckers/powers/tremere/dominate.dm b/monkestation/code/modules/bloodsuckers/powers/tremere/dominate.dm index 516ea3fa5da6..f51d8e6d21b9 100644 --- a/monkestation/code/modules/bloodsuckers/powers/tremere/dominate.dm +++ b/monkestation/code/modules/bloodsuckers/powers/tremere/dominate.dm @@ -140,7 +140,7 @@ target.become_blind(BLOODSUCKER_TRAIT) mesmerized.Immobilize(power_time) mesmerized.next_move = world.time + power_time - mesmerized.notransform = TRUE + ADD_TRAIT(mesmerized, TRAIT_NO_TRANSFORM, BLOODSUCKER_TRAIT) addtimer(CALLBACK(src, PROC_REF(end_mesmerize), user, target), power_time) if(issilicon(target)) var/mob/living/silicon/mesmerized = target @@ -148,7 +148,7 @@ owner.balloon_alert(owner, "temporarily shut [mesmerized] down.") /datum/action/cooldown/bloodsucker/targeted/tremere/proc/end_mesmerize(mob/living/user, mob/living/target) - target.notransform = FALSE + REMOVE_TRAIT(target, TRAIT_NO_TRANSFORM, BLOODSUCKER_TRAIT) target.cure_blind(BLOODSUCKER_TRAIT) REMOVE_TRAIT(target, TRAIT_MUTE, BLOODSUCKER_TRAIT) if(istype(user) && target.stat == CONSCIOUS && (target in view(6, get_turf(user)))) diff --git a/monkestation/code/modules/bloodsuckers/powers/tremere/thaumaturgey.dm b/monkestation/code/modules/bloodsuckers/powers/tremere/thaumaturgey.dm index c589922895b5..a00621387f97 100644 --- a/monkestation/code/modules/bloodsuckers/powers/tremere/thaumaturgey.dm +++ b/monkestation/code/modules/bloodsuckers/powers/tremere/thaumaturgey.dm @@ -132,7 +132,7 @@ damage = 20 var/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/bloodsucker_power -/obj/projectile/magic/arcane_barrage/bloodsucker/on_hit(target) +/obj/projectile/magic/arcane_barrage/bloodsucker/on_hit(target, blocked, pierce_hit) if(istype(target, /obj/structure/closet) && bloodsucker_power.level_current >= 3) var/obj/structure/closet/hit_closet = target if(hit_closet) diff --git a/monkestation/code/modules/ghost_players/job_helpers/injured_spawner.dm b/monkestation/code/modules/ghost_players/job_helpers/injured_spawner.dm index 7eaf15b53924..41e938a6f523 100644 --- a/monkestation/code/modules/ghost_players/job_helpers/injured_spawner.dm +++ b/monkestation/code/modules/ghost_players/job_helpers/injured_spawner.dm @@ -61,20 +61,23 @@ runs-- tested_part = victim.get_bodypart(pick(zones)) i = 1 - for(iter_test_wound_list in list(list(/datum/wound/blunt/moderate, /datum/wound/blunt/severe, /datum/wound/blunt/critical),\ - list(/datum/wound/slash/moderate, /datum/wound/slash/severe, /datum/wound/slash/critical),\ - list(/datum/wound/pierce/moderate, /datum/wound/pierce/severe, /datum/wound/pierce/critical),\ - list(/datum/wound/burn/moderate, /datum/wound/burn/severe, /datum/wound/burn/critical))) + for(iter_test_wound_list in list(list(/datum/wound/blunt/bone/moderate, /datum/wound/blunt/bone/severe, /datum/wound/blunt/bone/critical),\ + list(/datum/wound/slash/flesh/moderate, /datum/wound/slash/flesh/severe, /datum/wound/slash/flesh/critical),\ + list(/datum/wound/pierce/bleed/moderate, /datum/wound/pierce/bleed/severe, /datum/wound/pierce/bleed/critical),\ + list(/datum/wound/burn/flesh/moderate, /datum/wound/burn/flesh/severe, /datum/wound/burn/flesh/critical))) if(prob(20)) continue - var/datum/wound/iter_test_wound - for(iter_test_wound as anything in iter_test_wound_list) - var/threshold = initial(iter_test_wound.threshold_minimum) + rand(40, 60) // just enough to guarantee the next tier of wound, given the existing wound threshold penalty - if(dam_types[i] == BRUTE) - tested_part.receive_damage(WOUND_MINIMUM_DAMAGE + 15, 0, wound_bonus = threshold, sharpness=sharps[i]) - else if(dam_types[i] == BURN) - tested_part.receive_damage(0, WOUND_MINIMUM_DAMAGE + 15, wound_bonus = threshold, sharpness=sharps[i]) + var/datum/wound/iter_test_wound + var/datum/wound_pregen_data/iter_pregen_data = GLOB.all_wound_pregen_data[iter_test_wound] + var/threshold_penalty = 0 + + for(iter_test_wound in iter_test_wound_list) + var/threshold = iter_pregen_data.threshold_minimum - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty + if(dam_types[i] == BRUTE) + tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i]) + else if(dam_types[i] == BURN) + tested_part.receive_damage(0, WOUND_MINIMUM_DAMAGE, wound_bonus = threshold, sharpness=sharps[i]) i++ /obj/structure/injured_spawner/proc/rot_organs(mob/living/carbon/human/victim) diff --git a/monkestation/code/modules/liquids/ocean_generator.dm b/monkestation/code/modules/liquids/ocean_generator.dm index 609ff41d2523..46db1b9f09c1 100644 --- a/monkestation/code/modules/liquids/ocean_generator.dm +++ b/monkestation/code/modules/liquids/ocean_generator.dm @@ -96,7 +96,7 @@ /obj/effect/spawner/random/lavaland_mob/watcher = 40, /mob/living/basic/mining/bileworm = 20, /mob/living/basic/mining/lobstrosity/lava = 20, - /mob/living/simple_animal/hostile/asteroid/brimdemon = 20, + /mob/living/basic/mining/brimdemon = 20, /mob/living/basic/mining/goldgrub = 10, /obj/structure/spawner/lavaland/ocean = 2, /obj/structure/spawner/lavaland/ocean/goliath = 3, diff --git a/monkestation/code/modules/mob/living/basic/animatronic.dm b/monkestation/code/modules/mob/living/basic/animatronic.dm index 25f72a165e24..daf153b2b5e2 100644 --- a/monkestation/code/modules/mob/living/basic/animatronic.dm +++ b/monkestation/code/modules/mob/living/basic/animatronic.dm @@ -116,7 +116,7 @@ carbon_pulled.adjustOrganLoss(ORGAN_SLOT_BRAIN, 150, 150) //large amount of brain damage but it will never give you brain death carbon_pulled.apply_damage(150, BRUTE, HEAD) else - pulled.apply_damage_type(150, BRUTE) + pulled.apply_damage(150, BRUTE) if(pulled.client && !killed_list[pulled]) killed_list[pulled] = TRUE @@ -130,7 +130,7 @@ if(blood_hunger) . += "It's glaring at you." -/mob/living/basic/monkey_animatronic/melee_attack(atom/target, list/modifiers) +/mob/living/basic/monkey_animatronic/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE) . = ..() var/mob/living/living_target = target if(!istype(living_target)) @@ -162,7 +162,7 @@ /datum/ai_controller/basic_controller/animatronic blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_LOW_PRIORITY_HUNTING_TARGET = null, ) diff --git a/monkestation/code/modules/mob/living/carbon/human/species_type/arachnid.dm b/monkestation/code/modules/mob/living/carbon/human/species_type/arachnid.dm index 2c3989352131..d6860598873e 100644 --- a/monkestation/code/modules/mob/living/carbon/human/species_type/arachnid.dm +++ b/monkestation/code/modules/mob/living/carbon/human/species_type/arachnid.dm @@ -39,10 +39,19 @@ return TRUE return ..() -/datum/species/arachnid/check_species_weakness(obj/item/weapon, mob/living/attacker) - if(istype(weapon, /obj/item/melee/flyswatter)) - return 30 //flyswatters deal 30x damage to arachnids - return 1 +/datum/species/arachnid/on_species_gain(mob/living/carbon/human/human_who_gained_species, datum/species/old_species, pref_load) + . = ..() + RegisterSignal(human_who_gained_species, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS, PROC_REF(damage_weakness)) + +/datum/species/arachnid/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load) + . = ..() + UnregisterSignal(C, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS) + +/datum/species/arachnid/proc/damage_weakness(datum/source, list/damage_mods, damage_amount, damagetype, def_zone, sharpness, attack_direction, obj/item/attacking_item) + SIGNAL_HANDLER + + if(istype(attacking_item, /obj/item/melee/flyswatter)) + damage_mods += 30 // Yes, a 30x damage modifier /datum/species/arachnid/get_scream_sound(mob/living/carbon/human/human) return 'monkestation/sound/voice/screams/arachnid/arachnid_scream.ogg' diff --git a/monkestation/code/modules/mob/living/simple_animal/pets/honk_platinum.dm b/monkestation/code/modules/mob/living/simple_animal/pets/honk_platinum.dm index b1a2ee5c9770..7d6f144d642d 100644 --- a/monkestation/code/modules/mob/living/simple_animal/pets/honk_platinum.dm +++ b/monkestation/code/modules/mob/living/simple_animal/pets/honk_platinum.dm @@ -1,5 +1,5 @@ -/mob/living/simple_animal/parrot/honk_platinum +/mob/living/basic/parrot/honk_platinum name = "Honk Platinum" icon = 'monkestation/icons/mob/pets.dmi' desc = "The goodest birb." @@ -7,7 +7,6 @@ icon_living = "honk_fly" icon_sit = "honk_sit" icon_dead = "honk_dead" - speak = list("FUCK?","HONK!","Law?","Crime.") head_icon = 'monkestation/icons/mob/pets_held.dmi' held_lh = 'monkestation/icons/mob/pets_held_lh.dmi'//icons for holding mobs held_rh = 'monkestation/icons/mob/pets_held_rh.dmi' diff --git a/monkestation/code/modules/ocean_content/fluff/ocean_tendril.dm b/monkestation/code/modules/ocean_content/fluff/ocean_tendril.dm index 72887d5bff0f..72539a69d4df 100644 --- a/monkestation/code/modules/ocean_content/fluff/ocean_tendril.dm +++ b/monkestation/code/modules/ocean_content/fluff/ocean_tendril.dm @@ -32,7 +32,7 @@ mob_types = list(/mob/living/basic/mining/goliath) /obj/structure/spawner/lavaland/ocean/legion - mob_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord/legion/tendril) + mob_types = list(/mob/living/basic/mining/legion) /obj/structure/spawner/lavaland/ocean/fish max_mobs = 6 diff --git a/monkestation/code/modules/possession/basic_additions.dm b/monkestation/code/modules/possession/basic_additions.dm index 50365f6ef8d7..9fa5040a6d43 100644 --- a/monkestation/code/modules/possession/basic_additions.dm +++ b/monkestation/code/modules/possession/basic_additions.dm @@ -22,6 +22,10 @@ /// the shifted x offset of the head var/list/head_x_shift +/mob/living/basic/proc/apply_overlay(cache_index) + if((. = possession_overlays[cache_index])) + add_overlay(.) + /mob/living/basic/proc/create_overlay_index() var/list/overlays[1] possession_overlays = overlays @@ -39,6 +43,7 @@ update_held_items() /mob/living/basic/update_held_items() + . = ..() remove_overlay(1) var/list/hands_overlays = list() @@ -84,10 +89,6 @@ possession_overlays[1] = hands_overlays apply_overlay(1) -/mob/living/basic/proc/apply_overlay(cache_index) - if((. = possession_overlays[cache_index])) - add_overlay(.) - /mob/living/basic/proc/remove_overlay(cache_index) var/I = possession_overlays[cache_index] if(I) diff --git a/monkestation/code/modules/possession/item_overrides.dm b/monkestation/code/modules/possession/item_overrides.dm index 38bee5150972..06c8dc1ec273 100644 --- a/monkestation/code/modules/possession/item_overrides.dm +++ b/monkestation/code/modules/possession/item_overrides.dm @@ -13,10 +13,7 @@ return ..() return else if(istype(user, /mob/living/simple_animal)) - if(!user.dextrous) - if (obj_flags & CAN_BE_HIT) - return ..() - return + return . = TRUE diff --git a/monkestation/code/modules/ranching/chickens/_chicken.dm b/monkestation/code/modules/ranching/chickens/_chicken.dm index 816eb788ddb6..de71f07d561d 100644 --- a/monkestation/code/modules/ranching/chickens/_chicken.dm +++ b/monkestation/code/modules/ranching/chickens/_chicken.dm @@ -4,7 +4,7 @@ /datum/ai_controller/basic_controller/chick blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FIND_MOM_TYPES = list(/mob/living/basic/chicken), BB_IGNORE_MOM_TYPES = list(/mob/living/basic/chick), ) diff --git a/monkestation/code/modules/ranching/chickens/_chicken_defines.dm b/monkestation/code/modules/ranching/chickens/_chicken_defines.dm index 2a5ff880400f..77b63a24b678 100644 --- a/monkestation/code/modules/ranching/chickens/_chicken_defines.dm +++ b/monkestation/code/modules/ranching/chickens/_chicken_defines.dm @@ -107,8 +107,8 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targetting/attack/chicken, - /datum/pet_command/point_targetting/fetch, + /datum/pet_command/point_targeting/attack/chicken, + /datum/pet_command/point_targeting/fetch, /datum/pet_command/play_dead, ) ///how much extra fertile we are @@ -153,5 +153,5 @@ melee_cooldown_time = 1 // dumb var/datum/ai_behavior/targeted_mob_ability/min_range/chicken/what_range = /datum/ai_behavior/targeted_mob_ability/min_range/chicken/melee -/datum/pet_command/point_targetting/attack/chicken +/datum/pet_command/point_targeting/attack/chicken attack_behaviour = /datum/ai_behavior/basic_melee_attack/chicken diff --git a/monkestation/code/modules/ranching/chickens/ai/chicken_controller.dm b/monkestation/code/modules/ranching/chickens/ai/chicken_controller.dm index a3563adca331..573c858509ce 100644 --- a/monkestation/code/modules/ranching/chickens/ai/chicken_controller.dm +++ b/monkestation/code/modules/ranching/chickens/ai/chicken_controller.dm @@ -15,8 +15,8 @@ BB_CHICKEN_NESTING_BOX = null, BB_CHICKEN_FEED = null, - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETTING_DATUM = /datum/targeting_strategy/basic/not_friends, ) var/static/list/loc_connections = list( COMSIG_ATOM_ENTERED = PROC_REF(on_entered), @@ -49,8 +49,8 @@ BB_CHICKEN_NESTING_BOX = null, BB_CHICKEN_FEED = null, - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETTING_DATUM = /datum/targeting_strategy/basic/not_friends, ) planning_subtrees = list( @@ -76,8 +76,8 @@ BB_CHICKEN_NESTING_BOX = null, BB_CHICKEN_FEED = null, - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, - BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(), + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETTING_DATUM = /datum/targeting_strategy/basic/not_friends, ) planning_subtrees = list( diff --git a/monkestation/code/modules/ranching/chickens/tier1/clown.dm b/monkestation/code/modules/ranching/chickens/tier1/clown.dm index 8b1043ce477b..835ce1e95f98 100644 --- a/monkestation/code/modules/ranching/chickens/tier1/clown.dm +++ b/monkestation/code/modules/ranching/chickens/tier1/clown.dm @@ -37,7 +37,7 @@ // generic water balloon impact handler, need to move to new file if i make other water balloons -/obj/item/reagent_containers/water_balloon/throw_at(atom/target, range, speed, mob/thrower, spin, diagonals_first, datum/callback/callback, force, quickstart) +/obj/item/reagent_containers/water_balloon/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) . = ..() visible_message("The [src.name] bursts upon impact with \the [target.name]!") qdel(src) diff --git a/monkestation/code/modules/ranching/chickens/tier3/cockatrice.dm b/monkestation/code/modules/ranching/chickens/tier3/cockatrice.dm index 2de26da05192..d6a5ed5c3855 100644 --- a/monkestation/code/modules/ranching/chickens/tier3/cockatrice.dm +++ b/monkestation/code/modules/ranching/chickens/tier3/cockatrice.dm @@ -41,7 +41,8 @@ damage = 5 damage_type = BURN -/obj/projectile/magic/venomous_spit/on_hit(atom/target, blocked) +/obj/projectile/magic/venomous_spit/on_hit(atom/target, blocked, pierce_hit) + . = ..() if(iscarbon(target)) var/mob/living/carbon/user = target user.petrify(10) diff --git a/monkestation/code/modules/ranching/items.dm b/monkestation/code/modules/ranching/items.dm index 220dc5f6c3fa..69d78dbbaaba 100644 --- a/monkestation/code/modules/ranching/items.dm +++ b/monkestation/code/modules/ranching/items.dm @@ -236,13 +236,13 @@ /obj/item/chicken_feed/proc/try_place(atom/target) if(!isopenturf(target)) return FALSE - var/turf/open/targetted_turf = get_turf(target) + var/turf/open/targeted_turf = get_turf(target) var/list/compiled_reagents = list() for(var/datum/reagent/listed_reagent in reagents.reagent_list) compiled_reagents += new listed_reagent.type compiled_reagents[listed_reagent] = listed_reagent.volume - new /obj/effect/chicken_feed(targetted_turf, held_foods, compiled_reagents, mix_color_from_reagents(reagents.reagent_list), name) + new /obj/effect/chicken_feed(targeted_turf, held_foods, compiled_reagents, mix_color_from_reagents(reagents.reagent_list), name) placements_left-- if(placements_left <= 0) diff --git a/monkestation/code/modules/security/code/weapons/lawbringer.dm b/monkestation/code/modules/security/code/weapons/lawbringer.dm index b72dc41cbf59..8111290bc98f 100644 --- a/monkestation/code/modules/security/code/weapons/lawbringer.dm +++ b/monkestation/code/modules/security/code/weapons/lawbringer.dm @@ -412,7 +412,7 @@ damage = 5 damage_type = BURN -/obj/projectile/lawbringer/hotshot/on_hit(atom/target, blocked = FALSE) +/obj/projectile/lawbringer/hotshot/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) var/mob/living/carbon/M = target @@ -437,7 +437,8 @@ damage_type = BRUTE can_hit_turfs = TRUE -/obj/projectile/lawbringer/smokeshot/on_hit(atom/target, blocked = FALSE) +/obj/projectile/lawbringer/smokeshot/on_hit(atom/target, blocked = 0, pierce_hit) + . = ..() var/datum/effect_system/fluid_spread/smoke/smoke = new smoke.set_up(3, holder = src, location = get_turf(target)) smoke.start() @@ -466,7 +467,8 @@ wound_bonus = -5 var/anti_material_damage = 75 -/obj/projectile/lawbringer/bigshot/on_hit(atom/target, blocked = FALSE) +/obj/projectile/lawbringer/bigshot/on_hit(atom/target, blocked = 0, pierce_hit) + . = ..() if(ismecha(target)) var/obj/vehicle/sealed/mecha/M = target M.take_damage(anti_material_damage) @@ -506,7 +508,7 @@ icon_state = "banana" weak_against_armour = TRUE -/obj/projectile/lawbringer/clownshot/on_hit(mob/living/target, blocked = FALSE) +/obj/projectile/lawbringer/clownshot/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(ishuman(target)) var/mob/living/carbon/human/M = target @@ -518,7 +520,7 @@ M.visible_message(span_warning("[M] is is sent rocketing off their shoes!")) playsound(src, 'sound/items/airhorn.ogg', 100, TRUE, -1) var/atom/throw_target = get_edge_target_turf(target, angle2dir(Angle)) - target.throw_at(throw_target, 200, 8) + M.throw_at(throw_target, 200, 8) /** * lawbringer pulse mode: @@ -538,11 +540,12 @@ damage_type = BRUTE range = 5 -/obj/projectile/lawbringer/pulse/on_hit(mob/living/target, blocked = FALSE) +/obj/projectile/lawbringer/pulse/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) + var/mob/living/new_target = target var/atom/throw_target = get_edge_target_turf(target, angle2dir(Angle)) - target.throw_at(throw_target, 4, 1) + new_target.throw_at(throw_target, 4, 1) /** * lawbringer tideshot mode: @@ -572,10 +575,12 @@ light_power = 1 light_color = LIGHT_COLOR_HALOGEN -/obj/projectile/lawbringer/tideshot/on_hit(mob/living/target, blocked = FALSE) +/obj/projectile/lawbringer/tideshot/on_hit(atom/target, blocked = 0, pierce_hit) + . = ..() if(ishuman(target)) - if(target.mind) - if(is_assistant_job(target.mind.assigned_role)) + var/mob/living/carbon/human/new_target = target + if(new_target.mind) + if(is_assistant_job(new_target.mind.assigned_role)) var/mob/living/carbon/C = target C.add_mood_event("tased", /datum/mood_event/tased) to_chat(target, span_warning("As the beam hits you, body seems to crumple under its uselessness.")) diff --git a/monkestation/code/modules/spells/spell_types/aoe_spell/mind_swap.dm b/monkestation/code/modules/spells/spell_types/aoe_spell/mind_swap.dm index 777f196420fc..335a2e99b29c 100644 --- a/monkestation/code/modules/spells/spell_types/aoe_spell/mind_swap.dm +++ b/monkestation/code/modules/spells/spell_types/aoe_spell/mind_swap.dm @@ -27,7 +27,7 @@ /mob/living/brain, /mob/living/silicon/pai, /mob/living/simple_animal/hostile/megafauna, - /mob/living/simple_animal/hostile/guardian, + /mob/living/basic/guardian, )) //has the spell made a fake wizard yet var/made_false_wizard = FALSE diff --git a/monkestation/code/modules/storytellers/converted_events/solo/clown_operative.dm b/monkestation/code/modules/storytellers/converted_events/solo/clown_operative.dm index 8a2272bdf791..488c6adc5222 100644 --- a/monkestation/code/modules/storytellers/converted_events/solo/clown_operative.dm +++ b/monkestation/code/modules/storytellers/converted_events/solo/clown_operative.dm @@ -73,6 +73,8 @@ var/datum/antagonist/nukeop/leader/leader_antag_datum = new() nuke_team = leader_antag_datum.nuke_team most_experienced.add_antag_datum(leader_antag_datum) + var/mob/living/carbon/human/leader = most_experienced.current + leader.equip_species_outfit(/datum/outfit/syndicate/clownop/leader) if(antag_mind == most_experienced) return diff --git a/monkestation/code/modules/storytellers/converted_events/solo/nuclear_operative.dm b/monkestation/code/modules/storytellers/converted_events/solo/nuclear_operative.dm index 240c97793364..8a5df4ff4942 100644 --- a/monkestation/code/modules/storytellers/converted_events/solo/nuclear_operative.dm +++ b/monkestation/code/modules/storytellers/converted_events/solo/nuclear_operative.dm @@ -64,6 +64,8 @@ var/datum/antagonist/nukeop/leader/leader_antag_datum = new() nuke_team = leader_antag_datum.nuke_team most_experienced.add_antag_datum(leader_antag_datum) + var/mob/living/carbon/human/leader = most_experienced.current + leader.equip_species_outfit(/datum/outfit/syndicate/leader) if(antag_mind == most_experienced) return diff --git a/monkestation/code/modules/surgery/bodyparts/clockwork_bodyparts.dm b/monkestation/code/modules/surgery/bodyparts/clockwork_bodyparts.dm index 9e54b9375b24..3c48e54f763f 100644 --- a/monkestation/code/modules/surgery/bodyparts/clockwork_bodyparts.dm +++ b/monkestation/code/modules/surgery/bodyparts/clockwork_bodyparts.dm @@ -11,8 +11,8 @@ bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC change_exempt_flags = BP_BLOCK_CHANGE_SPECIES dmg_overlay_type = "robotic" - brute_reduction = 3 - burn_reduction = 2 + brute_modifier = 0.9 + burn_modifier = 0.95 damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) disabling_threshold_percentage = 1 @@ -29,8 +29,8 @@ bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC change_exempt_flags = BP_BLOCK_CHANGE_SPECIES dmg_overlay_type = "robotic" - brute_reduction = 3 - burn_reduction = 2 + brute_modifier = 0.9 + burn_modifier = 0.95 damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) disabling_threshold_percentage = 1 @@ -47,8 +47,8 @@ bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC change_exempt_flags = BP_BLOCK_CHANGE_SPECIES dmg_overlay_type = "robotic" - brute_reduction = 3 - burn_reduction = 2 + brute_modifier = 0.9 + burn_modifier = 0.95 damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) disabling_threshold_percentage = 1 @@ -65,8 +65,8 @@ bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC change_exempt_flags = BP_BLOCK_CHANGE_SPECIES dmg_overlay_type = "robotic" - brute_reduction = 3 - burn_reduction = 2 + brute_modifier = 0.9 + burn_modifier = 0.95 damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) disabling_threshold_percentage = 1 @@ -84,8 +84,8 @@ bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC change_exempt_flags = BP_BLOCK_CHANGE_SPECIES dmg_overlay_type = "robotic" - brute_reduction = 3 - burn_reduction = 2 + brute_modifier = 0.9 + burn_modifier = 0.95 damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) disabling_threshold_percentage = 1 @@ -103,7 +103,7 @@ bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC change_exempt_flags = BP_BLOCK_CHANGE_SPECIES dmg_overlay_type = "robotic" - brute_reduction = 3 - burn_reduction = 2 + brute_modifier = 0.9 + burn_modifier = 0.95 damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) disabling_threshold_percentage = 1 diff --git a/sound/attributions.txt b/sound/attributions.txt index b02d2fea2ab0..178082994592 100644 --- a/sound/attributions.txt +++ b/sound/attributions.txt @@ -88,3 +88,8 @@ https://freesound.org/people/squareal/sounds/237375/ statue_horror_sting.ogg is from Kevin MacLeod: Right Behind You (CC BY 3.0) https://www.youtube.com/watch?v=dRdpJq6nmWw +ding_short.ogg is from Natty23 (CC 4) +https://freesound.org/people/Natty23/sounds/411747/ + +jingle.ogg is from Zapsplat (https://www.zapsplat.com/license-type/standard-license/) +https://www.zapsplat.com/sound-effect-category/sleigh-bells/ diff --git a/sound/creatures/attribution.txt b/sound/creatures/attribution.txt new file mode 100644 index 000000000000..06d8361868cb --- /dev/null +++ b/sound/creatures/attribution.txt @@ -0,0 +1,14 @@ +cow.ogg sound adapted from Benboncan on Freesound +https://freesound.org/people/Benboncan/sounds/58277/ + +pig1.ogg and pig2.ogg adapted from Jofae on Freesound +https://freesound.org/people/Jofae/sounds/352698/ + +sheep1, sheep2, and sheep3.ogg adapted from milkotz on Freesound +https://freesound.org/people/milkotz/sounds/618865/ + +snake_hissing1.ogg adapted from schreibsel on Freesound (CC 0) +https://freesound.org/people/schreibsel/sounds/540162/ + +snake_hissing2.ogg adapted from xoiziox on Freesound (CC 0) +https://freesound.org/people/xoiziox/sounds/553374/ diff --git a/sound/creatures/cow.ogg b/sound/creatures/cow.ogg new file mode 100644 index 000000000000..c3143c8dd442 Binary files /dev/null and b/sound/creatures/cow.ogg differ diff --git a/sound/creatures/pig1.ogg b/sound/creatures/pig1.ogg new file mode 100644 index 000000000000..470be7a86d33 Binary files /dev/null and b/sound/creatures/pig1.ogg differ diff --git a/sound/creatures/pig2.ogg b/sound/creatures/pig2.ogg new file mode 100644 index 000000000000..597a13464fb5 Binary files /dev/null and b/sound/creatures/pig2.ogg differ diff --git a/sound/creatures/sheep1.ogg b/sound/creatures/sheep1.ogg new file mode 100644 index 000000000000..33c1041086e5 Binary files /dev/null and b/sound/creatures/sheep1.ogg differ diff --git a/sound/creatures/sheep2.ogg b/sound/creatures/sheep2.ogg new file mode 100644 index 000000000000..eed7d6aadbd7 Binary files /dev/null and b/sound/creatures/sheep2.ogg differ diff --git a/sound/creatures/sheep3.ogg b/sound/creatures/sheep3.ogg new file mode 100644 index 000000000000..7a596e088dae Binary files /dev/null and b/sound/creatures/sheep3.ogg differ diff --git a/sound/creatures/snake_hissing1.ogg b/sound/creatures/snake_hissing1.ogg new file mode 100644 index 000000000000..52a37d764c42 Binary files /dev/null and b/sound/creatures/snake_hissing1.ogg differ diff --git a/sound/creatures/snake_hissing2.ogg b/sound/creatures/snake_hissing2.ogg new file mode 100644 index 000000000000..bd11b7fb5f0f Binary files /dev/null and b/sound/creatures/snake_hissing2.ogg differ diff --git a/strings/names/guardian_descriptions.txt b/strings/names/guardian_descriptions.txt new file mode 100644 index 000000000000..678ec61fef63 --- /dev/null +++ b/strings/names/guardian_descriptions.txt @@ -0,0 +1,24 @@ +Black +Blazing +Bloody +Blue +Bronze +Dawn +Dusk +Gold +Green +Grey +Iron +Midnight +Orange +Pink +Plastitanium +Purple +Red +Shimmering +Shining +Silver +Sparkling +Steel +White +Yellow diff --git a/strings/names/guardian_gamepieces.txt b/strings/names/guardian_gamepieces.txt new file mode 100644 index 000000000000..10a99cf38fb4 --- /dev/null +++ b/strings/names/guardian_gamepieces.txt @@ -0,0 +1,13 @@ +Ace +Bishop +Club +Diamond +Heart +Jack +Joker +King +Knight +Pawn +Queen +Rook +Spade diff --git a/strings/names/guardian_tarot.txt b/strings/names/guardian_tarot.txt new file mode 100644 index 000000000000..5772c90d7ae8 --- /dev/null +++ b/strings/names/guardian_tarot.txt @@ -0,0 +1,23 @@ +Chariot +Death +Devil +Emperor +Empress +Fool +Fortune +Hangman +Hermit +Hierophant +Judgement +Justice +Lover +Magician +Moon +Priestess +Star +Strength +Sun +Temperance +Tower +Wheel +World diff --git a/strings/sillytips.txt b/strings/sillytips.txt index 41094d5b215f..9cfd166a364d 100644 --- a/strings/sillytips.txt +++ b/strings/sillytips.txt @@ -36,3 +36,4 @@ To defeat the slaughter demon, shoot at it until it dies. When a round ends nearly everything about it is lost forever, leave your salt behind with it. You can win a pulse rifle from the arcade machine. Honest. Your sprite represents your hitbox, so that afro makes you easier to kill. The sacrifices we make for style. +Gorillas can be killed by land mines placed along forest paths. diff --git a/strings/wounds/bone_scar_desc.json b/strings/wounds/bone_scar_desc.json index b1eb84bb8b79..2a89f0220021 100644 --- a/strings/wounds/bone_scar_desc.json +++ b/strings/wounds/bone_scar_desc.json @@ -1,6 +1,11 @@ { "generic": ["general disfigurement"], + "dislocate": [ + "the bone equivalent of a faded bruise", + "a series of tiny chip marks" + ], + "bluntmoderate": [ "the bone equivalent of a faded bruise", "a series of tiny chip marks" diff --git a/strings/wounds/flesh_scar_desc.json b/strings/wounds/flesh_scar_desc.json index d8c253873cc3..0fd78bec8e4f 100644 --- a/strings/wounds/flesh_scar_desc.json +++ b/strings/wounds/flesh_scar_desc.json @@ -1,6 +1,12 @@ { "generic": ["general disfigurement"], + "dislocate": [ + "light discoloring", + "a slight blue tint", + "a slightly deadened tint" + ], + "bluntmoderate": [ "light discoloring", "a slight blue tint", diff --git a/tgstation.dme b/tgstation.dme index d3bc1729f965..c407ffd0b9a9 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -79,6 +79,7 @@ #include "code\__DEFINES\construction.dm" #include "code\__DEFINES\cooldowns.dm" #include "code\__DEFINES\crafting.dm" +#include "code\__DEFINES\crushing.dm" #include "code\__DEFINES\ctf.dm" #include "code\__DEFINES\cult.dm" #include "code\__DEFINES\database.dm" @@ -152,6 +153,7 @@ #include "code\__DEFINES\mecha.dm" #include "code\__DEFINES\mechcomp.dm" #include "code\__DEFINES\medical.dm" +#include "code\__DEFINES\megafauna.dm" #include "code\__DEFINES\melee.dm" #include "code\__DEFINES\memory_defines.dm" #include "code\__DEFINES\mergers.dm" @@ -161,6 +163,7 @@ #include "code\__DEFINES\mod.dm" #include "code\__DEFINES\modular_computer.dm" #include "code\__DEFINES\monkeys.dm" +#include "code\__DEFINES\mood.dm" #include "code\__DEFINES\move_force.dm" #include "code\__DEFINES\movement.dm" #include "code\__DEFINES\movespeed_modification.dm" @@ -243,6 +246,7 @@ #include "code\__DEFINES\time.dm" #include "code\__DEFINES\tools.dm" #include "code\__DEFINES\toys.dm" +#include "code\__DEFINES\trader.dm" #include "code\__DEFINES\traits.dm" #include "code\__DEFINES\tram.dm" #include "code\__DEFINES\turbine_defines.dm" @@ -271,6 +275,7 @@ #include "code\__DEFINES\ai\pets.dm" #include "code\__DEFINES\ai\simplemob.dm" #include "code\__DEFINES\ai\tourist.dm" +#include "code\__DEFINES\ai\trader.dm" #include "code\__DEFINES\ai\vending.dm" #include "code\__DEFINES\ai\ventcrawling.dm" #include "code\__DEFINES\atmospherics\atmos_core.dm" @@ -291,6 +296,7 @@ #include "code\__DEFINES\dcs\signals\signals_assembly.dm" #include "code\__DEFINES\dcs\signals\signals_beam.dm" #include "code\__DEFINES\dcs\signals\signals_bitrunning.dm" +#include "code\__DEFINES\dcs\signals\signals_blob.dm" #include "code\__DEFINES\dcs\signals\signals_bot.dm" #include "code\__DEFINES\dcs\signals\signals_changeling.dm" #include "code\__DEFINES\dcs\signals\signals_circuit.dm" @@ -315,6 +321,7 @@ #include "code\__DEFINES\dcs\signals\signals_key.dm" #include "code\__DEFINES\dcs\signals\signals_ladder.dm" #include "code\__DEFINES\dcs\signals\signals_lazy_templates.dm" +#include "code\__DEFINES\dcs\signals\signals_leash.dm" #include "code\__DEFINES\dcs\signals\signals_lift.dm" #include "code\__DEFINES\dcs\signals\signals_light_eater.dm" #include "code\__DEFINES\dcs\signals\signals_medical.dm" @@ -363,6 +370,7 @@ #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_arcade.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_basic.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_carbon.dm" +#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_guardian.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_living.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_main.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_silicon.dm" @@ -650,7 +658,6 @@ #include "code\controllers\subsystem\pai.dm" #include "code\controllers\subsystem\parallax.dm" #include "code\controllers\subsystem\pathfinder.dm" -#include "code\controllers\subsystem\persistence.dm" #include "code\controllers\subsystem\persistent_paintings.dm" #include "code\controllers\subsystem\ping.dm" #include "code\controllers\subsystem\points_of_interest.dm" @@ -693,6 +700,15 @@ #include "code\controllers\subsystem\movement\movement.dm" #include "code\controllers\subsystem\movement\movement_types.dm" #include "code\controllers\subsystem\movement\spacedrift.dm" +#include "code\controllers\subsystem\persistence\_persistence.dm" +#include "code\controllers\subsystem\persistence\counter_delamination.dm" +#include "code\controllers\subsystem\persistence\custom_outfits.dm" +#include "code\controllers\subsystem\persistence\engravings.dm" +#include "code\controllers\subsystem\persistence\photo_albums.dm" +#include "code\controllers\subsystem\persistence\recipes.dm" +#include "code\controllers\subsystem\persistence\scars.dm" +#include "code\controllers\subsystem\persistence\tattoos.dm" +#include "code\controllers\subsystem\persistence\trophies.dm" #include "code\controllers\subsystem\processing\acid.dm" #include "code\controllers\subsystem\processing\ai_basic_avoidance.dm" #include "code\controllers\subsystem\processing\ai_behaviors.dm" @@ -807,7 +823,6 @@ #include "code\datums\actions\mobs\open_mob_commands.dm" #include "code\datums\actions\mobs\projectileattack.dm" #include "code\datums\actions\mobs\sign_language.dm" -#include "code\datums\actions\mobs\small_sprite.dm" #include "code\datums\actions\mobs\sneak.dm" #include "code\datums\actions\mobs\transform_weapon.dm" #include "code\datums\actions\mobs\sequences\dash_attack.dm" @@ -823,46 +838,62 @@ #include "code\datums\ai\bane\bane_controller.dm" #include "code\datums\ai\bane\bane_subtrees.dm" #include "code\datums\ai\basic_mobs\base_basic_controller.dm" +#include "code\datums\ai\basic_mobs\generic_controllers.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\basic_attacking.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\climb_tree.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\find_parent.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\nearest_targetting.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\nearest_targeting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\pick_up_item.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\run_away_from_target.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\set_travel_destination.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\step_towards_turf.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\stop_and_stare.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\targeted_mob_ability.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\targetting.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\targeting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\tipped_reaction.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\travel_towards.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\unbuckle_mob.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\ventcrawling.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\wounded_targeting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\write_on_paper.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\attack_adjacent_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\attack_obstacle_in_path.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\call_reinforcements.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\capricious_retaliate.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\climb_tree.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_food.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_paper_and_write.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_parent.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\flee_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\maintain_distance.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\mine_walls.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\move_to_cardinal.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\opportunistic_ventcrawler.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\prepare_travel_to_destination.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\ranged_skirmish.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\run_emote.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\shapechange_ambush.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_nearest_target_to_flee.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_wounded_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\sleep_with_no_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\stare_at_thing.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\targeted_mob_ability.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\teleport_away_from_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\tipped_subtree.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\travel_to_point.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\use_mob_ability.dm" #include "code\datums\ai\basic_mobs\pet_commands\fetch.dm" #include "code\datums\ai\basic_mobs\pet_commands\pet_command_planning.dm" #include "code\datums\ai\basic_mobs\pet_commands\pet_follow_friend.dm" -#include "code\datums\ai\basic_mobs\pet_commands\pet_use_targetted_ability.dm" +#include "code\datums\ai\basic_mobs\pet_commands\pet_use_targeted_ability.dm" #include "code\datums\ai\basic_mobs\pet_commands\play_dead.dm" -#include "code\datums\ai\basic_mobs\targetting_datums\basic_targetting_datum.dm" -#include "code\datums\ai\basic_mobs\targetting_datums\dont_target_friends.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\basic_targeting_strategy.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\dont_target_friends.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\with_object.dm" #include "code\datums\ai\cursed\cursed_behaviors.dm" #include "code\datums\ai\cursed\cursed_controller.dm" #include "code\datums\ai\cursed\cursed_subtrees.dm" @@ -876,6 +907,7 @@ #include "code\datums\ai\hauntium\hauntium_subtrees.dm" #include "code\datums\ai\hunting_behavior\hunting_behaviors.dm" #include "code\datums\ai\hunting_behavior\hunting_cockroach.dm" +#include "code\datums\ai\hunting_behavior\hunting_corpses.dm" #include "code\datums\ai\hunting_behavior\hunting_lights.dm" #include "code\datums\ai\hunting_behavior\hunting_mouse.dm" #include "code\datums\ai\idle_behaviors\_idle_behavior.dm" @@ -941,6 +973,7 @@ #include "code\datums\components\anti_magic.dm" #include "code\datums\components\appearance_on_aggro.dm" #include "code\datums\components\aquarium_content.dm" +#include "code\datums\components\area_based_godmode.dm" #include "code\datums\components\area_sound_manager.dm" #include "code\datums\components\areabound.dm" #include "code\datums\components\armor_plate.dm" @@ -948,12 +981,15 @@ #include "code\datums\components\attached_sticker.dm" #include "code\datums\components\aura_healing.dm" #include "code\datums\components\bakeable.dm" +#include "code\datums\components\basic_inhands.dm" #include "code\datums\components\basic_mob_attack_telegraph.dm" #include "code\datums\components\basic_ranged_ready_overlay.dm" #include "code\datums\components\beetlejuice.dm" +#include "code\datums\components\blob_minion.dm" #include "code\datums\components\blood_walk.dm" #include "code\datums\components\bloodysoles.dm" #include "code\datums\components\boomerang.dm" +#include "code\datums\components\breeding.dm" #include "code\datums\components\bullet_intercepting.dm" #include "code\datums\components\bumpattack.dm" #include "code\datums\components\burning.dm" @@ -978,11 +1014,14 @@ #include "code\datums\components\creamed.dm" #include "code\datums\components\cult_ritual_item.dm" #include "code\datums\components\curse_of_hunger.dm" +#include "code\datums\components\curse_of_polymorph.dm" #include "code\datums\components\customizable_reagent_holder.dm" #include "code\datums\components\damage_aura.dm" +#include "code\datums\components\damage_chain.dm" #include "code\datums\components\deadchat_control.dm" #include "code\datums\components\dejavu.dm" #include "code\datums\components\deployable.dm" +#include "code\datums\components\direct_explosive_trap.dm" #include "code\datums\components\drift.dm" #include "code\datums\components\earprotection.dm" #include "code\datums\components\echolocation.dm" @@ -997,6 +1036,7 @@ #include "code\datums\components\faction_granter.dm" #include "code\datums\components\fertile_egg.dm" #include "code\datums\components\fishing_spot.dm" +#include "code\datums\components\focused_attacker.dm" #include "code\datums\components\food_storage.dm" #include "code\datums\components\force_move.dm" #include "code\datums\components\fov_handler.dm" @@ -1024,11 +1064,15 @@ #include "code\datums\components\itembound.dm" #include "code\datums\components\itempicky.dm" #include "code\datums\components\jetpack.dm" +#include "code\datums\components\joint_damage.dm" #include "code\datums\components\jousting.dm" #include "code\datums\components\keep_me_secure.dm" #include "code\datums\components\knockoff.dm" #include "code\datums\components\label.dm" +#include "code\datums\components\leash.dm" +#include "code\datums\components\life_link.dm" #include "code\datums\components\light_eater.dm" +#include "code\datums\components\listen_and_repeat.dm" #include "code\datums\components\lock_on_cursor.dm" #include "code\datums\components\magnet.dm" #include "code\datums\components\manual_blinking.dm" @@ -1037,6 +1081,7 @@ #include "code\datums\components\mind_linker.dm" #include "code\datums\components\mirage_border.dm" #include "code\datums\components\mirv.dm" +#include "code\datums\components\mob_chain.dm" #include "code\datums\components\mob_harvest.dm" #include "code\datums\components\multiple_lives.dm" #include "code\datums\components\mutant_hands.dm" @@ -1052,13 +1097,14 @@ #include "code\datums\components\phylactery.dm" #include "code\datums\components\pinata.dm" #include "code\datums\components\pricetag.dm" -#include "code\datums\components\pry_open_door.dm" #include "code\datums\components\punchcooldown.dm" #include "code\datums\components\puzzgrid.dm" #include "code\datums\components\radiation_countdown.dm" #include "code\datums\components\radioactive_emitter.dm" #include "code\datums\components\ranged_attacks.dm" +#include "code\datums\components\ranged_mob_full_auto.dm" #include "code\datums\components\reagent_refiller.dm" +#include "code\datums\components\recharging_attacks.dm" #include "code\datums\components\redirect_attack_hand_from_turf.dm" #include "code\datums\components\regenerator.dm" #include "code\datums\components\religious_tool.dm" @@ -1070,6 +1116,7 @@ #include "code\datums\components\scope.dm" #include "code\datums\components\seclight_attachable.dm" #include "code\datums\components\seethrough.dm" +#include "code\datums\components\seethrough_mob.dm" #include "code\datums\components\shell.dm" #include "code\datums\components\shielded.dm" #include "code\datums\components\shovel_hands.dm" @@ -1116,6 +1163,7 @@ #include "code\datums\components\thermite.dm" #include "code\datums\components\tippable.dm" #include "code\datums\components\toggle_suit.dm" +#include "code\datums\components\torn_wall.dm" #include "code\datums\components\transforming.dm" #include "code\datums\components\trapdoor.dm" #include "code\datums\components\tree_climber.dm" @@ -1123,6 +1171,7 @@ #include "code\datums\components\udder.dm" #include "code\datums\components\unbreakable.dm" #include "code\datums\components\unobserved_actor.dm" +#include "code\datums\components\unusual_effect.dm" #include "code\datums\components\uplink.dm" #include "code\datums\components\usb_port.dm" #include "code\datums\components\vacuum.dm" @@ -1172,6 +1221,7 @@ #include "code\datums\components\riding\riding_vehicle.dm" #include "code\datums\components\style\style.dm" #include "code\datums\components\style\style_meter.dm" +#include "code\datums\components\trader\trader.dm" #include "code\datums\diseases\_disease.dm" #include "code\datums\diseases\_MobProcs.dm" #include "code\datums\diseases\adrenal_crisis.dm" @@ -1234,6 +1284,7 @@ #include "code\datums\elements\ai_flee_while_injured.dm" #include "code\datums\elements\ai_held_item.dm" #include "code\datums\elements\ai_retaliate.dm" +#include "code\datums\elements\ai_swap_combat_mode.dm" #include "code\datums\elements\ai_target_damagesource.dm" #include "code\datums\elements\amputating_limbs.dm" #include "code\datums\elements\animal_variety.dm" @@ -1272,8 +1323,10 @@ #include "code\datums\elements\death_linked.dm" #include "code\datums\elements\delete_on_drop.dm" #include "code\datums\elements\deliver_first.dm" +#include "code\datums\elements\dextrous.dm" #include "code\datums\elements\diggable.dm" #include "code\datums\elements\digitalcamo.dm" +#include "code\datums\elements\door_pryer.dm" #include "code\datums\elements\drag_pickup.dm" #include "code\datums\elements\dryable.dm" #include "code\datums\elements\earhealing.dm" @@ -1286,6 +1339,7 @@ #include "code\datums\elements\firestacker.dm" #include "code\datums\elements\floorloving.dm" #include "code\datums\elements\footstep.dm" +#include "code\datums\elements\footstep_override.dm" #include "code\datums\elements\forced_gravity.dm" #include "code\datums\elements\frozen.dm" #include "code\datums\elements\haunted.dm" @@ -1302,6 +1356,7 @@ #include "code\datums\elements\light_eaten.dm" #include "code\datums\elements\light_eater.dm" #include "code\datums\elements\loomable.dm" +#include "code\datums\elements\mob_grabber.dm" #include "code\datums\elements\mob_killed_tally.dm" #include "code\datums\elements\movement_turf_changer.dm" #include "code\datums\elements\movetype_handler.dm" @@ -1309,6 +1364,7 @@ #include "code\datums\elements\noticable_organ.dm" #include "code\datums\elements\obj_regen.dm" #include "code\datums\elements\openspace_item_click_handler.dm" +#include "code\datums\elements\ore_collecting.dm" #include "code\datums\elements\organ_set_bonus.dm" #include "code\datums\elements\pet_bonus.dm" #include "code\datums\elements\plant_backfire.dm" @@ -1333,6 +1389,7 @@ #include "code\datums\elements\squish.dm" #include "code\datums\elements\sticker.dm" #include "code\datums\elements\strippable.dm" +#include "code\datums\elements\structure_repair.dm" #include "code\datums\elements\swabbable.dm" #include "code\datums\elements\tear_wall.dm" #include "code\datums\elements\temporary_atom.dm" @@ -1350,9 +1407,12 @@ #include "code\datums\elements\waddling.dm" #include "code\datums\elements\wall_engraver.dm" #include "code\datums\elements\wall_smasher.dm" +#include "code\datums\elements\wall_tearer.dm" +#include "code\datums\elements\wall_walker.dm" #include "code\datums\elements\weapon_description.dm" #include "code\datums\elements\weather_listener.dm" #include "code\datums\elements\web_walker.dm" +#include "code\datums\elements\wheel.dm" #include "code\datums\elements\decals\_decal.dm" #include "code\datums\elements\decals\blood.dm" #include "code\datums\elements\food\dunkable.dm" @@ -1373,6 +1433,7 @@ #include "code\datums\greyscale\config_types\greyscale_configs.dm" #include "code\datums\greyscale\config_types\material_effects.dm" #include "code\datums\greyscale\config_types\mutant_organ_config.dm" +#include "code\datums\greyscale\config_types\greyscale_configs\greyscale_mobs.dm" #include "code\datums\helper_datums\events.dm" #include "code\datums\helper_datums\getrev.dm" #include "code\datums\helper_datums\icon_snapshot.dm" @@ -1578,13 +1639,16 @@ #include "code\datums\wires\syndicatebomb.dm" #include "code\datums\wires\tesla_coil.dm" #include "code\datums\wires\vending.dm" +#include "code\datums\wounds\_wound_static_data.dm" #include "code\datums\wounds\_wounds.dm" +#include "code\datums\wounds\blunt.dm" #include "code\datums\wounds\bones.dm" #include "code\datums\wounds\burns.dm" #include "code\datums\wounds\loss.dm" #include "code\datums\wounds\pierce.dm" #include "code\datums\wounds\slash.dm" #include "code\datums\wounds\scars\_scars.dm" +#include "code\datums\wounds\scars\_static_scar_data.dm" #include "code\game\alternate_appearance.dm" #include "code\game\atom_defense.dm" #include "code\game\atoms.dm" @@ -1821,7 +1885,7 @@ #include "code\game\objects\effects\particle_holder.dm" #include "code\game\objects\effects\phased_mob.dm" #include "code\game\objects\effects\portals.dm" -#include "code\game\objects\effects\poster.dm" +#include "code\game\objects\effects\poster_demotivational.dm" #include "code\game\objects\effects\poster_motivational.dm" #include "code\game\objects\effects\powerup.dm" #include "code\game\objects\effects\spiderwebs.dm" @@ -1867,6 +1931,9 @@ #include "code\game\objects\effects\particles\note_particles.dm" #include "code\game\objects\effects\particles\smoke.dm" #include "code\game\objects\effects\particles\water.dm" +#include "code\game\objects\effects\posters\contraband.dm" +#include "code\game\objects\effects\posters\official.dm" +#include "code\game\objects\effects\posters\poster.dm" #include "code\game\objects\effects\spawners\bombspawner.dm" #include "code\game\objects\effects\spawners\costume.dm" #include "code\game\objects\effects\spawners\gibspawner.dm" @@ -1981,10 +2048,12 @@ #include "code\game\objects\items\skub.dm" #include "code\game\objects\items\spear.dm" #include "code\game\objects\items\sticker.dm" +#include "code\game\objects\items\syndie_spraycan.dm" #include "code\game\objects\items\tail_pin.dm" #include "code\game\objects\items\taster.dm" #include "code\game\objects\items\teleportation.dm" #include "code\game\objects\items\theft_tools.dm" +#include "code\game\objects\items\tongs.dm" #include "code\game\objects\items\toy_mechs.dm" #include "code\game\objects\items\toys.dm" #include "code\game\objects\items\trash.dm" @@ -2271,6 +2340,7 @@ #include "code\game\objects\structures\morgue.dm" #include "code\game\objects\structures\mystery_box.dm" #include "code\game\objects\structures\noticeboard.dm" +#include "code\game\objects\structures\ore_containers.dm" #include "code\game\objects\structures\petrified_statue.dm" #include "code\game\objects\structures\pinatas.dm" #include "code\game\objects\structures\plasticflaps.dm" @@ -2400,6 +2470,7 @@ #include "code\modules\actionspeed\modifiers\drugs.dm" #include "code\modules\actionspeed\modifiers\mood.dm" #include "code\modules\actionspeed\modifiers\status_effects.dm" +#include "code\modules\actionspeed\modifiers\wound.dm" #include "code\modules\admin\admin.dm" #include "code\modules\admin\admin_fax_panel.dm" #include "code\modules\admin\admin_investigate.dm" @@ -2693,6 +2764,7 @@ #include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_map.dm" #include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_moodlets.dm" #include "code\modules\antagonists\heretic\magic\aggressive_spread.dm" +#include "code\modules\antagonists\heretic\magic\ascended_shapeshift.dm" #include "code\modules\antagonists\heretic\magic\ash_ascension.dm" #include "code\modules\antagonists\heretic\magic\ash_jaunt.dm" #include "code\modules\antagonists\heretic\magic\blood_cleave.dm" @@ -2723,12 +2795,12 @@ #include "code\modules\antagonists\heretic\magic\void_cold_cone.dm" #include "code\modules\antagonists\heretic\magic\void_phase.dm" #include "code\modules\antagonists\heretic\magic\void_pull.dm" -#include "code\modules\antagonists\heretic\mobs\maid_in_mirror.dm" #include "code\modules\antagonists\heretic\status_effects\buffs.dm" #include "code\modules\antagonists\heretic\status_effects\debuffs.dm" #include "code\modules\antagonists\heretic\status_effects\ghoul.dm" #include "code\modules\antagonists\heretic\status_effects\mark_effects.dm" #include "code\modules\antagonists\heretic\structures\carving_knife.dm" +#include "code\modules\antagonists\heretic\structures\knock_final.dm" #include "code\modules\antagonists\heretic\structures\mawed_crucible.dm" #include "code\modules\antagonists\highlander\highlander.dm" #include "code\modules\antagonists\hypnotized\hypnotized.dm" @@ -2769,7 +2841,6 @@ #include "code\modules\antagonists\pirate\pirate_special_items.dm" #include "code\modules\antagonists\pyro_slime\pyro_slime.dm" #include "code\modules\antagonists\revenant\haunted_item.dm" -#include "code\modules\antagonists\revenant\revenant_abilities.dm" #include "code\modules\antagonists\revenant\revenant_antag.dm" #include "code\modules\antagonists\revenant\revenant_blight.dm" #include "code\modules\antagonists\revolution\enemy_of_the_revolution.dm" @@ -2797,9 +2868,7 @@ #include "code\modules\antagonists\traitor\components\traitor_objective_limit_per_time.dm" #include "code\modules\antagonists\traitor\components\traitor_objective_mind_tracker.dm" #include "code\modules\antagonists\traitor\objectives\assassination.dm" -#include "code\modules\antagonists\traitor\objectives\demoralise_crew.dm" -#include "code\modules\antagonists\traitor\objectives\demoralise_graffiti.dm" -#include "code\modules\antagonists\traitor\objectives\demoralise_poster.dm" +#include "code\modules\antagonists\traitor\objectives\demoralise_assault.dm" #include "code\modules\antagonists\traitor\objectives\destroy_heirloom.dm" #include "code\modules\antagonists\traitor\objectives\destroy_item.dm" #include "code\modules\antagonists\traitor\objectives\eyesnatching.dm" @@ -3256,6 +3325,7 @@ #include "code\modules\client\verbs\who.dm" #include "code\modules\clothing\chameleon.dm" #include "code\modules\clothing\clothing.dm" +#include "code\modules\clothing\chameleon\chameleon_drone.dm" #include "code\modules\clothing\ears\_ears.dm" #include "code\modules\clothing\glasses\_glasses.dm" #include "code\modules\clothing\glasses\engine_goggles.dm" @@ -3699,6 +3769,7 @@ #include "code\modules\hydroponics\grown\random.dm" #include "code\modules\hydroponics\grown\replicapod.dm" #include "code\modules\hydroponics\grown\root.dm" +#include "code\modules\hydroponics\grown\seedling.dm" #include "code\modules\hydroponics\grown\sugarcane.dm" #include "code\modules\hydroponics\grown\tea_coffee.dm" #include "code\modules\hydroponics\grown\tobacco.dm" @@ -3989,7 +4060,6 @@ #include "code\modules\mining\machine_stacking.dm" #include "code\modules\mining\machine_unloading.dm" #include "code\modules\mining\mine_items.dm" -#include "code\modules\mining\minebot.dm" #include "code\modules\mining\money_bag.dm" #include "code\modules\mining\ores_coins.dm" #include "code\modules\mining\satchel_ore_boxdm.dm" @@ -4072,6 +4142,30 @@ #include "code\modules\mob\living\basic\festivus_pole.dm" #include "code\modules\mob\living\basic\health_adjustment.dm" #include "code\modules\mob\living\basic\tree.dm" +#include "code\modules\mob\living\basic\blob_minions\blob_ai.dm" +#include "code\modules\mob\living\basic\blob_minions\blob_mob.dm" +#include "code\modules\mob\living\basic\blob_minions\blob_spore.dm" +#include "code\modules\mob\living\basic\blob_minions\blob_zombie.dm" +#include "code\modules\mob\living\basic\blob_minions\blobbernaut.dm" +#include "code\modules\mob\living\basic\clown\clown.dm" +#include "code\modules\mob\living\basic\clown\clown_ai.dm" +#include "code\modules\mob\living\basic\cult\shade.dm" +#include "code\modules\mob\living\basic\cult\constructs\_construct.dm" +#include "code\modules\mob\living\basic\cult\constructs\artificer.dm" +#include "code\modules\mob\living\basic\cult\constructs\construct_ai.dm" +#include "code\modules\mob\living\basic\cult\constructs\harvester.dm" +#include "code\modules\mob\living\basic\cult\constructs\juggernaut.dm" +#include "code\modules\mob\living\basic\cult\constructs\proteon.dm" +#include "code\modules\mob\living\basic\cult\constructs\wraith.dm" +#include "code\modules\mob\living\basic\drone\_drone.dm" +#include "code\modules\mob\living\basic\drone\drone_say.dm" +#include "code\modules\mob\living\basic\drone\drone_tools.dm" +#include "code\modules\mob\living\basic\drone\drones_as_items.dm" +#include "code\modules\mob\living\basic\drone\extra_drone_types.dm" +#include "code\modules\mob\living\basic\drone\interaction.dm" +#include "code\modules\mob\living\basic\drone\inventory.dm" +#include "code\modules\mob\living\basic\drone\verbs.dm" +#include "code\modules\mob\living\basic\drone\visuals_icons.dm" #include "code\modules\mob\living\basic\farm_animals\deer.dm" #include "code\modules\mob\living\basic\farm_animals\pig.dm" #include "code\modules\mob\living\basic\farm_animals\rabbit.dm" @@ -4083,14 +4177,55 @@ #include "code\modules\mob\living\basic\farm_animals\cow\cow_ai.dm" #include "code\modules\mob\living\basic\farm_animals\cow\cow_moonicorn.dm" #include "code\modules\mob\living\basic\farm_animals\cow\cow_wisdom.dm" +#include "code\modules\mob\living\basic\farm_animals\goat\_goat.dm" +#include "code\modules\mob\living\basic\farm_animals\goat\goat_ai.dm" +#include "code\modules\mob\living\basic\farm_animals\goat\goat_subtypes.dm" +#include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla.dm" +#include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla_accessories.dm" +#include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla_ai.dm" +#include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla_emotes.dm" +#include "code\modules\mob\living\basic\guardian\guardian.dm" +#include "code\modules\mob\living\basic\guardian\guardian_creator.dm" +#include "code\modules\mob\living\basic\guardian\guardian_fluff.dm" +#include "code\modules\mob\living\basic\guardian\guardian_helpers.dm" +#include "code\modules\mob\living\basic\guardian\guardian_verbs.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\assassin.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\charger.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\dextrous.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\explosive.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\gaseous.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\gravitokinetic.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\lightning.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\protector.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\ranged.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\standard.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\support.dm" +#include "code\modules\mob\living\basic\heretic\_heretic_summon.dm" +#include "code\modules\mob\living\basic\heretic\ash_spirit.dm" #include "code\modules\mob\living\basic\heretic\fire_shark.dm" +#include "code\modules\mob\living\basic\heretic\flesh_stalker.dm" +#include "code\modules\mob\living\basic\heretic\flesh_worm.dm" +#include "code\modules\mob\living\basic\heretic\heretic_summon.dm" +#include "code\modules\mob\living\basic\heretic\maid_in_the_mirror.dm" +#include "code\modules\mob\living\basic\heretic\raw_prophet.dm" +#include "code\modules\mob\living\basic\heretic\rust_walker.dm" #include "code\modules\mob\living\basic\heretic\star_gazer.dm" +#include "code\modules\mob\living\basic\icemoon\ice_demon\ice_demon.dm" +#include "code\modules\mob\living\basic\icemoon\ice_demon\ice_demon_abilities.dm" +#include "code\modules\mob\living\basic\icemoon\ice_demon\ice_demon_ai.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp_abilities.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp_ai.dm" +#include "code\modules\mob\living\basic\icemoon\wolf\wolf.dm" +#include "code\modules\mob\living\basic\icemoon\wolf\wolf_ai.dm" +#include "code\modules\mob\living\basic\icemoon\wolf\wolf_extras.dm" +#include "code\modules\mob\living\basic\jungle\venus_human_trap.dm" #include "code\modules\mob\living\basic\jungle\mega_arachnid\mega_arachnid.dm" #include "code\modules\mob\living\basic\jungle\mega_arachnid\mega_arachnid_abilities.dm" #include "code\modules\mob\living\basic\jungle\mega_arachnid\mega_arachnid_ai.dm" +#include "code\modules\mob\living\basic\jungle\seedling\seedling.dm" +#include "code\modules\mob\living\basic\jungle\seedling\seedling_ai.dm" +#include "code\modules\mob\living\basic\jungle\seedling\seedling_projectiles.dm" #include "code\modules\mob\living\basic\lavaland\mining.dm" #include "code\modules\mob\living\basic\lavaland\basilisk\basilisk.dm" #include "code\modules\mob\living\basic\lavaland\basilisk\basilisk_overheat.dm" @@ -4100,6 +4235,10 @@ #include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_instrument.dm" #include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_loot.dm" #include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_vileworm.dm" +#include "code\modules\mob\living\basic\lavaland\brimdemon\brimbeam.dm" +#include "code\modules\mob\living\basic\lavaland\brimdemon\brimdemon.dm" +#include "code\modules\mob\living\basic\lavaland\brimdemon\brimdemon_ai.dm" +#include "code\modules\mob\living\basic\lavaland\brimdemon\brimdemon_loot.dm" #include "code\modules\mob\living\basic\lavaland\goldgrub\goldgrub.dm" #include "code\modules\mob\living\basic\lavaland\goldgrub\goldgrub_abilities.dm" #include "code\modules\mob\living\basic\lavaland\goldgrub\goldgrub_ai.dm" @@ -4108,22 +4247,56 @@ #include "code\modules\mob\living\basic\lavaland\goliath\goliath_ai.dm" #include "code\modules\mob\living\basic\lavaland\goliath\goliath_trophy.dm" #include "code\modules\mob\living\basic\lavaland\goliath\tentacle.dm" +#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutluncher_foodtrough.dm" +#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutlunchers.dm" +#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutlunchers_ai.dm" +#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutlunchers_inherit_datum.dm" +#include "code\modules\mob\living\basic\lavaland\hivelord\hivelord.dm" +#include "code\modules\mob\living\basic\lavaland\hivelord\hivelord_ai.dm" +#include "code\modules\mob\living\basic\lavaland\hivelord\spawn_hivelord_brood.dm" +#include "code\modules\mob\living\basic\lavaland\legion\legion.dm" +#include "code\modules\mob\living\basic\lavaland\legion\legion_ai.dm" +#include "code\modules\mob\living\basic\lavaland\legion\legion_brood.dm" +#include "code\modules\mob\living\basic\lavaland\legion\legion_tumour.dm" +#include "code\modules\mob\living\basic\lavaland\legion\spawn_legions.dm" #include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity.dm" #include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity_ai.dm" #include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity_trophy.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook_abilities.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook_ai.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook_village.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher_ai.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher_gaze.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher_overwatch.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher_projectiles.dm" +#include "code\modules\mob\living\basic\minebots\minebot.dm" +#include "code\modules\mob\living\basic\minebots\minebot_abilities.dm" +#include "code\modules\mob\living\basic\minebots\minebot_ai.dm" +#include "code\modules\mob\living\basic\minebots\minebot_upgrades.dm" #include "code\modules\mob\living\basic\pets\fox.dm" #include "code\modules\mob\living\basic\pets\penguin.dm" #include "code\modules\mob\living\basic\pets\pet.dm" +#include "code\modules\mob\living\basic\pets\sloth.dm" #include "code\modules\mob\living\basic\pets\dog\_dog.dm" #include "code\modules\mob\living\basic\pets\dog\corgi.dm" #include "code\modules\mob\living\basic\pets\dog\dog_subtypes.dm" #include "code\modules\mob\living\basic\pets\dog\strippable_items.dm" +#include "code\modules\mob\living\basic\pets\parrot\_parrot.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_items.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_subtypes.dm" +#include "code\modules\mob\living\basic\pets\parrot\poly.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_ai\_parrot_controller.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_ai\ghost_parrot_controller.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_ai\parrot_hoarding.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_ai\parrot_perching.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_ai\parroting_action.dm" +#include "code\modules\mob\living\basic\ruin_defender\skeleton.dm" #include "code\modules\mob\living\basic\ruin_defender\stickman.dm" +#include "code\modules\mob\living\basic\ruin_defender\wizard\wizard.dm" +#include "code\modules\mob\living\basic\ruin_defender\wizard\wizard_ai.dm" +#include "code\modules\mob\living\basic\ruin_defender\wizard\wizard_spells.dm" #include "code\modules\mob\living\basic\space_fauna\ant.dm" #include "code\modules\mob\living\basic\space_fauna\cat_surgeon.dm" #include "code\modules\mob\living\basic\space_fauna\faithless.dm" @@ -4134,7 +4307,9 @@ #include "code\modules\mob\living\basic\space_fauna\lightgeist.dm" #include "code\modules\mob\living\basic\space_fauna\morph.dm" #include "code\modules\mob\living\basic\space_fauna\mushroom.dm" +#include "code\modules\mob\living\basic\space_fauna\robot_customer.dm" #include "code\modules\mob\living\basic\space_fauna\spaceman.dm" +#include "code\modules\mob\living\basic\space_fauna\supermatter_spider.dm" #include "code\modules\mob\living\basic\space_fauna\bear\_bear.dm" #include "code\modules\mob\living\basic\space_fauna\bear\bear_ai_behavior.dm" #include "code\modules\mob\living\basic\space_fauna\bear\bear_ai_subtree.dm" @@ -4169,6 +4344,17 @@ #include "code\modules\mob\living\basic\space_fauna\regal_rat\regal_rat.dm" #include "code\modules\mob\living\basic\space_fauna\regal_rat\regal_rat_actions.dm" #include "code\modules\mob\living\basic\space_fauna\regal_rat\regal_rat_ai.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\_revenant.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_abilities.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_effects.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_harvest.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_items.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_objectives.dm" +#include "code\modules\mob\living\basic\space_fauna\snake\snake.dm" +#include "code\modules\mob\living\basic\space_fauna\snake\snake_ai.dm" +#include "code\modules\mob\living\basic\space_fauna\space_dragon\dragon_breath.dm" +#include "code\modules\mob\living\basic\space_fauna\space_dragon\dragon_gust.dm" +#include "code\modules\mob\living\basic\space_fauna\space_dragon\space_dragon.dm" #include "code\modules\mob\living\basic\space_fauna\spider\spider.dm" #include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_ai.dm" #include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spiders.dm" @@ -4186,9 +4372,16 @@ #include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\inflation.dm" #include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\wumborian_ai.dm" #include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\wumborian_fugu.dm" -#include "code\modules\mob\living\basic\syndicate\russian.dm" -#include "code\modules\mob\living\basic\syndicate\syndicate.dm" -#include "code\modules\mob\living\basic\syndicate\syndicate_ai.dm" +#include "code\modules\mob\living\basic\trader\trader.dm" +#include "code\modules\mob\living\basic\trader\trader_actions.dm" +#include "code\modules\mob\living\basic\trader\trader_ai.dm" +#include "code\modules\mob\living\basic\trader\trader_data.dm" +#include "code\modules\mob\living\basic\trader\trader_items.dm" +#include "code\modules\mob\living\basic\trooper\nanotrasen.dm" +#include "code\modules\mob\living\basic\trooper\russian.dm" +#include "code\modules\mob\living\basic\trooper\syndicate.dm" +#include "code\modules\mob\living\basic\trooper\trooper.dm" +#include "code\modules\mob\living\basic\trooper\trooper_ai.dm" #include "code\modules\mob\living\basic\vermin\axolotl.dm" #include "code\modules\mob\living\basic\vermin\butterfly.dm" #include "code\modules\mob\living\basic\vermin\cockroach.dm" @@ -4257,7 +4450,6 @@ #include "code\modules\mob\living\carbon\alien\special\alien_embryo.dm" #include "code\modules\mob\living\carbon\alien\special\facehugger.dm" #include "code\modules\mob\living\carbon\human\_species.dm" -#include "code\modules\mob\living\carbon\human\damage_procs.dm" #include "code\modules\mob\living\carbon\human\death.dm" #include "code\modules\mob\living\carbon\human\dummy.dm" #include "code\modules\mob\living\carbon\human\emote.dm" @@ -4341,9 +4533,6 @@ #include "code\modules\mob\living\silicon\robot\robot_say.dm" #include "code\modules\mob\living\simple_animal\animal_defense.dm" #include "code\modules\mob\living\simple_animal\damage_procs.dm" -#include "code\modules\mob\living\simple_animal\parrot.dm" -#include "code\modules\mob\living\simple_animal\revenant.dm" -#include "code\modules\mob\living\simple_animal\shade.dm" #include "code\modules\mob\living\simple_animal\simple_animal.dm" #include "code\modules\mob\living\simple_animal\bot\bot.dm" #include "code\modules\mob\living\simple_animal\bot\cleanbot.dm" @@ -4359,65 +4548,19 @@ #include "code\modules\mob\living\simple_animal\bot\SuperBeepsky.dm" #include "code\modules\mob\living\simple_animal\bot\vibebot.dm" #include "code\modules\mob\living\simple_animal\friendly\cat.dm" -#include "code\modules\mob\living\simple_animal\friendly\farm_animals.dm" #include "code\modules\mob\living\simple_animal\friendly\gondola.dm" #include "code\modules\mob\living\simple_animal\friendly\pet.dm" -#include "code\modules\mob\living\simple_animal\friendly\robot_customer.dm" -#include "code\modules\mob\living\simple_animal\friendly\sloth.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\_drone.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\drone_say.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\drone_tools.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\drones_as_items.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\extra_drone_types.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\interaction.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\inventory.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\verbs.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\visuals_icons.dm" -#include "code\modules\mob\living\simple_animal\guardian\guardian.dm" -#include "code\modules\mob\living\simple_animal\guardian\guardian_creator.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\assassin.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\charger.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\dextrous.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\explosive.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\gaseous.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\gravitokinetic.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\lightning.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\protector.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\ranged.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\standard.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\support.dm" #include "code\modules\mob\living\simple_animal\hostile\alien.dm" -#include "code\modules\mob\living\simple_animal\hostile\blob.dm" -#include "code\modules\mob\living\simple_animal\hostile\blobbernaut.dm" -#include "code\modules\mob\living\simple_animal\hostile\blobspore.dm" #include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm" -#include "code\modules\mob\living\simple_animal\hostile\heretic_monsters.dm" #include "code\modules\mob\living\simple_animal\hostile\hostile.dm" #include "code\modules\mob\living\simple_animal\hostile\illusion.dm" #include "code\modules\mob\living\simple_animal\hostile\mimic.dm" -#include "code\modules\mob\living\simple_animal\hostile\nanotrasen.dm" #include "code\modules\mob\living\simple_animal\hostile\ooze.dm" #include "code\modules\mob\living\simple_animal\hostile\pirate.dm" -#include "code\modules\mob\living\simple_animal\hostile\skeleton.dm" -#include "code\modules\mob\living\simple_animal\hostile\smspider.dm" -#include "code\modules\mob\living\simple_animal\hostile\space_dragon.dm" #include "code\modules\mob\living\simple_animal\hostile\vatbeast.dm" -#include "code\modules\mob\living\simple_animal\hostile\venus_human_trap.dm" -#include "code\modules\mob\living\simple_animal\hostile\wizard.dm" #include "code\modules\mob\living\simple_animal\hostile\zombie.dm" -#include "code\modules\mob\living\simple_animal\hostile\bosses\boss.dm" -#include "code\modules\mob\living\simple_animal\hostile\constructs\artificer.dm" -#include "code\modules\mob\living\simple_animal\hostile\constructs\constructs.dm" -#include "code\modules\mob\living\simple_animal\hostile\constructs\harvester.dm" -#include "code\modules\mob\living\simple_animal\hostile\constructs\juggernaut.dm" -#include "code\modules\mob\living\simple_animal\hostile\constructs\wraith.dm" -#include "code\modules\mob\living\simple_animal\hostile\gorilla\emotes.dm" -#include "code\modules\mob\living\simple_animal\hostile\gorilla\gorilla.dm" -#include "code\modules\mob\living\simple_animal\hostile\gorilla\visuals_icons.dm" #include "code\modules\mob\living\simple_animal\hostile\jungle\_jungle_mobs.dm" #include "code\modules\mob\living\simple_animal\hostile\jungle\leaper.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\mook.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\seedling.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\_megafauna.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\blood_drunk_miner.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\bubblegum.dm" @@ -4428,11 +4571,7 @@ #include "code\modules\mob\living\simple_animal\hostile\megafauna\hierophant.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\legion.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\wendigo.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\brimdemon.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\curse_blob.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\gutlunch.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\hivelord.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\ice_demon.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\mining_mobs.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\polarbear.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\wolf.dm" @@ -4441,11 +4580,8 @@ #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\herald.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\legionnaire.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\pandora.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\clown.dm" #include "code\modules\mob\living\simple_animal\hostile\retaliate\goose.dm" #include "code\modules\mob\living\simple_animal\hostile\retaliate\retaliate.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\snake.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\trader.dm" #include "code\modules\mob\living\simple_animal\slime\death.dm" #include "code\modules\mob\living\simple_animal\slime\emote.dm" #include "code\modules\mob\living\simple_animal\slime\life.dm" diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss index 14092402337a..4b2701934106 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss @@ -485,6 +485,11 @@ em { font-size: 90%; } +.bolditalic { + font-style: italic; + font-weight: bold; +} + .boldnotice { color: #6685f5; font-weight: bold; @@ -540,6 +545,10 @@ em { } .blob { + color: #ee4000; +} + +.blobannounce { color: #556b2f; font-weight: bold; font-size: 185%; diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss index 33d8d70ad0d3..16c10544bac8 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss @@ -602,6 +602,10 @@ h2.alert { } .blob { + color: #ee4000; +} + +.blobannounce { color: #323f1c; font-weight: bold; font-size: 185%; diff --git a/tgui/packages/tgui/interfaces/CrewConsole.js b/tgui/packages/tgui/interfaces/CrewConsole.js index 58e99dd9c9e8..937ff9c1baf2 100644 --- a/tgui/packages/tgui/interfaces/CrewConsole.js +++ b/tgui/packages/tgui/interfaces/CrewConsole.js @@ -13,14 +13,8 @@ const HEALTH_COLOR_BY_LEVEL = [ '#801308', ]; -const HEALTH_ICON_BY_LEVEL = [ - 'heart', - 'heart', - 'heart', - 'heart', - 'heartbeat', - 'skull', -]; +const STAT_LIVING = 0; +const STAT_DEAD = 4; const jobIsHead = (jobId) => jobId % 10 === 0; @@ -49,6 +43,16 @@ const jobToColor = (jobId) => { return COLORS.department.other; }; +const statToIcon = (life_status) => { + switch (life_status) { + case STAT_LIVING: + return 'heart'; + case STAT_DEAD: + return 'skull'; + } + return 'heartbeat'; +}; + const healthToAttribute = (oxy, tox, burn, brute, attributeList) => { const healthSum = oxy + tox + burn + brute; const level = Math.min(Math.max(Math.ceil(healthSum / 25), 0), 5); @@ -129,13 +133,7 @@ const CrewTableEntry = (props, context) => { {oxydam !== undefined ? ( { )} size={1} /> - ) : life_status ? ( + ) : life_status !== STAT_DEAD ? ( ) : ( @@ -162,7 +160,7 @@ const CrewTableEntry = (props, context) => { {'/'} - ) : life_status ? ( + ) : life_status !== STAT_DEAD ? ( 'Alive' ) : ( 'Dead' diff --git a/tgui/packages/tgui/interfaces/OreContainer.tsx b/tgui/packages/tgui/interfaces/OreContainer.tsx new file mode 100644 index 000000000000..1ec75332b752 --- /dev/null +++ b/tgui/packages/tgui/interfaces/OreContainer.tsx @@ -0,0 +1,120 @@ +import { createSearch, toTitleCase } from 'common/string'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, Input, Stack, Flex, Section } from '../components'; +import { Window } from '../layouts'; + +type Ores = { + id: string; + name: string; + amount: number; +}; + +type Ore_images = { + name: string; + icon: string; +}; + +type Data = { + ores: Ores[]; + ore_images: Ore_images[]; +}; + +export const OreContainer = (props, context) => { + const { act, data } = useBackend(context); + const { ores = [] } = data; + const [searchItem, setSearchItem] = useLocalState(context, 'searchItem', ''); + const search = createSearch(searchItem, (ore: Ores) => ore.name); + const ores_filtered = + searchItem.length > 0 ? ores.filter((ore) => search(ore)) : ores; + return ( + + + + +
+ { + setSearchItem(value); + }} + fluid + /> +
+
+ +
+ + {ores_filtered.map((ore) => ( + + + + + + + + + Amount: {ore.amount} + +
+
+
+
+
+ ); +}; + +const RetrieveIcon = (props, context) => { + const { data } = useBackend(context); + const { ore_images = [] } = data; + const { ore } = props; + + let icon_display = ore_images.find((icon) => icon.name === ore.name); + + if (!icon_display) { + return null; + } + + return ( + + ); +}; + +const Orename = (props) => { + const { ore_name } = props; + const return_name = ore_name.split(' '); + if (return_name.length === 0) { + return null; + } + return return_name[0]; +}; diff --git a/tools/UpdatePaths/Scripts/00000_simple_to_basic_construct_juggernaut.txt b/tools/UpdatePaths/Scripts/00000_simple_to_basic_construct_juggernaut.txt new file mode 100644 index 000000000000..f74e0e64585d --- /dev/null +++ b/tools/UpdatePaths/Scripts/00000_simple_to_basic_construct_juggernaut.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/construct/juggernaut/@SUBTYPES : /mob/living/basic/construct/juggernaut/@SUBTYPES{@OLD} diff --git a/tools/UpdatePaths/Scripts/00000_simple_to_basic_construct_proteon.txt b/tools/UpdatePaths/Scripts/00000_simple_to_basic_construct_proteon.txt new file mode 100644 index 000000000000..f13d42ce70fc --- /dev/null +++ b/tools/UpdatePaths/Scripts/00000_simple_to_basic_construct_proteon.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/construct/proteon/@SUBTYPES : /mob/living/basic/construct/proteon/@SUBTYPES{@OLD} diff --git a/tools/UpdatePaths/Scripts/78032_miningdrone.txt b/tools/UpdatePaths/Scripts/78032_miningdrone.txt new file mode 100644 index 000000000000..2ae5cefe8fb6 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78032_miningdrone.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/mining_drone : /mob/living/basic/mining_drone{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/78424_simple_to_basic_brimdemon.txt b/tools/UpdatePaths/Scripts/78424_simple_to_basic_brimdemon.txt new file mode 100644 index 000000000000..9895ad7f5f45 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78424_simple_to_basic_brimdemon.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/asteroid/brimdemon : /mob/living/basic/mining/brimdemon{@OLD} diff --git a/tools/UpdatePaths/Scripts/78448_basicclowns.txt b/tools/UpdatePaths/Scripts/78448_basicclowns.txt new file mode 100644 index 000000000000..91d134d157ea --- /dev/null +++ b/tools/UpdatePaths/Scripts/78448_basicclowns.txt @@ -0,0 +1,3 @@ +#comment Repaths simpleanimal clowns to basicmob clowns + +/mob/living/simple_animal/hostile/retaliate/clown/@SUBTYPES : /mob/living/basic/clown/@SUBTYPES{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/78539_ice_demons.txt b/tools/UpdatePaths/Scripts/78539_ice_demons.txt new file mode 100644 index 000000000000..f387d2005000 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78539_ice_demons.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/asteroid/ice_demon : /mob/living/basic/mining/ice_demon{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/78612_simple_to_basic_snakes.txt b/tools/UpdatePaths/Scripts/78612_simple_to_basic_snakes.txt new file mode 100644 index 000000000000..083fc74e24f6 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78612_simple_to_basic_snakes.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/retaliate/snake : /mob/living/basic/snake{@OLD} diff --git a/tools/UpdatePaths/Scripts/78624_simple_to_basic_legion.txt b/tools/UpdatePaths/Scripts/78624_simple_to_basic_legion.txt new file mode 100644 index 000000000000..bf397a83bb23 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78624_simple_to_basic_legion.txt @@ -0,0 +1,3 @@ +/mob/living/simple_animal/hostile/asteroid/hivelord : /mob/living/basic/mining/hivelord{@OLD} +/mob/living/simple_animal/hostile/big_legion : /mob/living/basic/mining/legion/big{@OLD} +/mob/living/simple_animal/hostile/asteroid/hivelord/legion/@SUBTYPES : /mob/living/basic/mining/legion/@SUBTYPES{@OLD} diff --git a/tools/UpdatePaths/Scripts/78733_simple_to_basic_raw_prophet.txt b/tools/UpdatePaths/Scripts/78733_simple_to_basic_raw_prophet.txt new file mode 100644 index 000000000000..d61019b146fa --- /dev/null +++ b/tools/UpdatePaths/Scripts/78733_simple_to_basic_raw_prophet.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/heretic_summon/raw_prophet : /mob/living/basic/heretic_summon/raw_prophet/ruins{@OLD;AIStatus=@SKIP;stop_automated_movement=@SKIP} diff --git a/tools/UpdatePaths/Scripts/78752_simple_to_basic_sloths.txt b/tools/UpdatePaths/Scripts/78752_simple_to_basic_sloths.txt new file mode 100644 index 000000000000..0312b163295a --- /dev/null +++ b/tools/UpdatePaths/Scripts/78752_simple_to_basic_sloths.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/sloth/@SUBTYPES : /mob/living/basic/sloth/@SUBTYPES{@OLD} diff --git a/tools/UpdatePaths/Scripts/78757_simple_to_basic_heretic_summons.txt b/tools/UpdatePaths/Scripts/78757_simple_to_basic_heretic_summons.txt new file mode 100644 index 000000000000..470eb429db73 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78757_simple_to_basic_heretic_summons.txt @@ -0,0 +1,4 @@ +/mob/living/simple_animal/hostile/heretic_summon/ash_spirit : /mob/living/basic/heretic_summon/ash_spirit{@OLD} +/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror : /mob/living/basic/heretic_summon/maid_in_the_mirror{@OLD} +/mob/living/simple_animal/hostile/heretic_summon/rust_spirit : /mob/living/basic/heretic_summon/rust_walker{@OLD;AIStatus=@SKIP;stop_automated_movement=@SKIP} +/mob/living/simple_animal/hostile/heretic_summon/stalker : /mob/living/basic/heretic_summon/stalker{@OLD} diff --git a/tools/UpdatePaths/Scripts/78759_simple_to_basic_goat.txt b/tools/UpdatePaths/Scripts/78759_simple_to_basic_goat.txt new file mode 100644 index 000000000000..2b092d0ce214 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78759_simple_to_basic_goat.txt @@ -0,0 +1,2 @@ +/mob/living/simple_animal/hostile/retaliate/goat{name="Pete"} : /mob/living/basic/goat/pete{@OLD} +/mob/living/simple_animal/hostile/retaliate/goat : /mob/living/basic/goat{@OLD} diff --git a/tools/UpdatePaths/Scripts/78807_simple_to_basic_construct_harvester.txt b/tools/UpdatePaths/Scripts/78807_simple_to_basic_construct_harvester.txt new file mode 100644 index 000000000000..93996fd123bc --- /dev/null +++ b/tools/UpdatePaths/Scripts/78807_simple_to_basic_construct_harvester.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/construct/harvester : /mob/living/basic/construct/harvester{@OLD} diff --git a/tools/UpdatePaths/Scripts/78917_simple_to_basic_nanotrasen.txt b/tools/UpdatePaths/Scripts/78917_simple_to_basic_nanotrasen.txt new file mode 100644 index 000000000000..eff2a7616c96 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78917_simple_to_basic_nanotrasen.txt @@ -0,0 +1,6 @@ +/mob/living/basic/syndicate/russian/@SUBTYPES : /mob/living/basic/trooper/russian/@SUBTYPES{@OLD} +/mob/living/basic/syndicate/@SUBTYPES : /mob/living/basic/trooper/syndicate/@SUBTYPES{@OLD} +/mob/living/simple_animal/hostile/nanotrasen/elite : /mob/living/basic/trooper/nanotrasen/ranged/elite{@OLD} +/mob/living/simple_animal/hostile/nanotrasen/@SUBTYPES : /mob/living/basic/trooper/nanotrasen/@SUBTYPES{@OLD} +/mob/living/simple_animal/hostile/retaliate/nanotrasenpeace : /mob/living/basic/trooper/nanotrasen/peaceful{@OLD} +/mob/living/simple_animal/hostile/retaliate/nanotrasenpeace/ranged : /mob/living/basic/trooper/nanotrasen/ranged/smg/peaceful{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/78918_simple_to_basic_gorilla.txt b/tools/UpdatePaths/Scripts/78918_simple_to_basic_gorilla.txt new file mode 100644 index 000000000000..a22b7848d351 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78918_simple_to_basic_gorilla.txt @@ -0,0 +1,2 @@ +/mob/living/simple_animal/hostile/gorilla/cargo_domestic : /mob/living/basic/gorilla/cargorilla{@OLD} +/mob/living/simple_animal/hostile/gorilla : /mob/living/basic/gorilla/@SUBTYPES{@OLD} diff --git a/tools/UpdatePaths/Scripts/78979_simple_to_basic_space_dragon.txt b/tools/UpdatePaths/Scripts/78979_simple_to_basic_space_dragon.txt new file mode 100644 index 000000000000..31210a5ea29f --- /dev/null +++ b/tools/UpdatePaths/Scripts/78979_simple_to_basic_space_dragon.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/space_dragon : /mob/living/basic/space_dragon/@SUBTYPES{@OLD} diff --git a/tools/UpdatePaths/Scripts/79015_simple_to_basic_construct_artificer.txt b/tools/UpdatePaths/Scripts/79015_simple_to_basic_construct_artificer.txt new file mode 100644 index 000000000000..554ed3fe1c22 --- /dev/null +++ b/tools/UpdatePaths/Scripts/79015_simple_to_basic_construct_artificer.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/construct/artificer/@SUBTYPES : /mob/living/basic/construct/artificer/@SUBTYPES{@OLD} diff --git a/tools/UpdatePaths/Scripts/79109_simple_to_basic_drone.txt b/tools/UpdatePaths/Scripts/79109_simple_to_basic_drone.txt new file mode 100644 index 000000000000..3dbac143c0a9 --- /dev/null +++ b/tools/UpdatePaths/Scripts/79109_simple_to_basic_drone.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/drone/@SUBTYPES : /mob/living/basic/drone/@SUBTYPES{@OLD} diff --git a/tools/UpdatePaths/Scripts/79187_simple_to_basic_traders.txt b/tools/UpdatePaths/Scripts/79187_simple_to_basic_traders.txt new file mode 100644 index 000000000000..77731b53c5d6 --- /dev/null +++ b/tools/UpdatePaths/Scripts/79187_simple_to_basic_traders.txt @@ -0,0 +1,2 @@ +/mob/living/simple_animal/hostile/retaliate/trader : /mob/living/basic/trader{@OLD} +/mob/living/simple_animal/hostile/retaliate/trader/mrbones : /mob/living/basic/trader/mrbones{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/79206_simple_to_basic_skeletons.txt b/tools/UpdatePaths/Scripts/79206_simple_to_basic_skeletons.txt new file mode 100644 index 000000000000..4bb4bcc50dd9 --- /dev/null +++ b/tools/UpdatePaths/Scripts/79206_simple_to_basic_skeletons.txt @@ -0,0 +1,2 @@ +/mob/living/simple_animal/hostile/skeleton/eskimo : /mob/living/basic/skeleton/settler{@OLD} +/mob/living/simple_animal/hostile/skeleton/@SUBTYPES : /mob/living/basic/skeleton/@SUBTYPES{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/79235_simple_to_basic_construct_wraith.txt b/tools/UpdatePaths/Scripts/79235_simple_to_basic_construct_wraith.txt new file mode 100644 index 000000000000..631f07688f9b --- /dev/null +++ b/tools/UpdatePaths/Scripts/79235_simple_to_basic_construct_wraith.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/construct/wraith/@SUBTYPES : /mob/living/basic/construct/wraith/@SUBTYPES{@OLD} diff --git a/tools/UpdatePaths/Scripts/79473_guardian_creator_repath.txt b/tools/UpdatePaths/Scripts/79473_guardian_creator_repath.txt new file mode 100644 index 000000000000..92f9e379d9a8 --- /dev/null +++ b/tools/UpdatePaths/Scripts/79473_guardian_creator_repath.txt @@ -0,0 +1,5 @@ +/obj/item/guardiancreator/choose/wizard : /obj/item/guardian_creator/wizard{@OLD} +/obj/item/guardiancreator/tech/@SUBTYPES : /obj/item/guardian_creator/tech/@SUBTYPES{@OLD} +/obj/item/guardiancreator/carp/@SUBTYPES : /obj/item/guardian_creator/carp/@SUBTYPES{@OLD} +/obj/item/guardian_creator/miner/@SUBTYPES : /obj/item/guardian_creator/miner/@SUBTYPES{@OLD} +/obj/item/guardiancreator/@SUBTYPES : /obj/item/guardian_creator/@SUBTYPES{@OLD} diff --git a/tools/UpdatePaths/Scripts/79476_simple_to_basic_wizard.txt b/tools/UpdatePaths/Scripts/79476_simple_to_basic_wizard.txt new file mode 100644 index 000000000000..55758f2bbdf3 --- /dev/null +++ b/tools/UpdatePaths/Scripts/79476_simple_to_basic_wizard.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/wizard : /mob/living/basic/wizard{@OLD} diff --git a/tools/UpdatePaths/Scripts/79508_gutlunchers.txt b/tools/UpdatePaths/Scripts/79508_gutlunchers.txt new file mode 100644 index 000000000000..f6a07058fb80 --- /dev/null +++ b/tools/UpdatePaths/Scripts/79508_gutlunchers.txt @@ -0,0 +1,5 @@ +/mob/living/simple_animal/hostile/asteroid/gutlunch : /mob/living/basic/mining/gutlunch{@OLD} +/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch : /mob/living/basic/mining/gutlunch/grub{@OLD} +/mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck : /mob/living/basic/mining/gutlunch/milk{@OLD} +/mob/living/simple_animal/hostile/asteroid/gutlunch/guthen : /mob/living/basic/mining/gutlunch/warrior{@OLD} + diff --git a/tools/UpdatePaths/Scripts/79508_ore_container.txt b/tools/UpdatePaths/Scripts/79508_ore_container.txt new file mode 100644 index 000000000000..d3851e0628cd --- /dev/null +++ b/tools/UpdatePaths/Scripts/79508_ore_container.txt @@ -0,0 +1 @@ +/obj/structure/material_stand : /obj/structure/ore_container/material_stand{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/79736_icemoon_wolf_basic.txt b/tools/UpdatePaths/Scripts/79736_icemoon_wolf_basic.txt new file mode 100644 index 000000000000..cb4497537ffb --- /dev/null +++ b/tools/UpdatePaths/Scripts/79736_icemoon_wolf_basic.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/asteroid/wolf : /mob/living/basic/mining/wolf{@OLD} diff --git a/tools/UpdatePaths/Scripts/79762_simple_to_basic_parrots.txt b/tools/UpdatePaths/Scripts/79762_simple_to_basic_parrots.txt new file mode 100644 index 000000000000..2f2e9c19a0f3 --- /dev/null +++ b/tools/UpdatePaths/Scripts/79762_simple_to_basic_parrots.txt @@ -0,0 +1,5 @@ +/mob/living/simple_animal/parrot : /mob/living/basic/parrot{@OLD} +/mob/living/simple_animal/parrot/natural : /mob/living/basic/parrot{@OLD} +/mob/living/simple_animal/parrot/poly : /mob/living/basic/parrot/poly{@OLD} + + diff --git a/tools/pull_request_hooks/tests/flakyTestPayloads/monkey_business.txt b/tools/pull_request_hooks/tests/flakyTestPayloads/monkey_business.txt index bc7cbc26924b..a52375357a07 100644 --- a/tools/pull_request_hooks/tests/flakyTestPayloads/monkey_business.txt +++ b/tools/pull_request_hooks/tests/flakyTestPayloads/monkey_business.txt @@ -168,12 +168,12 @@ 2022-11-01T15:23:05.7999555Z hint: Using 'master' as the name for the initial branch. This default branch name 2022-11-01T15:23:05.8000657Z hint: is subject to change. To configure the initial branch name to use in all 2022-11-01T15:23:05.8001229Z hint: of your new repositories, which will suppress this warning, call: -2022-11-01T15:23:05.8001635Z hint: +2022-11-01T15:23:05.8001635Z hint: 2022-11-01T15:23:05.8002546Z hint: git config --global init.defaultBranch -2022-11-01T15:23:05.8003017Z hint: +2022-11-01T15:23:05.8003017Z hint: 2022-11-01T15:23:05.8003562Z hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and 2022-11-01T15:23:05.8004498Z hint: 'development'. The just-created branch can be renamed via this command: -2022-11-01T15:23:05.8006177Z hint: +2022-11-01T15:23:05.8006177Z hint: 2022-11-01T15:23:05.8006532Z hint: git branch -m 2022-11-01T15:23:05.8016074Z Initialized empty Git repository in /home/runner/work/tgstation/tgstation/.git/ 2022-11-01T15:23:05.8029083Z [command]/usr/bin/git remote add origin https://github.com/tgstation/tgstation @@ -190,213 +190,213 @@ 2022-11-01T15:23:05.9154264Z ##[endgroup] 2022-11-01T15:23:05.9155507Z ##[group]Fetching the repository 2022-11-01T15:23:05.9167693Z [command]/usr/bin/git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +90d58213531368fd97e9955fe80b75ad69c20f24:refs/remotes/pull/70980/merge -2022-11-01T15:23:06.8635625Z remote: Enumerating objects: 12567, done. -2022-11-01T15:23:06.8636451Z remote: Counting objects: 0% (1/12567) -2022-11-01T15:23:06.8650219Z remote: Counting objects: 1% (126/12567) -2022-11-01T15:23:06.8660754Z remote: Counting objects: 2% (252/12567) -2022-11-01T15:23:06.8672560Z remote: Counting objects: 3% (378/12567) -2022-11-01T15:23:06.8672977Z remote: Counting objects: 4% (503/12567) -2022-11-01T15:23:06.8673376Z remote: Counting objects: 5% (629/12567) -2022-11-01T15:23:06.8675482Z remote: Counting objects: 6% (755/12567) -2022-11-01T15:23:06.8675791Z remote: Counting objects: 7% (880/12567) -2022-11-01T15:23:06.9160118Z remote: Counting objects: 8% (1006/12567) -2022-11-01T15:23:06.9160767Z remote: Counting objects: 9% (1132/12567) -2022-11-01T15:23:06.9161166Z remote: Counting objects: 10% (1257/12567) -2022-11-01T15:23:06.9161566Z remote: Counting objects: 11% (1383/12567) -2022-11-01T15:23:06.9161964Z remote: Counting objects: 12% (1509/12567) -2022-11-01T15:23:06.9162324Z remote: Counting objects: 13% (1634/12567) -2022-11-01T15:23:06.9162700Z remote: Counting objects: 14% (1760/12567) -2022-11-01T15:23:06.9163069Z remote: Counting objects: 15% (1886/12567) -2022-11-01T15:23:06.9163437Z remote: Counting objects: 16% (2011/12567) -2022-11-01T15:23:06.9163808Z remote: Counting objects: 17% (2137/12567) -2022-11-01T15:23:06.9164357Z remote: Counting objects: 18% (2263/12567) -2022-11-01T15:23:06.9165066Z remote: Counting objects: 19% (2388/12567) -2022-11-01T15:23:06.9165451Z remote: Counting objects: 20% (2514/12567) -2022-11-01T15:23:06.9165800Z remote: Counting objects: 21% (2640/12567) -2022-11-01T15:23:06.9166097Z remote: Counting objects: 22% (2765/12567) -2022-11-01T15:23:06.9166459Z remote: Counting objects: 23% (2891/12567) -2022-11-01T15:23:06.9166827Z remote: Counting objects: 24% (3017/12567) -2022-11-01T15:23:06.9167178Z remote: Counting objects: 25% (3142/12567) -2022-11-01T15:23:06.9167700Z remote: Counting objects: 26% (3268/12567) -2022-11-01T15:23:06.9168060Z remote: Counting objects: 27% (3394/12567) -2022-11-01T15:23:06.9168413Z remote: Counting objects: 28% (3519/12567) -2022-11-01T15:23:06.9168685Z remote: Counting objects: 29% (3645/12567) -2022-11-01T15:23:06.9169293Z remote: Counting objects: 30% (3771/12567) -2022-11-01T15:23:06.9169550Z remote: Counting objects: 31% (3896/12567) -2022-11-01T15:23:06.9169791Z remote: Counting objects: 32% (4022/12567) -2022-11-01T15:23:06.9170049Z remote: Counting objects: 33% (4148/12567) -2022-11-01T15:23:06.9170305Z remote: Counting objects: 34% (4273/12567) -2022-11-01T15:23:06.9170563Z remote: Counting objects: 35% (4399/12567) -2022-11-01T15:23:06.9170803Z remote: Counting objects: 36% (4525/12567) -2022-11-01T15:23:06.9171062Z remote: Counting objects: 37% (4650/12567) -2022-11-01T15:23:06.9171316Z remote: Counting objects: 38% (4776/12567) -2022-11-01T15:23:06.9171714Z remote: Counting objects: 39% (4902/12567) -2022-11-01T15:23:06.9171963Z remote: Counting objects: 40% (5027/12567) -2022-11-01T15:23:06.9172211Z remote: Counting objects: 41% (5153/12567) -2022-11-01T15:23:06.9172447Z remote: Counting objects: 42% (5279/12567) -2022-11-01T15:23:06.9172868Z remote: Counting objects: 43% (5404/12567) -2022-11-01T15:23:06.9173464Z remote: Counting objects: 44% (5530/12567) -2022-11-01T15:23:06.9173834Z remote: Counting objects: 45% (5656/12567) -2022-11-01T15:23:06.9174083Z remote: Counting objects: 46% (5781/12567) -2022-11-01T15:23:06.9174458Z remote: Counting objects: 47% (5907/12567) -2022-11-01T15:23:06.9174828Z remote: Counting objects: 48% (6033/12567) -2022-11-01T15:23:06.9175193Z remote: Counting objects: 49% (6158/12567) -2022-11-01T15:23:06.9175686Z remote: Counting objects: 50% (6284/12567) -2022-11-01T15:23:06.9176096Z remote: Counting objects: 51% (6410/12567) -2022-11-01T15:23:06.9176806Z remote: Counting objects: 52% (6535/12567) -2022-11-01T15:23:06.9177226Z remote: Counting objects: 53% (6661/12567) -2022-11-01T15:23:06.9383126Z remote: Counting objects: 54% (6787/12567) -2022-11-01T15:23:06.9383471Z remote: Counting objects: 55% (6912/12567) -2022-11-01T15:23:06.9383912Z remote: Counting objects: 56% (7038/12567) -2022-11-01T15:23:06.9384205Z remote: Counting objects: 57% (7164/12567) -2022-11-01T15:23:06.9384882Z remote: Counting objects: 58% (7289/12567) -2022-11-01T15:23:06.9385322Z remote: Counting objects: 59% (7415/12567) -2022-11-01T15:23:06.9385593Z remote: Counting objects: 60% (7541/12567) -2022-11-01T15:23:06.9386047Z remote: Counting objects: 61% (7666/12567) -2022-11-01T15:23:06.9386320Z remote: Counting objects: 62% (7792/12567) -2022-11-01T15:23:06.9386602Z remote: Counting objects: 63% (7918/12567) -2022-11-01T15:23:06.9387066Z remote: Counting objects: 64% (8043/12567) -2022-11-01T15:23:06.9387318Z remote: Counting objects: 65% (8169/12567) -2022-11-01T15:23:06.9387584Z remote: Counting objects: 66% (8295/12567) -2022-11-01T15:23:06.9387858Z remote: Counting objects: 67% (8420/12567) -2022-11-01T15:23:06.9388109Z remote: Counting objects: 68% (8546/12567) -2022-11-01T15:23:06.9388374Z remote: Counting objects: 69% (8672/12567) -2022-11-01T15:23:06.9388777Z remote: Counting objects: 70% (8797/12567) -2022-11-01T15:23:06.9389026Z remote: Counting objects: 71% (8923/12567) -2022-11-01T15:23:06.9389293Z remote: Counting objects: 72% (9049/12567) -2022-11-01T15:23:06.9389558Z remote: Counting objects: 73% (9174/12567) -2022-11-01T15:23:06.9389987Z remote: Counting objects: 74% (9300/12567) -2022-11-01T15:23:06.9390230Z remote: Counting objects: 75% (9426/12567) -2022-11-01T15:23:06.9390484Z remote: Counting objects: 76% (9551/12567) -2022-11-01T15:23:06.9390738Z remote: Counting objects: 77% (9677/12567) -2022-11-01T15:23:06.9390979Z remote: Counting objects: 78% (9803/12567) -2022-11-01T15:23:06.9391235Z remote: Counting objects: 79% (9928/12567) -2022-11-01T15:23:06.9391796Z remote: Counting objects: 80% (10054/12567) -2022-11-01T15:23:06.9392047Z remote: Counting objects: 81% (10180/12567) -2022-11-01T15:23:06.9392314Z remote: Counting objects: 82% (10305/12567) -2022-11-01T15:23:06.9392577Z remote: Counting objects: 83% (10431/12567) -2022-11-01T15:23:06.9392824Z remote: Counting objects: 84% (10557/12567) -2022-11-01T15:23:06.9393085Z remote: Counting objects: 85% (10682/12567) -2022-11-01T15:23:06.9394375Z remote: Counting objects: 86% (10808/12567) -2022-11-01T15:23:06.9394631Z remote: Counting objects: 87% (10934/12567) -2022-11-01T15:23:06.9394877Z remote: Counting objects: 88% (11059/12567) -2022-11-01T15:23:06.9395134Z remote: Counting objects: 89% (11185/12567) -2022-11-01T15:23:06.9395389Z remote: Counting objects: 90% (11311/12567) -2022-11-01T15:23:06.9395622Z remote: Counting objects: 91% (11436/12567) -2022-11-01T15:23:06.9395877Z remote: Counting objects: 92% (11562/12567) -2022-11-01T15:23:06.9396132Z remote: Counting objects: 93% (11688/12567) -2022-11-01T15:23:06.9396564Z remote: Counting objects: 94% (11813/12567) -2022-11-01T15:23:06.9396804Z remote: Counting objects: 95% (11939/12567) -2022-11-01T15:23:06.9397061Z remote: Counting objects: 96% (12065/12567) -2022-11-01T15:23:06.9397319Z remote: Counting objects: 97% (12190/12567) -2022-11-01T15:23:06.9397557Z remote: Counting objects: 98% (12316/12567) -2022-11-01T15:23:06.9397810Z remote: Counting objects: 99% (12442/12567) -2022-11-01T15:23:06.9398208Z remote: Counting objects: 100% (12567/12567) -2022-11-01T15:23:06.9398661Z remote: Counting objects: 100% (12567/12567), done. -2022-11-01T15:23:06.9398965Z remote: Compressing objects: 0% (1/10988) -2022-11-01T15:23:06.9399257Z remote: Compressing objects: 1% (110/10988) -2022-11-01T15:23:06.9439997Z remote: Compressing objects: 2% (220/10988) -2022-11-01T15:23:06.9561627Z remote: Compressing objects: 3% (330/10988) -2022-11-01T15:23:06.9680955Z remote: Compressing objects: 4% (440/10988) -2022-11-01T15:23:06.9723040Z remote: Compressing objects: 5% (550/10988) -2022-11-01T15:23:06.9876931Z remote: Compressing objects: 6% (660/10988) -2022-11-01T15:23:07.0170815Z remote: Compressing objects: 7% (770/10988) -2022-11-01T15:23:07.0504059Z remote: Compressing objects: 8% (880/10988) -2022-11-01T15:23:07.0836116Z remote: Compressing objects: 9% (989/10988) -2022-11-01T15:23:07.1460729Z remote: Compressing objects: 10% (1099/10988) -2022-11-01T15:23:07.6608683Z remote: Compressing objects: 11% (1209/10988) -2022-11-01T15:23:07.7588422Z remote: Compressing objects: 12% (1319/10988) -2022-11-01T15:23:07.9494647Z remote: Compressing objects: 13% (1429/10988) -2022-11-01T15:23:07.9527972Z remote: Compressing objects: 13% (1508/10988) -2022-11-01T15:23:07.9668498Z remote: Compressing objects: 14% (1539/10988) -2022-11-01T15:23:08.0219248Z remote: Compressing objects: 15% (1649/10988) -2022-11-01T15:23:08.0283852Z remote: Compressing objects: 16% (1759/10988) -2022-11-01T15:23:08.0452608Z remote: Compressing objects: 17% (1868/10988) -2022-11-01T15:23:08.0618448Z remote: Compressing objects: 18% (1978/10988) -2022-11-01T15:23:08.0889220Z remote: Compressing objects: 19% (2088/10988) -2022-11-01T15:23:08.1162946Z remote: Compressing objects: 20% (2198/10988) -2022-11-01T15:23:08.1247220Z remote: Compressing objects: 21% (2308/10988) -2022-11-01T15:23:08.1520626Z remote: Compressing objects: 22% (2418/10988) -2022-11-01T15:23:08.2139765Z remote: Compressing objects: 23% (2528/10988) -2022-11-01T15:23:08.2412924Z remote: Compressing objects: 24% (2638/10988) -2022-11-01T15:23:08.2597478Z remote: Compressing objects: 25% (2747/10988) -2022-11-01T15:23:08.2752211Z remote: Compressing objects: 26% (2857/10988) -2022-11-01T15:23:08.2966161Z remote: Compressing objects: 27% (2967/10988) -2022-11-01T15:23:08.3926822Z remote: Compressing objects: 28% (3077/10988) -2022-11-01T15:23:08.3927793Z remote: Compressing objects: 29% (3187/10988) -2022-11-01T15:23:08.4094739Z remote: Compressing objects: 30% (3297/10988) -2022-11-01T15:23:08.4338176Z remote: Compressing objects: 31% (3407/10988) -2022-11-01T15:23:08.4632502Z remote: Compressing objects: 32% (3517/10988) -2022-11-01T15:23:08.4940329Z remote: Compressing objects: 33% (3627/10988) -2022-11-01T15:23:08.5404129Z remote: Compressing objects: 34% (3736/10988) -2022-11-01T15:23:08.5814756Z remote: Compressing objects: 35% (3846/10988) -2022-11-01T15:23:08.6091092Z remote: Compressing objects: 36% (3956/10988) -2022-11-01T15:23:08.6446921Z remote: Compressing objects: 37% (4066/10988) -2022-11-01T15:23:08.6900873Z remote: Compressing objects: 38% (4176/10988) -2022-11-01T15:23:08.7213081Z remote: Compressing objects: 39% (4286/10988) -2022-11-01T15:23:08.7502723Z remote: Compressing objects: 40% (4396/10988) -2022-11-01T15:23:08.7869698Z remote: Compressing objects: 41% (4506/10988) -2022-11-01T15:23:08.8103626Z remote: Compressing objects: 42% (4615/10988) -2022-11-01T15:23:08.8451299Z remote: Compressing objects: 43% (4725/10988) -2022-11-01T15:23:08.8774688Z remote: Compressing objects: 44% (4835/10988) -2022-11-01T15:23:08.9003331Z remote: Compressing objects: 45% (4945/10988) -2022-11-01T15:23:08.9070180Z remote: Compressing objects: 45% (5034/10988) -2022-11-01T15:23:08.9344499Z remote: Compressing objects: 46% (5055/10988) -2022-11-01T15:23:08.9568190Z remote: Compressing objects: 47% (5165/10988) -2022-11-01T15:23:08.9805763Z remote: Compressing objects: 48% (5275/10988) -2022-11-01T15:23:09.0037568Z remote: Compressing objects: 49% (5385/10988) -2022-11-01T15:23:09.0301310Z remote: Compressing objects: 50% (5494/10988) -2022-11-01T15:23:09.0582321Z remote: Compressing objects: 51% (5604/10988) -2022-11-01T15:23:09.0808327Z remote: Compressing objects: 52% (5714/10988) -2022-11-01T15:23:09.1124129Z remote: Compressing objects: 53% (5824/10988) -2022-11-01T15:23:09.1387087Z remote: Compressing objects: 54% (5934/10988) -2022-11-01T15:23:09.1647455Z remote: Compressing objects: 55% (6044/10988) -2022-11-01T15:23:09.1915056Z remote: Compressing objects: 56% (6154/10988) -2022-11-01T15:23:09.2255320Z remote: Compressing objects: 57% (6264/10988) -2022-11-01T15:23:09.2457326Z remote: Compressing objects: 58% (6374/10988) -2022-11-01T15:23:09.2755910Z remote: Compressing objects: 59% (6483/10988) -2022-11-01T15:23:09.3017305Z remote: Compressing objects: 60% (6593/10988) -2022-11-01T15:23:09.3325069Z remote: Compressing objects: 61% (6703/10988) -2022-11-01T15:23:09.3546803Z remote: Compressing objects: 62% (6813/10988) -2022-11-01T15:23:09.3801684Z remote: Compressing objects: 63% (6923/10988) -2022-11-01T15:23:09.4067963Z remote: Compressing objects: 64% (7033/10988) -2022-11-01T15:23:09.5472387Z remote: Compressing objects: 65% (7143/10988) -2022-11-01T15:23:09.5473083Z remote: Compressing objects: 66% (7253/10988) -2022-11-01T15:23:09.5473536Z remote: Compressing objects: 67% (7362/10988) -2022-11-01T15:23:09.5473940Z remote: Compressing objects: 68% (7472/10988) -2022-11-01T15:23:09.5474375Z remote: Compressing objects: 69% (7582/10988) -2022-11-01T15:23:09.5474794Z remote: Compressing objects: 70% (7692/10988) -2022-11-01T15:23:09.5475212Z remote: Compressing objects: 71% (7802/10988) -2022-11-01T15:23:09.5475789Z remote: Compressing objects: 72% (7912/10988) -2022-11-01T15:23:09.5476209Z remote: Compressing objects: 73% (8022/10988) -2022-11-01T15:23:09.5477532Z remote: Compressing objects: 74% (8132/10988) -2022-11-01T15:23:09.5478233Z remote: Compressing objects: 75% (8241/10988) -2022-11-01T15:23:09.5478582Z remote: Compressing objects: 76% (8351/10988) -2022-11-01T15:23:09.5479163Z remote: Compressing objects: 77% (8461/10988) -2022-11-01T15:23:09.5479885Z remote: Compressing objects: 78% (8571/10988) -2022-11-01T15:23:09.5480434Z remote: Compressing objects: 79% (8681/10988) -2022-11-01T15:23:09.5480899Z remote: Compressing objects: 80% (8791/10988) -2022-11-01T15:23:09.5481594Z remote: Compressing objects: 81% (8901/10988) -2022-11-01T15:23:09.5482284Z remote: Compressing objects: 82% (9011/10988) -2022-11-01T15:23:09.5482865Z remote: Compressing objects: 83% (9121/10988) -2022-11-01T15:23:09.5483308Z remote: Compressing objects: 84% (9230/10988) -2022-11-01T15:23:09.5483685Z remote: Compressing objects: 85% (9340/10988) -2022-11-01T15:23:09.5484385Z remote: Compressing objects: 86% (9450/10988) -2022-11-01T15:23:09.5485582Z remote: Compressing objects: 87% (9560/10988) -2022-11-01T15:23:09.5486089Z remote: Compressing objects: 88% (9670/10988) -2022-11-01T15:23:09.5486597Z remote: Compressing objects: 89% (9780/10988) -2022-11-01T15:23:09.5487116Z remote: Compressing objects: 90% (9890/10988) -2022-11-01T15:23:09.5487742Z remote: Compressing objects: 91% (10000/10988) -2022-11-01T15:23:09.5488235Z remote: Compressing objects: 92% (10109/10988) -2022-11-01T15:23:09.5488673Z remote: Compressing objects: 93% (10219/10988) -2022-11-01T15:23:09.5489383Z remote: Compressing objects: 94% (10329/10988) -2022-11-01T15:23:09.5489745Z remote: Compressing objects: 95% (10439/10988) -2022-11-01T15:23:09.5490195Z remote: Compressing objects: 96% (10549/10988) -2022-11-01T15:23:09.5490745Z remote: Compressing objects: 97% (10659/10988) -2022-11-01T15:23:09.5491127Z remote: Compressing objects: 98% (10769/10988) -2022-11-01T15:23:09.5491596Z remote: Compressing objects: 99% (10879/10988) -2022-11-01T15:23:09.5492106Z remote: Compressing objects: 100% (10988/10988) -2022-11-01T15:23:09.5492683Z remote: Compressing objects: 100% (10988/10988), done. +2022-11-01T15:23:06.8635625Z remote: Enumerating objects: 12567, done. +2022-11-01T15:23:06.8636451Z remote: Counting objects: 0% (1/12567) +2022-11-01T15:23:06.8650219Z remote: Counting objects: 1% (126/12567) +2022-11-01T15:23:06.8660754Z remote: Counting objects: 2% (252/12567) +2022-11-01T15:23:06.8672560Z remote: Counting objects: 3% (378/12567) +2022-11-01T15:23:06.8672977Z remote: Counting objects: 4% (503/12567) +2022-11-01T15:23:06.8673376Z remote: Counting objects: 5% (629/12567) +2022-11-01T15:23:06.8675482Z remote: Counting objects: 6% (755/12567) +2022-11-01T15:23:06.8675791Z remote: Counting objects: 7% (880/12567) +2022-11-01T15:23:06.9160118Z remote: Counting objects: 8% (1006/12567) +2022-11-01T15:23:06.9160767Z remote: Counting objects: 9% (1132/12567) +2022-11-01T15:23:06.9161166Z remote: Counting objects: 10% (1257/12567) +2022-11-01T15:23:06.9161566Z remote: Counting objects: 11% (1383/12567) +2022-11-01T15:23:06.9161964Z remote: Counting objects: 12% (1509/12567) +2022-11-01T15:23:06.9162324Z remote: Counting objects: 13% (1634/12567) +2022-11-01T15:23:06.9162700Z remote: Counting objects: 14% (1760/12567) +2022-11-01T15:23:06.9163069Z remote: Counting objects: 15% (1886/12567) +2022-11-01T15:23:06.9163437Z remote: Counting objects: 16% (2011/12567) +2022-11-01T15:23:06.9163808Z remote: Counting objects: 17% (2137/12567) +2022-11-01T15:23:06.9164357Z remote: Counting objects: 18% (2263/12567) +2022-11-01T15:23:06.9165066Z remote: Counting objects: 19% (2388/12567) +2022-11-01T15:23:06.9165451Z remote: Counting objects: 20% (2514/12567) +2022-11-01T15:23:06.9165800Z remote: Counting objects: 21% (2640/12567) +2022-11-01T15:23:06.9166097Z remote: Counting objects: 22% (2765/12567) +2022-11-01T15:23:06.9166459Z remote: Counting objects: 23% (2891/12567) +2022-11-01T15:23:06.9166827Z remote: Counting objects: 24% (3017/12567) +2022-11-01T15:23:06.9167178Z remote: Counting objects: 25% (3142/12567) +2022-11-01T15:23:06.9167700Z remote: Counting objects: 26% (3268/12567) +2022-11-01T15:23:06.9168060Z remote: Counting objects: 27% (3394/12567) +2022-11-01T15:23:06.9168413Z remote: Counting objects: 28% (3519/12567) +2022-11-01T15:23:06.9168685Z remote: Counting objects: 29% (3645/12567) +2022-11-01T15:23:06.9169293Z remote: Counting objects: 30% (3771/12567) +2022-11-01T15:23:06.9169550Z remote: Counting objects: 31% (3896/12567) +2022-11-01T15:23:06.9169791Z remote: Counting objects: 32% (4022/12567) +2022-11-01T15:23:06.9170049Z remote: Counting objects: 33% (4148/12567) +2022-11-01T15:23:06.9170305Z remote: Counting objects: 34% (4273/12567) +2022-11-01T15:23:06.9170563Z remote: Counting objects: 35% (4399/12567) +2022-11-01T15:23:06.9170803Z remote: Counting objects: 36% (4525/12567) +2022-11-01T15:23:06.9171062Z remote: Counting objects: 37% (4650/12567) +2022-11-01T15:23:06.9171316Z remote: Counting objects: 38% (4776/12567) +2022-11-01T15:23:06.9171714Z remote: Counting objects: 39% (4902/12567) +2022-11-01T15:23:06.9171963Z remote: Counting objects: 40% (5027/12567) +2022-11-01T15:23:06.9172211Z remote: Counting objects: 41% (5153/12567) +2022-11-01T15:23:06.9172447Z remote: Counting objects: 42% (5279/12567) +2022-11-01T15:23:06.9172868Z remote: Counting objects: 43% (5404/12567) +2022-11-01T15:23:06.9173464Z remote: Counting objects: 44% (5530/12567) +2022-11-01T15:23:06.9173834Z remote: Counting objects: 45% (5656/12567) +2022-11-01T15:23:06.9174083Z remote: Counting objects: 46% (5781/12567) +2022-11-01T15:23:06.9174458Z remote: Counting objects: 47% (5907/12567) +2022-11-01T15:23:06.9174828Z remote: Counting objects: 48% (6033/12567) +2022-11-01T15:23:06.9175193Z remote: Counting objects: 49% (6158/12567) +2022-11-01T15:23:06.9175686Z remote: Counting objects: 50% (6284/12567) +2022-11-01T15:23:06.9176096Z remote: Counting objects: 51% (6410/12567) +2022-11-01T15:23:06.9176806Z remote: Counting objects: 52% (6535/12567) +2022-11-01T15:23:06.9177226Z remote: Counting objects: 53% (6661/12567) +2022-11-01T15:23:06.9383126Z remote: Counting objects: 54% (6787/12567) +2022-11-01T15:23:06.9383471Z remote: Counting objects: 55% (6912/12567) +2022-11-01T15:23:06.9383912Z remote: Counting objects: 56% (7038/12567) +2022-11-01T15:23:06.9384205Z remote: Counting objects: 57% (7164/12567) +2022-11-01T15:23:06.9384882Z remote: Counting objects: 58% (7289/12567) +2022-11-01T15:23:06.9385322Z remote: Counting objects: 59% (7415/12567) +2022-11-01T15:23:06.9385593Z remote: Counting objects: 60% (7541/12567) +2022-11-01T15:23:06.9386047Z remote: Counting objects: 61% (7666/12567) +2022-11-01T15:23:06.9386320Z remote: Counting objects: 62% (7792/12567) +2022-11-01T15:23:06.9386602Z remote: Counting objects: 63% (7918/12567) +2022-11-01T15:23:06.9387066Z remote: Counting objects: 64% (8043/12567) +2022-11-01T15:23:06.9387318Z remote: Counting objects: 65% (8169/12567) +2022-11-01T15:23:06.9387584Z remote: Counting objects: 66% (8295/12567) +2022-11-01T15:23:06.9387858Z remote: Counting objects: 67% (8420/12567) +2022-11-01T15:23:06.9388109Z remote: Counting objects: 68% (8546/12567) +2022-11-01T15:23:06.9388374Z remote: Counting objects: 69% (8672/12567) +2022-11-01T15:23:06.9388777Z remote: Counting objects: 70% (8797/12567) +2022-11-01T15:23:06.9389026Z remote: Counting objects: 71% (8923/12567) +2022-11-01T15:23:06.9389293Z remote: Counting objects: 72% (9049/12567) +2022-11-01T15:23:06.9389558Z remote: Counting objects: 73% (9174/12567) +2022-11-01T15:23:06.9389987Z remote: Counting objects: 74% (9300/12567) +2022-11-01T15:23:06.9390230Z remote: Counting objects: 75% (9426/12567) +2022-11-01T15:23:06.9390484Z remote: Counting objects: 76% (9551/12567) +2022-11-01T15:23:06.9390738Z remote: Counting objects: 77% (9677/12567) +2022-11-01T15:23:06.9390979Z remote: Counting objects: 78% (9803/12567) +2022-11-01T15:23:06.9391235Z remote: Counting objects: 79% (9928/12567) +2022-11-01T15:23:06.9391796Z remote: Counting objects: 80% (10054/12567) +2022-11-01T15:23:06.9392047Z remote: Counting objects: 81% (10180/12567) +2022-11-01T15:23:06.9392314Z remote: Counting objects: 82% (10305/12567) +2022-11-01T15:23:06.9392577Z remote: Counting objects: 83% (10431/12567) +2022-11-01T15:23:06.9392824Z remote: Counting objects: 84% (10557/12567) +2022-11-01T15:23:06.9393085Z remote: Counting objects: 85% (10682/12567) +2022-11-01T15:23:06.9394375Z remote: Counting objects: 86% (10808/12567) +2022-11-01T15:23:06.9394631Z remote: Counting objects: 87% (10934/12567) +2022-11-01T15:23:06.9394877Z remote: Counting objects: 88% (11059/12567) +2022-11-01T15:23:06.9395134Z remote: Counting objects: 89% (11185/12567) +2022-11-01T15:23:06.9395389Z remote: Counting objects: 90% (11311/12567) +2022-11-01T15:23:06.9395622Z remote: Counting objects: 91% (11436/12567) +2022-11-01T15:23:06.9395877Z remote: Counting objects: 92% (11562/12567) +2022-11-01T15:23:06.9396132Z remote: Counting objects: 93% (11688/12567) +2022-11-01T15:23:06.9396564Z remote: Counting objects: 94% (11813/12567) +2022-11-01T15:23:06.9396804Z remote: Counting objects: 95% (11939/12567) +2022-11-01T15:23:06.9397061Z remote: Counting objects: 96% (12065/12567) +2022-11-01T15:23:06.9397319Z remote: Counting objects: 97% (12190/12567) +2022-11-01T15:23:06.9397557Z remote: Counting objects: 98% (12316/12567) +2022-11-01T15:23:06.9397810Z remote: Counting objects: 99% (12442/12567) +2022-11-01T15:23:06.9398208Z remote: Counting objects: 100% (12567/12567) +2022-11-01T15:23:06.9398661Z remote: Counting objects: 100% (12567/12567), done. +2022-11-01T15:23:06.9398965Z remote: Compressing objects: 0% (1/10988) +2022-11-01T15:23:06.9399257Z remote: Compressing objects: 1% (110/10988) +2022-11-01T15:23:06.9439997Z remote: Compressing objects: 2% (220/10988) +2022-11-01T15:23:06.9561627Z remote: Compressing objects: 3% (330/10988) +2022-11-01T15:23:06.9680955Z remote: Compressing objects: 4% (440/10988) +2022-11-01T15:23:06.9723040Z remote: Compressing objects: 5% (550/10988) +2022-11-01T15:23:06.9876931Z remote: Compressing objects: 6% (660/10988) +2022-11-01T15:23:07.0170815Z remote: Compressing objects: 7% (770/10988) +2022-11-01T15:23:07.0504059Z remote: Compressing objects: 8% (880/10988) +2022-11-01T15:23:07.0836116Z remote: Compressing objects: 9% (989/10988) +2022-11-01T15:23:07.1460729Z remote: Compressing objects: 10% (1099/10988) +2022-11-01T15:23:07.6608683Z remote: Compressing objects: 11% (1209/10988) +2022-11-01T15:23:07.7588422Z remote: Compressing objects: 12% (1319/10988) +2022-11-01T15:23:07.9494647Z remote: Compressing objects: 13% (1429/10988) +2022-11-01T15:23:07.9527972Z remote: Compressing objects: 13% (1508/10988) +2022-11-01T15:23:07.9668498Z remote: Compressing objects: 14% (1539/10988) +2022-11-01T15:23:08.0219248Z remote: Compressing objects: 15% (1649/10988) +2022-11-01T15:23:08.0283852Z remote: Compressing objects: 16% (1759/10988) +2022-11-01T15:23:08.0452608Z remote: Compressing objects: 17% (1868/10988) +2022-11-01T15:23:08.0618448Z remote: Compressing objects: 18% (1978/10988) +2022-11-01T15:23:08.0889220Z remote: Compressing objects: 19% (2088/10988) +2022-11-01T15:23:08.1162946Z remote: Compressing objects: 20% (2198/10988) +2022-11-01T15:23:08.1247220Z remote: Compressing objects: 21% (2308/10988) +2022-11-01T15:23:08.1520626Z remote: Compressing objects: 22% (2418/10988) +2022-11-01T15:23:08.2139765Z remote: Compressing objects: 23% (2528/10988) +2022-11-01T15:23:08.2412924Z remote: Compressing objects: 24% (2638/10988) +2022-11-01T15:23:08.2597478Z remote: Compressing objects: 25% (2747/10988) +2022-11-01T15:23:08.2752211Z remote: Compressing objects: 26% (2857/10988) +2022-11-01T15:23:08.2966161Z remote: Compressing objects: 27% (2967/10988) +2022-11-01T15:23:08.3926822Z remote: Compressing objects: 28% (3077/10988) +2022-11-01T15:23:08.3927793Z remote: Compressing objects: 29% (3187/10988) +2022-11-01T15:23:08.4094739Z remote: Compressing objects: 30% (3297/10988) +2022-11-01T15:23:08.4338176Z remote: Compressing objects: 31% (3407/10988) +2022-11-01T15:23:08.4632502Z remote: Compressing objects: 32% (3517/10988) +2022-11-01T15:23:08.4940329Z remote: Compressing objects: 33% (3627/10988) +2022-11-01T15:23:08.5404129Z remote: Compressing objects: 34% (3736/10988) +2022-11-01T15:23:08.5814756Z remote: Compressing objects: 35% (3846/10988) +2022-11-01T15:23:08.6091092Z remote: Compressing objects: 36% (3956/10988) +2022-11-01T15:23:08.6446921Z remote: Compressing objects: 37% (4066/10988) +2022-11-01T15:23:08.6900873Z remote: Compressing objects: 38% (4176/10988) +2022-11-01T15:23:08.7213081Z remote: Compressing objects: 39% (4286/10988) +2022-11-01T15:23:08.7502723Z remote: Compressing objects: 40% (4396/10988) +2022-11-01T15:23:08.7869698Z remote: Compressing objects: 41% (4506/10988) +2022-11-01T15:23:08.8103626Z remote: Compressing objects: 42% (4615/10988) +2022-11-01T15:23:08.8451299Z remote: Compressing objects: 43% (4725/10988) +2022-11-01T15:23:08.8774688Z remote: Compressing objects: 44% (4835/10988) +2022-11-01T15:23:08.9003331Z remote: Compressing objects: 45% (4945/10988) +2022-11-01T15:23:08.9070180Z remote: Compressing objects: 45% (5034/10988) +2022-11-01T15:23:08.9344499Z remote: Compressing objects: 46% (5055/10988) +2022-11-01T15:23:08.9568190Z remote: Compressing objects: 47% (5165/10988) +2022-11-01T15:23:08.9805763Z remote: Compressing objects: 48% (5275/10988) +2022-11-01T15:23:09.0037568Z remote: Compressing objects: 49% (5385/10988) +2022-11-01T15:23:09.0301310Z remote: Compressing objects: 50% (5494/10988) +2022-11-01T15:23:09.0582321Z remote: Compressing objects: 51% (5604/10988) +2022-11-01T15:23:09.0808327Z remote: Compressing objects: 52% (5714/10988) +2022-11-01T15:23:09.1124129Z remote: Compressing objects: 53% (5824/10988) +2022-11-01T15:23:09.1387087Z remote: Compressing objects: 54% (5934/10988) +2022-11-01T15:23:09.1647455Z remote: Compressing objects: 55% (6044/10988) +2022-11-01T15:23:09.1915056Z remote: Compressing objects: 56% (6154/10988) +2022-11-01T15:23:09.2255320Z remote: Compressing objects: 57% (6264/10988) +2022-11-01T15:23:09.2457326Z remote: Compressing objects: 58% (6374/10988) +2022-11-01T15:23:09.2755910Z remote: Compressing objects: 59% (6483/10988) +2022-11-01T15:23:09.3017305Z remote: Compressing objects: 60% (6593/10988) +2022-11-01T15:23:09.3325069Z remote: Compressing objects: 61% (6703/10988) +2022-11-01T15:23:09.3546803Z remote: Compressing objects: 62% (6813/10988) +2022-11-01T15:23:09.3801684Z remote: Compressing objects: 63% (6923/10988) +2022-11-01T15:23:09.4067963Z remote: Compressing objects: 64% (7033/10988) +2022-11-01T15:23:09.5472387Z remote: Compressing objects: 65% (7143/10988) +2022-11-01T15:23:09.5473083Z remote: Compressing objects: 66% (7253/10988) +2022-11-01T15:23:09.5473536Z remote: Compressing objects: 67% (7362/10988) +2022-11-01T15:23:09.5473940Z remote: Compressing objects: 68% (7472/10988) +2022-11-01T15:23:09.5474375Z remote: Compressing objects: 69% (7582/10988) +2022-11-01T15:23:09.5474794Z remote: Compressing objects: 70% (7692/10988) +2022-11-01T15:23:09.5475212Z remote: Compressing objects: 71% (7802/10988) +2022-11-01T15:23:09.5475789Z remote: Compressing objects: 72% (7912/10988) +2022-11-01T15:23:09.5476209Z remote: Compressing objects: 73% (8022/10988) +2022-11-01T15:23:09.5477532Z remote: Compressing objects: 74% (8132/10988) +2022-11-01T15:23:09.5478233Z remote: Compressing objects: 75% (8241/10988) +2022-11-01T15:23:09.5478582Z remote: Compressing objects: 76% (8351/10988) +2022-11-01T15:23:09.5479163Z remote: Compressing objects: 77% (8461/10988) +2022-11-01T15:23:09.5479885Z remote: Compressing objects: 78% (8571/10988) +2022-11-01T15:23:09.5480434Z remote: Compressing objects: 79% (8681/10988) +2022-11-01T15:23:09.5480899Z remote: Compressing objects: 80% (8791/10988) +2022-11-01T15:23:09.5481594Z remote: Compressing objects: 81% (8901/10988) +2022-11-01T15:23:09.5482284Z remote: Compressing objects: 82% (9011/10988) +2022-11-01T15:23:09.5482865Z remote: Compressing objects: 83% (9121/10988) +2022-11-01T15:23:09.5483308Z remote: Compressing objects: 84% (9230/10988) +2022-11-01T15:23:09.5483685Z remote: Compressing objects: 85% (9340/10988) +2022-11-01T15:23:09.5484385Z remote: Compressing objects: 86% (9450/10988) +2022-11-01T15:23:09.5485582Z remote: Compressing objects: 87% (9560/10988) +2022-11-01T15:23:09.5486089Z remote: Compressing objects: 88% (9670/10988) +2022-11-01T15:23:09.5486597Z remote: Compressing objects: 89% (9780/10988) +2022-11-01T15:23:09.5487116Z remote: Compressing objects: 90% (9890/10988) +2022-11-01T15:23:09.5487742Z remote: Compressing objects: 91% (10000/10988) +2022-11-01T15:23:09.5488235Z remote: Compressing objects: 92% (10109/10988) +2022-11-01T15:23:09.5488673Z remote: Compressing objects: 93% (10219/10988) +2022-11-01T15:23:09.5489383Z remote: Compressing objects: 94% (10329/10988) +2022-11-01T15:23:09.5489745Z remote: Compressing objects: 95% (10439/10988) +2022-11-01T15:23:09.5490195Z remote: Compressing objects: 96% (10549/10988) +2022-11-01T15:23:09.5490745Z remote: Compressing objects: 97% (10659/10988) +2022-11-01T15:23:09.5491127Z remote: Compressing objects: 98% (10769/10988) +2022-11-01T15:23:09.5491596Z remote: Compressing objects: 99% (10879/10988) +2022-11-01T15:23:09.5492106Z remote: Compressing objects: 100% (10988/10988) +2022-11-01T15:23:09.5492683Z remote: Compressing objects: 100% (10988/10988), done. 2022-11-01T15:23:09.8456233Z Receiving objects: 0% (1/12567) 2022-11-01T15:23:10.1991946Z Receiving objects: 1% (126/12567) 2022-11-01T15:23:10.2129001Z Receiving objects: 2% (252/12567), 1.64 MiB | 3.13 MiB/s @@ -505,7 +505,7 @@ 2022-11-01T15:23:18.1101897Z Receiving objects: 97% (12190/12567), 131.07 MiB | 18.99 MiB/s 2022-11-01T15:23:18.1271902Z Receiving objects: 98% (12316/12567), 131.07 MiB | 18.99 MiB/s 2022-11-01T15:23:18.1310792Z Receiving objects: 99% (12442/12567), 131.07 MiB | 18.99 MiB/s -2022-11-01T15:23:18.1311930Z remote: Total 12567 (delta 1595), reused 7195 (delta 1446), pack-reused 0 +2022-11-01T15:23:18.1311930Z remote: Total 12567 (delta 1595), reused 7195 (delta 1446), pack-reused 0 2022-11-01T15:23:18.1338739Z Receiving objects: 100% (12567/12567), 131.07 MiB | 18.99 MiB/s 2022-11-01T15:23:18.1339954Z Receiving objects: 100% (12567/12567), 139.23 MiB | 16.22 MiB/s, done. 2022-11-01T15:23:18.1385156Z Resolving deltas: 0% (0/1595) @@ -657,22 +657,22 @@ 2022-11-01T15:23:20.3892548Z Updating files: 100% (11595/11595) 2022-11-01T15:23:20.3892853Z Updating files: 100% (11595/11595), done. 2022-11-01T15:23:20.4056043Z Note: switching to 'refs/remotes/pull/70980/merge'. -2022-11-01T15:23:20.4056307Z +2022-11-01T15:23:20.4056307Z 2022-11-01T15:23:20.4056668Z You are in 'detached HEAD' state. You can look around, make experimental 2022-11-01T15:23:20.4057485Z changes and commit them, and you can discard any commits you make in this 2022-11-01T15:23:20.4058470Z state without impacting any branches by switching back to a branch. -2022-11-01T15:23:20.4058838Z +2022-11-01T15:23:20.4058838Z 2022-11-01T15:23:20.4059084Z If you want to create a new branch to retain commits you create, you may 2022-11-01T15:23:20.4060410Z do so (now or later) by using -c with the switch command. Example: -2022-11-01T15:23:20.4060709Z +2022-11-01T15:23:20.4060709Z 2022-11-01T15:23:20.4061092Z git switch -c -2022-11-01T15:23:20.4061268Z +2022-11-01T15:23:20.4061268Z 2022-11-01T15:23:20.4061433Z Or undo this operation with: -2022-11-01T15:23:20.4061641Z +2022-11-01T15:23:20.4061641Z 2022-11-01T15:23:20.4061781Z git switch - -2022-11-01T15:23:20.4061964Z +2022-11-01T15:23:20.4061964Z 2022-11-01T15:23:20.4062222Z Turn off this advice by setting config variable advice.detachedHead to false -2022-11-01T15:23:20.4062504Z +2022-11-01T15:23:20.4062504Z 2022-11-01T15:23:20.4062736Z HEAD is now at 90d5821 Merge 1cb3ad143b2bd2b6467c31b7f52299c77448f1ee into 6ccb95a4ea337422d5d29cd85f5267e4c867ccff 2022-11-01T15:23:20.4116386Z ##[endgroup] 2022-11-01T15:23:20.4158288Z [command]/usr/bin/git log -1 --format='%H' @@ -707,9 +707,9 @@ 2022-11-01T15:23:29.0146860Z bash tools/ci/install_rust_g.sh 2022-11-01T15:23:29.0208114Z shell: /usr/bin/bash -e {0} 2022-11-01T15:23:29.0208371Z ##[endgroup] -2022-11-01T15:23:29.3467028Z +2022-11-01T15:23:29.3467028Z 2022-11-01T15:23:29.3467957Z WARNING: apt does not have a stable CLI interface. Use with caution in scripts. -2022-11-01T15:23:29.3470221Z +2022-11-01T15:23:29.3470221Z 2022-11-01T15:23:29.4665471Z Hit:1 http://azure.archive.ubuntu.com/ubuntu focal InRelease 2022-11-01T15:23:29.4669512Z Get:2 http://azure.archive.ubuntu.com/ubuntu focal-updates InRelease [114 kB] 2022-11-01T15:23:29.4687773Z Get:3 http://azure.archive.ubuntu.com/ubuntu focal-backports InRelease [108 kB] @@ -735,9 +735,9 @@ 2022-11-01T15:23:48.4655905Z Building dependency tree... 2022-11-01T15:23:48.4674811Z Reading state information... 2022-11-01T15:23:48.6158045Z 30 packages can be upgraded. Run 'apt list --upgradable' to see them. -2022-11-01T15:23:48.6289670Z +2022-11-01T15:23:48.6289670Z 2022-11-01T15:23:48.6290570Z WARNING: apt does not have a stable CLI interface. Use with caution in scripts. -2022-11-01T15:23:48.6290875Z +2022-11-01T15:23:48.6290875Z 2022-11-01T15:23:48.7009344Z Reading package lists... 2022-11-01T15:23:48.9786347Z Building dependency tree... 2022-11-01T15:23:48.9806498Z Reading state information... @@ -765,7 +765,7 @@ 2022-11-01T15:23:51.2386768Z Preconfiguring packages ... 2022-11-01T15:23:51.3495462Z Fetched 4528 kB in 1s (3176 kB/s) 2022-11-01T15:23:51.3919752Z Selecting previously unselected package gcc-11-base:i386. -2022-11-01T15:23:51.4344862Z (Reading database ... +2022-11-01T15:23:51.4344862Z (Reading database ... 2022-11-01T15:23:51.4345195Z (Reading database ... 5% 2022-11-01T15:23:51.4345492Z (Reading database ... 10% 2022-11-01T15:23:51.4346277Z (Reading database ... 15% @@ -852,98 +852,98 @@ 2022-11-01T15:24:20.0904985Z Setting up BYOND. 2022-11-01T15:24:20.1285049Z % Total % Received % Xferd Average Speed Time Time Time Current 2022-11-01T15:24:20.1288558Z Dload Upload Total Spent Left Speed -2022-11-01T15:24:20.1289906Z +2022-11-01T15:24:20.1289906Z 2022-11-01T15:24:20.2648629Z 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 2022-11-01T15:24:20.2651476Z 100 4021k 100 4021k 0 0 28.8M 0 --:--:-- --:--:-- --:--:-- 28.8M 2022-11-01T15:24:20.2868719Z Archive: byond.zip 2022-11-01T15:24:20.2869523Z creating: byond/ 2022-11-01T15:24:20.2872392Z creating: byond/key/ 2022-11-01T15:24:20.2872809Z creating: byond/web/ -2022-11-01T15:24:20.2873278Z inflating: byond/web/child.dms -2022-11-01T15:24:20.2873868Z inflating: byond/web/button.dms -2022-11-01T15:24:20.2874291Z inflating: byond/web/input.dms -2022-11-01T15:24:20.2874708Z inflating: byond/web/text.dms -2022-11-01T15:24:20.2990682Z inflating: byond/web/webclient.dart.js -2022-11-01T15:24:20.2991338Z inflating: byond/web/verbmenu.dms -2022-11-01T15:24:20.2992247Z inflating: byond/web/defaultSkin.dms -2022-11-01T15:24:20.2994907Z inflating: byond/web/hotbar.dms -2022-11-01T15:24:20.2995370Z inflating: byond/web/label.dms -2022-11-01T15:24:20.2997432Z inflating: byond/web/alert.dms -2022-11-01T15:24:20.3045767Z inflating: byond/web/message.dms -2022-11-01T15:24:20.3046084Z inflating: byond/web/drag.png -2022-11-01T15:24:20.3046357Z inflating: byond/web/map.dms -2022-11-01T15:24:20.3046639Z inflating: byond/web/splashlogo.png -2022-11-01T15:24:20.3046920Z inflating: byond/web/drop.png -2022-11-01T15:24:20.3109752Z inflating: byond/web/ext.js -2022-11-01T15:24:20.3110039Z inflating: byond/web/file.dms -2022-11-01T15:24:20.3110743Z inflating: byond/web/grid.dms -2022-11-01T15:24:20.3112568Z inflating: byond/web/bar.dms -2022-11-01T15:24:20.3116279Z inflating: byond/web/dpad.dms -2022-11-01T15:24:20.3117768Z inflating: byond/web/output.dms -2022-11-01T15:24:20.3118380Z inflating: byond/web/tab.dms -2022-11-01T15:24:20.3121184Z inflating: byond/web/info.dms -2022-11-01T15:24:20.3125342Z inflating: byond/web/color.dms -2022-11-01T15:24:20.3127736Z inflating: byond/web/gamepad.dms -2022-11-01T15:24:20.3129918Z inflating: byond/web/browser.dms -2022-11-01T15:24:20.3130867Z inflating: byond/web/status.dms -2022-11-01T15:24:20.3131904Z inflating: byond/web/any.dms -2022-11-01T15:24:20.3133198Z inflating: byond/web/pane.dms -2022-11-01T15:24:20.3134980Z inflating: byond/web/pop.dms -2022-11-01T15:24:20.3136094Z inflating: byond/license.txt -2022-11-01T15:24:20.3136925Z inflating: byond/legal.txt -2022-11-01T15:24:20.3138018Z inflating: byond/Makefile +2022-11-01T15:24:20.2873278Z inflating: byond/web/child.dms +2022-11-01T15:24:20.2873868Z inflating: byond/web/button.dms +2022-11-01T15:24:20.2874291Z inflating: byond/web/input.dms +2022-11-01T15:24:20.2874708Z inflating: byond/web/text.dms +2022-11-01T15:24:20.2990682Z inflating: byond/web/webclient.dart.js +2022-11-01T15:24:20.2991338Z inflating: byond/web/verbmenu.dms +2022-11-01T15:24:20.2992247Z inflating: byond/web/defaultSkin.dms +2022-11-01T15:24:20.2994907Z inflating: byond/web/hotbar.dms +2022-11-01T15:24:20.2995370Z inflating: byond/web/label.dms +2022-11-01T15:24:20.2997432Z inflating: byond/web/alert.dms +2022-11-01T15:24:20.3045767Z inflating: byond/web/message.dms +2022-11-01T15:24:20.3046084Z inflating: byond/web/drag.png +2022-11-01T15:24:20.3046357Z inflating: byond/web/map.dms +2022-11-01T15:24:20.3046639Z inflating: byond/web/splashlogo.png +2022-11-01T15:24:20.3046920Z inflating: byond/web/drop.png +2022-11-01T15:24:20.3109752Z inflating: byond/web/ext.js +2022-11-01T15:24:20.3110039Z inflating: byond/web/file.dms +2022-11-01T15:24:20.3110743Z inflating: byond/web/grid.dms +2022-11-01T15:24:20.3112568Z inflating: byond/web/bar.dms +2022-11-01T15:24:20.3116279Z inflating: byond/web/dpad.dms +2022-11-01T15:24:20.3117768Z inflating: byond/web/output.dms +2022-11-01T15:24:20.3118380Z inflating: byond/web/tab.dms +2022-11-01T15:24:20.3121184Z inflating: byond/web/info.dms +2022-11-01T15:24:20.3125342Z inflating: byond/web/color.dms +2022-11-01T15:24:20.3127736Z inflating: byond/web/gamepad.dms +2022-11-01T15:24:20.3129918Z inflating: byond/web/browser.dms +2022-11-01T15:24:20.3130867Z inflating: byond/web/status.dms +2022-11-01T15:24:20.3131904Z inflating: byond/web/any.dms +2022-11-01T15:24:20.3133198Z inflating: byond/web/pane.dms +2022-11-01T15:24:20.3134980Z inflating: byond/web/pop.dms +2022-11-01T15:24:20.3136094Z inflating: byond/license.txt +2022-11-01T15:24:20.3136925Z inflating: byond/legal.txt +2022-11-01T15:24:20.3138018Z inflating: byond/Makefile 2022-11-01T15:24:20.3138748Z creating: byond/man/ 2022-11-01T15:24:20.3140374Z creating: byond/man/man6/ -2022-11-01T15:24:20.3140865Z inflating: byond/man/man6/DreamDaemon.6 -2022-11-01T15:24:20.3142127Z inflating: byond/man/man6/DreamMaker.6 +2022-11-01T15:24:20.3140865Z inflating: byond/man/man6/DreamDaemon.6 +2022-11-01T15:24:20.3142127Z inflating: byond/man/man6/DreamMaker.6 2022-11-01T15:24:20.3149532Z creating: byond/lib/ 2022-11-01T15:24:20.3149763Z creating: byond/host/ -2022-11-01T15:24:20.3150009Z inflating: byond/host/readme.html -2022-11-01T15:24:20.3150561Z inflating: byond/host/readme-unix.txt +2022-11-01T15:24:20.3150009Z inflating: byond/host/readme.html +2022-11-01T15:24:20.3150561Z inflating: byond/host/readme-unix.txt 2022-11-01T15:24:20.3150816Z creating: byond/host/home/ 2022-11-01T15:24:20.3151052Z creating: byond/host/home/root/ 2022-11-01T15:24:20.3151304Z creating: byond/host/home/root/byond/ 2022-11-01T15:24:20.3151781Z creating: byond/host/home/root/byond/tools/ 2022-11-01T15:24:20.3152089Z creating: byond/host/home/root/byond/tools/root/ -2022-11-01T15:24:20.3153050Z inflating: byond/host/home/root/byond/tools/root/root.dmb +2022-11-01T15:24:20.3153050Z inflating: byond/host/home/root/byond/tools/root/root.dmb 2022-11-01T15:24:20.3154330Z creating: byond/host/shared/ 2022-11-01T15:24:20.3155440Z creating: byond/host/shared/byond/ 2022-11-01T15:24:20.3156763Z creating: byond/host/shared/byond/tools/ 2022-11-01T15:24:20.3157942Z creating: byond/host/shared/byond/tools/ftp/ -2022-11-01T15:24:20.3159544Z inflating: byond/host/shared/byond/tools/ftp/ftp.dmb +2022-11-01T15:24:20.3159544Z inflating: byond/host/shared/byond/tools/ftp/ftp.dmb 2022-11-01T15:24:20.3159981Z creating: byond/host/shared/byond/tools/admin/ -2022-11-01T15:24:20.3172339Z inflating: byond/host/shared/byond/tools/admin/admin.dmb +2022-11-01T15:24:20.3172339Z inflating: byond/host/shared/byond/tools/admin/admin.dmb 2022-11-01T15:24:20.3172798Z creating: byond/host/shared-web/ 2022-11-01T15:24:20.3173421Z creating: byond/host/shared-web/web/ 2022-11-01T15:24:20.3173784Z creating: byond/host/shared-web/web/tools/ 2022-11-01T15:24:20.3174165Z creating: byond/host/shared-web/web/tools/admin/ -2022-11-01T15:24:20.3176681Z inflating: byond/host/shared-web/web/tools/admin/index.dmb -2022-11-01T15:24:20.3186829Z inflating: byond/host/host.dmb -2022-11-01T15:24:20.3187123Z inflating: byond/host/host.start -2022-11-01T15:24:20.3188445Z inflating: byond/host/hostconf.orig -2022-11-01T15:24:20.3189903Z inflating: byond/host/hostconf.txt -2022-11-01T15:24:20.3191949Z inflating: byond/readme.txt +2022-11-01T15:24:20.3176681Z inflating: byond/host/shared-web/web/tools/admin/index.dmb +2022-11-01T15:24:20.3186829Z inflating: byond/host/host.dmb +2022-11-01T15:24:20.3187123Z inflating: byond/host/host.start +2022-11-01T15:24:20.3188445Z inflating: byond/host/hostconf.orig +2022-11-01T15:24:20.3189903Z inflating: byond/host/hostconf.txt +2022-11-01T15:24:20.3191949Z inflating: byond/readme.txt 2022-11-01T15:24:20.3192209Z creating: byond/bin/ -2022-11-01T15:24:20.3193769Z inflating: byond/bin/byondexec -2022-11-01T15:24:20.3195439Z inflating: byond/bin/DreamDownload -2022-11-01T15:24:20.3908232Z inflating: byond/bin/libbyond.so -2022-11-01T15:24:20.4069319Z inflating: byond/bin/libext.so -2022-11-01T15:24:20.4073220Z inflating: byond/bin/DreamDaemon -2022-11-01T15:24:20.4080176Z inflating: byond/bin/DreamMaker +2022-11-01T15:24:20.3193769Z inflating: byond/bin/byondexec +2022-11-01T15:24:20.3195439Z inflating: byond/bin/DreamDownload +2022-11-01T15:24:20.3908232Z inflating: byond/bin/libbyond.so +2022-11-01T15:24:20.4069319Z inflating: byond/bin/libext.so +2022-11-01T15:24:20.4073220Z inflating: byond/bin/DreamDaemon +2022-11-01T15:24:20.4080176Z inflating: byond/bin/DreamMaker 2022-11-01T15:24:20.4080431Z creating: byond/cfg/ -2022-11-01T15:24:20.4080676Z inflating: byond/cfg/release.txt +2022-11-01T15:24:20.4080676Z inflating: byond/cfg/release.txt 2022-11-01T15:24:20.4266638Z *************************** 2022-11-01T15:24:20.4273089Z Now run the following command: -2022-11-01T15:24:20.4285743Z +2022-11-01T15:24:20.4285743Z 2022-11-01T15:24:20.4296269Z source /home/runner/BYOND/byond/bin/byondsetup -2022-11-01T15:24:20.4307061Z +2022-11-01T15:24:20.4307061Z 2022-11-01T15:24:20.4314771Z If it generates errors, your shell is not compatible with 'sh', so you will 2022-11-01T15:24:20.4321421Z have to edit byondsetup and make it work with your shell. If the script works, you should be able to run DreamDaemon. -2022-11-01T15:24:20.4332109Z +2022-11-01T15:24:20.4332109Z 2022-11-01T15:24:20.4340024Z IMPORTANT: once you have the script working, you must add the above line 2022-11-01T15:24:20.4346843Z to your startup script. The name of your startup script depends on the 2022-11-01T15:24:20.4352983Z shell you use. Typical ones are .profile or .bash_profile. -2022-11-01T15:24:20.4363091Z +2022-11-01T15:24:20.4363091Z 2022-11-01T15:24:20.4371649Z Once everything is working, you can find out more about the software 2022-11-01T15:24:20.4381742Z by doing 'man DreamDaemon'. A host server has also been included 2022-11-01T15:24:20.4389897Z so edit host/hostconf.txt and boot up your world server! @@ -973,7 +973,7 @@ 2022-11-01T15:25:42.0446892Z Welcome BYOND! (5.0 Public Version 514.1588) 2022-11-01T15:26:04.0921674Z 865 global variables 2022-11-01T15:26:04.9224562Z World loaded at 15:26:04! -2022-11-01T15:26:04.9749013Z Running /tg/ revision: +2022-11-01T15:26:04.9749013Z Running /tg/ revision: 2022-11-01T15:26:04.9749563Z No commit information 2022-11-01T15:26:04.9837092Z Loading config file config.txt... 2022-11-01T15:26:04.9841145Z Loading config file maps.txt... @@ -1054,252 +1054,252 @@ 2022-11-01T15:28:07.7750369Z Initializations complete within 121.7 seconds! 2022-11-01T15:28:07.7824332Z Game start took 0s 2022-11-01T15:28:18.9688166Z ##[group]/datum/unit_test/log_mapping -2022-11-01T15:28:18.9688840Z +2022-11-01T15:28:18.9688840Z 2022-11-01T15:28:18.9691942Z PASS /datum/unit_test/log_mapping 0s 2022-11-01T15:28:18.9692629Z ##[endgroup] 2022-11-01T15:28:19.0858950Z ##[group]/datum/unit_test/ablative_hood_hud -2022-11-01T15:28:19.1163124Z +2022-11-01T15:28:19.1163124Z 2022-11-01T15:28:19.1164826Z PASS /datum/unit_test/ablative_hood_hud 0.1s 2022-11-01T15:28:19.1165814Z ##[endgroup] 2022-11-01T15:28:19.1391399Z ##[group]/datum/unit_test/ablative_hood_hud_with_helmet -2022-11-01T15:28:19.1679738Z +2022-11-01T15:28:19.1679738Z 2022-11-01T15:28:19.1680798Z PASS /datum/unit_test/ablative_hood_hud_with_helmet 0s 2022-11-01T15:28:19.1684214Z ##[endgroup] 2022-11-01T15:28:19.1915312Z ##[group]/datum/unit_test/achievements -2022-11-01T15:28:19.2020908Z +2022-11-01T15:28:19.2020908Z 2022-11-01T15:28:19.2021795Z PASS /datum/unit_test/achievements 0.1s 2022-11-01T15:28:19.2022783Z ##[endgroup] 2022-11-01T15:28:19.2454845Z ##[group]/datum/unit_test/anchored_mobs -2022-11-01T15:28:19.2456383Z +2022-11-01T15:28:19.2456383Z 2022-11-01T15:28:19.2457685Z PASS /datum/unit_test/anchored_mobs 0s 2022-11-01T15:28:19.2458601Z ##[endgroup] 2022-11-01T15:28:19.2632595Z ##[group]/datum/unit_test/anonymous_themes -2022-11-01T15:28:19.4123645Z +2022-11-01T15:28:19.4123645Z 2022-11-01T15:28:19.4125365Z PASS /datum/unit_test/anonymous_themes 0.2s 2022-11-01T15:28:19.4128592Z ##[endgroup] 2022-11-01T15:28:19.6372545Z ##[group]/datum/unit_test/autowiki -2022-11-01T15:28:21.1559221Z +2022-11-01T15:28:21.1559221Z 2022-11-01T15:28:21.1560055Z PASS /datum/unit_test/autowiki 1.5s 2022-11-01T15:28:21.1560742Z ##[endgroup] 2022-11-01T15:28:22.6418059Z ##[group]/datum/unit_test/autowiki_include_template -2022-11-01T15:28:22.6418298Z +2022-11-01T15:28:22.6418298Z 2022-11-01T15:28:22.6419112Z PASS /datum/unit_test/autowiki_include_template 0s 2022-11-01T15:28:22.6419629Z ##[endgroup] 2022-11-01T15:28:22.6592281Z ##[group]/datum/unit_test/barsigns_icon -2022-11-01T15:28:22.6850472Z +2022-11-01T15:28:22.6850472Z 2022-11-01T15:28:22.6851042Z PASS /datum/unit_test/barsigns_icon 0s 2022-11-01T15:28:22.6851783Z ##[endgroup] 2022-11-01T15:28:22.7025607Z ##[group]/datum/unit_test/barsigns_name -2022-11-01T15:28:22.7025827Z +2022-11-01T15:28:22.7025827Z 2022-11-01T15:28:22.7026411Z PASS /datum/unit_test/barsigns_name 0s 2022-11-01T15:28:22.7026869Z ##[endgroup] 2022-11-01T15:28:22.7196300Z ##[group]/datum/unit_test/bespoke_id -2022-11-01T15:28:22.7196495Z +2022-11-01T15:28:22.7196495Z 2022-11-01T15:28:22.7196933Z PASS /datum/unit_test/bespoke_id 0s 2022-11-01T15:28:22.7197373Z ##[endgroup] 2022-11-01T15:28:22.7528217Z ##[group]/datum/unit_test/binary_insert -2022-11-01T15:28:22.7528455Z +2022-11-01T15:28:22.7528455Z 2022-11-01T15:28:22.7528943Z PASS /datum/unit_test/binary_insert 0s 2022-11-01T15:28:22.7529444Z ##[endgroup] 2022-11-01T15:28:22.7720960Z ##[group]/datum/unit_test/bloody_footprints -2022-11-01T15:28:22.8157307Z +2022-11-01T15:28:22.8157307Z 2022-11-01T15:28:22.8158188Z PASS /datum/unit_test/bloody_footprints 0.1s 2022-11-01T15:28:22.8158909Z ##[endgroup] 2022-11-01T15:28:22.9653160Z ##[group]/datum/unit_test/breath_sanity -2022-11-01T15:28:23.0154449Z +2022-11-01T15:28:23.0154449Z 2022-11-01T15:28:23.0155311Z PASS /datum/unit_test/breath_sanity 0.1s 2022-11-01T15:28:23.0155975Z ##[endgroup] 2022-11-01T15:28:23.0421572Z ##[group]/datum/unit_test/breath_sanity_plasmamen -2022-11-01T15:28:23.0966156Z +2022-11-01T15:28:23.0966156Z 2022-11-01T15:28:23.0966990Z PASS /datum/unit_test/breath_sanity_plasmamen 0s 2022-11-01T15:28:23.0967848Z ##[endgroup] 2022-11-01T15:28:23.1238973Z ##[group]/datum/unit_test/breath_sanity_ashwalker -2022-11-01T15:28:23.1864867Z +2022-11-01T15:28:23.1864867Z 2022-11-01T15:28:23.1865526Z PASS /datum/unit_test/breath_sanity_ashwalker 0s 2022-11-01T15:28:23.1866107Z ##[endgroup] 2022-11-01T15:28:23.2143094Z ##[group]/datum/unit_test/cable_powernets -2022-11-01T15:28:23.2143293Z +2022-11-01T15:28:23.2143293Z 2022-11-01T15:28:23.2143753Z PASS /datum/unit_test/cable_powernets 0s 2022-11-01T15:28:23.2144353Z ##[endgroup] 2022-11-01T15:28:23.2296359Z ##[group]/datum/unit_test/card_mismatch -2022-11-01T15:28:23.2339244Z +2022-11-01T15:28:23.2339244Z 2022-11-01T15:28:23.2339785Z PASS /datum/unit_test/card_mismatch 0s 2022-11-01T15:28:23.2340711Z ##[endgroup] 2022-11-01T15:28:23.3389441Z ##[group]/datum/unit_test/chain_pull_through_space -2022-11-01T15:28:23.3417372Z +2022-11-01T15:28:23.3417372Z 2022-11-01T15:28:23.3428322Z PASS /datum/unit_test/chain_pull_through_space 0s 2022-11-01T15:28:23.3429414Z ##[endgroup] 2022-11-01T15:28:23.4797614Z ##[group]/datum/unit_test/chat_filter_sanity -2022-11-01T15:28:23.4802389Z +2022-11-01T15:28:23.4802389Z 2022-11-01T15:28:23.4803303Z PASS /datum/unit_test/chat_filter_sanity 0s 2022-11-01T15:28:23.4803804Z ##[endgroup] 2022-11-01T15:28:23.4985708Z ##[group]/datum/unit_test/circuit_component_category -2022-11-01T15:28:23.4985973Z +2022-11-01T15:28:23.4985973Z 2022-11-01T15:28:23.4986498Z PASS /datum/unit_test/circuit_component_category 0s 2022-11-01T15:28:23.4987054Z ##[endgroup] 2022-11-01T15:28:23.5170927Z ##[group]/datum/unit_test/closets -2022-11-01T15:28:25.4385541Z +2022-11-01T15:28:25.4385541Z 2022-11-01T15:28:25.4386681Z PASS /datum/unit_test/closets 1.9s 2022-11-01T15:28:25.4387368Z ##[endgroup] 2022-11-01T15:28:28.6598884Z ##[group]/datum/unit_test/harm_punch -2022-11-01T15:28:28.7131885Z +2022-11-01T15:28:28.7131885Z 2022-11-01T15:28:28.7132943Z PASS /datum/unit_test/harm_punch 0.1s 2022-11-01T15:28:28.7134837Z ##[endgroup] 2022-11-01T15:28:28.7421766Z ##[group]/datum/unit_test/harm_melee -2022-11-01T15:28:28.7933680Z +2022-11-01T15:28:28.7933680Z 2022-11-01T15:28:28.7934808Z PASS /datum/unit_test/harm_melee 0s 2022-11-01T15:28:28.7935540Z ##[endgroup] 2022-11-01T15:28:28.8380921Z ##[group]/datum/unit_test/harm_different_damage -2022-11-01T15:28:28.8975633Z +2022-11-01T15:28:28.8975633Z 2022-11-01T15:28:28.8976869Z PASS /datum/unit_test/harm_different_damage 0s 2022-11-01T15:28:28.8977671Z ##[endgroup] 2022-11-01T15:28:28.9761803Z ##[group]/datum/unit_test/attack_chain -2022-11-01T15:28:29.0318227Z +2022-11-01T15:28:29.0318227Z 2022-11-01T15:28:29.0319454Z PASS /datum/unit_test/attack_chain 0.1s 2022-11-01T15:28:29.0320200Z ##[endgroup] 2022-11-01T15:28:29.0662718Z ##[group]/datum/unit_test/disarm -2022-11-01T15:28:29.1273517Z +2022-11-01T15:28:29.1273517Z 2022-11-01T15:28:29.1275850Z PASS /datum/unit_test/disarm 0.1s 2022-11-01T15:28:29.1276795Z ##[endgroup] 2022-11-01T15:28:29.1606515Z ##[group]/datum/unit_test/component_duping -2022-11-01T15:28:29.1606753Z +2022-11-01T15:28:29.1606753Z 2022-11-01T15:28:29.1607266Z PASS /datum/unit_test/component_duping 0s 2022-11-01T15:28:29.1607745Z ##[endgroup] 2022-11-01T15:28:29.1774627Z ##[group]/datum/unit_test/confusion_symptom -2022-11-01T15:28:29.2032018Z +2022-11-01T15:28:29.2032018Z 2022-11-01T15:28:29.2033145Z PASS /datum/unit_test/confusion_symptom 0.1s 2022-11-01T15:28:29.2033987Z ##[endgroup] 2022-11-01T15:28:29.2246157Z ##[group]/datum/unit_test/connect_loc_basic -2022-11-01T15:28:29.2248367Z +2022-11-01T15:28:29.2248367Z 2022-11-01T15:28:29.2249487Z PASS /datum/unit_test/connect_loc_basic 0s 2022-11-01T15:28:29.2250109Z ##[endgroup] 2022-11-01T15:28:29.2416396Z ##[group]/datum/unit_test/connect_loc_change_turf -2022-11-01T15:28:29.2424538Z +2022-11-01T15:28:29.2424538Z 2022-11-01T15:28:29.2425333Z PASS /datum/unit_test/connect_loc_change_turf 0s 2022-11-01T15:28:29.2425972Z ##[endgroup] 2022-11-01T15:28:29.2592394Z ##[group]/datum/unit_test/connect_loc_multiple_on_turf -2022-11-01T15:28:29.2599395Z +2022-11-01T15:28:29.2599395Z 2022-11-01T15:28:29.2599862Z PASS /datum/unit_test/connect_loc_multiple_on_turf 0s 2022-11-01T15:28:29.2600716Z ##[endgroup] 2022-11-01T15:28:29.2917858Z ##[group]/datum/unit_test/crayon_naming -2022-11-01T15:28:29.2996631Z +2022-11-01T15:28:29.2996631Z 2022-11-01T15:28:29.2997310Z PASS /datum/unit_test/crayon_naming 0s 2022-11-01T15:28:29.2997930Z ##[endgroup] 2022-11-01T15:28:29.3177352Z ##[group]/datum/unit_test/dcs_get_id_from_arguments -2022-11-01T15:28:29.3178559Z +2022-11-01T15:28:29.3178559Z 2022-11-01T15:28:29.3182903Z PASS /datum/unit_test/dcs_get_id_from_arguments 0s 2022-11-01T15:28:29.3183682Z ##[endgroup] 2022-11-01T15:28:29.3371899Z ##[group]/datum/unit_test/designs -2022-11-01T15:28:29.3438160Z +2022-11-01T15:28:29.3438160Z 2022-11-01T15:28:29.3438713Z PASS /datum/unit_test/designs 0s 2022-11-01T15:28:29.3439280Z ##[endgroup] 2022-11-01T15:28:29.3630774Z ##[group]/datum/unit_test/dummy_spawn_species -2022-11-01T15:28:29.7865710Z +2022-11-01T15:28:29.7865710Z 2022-11-01T15:28:29.7866984Z PASS /datum/unit_test/dummy_spawn_species 0.4s 2022-11-01T15:28:29.7868408Z ##[endgroup] 2022-11-01T15:28:30.2064941Z ##[group]/datum/unit_test/dummy_spawn_outfit 2022-11-01T15:28:30.2287943Z Job type /datum/job/ai could not be retrieved from SSjob -2022-11-01T15:28:30.5729399Z +2022-11-01T15:28:30.5729399Z 2022-11-01T15:28:30.5730637Z PASS /datum/unit_test/dummy_spawn_outfit 0.3s 2022-11-01T15:28:30.5731452Z ##[endgroup] 2022-11-01T15:28:30.8929561Z ##[group]/datum/unit_test/dynamic_roundstart_ruleset_sanity -2022-11-01T15:28:30.8930214Z +2022-11-01T15:28:30.8930214Z 2022-11-01T15:28:30.8933249Z PASS /datum/unit_test/dynamic_roundstart_ruleset_sanity 0s 2022-11-01T15:28:30.8933929Z ##[endgroup] 2022-11-01T15:28:30.9093619Z ##[group]/datum/unit_test/dynamic_unique_antag_flags -2022-11-01T15:28:30.9094243Z +2022-11-01T15:28:30.9094243Z 2022-11-01T15:28:30.9097412Z PASS /datum/unit_test/dynamic_unique_antag_flags 0s 2022-11-01T15:28:30.9098086Z ##[endgroup] 2022-11-01T15:28:30.9259102Z ##[group]/datum/unit_test/egg_glands -2022-11-01T15:28:30.9738261Z +2022-11-01T15:28:30.9738261Z 2022-11-01T15:28:30.9739529Z PASS /datum/unit_test/egg_glands 0s 2022-11-01T15:28:30.9742814Z ##[endgroup] 2022-11-01T15:28:30.9910488Z ##[group]/datum/unit_test/emoting -2022-11-01T15:28:31.0191020Z +2022-11-01T15:28:31.0191020Z 2022-11-01T15:28:31.0192873Z PASS /datum/unit_test/emoting 0.1s 2022-11-01T15:28:31.0196009Z ##[endgroup] 2022-11-01T15:28:31.0470998Z ##[group]/datum/unit_test/food_edibility_check -2022-11-01T15:28:32.3907505Z +2022-11-01T15:28:32.3907505Z 2022-11-01T15:28:32.3908587Z PASS /datum/unit_test/food_edibility_check 1.3s 2022-11-01T15:28:32.3909262Z ##[endgroup] 2022-11-01T15:28:33.7158865Z ##[group]/datum/unit_test/atmospheric_gas_transfer -2022-11-01T15:28:33.7169071Z +2022-11-01T15:28:33.7169071Z 2022-11-01T15:28:33.7171453Z PASS /datum/unit_test/atmospheric_gas_transfer 0s 2022-11-01T15:28:33.7173553Z ##[endgroup] 2022-11-01T15:28:33.7346695Z ##[group]/datum/unit_test/get_turf_pixel -2022-11-01T15:28:33.7367134Z +2022-11-01T15:28:33.7367134Z 2022-11-01T15:28:33.7368982Z PASS /datum/unit_test/get_turf_pixel 0s 2022-11-01T15:28:33.7371248Z ##[endgroup] 2022-11-01T15:28:33.7556559Z ##[group]/datum/unit_test/greyscale_item_icon_states -2022-11-01T15:28:33.7624830Z +2022-11-01T15:28:33.7624830Z 2022-11-01T15:28:33.7626511Z PASS /datum/unit_test/greyscale_item_icon_states 0s 2022-11-01T15:28:33.7628685Z ##[endgroup] 2022-11-01T15:28:33.7811590Z ##[group]/datum/unit_test/greyscale_color_count -2022-11-01T15:28:33.7978824Z +2022-11-01T15:28:33.7978824Z 2022-11-01T15:28:33.7981042Z PASS /datum/unit_test/greyscale_color_count 0s 2022-11-01T15:28:33.7981873Z ##[endgroup] 2022-11-01T15:28:33.8551069Z ##[group]/datum/unit_test/hallucination_icons -2022-11-01T15:28:34.1090174Z +2022-11-01T15:28:34.1090174Z 2022-11-01T15:28:34.1096431Z PASS /datum/unit_test/hallucination_icons 0.3s 2022-11-01T15:28:34.1099789Z ##[endgroup] 2022-11-01T15:28:34.3281283Z ##[group]/datum/unit_test/heretic_knowledge -2022-11-01T15:28:34.3305016Z +2022-11-01T15:28:34.3305016Z 2022-11-01T15:28:34.3306029Z PASS /datum/unit_test/heretic_knowledge 0s 2022-11-01T15:28:34.3306904Z ##[endgroup] 2022-11-01T15:28:34.3483924Z ##[group]/datum/unit_test/heretic_main_paths -2022-11-01T15:28:34.3484902Z +2022-11-01T15:28:34.3484902Z 2022-11-01T15:28:34.3487952Z PASS /datum/unit_test/heretic_main_paths 0s 2022-11-01T15:28:34.3491126Z ##[endgroup] 2022-11-01T15:28:34.3668321Z ##[group]/datum/unit_test/heretic_rituals -2022-11-01T15:28:34.4513883Z +2022-11-01T15:28:34.4513883Z 2022-11-01T15:28:34.4515231Z PASS /datum/unit_test/heretic_rituals 0.1s 2022-11-01T15:28:34.4518615Z ##[endgroup] 2022-11-01T15:28:34.5255676Z ##[group]/datum/unit_test/hanukkah_2123 -2022-11-01T15:28:34.5256380Z +2022-11-01T15:28:34.5256380Z 2022-11-01T15:28:34.5258696Z PASS /datum/unit_test/hanukkah_2123 0s 2022-11-01T15:28:34.5306060Z ##[endgroup] 2022-11-01T15:28:34.5435626Z ##[group]/datum/unit_test/ramadan_2165 -2022-11-01T15:28:34.5435839Z +2022-11-01T15:28:34.5435839Z 2022-11-01T15:28:34.5436316Z PASS /datum/unit_test/ramadan_2165 0s 2022-11-01T15:28:34.5436790Z ##[endgroup] 2022-11-01T15:28:34.5758281Z ##[group]/datum/unit_test/thanksgiving_2020 -2022-11-01T15:28:34.5758510Z +2022-11-01T15:28:34.5758510Z 2022-11-01T15:28:34.5759790Z PASS /datum/unit_test/thanksgiving_2020 0s 2022-11-01T15:28:34.5760323Z ##[endgroup] 2022-11-01T15:28:34.5922636Z ##[group]/datum/unit_test/mother_3683 -2022-11-01T15:28:34.5923203Z +2022-11-01T15:28:34.5923203Z 2022-11-01T15:28:34.5923668Z PASS /datum/unit_test/mother_3683 0s 2022-11-01T15:28:34.5924152Z ##[endgroup] 2022-11-01T15:28:34.6258731Z ##[group]/datum/unit_test/hello_2020 -2022-11-01T15:28:34.6258967Z +2022-11-01T15:28:34.6258967Z 2022-11-01T15:28:34.6259461Z PASS /datum/unit_test/hello_2020 0s 2022-11-01T15:28:34.6260389Z ##[endgroup] 2022-11-01T15:28:34.6425516Z ##[group]/datum/unit_test/new_year_1983 -2022-11-01T15:28:34.6425720Z +2022-11-01T15:28:34.6425720Z 2022-11-01T15:28:34.6426174Z PASS /datum/unit_test/new_year_1983 0s 2022-11-01T15:28:34.6426620Z ##[endgroup] 2022-11-01T15:28:34.6759224Z ##[group]/datum/unit_test/moth_week_2020 -2022-11-01T15:28:34.6792170Z +2022-11-01T15:28:34.6792170Z 2022-11-01T15:28:34.6792723Z PASS /datum/unit_test/moth_week_2020 0s 2022-11-01T15:28:34.6793240Z ##[endgroup] 2022-11-01T15:28:34.6958289Z ##[group]/datum/unit_test/human_through_recycler -2022-11-01T15:28:34.7370291Z +2022-11-01T15:28:34.7370291Z 2022-11-01T15:28:34.7371226Z PASS /datum/unit_test/human_through_recycler 0.1s 2022-11-01T15:28:34.7372112Z ##[endgroup] 2022-11-01T15:28:34.8623820Z ##[group]/datum/unit_test/hydroponics_extractor_storage -2022-11-01T15:28:34.8996006Z +2022-11-01T15:28:34.8996006Z 2022-11-01T15:28:34.8996940Z PASS /datum/unit_test/hydroponics_extractor_storage 0s 2022-11-01T15:28:34.8997589Z ##[endgroup] 2022-11-01T15:28:34.9278188Z ##[group]/datum/unit_test/hydroponics_harvest -2022-11-01T15:28:35.0009791Z +2022-11-01T15:28:35.0009791Z 2022-11-01T15:28:35.0010716Z PASS /datum/unit_test/hydroponics_harvest 0.1s 2022-11-01T15:28:35.0012013Z ##[endgroup] 2022-11-01T15:28:35.0747162Z ##[group]/datum/unit_test/hydroponics_self_mutation -2022-11-01T15:28:35.1406271Z +2022-11-01T15:28:35.1406271Z 2022-11-01T15:28:35.1407332Z PASS /datum/unit_test/hydroponics_self_mutation 0.1s 2022-11-01T15:28:35.1408057Z ##[endgroup] 2022-11-01T15:28:35.2102485Z ##[group]/datum/unit_test/hydroponics_validate_genes -2022-11-01T15:28:35.2762621Z +2022-11-01T15:28:35.2762621Z 2022-11-01T15:28:35.2763561Z PASS /datum/unit_test/hydroponics_validate_genes 0s 2022-11-01T15:28:35.2764288Z ##[endgroup] 2022-11-01T15:28:35.3749912Z ##[group]/datum/unit_test/defined_inhand_icon_states @@ -1420,200 +1420,200 @@ 2022-11-01T15:28:36.6241586Z /obj/item/bonesetter does not have an inhand_icon_state value - Possible matching sprites for "bonesetter" found in: 'icons/mob/inhands/equipment/medical_lefthand.dmi' & 'icons/mob/inhands/equipment/medical_righthand.dmi' 2022-11-01T15:28:36.6242563Z /obj/item/blood_filter does not have an inhand_icon_state value - Possible matching sprites for "bloodfilter" found in: 'icons/mob/inhands/equipment/medical_lefthand.dmi' & 'icons/mob/inhands/equipment/medical_righthand.dmi' 2022-11-01T15:28:36.6243567Z /obj/item/mecha_ammo/flashbang does not have an inhand_icon_state value - Possible matching sprites for "flashbang" found in: 'icons/mob/inhands/equipment/security_righthand.dmi' & 'icons/mob/inhands/equipment/security_lefthand.dmi' -2022-11-01T15:28:36.6243984Z +2022-11-01T15:28:36.6243984Z 2022-11-01T15:28:36.6244251Z PASS /datum/unit_test/defined_inhand_icon_states 1.3s 2022-11-01T15:28:36.6245107Z ##[endgroup] 2022-11-01T15:28:38.3781123Z ##[group]/datum/unit_test/keybinding_init -2022-11-01T15:28:38.3781642Z +2022-11-01T15:28:38.3781642Z 2022-11-01T15:28:38.3782256Z PASS /datum/unit_test/keybinding_init 0s 2022-11-01T15:28:38.3782848Z ##[endgroup] 2022-11-01T15:28:38.3952800Z ##[group]/datum/unit_test/knockoff_component -2022-11-01T15:28:38.4583090Z +2022-11-01T15:28:38.4583090Z 2022-11-01T15:28:38.4584605Z PASS /datum/unit_test/knockoff_component 0.1s 2022-11-01T15:28:38.4585463Z ##[endgroup] 2022-11-01T15:28:38.5388034Z ##[group]/datum/unit_test/limbsanity -2022-11-01T15:28:38.6201426Z +2022-11-01T15:28:38.6201426Z 2022-11-01T15:28:38.6202784Z PASS /datum/unit_test/limbsanity 0.1s 2022-11-01T15:28:38.6203543Z ##[endgroup] 2022-11-01T15:28:38.6375016Z ##[group]/datum/unit_test/load_map_security 2022-11-01T15:28:38.6378905Z map directory not in whitelist: data/load_map_security_temp for map runtimestation -2022-11-01T15:28:38.6380282Z +2022-11-01T15:28:38.6380282Z 2022-11-01T15:28:38.6381773Z PASS /datum/unit_test/load_map_security 0s 2022-11-01T15:28:38.6382439Z ##[endgroup] 2022-11-01T15:28:38.6552883Z ##[group]/datum/unit_test/machine_disassembly -2022-11-01T15:28:38.6594171Z +2022-11-01T15:28:38.6594171Z 2022-11-01T15:28:38.6595097Z PASS /datum/unit_test/machine_disassembly 0s 2022-11-01T15:28:38.6595719Z ##[endgroup] 2022-11-01T15:28:38.6872178Z ##[group]/datum/unit_test/mecha_damage -2022-11-01T15:28:38.7397615Z +2022-11-01T15:28:38.7397615Z 2022-11-01T15:28:38.7398654Z PASS /datum/unit_test/mecha_damage 0.1s 2022-11-01T15:28:38.7399283Z ##[endgroup] 2022-11-01T15:28:38.7754903Z ##[group]/datum/unit_test/test_human_base -2022-11-01T15:28:38.8529341Z +2022-11-01T15:28:38.8529341Z 2022-11-01T15:28:38.8530478Z PASS /datum/unit_test/test_human_base 0.1s 2022-11-01T15:28:38.8531256Z ##[endgroup] 2022-11-01T15:28:38.9752946Z ##[group]/datum/unit_test/test_human_bone -2022-11-01T15:28:39.0547873Z +2022-11-01T15:28:39.0547873Z 2022-11-01T15:28:39.0550271Z PASS /datum/unit_test/test_human_bone 0.1s 2022-11-01T15:28:39.0551284Z ##[endgroup] 2022-11-01T15:28:39.1298729Z ##[group]/datum/unit_test/merge_type -2022-11-01T15:28:39.1300463Z +2022-11-01T15:28:39.1300463Z 2022-11-01T15:28:39.1301413Z PASS /datum/unit_test/merge_type 0s 2022-11-01T15:28:39.1302403Z ##[endgroup] 2022-11-01T15:28:39.1477365Z ##[group]/datum/unit_test/metabolization -2022-11-01T15:28:39.3901954Z +2022-11-01T15:28:39.3901954Z 2022-11-01T15:28:39.3903433Z PASS /datum/unit_test/metabolization 0.2s 2022-11-01T15:28:39.3906497Z ##[endgroup] 2022-11-01T15:28:39.7294447Z ##[group]/datum/unit_test/on_mob_end_metabolize -2022-11-01T15:28:39.7598821Z +2022-11-01T15:28:39.7598821Z 2022-11-01T15:28:39.7600420Z PASS /datum/unit_test/on_mob_end_metabolize 0s 2022-11-01T15:28:39.7649553Z ##[endgroup] 2022-11-01T15:28:39.7835943Z ##[group]/datum/unit_test/addictions -2022-11-01T15:28:39.8761920Z +2022-11-01T15:28:39.8761920Z 2022-11-01T15:28:39.8763025Z PASS /datum/unit_test/addictions 0.1s 2022-11-01T15:28:39.8764090Z ##[endgroup] 2022-11-01T15:28:39.9657360Z ##[group]/datum/unit_test/actions_moved_on_mind_transfer -2022-11-01T15:28:39.9971522Z +2022-11-01T15:28:39.9971522Z 2022-11-01T15:28:39.9972744Z PASS /datum/unit_test/actions_moved_on_mind_transfer 0s 2022-11-01T15:28:39.9974074Z ##[endgroup] 2022-11-01T15:28:40.0547546Z ##[group]/datum/unit_test/mob_faction -2022-11-01T15:28:44.4337194Z +2022-11-01T15:28:44.4337194Z 2022-11-01T15:28:44.4338995Z PASS /datum/unit_test/mob_faction 4.4s 2022-11-01T15:28:44.4343946Z ##[endgroup] 2022-11-01T15:28:50.4570438Z ##[group]/datum/unit_test/mob_spawn -2022-11-01T15:28:50.4788560Z +2022-11-01T15:28:50.4788560Z 2022-11-01T15:28:50.4789841Z PASS /datum/unit_test/mob_spawn 0s 2022-11-01T15:28:50.4791361Z ##[endgroup] 2022-11-01T15:28:50.6427929Z ##[group]/datum/unit_test/modsuit_checks -2022-11-01T15:28:50.8774698Z +2022-11-01T15:28:50.8774698Z 2022-11-01T15:28:50.8776366Z PASS /datum/unit_test/modsuit_checks 0.2s 2022-11-01T15:28:50.8777936Z ##[endgroup] 2022-11-01T15:28:51.1124046Z ##[group]/datum/unit_test/modular_map_loader -2022-11-01T15:28:51.1141260Z +2022-11-01T15:28:51.1141260Z 2022-11-01T15:28:51.1142098Z PASS /datum/unit_test/modular_map_loader 0s 2022-11-01T15:28:51.1142848Z ##[endgroup] 2022-11-01T15:28:51.1326741Z ##[group]/datum/unit_test/mouse_bite_cable -2022-11-01T15:28:51.1380480Z +2022-11-01T15:28:51.1380480Z 2022-11-01T15:28:51.1381073Z PASS /datum/unit_test/mouse_bite_cable 0s 2022-11-01T15:28:51.1381752Z ##[endgroup] 2022-11-01T15:28:51.1577018Z ##[group]/datum/unit_test/novaflower_burn -2022-11-01T15:28:51.2169264Z +2022-11-01T15:28:51.2169264Z 2022-11-01T15:28:51.2170162Z PASS /datum/unit_test/novaflower_burn 0.1s 2022-11-01T15:28:51.2170901Z ##[endgroup] 2022-11-01T15:28:51.2996852Z ##[group]/datum/unit_test/ntnetwork -2022-11-01T15:28:51.3019053Z +2022-11-01T15:28:51.3019053Z 2022-11-01T15:28:51.3019857Z PASS /datum/unit_test/ntnetwork 0.1s 2022-11-01T15:28:51.3020531Z ##[endgroup] 2022-11-01T15:28:51.3196087Z ##[group]/datum/unit_test/nuke_cinematic -2022-11-01T15:28:56.0755977Z +2022-11-01T15:28:56.0755977Z 2022-11-01T15:28:56.0763029Z PASS /datum/unit_test/nuke_cinematic 4.7s 2022-11-01T15:28:56.0764783Z ##[endgroup] 2022-11-01T15:28:56.3446321Z ##[group]/datum/unit_test/objectives_category -2022-11-01T15:28:56.3446835Z +2022-11-01T15:28:56.3446835Z 2022-11-01T15:28:56.3447470Z PASS /datum/unit_test/objectives_category 0s 2022-11-01T15:28:56.3448136Z ##[endgroup] 2022-11-01T15:28:56.3614737Z ##[group]/datum/unit_test/operating_table -2022-11-01T15:28:56.4176620Z +2022-11-01T15:28:56.4176620Z 2022-11-01T15:28:56.4177598Z PASS /datum/unit_test/operating_table 0.1s 2022-11-01T15:28:56.4178689Z ##[endgroup] 2022-11-01T15:28:56.4981266Z ##[group]/datum/unit_test/outfit_sanity -2022-11-01T15:29:06.6657414Z +2022-11-01T15:29:06.6657414Z 2022-11-01T15:29:06.6658439Z PASS /datum/unit_test/outfit_sanity 10.2s 2022-11-01T15:29:06.6659196Z ##[endgroup] 2022-11-01T15:29:16.7908070Z ##[group]/datum/unit_test/paintings -2022-11-01T15:29:16.8212273Z +2022-11-01T15:29:16.8212273Z 2022-11-01T15:29:16.8213346Z PASS /datum/unit_test/paintings 0.1s 2022-11-01T15:29:16.8214069Z ##[endgroup] 2022-11-01T15:29:16.8389425Z ##[group]/datum/unit_test/pills -2022-11-01T15:29:16.8676028Z +2022-11-01T15:29:16.8676028Z 2022-11-01T15:29:16.8677434Z PASS /datum/unit_test/pills 0s 2022-11-01T15:29:16.8678165Z ##[endgroup] 2022-11-01T15:29:16.9417567Z ##[group]/datum/unit_test/plane_double_transform -2022-11-01T15:29:16.9735002Z +2022-11-01T15:29:16.9735002Z 2022-11-01T15:29:16.9735721Z PASS /datum/unit_test/plane_double_transform 0s 2022-11-01T15:29:16.9736359Z ##[endgroup] 2022-11-01T15:29:17.0062562Z ##[group]/datum/unit_test/plane_dupe_detector -2022-11-01T15:29:17.0065142Z +2022-11-01T15:29:17.0065142Z 2022-11-01T15:29:17.0067978Z PASS /datum/unit_test/plane_dupe_detector 0s 2022-11-01T15:29:17.0071640Z ##[endgroup] 2022-11-01T15:29:17.0244509Z ##[group]/datum/unit_test/plantgrowth -2022-11-01T15:29:17.0776528Z +2022-11-01T15:29:17.0776528Z 2022-11-01T15:29:17.0779718Z PASS /datum/unit_test/plantgrowth 0s 2022-11-01T15:29:17.0781279Z ##[endgroup] 2022-11-01T15:29:17.0965505Z ##[group]/datum/unit_test/preference_species -2022-11-01T15:29:17.0966443Z +2022-11-01T15:29:17.0966443Z 2022-11-01T15:29:17.0969864Z PASS /datum/unit_test/preference_species 0s 2022-11-01T15:29:17.0973093Z ##[endgroup] 2022-11-01T15:29:17.1152354Z ##[group]/datum/unit_test/preferences_implement_everything -2022-11-01T15:29:23.4577951Z +2022-11-01T15:29:23.4577951Z 2022-11-01T15:29:23.4578850Z PASS /datum/unit_test/preferences_implement_everything 6.3s 2022-11-01T15:29:23.4579521Z ##[endgroup] 2022-11-01T15:29:29.7830207Z ##[group]/datum/unit_test/preferences_valid_savefile_key -2022-11-01T15:29:29.7830930Z +2022-11-01T15:29:29.7830930Z 2022-11-01T15:29:29.7833510Z PASS /datum/unit_test/preferences_valid_savefile_key 0s 2022-11-01T15:29:29.7834275Z ##[endgroup] 2022-11-01T15:29:29.8033816Z ##[group]/datum/unit_test/preferences_valid_main_feature_name -2022-11-01T15:29:29.8034550Z +2022-11-01T15:29:29.8034550Z 2022-11-01T15:29:29.8035744Z PASS /datum/unit_test/preferences_valid_main_feature_name 0s 2022-11-01T15:29:29.8089387Z ##[endgroup] 2022-11-01T15:29:29.8234720Z ##[group]/datum/unit_test/projectile_movetypes -2022-11-01T15:29:29.8234940Z +2022-11-01T15:29:29.8234940Z 2022-11-01T15:29:29.8235533Z PASS /datum/unit_test/projectile_movetypes 0s 2022-11-01T15:29:29.8236221Z ##[endgroup] 2022-11-01T15:29:29.8415787Z ##[group]/datum/unit_test/gun_go_bang -2022-11-01T15:29:29.9160628Z +2022-11-01T15:29:29.9160628Z 2022-11-01T15:29:29.9161545Z PASS /datum/unit_test/gun_go_bang 0.1s 2022-11-01T15:29:29.9162229Z ##[endgroup] 2022-11-01T15:29:30.0035502Z ##[group]/datum/unit_test/quirk_icons -2022-11-01T15:29:30.0035718Z +2022-11-01T15:29:30.0035718Z 2022-11-01T15:29:30.0036645Z PASS /datum/unit_test/quirk_icons 0s 2022-11-01T15:29:30.0037121Z ##[endgroup] 2022-11-01T15:29:30.0231538Z ##[group]/datum/unit_test/range_return -2022-11-01T15:29:30.0231750Z +2022-11-01T15:29:30.0231750Z 2022-11-01T15:29:30.0232212Z PASS /datum/unit_test/range_return 0s 2022-11-01T15:29:30.0232658Z ##[endgroup] 2022-11-01T15:29:30.0427797Z ##[group]/datum/unit_test/frame_stacking -2022-11-01T15:29:30.1020458Z +2022-11-01T15:29:30.1020458Z 2022-11-01T15:29:30.1021324Z PASS /datum/unit_test/frame_stacking 0.1s 2022-11-01T15:29:30.1022036Z ##[endgroup] 2022-11-01T15:29:30.1824738Z ##[group]/datum/unit_test/reagent_id_typos -2022-11-01T15:29:30.1842378Z +2022-11-01T15:29:30.1842378Z 2022-11-01T15:29:30.1842930Z PASS /datum/unit_test/reagent_id_typos 0s 2022-11-01T15:29:30.1843838Z ##[endgroup] 2022-11-01T15:29:30.2065945Z ##[group]/datum/unit_test/reagent_mob_expose -2022-11-01T15:29:30.2431485Z +2022-11-01T15:29:30.2431485Z 2022-11-01T15:29:30.2432396Z PASS /datum/unit_test/reagent_mob_expose 0s 2022-11-01T15:29:30.2433748Z ##[endgroup] 2022-11-01T15:29:30.2736664Z ##[group]/datum/unit_test/reagent_mob_procs -2022-11-01T15:29:30.3087817Z +2022-11-01T15:29:30.3087817Z 2022-11-01T15:29:30.3088677Z PASS /datum/unit_test/reagent_mob_procs 0.1s 2022-11-01T15:29:30.3089382Z ##[endgroup] 2022-11-01T15:29:30.3347974Z ##[group]/datum/unit_test/reagent_names -2022-11-01T15:29:31.2354819Z +2022-11-01T15:29:31.2354819Z 2022-11-01T15:29:31.2355678Z PASS /datum/unit_test/reagent_names 0.9s 2022-11-01T15:29:31.2356318Z ##[endgroup] 2022-11-01T15:29:32.1042828Z ##[group]/datum/unit_test/reagent_recipe_collisions -2022-11-01T15:29:32.5727294Z +2022-11-01T15:29:32.5727294Z 2022-11-01T15:29:32.5728917Z PASS /datum/unit_test/reagent_recipe_collisions 0.4s 2022-11-01T15:29:32.5730155Z ##[endgroup] 2022-11-01T15:29:32.9927755Z ##[group]/datum/unit_test/reagent_transfer -2022-11-01T15:29:32.9934109Z +2022-11-01T15:29:32.9934109Z 2022-11-01T15:29:32.9935981Z PASS /datum/unit_test/reagent_transfer 0s 2022-11-01T15:29:32.9936895Z ##[endgroup] 2022-11-01T15:29:33.0126783Z ##[group]/datum/unit_test/stop_drop_and_roll -2022-11-01T15:29:33.0390175Z +2022-11-01T15:29:33.0390175Z 2022-11-01T15:29:33.0391562Z PASS /datum/unit_test/stop_drop_and_roll 0s 2022-11-01T15:29:33.0394218Z ##[endgroup] 2022-11-01T15:29:33.0646804Z ##[group]/datum/unit_test/container_resist -2022-11-01T15:29:33.1054797Z +2022-11-01T15:29:33.1054797Z 2022-11-01T15:29:33.1056167Z PASS /datum/unit_test/container_resist 0.1s 2022-11-01T15:29:33.1057687Z ##[endgroup] 2022-11-01T15:29:33.1496944Z ##[group]/datum/unit_test/get_message_mods -2022-11-01T15:29:33.1778888Z +2022-11-01T15:29:33.1778888Z 2022-11-01T15:29:33.1780246Z PASS /datum/unit_test/get_message_mods 0s 2022-11-01T15:29:33.1781311Z ##[endgroup] 2022-11-01T15:29:33.2061026Z ##[group]/datum/unit_test/say_signal -2022-11-01T15:29:33.2078006Z +2022-11-01T15:29:33.2078006Z 2022-11-01T15:29:33.2079823Z PASS /datum/unit_test/say_signal 0s 2022-11-01T15:29:33.2083007Z ##[endgroup] 2022-11-01T15:29:33.2288010Z ##[group]/datum/unit_test/screenshot_antag_icons @@ -1647,12 +1647,12 @@ 2022-11-01T15:29:33.5122130Z screenshot_antag_icons_blobinfection was put in data/screenshots_new 2022-11-01T15:29:33.5146398Z screenshot_antag_icons_obsessed was put in data/screenshots_new 2022-11-01T15:29:33.5153740Z screenshot_antag_icons_malfaimidround was put in data/screenshots_new -2022-11-01T15:29:33.5154025Z +2022-11-01T15:29:33.5154025Z 2022-11-01T15:29:33.5154740Z PASS /datum/unit_test/screenshot_antag_icons 0.3s 2022-11-01T15:29:33.5155554Z ##[endgroup] 2022-11-01T15:29:33.7840161Z ##[group]/datum/unit_test/screenshot_basic 2022-11-01T15:29:33.7846135Z screenshot_basic_red was put in data/screenshots_new -2022-11-01T15:29:33.7850103Z +2022-11-01T15:29:33.7850103Z 2022-11-01T15:29:33.7851242Z PASS /datum/unit_test/screenshot_basic 0s 2022-11-01T15:29:33.7851736Z ##[endgroup] 2022-11-01T15:29:33.8047719Z ##[group]/datum/unit_test/screenshot_humanoids @@ -1709,112 +1709,112 @@ 2022-11-01T15:30:06.7544848Z screenshot_humanoids__datum_species_vampire was put in data/screenshots_new 2022-11-01T15:30:07.6424794Z screenshot_humanoids__datum_species_zombie was put in data/screenshots_new 2022-11-01T15:30:08.5972035Z screenshot_humanoids__datum_species_zombie_infectious was put in data/screenshots_new -2022-11-01T15:30:08.5972336Z +2022-11-01T15:30:08.5972336Z 2022-11-01T15:30:08.5972854Z PASS /datum/unit_test/screenshot_humanoids 34.7s 2022-11-01T15:30:08.5973526Z ##[endgroup] 2022-11-01T15:30:44.2099567Z ##[group]/datum/unit_test/screenshot_saturnx 2022-11-01T15:30:44.5011065Z screenshot_saturnx_invisibility was put in data/screenshots_new -2022-11-01T15:30:44.5011333Z +2022-11-01T15:30:44.5011333Z 2022-11-01T15:30:44.5012180Z PASS /datum/unit_test/screenshot_saturnx 0.3s 2022-11-01T15:30:44.5012769Z ##[endgroup] 2022-11-01T15:30:44.7785909Z ##[group]/datum/unit_test/security_officer_roundstart_distribution -2022-11-01T15:30:44.9017426Z +2022-11-01T15:30:44.9017426Z 2022-11-01T15:30:44.9018614Z PASS /datum/unit_test/security_officer_roundstart_distribution 0.2s 2022-11-01T15:30:44.9019313Z ##[endgroup] 2022-11-01T15:30:45.0427506Z ##[group]/datum/unit_test/security_officer_latejoin_distribution -2022-11-01T15:30:45.5391742Z +2022-11-01T15:30:45.5391742Z 2022-11-01T15:30:45.5392426Z PASS /datum/unit_test/security_officer_latejoin_distribution 0.5s 2022-11-01T15:30:45.5393125Z ##[endgroup] 2022-11-01T15:30:46.2210379Z ##[group]/datum/unit_test/security_levels -2022-11-01T15:30:46.2210589Z +2022-11-01T15:30:46.2210589Z 2022-11-01T15:30:46.2216058Z PASS /datum/unit_test/security_levels 0s 2022-11-01T15:30:46.2216555Z ##[endgroup] 2022-11-01T15:30:46.2399271Z ##[group]/datum/unit_test/servingtray -2022-11-01T15:30:46.2779259Z +2022-11-01T15:30:46.2779259Z 2022-11-01T15:30:46.2780125Z PASS /datum/unit_test/servingtray 0s 2022-11-01T15:30:46.2780972Z ##[endgroup] 2022-11-01T15:30:46.3048093Z ##[group]/datum/unit_test/simple_animal_freeze -2022-11-01T15:30:46.3059709Z +2022-11-01T15:30:46.3059709Z 2022-11-01T15:30:46.3060267Z PASS /datum/unit_test/simple_animal_freeze 0s 2022-11-01T15:30:46.3060938Z ##[endgroup] 2022-11-01T15:30:46.3241320Z ##[group]/datum/unit_test/siunit -2022-11-01T15:30:46.3241508Z +2022-11-01T15:30:46.3241508Z 2022-11-01T15:30:46.3241961Z PASS /datum/unit_test/siunit 0s 2022-11-01T15:30:46.3242362Z ##[endgroup] 2022-11-01T15:30:46.3753674Z ##[group]/datum/unit_test/slips -2022-11-01T15:30:46.4300467Z +2022-11-01T15:30:46.4300467Z 2022-11-01T15:30:46.4301480Z PASS /datum/unit_test/slips 0.1s 2022-11-01T15:30:46.4302301Z ##[endgroup] 2022-11-01T15:30:46.5100922Z ##[group]/datum/unit_test/spawn_humans -2022-11-01T15:30:51.5880046Z +2022-11-01T15:30:51.5880046Z 2022-11-01T15:30:51.5881071Z PASS /datum/unit_test/spawn_humans 5s 2022-11-01T15:30:51.5881789Z ##[endgroup] 2022-11-01T15:30:51.6350612Z ##[group]/datum/unit_test/spawn_mobs -2022-11-01T15:30:51.7210346Z +2022-11-01T15:30:51.7210346Z 2022-11-01T15:30:51.7211375Z PASS /datum/unit_test/spawn_mobs 0.1s 2022-11-01T15:30:51.7212072Z ##[endgroup] 2022-11-01T15:30:51.8820435Z ##[group]/datum/unit_test/species_change_clothing -2022-11-01T15:30:51.9839314Z +2022-11-01T15:30:51.9839314Z 2022-11-01T15:30:51.9840163Z PASS /datum/unit_test/species_change_clothing 0.1s 2022-11-01T15:30:51.9840797Z ##[endgroup] 2022-11-01T15:30:52.0595249Z ##[group]/datum/unit_test/species_change_organs -2022-11-01T15:30:52.1312087Z +2022-11-01T15:30:52.1312087Z 2022-11-01T15:30:52.1313114Z PASS /datum/unit_test/species_change_organs 0.1s 2022-11-01T15:30:52.1313735Z ##[endgroup] 2022-11-01T15:30:52.2788548Z ##[group]/datum/unit_test/species_config_sanity -2022-11-01T15:30:52.2788764Z +2022-11-01T15:30:52.2788764Z 2022-11-01T15:30:52.2789218Z PASS /datum/unit_test/species_config_sanity 0s 2022-11-01T15:30:52.2789672Z ##[endgroup] 2022-11-01T15:30:52.2965591Z ##[group]/datum/unit_test/species_unique_id -2022-11-01T15:30:52.2965794Z +2022-11-01T15:30:52.2965794Z 2022-11-01T15:30:52.2966648Z PASS /datum/unit_test/species_unique_id 0s 2022-11-01T15:30:52.2967087Z ##[endgroup] 2022-11-01T15:30:52.3145296Z ##[group]/datum/unit_test/species_whitelist_check -2022-11-01T15:30:52.3145500Z +2022-11-01T15:30:52.3145500Z 2022-11-01T15:30:52.3145979Z PASS /datum/unit_test/species_whitelist_check 0s 2022-11-01T15:30:52.3146421Z ##[endgroup] 2022-11-01T15:30:52.3319686Z ##[group]/datum/unit_test/spell_invocations -2022-11-01T15:30:52.3320202Z +2022-11-01T15:30:52.3320202Z 2022-11-01T15:30:52.3320653Z PASS /datum/unit_test/spell_invocations 0s 2022-11-01T15:30:52.3321098Z ##[endgroup] 2022-11-01T15:30:52.3490883Z ##[group]/datum/unit_test/mind_swap_spell -2022-11-01T15:30:52.4036851Z +2022-11-01T15:30:52.4036851Z 2022-11-01T15:30:52.4037676Z PASS /datum/unit_test/mind_swap_spell 0.1s 2022-11-01T15:30:52.4038275Z ##[endgroup] 2022-11-01T15:30:52.4828774Z ##[group]/datum/unit_test/spell_names -2022-11-01T15:30:52.4828988Z +2022-11-01T15:30:52.4828988Z 2022-11-01T15:30:52.4829550Z PASS /datum/unit_test/spell_names 0s 2022-11-01T15:30:52.4829982Z ##[endgroup] 2022-11-01T15:30:52.5000265Z ##[group]/datum/unit_test/shapeshift_spell_validity -2022-11-01T15:30:52.5004478Z +2022-11-01T15:30:52.5004478Z 2022-11-01T15:30:52.5005145Z PASS /datum/unit_test/shapeshift_spell_validity 0.1s 2022-11-01T15:30:52.5005621Z ##[endgroup] 2022-11-01T15:30:52.5165907Z ##[group]/datum/unit_test/shapeshift_spell -2022-11-01T15:30:52.8039754Z +2022-11-01T15:30:52.8039754Z 2022-11-01T15:30:52.8040573Z PASS /datum/unit_test/shapeshift_spell 0.3s 2022-11-01T15:30:52.8041200Z ##[endgroup] 2022-11-01T15:30:53.0783548Z ##[group]/datum/unit_test/shapeshift_holoparasites -2022-11-01T15:30:53.1126288Z +2022-11-01T15:30:53.1126288Z 2022-11-01T15:30:53.1127333Z PASS /datum/unit_test/shapeshift_holoparasites 0.1s 2022-11-01T15:30:53.1128391Z ##[endgroup] 2022-11-01T15:30:53.1559214Z ##[group]/datum/unit_test/spritesheets -2022-11-01T15:30:53.1593744Z +2022-11-01T15:30:53.1593744Z 2022-11-01T15:30:53.1596242Z PASS /datum/unit_test/spritesheets 0s 2022-11-01T15:30:53.1599434Z ##[endgroup] 2022-11-01T15:30:53.1775369Z ##[group]/datum/unit_test/stack_singular_name -2022-11-01T15:30:53.1777955Z +2022-11-01T15:30:53.1777955Z 2022-11-01T15:30:53.1780282Z PASS /datum/unit_test/stack_singular_name 0s 2022-11-01T15:30:53.1781032Z ##[endgroup] 2022-11-01T15:30:53.1978111Z ##[group]/datum/unit_test/stomach -2022-11-01T15:30:53.2306019Z +2022-11-01T15:30:53.2306019Z 2022-11-01T15:30:53.2308397Z PASS /datum/unit_test/stomach 0.1s 2022-11-01T15:30:53.2311132Z ##[endgroup] 2022-11-01T15:30:53.2563959Z ##[group]/datum/unit_test/strip_menu_ui_status -2022-11-01T15:30:53.3090477Z +2022-11-01T15:30:53.3090477Z 2022-11-01T15:30:53.3091739Z PASS /datum/unit_test/strip_menu_ui_status 0.1s 2022-11-01T15:30:53.3094850Z ##[endgroup] 2022-11-01T15:30:53.3492514Z ##[group]/datum/unit_test/subsystem_init -2022-11-01T15:30:53.3493184Z +2022-11-01T15:30:53.3493184Z 2022-11-01T15:30:53.3495636Z PASS /datum/unit_test/subsystem_init 0s 2022-11-01T15:30:53.3498157Z ##[endgroup] 2022-11-01T15:30:53.3664175Z ##[group]/datum/unit_test/suit_storage_icons @@ -2078,83 +2078,83 @@ 2022-11-01T15:30:55.7752422Z 258 - /obj/item/storage/lockbox/medal using invalid icon_state, "medalbox+l" 2022-11-01T15:30:55.7752898Z 259 - /obj/item/crowbar/red/caravan using invalid icon_state, "crowbar_caravan" 2022-11-01T15:30:55.7757058Z 260 - /obj/item/crowbar/drone using invalid icon_state, "crowbar_cyborg" -2022-11-01T15:30:55.7784906Z +2022-11-01T15:30:55.7784906Z 2022-11-01T15:30:55.7785304Z PASS /datum/unit_test/suit_storage_icons 2.4s 2022-11-01T15:30:55.7786128Z ##[endgroup] 2022-11-01T15:30:58.1989647Z ##[group]/datum/unit_test/amputation -2022-11-01T15:30:58.2542620Z +2022-11-01T15:30:58.2542620Z 2022-11-01T15:30:58.2543982Z PASS /datum/unit_test/amputation 0.1s 2022-11-01T15:30:58.2545007Z ##[endgroup] 2022-11-01T15:30:58.2861052Z ##[group]/datum/unit_test/brain_surgery -2022-11-01T15:30:58.3393398Z +2022-11-01T15:30:58.3393398Z 2022-11-01T15:30:58.3395072Z PASS /datum/unit_test/brain_surgery 0.1s 2022-11-01T15:30:58.3396351Z ##[endgroup] 2022-11-01T15:30:58.3713295Z ##[group]/datum/unit_test/head_transplant -2022-11-01T15:30:58.4648885Z +2022-11-01T15:30:58.4648885Z 2022-11-01T15:30:58.4650964Z PASS /datum/unit_test/head_transplant 0.1s 2022-11-01T15:30:58.4652113Z ##[endgroup] 2022-11-01T15:30:58.5529908Z ##[group]/datum/unit_test/multiple_surgeries -2022-11-01T15:30:58.6322272Z +2022-11-01T15:30:58.6322272Z 2022-11-01T15:30:58.6323733Z PASS /datum/unit_test/multiple_surgeries 0.1s 2022-11-01T15:30:58.6324863Z ##[endgroup] 2022-11-01T15:30:58.7192842Z ##[group]/datum/unit_test/start_tend_wounds -2022-11-01T15:30:58.7696686Z +2022-11-01T15:30:58.7696686Z 2022-11-01T15:30:58.7697933Z PASS /datum/unit_test/start_tend_wounds 0s 2022-11-01T15:30:58.7699007Z ##[endgroup] 2022-11-01T15:30:58.8006481Z ##[group]/datum/unit_test/tend_wounds -2022-11-01T15:30:58.9098267Z +2022-11-01T15:30:58.9098267Z 2022-11-01T15:30:58.9099817Z PASS /datum/unit_test/tend_wounds 0.1s 2022-11-01T15:30:58.9100900Z ##[endgroup] 2022-11-01T15:30:59.0574781Z ##[group]/datum/unit_test/auto_teleporter_linking -2022-11-01T15:30:59.0986969Z +2022-11-01T15:30:59.0986969Z 2022-11-01T15:30:59.0988029Z PASS /datum/unit_test/auto_teleporter_linking 0s 2022-11-01T15:30:59.0989210Z ##[endgroup] 2022-11-01T15:30:59.1397849Z ##[group]/datum/unit_test/tgui_create_message -2022-11-01T15:30:59.1398053Z +2022-11-01T15:30:59.1398053Z 2022-11-01T15:30:59.1398515Z PASS /datum/unit_test/tgui_create_message 0s 2022-11-01T15:30:59.1398931Z ##[endgroup] 2022-11-01T15:30:59.1585159Z ##[group]/datum/unit_test/timer_sanity -2022-11-01T15:30:59.1585371Z +2022-11-01T15:30:59.1585371Z 2022-11-01T15:30:59.1585837Z PASS /datum/unit_test/timer_sanity 0s 2022-11-01T15:30:59.1586293Z ##[endgroup] 2022-11-01T15:30:59.1763570Z ##[group]/datum/unit_test/traitor -2022-11-01T15:31:01.0798012Z +2022-11-01T15:31:01.0798012Z 2022-11-01T15:31:01.0799101Z PASS /datum/unit_test/traitor 1.9s 2022-11-01T15:31:01.0799858Z ##[endgroup] 2022-11-01T15:31:05.2187120Z ##[group]/datum/unit_test/verify_config_tags -2022-11-01T15:31:05.2190123Z +2022-11-01T15:31:05.2190123Z 2022-11-01T15:31:05.2191991Z PASS /datum/unit_test/verify_config_tags 0s 2022-11-01T15:31:05.2193777Z ##[endgroup] 2022-11-01T15:31:05.2366479Z ##[group]/datum/unit_test/wizard_loadout -2022-11-01T15:31:05.3463154Z +2022-11-01T15:31:05.3463154Z 2022-11-01T15:31:05.3463988Z PASS /datum/unit_test/wizard_loadout 0.1s 2022-11-01T15:31:05.3465519Z ##[endgroup] 2022-11-01T15:31:05.4918147Z ##[group]/datum/unit_test/find_reference_sanity -2022-11-01T15:31:05.4922156Z +2022-11-01T15:31:05.4922156Z 2022-11-01T15:31:05.4923183Z PASS /datum/unit_test/find_reference_sanity 0s 2022-11-01T15:31:05.4924120Z ##[endgroup] 2022-11-01T15:31:05.5122652Z ##[group]/datum/unit_test/find_reference_baseline -2022-11-01T15:31:05.5126059Z +2022-11-01T15:31:05.5126059Z 2022-11-01T15:31:05.5126875Z PASS /datum/unit_test/find_reference_baseline 0s 2022-11-01T15:31:05.5127655Z ##[endgroup] 2022-11-01T15:31:05.5361446Z ##[group]/datum/unit_test/find_reference_exotic -2022-11-01T15:31:05.5367626Z +2022-11-01T15:31:05.5367626Z 2022-11-01T15:31:05.5369228Z PASS /datum/unit_test/find_reference_exotic 0s 2022-11-01T15:31:05.5369940Z ##[endgroup] 2022-11-01T15:31:05.5551795Z ##[group]/datum/unit_test/find_reference_esoteric -2022-11-01T15:31:05.5558725Z +2022-11-01T15:31:05.5558725Z 2022-11-01T15:31:05.5559583Z PASS /datum/unit_test/find_reference_esoteric 0s 2022-11-01T15:31:05.5560670Z ##[endgroup] 2022-11-01T15:31:05.5737466Z ##[group]/datum/unit_test/find_reference_null_key_entry -2022-11-01T15:31:05.5741407Z +2022-11-01T15:31:05.5741407Z 2022-11-01T15:31:05.5742596Z PASS /datum/unit_test/find_reference_null_key_entry 0s 2022-11-01T15:31:05.5743489Z ##[endgroup] 2022-11-01T15:31:05.6053804Z ##[group]/datum/unit_test/find_reference_assoc_investigation -2022-11-01T15:31:05.6057935Z +2022-11-01T15:31:05.6057935Z 2022-11-01T15:31:05.6058863Z PASS /datum/unit_test/find_reference_assoc_investigation 0s 2022-11-01T15:31:05.6060051Z ##[endgroup] 2022-11-01T15:31:05.6233030Z ##[group]/datum/unit_test/find_reference_static_investigation -2022-11-01T15:31:05.8520469Z +2022-11-01T15:31:05.8520469Z 2022-11-01T15:31:05.8521954Z PASS /datum/unit_test/find_reference_static_investigation 0.2s 2022-11-01T15:31:05.8523118Z ##[endgroup] 2022-11-01T15:31:06.0724951Z ##[group]/datum/unit_test/monkey_business @@ -2166,8 +2166,8 @@ 2022-11-01T15:31:23.2325062Z /datum/forensics (/datum/forensics): New(the blood splatter (/obj/effect/decal/cleanable/blood/hitsplatter), null, null, /list (/list), null) 2022-11-01T15:31:23.2325582Z the blood splatter (/obj/effect/decal/cleanable/blood/hitsplatter): add blood DNA(/list (/list)) 2022-11-01T15:31:23.2326006Z Anthony Hayhurst (461) (/mob/living/carbon/human): spray blood(2, 1) -2022-11-01T15:31:23.2326395Z Rough Abrasion (/datum/wound/slash/moderate): wound injury(null, 2) -2022-11-01T15:31:23.2327172Z Rough Abrasion (/datum/wound/slash/moderate): apply wound(the monkey left leg (/obj/item/bodypart/l_leg/monkey), 0, null, 0, 2) +2022-11-01T15:31:23.2326395Z Rough Abrasion (/datum/wound/slash/flesh/moderate): wound injury(null, 2) +2022-11-01T15:31:23.2327172Z Rough Abrasion (/datum/wound/slash/flesh/moderate): apply wound(the monkey left leg (/obj/item/bodypart/l_leg/monkey), 0, null, 0, 2) 2022-11-01T15:31:23.2327747Z the monkey left leg (/obj/item/bodypart/l_leg/monkey): check wounding(2, 8, 5, 15, 2) 2022-11-01T15:31:23.2328155Z the monkey left leg (/obj/item/bodypart/l_leg/monkey): receive damage(8, 0, 0, 0, 1, null, 5, 15, 1, 2) 2022-11-01T15:31:23.2328594Z Monkey (/datum/species/monkey): apply damage(8, "brute", "l_leg", 0, Anthony Hayhurst (461) (/mob/living/carbon/human), 0, 0, 5, 15, 1, 2) @@ -2191,8 +2191,8 @@ /datum/forensics (/datum/forensics): New(the blood splatter (/obj/effect/decal/cleanable/blood/hitsplatter), null, null, /list (/list), null) the blood splatter (/obj/effect/decal/cleanable/blood/hitsplatter): add blood DNA(/list (/list)) Anthony Hayhurst (461) (/mob/living/carbon/human): spray blood(2, 1) - Rough Abrasion (/datum/wound/slash/moderate): wound injury(null, 2) - Rough Abrasion (/datum/wound/slash/moderate): apply wound(the monkey left leg (/obj/item/bodypart/l_leg/monkey), 0, null, 0, 2) + Rough Abrasion (/datum/wound/slash/flesh/moderate): wound injury(null, 2) + Rough Abrasion (/datum/wound/slash/flesh/moderate): apply wound(the monkey left leg (/obj/item/bodypart/l_leg/monkey), 0, null, 0, 2) the monkey left leg (/obj/item/bodypart/l_leg/monkey): check wounding(2, 8, 5, 15, 2) the monkey left leg (/obj/item/bodypart/l_leg/monkey): receive damage(8, 0, 0, 0, 1, null, 5, 15, 1, 2) Monkey (/datum/species/monkey): apply damage(8, "brute", "l_leg", 0, Anthony Hayhurst (461) (/mob/living/carbon/human), 0, 0, 5, 15, 1, 2) @@ -2217,8 +2217,8 @@ 2022-11-01T15:31:44.6333171Z /datum/forensics (/datum/forensics): New(the blood splatter (/obj/effect/decal/cleanable/blood/hitsplatter), null, null, /list (/list), null) 2022-11-01T15:31:44.6333653Z the blood splatter (/obj/effect/decal/cleanable/blood/hitsplatter): add blood DNA(/list (/list)) 2022-11-01T15:31:44.6334238Z Anthony Hayhurst (461) (/mob/living/carbon/human): spray blood(2, 1) -2022-11-01T15:31:44.6334605Z Rough Abrasion (/datum/wound/slash/moderate): wound injury(null, 2) -2022-11-01T15:31:44.6335055Z Rough Abrasion (/datum/wound/slash/moderate): apply wound(the monkey left leg (/obj/item/bodypart/l_leg/monkey), 0, null, 0, 2) +2022-11-01T15:31:44.6334605Z Rough Abrasion (/datum/wound/slash/flesh/moderate): wound injury(null, 2) +2022-11-01T15:31:44.6335055Z Rough Abrasion (/datum/wound/slash/flesh/moderate): apply wound(the monkey left leg (/obj/item/bodypart/l_leg/monkey), 0, null, 0, 2) 2022-11-01T15:31:44.6335679Z the monkey left leg (/obj/item/bodypart/l_leg/monkey): check wounding(2, 8, 5, 15, 2) 2022-11-01T15:31:44.6336429Z the monkey left leg (/obj/item/bodypart/l_leg/monkey): receive damage(8, 0, 0, 0, 1, null, 5, 15, 1, 2) 2022-11-01T15:31:44.6336913Z Monkey (/datum/species/monkey): apply damage(8, "brute", "l_leg", 0, Anthony Hayhurst (461) (/mob/living/carbon/human), 0, 0, 5, 15, 1, 2) @@ -2236,7 +2236,7 @@ 2022-11-01T15:31:44.6343234Z Master (/datum/controller/master): StartProcessing(0) at _forensics.dm:232 2022-11-01T15:31:44.6345243Z ##[error]FAIL /datum/unit_test/monkey_business 38.6s 2022-11-01T15:31:46.9833542Z ##[group]/datum/unit_test/create_and_destroy -2022-11-01T15:37:15.1785471Z +2022-11-01T15:37:15.1785471Z 2022-11-01T15:37:15.1787041Z PASS /datum/unit_test/create_and_destroy 328.2s 2022-11-01T15:37:15.1788074Z ##[endgroup] 2022-11-01T15:37:15.2115771Z Shutting down Chat subsystem... @@ -2377,12 +2377,12 @@ 2022-11-01T15:37:31.3325316Z Total size of all the files uploaded is 138917 bytes 2022-11-01T15:37:31.3326061Z File upload process has finished. Finalizing the artifact upload 2022-11-01T15:37:31.4289346Z Artifact has been finalized. All files have been successfully uploaded! -2022-11-01T15:37:31.4291368Z +2022-11-01T15:37:31.4291368Z 2022-11-01T15:37:31.4293992Z The raw size of all the files that were specified for upload is 139272 bytes 2022-11-01T15:37:31.4298141Z The size of all the files that were uploaded is 138917 bytes. This takes into account any gzip compression used to reduce the upload size, time and storage -2022-11-01T15:37:31.4301961Z -2022-11-01T15:37:31.4303707Z Note: The size of downloaded zips can differ significantly from the reported size. For more information see: https://github.com/actions/upload-artifact#zipped-artifact-downloads -2022-11-01T15:37:31.4304326Z +2022-11-01T15:37:31.4301961Z +2022-11-01T15:37:31.4303707Z Note: The size of downloaded zips can differ significantly from the reported size. For more information see: https://github.com/actions/upload-artifact#zipped-artifact-downloads +2022-11-01T15:37:31.4304326Z 2022-11-01T15:37:31.4305332Z Artifact test_artifacts_tramstation has been successfully uploaded! 2022-11-01T15:37:31.4451625Z Post job cleanup. 2022-11-01T15:37:31.5950708Z [command]/usr/bin/git version @@ -2436,12 +2436,12 @@ 2022-11-01T15:37:31.7590084Z 2022-11-01 15:22:42+00:00 [Note] [Entrypoint]: Starting temporary server 2022-11-01T15:37:31.7590409Z 2022-11-01 15:22:43+00:00 [Note] [Entrypoint]: Temporary server started. 2022-11-01T15:37:31.7590742Z '/var/lib/mysql/mysql.sock' -> '/var/run/mysqld/mysqld.sock' -2022-11-01T15:37:31.7590984Z +2022-11-01T15:37:31.7590984Z 2022-11-01T15:37:31.7591244Z 2022-11-01 15:22:46+00:00 [Note] [Entrypoint]: Stopping temporary server 2022-11-01T15:37:31.7591577Z 2022-11-01 15:22:47+00:00 [Note] [Entrypoint]: Temporary server stopped -2022-11-01T15:37:31.7592516Z +2022-11-01T15:37:31.7592516Z 2022-11-01T15:37:31.7592791Z 2022-11-01 15:22:47+00:00 [Note] [Entrypoint]: MySQL init process done. Ready for start up. -2022-11-01T15:37:31.7593063Z +2022-11-01T15:37:31.7593063Z 2022-11-01T15:37:31.7608136Z Stop and remove container: e281b5d836644f53b33d06a88663b086_mysqllatest_c6a68e 2022-11-01T15:37:31.7616365Z ##[command]/usr/bin/docker rm --force cd1d0f20f8f882176c274fb882d88cea0d4e9a7b2aebc33f1123c02f7b908aa1 2022-11-01T15:37:32.0241098Z cd1d0f20f8f882176c274fb882d88cea0d4e9a7b2aebc33f1123c02f7b908aa1