diff --git a/_maps/map_files/debug/roguetest.dmm b/_maps/map_files/debug/roguetest.dmm
index aa234934455..61c38112bf0 100644
--- a/_maps/map_files/debug/roguetest.dmm
+++ b/_maps/map_files/debug/roguetest.dmm
@@ -1036,16 +1036,12 @@
/obj/structure/mana_pylon,
/turf/open/floor/churchbrick,
/area/indoors/town/keep)
-"dh" = (
-/obj/machinery/light/fueled/chand{
- plane = -8;
- layer = 4
- },
-/turf/open/floor/carpet/lord/center,
-/area/indoors/town/keep)
"dl" = (
/turf/open/floor/blocks,
/area/indoors/town/keep)
+"dz" = (
+/turf/closed/wall/mineral/stone/window,
+/area)
"dL" = (
/obj/machinery/light/fueled/torchholder/hotspring/standing{
pixel_x = 15;
@@ -1063,11 +1059,6 @@
/obj/machinery/light/fueled/torchholder/r,
/turf/open/floor/churchbrick,
/area/indoors/town/keep)
-"ea" = (
-/obj/structure/carpet/purple,
-/obj/machinery/light/fueled/firebowl/church,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"ef" = (
/obj/structure/hotspring/border/five,
/obj/structure/flora/hotspring_rocks{
@@ -1099,11 +1090,6 @@
/obj/structure/rotation_piece/cog,
/turf/open/floor/grass/yel,
/area/outdoors)
-"eB" = (
-/obj/structure/carpet/purple,
-/obj/structure/chair/stool,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"eE" = (
/obj/structure/stairs/stone/reddish{
dir = 8
@@ -1176,23 +1162,6 @@
/obj/structure/fake_machine/scomm/l,
/turf/open/floor/cobble,
/area/indoors)
-"gm" = (
-/obj/structure/stairs{
- dir = 1
- },
-/obj/structure/fluff/railing/wood{
- dir = 8;
- pixel_y = -1
- },
-/turf/open/floor/wood,
-/area/indoors/town/keep)
-"gp" = (
-/obj/structure/fluff/railing/wood{
- dir = 4;
- pixel_y = -1
- },
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"gr" = (
/obj/structure/water_pipe,
/obj/structure/sprinkler,
@@ -1226,12 +1195,6 @@
},
/turf/open/floor/wood,
/area/indoors)
-"gG" = (
-/obj/effect/decal/carpet/kover_darkred{
- pixel_y = 25
- },
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"gJ" = (
/obj/structure/flora/shroom_tree{
pixel_x = -9
@@ -1268,36 +1231,10 @@
/obj/structure/table/wood/treestump/shroomstump,
/turf/open/floor/dirt,
/area/outdoors/mountains/decap)
-"hm" = (
-/obj/structure/fluff/railing/wood{
- dir = 4;
- pixel_y = -1
- },
-/obj/structure/fluff/railing/wood,
-/turf/open/floor/wood,
-/area/indoors/town/keep)
-"hp" = (
-/obj/structure/fluff/railing/wood,
-/obj/structure/chair/stool,
-/turf/open/floor/wood,
-/area/indoors/town/keep)
-"hC" = (
-/obj/structure/fluff/railing/wood{
- dir = 8;
- pixel_y = -1
- },
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"hF" = (
/obj/structure/dungeon_entry/center,
/turf/open/floor/dirt,
/area/outdoors/mountains/decap)
-"hJ" = (
-/obj/structure/fluff/railing/wood{
- dir = 1
- },
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"hN" = (
/obj/structure/hotspring/border/two,
/obj/structure/bridge{
@@ -1328,14 +1265,6 @@
/obj/machinery/essence/divider,
/turf/open/floor/churchbrick,
/area/indoors/town/keep)
-"iq" = (
-/obj/structure/fluff/railing/wood,
-/turf/open/floor/wood,
-/area/indoors/town/keep)
-"iv" = (
-/obj/structure/notice_board,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"iE" = (
/obj/machinery/light/fueled/torchholder/hotspring/standing{
pixel_x = 15
@@ -1351,14 +1280,6 @@
/obj/structure/hotspring/border/three,
/turf/open/floor/dirt/road,
/area/outdoors)
-"jk" = (
-/obj/structure/fluff/railing/wood,
-/obj/structure/fluff/railing/wood{
- dir = 4;
- pixel_y = -1
- },
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"jp" = (
/obj/effect/decal/carpet/square{
pixel_y = -17;
@@ -1423,12 +1344,10 @@
/obj/structure/closet/crate/miningcar,
/turf/open/floor/grass/yel,
/area/outdoors)
-"kc" = (
-/obj/structure/stairs/stone{
- dir = 1
- },
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
+"ke" = (
+/obj/machinery/light/fueled/firebowl,
+/turf/open/floor/wood,
+/area)
"ki" = (
/obj/structure/rotation_piece/cog{
dir = 4
@@ -1490,18 +1409,6 @@
},
/turf/open/floor/cobblerock,
/area/outdoors/mountains/decap)
-"lR" = (
-/obj/structure/door,
-/turf/open/floor/carpet/lord/center,
-/area/indoors/town/keep)
-"lT" = (
-/obj/structure/fluff/railing/wood,
-/obj/structure/fluff/railing/wood{
- dir = 8;
- pixel_y = -1
- },
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"lV" = (
/obj/machinery/light/fueled/torchholder/r,
/turf/open/floor/cobble,
@@ -1537,11 +1444,6 @@
},
/turf/open/floor/dirt,
/area/outdoors/mountains/decap)
-"mG" = (
-/obj/structure/carpet/blue,
-/obj/structure/table/wood/plain/alt,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"mM" = (
/mob/living/carbon/human/species/rousman{
name = "UNIQUE BLOCKER TESTER"
@@ -1552,6 +1454,12 @@
/obj/structure/flora/ausbushes/lavendergrass,
/turf/open/floor/grass/yel,
/area/indoors)
+"mR" = (
+/obj/structure/waterwheel,
+/turf/open/water/river{
+ dir = 4
+ },
+/area)
"mY" = (
/turf/open/floor/wood,
/area/indoors)
@@ -1581,10 +1489,6 @@
},
/turf/open/floor/grass/yel,
/area/outdoors)
-"ot" = (
-/obj/structure/bookcase/random_recipes,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"ou" = (
/turf/closed/wall/mineral/decorstone,
/area/outdoors)
@@ -1637,18 +1541,10 @@
/obj/structure/ladder,
/turf/open/floor/grass/yel,
/area/outdoors)
-"px" = (
-/turf/closed/basic,
-/area/outdoors/wilderness/beside_thatchwood)
"pD" = (
/obj/structure/hotspring/border/fourteen,
/turf/open/floor/dirt/road,
/area/outdoors)
-"pG" = (
-/obj/structure/table/wood/plain,
-/obj/structure/quest_counter,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"pQ" = (
/obj/structure/rotation_piece,
/turf/open/floor/wood,
@@ -1703,10 +1599,6 @@
/obj/item/clothing/shoes/nobleboot/thighboots,
/turf/open/floor/churchbrick,
/area/indoors/town/keep)
-"qw" = (
-/obj/machinery/light/fueled/torchholder/c,
-/turf/open/floor/wood,
-/area/indoors/town/keep)
"qG" = (
/obj/machinery/light/fueled/torchholder/hotspring/standing{
pixel_x = 15;
@@ -1715,10 +1607,6 @@
/obj/structure/flora/grass,
/turf/open/floor/grass/yel,
/area/outdoors)
-"qM" = (
-/obj/machinery/light/fueled/torchholder/c,
-/turf/open/floor/ruinedwood/spiral,
-/area/indoors/town/keep)
"qY" = (
/obj/structure/flora/ausbushes/brflowers,
/turf/open/floor/cobble/mossy,
@@ -1745,11 +1633,6 @@
},
/turf/open/floor/grass/yel,
/area/outdoors)
-"rl" = (
-/obj/structure/carpet/cyan,
-/obj/structure/table/wood/plain/alt,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"rm" = (
/obj/structure/flora/shroom_tree{
pixel_x = -36;
@@ -1834,9 +1717,9 @@
/turf/open/floor/cobblerock,
/area/indoors/town/keep)
"sU" = (
-/obj/machinery/light/fueled/firebowl/church,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
+/obj/machinery/light/fueled/firebowl,
+/turf/closed/wall/mineral/stone/window,
+/area)
"sX" = (
/obj/effect/decal/cleanable/ritual_rune/arcyne/spellobject_imbue,
/turf/open/floor/churchbrick,
@@ -1924,27 +1807,13 @@
},
/turf/open/floor/grass/yel,
/area/outdoors)
-"uy" = (
-/obj/structure/fluff/railing/wood{
- dir = 4;
- pixel_y = -1
- },
-/turf/open/floor/wood,
-/area/indoors/town/keep)
-"uF" = (
-/obj/structure/carpet/red,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
-"uH" = (
-/obj/structure/fluff/railing/wood{
- dir = 1
- },
-/turf/open/floor/carpet/lord/center,
-/area/indoors/town/keep)
"uL" = (
/obj/structure/hotspring/border/two,
/turf/open/floor/cobblerock,
/area/outdoors/mountains/decap)
+"uN" = (
+/turf/open/floor/wood,
+/area)
"uV" = (
/turf/open/lava/flow{
dir = 8
@@ -1965,10 +1834,6 @@
/obj/structure/flora/grass/water/reeds,
/turf/open/water/clean,
/area/outdoors)
-"vy" = (
-/obj/machinery/light/fueled/torchholder/r,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"vD" = (
/turf/open/floor/churchbrick,
/area/indoors/town/keep)
@@ -1983,22 +1848,10 @@
/obj/structure/hotspring/border/nine,
/turf/open/floor/cobblerock,
/area/outdoors/mountains/decap)
-"wq" = (
-/obj/structure/fluff/railing/wood{
- dir = 8;
- pixel_y = -1
- },
-/obj/structure/fermentation_keg/random/water{
- pixel_y = 5;
- pixel_x = -3
- },
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
-"wC" = (
-/obj/structure/carpet/blue,
-/obj/structure/chair/stool,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
+"wi" = (
+/obj/structure/rotation_piece/cog/large,
+/turf/open/floor/grass/yel,
+/area)
"wD" = (
/obj/structure/bridge_stakes{
dir = 8
@@ -2040,20 +1893,10 @@
},
/turf/open/floor/blocks,
/area/indoors/town/keep)
-"wO" = (
-/obj/structure/stairs/fancy/c{
- dir = 1
- },
-/turf/open/floor/carpet/lord/center,
-/area/indoors/town/keep)
"wR" = (
/obj/item/reagent_containers/glass/carafe/water,
/turf/open/floor/grass/yel,
/area/outdoors)
-"wW" = (
-/obj/structure/fluff/railing/wood,
-/turf/open/floor/ruinedwood/spiral,
-/area/indoors/town/keep)
"wY" = (
/obj/structure/flora/sakura{
pixel_y = -13
@@ -2126,16 +1969,6 @@
},
/turf/open/floor/churchbrick,
/area/indoors/town/keep)
-"yH" = (
-/obj/effect/mapping_helpers/broodmother_entry,
-/turf/closed/mineral/bedrock,
-/area/outdoors)
-"yM" = (
-/obj/machinery/light/fueled/wallfire{
- pixel_y = 11
- },
-/turf/open/floor/wood,
-/area/indoors/town/keep)
"yP" = (
/turf/closed/mineral/bedrock,
/area/outdoors/mountains/decap)
@@ -2375,16 +2208,6 @@
/obj/structure/hotspring/border/eleven,
/turf/open/floor/dirt/road,
/area/outdoors)
-"zV" = (
-/obj/structure/fluff/railing/wood{
- dir = 1
- },
-/obj/structure/fluff/railing/wood{
- dir = 4;
- pixel_y = -1
- },
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"Ab" = (
/obj/structure/hotspring/border/thirteen,
/obj/machinery/light/fueled/torchholder/hotspring/standing{
@@ -2414,13 +2237,6 @@
},
/turf/open/floor/grass/yel,
/area/outdoors)
-"Ax" = (
-/obj/structure/stairs{
- dir = 8
- },
-/obj/structure/fluff/railing/wood,
-/turf/open/floor/wood,
-/area/indoors/town/keep)
"Az" = (
/obj/machinery/light/fueled/torchholder/hotspring/standing{
pixel_x = -8;
@@ -2453,10 +2269,6 @@
/obj/structure/water_pipe,
/turf/closed/wall/mineral/wood,
/area/indoors)
-"Ba" = (
-/obj/structure/fluff/railing/wood,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"Bu" = (
/obj/structure/fake_machine/stockpile,
/turf/open/floor/churchbrick,
@@ -2476,10 +2288,10 @@
/obj/effect/falling_sakura,
/turf/open/water/hotspring,
/area/outdoors)
-"Cv" = (
-/obj/structure/chair/stool,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
+"Ck" = (
+/obj/structure/stairs/stone/reddish,
+/turf/open/floor/wood,
+/area)
"Cz" = (
/obj/item/mana_battery/mana_crystal/standard,
/obj/effect/decal/cleanable/ritual_rune/arcyne/mana_siphon/flight,
@@ -2489,10 +2301,9 @@
/obj/structure/soil/debug_soil/random,
/turf/open/floor/dirt/road,
/area/indoors/town/keep)
-"CJ" = (
-/obj/machinery/light/fueled/firebowl/standing,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
+"CN" = (
+/turf/closed/mineral/bedrock,
+/area)
"CO" = (
/obj/effect/landmark/observer_start,
/turf/open/floor/cobblerock,
@@ -2535,13 +2346,6 @@
"DW" = (
/turf/open/floor/wood,
/area/outdoors)
-"Ek" = (
-/obj/structure/stairs{
- dir = 4
- },
-/obj/structure/fluff/railing/wood,
-/turf/open/floor/wood,
-/area/indoors/town/keep)
"EI" = (
/obj/structure/gearbox,
/turf/open/floor/cobblerock,
@@ -2601,6 +2405,10 @@
"Fy" = (
/turf/closed/wall/mineral/decorstone,
/area/indoors)
+"Fz" = (
+/obj/machinery/light/fueled/firebowl,
+/turf/open/floor/dirt,
+/area)
"FM" = (
/obj/item/spell_focus,
/turf/open/floor/churchbrick,
@@ -2735,10 +2543,6 @@
/obj/item/arcyne_spellobject/wand,
/turf/open/floor/churchbrick,
/area/indoors/town/keep)
-"IF" = (
-/obj/machinery/light/fueled/torchholder/l,
-/turf/open/floor/wood,
-/area/indoors/town/keep)
"IK" = (
/obj/structure/irrigation_channel,
/obj/structure/rotation_piece{
@@ -2767,13 +2571,6 @@
"Jd" = (
/turf/closed/wall/mineral/stone,
/area)
-"Ji" = (
-/obj/structure/fluff/railing/wood{
- dir = 8;
- pixel_y = -1
- },
-/turf/open/floor/wood,
-/area/indoors/town/keep)
"Jj" = (
/obj/structure/hotspring/border/eight,
/turf/open/floor/cobblerock,
@@ -2789,17 +2586,11 @@
/turf/open/floor/blocks,
/area/indoors/town/keep)
"Jz" = (
-/obj/structure/window/openclose,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
-"JK" = (
-/obj/structure/fluff/railing/wood{
- dir = 4;
- pixel_y = -1
+/obj/structure/stairs/stone/reddish{
+ dir = 1
},
-/obj/structure/closet/crate/chest,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
+/turf/open/floor/concrete,
+/area)
"JM" = (
/obj/structure/well,
/turf/open/floor/cobblerock,
@@ -2954,6 +2745,9 @@
},
/turf/open/floor/cobblerock,
/area/outdoors)
+"MT" = (
+/turf/open/floor/grass/yel,
+/area)
"MV" = (
/obj/structure/flora/ausbushes/ywflowers,
/obj/structure/flora/grass,
@@ -2983,10 +2777,6 @@
/obj/structure/flora/ausbushes/ywflowers,
/turf/open/floor/grass/yel,
/area/outdoors)
-"Nf" = (
-/obj/structure/carpet/cyan,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"No" = (
/obj/structure/water_pipe,
/obj/structure/pressurizer{
@@ -2998,6 +2788,9 @@
/obj/structure/irrigation_channel,
/turf/open/floor/grass/yel,
/area/outdoors)
+"NK" = (
+/turf/open/floor/concrete,
+/area)
"NM" = (
/obj/effect/lily_petal/two,
/turf/open/water/hotspring,
@@ -3012,14 +2805,6 @@
},
/turf/open/floor/wood,
/area/indoors)
-"Oq" = (
-/obj/structure/fluff/railing/wood{
- dir = 8;
- pixel_y = -1
- },
-/obj/structure/chair/stool,
-/turf/open/floor/wood,
-/area/indoors/town/keep)
"OK" = (
/turf/open/floor/cobblerock,
/area/indoors/town/keep)
@@ -3069,21 +2854,16 @@
/turf/open/floor/grass/yel,
/area/outdoors)
"PE" = (
-/obj/structure/chair/stool,
-/turf/open/floor/wood,
-/area/indoors/town/keep)
+/obj/machinery/light/fueled/firebowl,
+/turf/open/floor/concrete,
+/area)
+"PX" = (
+/turf/open/floor/cobblerock,
+/area)
"Qi" = (
/obj/structure/flora/ausbushes/lavendergrass,
/turf/open/floor/grass/yel,
/area/outdoors)
-"Qn" = (
-/obj/structure/fluff/railing/wood,
-/turf/open/floor/carpet/lord/center,
-/area/indoors/town/keep)
-"Qw" = (
-/mob/living/carbon/human/species/goblin/npc/ambush,
-/turf/closed/basic,
-/area/outdoors/wilderness/beside_thatchwood)
"QA" = (
/obj/structure/water_pipe,
/obj/structure/sprinkler,
@@ -3098,10 +2878,6 @@
"QN" = (
/turf/open/floor/grass/yel,
/area/outdoors)
-"QP" = (
-/obj/structure/carpet/purple,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"QR" = (
/obj/machinery/light/fueled/torchholder/hotspring/standing{
pixel_x = -15
@@ -3121,14 +2897,14 @@
},
/turf/open/floor/dirt/road,
/area/outdoors)
+"Rr" = (
+/obj/machinery/light/fueled/firebowl,
+/turf/open/openspace,
+/area)
"RC" = (
/obj/structure/hotspring/border/two,
/turf/open/floor/dirt/road,
/area/outdoors)
-"RD" = (
-/obj/structure/table/wood/plain/alt,
-/turf/open/floor/wood,
-/area/indoors/town/keep)
"RF" = (
/obj/structure/hotspring/border,
/turf/open/floor/dirt/road,
@@ -3151,16 +2927,6 @@
},
/turf/open/floor/grass/yel,
/area/outdoors)
-"Sw" = (
-/obj/structure/fluff/railing/wood{
- dir = 1
- },
-/obj/structure/fluff/railing/wood{
- dir = 8;
- pixel_y = -1
- },
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"Sx" = (
/obj/structure/flora/grass,
/obj/structure/minecart_rail,
@@ -3255,14 +3021,6 @@
/obj/structure/roguetent/preopen,
/turf/open/floor/dirt,
/area/outdoors/mountains/decap)
-"TU" = (
-/obj/structure/fluff/railing/wood{
- dir = 8;
- pixel_y = -1
- },
-/obj/structure/fluff/railing/wood,
-/turf/open/floor/wood,
-/area/indoors/town/keep)
"Ua" = (
/turf/open/water/river{
dir = 4
@@ -3288,22 +3046,15 @@
/obj/machinery/essence/reservoir,
/turf/open/floor/churchbrick,
/area/indoors/town/keep)
-"Uq" = (
-/obj/structure/carpet/cyan,
-/obj/structure/chair/stool,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
-"Us" = (
-/turf/open/floor/carpet/lord/center,
-/area/indoors/town/keep)
"UD" = (
/turf/open/water/hotspring,
/area/outdoors/mountains/decap)
-"UG" = (
-/obj/structure/carpet/purple,
-/obj/structure/table/wood/plain/alt,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
+"UE" = (
+/obj/structure/stairs/stone/reddish{
+ dir = 1
+ },
+/turf/open/floor/wood,
+/area)
"UW" = (
/obj/structure/table/vtable,
/obj/effect/landmark/quest_dropoff{
@@ -3365,21 +3116,6 @@
/obj/structure/flora/grass,
/turf/open/floor/grass/yel,
/area/outdoors)
-"VF" = (
-/obj/structure/fermentation_keg/random/water{
- pixel_y = 5;
- pixel_x = -7
- },
-/obj/structure/fermentation_keg/random/water{
- pixel_y = 5;
- pixel_x = 9
- },
-/obj/structure/fermentation_keg/random/water{
- pixel_y = -6;
- pixel_x = 9
- },
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"VG" = (
/obj/structure/flora/grass/herb/urtica,
/turf/open/floor/grass/yel,
@@ -3407,6 +3143,9 @@
"Wr" = (
/turf/open/floor/cobblerock,
/area/outdoors)
+"Ww" = (
+/turf/closed/wall/mineral/wood,
+/area)
"Wx" = (
/obj/structure/door/red{
name = "dark room"
@@ -3430,10 +3169,6 @@
},
/turf/open/floor/grass/yel,
/area/outdoors)
-"WN" = (
-/obj/structure/table/wood/plain,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"WU" = (
/obj/item/reagent_containers/food/snacks/apple_dried{
pixel_x = -9
@@ -3563,20 +3298,17 @@
},
/turf/open/floor/blocks,
/area/indoors/town/keep)
+"Xf" = (
+/obj/structure/stairs/stone/reddish,
+/turf/open/floor/concrete,
+/area)
+"Xj" = (
+/turf/open/floor/dirt,
+/area)
"XL" = (
/obj/structure/hotspring/border/twelve,
/turf/open/floor/dirt/road,
/area/outdoors)
-"XQ" = (
-/obj/structure/stairs{
- dir = 1
- },
-/obj/structure/fluff/railing/wood{
- dir = 4;
- pixel_y = -1
- },
-/turf/open/floor/wood,
-/area/indoors/town/keep)
"XS" = (
/obj/structure/door,
/turf/open/floor/churchbrick,
@@ -3610,14 +3342,14 @@
/obj/structure/flora/grass,
/turf/open/floor/grass/yel,
/area/outdoors)
+"YP" = (
+/obj/structure/flora/ausbushes/ppflowers,
+/turf/open/floor/grass/yel,
+/area)
"YR" = (
/obj/structure/hotspring/border/twelve,
/turf/open/floor/cobblerock,
/area/outdoors/mountains/decap)
-"YU" = (
-/obj/structure/carpet/blue,
-/turf/open/floor/church/gold,
-/area/indoors/town/keep)
"Zs" = (
/turf/closed/wall/mineral/wood,
/area/indoors)
@@ -19514,28 +19246,28 @@ as
as
as
as
-at
-at
-at
-at
-QN
-QN
-QN
-at
-at
-at
+CN
+CN
+CN
+CN
+MT
+MT
+MT
+CN
+CN
+CN
ac
ac
ac
ac
-at
-Yr
-xh
-QN
-QN
-ai
-jC
-Zs
+CN
+wi
+mR
+MT
+MT
+Xj
+YP
+Ww
Zs
Zs
Zs
@@ -19774,26 +19506,26 @@ as
as
as
as
-at
-at
-at
-at
-zg
-zg
-zg
-zg
-zg
-zg
-zg
-zg
-zg
-zg
-at
-at
-QN
-at
-at
-at
+CN
+CN
+CN
+CN
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+CN
+CN
+MT
+CN
+CN
+CN
as
as
as
@@ -20021,11 +19753,6 @@ as
as
as
as
-zg
-zg
-zg
-zg
-zg
as
as
as
@@ -20034,23 +19761,28 @@ as
as
as
as
-zg
-zg
-IF
+as
+as
+as
+as
+NK
+NK
+NK
+NK
PE
-RD
-iq
-Zv
-oZ
-Zv
-Zv
-zg
-zg
-at
-QN
-ai
-QN
-at
+dz
+NK
+NK
+NK
+dz
+NK
+NK
+NK
+NK
+NK
+Xj
+MT
+CN
as
as
as
@@ -20276,39 +20008,39 @@ as
as
as
as
-zg
-zg
-zg
-Zv
-Zv
-Zv
-zg
-zg
-zg
as
as
as
as
as
-zg
-zg
-GB
-GB
-GB
-PE
-iq
-iv
-gG
-Zv
-Zv
-Zv
-zg
-at
-Wr
-Wr
-QN
-at
-at
+as
+as
+as
+as
+as
+as
+as
+as
+as
+NK
+NK
+NK
+NK
+NK
+dz
+NK
+NK
+NK
+dz
+NK
+NK
+NK
+NK
+NK
+PX
+MT
+CN
+CN
as
as
as
@@ -20532,40 +20264,40 @@ as
as
as
as
-zg
-zg
-Zv
-Zv
-Zv
-Zv
-Zv
-Zv
-Zv
-zg
-zg
as
as
as
as
-zg
-qw
-GB
-GB
-uy
-uy
-hm
-Zv
-Zv
-wC
-mG
-Zv
-zg
-zg
-Wr
-Wr
-QN
-at
-at
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+NK
+NK
+NK
+NK
+NK
+dz
+NK
+NK
+NK
+sU
+NK
+NK
+NK
+NK
+NK
+PX
+MT
+CN
+CN
as
as
as
@@ -20788,41 +20520,41 @@ as
as
as
as
-zg
-zg
-oZ
-Zv
-Zv
-Zv
-Zv
-Zv
-Zv
-Zv
-oZ
-zg
-zg
-zg
-zg
-zg
-zg
-GB
-GB
-hm
-Zv
-uF
-Zv
-Zv
-Zv
-YU
-wC
-Zv
-oZ
-zg
-zg
-ai
-ai
-QN
-at
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+NK
+NK
+dz
+dz
+NK
+dz
+NK
+NK
+NK
+dz
+NK
+NK
+NK
+NK
+NK
+Xj
+Fz
+MT
+CN
as
as
as
@@ -21045,41 +20777,41 @@ as
as
as
as
-zg
-Zv
-Zv
-Zv
-Zv
-gp
-gp
-gp
-Zv
-Zv
-Zv
-Zv
-zg
-qM
-XQ
-XQ
-wW
-GB
-iq
-CJ
-Cv
-uF
-WN
-Cv
-Zv
-wC
-YU
-Zv
-Zv
-Zv
-zg
-ai
-Wr
-QN
-at
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+NK
+NK
+NK
+NK
+NK
+dz
+NK
+NK
+NK
+dz
+NK
+NK
+NK
+NK
+NK
+Xj
+PX
+MT
+CN
as
as
as
@@ -21301,42 +21033,42 @@ as
as
as
as
-zg
-zg
-Zv
-Zv
-Zv
-jk
-px
-px
-Qw
-zV
-Zv
-Zv
-Zv
-zg
-Ek
-GB
-iq
-Ax
-GB
-iq
-Zv
-Zv
-uF
-pG
-Zv
-Zv
-mG
-wC
-Zv
-Zv
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+NK
+NK
+NK
+NK
+Xf
+dz
+dz
+dz
+dz
+dz
+dz
+dz
+dz
sU
Jz
-Wr
-Wr
-QN
-at
+PX
+PX
+MT
+CN
as
as
as
@@ -21558,42 +21290,42 @@ as
as
as
as
-zg
-Zv
-Zv
-Zv
-Ba
-px
-px
-px
-px
-px
-hJ
-Zv
-Zv
-Zv
-Ba
-GB
-GB
-GB
-GB
-iq
-JK
-WN
-WN
-Zv
-Zv
-Zv
-Zv
-Zv
-Zv
-Zv
-kc
-zg
-Wr
-Wr
-ai
-at
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+NK
+NK
+NK
+dz
+NK
+dz
+NK
+NK
+NK
+dz
+NK
+NK
+NK
+dz
+NK
+PX
+PX
+Xj
+CN
as
as
as
@@ -21815,42 +21547,42 @@ as
as
as
as
-zg
-Zv
-Zv
-Zv
-Ba
-Qw
-px
-px
-px
-px
-uH
-Us
-Us
-Us
-Qn
-yM
-GB
-GB
-GB
-Us
-wO
-Us
-Us
-Us
-Us
-dh
-Us
-Us
-Us
-Us
-Us
-lR
-Wr
-ai
-Wr
-yH
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+NK
+NK
+NK
+sU
+NK
+dz
+NK
+dz
+NK
+dz
+NK
+dz
+NK
+dz
+NK
+PX
+Xj
+PX
+CN
as
as
as
@@ -22072,42 +21804,42 @@ as
as
as
as
-zg
-Zv
-Zv
-Zv
-Ba
-px
-px
-Qw
-px
-px
-hJ
-Zv
-Zv
-Zv
-Ba
-GB
-GB
-GB
-GB
-iq
-wq
-pG
-WN
-Cv
-Zv
-Zv
-Zv
-Zv
-Zv
-Zv
-Zv
-zg
-Wr
-ai
-Wr
-at
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+NK
+NK
+dz
+NK
+NK
+NK
+NK
+dz
+NK
+NK
+NK
+dz
+NK
+NK
+NK
+PX
+Xj
+PX
+CN
as
as
as
@@ -22329,42 +22061,42 @@ as
as
as
as
-zg
-zg
-Zv
-Zv
-Zv
-lT
-px
-px
-px
-Sw
-Zv
-Zv
-Zv
-zg
-Ax
-GB
-iq
-Ek
-GB
-iq
-uF
-uF
-uF
-WN
-Zv
-Zv
-Zv
-Zv
-Zv
-Cv
-ea
-Jz
-Wr
-ai
-Wr
-at
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+NK
+NK
+dz
+NK
+dz
+sU
+dz
+dz
+dz
+dz
+dz
+dz
+dz
+dz
+dz
+PX
+Xj
+PX
+CN
as
as
as
@@ -22587,41 +22319,41 @@ as
as
as
as
-zg
-Zv
-Zv
-Zv
-Zv
-hC
-hC
-hC
-Zv
-Zv
-Zv
-Zv
-zg
-qM
-gm
-gm
-wW
-GB
-iq
-VF
-Zv
-Cv
-WN
-Cv
-Nf
-Uq
-Nf
-Zv
-UG
-QP
-zg
-ai
-ai
-QN
-at
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+NK
+NK
+dz
+NK
+NK
+NK
+NK
+NK
+NK
+NK
+NK
+dz
+NK
+NK
+NK
+Xj
+Xj
+MT
+CN
as
as
as
@@ -22844,41 +22576,41 @@ as
as
as
as
-zg
-zg
-vy
-Zv
-Zv
-Zv
-Zv
-Zv
-Zv
-Zv
-vy
-zg
-zg
-zg
-zg
-zg
-zg
-GB
-GB
-TU
-CJ
-Zv
-Zv
-Zv
-Uq
-rl
-Nf
-Zv
-eB
-zg
-zg
-ai
-ai
-at
-at
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+NK
+NK
+NK
+dz
+dz
+dz
+dz
+NK
+dz
+NK
+dz
+sU
+NK
+dz
+NK
+Xj
+Fz
+CN
+CN
as
as
as
@@ -23102,39 +22834,39 @@ as
as
as
as
-zg
-zg
-Zv
-Zv
-Zv
-Zv
-Zv
-Zv
-Zv
-zg
-zg
as
as
as
as
-zg
-qw
-GB
-GB
-Ji
-Oq
-TU
-Zv
-Zv
-Zv
-Zv
-Zv
-zg
-zg
-Wr
-QN
-at
-at
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+NK
+NK
+NK
+NK
+dz
+NK
+NK
+NK
+NK
+NK
+dz
+NK
+dz
+NK
+NK
+MT
+CN
+CN
as
as
as
@@ -23360,37 +23092,37 @@ as
as
as
as
-zg
-zg
-zg
-Zv
-Zv
-Zv
-zg
-zg
-zg
as
as
as
as
as
-zg
-zg
-GB
-GB
-PE
-RD
-hp
-Zv
-Zv
-Zv
-Zv
-vy
-zg
-at
-at
-QN
-at
+as
+as
+as
+as
+as
+as
+as
+as
+as
+NK
+NK
+NK
+NK
+sU
+NK
+dz
+dz
+dz
+dz
+NK
+NK
+dz
+NK
+NK
+MT
+CN
as
as
as
@@ -23619,11 +23351,6 @@ as
as
as
as
-zg
-zg
-zg
-zg
-zg
as
as
as
@@ -23632,21 +23359,26 @@ as
as
as
as
-zg
-zg
-kw
-GB
-GB
-iq
-ot
-vy
-Zv
-Zv
-zg
-zg
as
-at
-at
+as
+as
+as
+NK
+NK
+NK
+NK
+dz
+NK
+NK
+NK
+NK
+NK
+NK
+NK
+NK
+NK
+NK
+CN
as
as
as
@@ -23890,16 +23622,16 @@ as
as
as
as
-zg
-zg
-zg
-zg
-zg
-zg
-zg
-zg
-zg
-zg
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
as
as
as
@@ -78167,264 +77899,7 @@ ac
ac
ac
"}
-(36,1,2) = {"
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-as
-as
-as
-as
-as
-as
-as
-as
-as
-as
-as
-as
-as
-as
-as
-as
-as
-as
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ab
-ab
-am
-pc
-am
-aj
-xv
-jD
-jD
-nX
-QN
-QN
-ss
-ss
-QN
-QN
-QN
-Uf
-MS
-Rf
-cW
-QN
-qc
-jC
-QN
-QN
-at
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-"}
-(37,1,2) = {"
+(36,1,2) = {"
ac
ac
ac
@@ -78632,32 +78107,32 @@ ac
ac
ac
ac
-ac
-at
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
+ab
+ab
+am
+pc
+am
+aj
+xv
+jD
+jD
+nX
+QN
+QN
ss
ss
-Sz
-ss
-Pr
QN
QN
-ss
+QN
+Uf
+MS
+Rf
+cW
+QN
+qc
+jC
+QN
+QN
at
ac
ac
@@ -78681,7 +78156,7 @@ ac
ac
ac
"}
-(38,1,2) = {"
+(37,1,2) = {"
ac
ac
ac
@@ -78911,11 +78386,10 @@ ss
ss
Sz
ss
+Pr
+QN
+QN
ss
-ss
-ss
-ss
-at
at
ac
ac
@@ -78937,8 +78411,9 @@ ac
ac
ac
ac
+ac
"}
-(39,1,2) = {"
+(38,1,2) = {"
ac
ac
ac
@@ -79172,7 +78647,7 @@ ss
ss
ss
ss
-ss
+at
at
ac
ac
@@ -79195,7 +78670,7 @@ ac
ac
ac
"}
-(40,1,2) = {"
+(39,1,2) = {"
ac
ac
ac
@@ -79452,7 +78927,7 @@ ac
ac
ac
"}
-(41,1,2) = {"
+(40,1,2) = {"
ac
ac
ac
@@ -79709,7 +79184,7 @@ ac
ac
ac
"}
-(42,1,2) = {"
+(41,1,2) = {"
ac
ac
ac
@@ -79943,7 +79418,7 @@ ss
ss
ss
ss
-at
+ss
at
ac
ac
@@ -79966,7 +79441,7 @@ ac
ac
ac
"}
-(43,1,2) = {"
+(42,1,2) = {"
ac
ac
ac
@@ -80201,7 +79676,7 @@ ss
ss
ss
at
-ac
+at
ac
ac
ac
@@ -80223,7 +79698,7 @@ ac
ac
ac
"}
-(44,1,2) = {"
+(43,1,2) = {"
ac
ac
ac
@@ -80433,8 +79908,6 @@ ac
ac
ac
at
-at
-at
ss
ss
ss
@@ -80447,7 +79920,9 @@ ss
ss
ss
ss
-zx
+ss
+ss
+ss
ss
ss
ss
@@ -80480,7 +79955,7 @@ ac
ac
ac
"}
-(45,1,2) = {"
+(44,1,2) = {"
ac
ac
ac
@@ -80689,8 +80164,8 @@ ac
ac
ac
ac
-ac
-ac
+at
+at
at
ss
ss
@@ -80704,7 +80179,7 @@ ss
ss
ss
ss
-ss
+zx
ss
ss
ss
@@ -80737,264 +80212,7 @@ ac
ac
ac
"}
-(46,1,2) = {"
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-as
-as
-as
-as
-as
-as
-as
-Jd
-Jd
-Jd
-Jd
-Jd
-Jd
-Jd
-as
-as
-as
-as
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-at
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-ss
-Sz
-ss
-ss
-ss
-ss
-ss
-at
-at
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-"}
-(47,1,2) = {"
+(45,1,2) = {"
ac
ac
ac
@@ -81180,14 +80398,14 @@ as
as
as
as
-Jd
-Jd
-GE
-GE
-GE
-GE
-GE
-Jd
+as
+as
+as
+as
+as
+as
+as
+as
as
as
as
@@ -81228,7 +80446,6 @@ ss
ss
ss
ss
-ss
at
ac
ac
@@ -81250,8 +80467,9 @@ ac
ac
ac
ac
+ac
"}
-(48,1,2) = {"
+(46,1,2) = {"
ac
ac
ac
@@ -81437,13 +80655,13 @@ as
as
as
as
+as
+Jd
+Jd
+Jd
+Jd
+Jd
Jd
-gs
-GE
-GE
-GE
-GE
-GE
Jd
as
as
@@ -81485,7 +80703,6 @@ ss
ss
ss
ss
-ss
at
at
ac
@@ -81507,8 +80724,9 @@ ac
ac
ac
ac
+ac
"}
-(49,1,2) = {"
+(47,1,2) = {"
ac
ac
ac
@@ -81695,7 +80913,7 @@ as
as
as
Jd
-gs
+Jd
GE
GE
GE
@@ -81743,11 +80961,6 @@ ss
ss
ss
ss
-ss
-at
-at
-at
-at
at
ac
ac
@@ -81764,8 +80977,13 @@ ac
ac
ac
ac
+ac
+ac
+ac
+ac
+ac
"}
-(50,1,2) = {"
+(48,1,2) = {"
ac
ac
ac
@@ -82000,11 +81218,7 @@ ss
ss
ss
ss
-ss
-ss
-ss
-ss
-ss
+at
at
ac
ac
@@ -82021,8 +81235,12 @@ ac
ac
ac
ac
+ac
+ac
+ac
+ac
"}
-(51,1,2) = {"
+(49,1,2) = {"
ac
ac
ac
@@ -82209,7 +81427,7 @@ as
as
as
Jd
-Jd
+gs
GE
GE
GE
@@ -82258,10 +81476,10 @@ ss
ss
ss
ss
-ss
-ss
-ss
-ss
+at
+at
+at
+at
at
ac
ac
@@ -82279,7 +81497,7 @@ ac
ac
ac
"}
-(52,1,2) = {"
+(50,1,2) = {"
ac
ac
ac
@@ -82465,13 +81683,13 @@ as
as
as
as
-as
-Jd
-Jd
-Jd
-Jd
-Jd
Jd
+gs
+GE
+GE
+GE
+GE
+GE
Jd
as
as
@@ -82490,10 +81708,10 @@ ac
ac
ac
ac
-Zs
-oB
-oB
-Zs
+at
+ss
+ss
+ss
ss
ss
ss
@@ -82536,7 +81754,7 @@ ac
ac
ac
"}
-(53,1,2) = {"
+(51,1,2) = {"
ac
ac
ac
@@ -82722,14 +81940,14 @@ as
as
as
as
-as
-as
-as
-as
-as
-as
-as
-as
+Jd
+Jd
+GE
+GE
+GE
+GE
+GE
+Jd
as
as
as
@@ -82737,21 +81955,21 @@ as
ac
ac
ac
-GE
-GE
-GE
-GE
ac
ac
ac
ac
ac
-Zs
-yt
-mY
-mY
-gy
-Zs
+ac
+ac
+ac
+ac
+ac
+at
+ss
+ss
+ss
+ss
ss
ss
ss
@@ -82793,7 +82011,7 @@ ac
ac
ac
"}
-(54,1,2) = {"
+(52,1,2) = {"
ac
ac
ac
@@ -82980,13 +82198,13 @@ as
as
as
as
-as
-as
-as
-as
-as
-as
-as
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
as
as
as
@@ -82996,19 +82214,19 @@ ac
ac
ac
ac
-GE
-GE
+ac
+ac
+ac
ac
ac
ac
ac
ac
Zs
-lp
-pQ
-pQ
-Gu
-ag
+oB
+oB
+Zs
+ss
ss
ss
ss
@@ -83050,7 +82268,7 @@ ac
ac
ac
"}
-(55,1,2) = {"
+(53,1,2) = {"
ac
ac
ac
@@ -83251,21 +82469,21 @@ as
ac
ac
ac
-ac
-ac
-ac
+GE
GE
GE
GE
ac
ac
ac
-zB
-qj
+ac
+ac
+Zs
+yt
mY
-aI
-aI
-ag
+mY
+gy
+Zs
ss
ss
ss
@@ -83307,7 +82525,7 @@ ac
ac
ac
"}
-(56,1,2) = {"
+(54,1,2) = {"
ac
ac
ac
@@ -83510,19 +82728,19 @@ ac
ac
ac
ac
-ac
-GE
GE
GE
ac
ac
ac
+ac
+ac
Zs
-sG
-mY
-aI
-jE
-Zs
+lp
+pQ
+pQ
+Gu
+ag
ss
ss
ss
@@ -83531,12 +82749,12 @@ ss
ss
ss
ss
-Tz
-DW
-DW
-DW
-Mr
-DW
+ss
+ss
+ss
+ss
+Sz
+ss
ss
ss
ss
@@ -83564,7 +82782,7 @@ ac
ac
ac
"}
-(57,1,2) = {"
+(55,1,2) = {"
ac
ac
ac
@@ -83771,14 +82989,15 @@ ac
GE
GE
GE
-GE
-GE
-GE
-GE
-Zs
-Of
-Of
-Zs
+ac
+ac
+ac
+zB
+qj
+mY
+aI
+aI
+ag
ss
ss
ss
@@ -83788,12 +83007,11 @@ ss
ss
ss
ss
-ck
-DW
-DW
-DW
-DW
-DW
+ss
+ss
+ss
+Sz
+ss
ss
ss
ss
@@ -83821,25 +83039,7 @@ ac
ac
ac
"}
-(58,1,2) = {"
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
+(56,1,2) = {"
ac
ac
ac
@@ -84019,6 +83219,24 @@ ac
ac
ac
ac
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
ac
ac
ac
@@ -84028,28 +83246,28 @@ ac
GE
GE
GE
-GE
-GE
-GE
-GE
ac
ac
ac
-at
+Zs
+sG
+mY
+aI
+jE
+Zs
ss
ss
ss
ss
ss
ss
-Az
-DW
-DW
-DW
-DW
+ss
+ss
+Tz
DW
DW
DW
+Mr
DW
ss
ss
@@ -84060,7 +83278,7 @@ ss
ss
ss
ss
-at
+ss
at
ac
ac
@@ -84078,25 +83296,7 @@ ac
ac
ac
"}
-(59,1,2) = {"
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
+(57,1,2) = {"
ac
ac
ac
@@ -84276,6 +83476,24 @@ ac
ac
ac
ac
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
+as
ac
ac
ac
@@ -84289,20 +83507,20 @@ GE
GE
GE
GE
-ac
-ac
-ac
-at
+Zs
+Of
+Of
+Zs
ss
ss
ss
ss
ss
ss
-DW
-DW
-DW
-DW
+ss
+ss
+ss
+ck
DW
DW
DW
@@ -84316,9 +83534,9 @@ ss
ss
ss
ss
+ss
+ss
at
-at
-ac
ac
ac
ac
@@ -84335,7 +83553,9 @@ ac
ac
ac
"}
-(60,1,2) = {"
+(58,1,2) = {"
+ac
+ac
ac
ac
ac
@@ -84534,8 +83754,6 @@ ac
ac
ac
ac
-as
-as
ac
ac
ac
@@ -84556,6 +83774,7 @@ ss
ss
ss
ss
+Az
DW
DW
DW
@@ -84564,14 +83783,15 @@ DW
DW
DW
DW
-DW
-at
-at
-at
-at
-at
-at
-at
+ss
+ss
+ss
+ss
+ss
+ss
+ss
+ss
+ss
at
at
ac
@@ -84589,10 +83809,10 @@ ac
ac
ac
ac
+"}
+(59,1,2) = {"
ac
ac
-"}
-(61,1,2) = {"
ac
ac
ac
@@ -84790,9 +84010,7 @@ ac
ac
ac
ac
-as
ac
-as
ac
ac
ac
@@ -84802,25 +84020,34 @@ GE
GE
GE
GE
+GE
ac
ac
ac
-ac
-at
at
ss
ss
ss
ss
ss
-kL
-kL
-kL
+ss
+DW
+DW
+DW
+DW
DW
DW
DW
DW
DW
+ss
+ss
+ss
+ss
+ss
+ss
+ss
+ss
at
at
ac
@@ -84839,17 +84066,8 @@ ac
ac
ac
ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
"}
-(62,1,2) = {"
+(60,1,2) = {"
ac
ac
ac
@@ -85049,18 +84267,17 @@ ac
ac
ac
as
-ac
-ac
+as
ac
ac
ac
GE
GE
GE
-ac
-ac
-ac
-ac
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -85070,6 +84287,8 @@ ss
ss
ss
ss
+ss
+DW
DW
DW
DW
@@ -85079,6 +84298,14 @@ DW
DW
DW
at
+at
+at
+at
+at
+at
+at
+at
+at
ac
ac
ac
@@ -85096,6 +84323,8 @@ ac
ac
ac
ac
+"}
+(61,1,2) = {"
ac
ac
ac
@@ -85105,13 +84334,6 @@ ac
ac
ac
ac
-"}
-(63,1,2) = {"
-ac
-ac
-ac
-ac
-ac
ac
ac
ac
@@ -85300,14 +84522,39 @@ ac
ac
ac
ac
+as
ac
+as
ac
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
ac
+at
+at
+ss
+ss
+ss
+ss
+ss
+kL
+kL
+kL
+DW
+DW
+DW
+DW
+DW
+at
+at
ac
ac
ac
@@ -85321,21 +84568,6 @@ ac
ac
ac
ac
-at
-at
-at
-at
-at
-at
-at
-at
-at
-at
-at
-at
-at
-at
-at
ac
ac
ac
@@ -85348,6 +84580,8 @@ ac
ac
ac
ac
+"}
+(62,1,2) = {"
ac
ac
ac
@@ -85362,8 +84596,6 @@ ac
ac
ac
ac
-"}
-(64,1,2) = {"
ac
ac
ac
@@ -85548,29 +84780,42 @@ ac
ac
ac
ac
+as
ac
ac
ac
ac
ac
+GE
+GE
+GE
ac
ac
ac
ac
-Jd
-Jd
-Jd
-Jd
-Jd
ac
ac
ac
+at
+ss
+ss
+ss
+ss
+ss
+DW
+DW
+DW
+DW
+DW
+DW
+DW
+DW
+at
ac
ac
ac
ac
ac
-as
ac
ac
ac
@@ -85592,6 +84837,8 @@ ac
ac
ac
ac
+"}
+(63,1,2) = {"
ac
ac
ac
@@ -85619,8 +84866,6 @@ ac
ac
ac
ac
-"}
-(65,1,2) = {"
ac
ac
ac
@@ -85798,6 +85043,31 @@ ac
ac
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+CN
+CN
+CN
+CN
+CN
+CN
+CN
+CN
+CN
+CN
+CN
+CN
+CN
ac
ac
ac
@@ -85812,15 +85082,6 @@ ac
ac
ac
ac
-Jd
-Jd
-Jd
-GE
-GE
-GE
-Jd
-Jd
-Jd
ac
ac
ac
@@ -85833,6 +85094,8 @@ ac
ac
ac
ac
+"}
+(64,1,2) = {"
ac
ac
ac
@@ -85876,8 +85139,6 @@ ac
ac
ac
ac
-"}
-(66,1,2) = {"
ac
ac
ac
@@ -86028,12 +85289,46 @@ ac
ac
ac
ac
+Jd
+Jd
+Jd
+Jd
+Jd
ac
ac
ac
ac
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -86056,6 +85351,8 @@ ac
ac
ac
ac
+"}
+(65,1,2) = {"
ac
ac
ac
@@ -86068,17 +85365,6 @@ ac
ac
ac
ac
-Jd
-Jd
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-Jd
-Jd
ac
ac
ac
@@ -86133,8 +85419,6 @@ ac
ac
ac
ac
-"}
-(67,1,2) = {"
ac
ac
ac
@@ -86260,10 +85544,48 @@ ac
ac
ac
ac
+Jd
+Jd
+Jd
+GE
+GE
+GE
+Jd
+Jd
+Jd
ac
ac
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -86286,6 +85608,9 @@ ac
ac
ac
ac
+"}
+(66,1,2) = {"
+ac
ac
ac
ac
@@ -86324,19 +85649,6 @@ ac
ac
ac
ac
-Jd
-Jd
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-Jd
-Jd
ac
ac
ac
@@ -86390,8 +85702,6 @@ ac
ac
ac
ac
-"}
-(68,1,2) = {"
ac
ac
ac
@@ -86490,8 +85800,49 @@ ac
ac
ac
ac
+Jd
+Jd
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+Jd
+Jd
+ac
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+Rr
+GE
+GE
+GE
+GE
+GE
+Rr
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -86514,6 +85865,8 @@ ac
ac
ac
ac
+"}
+(67,1,2) = {"
ac
ac
ac
@@ -86581,19 +85934,6 @@ ac
ac
ac
ac
-Jd
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-Jd
ac
ac
ac
@@ -86647,8 +85987,6 @@ ac
ac
ac
ac
-"}
-(69,1,2) = {"
ac
ac
ac
@@ -86718,8 +86056,50 @@ ac
ac
ac
ac
+Jd
+Jd
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+Jd
+Jd
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -86742,6 +86122,8 @@ ac
ac
ac
ac
+"}
+(68,1,2) = {"
ac
ac
ac
@@ -86837,21 +86219,6 @@ ac
ac
ac
ac
-Jd
-Jd
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-Jd
-Jd
ac
ac
ac
@@ -86904,8 +86271,6 @@ ac
ac
ac
ac
-"}
-(70,1,2) = {"
ac
ac
ac
@@ -86948,8 +86313,50 @@ ac
ac
ac
ac
+Jd
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+Jd
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -86972,6 +86379,8 @@ ac
ac
ac
ac
+"}
+(69,1,2) = {"
ac
ac
ac
@@ -87094,21 +86503,6 @@ ac
ac
ac
ac
-Jd
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-Jd
ac
ac
ac
@@ -87161,8 +86555,6 @@ ac
ac
ac
ac
-"}
-(71,1,2) = {"
ac
ac
ac
@@ -87177,7 +86569,51 @@ ac
ac
ac
ac
+Jd
+Jd
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+Jd
+Jd
ac
+GE
+GE
+GE
+GE
+GE
+GE
+Ck
+uN
+uN
+uN
+uN
+uN
+uN
+uN
+UE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -87200,6 +86636,8 @@ ac
ac
ac
ac
+"}
+(70,1,2) = {"
ac
ac
ac
@@ -87351,21 +86789,6 @@ ac
ac
ac
ac
-Jd
-GE
-GE
-GE
-GE
-GE
-GE
-vj
-GE
-GE
-GE
-GE
-GE
-GE
-Jd
ac
ac
ac
@@ -87403,6 +86826,58 @@ ac
ac
ac
ac
+Jd
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+Jd
+ac
+GE
+GE
+GE
+GE
+GE
+GE
+uN
+GE
+GE
+GE
+uN
+GE
+GE
+GE
+uN
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+ac
+ac
+ac
+ac
+ac
+ac
+ac
ac
ac
ac
@@ -87419,7 +86894,7 @@ ac
ac
ac
"}
-(72,1,2) = {"
+(71,1,2) = {"
ac
ac
ac
@@ -87615,7 +87090,7 @@ GE
GE
GE
GE
-GE
+vj
GE
GE
GE
@@ -87624,6 +87099,37 @@ GE
GE
Jd
ac
+GE
+GE
+GE
+GE
+GE
+GE
+ke
+GE
+uN
+GE
+uN
+GE
+ke
+GE
+uN
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+ac
+ac
ac
ac
ac
@@ -87642,41 +87148,10 @@ ac
ac
ac
ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-"}
-(73,1,2) = {"
+ac
+ac
+"}
+(72,1,2) = {"
ac
ac
ac
@@ -87866,7 +87341,8 @@ ac
ac
ac
Jd
-Jd
+GE
+GE
GE
GE
GE
@@ -87879,37 +87355,36 @@ GE
GE
GE
Jd
-Jd
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+uN
+GE
+GE
+GE
+uN
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -87933,8 +87408,7 @@ ac
ac
ac
"}
-(74,1,2) = {"
-ac
+(73,1,2) = {"
ac
ac
ac
@@ -88124,6 +87598,7 @@ ac
ac
ac
Jd
+Jd
GE
GE
GE
@@ -88136,37 +87611,37 @@ GE
GE
GE
Jd
+Jd
ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
+GE
+GE
+GE
+GE
+GE
+uN
+uN
+uN
+uN
+uN
+uN
+uN
+uN
+uN
+uN
+uN
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -88190,7 +87665,7 @@ ac
ac
ac
"}
-(75,1,2) = {"
+(74,1,2) = {"
ac
ac
ac
@@ -88381,7 +87856,8 @@ ac
ac
ac
Jd
-Jd
+GE
+GE
GE
GE
GE
@@ -88392,24 +87868,37 @@ GE
GE
GE
Jd
-Jd
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
-ac
ac
ac
+GE
+GE
+GE
+Rr
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -88432,6 +87921,8 @@ ac
ac
ac
ac
+"}
+(75,1,2) = {"
ac
ac
ac
@@ -88446,8 +87937,6 @@ ac
ac
ac
ac
-"}
-(76,1,2) = {"
ac
ac
ac
@@ -88623,8 +88112,50 @@ ac
ac
ac
ac
+Jd
+Jd
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+Jd
+Jd
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -88638,17 +88169,6 @@ ac
ac
ac
ac
-Jd
-Jd
-GE
-GE
-GE
-GE
-GE
-GE
-GE
-Jd
-Jd
ac
ac
ac
@@ -88658,6 +88178,8 @@ ac
ac
ac
ac
+"}
+(76,1,2) = {"
ac
ac
ac
@@ -88703,8 +88225,6 @@ ac
ac
ac
ac
-"}
-(77,1,2) = {"
ac
ac
ac
@@ -88850,9 +88370,49 @@ ac
ac
ac
ac
+Jd
+Jd
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+Jd
+Jd
ac
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -88875,6 +88435,8 @@ ac
ac
ac
ac
+"}
+(77,1,2) = {"
ac
ac
ac
@@ -88896,15 +88458,6 @@ ac
ac
ac
ac
-Jd
-Jd
-Jd
-GE
-GE
-GE
-Jd
-Jd
-Jd
ac
ac
ac
@@ -88960,8 +88513,6 @@ ac
ac
ac
ac
-"}
-(78,1,2) = {"
ac
ac
ac
@@ -89077,10 +88628,48 @@ ac
ac
ac
ac
+Jd
+Jd
+Jd
+GE
+GE
+GE
+Jd
+Jd
+Jd
ac
ac
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+Rr
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -89103,6 +88692,8 @@ ac
ac
ac
ac
+"}
+(78,1,2) = {"
ac
ac
ac
@@ -89155,11 +88746,6 @@ ac
ac
ac
ac
-Jd
-Jd
-Jd
-Jd
-Jd
ac
ac
ac
@@ -89217,8 +88803,6 @@ ac
ac
ac
ac
-"}
-(79,1,2) = {"
ac
ac
ac
@@ -89303,12 +88887,46 @@ ac
ac
ac
ac
+Jd
+Jd
+Jd
+Jd
+Jd
ac
ac
ac
ac
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -89331,6 +88949,8 @@ ac
ac
ac
ac
+"}
+(79,1,2) = {"
ac
ac
ac
@@ -89474,8 +89094,6 @@ ac
ac
ac
ac
-"}
-(80,1,2) = {"
ac
ac
ac
@@ -89537,6 +89155,35 @@ ac
ac
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -89559,6 +89206,8 @@ ac
ac
ac
ac
+"}
+(80,1,2) = {"
ac
ac
ac
@@ -89731,8 +89380,6 @@ ac
ac
ac
ac
-"}
-(81,1,2) = {"
ac
ac
ac
@@ -89765,6 +89412,35 @@ ac
ac
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
@@ -89787,6 +89463,8 @@ ac
ac
ac
ac
+"}
+(81,1,2) = {"
ac
ac
ac
@@ -89988,8 +89666,46 @@ ac
ac
ac
ac
-"}
-(82,1,2) = {"
+ac
+ac
+ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+ac
+ac
+ac
+ac
+ac
+ac
+ac
+ac
ac
ac
ac
@@ -90004,6 +89720,10 @@ ac
ac
ac
ac
+"}
+(82,1,2) = {"
+ac
+ac
ac
ac
ac
@@ -90206,6 +89926,18 @@ ac
ac
ac
ac
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
+GE
ac
ac
ac
diff --git a/code/__DEFINES/ai/_ai.dm b/code/__DEFINES/ai/_ai.dm
index f21b747335a..21a6ab181a8 100644
--- a/code/__DEFINES/ai/_ai.dm
+++ b/code/__DEFINES/ai/_ai.dm
@@ -1,8 +1,14 @@
#define GET_AI_BEHAVIOR(behavior_type) SSai_behaviors.ai_behaviors[behavior_type]
#define HAS_AI_CONTROLLER_TYPE(thing, type) istype(thing?.ai_controller, type)
-#define AI_STATUS_ON "ai_on"
-#define AI_STATUS_OFF "ai_off"
-#define AI_STATUS_IDLE "ai_idle"
+
+//AI controller flags
+//If you add a new status, be sure to add it to the ai_controllers subsystem's ai_controllers_by_status list.
+///The AI is currently active.
+#define AI_STATUS_ON "ai_on"
+///The AI is currently offline for any reason.
+#define AI_STATUS_OFF "ai_off"
+///The AI is currently in idle mode.
+#define AI_STATUS_IDLE "ai_idle"
//Flags returned by get_able_to_run()
///pauses AI processing
@@ -15,10 +21,11 @@
#define SHOULD_STAND(source) (source.resting)
#define IS_DEAD_OR_INCAP(source) (source.incapacitated(IGNORE_GRAB) || source.stat)
-
// How far should we, by default, be looking for interesting things to de-idle?
#define AI_DEFAULT_INTERESTING_DIST 10
+/// Generic max path length
+#define AI_MAX_PATH_LENGTH 30
///Max pathing attempts before auto-fail
#define MAX_PATHING_ATTEMPTS 30
///Flags for ai_behavior new()
@@ -355,6 +362,8 @@
#define ALCHEMY_STATE_RETURN_BOTTLE "return_bottle"
#define ALCHEMY_STATE_RETURN_WATER_CONTAINER "return_container"
#define ALCHEMY_STATE_RETURN_ESSENCE_VIAL "return_vial"
+///Path we should use next time we use the movement datum
+#define BB_PATH_TO_USE "BB_path_to_use"
// Keys used by one and only one behavior
// Used to hold state without making bigass lists
diff --git a/code/__DEFINES/dcs/signals/signals_moveloop.dm b/code/__DEFINES/dcs/signals/signals_moveloop.dm
index 44cf57c7205..ae932ae1df3 100644
--- a/code/__DEFINES/dcs/signals/signals_moveloop.dm
+++ b/code/__DEFINES/dcs/signals/signals_moveloop.dm
@@ -1,11 +1,13 @@
-///from [/datum/move_loop/start_loop] ():
+///from [/datum/move_loop/loop_started] ():
#define COMSIG_MOVELOOP_START "moveloop_start"
-///from [/datum/move_loop/stop_loop] ():
+///from [/datum/move_loop/loop_stopped] ():
#define COMSIG_MOVELOOP_STOP "moveloop_stop"
///from [/datum/move_loop/process] ():
#define COMSIG_MOVELOOP_PREPROCESS_CHECK "moveloop_preprocess_check"
#define MOVELOOP_SKIP_STEP (1<<0)
-///from [/datum/move_loop/process] (succeeded, visual_delay):
+///from [/datum/move_loop/process] (result, visual_delay): //Result is an enum value. Enums defined in __DEFINES/movement.dm
#define COMSIG_MOVELOOP_POSTPROCESS "moveloop_postprocess"
-//from [/datum/move_loop/has_target/jps/recalculate_path] ():
-#define COMSIG_MOVELOOP_JPS_REPATH "moveloop_jps_repath"
+//from [/datum/move_loop/has_target/recalculate_path] ():
+#define COMSIG_MOVELOOP_REPATH "moveloop_repath"
+///from [/datum/move_loop/has_target/on_finish_pathing]
+#define COMSIG_MOVELOOP_FINISHED_PATHING "moveloop_finished_pathing"
diff --git a/code/__DEFINES/movement.dm b/code/__DEFINES/movement.dm
index a972a9e6a02..82717adc08b 100644
--- a/code/__DEFINES/movement.dm
+++ b/code/__DEFINES/movement.dm
@@ -46,6 +46,23 @@ GLOBAL_VAR_INIT(glide_size_multiplier, 1.0)
///Added to the move packet `processing_move_loop_flags` so we know when a loop is behind movement even in absence of other flags
#define MOVED_BY_MOVEMENT_LOOP (1<<6)
+//Index defines for movement bucket data packets
+#define MOVEMENT_BUCKET_TIME 1
+#define MOVEMENT_BUCKET_LIST 2
+
+// Movement loop status flags
+/// Has the loop been paused, soon to be resumed?
+#define MOVELOOP_STATUS_PAUSED (1<<0)
+/// Is the loop running? (Is true even when paused)
+#define MOVELOOP_STATUS_RUNNING (1<<1)
+/// Is the loop queued in a subsystem?
+#define MOVELOOP_STATUS_QUEUED (1<<2)
+
+///Return values for moveloop Move()
+#define MOVELOOP_FAILURE 0
+#define MOVELOOP_SUCCESS 1
+#define MOVELOOP_NOT_READY 2
+
#define DEFAULT_MOB_SNEAK_TIME 5 SECONDS
/**
@@ -113,8 +130,3 @@ GLOBAL_VAR_INIT(glide_size_multiplier, 1.0)
#define ZMOVE_FALL_FLAGS (ZMOVE_FALL_CHECKS|ZMOVE_ALLOW_BUCKLED)
/// Used when swimming
#define ZMOVE_SWIM_FLAGS (ZMOVE_WATER_CHECKS|ZMOVE_INCAPACITATED_CHECKS|ZMOVE_CHECK_PULLS|ZMOVE_ALLOW_BUCKLED)
-
-///Return values for moveloop Move()
-#define MOVELOOP_FAILURE 0
-#define MOVELOOP_SUCCESS 1
-#define MOVELOOP_NOT_READY 2
diff --git a/code/__DEFINES/path.dm b/code/__DEFINES/path.dm
new file mode 100644
index 00000000000..9909c5c1bfa
--- /dev/null
+++ b/code/__DEFINES/path.dm
@@ -0,0 +1,24 @@
+
+// Define set that decides how an atom will be scanned for astar things
+/// If set, we make the assumption that CanAStarPass() will NEVER return FALSE unless density is true
+#define CANASTARPASS_DENSITY 0
+/// If this is set, we bypass density checks and always call the proc
+#define CANASTARPASS_ALWAYS_PROC 1
+
+/// Don't remove diagonals
+#define DIAGONAL_DO_NOTHING NONE
+/// Removal all diagaonals
+#define DIAGONAL_REMOVE_ALL 1
+/// Remove diagonals on edges
+#define DIAGONAL_REMOVE_CLUNKY 2
+
+/**
+ * A helper macro to see if it's possible to step from the first turf into the second one, minding things like door access and directional windows.
+ * Note that this can only be used inside the [datum/pathfind][pathfind datum] since it uses variables from said datum.
+ * If you really want to optimize things, optimize this, cuz this gets called a lot.
+ */
+#define CAN_STEP(cur_turf, next, pass_info, avoid) \
+ (next && !next.density && (next != avoid) && !cur_turf.LinkBlockedWithAccess(next, pass_info))
+/// Check if we are close enough to be considered done
+/// Only relevant for a point on the same Z level
+#define ASTAR_CLOSE_ENOUGH_TO_END(end, checking_turf) (end == checking_turf || (mintargetdist && (get_dist(checking_turf, end) <= mintargetdist)))
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index f1dd4979e02..55371da0412 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -179,6 +179,7 @@
#define FIRE_PRIORITY_NPC 20
#define FIRE_PRIORITY_NPC_MOVEMENT 21
#define FIRE_PRIORITY_NPC_ACTIONS 22
+#define FIRE_PRIORITY_PATHFINDING 23
#define FIRE_PRIORITY_PROCESS 25
#define FIRE_PRIORITY_THROWING 25
#define FIRE_PRIORITY_SPACEDRIFT 30
diff --git a/code/__DEFINES/traits/definitions.dm b/code/__DEFINES/traits/definitions.dm
index 4b53e984680..4f0fcce94be 100644
--- a/code/__DEFINES/traits/definitions.dm
+++ b/code/__DEFINES/traits/definitions.dm
@@ -619,12 +619,14 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// This item can't be pickpocketed
#define TRAIT_HARD_TO_STEAL "hard_to_steal"
-// **** turf traits
-///Turf slowdown will be ignored when this trait is added to a turf.
-#define TRAIT_TURF_IGNORE_SLOWDOWN "turf_ignore_slowdown"
+// ************* turf traits
+/// Turf trait for when a turf is transparent
+#define TRAIT_Z_TRANSPARENT "turf_z_transparent"
/// Turf is one that ai mobs will generally avoid pathing through
/// Doesn't need to be applied to any turfs that override can_cross_safely
-#define TRAIT_AI_AVOID_TURF "warning_turf"
+#define TRAIT_AI_AVOID_TURF "ai_avoid_turf"
+///Turf slowdown will be ignored when this trait is added to a turf.
+#define TRAIT_TURF_IGNORE_SLOWDOWN "turf_ignore_slowdown"
// ************* Debug traits
/// This object has sound debugging tools attached to it
diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm
index bdf68f7642c..037fdbfb2af 100644
--- a/code/__DEFINES/turfs.dm
+++ b/code/__DEFINES/turfs.dm
@@ -59,6 +59,13 @@
/// Turf is currently queued in GLOB.SUNLIGHT_QUEUE_CORNER and should not be re-queued to avoid duplicates
#define TURF_SUNLIGHT_QUEUED (1<<5)
+/// Turf will be passable if density is 0
+#define TURF_PATHING_PASS_DENSITY 0
+/// Turf will be passable depending on [CanAStarPass] return value
+#define TURF_PATHING_PASS_PROC 1
+/// Turf is never passable
+#define TURF_PATHING_PASS_NO 2
+
//water_height defines
#define WATER_HEIGHT_ANKLE 1
#define WATER_HEIGHT_SHALLOW 2
diff --git a/code/__HELPERS/AStar.dm b/code/__HELPERS/AStar.dm
index 54a269a3818..9d93c6c0e24 100644
--- a/code/__HELPERS/AStar.dm
+++ b/code/__HELPERS/AStar.dm
@@ -41,33 +41,9 @@ Actual Adjacent procs :
node[TOTAL_COST_F] = new_g + new_h * (1 + PF_TIEBREAKER); \
node[NODE_TURN] = new_nt
-#define ASTAR_CLOSE_ENOUGH_TO_END(end, checking_turf, mintargetdist) \
- (checking_turf == end || (mintargetdist && (get_dist_3d(checking_turf, end) <= mintargetdist)))
-
#define SORT_TOTAL_COST_F(list) (list[TOTAL_COST_F])
#define PF_TIEBREAKER 0.005
-#define MASK_ODD 85
-#define MASK_EVEN 170
-
-/proc/PathWeightCompare(list/a, list/b)
- return a[TOTAL_COST_F] - b[TOTAL_COST_F]
-
-/proc/HeapPathWeightCompare(list/a, list/b)
- return b[TOTAL_COST_F] - a[TOTAL_COST_F]
-
-/proc/get_path_to(atom/movable/requester, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id = null, turf/exclude = null, simulated_only = TRUE, check_z_levels = TRUE)
- var/l = SSpathfinder.mobs.getfree(requester)
- while (!l)
- stoplag(3)
- if(QDELETED(requester)) // check if we've stopped existing, since we slept
- return list() // no path, we got deleted
- l = SSpathfinder.mobs.getfree(requester)
- var/list/path = AStar(requester, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent, id, exclude, simulated_only, check_z_levels)
- SSpathfinder.mobs.found(l)
- if (!path)
- path = list()
- return path
/proc/AStar(atom/movable/requester, _end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id = null, turf/exclude = null, simulated_only = TRUE, check_z_levels = TRUE)
var/turf/end = get_turf(_end)
@@ -184,18 +160,18 @@ Actual Adjacent procs :
* Useful for wave defense etc, where a point might be behind unbroken obstacles -
* mobs should still path as close as possible instead of giving up entirely.
*/
-/proc/get_path_to_closest_approach(atom/movable/requester, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id = null, turf/exclude = null, simulated_only = TRUE, check_z_levels = TRUE)
- var/l = SSpathfinder.mobs.getfree(requester)
- while (!l)
- stoplag(3)
- if(QDELETED(requester))
- return list()
- l = SSpathfinder.mobs.getfree(requester)
- var/list/path = AStarClosestApproach(requester, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent, id, exclude, simulated_only, check_z_levels)
- SSpathfinder.mobs.found(l)
- if (!path)
- path = list()
- return path
+// /proc/get_path_to_closest_approach(atom/movable/requester, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id = null, turf/exclude = null, simulated_only = TRUE, check_z_levels = TRUE)
+// var/l = SSpathfinder.mobs.getfree(requester)
+// while (!l)
+// stoplag(3)
+// if(QDELETED(requester))
+// return list()
+// l = SSpathfinder.mobs.getfree(requester)
+// var/list/path = AStarClosestApproach(requester, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent, id, exclude, simulated_only, check_z_levels)
+// SSpathfinder.mobs.found(l)
+// if (!path)
+// path = list()
+// return path
/proc/AStarClosestApproach(atom/movable/requester, _end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id = null, turf/exclude = null, simulated_only = TRUE, check_z_levels = TRUE)
var/turf/end = get_turf(_end)
@@ -351,10 +327,12 @@ Actual Adjacent procs :
// Add a helper function to compute 3D Manhattan distance
/turf/proc/Distance3D(turf/T)
- if(!T)
+ if(!istype(T))
return 0
+
if(z == T.z)
return abs(x - T.x) + abs(y - T.y)
+
if(is_in_zweb(z, T.z))
return abs(x - T.x) + abs(y - T.y) + abs(z - T.z) * 5
@@ -404,23 +382,6 @@ Actual Adjacent procs :
return abs(cx - T.x) + abs(cy - T.y) + abs(cz - T.z) * 5 + hops * 5
-/turf/proc/LinkBlockedWithAccess(turf/T, requester, ID)
- var/adir = get_dir(src, T)
- var/rdir = ((adir & MASK_ODD)<<1)|((adir & MASK_EVEN)>>1)
- for(var/obj/O in T)
- if(!O.CanAStarPass(ID, rdir, requester))
- return TRUE
- for(var/obj/O in src)
- if(!O.CanAStarPass(ID, adir, requester))
- return TRUE
- for(var/mob/living/M in T)
- if(!M.CanPass(requester, src))
- return TRUE
- for(var/obj/structure/M in T)
- if(!M.CanPass(requester, src))
- return TRUE
- return FALSE
-
#undef ATURF
#undef TOTAL_COST_F
#undef DIST_FROM_START_G
@@ -430,4 +391,4 @@ Actual Adjacent procs :
#undef BLOCKED_FROM
#undef ASTAR_NODE
#undef ASTAR_UPDATE_NODE
-#undef ASTAR_CLOSE_ENOUGH_TO_END
+//#undef ASTAR_CLOSE_ENOUGH_TO_END
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index 9eb3499ab46..26985fe39ad 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -185,6 +185,33 @@ if (length(L) < I) { \
};\
} while(FALSE)
+/// The above but the tree is sorted in reverse.
+#define BINARY_INSERT_REVERSE(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \
+ do {\
+ var/list/__BIN_LIST = LIST;\
+ var/__BIN_CTTL = length(__BIN_LIST);\
+ if(!__BIN_CTTL) {\
+ __BIN_LIST += INPUT;\
+ } else {\
+ var/__BIN_LEFT = 1;\
+ var/__BIN_RIGHT = __BIN_CTTL;\
+ var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\
+ var ##TYPECONT/__BIN_ITEM;\
+ while(__BIN_LEFT < __BIN_RIGHT) {\
+ __BIN_ITEM = COMPTYPE;\
+ if(__BIN_ITEM.##COMPARISON >= COMPARE.##COMPARISON) {\
+ __BIN_LEFT = __BIN_MID + 1;\
+ } else {\
+ __BIN_RIGHT = __BIN_MID;\
+ };\
+ __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\
+ };\
+ __BIN_ITEM = COMPTYPE;\
+ __BIN_MID = __BIN_ITEM.##COMPARISON < COMPARE.##COMPARISON ? __BIN_MID : __BIN_MID + 1;\
+ __BIN_LIST.Insert(__BIN_MID, INPUT);\
+ };\
+ } while(FALSE)
+
//Returns a list in plain english as a string
/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" )
var/total = length(input)
@@ -790,11 +817,12 @@ if (length(L) < I) { \
else
L1[key] = other_value
-/proc/assoc_list_strip_value(list/input)
- var/list/ret = list()
+/// Turns an associative list into a flat list of keys
+/proc/assoc_to_keys(list/input)
+ var/list/keys = list()
for(var/key in input)
- ret += key
- return ret
+ keys += key
+ return keys
/proc/compare_list(list/l,list/d)
if(!islist(l) || !islist(d))
diff --git a/code/__HELPERS/heap.dm b/code/__HELPERS/heap.dm
index 2385c600cb7..348007fb5d0 100644
--- a/code/__HELPERS/heap.dm
+++ b/code/__HELPERS/heap.dm
@@ -1,28 +1,35 @@
-/datum/Heap
+/datum/heap
var/list/L
var/cmp
-/datum/Heap/New(compare)
+/datum/heap/New(compare)
L = new()
cmp = compare
-/datum/Heap/proc/IsEmpty()
- return !L.len
+/datum/heap/Destroy(force, ...)
+ for(var/i in L) // because this is before the list helpers are loaded
+ qdel(i)
+ L = null
+ return ..()
-/datum/Heap/proc/Insert(atom/A)
+/datum/heap/proc/is_empty()
+ return !length(L)
+
+/datum/heap/proc/insert(atom/A)
L[++L.len] = A
- Swim(length(L))
+ swim(length(L))
-/datum/Heap/proc/Pop()
- if(!L.len)
- return null
+/datum/heap/proc/pop()
+ if(!length(L))
+ return 0
. = L[1]
- L[1] = L[L.len]
- L.Cut(L.len)
- if(L.len)
- Sink(1)
-/datum/Heap/proc/Swim(index)
+ L[1] = L[length(L)]
+ L.Cut(length(L))
+ if(length(L))
+ sink(1)
+
+/datum/heap/proc/swim(index)
while(index > 1)
var/parent = round(index * 0.5)
if(call(cmp)(L[index], L[parent]) <= 0)
@@ -30,36 +37,35 @@
L.Swap(index, parent)
index = parent
-/datum/Heap/proc/Sink(index)
- var/length = L.len
- var/max_iterations = length
-
- while(max_iterations > 0)
- var/left_child = index * 2
- var/right_child = index * 2 + 1
- var/largest = index
+//Get a node down to its right position in the heap
+/datum/heap/proc/sink(index)
+ var/g_child = get_greater_child(index)
- if(left_child <= length && call(cmp)(L[left_child], L[largest]) > 0)
- largest = left_child
+ while(g_child > 0 && (call(cmp)(L[index],L[g_child]) < 0))
+ L.Swap(index,g_child)
+ index = g_child
+ g_child = get_greater_child(index)
- if(right_child <= length && call(cmp)(L[right_child], L[largest]) > 0)
- largest = right_child
+//Returns the greater (relative to the comparison proc) of a node children
+//or 0 if there's no child
+/datum/heap/proc/get_greater_child(index)
+ if(index * 2 > length(L))
+ return 0
- if(largest == index)
- break
-
- L.Swap(index, largest)
- index = largest
- max_iterations--
+ if(index * 2 + 1 > length(L))
+ return index * 2
- if(max_iterations <= 0)
- stack_trace("Potential infinite loop in Heap Sink method")
+ if(call(cmp)(L[index * 2],L[index * 2 + 1]) < 0)
+ return index * 2 + 1
+ else
+ return index * 2
-/datum/Heap/proc/ReSort(atom/A)
+//Replaces a given node so it verify the heap condition
+/datum/heap/proc/resort(A)
var/index = L.Find(A)
- if(index)
- Swim(index)
- Sink(index)
-/datum/Heap/proc/List()
+ swim(index)
+ sink(index)
+
+/datum/heap/proc/List()
return L.Copy()
diff --git a/code/__HELPERS/mapping_helpers.dm b/code/__HELPERS/mapping_helpers.dm
index 2969f7c6c5c..c6ee49ba679 100644
--- a/code/__HELPERS/mapping_helpers.dm
+++ b/code/__HELPERS/mapping_helpers.dm
@@ -1,3 +1,21 @@
+/// Create directional subtypes for a path to simplify mapping.
+#define MAPPING_DIRECTIONAL_HELPERS(path, offset) ##path/directional/north {\
+ dir = NORTH; \
+ pixel_y = offset; \
+} \
+##path/directional/south {\
+ dir = SOUTH; \
+ pixel_y = -offset; \
+} \
+##path/directional/east {\
+ dir = EAST; \
+ pixel_x = offset; \
+} \
+##path/directional/west {\
+ dir = WEST; \
+ pixel_x = -offset; \
+}
+
// No non-player species in this for now
/// For a /obj/effect/mob_spawn/corpse/human or /obj/effect/mob_spawn/human (if it ever exists) create all species subtypes
#define CREATE_ALL_SPECIES_SPAWNERS(path) \
diff --git a/code/__HELPERS/paths/_path.dm b/code/__HELPERS/paths/_path.dm
new file mode 100644
index 00000000000..11f9b6db226
--- /dev/null
+++ b/code/__HELPERS/paths/_path.dm
@@ -0,0 +1,200 @@
+/// The datum used to handle pathfinding, completely self-contained
+/datum/pathfind
+ /// The turf we started at
+ var/turf/start
+
+ // general pathfinding vars/args
+ /// The maximum number of steps a path can be before it is considered too long and ignored. Diagonals are 2 steps, so it's Manhattan distance.
+ var/max_steps = 30
+ /// A specific turf we're avoiding, like if a mulebot is being blocked by someone t-posing in a doorway we're trying to get through
+ var/turf/avoid
+ /// The callbacks to invoke when we're done working, passing in the completed product
+ /// Invoked in order
+ var/list/datum/callback/on_finish
+ /// Datum that holds the canpass info of this pathing attempt. This is what CanAstarPass sees
+ var/datum/can_pass_info/pass_info
+
+/datum/pathfind/Destroy(force)
+ . = ..()
+ SSpathfinder.active_pathing -= src
+ SSpathfinder.currentrun -= src
+ hand_back(null)
+ avoid = null
+
+/**
+ * "starts" off the pathfinding, by storing the values this datum will need to work later on
+ * returns FALSE if it fails to setup properly, TRUE otherwise
+ */
+/datum/pathfind/proc/start()
+ if(!start)
+ stack_trace("Invalid pathfinding start")
+ return FALSE
+ return TRUE
+
+/**
+ * search_step() is the workhorse of pathfinding. It'll do the searching logic, and will slowly build up a path
+ * returns TRUE if everything is stable, FALSE if the pathfinding logic has failed, and we need to abort
+ */
+/datum/pathfind/proc/search_step()
+ return TRUE
+
+/**
+ * early_exit() is called when something goes wrong in processing, and we need to halt the pathfinding NOW
+ */
+/datum/pathfind/proc/early_exit()
+ hand_back(null)
+ qdel(src)
+
+/**
+ * Cleanup pass for the pathfinder. This tidies up the path, and fufills the pathfind's obligations
+ */
+/datum/pathfind/proc/finished()
+ qdel(src)
+
+/**
+ * Call to return a value to whoever spawned this pathfinding work
+ * Will fail if it's already been called
+ */
+/datum/pathfind/proc/hand_back(value)
+ for(var/datum/callback/finished as anything in on_finish)
+ finished.Invoke(value)
+ on_finish = null
+
+/**
+ * For seeing if we can actually move between 2 given turfs while accounting for our access and the requester's pass_flags
+ *
+ * Assumes destinantion turf is non-dense - check and shortcircuit in code invoking this proc to avoid overhead.
+ * Makes some other assumptions, such as assuming that unless declared, non dense objects will not block movement.
+ * It's fragile, but this is VERY much the most expensive part of pathing, so it'd better be fast
+ *
+ * Arguments:
+ * * destination_turf - Where are we going from where we are?
+ * * pass_info - Holds all the info about what this path attempt can go through
+*/
+/turf/proc/LinkBlockedWithAccess(turf/destination_turf, datum/can_pass_info/pass_info)
+ var/actual_dir = get_dir(src, destination_turf)
+ if(actual_dir == 0)
+ return FALSE
+
+ if(ISDIAGONALDIR(actual_dir))
+ var/in_dir = get_dir(destination_turf, src) // eg. northwest (1+8) = 9 (00001001)
+ var/first_step_direction_a = in_dir & 3 // eg. north (1+8)&3 (0000 0011) = 1 (0000 0001)
+ var/first_step_direction_b = in_dir & 12 // eg. west (1+8)&12 (0000 1100) = 8 (0000 1000)
+
+ for(var/first_step_direction in list(first_step_direction_a,first_step_direction_b))
+ var/turf/midstep_turf = get_step(destination_turf, first_step_direction)
+ var/way_blocked = midstep_turf.density || LinkBlockedWithAccess(midstep_turf, pass_info) || midstep_turf.LinkBlockedWithAccess(destination_turf, pass_info)
+ if(!way_blocked)
+ return FALSE
+
+ return TRUE
+
+ /// These are generally cheaper than looping contents so they go first
+ switch(destination_turf.pathing_pass_method)
+ if(TURF_PATHING_PASS_PROC)
+ if(!destination_turf.CanAStarPass(actual_dir, pass_info))
+ return TRUE
+
+ if(TURF_PATHING_PASS_NO)
+ return TRUE
+
+ var/static/list/directional_blocker_cache = typecacheof(list(/obj/structure/fluff/railing, /obj/structure/meatvineborder))
+ // Source border object checks
+ for(var/obj/border in src)
+ if(!directional_blocker_cache[border.type])
+ continue
+ if(!border.density && border.can_astar_pass == CANASTARPASS_DENSITY)
+ continue
+ if(!border.CanAStarPass(actual_dir, pass_info, TRUE))
+ return TRUE
+
+ // Destination blockers check
+ var/reverse_dir = get_dir(destination_turf, src)
+ for(var/obj/iter_object in destination_turf)
+ // This is an optimization because of the massive call count of this code
+ if(!iter_object.density && iter_object.can_astar_pass == CANASTARPASS_DENSITY)
+ continue
+ if(!iter_object.CanAStarPass(reverse_dir, pass_info))
+ return TRUE
+
+ return FALSE
+
+// Could easily be a struct if/when we get that
+/**
+ * Holds all information about what an atom can move through
+ * Passed into CanAStarPass to provide context for a pathing attempt
+ *
+ * Also used to check if using a cached path_map is safe
+ * There are some vars here that are unused. They exist to cover cases where requester_ref is used
+ * They're the properties of requester_ref used in those cases.
+ * It's kinda annoying, but there's some proc chains we can't convert to this datum
+ */
+/datum/can_pass_info
+ /// What we can pass through. Mirrors /atom/movable/pass_flags
+ var/pass_flags = NONE
+ /// What access we have, airlocks, windoors, etc
+ var/list/access = null
+ /// What sort of movement do we have. Mirrors /atom/movable/movement_type
+ var/movement_type = NONE
+ /// Are we being thrown?
+ var/thrown = FALSE
+ /// Are we anchored
+ var/anchored = FALSE
+
+ /// Are we a ghost? (they have effectively unique pathfinding)
+ var/is_observer = FALSE
+ /// Are we a living mob?
+ var/is_living = FALSE
+ /// What is the size of our mob
+ var/mob_size = null
+ /// Is our mob incapacitated
+ var/incapacitated = FALSE
+ /// Is our mob incorporeal
+ var/incorporeal_move = FALSE
+ /// If our mob has a rider, what does it look like
+ var/datum/can_pass_info/rider_info = null
+ /// If our mob is buckled to something, what's it like
+ var/datum/can_pass_info/buckled_info = null
+
+ /// Pass information for the object we are pulling, if any
+ var/datum/can_pass_info/pulling_info = null
+
+ /// Weakref to the requester used to generate this info
+ /// Should not use this almost ever, it's for context and to allow for proc chains that
+ /// Require a movable
+ var/datum/weakref/requester_ref = null
+
+/datum/can_pass_info/New(atom/movable/construct_from, list/access, call_depth = 0)
+ // No infiniloops
+ if(call_depth > 10)
+ return
+
+ if(access)
+ src.access = access.Copy()
+
+ if(isnull(construct_from))
+ return
+
+ src.requester_ref = WEAKREF(construct_from)
+ src.pass_flags = construct_from.pass_flags
+ src.movement_type = construct_from.movement_type
+ src.thrown = !!construct_from.throwing
+ src.anchored = construct_from.anchored
+
+ if(ismob(construct_from))
+ var/mob/mob_construct = construct_from
+ if(mob_construct.buckled)
+ src.buckled_info = new(mob_construct.buckled, access, call_depth + 1)
+
+ if(isobserver(construct_from))
+ src.is_observer = TRUE
+
+ if(isliving(construct_from))
+ var/mob/living/living_construct = construct_from
+ src.is_living = TRUE
+ src.incapacitated = living_construct.incapacitated()
+ src.mob_size = living_construct.mob_size
+ src.incorporeal_move = living_construct.incorporeal_move
+
+ if(construct_from.pulling)
+ src.pulling_info = new(construct_from.pulling, access, call_depth + 1)
diff --git a/code/__HELPERS/paths/astar/path_astar.dm b/code/__HELPERS/paths/astar/path_astar.dm
new file mode 100644
index 00000000000..87f3e0343fa
--- /dev/null
+++ b/code/__HELPERS/paths/astar/path_astar.dm
@@ -0,0 +1,291 @@
+/// A datum to hold astar node information
+/datum/astar_node
+ /// The turf associated with this node
+ var/turf/turf = null
+ /// The node we just came from
+ var/datum/astar_node/previous_node = null
+ /// The A* node weight (f_value = distance_from_start + heuristic)
+ var/f_value = 0
+ /// The A* node heuristic (a rough estimate of how far we are from the goal)
+ var/node_heuristic = 0
+ /// Biased distance from the start
+ var/distance_from_start = 0
+ /// Distance is affected by bias to smooth out diagonals, this tracks the raw number of steps required.
+ var/real_distance_from_start = 0
+
+/datum/astar_node/Destroy(force, ...)
+ previous_node = null
+ return ..()
+
+/datum/pathfind/astar
+ /// The thing that we're actually trying to path for
+ var/atom/movable/invoker
+ /// The turf we're trying to path to (note that this won't track a moving target)
+ var/turf/end
+ /// The list we compile at the end if successful to pass back
+ var/list/path
+
+ /// A k:v list of turf -> directions. The directions are directions the pathfinder attempted to step into the turf but failed.
+ var/list/closed
+ /// A binary search tree containing the discovered nodes.
+ var/list/open_binary_tree
+ /// A k:V list of turf -> astar node
+ var/list/open_turf_to_node
+
+ /// How far away we have to get to the end target before we can call it quits
+ var/mintargetdist = 0
+ /// If we should delete the first step in the path or not. Used often because it is just the starting tile
+ var/skip_first = FALSE
+ /// Defines how we handle diagonal moves. See __DEFINES/path.dm
+ var/use_diagonals = TRUE
+ /// An optional callback to invoke to return a positive value to add to the path's distance.
+ var/datum/callback/heuristic_function
+
+ /// If true, when we can't find a path to a point we unwind the last valid node
+ var/return_closest_approach = FALSE
+ /// Node with the best node_heuristic
+ var/datum/astar_node/best_node
+
+#ifdef DEBUG_PATHFINDING
+ /// List of all nodes we've parsed, used for debug spew.
+ var/list/all_nodes_ever = list()
+#endif
+
+/datum/pathfind/astar/New(
+ atom/movable/invoker,
+ atom/goal,
+ access,
+ max_steps,
+ mintargetdist,
+ avoid,
+ skip_first,
+ use_diagonals,
+ list/on_finish,
+ datum/callback/heuristic_function,
+ return_closest_approach
+)
+ src.invoker = invoker
+ src.pass_info = new(invoker, access)
+
+ end = get_turf(goal)
+ open_binary_tree = new()
+ open_turf_to_node = new()
+ closed = new()
+
+ src.max_steps = max_steps
+ src.mintargetdist = mintargetdist
+ src.avoid = avoid
+ src.skip_first = skip_first
+ src.use_diagonals = use_diagonals
+ src.on_finish = on_finish
+ src.heuristic_function = heuristic_function || CALLBACK(src, PROC_REF(generic_heuristic))
+ src.return_closest_approach = return_closest_approach
+
+/datum/pathfind/astar/Destroy(force, ...)
+ invoker = null
+ end = null
+ open_binary_tree = null
+ open_turf_to_node = null
+ closed = null
+ heuristic_function = null
+ best_node = null
+ return ..()
+
+/**
+ * "starts" off the pathfinding, by storing the values this datum will need to work later on
+ * returns FALSE if it fails to setup properly, TRUE otherwise
+ */
+/datum/pathfind/astar/start()
+ start ||= get_turf(invoker)
+ . = ..()
+ if(!.)
+ return
+ if(!get_turf(end))
+ stack_trace("Invalid A* destination")
+ return FALSE
+
+ // If the turf is out of the step range we already know it's too far.
+ if(max_steps && (max_steps < get_dist_manhattan(start, end)))
+ return FALSE
+
+ var/datum/astar_node/start_node = new /datum/astar_node
+ start_node.turf = start
+ start_node.f_value = 0
+ start_node.distance_from_start = 0
+ start_node.node_heuristic = 0
+ start_node.real_distance_from_start = 0
+
+ open_turf_to_node[start] = start_node
+ binary_insert_node(start_node)
+
+ return TRUE
+
+/**
+ * Cleanup pass for the pathfinder. This tidies up the path, and fufills the pathfind's obligations
+ */
+/datum/pathfind/astar/finished()
+ var/list/path = src.path || list()
+ if(length(path) > 0 && skip_first)
+ path.Cut(1,2)
+
+ hand_back(path)
+#ifdef DEBUG_PATHFINDING
+ /// If the global flag is set, replace the current global node spew.
+ if(GLOB.__pathfinding_debug_generate)
+ GLOB.__pathfinding_debug_info = all_nodes_ever
+#endif
+ return ..()
+
+/datum/pathfind/astar/search_step(tick_check = TRUE)
+ if(QDELETED(invoker))
+ return FALSE
+
+ var/static/list/lateral_search_dirs = list(EAST, WEST, NORTH, SOUTH)
+ var/static/list/all_search_dirs = list(EAST, WEST, NORTH, SOUTH, NORTHEAST, SOUTHWEST, NORTHWEST, SOUTHEAST)
+
+ while(length(open_binary_tree) && !path)
+ var/datum/astar_node/current_node = open_binary_tree[length(open_binary_tree)]
+ open_binary_tree.len--
+
+ var/turf/current_node_turf = current_node.turf
+ closed[current_node_turf] = ALL
+
+ if(max_steps && current_node.real_distance_from_start > max_steps)
+ continue
+
+ // Check to see if we're close enough to the end destination.
+ if(current_node_turf.z == end.z && ASTAR_CLOSE_ENOUGH_TO_END(end, current_node_turf))
+ unwind_path(current_node)
+ return TRUE
+
+ // Scan cardinal turfs for valid movements.
+ for(var/scan_direction in use_diagonals ? all_search_dirs : lateral_search_dirs)
+ var/is_diagonal = ISDIAGONALDIR(scan_direction)
+ var/turf/searching_turf = is_diagonal ? get_step(current_node_turf, scan_direction) : null
+
+ // For cardinal moves check if there is stairs to move onto first
+ if(!is_diagonal)
+ var/obj/structure/stairs/z_mover = locate() in current_node_turf
+ if(!z_mover)
+ searching_turf = get_step(current_node_turf, scan_direction)
+ else
+ // Technically we could go further and account for legendary skills/flying/etc to just jump straight off open space,
+ // we aren't going to (yet)
+ searching_turf = z_mover.get_transit_destination(scan_direction)
+
+ if(closed[searching_turf] & scan_direction)
+ continue // Turf is known to be blocked from this direction, skip!
+
+ if(!(is_diagonal ? can_step_diagonal(current_node_turf, searching_turf) : CAN_STEP(current_node_turf, searching_turf, pass_info, avoid)))
+ closed[searching_turf] |= scan_direction
+ continue // Turf cannot be entered, atleast from this direction. Skip!
+
+ var/datum/astar_node/existing_node = open_turf_to_node[searching_turf]
+
+ // Prefer straighter lines for more visual appeal. Penalize changing from cardinal to diagonal, but if you're already diagonal, it's okay.
+ var/distance_g = current_node.distance_from_start
+ var/real_distance = current_node.real_distance_from_start
+ if(is_diagonal)
+ // Diagonal is not continuing from previous node
+ if(!current_node.previous_node || !ISDIAGONALDIR(get_dir(current_node.previous_node.turf, current_node_turf)))
+ distance_g += 2
+ real_distance += 2
+
+ // Diagonal is continuing from previous node
+ else
+ distance_g += sqrt(2) // It const folds dont cry
+ real_distance += 2
+ else
+ distance_g += 1
+ real_distance += 1
+
+ // If the node already exists, update it to reflect new information. Maybe we found a shorter path to it, or similar.
+ if(existing_node)
+ if(distance_g < existing_node.distance_from_start)
+ existing_node.previous_node = current_node
+ existing_node.distance_from_start = distance_g
+ existing_node.real_distance_from_start = real_distance
+ existing_node.f_value = distance_g + existing_node.node_heuristic
+ open_binary_tree -= existing_node
+ binary_insert_node(existing_node)
+ continue
+
+ // The node isn't known to us so we need to check the heuristic.
+ var/node_heuristic = heuristic_function.Invoke(searching_turf, end)
+ if(node_heuristic == 0)
+ closed[searching_turf] |= scan_direction
+ continue
+
+ // Node is not known, create it.
+ var/datum/astar_node/new_node = new /datum/astar_node
+ new_node.turf = searching_turf
+ new_node.f_value = distance_g + node_heuristic
+ new_node.distance_from_start = distance_g
+ new_node.real_distance_from_start = real_distance
+ new_node.node_heuristic = node_heuristic
+ new_node.previous_node = current_node
+
+ binary_insert_node(new_node)
+
+ open_turf_to_node[searching_turf] = new_node
+#ifdef DEBUG_PATHFINDING
+ all_nodes_ever[++all_nodes_ever.len] = new_node
+#endif
+ // Check to see if we're close enough to the end destination.
+ if(new_node.turf.z == end.z && ASTAR_CLOSE_ENOUGH_TO_END(end, new_node.turf))
+ unwind_path(new_node)
+ return TRUE
+
+ // Stable, we'll just be back later
+ if(tick_check && TICK_CHECK)
+ return TRUE
+
+ if(best_node && !length(path))
+ unwind_path(best_node)
+
+ return TRUE
+
+/datum/pathfind/astar/proc/binary_insert_node(datum/astar_node/node)
+ if(return_closest_approach)
+ if(!best_node)
+ best_node = node
+ else
+ // 0 is the start
+ var/old_heuristic = best_node.node_heuristic ? best_node.node_heuristic : INFINITY
+ if(node.node_heuristic < old_heuristic)
+ best_node = node
+
+ BINARY_INSERT_REVERSE(node, open_binary_tree, /datum/astar_node, node, f_value, COMPARE_KEY)
+
+/datum/pathfind/astar/proc/can_step_diagonal(turf/from_turf, turf/to_turf)
+ var/in_dir = get_dir(from_turf, to_turf) // eg. northwest (1+8) = 9 (00001001)
+ var/first_step_direction_a = in_dir & 3 // eg. north (1+8)&3 (0000 0011) = 1 (0000 0001)
+ var/first_step_direction_b = in_dir & 12 // eg. west (1+8)&12 (0000 1100) = 8 (0000 1000)
+
+ for(var/direction in list(first_step_direction_a, first_step_direction_b))
+ var/turf/midpoint = get_step(from_turf, direction)
+ // If the midpoint is known to be inaccessible from the starting direction, no need to check it again.
+ if(closed[midpoint] & direction)
+ continue
+
+ if(CAN_STEP(midpoint, to_turf, pass_info, avoid))
+ return TRUE
+
+ return FALSE
+
+/// The generic heuristic
+/datum/pathfind/astar/proc/generic_heuristic(turf/searching_turf, turf/end)
+ return get_dist_euclidean(searching_turf, end)
+
+/// Called when we've hit the goal with the node that represents the last tile, then sets the path var to that path so it can be returned by [datum/pathfind/proc/search]
+/datum/pathfind/astar/proc/unwind_path(datum/astar_node/unwind_node)
+ path = new()
+ var/turf/iter_turf = unwind_node.turf
+ path += iter_turf
+
+ var/datum/astar_node/iter_node = unwind_node.previous_node
+ while(iter_node)
+ path.Insert(1, iter_node.turf)
+ iter_node = iter_node.previous_node
+
+ return path
diff --git a/code/__HELPERS/paths/mapping_helpers.dm b/code/__HELPERS/paths/mapping_helpers.dm
deleted file mode 100644
index e869d33136a..00000000000
--- a/code/__HELPERS/paths/mapping_helpers.dm
+++ /dev/null
@@ -1,18 +0,0 @@
-
-/// Create directional subtypes for a path to simplify mapping.
-#define MAPPING_DIRECTIONAL_HELPERS(path, offset) ##path/directional/north {\
- dir = NORTH; \
- pixel_y = offset; \
-} \
-##path/directional/south {\
- dir = SOUTH; \
- pixel_y = -offset; \
-} \
-##path/directional/east {\
- dir = EAST; \
- pixel_x = offset; \
-} \
-##path/directional/west {\
- dir = WEST; \
- pixel_x = -offset; \
-}
diff --git a/code/__HELPERS/paths/path_debug.dm b/code/__HELPERS/paths/path_debug.dm
new file mode 100644
index 00000000000..e4c1a076880
--- /dev/null
+++ b/code/__HELPERS/paths/path_debug.dm
@@ -0,0 +1,119 @@
+#define CHEBYSHEV "Chebyshev - get_dist"
+#define EUCLIDEAN "Euclidean - get_dist_euclidean"
+#define MANHATTAN "Manhattan - get_dist_manhattan"
+#define OCTILE "Octile - get_dist_octile - Experimental"
+
+/obj/item/pathfinding_stick
+ name = "Pathfinder stick"
+ icon = 'icons/roguetown/items/natural.dmi'
+ icon_state = "stick1"
+
+ var/static/list/callbacks = list(
+ CHEBYSHEV = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(_get_dist)),
+ EUCLIDEAN = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(get_dist_euclidean)),
+ MANHATTAN = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(get_dist_manhattan)),
+ OCTILE = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(get_dist_octile)),
+ )
+ var/selected = CHEBYSHEV
+ var/diagonals = TRUE
+
+/obj/item/pathfinding_stick/examine(mob/user)
+ . = ..()
+ . += "It's set to [selected]."
+ . += "It's [diagonals ? "" : "not"] using diagonals."
+
+/obj/item/pathfinding_stick/attack_self(mob/user, list/modifiers)
+ . = ..()
+
+ var/heuristic = tgui_input_list(user, "Select heuristic", "Pathfinder", callbacks)
+ if(!heuristic)
+ return
+
+ selected = heuristic
+
+/obj/item/pathfinding_stick/attack_self_secondary(mob/user, list/modifiers)
+ . = ..()
+ diagonals = !diagonals
+
+ balloon_alert(user, "[diagonals ? "" : "not"] using diagonals.")
+
+/obj/item/pathfinding_stick/afterattack(atom/target, mob/living/user, proximity_flag, list/modifiers)
+ . = ..()
+
+ if(proximity_flag || !target)
+ return
+
+ start_pathing(user, target)
+
+/obj/item/pathfinding_stick/proc/start_pathing(mob/living/user, atom/target)
+ var/datum/callback/path_heuristic = callbacks[selected]
+
+ var/time = world.timeofday
+
+ var/list/path = SSpathfinder.astar_pathfind_now(user, target, 100, 0, null, null, FALSE, diagonals, path_heuristic)
+
+ var/after_time = world.timeofday
+
+ if(!length(path))
+ to_chat(user, span_alert("Pathfinding could not be completed."))
+ return
+
+ to_chat(user, span_info("Path length [length(path)] took [DS2MS(after_time - time)] ms to complete."))
+
+ var/static/list/images
+
+ LAZYINITLIST(images)
+ if(length(images))
+ for(var/image/I as anything in images)
+ user.client.images -= I
+
+ for(var/index in 1 to length(path))
+ var/turf/current_turf = path[index]
+ var/image/path_display
+ if(index == 1)
+ path_display = image('icons/turf/floors.dmi', current_turf, "pure_white")
+ path_display.plane = ABOVE_LIGHTING_PLANE
+ path_display.alpha = 200
+ path_display.color = "#FF0000"
+ path_display.layer = 2 // Render above whiteness, but below maptext.
+
+ else if(index == length(path))
+ path_display = image('icons/turf/floors.dmi', current_turf, "pure_white")
+ path_display.plane = ABOVE_LIGHTING_PLANE
+ path_display.alpha = 200
+ path_display.color = "#00FF00"
+ path_display.layer = 2 // Render above whiteness, but below maptext.
+
+ else
+ path_display = image('icons/effects/navigation.dmi', current_turf)
+ path_display.plane = ABOVE_LIGHTING_PLANE
+ path_display.color = COLOR_BLUE //So it stands out better against the white.
+ path_display.alpha = 200
+
+ var/turf/turf_ahead = path[index+1]
+ var/turf/turf_behind = path[index-1]
+ var/dir_1 = 0
+ var/dir_2 = 0
+
+ dir_1 = turn(angle2dir(get_angle(turf_ahead, current_turf)), 180)
+ dir_2 = turn(angle2dir(get_angle(turf_behind, current_turf)), 180)
+ if(dir_1 > dir_2)
+ dir_1 = dir_2
+ dir_2 = turn(angle2dir(get_angle(turf_ahead, current_turf)), 180)
+
+ path_display.icon_state = "[dir_1]-[dir_2]"
+#ifndef DEBUG_PATHFINDING //Disables this for hygiene.
+ path_display.maptext = MAPTEXT("[index]---[get_dist_euclidean(current_turf, target)]")
+#else
+ path_display.alpha = 255 //Make it solid if we're doing debug rendering.
+ path_display.layer = 2 // Render above whiteness, but below maptext.
+#endif
+
+
+ images += path_display
+ user.client.images += path_display
+
+#undef CHEBYSHEV
+#undef EUCLIDEAN
+#undef MANHATTAN
+#undef OCTILE
diff --git a/code/__HELPERS/paths/path_helpers.dm b/code/__HELPERS/paths/path_helpers.dm
new file mode 100644
index 00000000000..dbd5d2d1478
--- /dev/null
+++ b/code/__HELPERS/paths/path_helpers.dm
@@ -0,0 +1,47 @@
+/**
+ * This proc uses A* to find the most optimal path between two turfs. It allows using a custom heuristic callback to change the
+ * weights of nodes. A* will always return the most optimal path.
+ *
+ * Arguments:
+ * * invoker: The movable atom that's trying to find the path
+ * * end: What we're trying to path to. It doesn't matter if this is a turf or some other atom, we're gonna just path to the turf it's on anyway
+ * * max_steps: The maximum number of steps we can take in a given path to search (default: 30, 0 = infinite)
+ * * mintargetdistance: Minimum distance to the target before path returns, could be used to get near a target, but not right to it - for an AI mob with a gun, for example.
+ * * access: A list representing what access we have and what doors we can open.
+ * * simulated_only: Whether we consider turfs without atmos simulation (AKA do we want to ignore space)
+ * * exclude: If we want to avoid a specific turf, like if we're a mulebot who already got blocked by some turf
+ * * skip_first: Whether or not to delete the first item in the path. This would be done because the first item is the starting tile, which can break movement for some creatures.
+ * * use_diagonals: If you want the path to include diagonal steps. Set to FALSE for cardinal moves only.
+ * * heuristic: A proc to call to determine how nodes are weighted. The higher the returned value, the less likely the pathfinder wants to traverse. 0 means invalid turf.
+ */
+/proc/astar_path_to(
+ atom/movable/invoker,
+ atom/end,
+ max_steps = 30,
+ mintargetdist,
+ list/access,
+ turf/exclude,
+ skip_first = TRUE,
+ use_diagonals = TRUE,
+ datum/callback/heuristic,
+)
+ var/datum/pathfind_packet/packet = new
+ // We're guarenteed that list will be the first list in pathfinding_finished's argset because of how callback handles the arguments list
+ var/datum/callback/await = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(pathfinding_finished), packet)
+ if(!SSpathfinder.astar_pathfind(invoker, end, max_steps, mintargetdist, access, exclude, skip_first, use_diagonals, list(await), heuristic))
+ return null
+
+ UNTIL(packet.path)
+
+ return packet.path
+
+/// Wrapper around the path list since we play with refs.
+/datum/pathfind_packet
+ /// The unwound path, set when it's finished.
+ var/list/path
+
+/// Uses funny pass by reference bullshit to take the path created by pathfinding, and insert it into a return list
+/// We'll be able to use this return list to tell a sleeping proc to continue execution
+/proc/pathfinding_finished(datum/pathfind_packet/packet, list/path)
+ // We use += here to ensure the list is still pointing at the same thing
+ packet.path = path
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index 352efc489e3..cb33f49e35e 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -17,7 +17,7 @@
ADD_TRAIT(src, TRAIT_DEAF, TRAIT_GENERIC)
Stun(6000, 1, 1)
ADD_TRAIT(src, TRAIT_MUTE, TRAIT_GENERIC)
- walk(src, 0) //stops them mid pathing even if they're stunimmune
+ GLOB.move_manager.stop_looping(src)
if(client)
add_verb(client, /client/proc/commendsomeone)
diff --git a/code/__HELPERS/spatial_info.dm b/code/__HELPERS/spatial_info.dm
index b8f79832038..ee643a1519a 100644
--- a/code/__HELPERS/spatial_info.dm
+++ b/code/__HELPERS/spatial_info.dm
@@ -451,3 +451,46 @@
if(our_area == get_area(carbon))
return FALSE
return TRUE
+
+///Returns the distance between two atoms
+/proc/get_dist_euclidean(atom/first_location, atom/second_location)
+ if(!first_location.z || !second_location.z)
+ return INFINITY
+
+ if(first_location == second_location)
+ return -1
+
+ var/dx = first_location.x - second_location.x
+ var/dy = first_location.y - second_location.y
+
+ var/dist = sqrt(dx ** 2 + dy ** 2)
+
+ return dist
+
+/// Returns the manhattan distance between two atoms. Returns INFINITY if either are not on a turf, for BYOND get_dist() parity.
+/proc/get_dist_manhattan(atom/first_location, atom/second_location)
+ if(!first_location.z || !second_location.z)
+ return INFINITY
+
+ if(first_location == second_location)
+ return -1
+
+ var/dx = first_location.x - second_location.x
+ var/dy = first_location.y - second_location.y
+ var/dz = first_location.z - second_location.z
+
+ return abs(dx) + abs(dy) + abs(dz)
+
+/// Returns the octile distance between two atoms. Returns INFINITY if either are not on a turf, for BYOND get_dist() parity.
+/proc/get_dist_octile(atom/first_location, atom/second_location)
+ if(!first_location.z || !second_location.z)
+ return INFINITY
+
+ if(first_location == second_location)
+ return -1
+
+ var/dx = abs(first_location.x - second_location.x)
+ var/dy = abs(first_location.y - second_location.y)
+ var/F = (sqrt(2) - 1)
+
+ return (dx < dy) ? F * dx + dy : F * dy + dx
diff --git a/code/controllers/subsystem/processing/ai_movement.dm b/code/controllers/subsystem/movement/ai_movement.dm
similarity index 52%
rename from code/controllers/subsystem/processing/ai_movement.dm
rename to code/controllers/subsystem/movement/ai_movement.dm
index ae8570d17da..20af8649c09 100644
--- a/code/controllers/subsystem/processing/ai_movement.dm
+++ b/code/controllers/subsystem/movement/ai_movement.dm
@@ -1,20 +1,19 @@
/// The subsystem used to tick [/datum/ai_movement] instances. Handling the movement of individual AI instances
-PROCESSING_SUBSYSTEM_DEF(ai_movement)
+MOVEMENT_SUBSYSTEM_DEF(ai_movement)
name = "AI movement"
- flags = SS_KEEP_TIMING|SS_BACKGROUND
+ flags = SS_BACKGROUND|SS_TICKER
priority = FIRE_PRIORITY_NPC_MOVEMENT
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
init_order = INIT_ORDER_AI_MOVEMENT
- wait = 1
+
///an assoc list of all ai_movement types. Assoc type to instance
var/list/movement_types
-/datum/controller/subsystem/processing/ai_movement/Initialize(timeofday)
+/datum/controller/subsystem/movement/ai_movement/Initialize(timeofday)
SetupAIMovementInstances()
return ..()
-/datum/controller/subsystem/processing/ai_movement/proc/SetupAIMovementInstances()
+/datum/controller/subsystem/movement/ai_movement/proc/SetupAIMovementInstances()
movement_types = list()
- for(var/key in subtypesof(/datum/ai_movement))
- var/datum/ai_movement/ai_movement = new key
- movement_types[key] = ai_movement
+ for(var/datum/ai_movement/ai_movement as anything in subtypesof(/datum/ai_movement))
+ movement_types[ai_movement] = new ai_movement
diff --git a/code/controllers/subsystem/movement/move_manager.dm b/code/controllers/subsystem/movement/move_manager.dm
index f9389768364..01fbadebf5c 100644
--- a/code/controllers/subsystem/movement/move_manager.dm
+++ b/code/controllers/subsystem/movement/move_manager.dm
@@ -17,16 +17,15 @@
*
* You can find the logic for this control in this file
*
- * Specifics of how different loops operate can be found in the movement_types.dm file, alongside the [add to loop][/datum/controller/subsystem/move_manager/proc/add_to_loop] helper procs that use them
+ * Specifics of how different loops operate can be found in the movement_types.dm file, alongside the [add to loop][/datum/move_manager/proc/add_to_loop] helper procs that use them
*
**/
-SUBSYSTEM_DEF(move_manager)
- name = "Movement Handler"
- flags = SS_NO_INIT | SS_NO_FIRE
- runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+/datum/move_manager
+
+GLOBAL_DATUM_INIT(move_manager, /datum/move_manager, new)
///Adds a movable thing to a movement subsystem. Returns TRUE if it all worked, FALSE if it failed somehow
-/datum/controller/subsystem/move_manager/proc/add_to_loop(atom/movable/thing_to_add, datum/controller/subsystem/movement/subsystem = SSmovement, datum/move_loop/loop_type, priority = MOVEMENT_DEFAULT_PRIORITY, flags, datum/extra_info)
+/datum/move_manager/proc/add_to_loop(atom/movable/thing_to_add, datum/controller/subsystem/movement/subsystem = SSmovement, datum/move_loop/loop_type, priority = MOVEMENT_DEFAULT_PRIORITY, flags, datum/extra_info)
var/datum/movement_packet/our_data = thing_to_add.move_packet
if(!our_data)
our_data = new(thing_to_add)
@@ -35,7 +34,7 @@ SUBSYSTEM_DEF(move_manager)
return our_data.add_loop(arglist(arguments))
///Returns the subsystem's loop if we're processing on it, null otherwise
-/datum/controller/subsystem/move_manager/proc/processing_on(atom/movable/packet_owner, datum/controller/subsystem/movement/subsystem)
+/datum/move_manager/proc/processing_on(atom/movable/packet_owner, datum/controller/subsystem/movement/subsystem)
var/datum/movement_packet/packet = packet_owner.move_packet
if(!packet)
return
@@ -83,10 +82,14 @@ SUBSYSTEM_DEF(move_manager)
///Adds a loop to our parent. Returns the created loop if a success, null otherwise
/datum/movement_packet/proc/add_loop(datum/controller/subsystem/movement/subsystem, datum/move_loop/loop_type, priority, flags, datum/extra_info)
var/datum/move_loop/existing_loop = existing_loops[subsystem]
+
if(existing_loop && existing_loop.priority > priority)
if(!(existing_loop.flags & MOVEMENT_LOOP_IGNORE_PRIORITY) && !(flags & MOVEMENT_LOOP_IGNORE_PRIORITY))
return //Give up
+ if(existing_loop?.compare_loops(arglist(args.Copy(2))))
+ return //it already exists stop trying to make the same moveloop
+
var/datum/move_loop/new_loop = new loop_type(src, subsystem, parent, priority, flags, extra_info) //Pass the mob to move and ourselves in via new
var/list/arguments = args.Copy(6) //Just send the args we've not already dealt with
@@ -117,9 +120,12 @@ SUBSYSTEM_DEF(move_manager)
var/datum/controller/subsystem/movement/current_subsystem = running_loop.controller
- current_subsystem.remove_loop(running_loop)
- contesting_subsystem.add_loop(contestant)
+ var/current_running_loop = running_loop
running_loop = contestant
+ current_subsystem.remove_loop(current_running_loop)
+ if(running_loop != contestant) // A signal registrant could have messed with things
+ return FALSE
+ contesting_subsystem.add_loop(contestant)
return TRUE
///Tries to figure out the current favorite loop to run. More complex then just deciding between two different loops, assumes no running loop currently exists
@@ -134,7 +140,7 @@ SUBSYSTEM_DEF(move_manager)
var/datum/move_loop/checking = existing_loops[owner]
if(checking.flags & MOVEMENT_LOOP_IGNORE_PRIORITY)
continue
- if(favorite && favorite.priority < checking.priority)
+ if(favorite && favorite.priority > checking.priority)
continue
favorite = checking
@@ -148,8 +154,8 @@ SUBSYSTEM_DEF(move_manager)
/datum/movement_packet/proc/remove_loop(datum/controller/subsystem/movement/remove_from, datum/move_loop/loop_to_remove)
if(loop_to_remove == running_loop)
- remove_from.remove_loop(loop_to_remove)
running_loop = null
+ remove_from.remove_loop(loop_to_remove)
if(loop_to_remove.flags & MOVEMENT_LOOP_IGNORE_PRIORITY)
remove_from.remove_loop(loop_to_remove)
if(QDELETED(src))
diff --git a/code/controllers/subsystem/movement/movement_loop.dm b/code/controllers/subsystem/movement/movement_loop.dm
index 758a751b127..cbc36b7408c 100644
--- a/code/controllers/subsystem/movement/movement_loop.dm
+++ b/code/controllers/subsystem/movement/movement_loop.dm
@@ -1,18 +1,29 @@
SUBSYSTEM_DEF(movement)
name = "Movement Loops"
- flags = SS_NO_INIT|SS_BACKGROUND|SS_TICKER
+ flags = SS_NO_INIT|SS_TICKER
wait = 1 //Fire each tick
- ///The list of datums we're processing
- var/list/processing = list()
- ///Used to make pausing possible
- var/list/currentrun = list()
+ /*
+ A brief aside about the bucketing system here
+
+ The goal is to allow for higher loads of semi long delays while reducing cpu usage
+ Bucket insertion and management are much less complex then what you might see in SStimer
+ This is intentional, as we loop our delays much more often then that ss is designed for
+ We also have much shorter term timers, so we need to worry about redundant buckets much less
+ */
+ ///Assoc list of "target time" -> list(things to process). Used for quick lookup
+ var/list/buckets = list()
+ ///Sorted list of list(target time, bucket to process)
+ var/list/sorted_buckets = list()
///The time we started our last fire at
var/canonical_time = 0
///The visual delay of the subsystem
var/visual_delay = 1
/datum/controller/subsystem/movement/stat_entry(msg)
- msg = "P:[length(processing)]"
+ var/total_len = 0
+ for(var/list/bucket as anything in sorted_buckets)
+ total_len += length(bucket[MOVEMENT_BUCKET_LIST])
+ msg = "B:[length(sorted_buckets)] E:[total_len]"
return ..()
/datum/controller/subsystem/movement/Recover()
@@ -22,29 +33,101 @@ SUBSYSTEM_DEF(movement)
var/our_name = typenames[length(typenames)] //Get the last name in the list, IE the subsystem identifier
var/datum/controller/subsystem/movement/old_version = global.vars["SS[our_name]"]
- processing = old_version.processing
- currentrun = old_version.currentrun
+ buckets = old_version.buckets
+ sorted_buckets = old_version.sorted_buckets
/datum/controller/subsystem/movement/fire(resumed)
if(!resumed)
canonical_time = world.time
- currentrun = processing.Copy()
-
- var/list/running = currentrun //Cache for... you've heard this before
- while(length(running))
- var/datum/move_loop/loop = running[length(running)]
- running.len--
- if(loop.timer <= canonical_time)
- loop.process() //This shouldn't get nulls, if it does, runtime
- if (MC_TICK_CHECK)
+
+ for(var/list/bucket_info as anything in sorted_buckets)
+ var/time = bucket_info[MOVEMENT_BUCKET_TIME]
+ if(time > canonical_time || MC_TICK_CHECK)
return
- visual_delay = MC_AVERAGE_FAST(visual_delay, max((world.time - canonical_time) / wait, 1))
+ pour_bucket(bucket_info)
+
+/// Processes a bucket of movement loops (This should only ever be called by fire(), it exists to prevent runtime fuckery)
+/datum/controller/subsystem/movement/proc/pour_bucket(list/bucket_info)
+ var/list/processing = bucket_info[MOVEMENT_BUCKET_LIST] // Cache for lookup speed
+ while(length(processing))
+ var/datum/move_loop/loop = processing[length(processing)]
+ processing.len--
+ // No longer queued since we just got removed from the loop
+ loop.queued_time = null
+ loop.process() //This shouldn't get nulls, if it does, runtime
+ if(!QDELETED(loop) && loop.status & MOVELOOP_STATUS_QUEUED) //Re-Insert the loop
+ loop.status &= ~MOVELOOP_STATUS_QUEUED
+ loop.timer = world.time + loop.delay
+ queue_loop(loop)
+ if (MC_TICK_CHECK)
+ break
+
+ if(length(processing))
+ return // Still work to be done
+ var/bucket_time = bucket_info[MOVEMENT_BUCKET_TIME]
+ smash_bucket(1, bucket_time) // We assume we're the first bucket in the queue right now
+ visual_delay = MC_AVERAGE_FAST(visual_delay, max((world.time - canonical_time) / TICKS2DS(wait), 1))
+
+/// Removes a bucket from our system. You only need to pass in the time, but if you pass in the index of the list you save us some work
+/datum/controller/subsystem/movement/proc/smash_bucket(index, bucket_time)
+ var/sorted_length = length(sorted_buckets)
+ if(!index)
+ index = sorted_length + 1 // let's setup the failure condition
+ for(var/i in 1 to sorted_length)
+ var/list/bucket_info = sorted_buckets[i]
+ if(bucket_info[MOVEMENT_BUCKET_TIME] != bucket_time)
+ continue
+ index = i
+ break
+ //This is technically possible, if our bucket is smashed inside the loop's process
+ //Let's be nice, the cost of doing it is cheap
+ if(index > sorted_length || !buckets["[bucket_time]"])
+ return
+
+ sorted_buckets.Cut(index, index + 1) //Removes just this list
+ //Removes the assoc lookup too
+ buckets -= "[bucket_time]"
+
+/datum/controller/subsystem/movement/proc/queue_loop(datum/move_loop/loop)
+ if(loop.status & MOVELOOP_STATUS_QUEUED)
+ stack_trace("A move loop attempted to queue while already queued")
+ return
+ loop.queued_time = loop.timer
+ loop.status |= MOVELOOP_STATUS_QUEUED
+ var/list/our_bucket = buckets["[loop.queued_time]"]
+ // If there's no bucket for this, lets set them up
+ if(!our_bucket)
+ buckets["[loop.queued_time]"] = list()
+ our_bucket = buckets["[loop.queued_time]"]
+ // This makes assoc buckets and sorted buckets point to the same place, allowing for quicker inserts
+ var/list/new_bucket = list(list(loop.queued_time, our_bucket))
+ var/list/compare_item = list(loop.queued_time)
+ BINARY_INSERT_DEFINE(new_bucket, sorted_buckets, SORT_VAR_NO_TYPE, compare_item, SORT_FIRST_INDEX, COMPARE_KEY)
+
+ our_bucket += loop
+
+/datum/controller/subsystem/movement/proc/dequeue_loop(datum/move_loop/loop)
+ // Go home, you're not here anyway
+ if(!(loop.status & MOVELOOP_STATUS_QUEUED))
+ return
+ if(isnull(loop.queued_time)) // This happens if a moveloop is dequeued while handling process()
+ loop.status &= ~MOVELOOP_STATUS_QUEUED
+ return
+ var/list/our_entries = buckets["[loop.queued_time]"]
+ our_entries -= loop
+ if(!length(our_entries))
+ smash_bucket(bucket_time = loop.queued_time) // We can't pass an index in for context because we don't know our position
+ loop.queued_time = null
+ loop.status &= ~MOVELOOP_STATUS_QUEUED
/datum/controller/subsystem/movement/proc/add_loop(datum/move_loop/add)
- processing += add
- add.start_loop()
+ if(add.status & MOVELOOP_STATUS_QUEUED)
+ CRASH("Loop being added that is already queued.")
+ add.loop_started()
+ if(QDELETED(add) || add.status & MOVELOOP_STATUS_QUEUED)
+ return
+ queue_loop(add)
/datum/controller/subsystem/movement/proc/remove_loop(datum/move_loop/remove)
- processing -= remove
- currentrun -= remove
- remove.stop_loop()
+ dequeue_loop(remove)
+ remove.loop_stopped()
diff --git a/code/controllers/subsystem/movement/movement_types.dm b/code/controllers/subsystem/movement/movement_types.dm
index cf3a4e018d3..8c9287815c0 100644
--- a/code/controllers/subsystem/movement/movement_types.dm
+++ b/code/controllers/subsystem/movement/movement_types.dm
@@ -11,7 +11,7 @@
var/datum/extra_info
///The thing we're moving about
var/atom/movable/moving
- ///Defines how different move loops override each other. Lower numbers beat higher numbers
+ ///Defines how different move loops override each other. Higher numbers beat lower numbers
var/priority = MOVEMENT_DEFAULT_PRIORITY
///Bitfield of different things that affect how a loop operates
var/flags
@@ -20,7 +20,13 @@
///Delay between each move in deci-seconds
var/delay = 1
///The next time we should process
+ ///Used primarially as a hint to be reasoned about by our [controller], and as the id of our bucket
var/timer = 0
+ ///The time we are CURRENTLY queued for processing
+ ///Do not modify this directly
+ var/queued_time = -1
+ /// Status bitfield for what state the move loop is currently in
+ var/status = NONE
/datum/move_loop/New(datum/movement_packet/owner, datum/controller/subsystem/movement/controller, atom/moving, priority, flags, datum/extra_info)
src.owner = owner
@@ -32,6 +38,15 @@
src.priority = priority
src.flags = flags
+/datum/move_loop/Destroy()
+ if(owner)
+ owner.remove_loop(controller, src)
+ owner = null
+ moving = null
+ controller = null
+ extra_info = null
+ return ..()
+
/datum/move_loop/proc/setup(delay = 1, timeout = INFINITY)
if(!ismovable(moving) || !owner)
return FALSE
@@ -40,38 +55,58 @@
src.lifetime = timeout
return TRUE
-/datum/move_loop/proc/start_loop()
+///check if this exact moveloop datum already exists (in terms of vars) so we can avoid creating a new one to overwrite the old duplicate
+/datum/move_loop/proc/compare_loops(datum/move_loop/loop_type, priority, flags, extra_info, delay = 1, timeout = INFINITY)
SHOULD_CALL_PARENT(TRUE)
+
+ if(loop_type == type && priority == src.priority && flags == src.flags && delay == src.delay && timeout == lifetime)
+ return TRUE
+
+ return FALSE
+
+///Called when a loop is starting by a movement subsystem
+/datum/move_loop/proc/loop_started()
+ SHOULD_CALL_PARENT(TRUE)
+
SEND_SIGNAL(src, COMSIG_MOVELOOP_START)
+ status |= MOVELOOP_STATUS_RUNNING
//If this is our first time starting to move with this loop
//And we're meant to start instantly
if(!timer && flags & MOVEMENT_LOOP_START_FAST)
timer = world.time
return
+
timer = world.time + delay
-/datum/move_loop/proc/stop_loop()
+///Called when a loop is stopped, doesn't stop the loop itself
+/datum/move_loop/proc/loop_stopped()
SHOULD_CALL_PARENT(TRUE)
+
+ status &= ~MOVELOOP_STATUS_RUNNING
SEND_SIGNAL(src, COMSIG_MOVELOOP_STOP)
/datum/move_loop/proc/info_deleted(datum/source)
SIGNAL_HANDLER
- extra_info = null
-/datum/move_loop/Destroy()
- if(owner)
- owner.remove_loop(controller, src)
- owner = null
- moving = null
- controller = null
extra_info = null
- return ..()
-///Exists as a helper so outside code can modify delay while also modifying timer
+///Exists as a helper so outside code can modify delay in a sane way
/datum/move_loop/proc/set_delay(new_delay)
delay = max(new_delay, world.tick_lag)
timer = world.time + delay
+///Pauses the move loop for some passed in period
+///This functionally means shifting its timer up, and clearing it from its current bucket
+/datum/move_loop/proc/pause_for(time)
+ if(!controller || !(status & MOVELOOP_STATUS_RUNNING)) //No controller or not running? go away
+ return
+ //Dequeue us from our current bucket
+ controller.dequeue_loop(src)
+ //Offset our timer
+ timer = world.time + time
+ //Now requeue us with our new target start time
+ controller.queue_loop(src)
+
/datum/move_loop/process()
var/old_delay = delay //The signal can sometimes change delay
@@ -85,6 +120,7 @@
return
var/visual_delay = controller.visual_delay
+
var/old_dir = moving.dir
var/old_loc = moving.loc
@@ -111,8 +147,26 @@
/datum/move_loop/proc/move()
return MOVELOOP_FAILURE
+///Pause our loop untill restarted with resume_loop()
+/datum/move_loop/proc/pause_loop()
+ if(!controller || !(status & MOVELOOP_STATUS_RUNNING) || (status & MOVELOOP_STATUS_PAUSED)) //we dead
+ return
+
+ //Dequeue us from our current bucket
+ controller.dequeue_loop(src)
+ status |= MOVELOOP_STATUS_PAUSED
+
+///Resume our loop after being paused by pause_loop()
+/datum/move_loop/proc/resume_loop()
+ if(!controller || (status & MOVELOOP_STATUS_RUNNING|MOVELOOP_STATUS_PAUSED) != (MOVELOOP_STATUS_RUNNING|MOVELOOP_STATUS_PAUSED))
+ return
+
+ timer = world.time
+ controller.queue_loop(src)
+ status &= ~MOVELOOP_STATUS_PAUSED
+
///Removes the atom from some movement subsystem. Defaults to SSmovement
-/datum/controller/subsystem/move_manager/proc/stop_looping(atom/movable/moving, datum/controller/subsystem/movement/subsystem = SSmovement)
+/datum/move_manager/proc/stop_looping(atom/movable/moving, datum/controller/subsystem/movement/subsystem = SSmovement)
var/datum/movement_packet/our_info = moving.move_packet
if(!our_info)
return FALSE
@@ -133,7 +187,7 @@
* flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
*
**/
-/datum/controller/subsystem/move_manager/proc/move(moving, direction, delay, timeout, subsystem, priority, flags, datum/extra_info, datum/move_loop/move_loop_type = /datum/move_loop/move)
+/datum/move_manager/proc/move(moving, direction, delay, timeout, subsystem, priority, flags, datum/extra_info, datum/move_loop/move_loop_type = /datum/move_loop/move)
return add_to_loop(moving, subsystem, move_loop_type, priority, flags, extra_info, delay, timeout, direction)
///Replacement for walk()
@@ -146,11 +200,17 @@
return
direction = dir
+
+/datum/move_loop/move/compare_loops(datum/move_loop/loop_type, priority, flags, extra_info, delay, timeout, dir)
+ if(..() && direction == dir)
+ return TRUE
+ return FALSE
+
/datum/move_loop/move/move()
var/atom/old_loc = moving.loc
moving.Move(get_step(moving, direction), direction, FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE))
// We cannot rely on the return value of Move(), we care about teleports and it doesn't
- // Moving also can be null on occasion, if the move deleted it and therefor us
+ // Moving also can be null on occasion, if the move deleted it and therefore us
return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE
/**
@@ -168,7 +228,7 @@
* flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
*
**/
-/datum/controller/subsystem/move_manager/proc/move_to_dir(moving, direction, delay, timeout, subsystem, priority, flags, datum/extra_info)
+/datum/move_manager/proc/move_to_dir(moving, direction, delay, timeout, subsystem, priority, flags, datum/extra_info)
return add_to_loop(moving, subsystem, /datum/move_loop/move/move_to, priority, flags, extra_info, delay, timeout, direction)
/datum/move_loop/move/move_to
@@ -176,8 +236,7 @@
/datum/move_loop/move/move_to/move()
var/atom/old_loc = moving.loc
step_to(moving, get_step(moving, direction))
- return old_loc != moving.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE
-
+ return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE
/**
* Like move(), but we don't care about collision at all
@@ -194,7 +253,7 @@
* flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
*
**/
-/datum/controller/subsystem/move_manager/proc/force_move_dir(moving, direction, delay, timeout, subsystem, priority, flags, datum/extra_info)
+/datum/move_manager/proc/force_move_dir(moving, direction, delay, timeout, subsystem, priority, flags, datum/extra_info)
return add_to_loop(moving, subsystem, /datum/move_loop/move/force, priority, flags, extra_info, delay, timeout, direction)
/datum/move_loop/move/force
@@ -222,6 +281,12 @@
if(!isturf(target))
RegisterSignal(target, COMSIG_QDELETING, PROC_REF(handle_no_target)) //Don't do this for turfs, because we don't care
+
+/datum/move_loop/has_target/compare_loops(datum/move_loop/loop_type, priority, flags, extra_info, delay, timeout, atom/chasing)
+ if(..() && chasing == target)
+ return TRUE
+ return FALSE
+
/datum/move_loop/has_target/Destroy()
target = null
return ..()
@@ -230,7 +295,6 @@
SIGNAL_HANDLER
qdel(src)
-
/**
* Used for force-move loops, similar to move_towards_legacy() but not quite the same
*
@@ -246,7 +310,7 @@
* flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
*
**/
-/datum/controller/subsystem/move_manager/proc/force_move(moving, chasing, delay, timeout, subsystem, priority, flags, datum/extra_info)
+/datum/move_manager/proc/force_move(moving, chasing, delay, timeout, subsystem, priority, flags, datum/extra_info)
return add_to_loop(moving, subsystem, /datum/move_loop/has_target/force_move, priority, flags, extra_info, delay, timeout, chasing)
///Used for force-move loops
@@ -267,6 +331,11 @@
return
distance = dist
+/datum/move_loop/has_target/dist_bound/compare_loops(datum/move_loop/loop_type, priority, flags, extra_info, delay, timeout, atom/chasing, dist = 0)
+ if(..() && distance == dist)
+ return TRUE
+ return FALSE
+
///Returns FALSE if the movement should pause, TRUE otherwise
/datum/move_loop/has_target/dist_bound/proc/check_dist()
return FALSE
@@ -293,7 +362,7 @@
* flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
*
**/
-/datum/controller/subsystem/move_manager/proc/move_to(moving, chasing, min_dist, delay, timeout, subsystem, priority, flags, datum/extra_info)
+/datum/move_manager/proc/move_to(moving, chasing, min_dist, delay, timeout, subsystem, priority, flags, datum/extra_info)
return add_to_loop(moving, subsystem, /datum/move_loop/has_target/dist_bound/move_to, priority, flags, extra_info, delay, timeout, chasing, min_dist)
///Wrapper around walk_to()
@@ -327,7 +396,7 @@
* flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
*
**/
-/datum/controller/subsystem/move_manager/proc/move_away(moving, chasing, max_dist, delay, timeout, subsystem, priority, flags, datum/extra_info)
+/datum/move_manager/proc/move_away(moving, chasing, max_dist, delay, timeout, subsystem, priority, flags, datum/extra_info)
return add_to_loop(moving, subsystem, /datum/move_loop/has_target/dist_bound/move_away, priority, flags, extra_info, delay, timeout, chasing, max_dist)
///Wrapper around walk_away()
@@ -361,7 +430,7 @@
* flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
*
**/
-/datum/controller/subsystem/move_manager/proc/move_towards(moving, chasing, delay, home, timeout, subsystem, priority, flags, datum/extra_info)
+/datum/move_manager/proc/move_towards(moving, chasing, delay, home, timeout, subsystem, priority, flags, datum/extra_info)
return add_to_loop(moving, subsystem, /datum/move_loop/has_target/move_towards, priority, flags, extra_info, delay, timeout, chasing, home)
/**
@@ -380,7 +449,7 @@
* flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
*
**/
-/datum/controller/subsystem/move_manager/proc/home_onto(moving, chasing, delay, timeout, subsystem, priority, flags, datum/extra_info)
+/datum/move_manager/proc/home_onto(moving, chasing, delay, timeout, subsystem, priority, flags, datum/extra_info)
return move_towards(moving, chasing, delay, TRUE, timeout, subsystem, priority, flags, extra_info)
///Used as a alternative to walk_towards
@@ -412,6 +481,11 @@
RegisterSignal(moving, COMSIG_MOVABLE_MOVED, PROC_REF(handle_move))
update_slope()
+/datum/move_loop/has_target/move_towards/compare_loops(datum/move_loop/loop_type, priority, flags, extra_info, delay, timeout, atom/chasing, home = FALSE)
+ if(..() && home == src.home)
+ return TRUE
+ return FALSE
+
/datum/move_loop/has_target/move_towards/Destroy()
if(home)
if(ismovable(target))
@@ -511,7 +585,7 @@
* flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
*
**/
-/datum/controller/subsystem/move_manager/proc/move_towards_legacy(moving, chasing, delay, timeout, subsystem, priority, flags, datum/extra_info)
+/datum/move_manager/proc/move_towards_legacy(moving, chasing, delay, timeout, subsystem, priority, flags, datum/extra_info)
return add_to_loop(moving, subsystem, /datum/move_loop/has_target/move_towards_budget, priority, flags, extra_info, delay, timeout, chasing)
///The actual implementation of walk_towards()
@@ -522,7 +596,6 @@
var/atom/old_loc = moving.loc
moving.Move(target_turf, get_dir(moving, target_turf), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE))
return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE
-
/**
* Helper proc for the move_rand datum
*
@@ -538,7 +611,7 @@
* flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
*
**/
-/datum/controller/subsystem/move_manager/proc/move_rand(moving, directions, delay, timeout, subsystem, priority, flags, datum/extra_info)
+/datum/move_manager/proc/move_rand(moving, directions, delay, timeout, subsystem, priority, flags, datum/extra_info)
if(!directions)
directions = GLOB.alldirs
return add_to_loop(moving, subsystem, /datum/move_loop/move_rand, priority, flags, extra_info, delay, timeout, directions)
@@ -559,9 +632,14 @@
return
potential_directions = directions
+/datum/move_loop/move_rand/compare_loops(datum/move_loop/loop_type, priority, flags, extra_info, delay, timeout, list/directions)
+ if(..() && (length(potential_directions | directions) == length(potential_directions))) //i guess this could be useful if actually it really has yet to move
+ return TRUE
+ return FALSE
+
/datum/move_loop/move_rand/move()
var/list/potential_dirs = potential_directions.Copy()
- while(potential_dirs.len)
+ while(length(potential_dirs))
var/testdir = pick(potential_dirs)
var/turf/moving_towards = get_step(moving, testdir)
var/atom/old_loc = moving.loc
@@ -585,7 +663,7 @@
* flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
*
**/
-/datum/controller/subsystem/move_manager/proc/move_to_rand(moving, delay, timeout, subsystem, priority, flags, datum/extra_info)
+/datum/move_manager/proc/move_to_rand(moving, delay, timeout, subsystem, priority, flags, datum/extra_info)
return add_to_loop(moving, subsystem, /datum/move_loop/move_to_rand, priority, flags, extra_info, delay, timeout)
///Wrapper around step_rand
@@ -596,56 +674,3 @@
var/turf/next = get_step_rand(moving)
moving.Move(next, get_dir(moving, next), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE))
return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE
-
-/datum/move_loop/minecart
- var/direction
- var/aerial_distance
- var/aerial_velocity
-
-/datum/move_loop/minecart/setup(delay, timeout, dir)
- . = ..()
- if(!.)
- return
- direction = dir
-
-/datum/move_loop/minecart/Destroy()
- if(!QDELETED(moving) && istype(moving, /obj/structure/closet/crate/miningcar))
- var/turf/moving_turf = get_turf(moving)
- if(istype(moving_turf, /turf/open/openspace))
- var/obj/structure/closet/crate/miningcar/minecart = moving
- minecart.handle_aerial_fall(freefall = TRUE)
- . = ..()
-
-/datum/move_loop/minecart/process()
- . = ..()
- if(QDELETED(src) || QDELETED(moving))
- return
- var/turf/moving_turf = get_turf(moving)
- if(istype(moving_turf, /turf/open/openspace))
- aerial_velocity += (delay / 10) * 9.8
- aerial_distance += aerial_velocity * (delay / 10)
- if(aerial_distance >= 1)
- aerial_distance -= 1
- if(istype(moving, /obj/structure/closet/crate/miningcar))
- var/obj/structure/closet/crate/miningcar/minecart = moving
- minecart.handle_aerial_fall()
- else
- aerial_distance = 0
- aerial_velocity = 0
-
-/datum/move_loop/minecart/move()
- var/atom/old_loc = moving.loc
- var/turf/new_loc = get_step(moving, direction)
- if(locate(/obj/structure/minecart_rail) in old_loc)
- if(istype(new_loc, /turf/open/openspace))
- var/turf/below_turf = GET_TURF_BELOW(new_loc)
- if(locate(/obj/structure/minecart_rail) in below_turf)
- new_loc = below_turf
- else if(!(locate(/obj/structure/minecart_rail) in new_loc))
- var/turf/above_turf = GET_TURF_ABOVE(new_loc)
- if(locate(/obj/structure/minecart_rail) in above_turf)
- new_loc = above_turf
-
- moving.Move(new_loc, direction, FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE))
- // We cannot rely on the return value of Move(), we care about teleports and it doesn't
- return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE
diff --git a/code/controllers/subsystem/movement/movment_loops/astar.dm b/code/controllers/subsystem/movement/movment_loops/astar.dm
new file mode 100644
index 00000000000..371216d7ae9
--- /dev/null
+++ b/code/controllers/subsystem/movement/movment_loops/astar.dm
@@ -0,0 +1,224 @@
+/**
+ * Used for following astar defined paths. The proc signature here's a bit long, I'm sorry
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * chasing - The atom we want to move towards
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * repath_delay - How often we're allowed to recalculate our path
+ * max_path_length - The maximum number of steps we can take in a given path to search (default: 30, 0 = infinite)
+ * miminum_distance - Minimum distance to the target before path returns, could be used to get near a target, but not right to it - for an AI mob with a gun, for example
+ * access - A list representing what access we have and what doors we can open
+ * simulated_only - Whether we consider turfs without atmos simulation (AKA do we want to ignore space)
+ * avoid - If we want to avoid a specific turf, like if we're a mulebot who already got blocked by some turf
+ * skip_first - Whether or not to delete the first item in the path. This would be done because the first item is the starting tile, which can break things
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/move_manager/proc/astar_move(
+ moving,
+ chasing,
+ delay,
+ timeout,
+ repath_delay,
+ max_path_length,
+ minimum_distance,
+ list/access,
+ turf/avoid,
+ skip_first,
+ subsystem,
+ priority,
+ flags,
+ datum/extra_info,
+ initial_path,
+ use_diagonals,
+ datum/callback/heuristic,
+ closest_approach,
+)
+ return add_to_loop(
+ moving,
+ subsystem,
+ /datum/move_loop/has_target/astar,
+ priority,
+ flags,
+ extra_info,
+ delay,
+ timeout,
+ chasing,
+ repath_delay,
+ max_path_length,
+ minimum_distance,
+ access,
+ avoid,
+ skip_first,
+ initial_path,
+ use_diagonals,
+ heuristic,
+ closest_approach,
+ )
+
+/datum/move_loop/has_target/astar
+ ///How often we're allowed to recalculate our path
+ var/repath_delay
+ ///Max amount of steps to search
+ var/max_path_length
+ ///Minimum distance to the target before path returns
+ var/minimum_distance
+ ///A list representing what access we have and what doors we can open.
+ var/list/access
+ ///A perticular turf to avoid
+ var/turf/avoid
+ ///Should we skip the first step? This is the tile we're currently on, which breaks some things
+ var/skip_first
+ ///A list for the path we're currently following
+ var/list/movement_path
+ /// Should we use diagonals, or use only cardinals?
+ var/use_diagonals
+ ///Cooldown for repathing, prevents spam
+ COOLDOWN_DECLARE(repath_cooldown)
+ ///Cooldown for anticipated paths
+ COOLDOWN_DECLARE(repath_anticipated_cooldown)
+ ///Bool used to determine if we're already making a path in Astar. this prevents us from re-pathing while we're already busy.
+ var/is_pathing = FALSE
+ ///Callbacks to invoke once we make a path
+ var/list/datum/callback/on_finish_callbacks = list()
+ ///AStar heuristic function
+ var/datum/callback/heuristic
+ ///If the closest approach is returned when pathing fails EXPENSIVE!
+ var/closest_approach
+
+/datum/move_loop/has_target/astar/New(datum/movement_packet/owner, datum/controller/subsystem/movement/controller, atom/moving, priority, flags, datum/extra_info)
+ . = ..()
+ on_finish_callbacks += CALLBACK(src, PROC_REF(on_finish_pathing))
+
+/datum/move_loop/has_target/astar/setup(
+ delay,
+ timeout,
+ atom/chasing,
+ repath_delay,
+ max_path_length,
+ minimum_distance,
+ list/access,
+ turf/avoid,
+ skip_first,
+ list/initial_path,
+ use_diagonals,
+ datum/callback/heuristic,
+ closest_approach,
+)
+ . = ..()
+ if(!.)
+ return
+ src.repath_delay = repath_delay
+ src.max_path_length = max_path_length
+ src.minimum_distance = minimum_distance
+ src.access = access
+ src.avoid = avoid
+ src.skip_first = skip_first
+ src.use_diagonals = use_diagonals
+ src.heuristic = heuristic
+ src.closest_approach = closest_approach
+ movement_path = initial_path?.Copy()
+
+/datum/move_loop/has_target/astar/compare_loops(
+ datum/move_loop/loop_type,
+ priority,
+ flags,
+ extra_info,
+ delay,
+ timeout,
+ atom/chasing,
+ repath_delay,
+ max_path_length,
+ minimum_distance,
+ list/access,
+ turf/avoid,
+ skip_first,
+ initial_path,
+ use_diagonals,
+ datum/callback/heuristic,
+ closest_approach,
+)
+ if(..() && \
+ repath_delay == src.repath_delay && \
+ max_path_length == src.max_path_length && \
+ minimum_distance == src.minimum_distance && \
+ access ~= src.access && \
+ avoid == src.avoid && \
+ use_diagonals == src.use_diagonals && \
+ heuristic == src.heuristic && \
+ closest_approach == src.closest_approach)
+ return TRUE
+ return FALSE
+
+/datum/move_loop/has_target/astar/loop_started()
+ . = ..()
+ if(!length(movement_path))
+ INVOKE_ASYNC(src, PROC_REF(recalculate_path))
+
+/datum/move_loop/has_target/astar/loop_stopped()
+ . = ..()
+ movement_path = null
+
+/datum/move_loop/has_target/astar/Destroy()
+ avoid = null
+ on_finish_callbacks = null
+ return ..()
+
+///Tries to calculate a new path for this moveloop.
+/datum/move_loop/has_target/astar/proc/recalculate_path()
+ if(!COOLDOWN_FINISHED(src, repath_cooldown))
+ return
+
+ COOLDOWN_START(src, repath_cooldown, repath_delay)
+
+ var/path_ok = SSpathfinder.astar_pathfind(
+ moving,
+ target,
+ max_path_length,
+ minimum_distance,
+ access,
+ avoid,
+ skip_first,
+ use_diagonals = use_diagonals,
+ on_finish = on_finish_callbacks,
+ heuristic = heuristic,
+ closest_approach = closest_approach,
+ )
+
+ if(path_ok)
+ is_pathing = TRUE
+ SEND_SIGNAL(src, COMSIG_MOVELOOP_REPATH)
+
+///Called when a path has finished being created
+/datum/move_loop/has_target/astar/proc/on_finish_pathing(list/path)
+ movement_path = path
+ is_pathing = FALSE
+ SEND_SIGNAL(src, COMSIG_MOVELOOP_FINISHED_PATHING, path)
+
+/datum/move_loop/has_target/astar/move()
+ if(!length(movement_path))
+ if(is_pathing)
+ return MOVELOOP_NOT_READY
+ else
+ INVOKE_ASYNC(src, PROC_REF(recalculate_path))
+ return MOVELOOP_FAILURE
+
+ var/turf/next_step = movement_path[1]
+ var/atom/old_loc = moving.loc
+ moving.Move(next_step, get_dir(moving, next_step), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE))
+ . = (old_loc != moving?.loc) ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE
+
+ // this check if we're on exactly the next tile may be overly brittle for dense objects who may get bumped slightly
+ // to the side while moving but could maybe still follow their path without needing a whole new path
+ if(get_turf(moving) == next_step)
+ if(length(movement_path))
+ movement_path.Cut(1,2)
+ else
+ INVOKE_ASYNC(src, PROC_REF(recalculate_path))
+ return MOVELOOP_FAILURE
diff --git a/code/controllers/subsystem/movement/movment_loops/hybrid.dm b/code/controllers/subsystem/movement/movment_loops/hybrid.dm
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/code/controllers/subsystem/movement/movment_loops/minecart.dm b/code/controllers/subsystem/movement/movment_loops/minecart.dm
new file mode 100644
index 00000000000..d9177f9c5d3
--- /dev/null
+++ b/code/controllers/subsystem/movement/movment_loops/minecart.dm
@@ -0,0 +1,52 @@
+/datum/move_loop/minecart
+ var/direction
+ var/aerial_distance
+ var/aerial_velocity
+
+/datum/move_loop/minecart/setup(delay, timeout, dir)
+ . = ..()
+ if(!.)
+ return
+ direction = dir
+
+/datum/move_loop/minecart/Destroy()
+ if(!QDELETED(moving) && istype(moving, /obj/structure/closet/crate/miningcar))
+ var/turf/moving_turf = get_turf(moving)
+ if(istype(moving_turf, /turf/open/openspace))
+ var/obj/structure/closet/crate/miningcar/minecart = moving
+ minecart.handle_aerial_fall(freefall = TRUE)
+ return ..()
+
+/datum/move_loop/minecart/process()
+ . = ..()
+ if(QDELETED(src) || QDELETED(moving))
+ return
+ var/turf/moving_turf = get_turf(moving)
+ if(istype(moving_turf, /turf/open/openspace))
+ aerial_velocity += (delay / 10) * 9.8
+ aerial_distance += aerial_velocity * (delay / 10)
+ if(aerial_distance >= 1)
+ aerial_distance -= 1
+ if(istype(moving, /obj/structure/closet/crate/miningcar))
+ var/obj/structure/closet/crate/miningcar/minecart = moving
+ minecart.handle_aerial_fall()
+ else
+ aerial_distance = 0
+ aerial_velocity = 0
+
+/datum/move_loop/minecart/move()
+ var/atom/old_loc = moving.loc
+ var/turf/new_loc = get_step(moving, direction)
+ if(locate(/obj/structure/minecart_rail) in old_loc)
+ if(istype(new_loc, /turf/open/openspace))
+ var/turf/below_turf = GET_TURF_BELOW(new_loc)
+ if(locate(/obj/structure/minecart_rail) in below_turf)
+ new_loc = below_turf
+ else if(!(locate(/obj/structure/minecart_rail) in new_loc))
+ var/turf/above_turf = GET_TURF_ABOVE(new_loc)
+ if(locate(/obj/structure/minecart_rail) in above_turf)
+ new_loc = above_turf
+
+ moving.Move(new_loc, direction, FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE))
+ // We cannot rely on the return value of Move(), we care about teleports and it doesn't
+ return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE
diff --git a/code/controllers/subsystem/pathfinder.dm b/code/controllers/subsystem/pathfinder.dm
index 4cc3c76cf86..64b2bc1a2e9 100644
--- a/code/controllers/subsystem/pathfinder.dm
+++ b/code/controllers/subsystem/pathfinder.dm
@@ -1,46 +1,108 @@
+
+/// Queues and manages pathfinding steps
SUBSYSTEM_DEF(pathfinder)
name = "Pathfinder"
init_order = INIT_ORDER_PATH
- flags = SS_NO_FIRE
- var/datum/flowcache/mobs
- var/datum/flowcache/circuits
+ priority = FIRE_PRIORITY_PATHFINDING
+ flags = SS_NO_INIT
+ wait = 0.5
+ /// List of pathfind datums we are currently trying to process
+ var/list/datum/pathfind/active_pathing = list()
+ /// List of pathfind datums being ACTIVELY processed. exists to make subsystem stats readable
+ var/list/datum/pathfind/currentrun = list()
-/datum/controller/subsystem/pathfinder/Initialize()
- mobs = new(10)
- circuits = new(3)
+/datum/controller/subsystem/pathfinder/stat_entry(msg)
+ msg = "P:[length(active_pathing)]"
return ..()
-/datum/flowcache
- var/lcount
- var/run
- var/free
- var/list/flow
-
-/datum/flowcache/New(n)
- . = ..()
- lcount = n
- run = 0
- free = 1
- flow = new/list(lcount)
-
-/datum/flowcache/proc/getfree(atom/M)
- if(run < lcount)
- run += 1
- while(flow[free])
- CHECK_TICK
- free = (free % lcount) + 1
- var/t = addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/flowcache, toolong), free), 150, TIMER_STOPPABLE)
- flow[free] = t
- flow[t] = M
- return free
- else
- return 0
-
-/datum/flowcache/proc/toolong(l)
- log_game("Pathfinder route took longer than 150 ticks, src bot [flow[flow[l]]]")
- found(l)
-
-/datum/flowcache/proc/found(l)
- deltimer(flow[l])
- flow[l] = null
- run -= 1
+// This is another one of those subsystems (hey lighting) in which one "Run" means fully processing a queue
+// We'll use a copy for this just to be nice to people reading the mc panel
+/datum/controller/subsystem/pathfinder/fire(resumed)
+ if(!resumed)
+ src.currentrun = active_pathing.Copy()
+
+ // Dies of sonic speed from caching datum var reads
+ var/list/currentrun = src.currentrun
+ while(length(currentrun))
+ var/datum/pathfind/path = currentrun[length(currentrun)]
+ if(!path.search_step()) // Something's wrong
+ path.early_exit()
+ currentrun.len--
+ continue
+ if(MC_TICK_CHECK)
+ return
+ path.finished()
+ // Next please
+ currentrun.len--
+
+/datum/controller/subsystem/pathfinder/proc/astar_pathfind(
+ atom/movable/invoker,
+ atom/end,
+ max_steps = 30,
+ mintargetdist,
+ list/access,
+ turf/exclude,
+ skip_first = TRUE,
+ use_diagonals = TRUE,
+ list/on_finish,
+ datum/callback/heuristic,
+ closest_approach,
+)
+
+ var/datum/pathfind/astar/path = new(
+ invoker,
+ end,
+ access,
+ max_steps,
+ mintargetdist,
+ exclude,
+ skip_first,
+ use_diagonals,
+ on_finish,
+ heuristic,
+ closest_approach,
+ )
+
+ if(path.start())
+ active_pathing += path
+ return TRUE
+
+ return FALSE
+
+/datum/controller/subsystem/pathfinder/proc/astar_pathfind_now(
+ atom/movable/invoker,
+ atom/end,
+ max_steps = 14,
+ mintargetdist,
+ list/access,
+ turf/exclude,
+ skip_first = TRUE,
+ use_diagonals = TRUE,
+ datum/callback/heuristic,
+ closest_approach,
+)
+
+ var/datum/pathfind/astar/path = new(
+ invoker,
+ end,
+ access,
+ max_steps,
+ mintargetdist,
+ exclude,
+ skip_first,
+ use_diagonals,
+ null,
+ heuristic,
+ closest_approach,
+ )
+
+ if(!path.start())
+ return FALSE
+
+ if(!path.search_step(FALSE))
+ path.early_exit()
+ return FALSE
+
+ path.finished()
+
+ return path.path
diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm
index a637546e32a..6421cdb6501 100644
--- a/code/datums/ai/_ai_controller.dm
+++ b/code/datums/ai/_ai_controller.dm
@@ -53,12 +53,6 @@ have ways of interacting with a specific atom and control it. They posses a blac
COOLDOWN_DECLARE(movement_cooldown)
///Delay between movements. This is on the controller so we can keep the movement datum singleton
var/movement_delay = 0.1 SECONDS
- ///A list for the path we're currently following, if we're using AStar pathing
- var/list/movement_path
- ///Cooldown for JPS movement, how often we're allowed to try making a new path
- COOLDOWN_DECLARE(repath_cooldown)
- ///Cooldown for loot scanning
- COOLDOWN_DECLARE(loot_scan_cooldown)
///AI paused time
var/paused_until = 0
@@ -80,6 +74,8 @@ have ways of interacting with a specific atom and control it. They posses a blac
///this is just so we can pause/unpause ai from arbitrary signals
var/list/pause_signals
+ COOLDOWN_DECLARE(loot_scan_cooldown)
+
/datum/ai_controller/New(atom/new_pawn)
change_ai_movement_type(ai_movement)
init_subtrees()
@@ -94,30 +90,27 @@ have ways of interacting with a specific atom and control it. They posses a blac
our_cells = null
inventory_component = null
set_movement_target(type, null)
- if(ai_movement.moving_controllers[src])
- ai_movement.stop_moving_towards(src)
return ..()
///Sets the current movement target, with an optional param to override the movement behavior
/datum/ai_controller/proc/set_movement_target(source, atom/target, datum/ai_movement/new_movement)
if(current_movement_target)
- UnregisterSignal(current_movement_target, list(COMSIG_PREQDELETED))
+ UnregisterSignal(current_movement_target, list(COMSIG_MOVABLE_MOVED, COMSIG_PREQDELETED))
+
if(!isnull(target) && !isatom(target))
stack_trace("[pawn]'s current movement target is not an atom, rather a [target.type]! Did you accidentally set it to a weakref?")
CancelActions()
return
- if(target != current_movement_target)
- var/datum/ai_movement/hybrid_pathing/hybrid = ai_movement
- if(istype(hybrid))
- hybrid.using_closest_approach -= WEAKREF(src)
+
movement_target_source = source
current_movement_target = target
if(!isnull(current_movement_target))
+ RegisterSignal(current_movement_target, COMSIG_MOVABLE_MOVED, PROC_REF(on_movement_target_move))
RegisterSignal(current_movement_target, COMSIG_PREQDELETED, PROC_REF(on_movement_target_delete))
+
if(new_movement)
change_ai_movement_type(new_movement)
-
/**
* Removes a subtree from planning_subtrees by typepath.
* Safe to call whether planning_subtrees holds instances or typepaths.
@@ -260,12 +253,26 @@ have ways of interacting with a specific atom and control it. They posses a blac
/datum/ai_controller/proc/update_grid(datum/source, datum/spatial_grid_cell/new_cell)
SIGNAL_HANDLER
+
set_new_cells()
+ if(current_movement_target)
+ check_target_max_distance()
+
+/datum/ai_controller/proc/on_movement_target_move(atom/source)
+ SIGNAL_HANDLER
+
+ check_target_max_distance()
+
/datum/ai_controller/proc/on_movement_target_delete(atom/source)
SIGNAL_HANDLER
+
set_movement_target(source = type, target = null)
+/datum/ai_controller/proc/check_target_max_distance()
+ if(get_dist(current_movement_target, pawn) > max_target_distance)
+ CancelActions()
+
/datum/ai_controller/proc/set_new_cells()
var/turf/our_turf = get_turf(pawn)
if(isnull(our_turf))
@@ -336,12 +343,17 @@ have ways of interacting with a specific atom and control it. They posses a blac
///Proc for deinitializing the pawn to the old controller
/datum/ai_controller/proc/UnpossessPawn(destroy)
SHOULD_CALL_PARENT(TRUE)
+
+ if(ai_movement.moving_controllers[src])
+ ai_movement.stop_moving_towards(src)
+
if(isnull(pawn))
return // instantiated without an applicable pawn, fine
set_ai_status(AI_STATUS_OFF)
UnregisterSignal(pawn, list(COMSIG_MOVABLE_Z_CHANGED, COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT, COMSIG_MOB_STATCHANGE, COMSIG_QDELETING))
clear_able_to_run()
+
var/turf/pawn_turf = get_turf(pawn)
if(pawn_turf)
GLOB.ai_controllers_by_zlevel[pawn_turf.z] -= src
@@ -366,8 +378,7 @@ have ways of interacting with a specific atom and control it. They posses a blac
var/run_flags = get_able_to_run()
if(run_flags & AI_UNABLE_TO_RUN)
able_to_run = FALSE
- walk(pawn, 0) //stop moving
- // GLOB.move_manager.stop_looping(pawn) //stop moving
+ GLOB.move_manager.stop_looping(pawn) //stop moving
else
able_to_run = TRUE
set_ai_status(get_expected_ai_status(), run_flags)
@@ -393,6 +404,7 @@ have ways of interacting with a specific atom and control it. They posses a blac
/datum/ai_controller/proc/can_move()
if(QDELETED(pawn))
return
+
var/mob/living/living_pawn = pawn
if(!able_to_run)
return FALSE
@@ -400,12 +412,16 @@ have ways of interacting with a specific atom and control it. They posses a blac
return FALSE
if(HAS_TRAIT(living_pawn, TRAIT_INCAPACITATED))
return FALSE
+
if(ai_traits & STOP_MOVING_WHEN_PULLED && living_pawn.pulledby)
return FALSE
+
if(!isturf(living_pawn.loc)) //No moving if not on a turf
return FALSE
+
if(HAS_TRAIT(living_pawn, TRAIT_IMMOBILIZED))
return FALSE
+
if(living_pawn.pulledby?.grab_state > GRAB_PASSIVE)
return FALSE
@@ -447,6 +463,10 @@ have ways of interacting with a specific atom and control it. They posses a blac
/// Generates a plan and see if our existing one is still valid.
/datum/ai_controller/process(delta_time)
+ if(!able_to_run)
+ GLOB.move_manager.stop_looping(pawn) //stop moving
+ return //this should remove them from processing in the future through event-based stuff.
+
if(!LAZYLEN(current_behaviors) && idle_behavior)
idle_behavior.perform_idle_behavior(delta_time, src) //Do some stupid shit while we have nothing to do
return
@@ -477,49 +497,49 @@ have ways of interacting with a specific atom and control it. They posses a blac
// Action cooldowns cannot happen faster than delta_time, so delta_time should be the value used in this scenario.
var/action_delta_time = max(current_behavior.get_cooldown(src) * 0.1, delta_time)
- if(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT) //Might need to move closer
- if(!current_movement_target)
- current_behavior.finish_action(src, FALSE)
- return //This can cause issues, so don't let these slide.
-
- ///Stops pawns from performing such actions that should require the target to be adjacent.
- var/mob/living/moving_pawn = pawn
- var/can_reach = !(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_REACH) || moving_pawn.CanReach(current_movement_target)
-
- if(isliving(current_movement_target))
- var/mob/living/living_pawn = pawn
- var/mob/living/living_target = current_movement_target
- if(living_target.rogue_sneaking)
- if(!living_pawn.npc_detect_sneak(living_target, 0))
- failed_sneak_check++
- else
- failed_sneak_check = 0
-
- if(prob(8))
- moving_pawn.emote("cidle")
-
- if(((can_reach && current_behavior.required_distance >= get_dist(moving_pawn, current_movement_target))) || failed_sneak_check > 4) ///Are we close
- if(ai_movement.moving_controllers[src] == current_movement_target) //We are close enough, if we're moving stop.
- ai_movement.stop_moving_towards(src)
-
- if(failed_sneak_check > 4)
- ai_movement.stop_moving_towards(src)
+ if(!(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT))
+ if(behavior_cooldowns[current_behavior] > world.time)
+ continue
+ ProcessBehavior(action_delta_time, current_behavior)
+ return
+
+ if(isnull(current_movement_target))
+ fail_behavior(current_behavior)
+ return
+
+ ///Stops pawns from performing such actions that should require the target to be adjacent.
+ var/mob/living/moving_pawn = pawn
+ var/can_reach = !(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_REACH) || moving_pawn.CanReach(current_movement_target)
+
+ if(isliving(current_movement_target))
+ var/mob/living/living_pawn = pawn
+ var/mob/living/living_target = current_movement_target
+ if(living_target.rogue_sneaking)
+ if(!living_pawn.npc_detect_sneak(living_target, 0))
+ failed_sneak_check++
+ else
failed_sneak_check = 0
- if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
- continue
- ProcessBehavior(action_delta_time, current_behavior)
- return
+ if(prob(8))
+ moving_pawn.emote("cidle")
+
+ if(((can_reach && current_behavior.required_distance >= get_dist(moving_pawn, current_movement_target))) || failed_sneak_check > 4) ///Are we close
+ if(ai_movement.moving_controllers[src] == current_movement_target) //We are close enough, if we're moving stop.
+ ai_movement.stop_moving_towards(src)
+
+ if(failed_sneak_check > 4)
+ ai_movement.stop_moving_towards(src)
+ failed_sneak_check = 0
- else if(ai_movement.moving_controllers[src] != current_movement_target) //We're too far, if we're not already moving start doing it.
- ai_movement.start_moving_towards(src, current_movement_target) //Then start moving
+ if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
+ continue
+ ProcessBehavior(action_delta_time, current_behavior)
+ return
- if(current_behavior.behavior_flags & AI_BEHAVIOR_MOVE_AND_PERFORM) //If we can move and perform then do so.
- if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
- continue
- ProcessBehavior(action_delta_time, current_behavior)
- return
- else //No movement required
+ else if(ai_movement.moving_controllers[src] != current_movement_target) //We're too far, if we're not already moving start doing it.
+ ai_movement.start_moving_towards(src, current_movement_target) //Then start moving
+
+ if(current_behavior.behavior_flags & AI_BEHAVIOR_MOVE_AND_PERFORM) //If we can move and perform then do so.
if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
continue
ProcessBehavior(action_delta_time, current_behavior)
@@ -650,11 +670,14 @@ have ways of interacting with a specific atom and control it. They posses a blac
if(!LAZYLEN(current_behaviors))
return
for(var/datum/ai_behavior/current_behavior as anything in current_behaviors)
- var/list/arguments = list(src, FALSE)
- var/list/stored_arguments = behavior_args[current_behavior.type]
- if(stored_arguments)
- arguments += stored_arguments
- current_behavior.finish_action(arglist(arguments))
+ fail_behavior(current_behavior)
+
+/datum/ai_controller/proc/fail_behavior(datum/ai_behavior/current_behavior)
+ var/list/arguments = list(src, FALSE)
+ var/list/stored_arguments = behavior_args[current_behavior.type]
+ if(stored_arguments)
+ arguments += stored_arguments
+ current_behavior.finish_action(arglist(arguments))
/datum/ai_controller/proc/on_sentience_gained()
UnregisterSignal(pawn, COMSIG_MOB_LOGIN)
@@ -680,6 +703,17 @@ have ways of interacting with a specific atom and control it. They posses a blac
/datum/ai_controller/proc/get_access()
return
+///Returns the minimum required distance to preform one of our current behaviors. Honestly this should just be cached or something but fuck you
+/datum/ai_controller/proc/get_minimum_distance()
+ var/minimum_distance = max_target_distance
+ // right now I'm just taking the shortest minimum distance of our current behaviors, at some point in the future
+ // we should let whatever sets the current_movement_target also set the min distance and max path length
+ // (or at least cache it on the controller)
+ for(var/datum/ai_behavior/iter_behavior as anything in current_behaviors)
+ if(iter_behavior.required_distance < minimum_distance)
+ 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]
diff --git a/code/datums/ai/ai_movement/_ai_planning_subtree.dm b/code/datums/ai/_ai_planning_subtree.dm
similarity index 100%
rename from code/datums/ai/ai_movement/_ai_planning_subtree.dm
rename to code/datums/ai/_ai_planning_subtree.dm
diff --git a/code/datums/ai/ai_movement/_ai_movement.dm b/code/datums/ai/ai_movement/_ai_movement.dm
index 72d4a02e17f..e8c343f2c8d 100644
--- a/code/datums/ai/ai_movement/_ai_movement.dm
+++ b/code/datums/ai/ai_movement/_ai_movement.dm
@@ -2,22 +2,25 @@
/datum/ai_movement
///Assoc list ist of controllers that are currently moving as key, and what they are moving to as value
var/list/moving_controllers = list()
- ///Does this ai require processing?
- var/requires_processing = TRUE
///how many attempts do we make at pathing
var/max_pathing_attempts
- ///how long path limit movements bother checking
- var/max_path_distance = 30
+//Override this to setup the moveloop you want to use
/datum/ai_movement/proc/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
+ SHOULD_CALL_PARENT(TRUE)
+
controller.pathing_attempts = 0
controller.set_blackboard_key(BB_CURRENT_MIN_MOVE_DISTANCE, min_distance)
- if(!length(moving_controllers) && requires_processing)
- START_PROCESSING(SSai_movement, src)
moving_controllers[controller] = current_movement_target
/datum/ai_movement/proc/stop_moving_towards(datum/ai_controller/controller)
controller.pathing_attempts = 0
moving_controllers -= controller
- if(!length(moving_controllers) && requires_processing)
- STOP_PROCESSING(SSai_movement, src)
+ // We got deleted as we finished an action
+ if(!QDELETED(controller.pawn))
+ GLOB.move_manager.stop_looping(controller.pawn, SSai_movement)
+
+/datum/ai_movement/proc/increment_pathing_failures(datum/ai_controller/controller)
+ controller.pathing_attempts++
+ if(controller.pathing_attempts >= max_pathing_attempts)
+ controller.CancelActions()
diff --git a/code/datums/ai/ai_movement/ai_movement_adaptive.dm b/code/datums/ai/ai_movement/ai_movement_adaptive.dm
new file mode 100644
index 00000000000..53bc97f39bc
--- /dev/null
+++ b/code/datums/ai/ai_movement/ai_movement_adaptive.dm
@@ -0,0 +1,36 @@
+/datum/ai_movement/basic_avoidance/adaptive
+
+/datum/ai_movement/basic_avoidance/adaptive/pre_move(datum/move_loop/has_target/dist_bound/source)
+ var/datum/ai_controller/controller = source.extra_info
+ if(source.target.z == controller.pawn.z)
+ return ..()
+ // If the Z changes the loop fails
+ stop_moving_towards(controller)
+ controller.change_ai_movement_type(/datum/ai_movement/astar/adaptive) // Moved a Z? it's AStar time
+ return MOVELOOP_SKIP_STEP
+
+/datum/ai_movement/basic_avoidance/adaptive/post_move(datum/move_loop/source, succeeded)
+ . = ..()
+ if (succeeded != MOVELOOP_FAILURE)
+ return
+ var/datum/ai_controller/controller = source.extra_info
+ stop_moving_towards(controller)
+ controller.change_ai_movement_type(/datum/ai_movement/astar/adaptive) // we failed? it's AStar time
+
+/datum/ai_movement/astar/adaptive
+
+/datum/ai_movement/astar/adaptive/post_move(datum/move_loop/source, succeeded)
+ . = ..()
+ var/datum/move_loop/has_target/astar/loop = source
+ if(length(loop.movement_path))
+ return
+
+ var/datum/ai_controller/controller = source.extra_info
+ if(QDELETED(controller) || QDELETED(controller.pawn))
+ return
+
+ var/turf/current = get_turf(controller.pawn)
+ var/turf/target = get_turf(loop.target)
+ if(current == target || get_dist(current, target) <= loop.minimum_distance)
+ stop_moving_towards(controller)
+ controller.change_ai_movement_type(/datum/ai_movement/basic_avoidance/adaptive) // we succeeded? it's basic time
diff --git a/code/datums/ai/ai_movement/ai_movement_astar.dm b/code/datums/ai/ai_movement/ai_movement_astar.dm
new file mode 100644
index 00000000000..99d257a4981
--- /dev/null
+++ b/code/datums/ai/ai_movement/ai_movement_astar.dm
@@ -0,0 +1,111 @@
+/**
+ * This movement datum represents smart-pathing
+ */
+/datum/ai_movement/astar
+ max_pathing_attempts = 4
+ var/closest_approach = FALSE
+
+/datum/ai_movement/astar/closest_approach
+ closest_approach = TRUE
+
+/datum/ai_movement/astar/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
+ . = ..()
+ var/atom/movable/moving = controller.pawn
+ var/delay = controller.movement_delay
+
+ var/datum/move_loop/loop = GLOB.move_manager.astar_move(
+ moving,
+ current_movement_target,
+ delay,
+ repath_delay = 0.5 SECONDS,
+ max_path_length = AI_MAX_PATH_LENGTH,
+ minimum_distance = controller.get_minimum_distance(),
+ access = controller.get_access(),
+ subsystem = SSai_movement,
+ extra_info = controller,
+ initial_path = controller.blackboard[BB_PATH_TO_USE],
+ closest_approach = src.closest_approach
+ )
+ controller.clear_blackboard_key(BB_PATH_TO_USE)
+
+ RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move))
+ RegisterSignal(loop, COMSIG_MOVELOOP_REPATH, PROC_REF(repath_incoming))
+
+/datum/ai_movement/astar/proc/pre_move(datum/move_loop/has_target/astar/source)
+ SIGNAL_HANDLER
+
+ var/datum/ai_controller/controller = source.extra_info
+ source.delay = controller.movement_delay
+
+ // Check if this controller can actually run, so we don't chase people with corpses
+ if(!controller.able_to_run)
+ controller.CancelActions()
+ qdel(source) //stop moving
+ return MOVELOOP_SKIP_STEP
+
+ if(controller.can_move())
+ return NONE
+
+ increment_pathing_failures(controller)
+
+ return MOVELOOP_SKIP_STEP
+
+/datum/ai_movement/astar/proc/post_move(datum/move_loop/has_target/astar/source, succeeded)
+ SIGNAL_HANDLER
+
+ var/datum/ai_controller/controller = source.extra_info
+ if(!succeeded)
+ increment_pathing_failures(controller)
+ return
+
+ // Anticipated paths for smoother but more expensive pathing
+ if(source.is_pathing || !COOLDOWN_FINISHED(source, repath_anticipated_cooldown))
+ return
+
+ // We only do this if we are about to be near the end of the path
+ var/remaining_path = length(source.movement_path)
+ if(remaining_path > 5)
+ return
+
+ var/turf/end = get_turf(controller.current_movement_target)
+ var/turf/checking = get_turf(controller.pawn)
+ var/mintargetdist = controller.get_minimum_distance()
+
+ var/dist_to_end = get_dist_3d(end, checking)
+
+ // Same as ASTAR_CLOSE_ENOUGH_TO_END
+ if(end == checking || (mintargetdist && (dist_to_end <= mintargetdist)))
+ return
+
+ // Severely limit max length since the target is close and we don't want to take a long time
+ var/max_steps = min(dist_to_end, 8)
+
+ COOLDOWN_START(source, repath_anticipated_cooldown, 2 SECONDS)
+
+ var/list/path = SSpathfinder.astar_pathfind_now( \
+ controller.pawn, \
+ controller.current_movement_target, \
+ max_steps, \
+ mintargetdist, \
+ controller.get_access(), \
+ source.avoid, \
+ source.skip_first, \
+ source.use_diagonals, \
+ source.heuristic, \
+ )
+
+ if(!length(path))
+ return
+
+ source.access = controller.get_access()
+ source.minimum_distance = controller.get_minimum_distance()
+ source.movement_path = path
+
+/datum/ai_movement/astar/proc/repath_incoming(datum/move_loop/has_target/astar/source)
+ SIGNAL_HANDLER
+
+ var/datum/ai_controller/controller = source.extra_info
+
+ source.access = controller.get_access()
+ source.minimum_distance = controller.get_minimum_distance()
diff --git a/code/datums/ai/ai_movement/ai_movement_basic_advoidance.dm b/code/datums/ai/ai_movement/ai_movement_basic_advoidance.dm
new file mode 100644
index 00000000000..13177bd9f5b
--- /dev/null
+++ b/code/datums/ai/ai_movement/ai_movement_basic_advoidance.dm
@@ -0,0 +1,46 @@
+/datum/ai_movement/basic_avoidance
+ max_pathing_attempts = 10
+
+/datum/ai_movement/basic_avoidance/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
+ . = ..()
+ var/atom/movable/moving = controller.pawn
+ var/min_dist = controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE]
+ var/delay = controller.movement_delay
+ var/datum/move_loop/loop = GLOB.move_manager.move_to(moving, current_movement_target, min_dist, delay, subsystem = SSai_movement, extra_info = controller)
+ RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move))
+
+/datum/ai_movement/basic_avoidance/proc/pre_move(datum/move_loop/has_target/dist_bound/source)
+ SIGNAL_HANDLER
+
+ var/datum/ai_controller/controller = source.extra_info
+ source.delay = controller.movement_delay
+ source.distance = controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE]
+
+ // Check if this controller can actually run, so we don't chase people with corpses
+ if(!controller.able_to_run)
+ controller.CancelActions()
+ qdel(source) //stop moving
+ return MOVELOOP_SKIP_STEP
+
+ var/atom/movable/pawn = source.moving
+ var/can_move = controller.can_move()
+
+ var/turf/target_turf = get_step_to(pawn, source.target)
+ if(is_type_in_typecache(target_turf, GLOB.dangerous_turfs))
+ can_move = FALSE
+
+ if(can_move)
+ return
+
+ increment_pathing_failures(controller)
+ return MOVELOOP_SKIP_STEP
+
+/datum/ai_movement/basic_avoidance/proc/post_move(datum/move_loop/source, succeeded)
+ SIGNAL_HANDLER
+
+ if(succeeded)
+ return
+
+ var/datum/ai_controller/controller = source.extra_info
+ increment_pathing_failures(controller)
diff --git a/code/datums/ai/ai_movement/ai_movement_dumb.dm b/code/datums/ai/ai_movement/ai_movement_dumb.dm
new file mode 100644
index 00000000000..aafc6665c75
--- /dev/null
+++ b/code/datums/ai/ai_movement/ai_movement_dumb.dm
@@ -0,0 +1,45 @@
+///The most braindead type of movement, bee-line to the target with no concern of whats infront of us.
+/datum/ai_movement/dumb
+ max_pathing_attempts = 16
+
+/datum/ai_movement/dumb/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
+ . = ..()
+ var/atom/movable/moving = controller.pawn
+ var/delay = controller.movement_delay
+ var/datum/move_loop/loop = GLOB.move_manager.move_towards_legacy(moving, current_movement_target, delay, subsystem = SSai_movement, extra_info = controller)
+ RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move))
+
+/datum/ai_movement/dumb/proc/pre_move(datum/move_loop/has_target/source)
+ SIGNAL_HANDLER
+
+ var/datum/ai_controller/controller = source.extra_info
+ source.delay = controller.movement_delay
+
+ // Check if this controller can actually run, so we don't chase people with corpses
+ if(!controller.able_to_run)
+ controller.CancelActions()
+ qdel(source) //stop moving
+ return MOVELOOP_SKIP_STEP
+
+ var/atom/movable/pawn = source.moving
+ var/can_move = controller.can_move()
+
+ var/turf/target_turf = get_step_towards(pawn, source.target)
+ if(is_type_in_typecache(target_turf, GLOB.dangerous_turfs))
+ can_move = FALSE
+
+ if(can_move)
+ return
+
+ increment_pathing_failures(controller)
+ return MOVELOOP_SKIP_STEP
+
+/datum/ai_movement/dumb/proc/post_move(datum/move_loop/source, succeeded)
+ SIGNAL_HANDLER
+
+ if(succeeded)
+ return
+
+ var/datum/ai_controller/controller = source.extra_info
+ increment_pathing_failures(controller)
diff --git a/code/datums/ai/ai_movement/astar_movement.dm b/code/datums/ai/ai_movement/astar_movement.dm
deleted file mode 100644
index e68229c6f68..00000000000
--- a/code/datums/ai/ai_movement/astar_movement.dm
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * This movement datum represents smart-pathing
- */
-/datum/ai_movement/astar
- max_pathing_attempts = 4
- max_path_distance = 30
-
-///Put your movement behavior in here!
-/datum/ai_movement/astar/process(delta_time)
- for(var/datum/ai_controller/controller as anything in moving_controllers)
- if(!COOLDOWN_FINISHED(controller, movement_cooldown))
- continue
- COOLDOWN_START(controller, movement_cooldown, controller.movement_delay)
-
- var/atom/movable/movable_pawn = controller.pawn
-
- if(!controller.can_move())
- continue
-
- var/minimum_distance = controller.max_target_distance
- // right now I'm just taking the shortest minimum distance of our current behaviors, at some point in the future
- // we should let whatever sets the current_movement_target also set the min distance and max path length
- // (or at least cache it on the controller)
- for(var/datum/ai_behavior/iter_behavior as anything in controller.current_behaviors)
- if(iter_behavior.required_distance < minimum_distance)
- minimum_distance = iter_behavior.required_distance
-
- if(get_dist(movable_pawn, controller.current_movement_target) <= minimum_distance)
- continue
-
- var/generate_path = FALSE // set to TRUE when we either have no path, or we failed a step
- if(length(controller.movement_path))
- var/turf/next_step = controller.movement_path[1]
- movable_pawn.Move(next_step)
-
- // this check if we're on exactly the next tile may be overly brittle for dense pawns who may get bumped slightly
- // to the side while moving but could maybe still follow their path without needing a whole new path
- if(get_turf(movable_pawn) == next_step)
- controller.movement_path.Cut(1,2)
- else
- generate_path = TRUE
- else
- generate_path = TRUE
-
- if(generate_path)
- if(!COOLDOWN_FINISHED(controller, repath_cooldown))
- continue
- controller.pathing_attempts++
- if(controller.pathing_attempts >= max_pathing_attempts)
- controller.CancelActions()
- continue
-
- COOLDOWN_START(controller, repath_cooldown, 2 SECONDS)
- controller.movement_path = get_path_to(movable_pawn, controller.current_movement_target, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), max_path_distance + 1, 250, minimum_distance, id=controller.get_access())
-
-/datum/ai_movement/astar/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target)
- controller.movement_path = null
- return ..()
-
-/datum/ai_movement/astar/stop_moving_towards(datum/ai_controller/controller)
- controller.movement_path = null
- return ..()
diff --git a/code/datums/ai/ai_movement/basic_avoidance.dm b/code/datums/ai/ai_movement/basic_avoidance.dm
deleted file mode 100644
index b53e621921e..00000000000
--- a/code/datums/ai/ai_movement/basic_avoidance.dm
+++ /dev/null
@@ -1,29 +0,0 @@
-///Uses Byond's basic obstacle avoidance mvovement
-/datum/ai_movement/basic_avoidance
- requires_processing = TRUE
- max_pathing_attempts = 10
-
-///Put your movement behavior in here!
-/datum/ai_movement/basic_avoidance/process(delta_time)
- for(var/datum/ai_controller/controller as anything in moving_controllers)
- if(!COOLDOWN_FINISHED(controller, movement_cooldown))
- continue
- COOLDOWN_START(controller, movement_cooldown, controller.movement_delay)
-
- var/atom/movable/movable_pawn = controller.pawn
- var/can_move = TRUE
-
- if(!controller.can_move())
- continue
-
- var/current_loc = get_turf(movable_pawn)
-
- var/turf/target_turf = get_step_towards(movable_pawn, controller.current_movement_target)
-
- if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs) && can_move)
- step_to(movable_pawn, controller.current_movement_target, controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE], controller.movement_delay)
-
- if(current_loc == get_turf(movable_pawn)) //Did we even move after trying to move?
- controller.pathing_attempts++
- if(controller.pathing_attempts >= max_pathing_attempts)
- controller.CancelActions()
diff --git a/code/datums/ai/ai_movement/dumb_movement.dm b/code/datums/ai/ai_movement/dumb_movement.dm
deleted file mode 100644
index 14b12219c1e..00000000000
--- a/code/datums/ai/ai_movement/dumb_movement.dm
+++ /dev/null
@@ -1,24 +0,0 @@
-///The most braindead type of movement, bee-line to the target with no concern of whats infront of us.
-/datum/ai_movement/dumb
- max_pathing_attempts = 16
-
-///Put your movement behavior in here!
-/datum/ai_movement/dumb/process(delta_time)
- for(var/datum/ai_controller/controller as anything in moving_controllers)
- if(!COOLDOWN_FINISHED(controller, movement_cooldown))
- continue
- COOLDOWN_START(controller, movement_cooldown, controller.movement_delay)
- var/atom/movable/movable_pawn = controller.pawn
- var/can_move = TRUE
-
- if(!controller.can_move())
- continue
-
- var/current_loc = get_turf(movable_pawn)
- var/turf/target_turf = get_step_towards(movable_pawn, controller.current_movement_target)
- if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs) && can_move)
- movable_pawn.Move(target_turf, get_dir(current_loc, target_turf))
- if(current_loc == get_turf(movable_pawn)) //Did we even move after trying to move?
- controller.pathing_attempts++
- if(controller.pathing_attempts >= max_pathing_attempts)
- controller.CancelActions()
diff --git a/code/datums/ai/ai_movement/hybrid_movement.dm b/code/datums/ai/ai_movement/hybrid_movement.dm
index 48b5fb845e3..e3e91820b49 100644
--- a/code/datums/ai/ai_movement/hybrid_movement.dm
+++ b/code/datums/ai/ai_movement/hybrid_movement.dm
@@ -1,291 +1,285 @@
-///Uses Byond's basic obstacle avoidance movement unless the target is on a z-level different to ours
-/datum/ai_movement/hybrid_pathing/gnome
- max_path_distance = 100 //gnomes are psydon's smartest genetic freak
- max_basic_failures = 2
- always_advanced = TRUE
-
-/datum/ai_movement/hybrid_pathing/wave_defense
- path_to_closest_on_fail = TRUE
- always_advanced = TRUE
-
-/datum/ai_movement/hybrid_pathing
- requires_processing = TRUE
- max_pathing_attempts = 12
- max_path_distance = 30
-
- var/list/fallback_fail = list()
- var/list/falling_back = list()
-
- // Variables for asynchronous path generation
- var/repath_anticipation_distance = 5 // Start generating new path when this close to the end
- var/future_path_blackboard_key = BB_FUTURE_MOVEMENT_PATH
-
- var/next_resolve = 0
- var/max_basic_failures = 3 // How many consecutive basic movement failures before switching to A*
- var/always_advanced = FALSE
- /// If TRUE, when get_path_to fails (no path), fall back to a path toward the closest reachable point
- var/path_to_closest_on_fail = FALSE
- /// Per-controller: are we currently using a closest-approach fallback path (target confirmed unreachable)
- var/list/using_closest_approach = list()
-
-/datum/ai_movement/hybrid_pathing/process(delta_time)
- if(world.time < next_resolve)
- next_resolve = world.time + 5 MINUTES
-
- for(var/datum/weakref/weakref in falling_back)
- if(!weakref.resolve())
- fallback_fail -= weakref
- falling_back -= weakref
-
- for(var/datum/ai_controller/controller as anything in moving_controllers)
- if(!(future_path_blackboard_key in controller.blackboard))
- controller.add_blackboard_key(future_path_blackboard_key, null)
- if(!COOLDOWN_FINISHED(controller, movement_cooldown))
- continue
- COOLDOWN_START(controller, movement_cooldown, controller.movement_delay)
-
- if(!controller.can_move())
- continue
-
- var/atom/movable/movable_pawn = controller.pawn
- var/turf/target_turf = get_step_towards(movable_pawn, controller.current_movement_target)
- var/turf/end_turf = get_turf(controller.current_movement_target)
- var/advanced = TRUE
- var/turf/current_turf = get_turf(movable_pawn)
-
- var/mob/cliented_mob = controller.current_movement_target
- var/cliented = FALSE
- if(istype(cliented_mob))
- if(cliented_mob.client)
- cliented = TRUE
-
- // Check if we've moved to a lower Z-level (possibly thrown) and our path expects us to be higher
- if(length(controller.movement_path) && controller.movement_path[1])
- var/turf/next_step = controller.movement_path[1]
-
- // If the next step is on a higher Z-level than our current position,
- // verify that there are stairs at our current position leading up
- if(next_step.z > current_turf.z)
- // Check if there's a valid stair to go up
- var/turf/above = GET_TURF_ABOVE(current_turf)
- var/can_go_up = FALSE
-
- if(above && !above.density)
- // Look for stairs at current location
- for(var/obj/structure/stairs/S in current_turf.contents)
- var/turf/dest = get_step(above, S.dir)
- if(dest == next_step || get_step(dest, S.dir) == next_step)
- can_go_up = TRUE
- break
-
- // If we can't go up but our path expects us to, we've likely been thrown down
- // So regenerate the path from our current position
- if(!can_go_up)
- controller.movement_path = null
- controller.clear_blackboard_key(future_path_blackboard_key)
- var/datum/weakref/weak = WEAKREF(controller)
- if(weak in falling_back)
- falling_back[weak] = FALSE
- if(weak in fallback_fail)
- fallback_fail[weak] = FALSE
- continue
-
- // Basic movement for targets on the same z-level with no existing path
- if(end_turf?.z == movable_pawn?.z && !length(controller.movement_path) && !cliented && !always_advanced)
- advanced = FALSE
- var/can_move = controller.can_move()
-
- var/current_loc = get_turf(movable_pawn)
-
- if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs) && can_move)
- step_to(movable_pawn, target_turf, controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE], controller.movement_delay)
-
- // Check if movement was successful
- if(current_loc != get_turf(movable_pawn))
- // Successful basic movement - reset failure counter and clear fallback state
- controller.pathing_attempts = 0
- var/datum/weakref/weak = WEAKREF(controller)
- falling_back -= weak
- fallback_fail -= weak
- else
- // Movement failed - increment failure counter
- controller.pathing_attempts++
-
- // Only switch to A* after multiple consecutive failures
- if(controller.pathing_attempts >= max_basic_failures)
- advanced = TRUE
- controller.movement_path = null
- controller.clear_blackboard_key(future_path_blackboard_key)
- var/datum/weakref/weak = WEAKREF(controller)
- if(!(weak in falling_back))
- falling_back |= weak
- falling_back[weak] = FALSE
- falling_back[weak] = TRUE
- SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Unable to Basic Move after [max_basic_failures] attempts, swapping to AStar.")
-
- // Check if we've exceeded maximum pathing attempts
- if(controller.pathing_attempts >= max_pathing_attempts)
- controller.CancelActions()
- SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Failed pathfinding cancelling.")
-
- if(advanced)
- var/minimum_distance = controller.max_target_distance
- // Get the minimum distance required by current behaviors
- for(var/datum/ai_behavior/iter_behavior as anything in controller.current_behaviors)
- if(iter_behavior.required_distance < minimum_distance)
- minimum_distance = iter_behavior.required_distance
- if(ispath(controller.movement_target_source, /datum/action_state_manager))
- minimum_distance = 1
-
- if(get_dist(movable_pawn, controller.current_movement_target) <= minimum_distance)
- using_closest_approach -= WEAKREF(controller)
- continue
-
- var/generate_path = FALSE
- var/list/future_path = controller.blackboard[future_path_blackboard_key]
-
- // Path following logic
- if(length(controller.movement_path))
- var/turf/last_turf = controller.movement_path[length(controller.movement_path)]
- var/turf/next_step = controller.movement_path[1]
- var/remaining_path_length = length(controller.movement_path)
-
- // Move to the next step in the path
- if(next_step.z != movable_pawn.z)
- //check if there's a valid transition
- var/can_transition = FALSE
-
- // Look for stairs at our current position
- var/obj/structure/stairs/S = locate(/obj/structure/stairs) in current_turf
- if(S)
- // Check if the next step is the target going UP or DOWN the stairs
- if(S.get_target_loc(S.dir) == next_step || S.get_target_loc(REVERSE_DIR(S.dir)) == next_step)
- can_transition = TRUE
- else
- // Fallback for falling / moving down into open space without stairs
- if(next_step.z < movable_pawn.z && GET_TURF_BELOW(current_turf) == next_step)
- can_transition = TRUE
-
- // Only move if we can legitimately transition, otherwise regenerate path
- if(can_transition)
- movable_pawn.Move(next_step)
- else
- // Can't reach next step legitimately, need new path
- generate_path = TRUE
- controller.clear_blackboard_key(future_path_blackboard_key)
-
- // Only move if we can legitimately transition, otherwise regenerate path
- if(can_transition)
- movable_pawn.Move(next_step)
- else
- // Can't reach next step legitimately, need new path
- generate_path = TRUE
- controller.clear_blackboard_key(future_path_blackboard_key)
- else
- step_to(movable_pawn, next_step, controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE], controller.movement_delay)
-
- // Check if target has moved significantly from the end of our path
- if(last_turf != get_turf(controller.current_movement_target))
- // If we have a pre-generated future path and it's relevant, use it
- if(future_path && length(future_path) && future_path[length(future_path)] == get_turf(controller.current_movement_target))
- controller.movement_path = future_path.Copy()
- controller.clear_blackboard_key(future_path_blackboard_key)
- SEND_SIGNAL(controller.pawn, COMSIG_AI_PATH_SWAPPED, controller.movement_path)
- else
- generate_path = TRUE
- controller.clear_blackboard_key(future_path_blackboard_key)
-
- // Update current path - remove steps we've completed
- var/datum/weakref/used_ref = WEAKREF(controller)
- if(!(used_ref in falling_back))
- falling_back |= used_ref
- falling_back[used_ref] = TRUE
- if(get_turf(movable_pawn) == next_step || (istype(next_step, /turf/open/openspace) && get_turf(movable_pawn) == GET_TURF_BELOW(next_step)))
- controller.movement_path.Cut(1,2)
- if(length(controller.movement_path))
- var/turf/double_checked = controller.movement_path[1]
-
- if(get_turf(movable_pawn) == double_checked) // Handle z-level stack issues
- controller.movement_path.Cut(1,2)
-
- if(!length(controller.movement_path) && falling_back[used_ref])
- falling_back[used_ref] = FALSE
- // Successfully completed A* path - allow basic movement again on next iteration
- controller.pathing_attempts = 0
- else
- if(!falling_back[used_ref])
- generate_path = TRUE
- controller.clear_blackboard_key(future_path_blackboard_key)
- else
- fallback_fail[used_ref]++
- if(fallback_fail[used_ref] >= 2)
- fallback_fail[used_ref] = 0
- generate_path = TRUE
- if(!(used_ref in falling_back))
- falling_back |= used_ref
- falling_back[used_ref] = FALSE
- falling_back[used_ref] = FALSE
- controller.clear_blackboard_key(future_path_blackboard_key)
-
- // If we're nearing the end of our path, preemptively generate the next path
- // Only do this if we have a valid current path and aren't already generating a future path
- if(!generate_path && remaining_path_length <= repath_anticipation_distance && !future_path && COOLDOWN_FINISHED(controller, repath_cooldown))
- // Target doesnt exist anymore or we picked it up already
- if(QDELETED(controller.current_movement_target) || controller.current_movement_target.loc == movable_pawn)
- continue
- COOLDOWN_START(controller, repath_cooldown, 1 SECONDS) // Shorter cooldown for anticipatory pathing
- // Generate the future path and store it in the controller's blackboard
- var/list/new_future_path = get_path_to(movable_pawn, controller.current_movement_target, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
- max_path_distance + 1, max_path_distance + 1, minimum_distance, id=controller.get_access())
- controller.set_blackboard_key(future_path_blackboard_key, new_future_path)
- SEND_SIGNAL(controller.pawn, COMSIG_AI_FUTURE_PATH_GENERATED, new_future_path)
- else
- generate_path = TRUE
-
- // Generate a new primary path if needed
- if(generate_path)
- if(!COOLDOWN_FINISHED(controller, repath_cooldown))
- continue
-
- var/datum/weakref/weak_controller = WEAKREF(controller)
- var/already_closest_approach = path_to_closest_on_fail && (weak_controller in using_closest_approach)
-
- if(already_closest_approach)
- // Don't burn pathing_attempts re-trying a path we already know fails -
- // go straight back to closest-approach
- if(QDELETED(controller.current_movement_target) || controller.current_movement_target.loc == movable_pawn)
- continue
- COOLDOWN_START(controller, repath_cooldown, 1.5 SECONDS)
- controller.movement_path = get_path_to_closest_approach(movable_pawn, controller.current_movement_target, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
- max_path_distance + 1, max_path_distance + 1, minimum_distance, id=controller.get_access())
- controller.clear_blackboard_key(future_path_blackboard_key)
- if(!length(controller.movement_path))
- // Even closest-approach gives nothing now (e.g. truly boxed in) - give up this cycle
- controller.CancelActions()
- SEND_SIGNAL(controller, COMSIG_AI_PATHING_FAILED)
- else
- SEND_SIGNAL(controller.pawn, COMSIG_AI_PATH_GENERATED, controller.movement_path)
- continue
-
- controller.pathing_attempts++
- if(controller.pathing_attempts >= max_pathing_attempts)
- if(path_to_closest_on_fail)
- controller.movement_path = get_path_to_closest_approach(movable_pawn, controller.current_movement_target, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
- max_path_distance + 1, max_path_distance + 1, minimum_distance, id=controller.get_access())
- controller.clear_blackboard_key(future_path_blackboard_key)
- if(length(controller.movement_path))
- controller.pathing_attempts = 0
- using_closest_approach[weak_controller] = TRUE
- SEND_SIGNAL(controller.pawn, COMSIG_AI_PATH_GENERATED, controller.movement_path)
- continue
- controller.CancelActions()
- SEND_SIGNAL(controller, COMSIG_AI_PATHING_FAILED)
- continue
- // Target doesnt exist anymore or we picked it up already
- if(QDELETED(controller.current_movement_target) || controller.current_movement_target.loc == movable_pawn)
- continue
- COOLDOWN_START(controller, repath_cooldown, 1.5 SECONDS)
- controller.movement_path = get_path_to(movable_pawn, controller.current_movement_target, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
- max_path_distance + 1, max_path_distance + 1, minimum_distance, id=controller.get_access())
- controller.clear_blackboard_key(future_path_blackboard_key)
- SEND_SIGNAL(controller.pawn, COMSIG_AI_PATH_GENERATED, controller.movement_path)
+// ///Uses Byond's basic obstacle avoidance movement unless the target is on a z-level different to ours
+// /datum/ai_movement/hybrid_pathing/gnome
+// max_path_distance = 100 //gnomes are psydon's smartest genetic freak
+// max_basic_failures = 2
+// always_advanced = TRUE
+
+// /datum/ai_movement/hybrid_pathing
+// requires_processing = TRUE
+// max_pathing_attempts = 12
+// max_path_distance = 30
+
+// /datum/ai_movement/hybrid_pathing/wave_defense
+// path_to_closest_on_fail = TRUE
+// always_advanced = TRUE
+
+
+// var/list/fallback_fail = list()
+// var/list/falling_back = list()
+
+// // Variables for asynchronous path generation
+// var/repath_anticipation_distance = 5 // Start generating new path when this close to the end
+// var/future_path_blackboard_key = BB_FUTURE_MOVEMENT_PATH
+
+
+ // var/next_resolve = 0
+ // var/max_basic_failures = 3 // How many consecutive basic movement failures before switching to A*
+ // var/always_advanced = FALSE
+ // /// If TRUE, when get_path_to fails (no path), fall back to a path toward the closest reachable point
+ // var/path_to_closest_on_fail = FALSE
+ // /// Per-controller: are we currently using a closest-approach fallback path (target confirmed unreachable)
+ // var/list/using_closest_approach = list()
+
+// /datum/ai_movement/hybrid_pathing/process(delta_time)
+// if(world.time < next_resolve)
+// next_resolve = world.time + 5 MINUTES
+
+// for(var/datum/weakref/weakref in falling_back)
+// if(!weakref.resolve())
+// fallback_fail -= weakref
+// falling_back -= weakref
+
+// for(var/datum/ai_controller/controller as anything in moving_controllers)
+// if(!(future_path_blackboard_key in controller.blackboard))
+// controller.add_blackboard_key(future_path_blackboard_key, null)
+// if(!COOLDOWN_FINISHED(controller, movement_cooldown))
+// continue
+// COOLDOWN_START(controller, movement_cooldown, controller.movement_delay)
+
+// if(!controller.can_move())
+// continue
+
+// var/atom/movable/movable_pawn = controller.pawn
+// var/turf/target_turf = get_step_towards(movable_pawn, controller.current_movement_target)
+// var/turf/end_turf = get_turf(controller.current_movement_target)
+// var/advanced = TRUE
+// var/turf/current_turf = get_turf(movable_pawn)
+
+// var/mob/cliented_mob = controller.current_movement_target
+// var/cliented = FALSE
+// if(istype(cliented_mob))
+// if(cliented_mob.client)
+// cliented = TRUE
+
+// // Check if we've moved to a lower Z-level (possibly thrown) and our path expects us to be higher
+// if(length(controller.movement_path) && controller.movement_path[1])
+// var/turf/next_step = controller.movement_path[1]
+
+// // If the next step is on a higher Z-level than our current position,
+// // verify that there are stairs at our current position leading up
+// if(next_step.z > current_turf.z)
+// // Check if there's a valid stair to go up
+// var/turf/above = GET_TURF_ABOVE(current_turf)
+// var/can_go_up = FALSE
+
+// if(above && !above.density)
+// // Look for stairs at current location
+// for(var/obj/structure/stairs/S in current_turf.contents)
+// var/turf/dest = get_step(above, S.dir)
+// if(dest == next_step || get_step(dest, S.dir) == next_step)
+// can_go_up = TRUE
+// break
+
+// // If we can't go up but our path expects us to, we've likely been thrown down
+// // So regenerate the path from our current position
+// if(!can_go_up)
+// controller.movement_path = null
+// controller.clear_blackboard_key(future_path_blackboard_key)
+// var/datum/weakref/weak = WEAKREF(controller)
+// if(weak in falling_back)
+// falling_back[weak] = FALSE
+// if(weak in fallback_fail)
+// fallback_fail[weak] = FALSE
+// continue
+
+// // Basic movement for targets on the same z-level with no existing path
+// if(end_turf?.z == movable_pawn?.z && !length(controller.movement_path) && !cliented && !always_advanced)
+// advanced = FALSE
+// var/can_move = controller.can_move()
+
+// var/current_loc = get_turf(movable_pawn)
+
+// if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs) && can_move)
+// step_to(movable_pawn, target_turf, controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE], controller.movement_delay)
+
+// // Check if movement was successful
+// if(current_loc != get_turf(movable_pawn))
+// // Successful basic movement - reset failure counter and clear fallback state
+// controller.pathing_attempts = 0
+// var/datum/weakref/weak = WEAKREF(controller)
+// falling_back -= weak
+// fallback_fail -= weak
+// else
+// // Movement failed - increment failure counter
+// controller.pathing_attempts++
+
+// // Only switch to A* after multiple consecutive failures
+// if(controller.pathing_attempts >= max_basic_failures)
+// advanced = TRUE
+// controller.movement_path = null
+// controller.clear_blackboard_key(future_path_blackboard_key)
+// var/datum/weakref/weak = WEAKREF(controller)
+// if(!(weak in falling_back))
+// falling_back |= weak
+// falling_back[weak] = FALSE
+// falling_back[weak] = TRUE
+// SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Unable to Basic Move after [max_basic_failures] attempts, swapping to AStar.")
+
+// // Check if we've exceeded maximum pathing attempts
+// if(controller.pathing_attempts >= max_pathing_attempts)
+// controller.CancelActions()
+// SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Failed pathfinding cancelling.")
+
+ // if(advanced)
+ // var/minimum_distance = controller.max_target_distance
+ // // Get the minimum distance required by current behaviors
+ // for(var/datum/ai_behavior/iter_behavior as anything in controller.current_behaviors)
+ // if(iter_behavior.required_distance < minimum_distance)
+ // minimum_distance = iter_behavior.required_distance
+ // if(ispath(controller.movement_target_source, /datum/action_state_manager))
+ // minimum_distance = 1
+
+ // if(get_dist(movable_pawn, controller.current_movement_target) <= minimum_distance)
+ // using_closest_approach -= WEAKREF(controller)
+ // continue
+
+// var/generate_path = FALSE
+// var/list/future_path = controller.blackboard[future_path_blackboard_key]
+
+// // Path following logic
+// if(length(controller.movement_path))
+// var/turf/last_turf = controller.movement_path[length(controller.movement_path)]
+// var/turf/next_step = controller.movement_path[1]
+// var/remaining_path_length = length(controller.movement_path)
+
+ // // Move to the next step in the path
+ // if(next_step.z != movable_pawn.z)
+ // //check if there's a valid transition
+ // var/can_transition = FALSE
+
+ // // Look for stairs at our current position
+ // var/obj/structure/stairs/S = locate(/obj/structure/stairs) in current_turf
+ // if(S)
+ // // Check if the next step is the target going UP or DOWN the stairs
+ // if(S.get_target_loc(S.dir) == next_step || S.get_target_loc(REVERSE_DIR(S.dir)) == next_step)
+ // can_transition = TRUE
+ // else
+ // // Fallback for falling / moving down into open space without stairs
+ // if(next_step.z < movable_pawn.z && GET_TURF_BELOW(current_turf) == next_step)
+ // can_transition = TRUE
+
+ // // Only move if we can legitimately transition, otherwise regenerate path
+ // if(can_transition)
+ // movable_pawn.Move(next_step)
+ // else
+ // // Can't reach next step legitimately, need new path
+ // generate_path = TRUE
+ // controller.clear_blackboard_key(future_path_blackboard_key)
+// else
+// step_to(movable_pawn, next_step, controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE], controller.movement_delay)
+
+// // Check if target has moved significantly from the end of our path
+// if(last_turf != get_turf(controller.current_movement_target))
+// // If we have a pre-generated future path and it's relevant, use it
+// if(future_path && length(future_path) && future_path[length(future_path)] == get_turf(controller.current_movement_target))
+// controller.movement_path = future_path.Copy()
+// controller.clear_blackboard_key(future_path_blackboard_key)
+// SEND_SIGNAL(controller.pawn, COMSIG_AI_PATH_SWAPPED, controller.movement_path)
+// else
+// generate_path = TRUE
+// controller.clear_blackboard_key(future_path_blackboard_key)
+
+// // Update current path - remove steps we've completed
+// var/datum/weakref/used_ref = WEAKREF(controller)
+// if(!(used_ref in falling_back))
+// falling_back |= used_ref
+// falling_back[used_ref] = TRUE
+// if(get_turf(movable_pawn) == next_step || (istype(next_step, /turf/open/openspace) && get_turf(movable_pawn) == GET_TURF_BELOW(next_step)))
+// controller.movement_path.Cut(1,2)
+// if(length(controller.movement_path))
+// var/turf/double_checked = controller.movement_path[1]
+
+// if(get_turf(movable_pawn) == double_checked) // Handle z-level stack issues
+// controller.movement_path.Cut(1,2)
+
+// if(!length(controller.movement_path) && falling_back[used_ref])
+// falling_back[used_ref] = FALSE
+// // Successfully completed A* path - allow basic movement again on next iteration
+// controller.pathing_attempts = 0
+// else
+// if(!falling_back[used_ref])
+// generate_path = TRUE
+// controller.clear_blackboard_key(future_path_blackboard_key)
+// else
+// fallback_fail[used_ref]++
+// if(fallback_fail[used_ref] >= 2)
+// fallback_fail[used_ref] = 0
+// generate_path = TRUE
+// if(!(used_ref in falling_back))
+// falling_back |= used_ref
+// falling_back[used_ref] = FALSE
+// falling_back[used_ref] = FALSE
+// controller.clear_blackboard_key(future_path_blackboard_key)
+
+// // If we're nearing the end of our path, preemptively generate the next path
+// // Only do this if we have a valid current path and aren't already generating a future path
+// if(!generate_path && remaining_path_length <= repath_anticipation_distance && !future_path && COOLDOWN_FINISHED(controller, repath_cooldown))
+// // Target doesnt exist anymore or we picked it up already
+// if(QDELETED(controller.current_movement_target) || controller.current_movement_target.loc == movable_pawn)
+// continue
+// COOLDOWN_START(controller, repath_cooldown, 1 SECONDS) // Shorter cooldown for anticipatory pathing
+// // Generate the future path and store it in the controller's blackboard
+// var/list/new_future_path = get_path_to(movable_pawn, controller.current_movement_target, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
+// max_path_distance + 1, max_path_distance + 1, minimum_distance, id=controller.get_access())
+// controller.set_blackboard_key(future_path_blackboard_key, new_future_path)
+// SEND_SIGNAL(controller.pawn, COMSIG_AI_FUTURE_PATH_GENERATED, new_future_path)
+// else
+// generate_path = TRUE
+
+ // // Generate a new primary path if needed
+ // if(generate_path)
+ // if(!COOLDOWN_FINISHED(controller, repath_cooldown))
+ // continue
+
+ // var/datum/weakref/weak_controller = WEAKREF(controller)
+ // var/already_closest_approach = path_to_closest_on_fail && (weak_controller in using_closest_approach)
+
+ // if(already_closest_approach)
+ // // Don't burn pathing_attempts re-trying a path we already know fails -
+ // // go straight back to closest-approach
+ // if(QDELETED(controller.current_movement_target) || controller.current_movement_target.loc == movable_pawn)
+ // continue
+ // COOLDOWN_START(controller, repath_cooldown, 1.5 SECONDS)
+ // controller.movement_path = get_path_to_closest_approach(movable_pawn, controller.current_movement_target, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
+ // max_path_distance + 1, max_path_distance + 1, minimum_distance, id=controller.get_access())
+ // controller.clear_blackboard_key(future_path_blackboard_key)
+ // if(!length(controller.movement_path))
+ // // Even closest-approach gives nothing now (e.g. truly boxed in) - give up this cycle
+ // controller.CancelActions()
+ // SEND_SIGNAL(controller, COMSIG_AI_PATHING_FAILED)
+ // else
+ // SEND_SIGNAL(controller.pawn, COMSIG_AI_PATH_GENERATED, controller.movement_path)
+ // continue
+
+ // controller.pathing_attempts++
+ // if(controller.pathing_attempts >= max_pathing_attempts)
+ // if(path_to_closest_on_fail)
+ // controller.movement_path = get_path_to_closest_approach(movable_pawn, controller.current_movement_target, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
+ // max_path_distance + 1, max_path_distance + 1, minimum_distance, id=controller.get_access())
+ // controller.clear_blackboard_key(future_path_blackboard_key)
+ // if(length(controller.movement_path))
+ // controller.pathing_attempts = 0
+ // using_closest_approach[weak_controller] = TRUE
+ // SEND_SIGNAL(controller.pawn, COMSIG_AI_PATH_GENERATED, controller.movement_path)
+ // continue
+ // controller.CancelActions()
+ // SEND_SIGNAL(controller, COMSIG_AI_PATHING_FAILED)
+ // continue
+ // // Target doesnt exist anymore or we picked it up already
+ // if(QDELETED(controller.current_movement_target) || controller.current_movement_target.loc == movable_pawn)
+ // continue
+ // COOLDOWN_START(controller, repath_cooldown, 1.5 SECONDS)
+ // controller.movement_path = get_path_to(movable_pawn, controller.current_movement_target, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
+ // max_path_distance + 1, max_path_distance + 1, minimum_distance, id=controller.get_access())
+ // controller.clear_blackboard_key(future_path_blackboard_key)
+ // SEND_SIGNAL(controller.pawn, COMSIG_AI_PATH_GENERATED, controller.movement_path)
diff --git a/code/datums/ai/ai_movement/hybrid_putrid.dm b/code/datums/ai/ai_movement/hybrid_putrid.dm
index c6be66d932d..a1015d49641 100644
--- a/code/datums/ai/ai_movement/hybrid_putrid.dm
+++ b/code/datums/ai/ai_movement/hybrid_putrid.dm
@@ -1,371 +1,371 @@
-/datum/ai_movement/hybrid_pathing/wormhole_aware
- max_path_distance = 100
- // Extends the hybrid pathing to consider wormholes as shortcuts
- var/wormhole_check_distance = 15 // How far to look for wormholes
- var/wormhole_check_cooldown = 3 SECONDS
-
-/datum/ai_movement/hybrid_pathing/wormhole_aware/process(delta_time)
- for(var/datum/ai_controller/controller as anything in moving_controllers)
- var/datum/weakref/used_ref = WEAKREF(controller)
- if(!(future_path_blackboard_key in controller.blackboard))
- controller.add_blackboard_key(future_path_blackboard_key, null)
- if(!COOLDOWN_FINISHED(controller, movement_cooldown))
- continue
- COOLDOWN_START(controller, movement_cooldown, controller.movement_delay)
-
- if(!controller.can_move())
- continue
-
- var/atom/movable/movable_pawn = controller.pawn
- var/turf/target_turf = get_step_towards(movable_pawn, controller.current_movement_target)
- var/turf/end_turf = get_turf(controller.current_movement_target)
- var/advanced = TRUE
- var/turf/current_turf = get_turf(movable_pawn)
-
- // Store location before movement attempt
- var/turf/location_before_move = current_turf
-
- // Check for wormhole shortcuts periodically
- var/last_check = controller.blackboard[BB_LAST_WORMHOLE_CHECK]
- if(!last_check)
- last_check = 0
-
- if(world.time > last_check + wormhole_check_cooldown)
- controller.set_blackboard_key(BB_LAST_WORMHOLE_CHECK, world.time)
- var/obj/structure/meatvine/intestine_wormhole/shortcut = find_wormhole_shortcut(movable_pawn, controller.current_movement_target, controller)
- if(shortcut)
- // Try to path to the wormhole entrance
- controller.movement_path = get_path_to(movable_pawn, shortcut, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
- max_path_distance + 1, max_path_distance + 1, 0, id=controller.get_access())
- if(length(controller.movement_path))
- controller.set_blackboard_key(BB_WORMHOLE_TARGET, shortcut)
- SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Routing through wormhole")
-
- // Check if we've reached a wormhole we were targeting
- var/obj/structure/meatvine/intestine_wormhole/wormhole_target = controller.blackboard[BB_WORMHOLE_TARGET]
- if(wormhole_target && get_turf(movable_pawn) == get_turf(wormhole_target))
- var/obj/structure/meatvine/intestine_wormhole/best_exit = find_best_wormhole_exit(wormhole_target, controller.current_movement_target)
- if(best_exit && best_exit != wormhole_target)
- // Teleport through the wormhole
- movable_pawn.forceMove(get_turf(best_exit))
- controller.clear_blackboard_key(BB_WORMHOLE_TARGET)
- controller.movement_path = null // Regenerate path from new position
- controller.clear_blackboard_key(future_path_blackboard_key)
- SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Traveled through wormhole")
- continue
-
- var/mob/cliented_mob = controller.current_movement_target
- var/cliented = FALSE
- if(istype(cliented_mob))
- if(cliented_mob.client)
- cliented = TRUE
-
- // Check if we've moved to a lower Z-level (possibly thrown) and our path expects us to be higher
- if(length(controller.movement_path) && controller.movement_path[1])
- var/turf/next_step = controller.movement_path[1]
-
- if(next_step.z > current_turf.z)
- var/turf/above = get_step_multiz(current_turf, UP)
- var/can_go_up = FALSE
-
- if(above && !above.density)
- for(var/obj/structure/stairs/S in current_turf.contents)
- var/turf/dest = get_step(above, S.dir)
- if(dest == next_step || get_step(dest, S.dir) == next_step)
- can_go_up = TRUE
- break
-
- if(!can_go_up)
- controller.movement_path = null
- controller.clear_blackboard_key(future_path_blackboard_key)
- if(!(used_ref in falling_back))
- falling_back |= used_ref
- fallback_fail |= used_ref
- falling_back[used_ref] = FALSE
- fallback_fail[used_ref] = 0
- continue
-
- // Basic movement for targets on the same z-level with no existing path
- if(end_turf?.z == movable_pawn?.z && !length(controller.movement_path) && !cliented)
- advanced = FALSE
- var/can_move = controller.can_move()
- var/current_loc = get_turf(movable_pawn)
-
- if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs) && can_move)
- step_to(movable_pawn, target_turf, controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE], controller.movement_delay)
-
- // Check if movement was successful
- if(current_loc != get_turf(movable_pawn))
- // Successful basic movement - reset failure counter and clear fallback state
- controller.pathing_attempts = 0
- falling_back -= used_ref
- fallback_fail -= used_ref
- else
- // Movement failed - check if it's due to bounds
- if(is_blocked_by_bounds(movable_pawn, controller))
- SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "At bounds edge, cancelling action")
- controller.CancelActions()
- stop_moving_towards(controller)
- continue
-
- // Movement failed - increment failure counter
- controller.pathing_attempts++
-
- // Only switch to A* after multiple consecutive failures
- if(controller.pathing_attempts >= max_basic_failures)
- advanced = TRUE
- controller.movement_path = null
- controller.clear_blackboard_key(future_path_blackboard_key)
- if(!(used_ref in falling_back))
- falling_back |= used_ref
- fallback_fail |= used_ref
- falling_back[used_ref] = TRUE
- fallback_fail[used_ref] = 0
- SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Unable to Basic Move after [max_basic_failures] attempts, swapping to AStar.")
-
- // Check if we've exceeded maximum pathing attempts
- if(controller.pathing_attempts >= max_pathing_attempts)
- controller.CancelActions()
- SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Failed pathfinding cancelling.")
-
- if(advanced)
- var/minimum_distance = controller.max_target_distance
- for(var/datum/ai_behavior/iter_behavior as anything in controller.current_behaviors)
- if(iter_behavior.required_distance < minimum_distance)
- minimum_distance = iter_behavior.required_distance
-
- if(get_dist(movable_pawn, controller.current_movement_target) <= minimum_distance)
- continue
-
- var/generate_path = FALSE
- var/list/future_path = controller.blackboard[future_path_blackboard_key]
-
- // Path following logic
- if(length(controller.movement_path))
- var/turf/last_turf = controller.movement_path[length(controller.movement_path)]
- var/turf/next_step = controller.movement_path[1]
- var/remaining_path_length = length(controller.movement_path)
-
- if(next_step.z != movable_pawn.z)
- var/can_transition = FALSE
-
- if(next_step.z > movable_pawn.z)
- for(var/obj/structure/stairs/S in current_turf.contents)
- var/turf/above = get_step_multiz(current_turf, UP)
- if(above)
- var/turf/dest = get_step(above, S.dir)
- if(dest == next_step)
- can_transition = TRUE
- break
-
- else if(next_step.z < movable_pawn.z)
- var/turf/below = get_step_multiz(current_turf, DOWN)
- if(below == next_step)
- can_transition = TRUE
- else
- for(var/obj/structure/stairs/S in current_turf.contents)
- var/turf/dest = get_step(below, turn(S.dir, 180))
- if(dest == next_step)
- can_transition = TRUE
- break
-
- if(can_transition)
- movable_pawn.Move(next_step)
- else
- generate_path = TRUE
- controller.clear_blackboard_key(future_path_blackboard_key)
- else
- step_to(movable_pawn, next_step, controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE], controller.movement_delay)
-
- if(last_turf != get_turf(controller.current_movement_target))
- if(future_path && length(future_path) && future_path[length(future_path)] == get_turf(controller.current_movement_target))
- controller.movement_path = future_path.Copy()
- controller.clear_blackboard_key(future_path_blackboard_key)
- SEND_SIGNAL(controller.pawn, COMSIG_AI_PATH_SWAPPED, controller.movement_path)
- else
- generate_path = TRUE
- controller.clear_blackboard_key(future_path_blackboard_key)
-
- if(!(used_ref in falling_back))
- falling_back |= used_ref
- fallback_fail |= used_ref
- falling_back[used_ref] = TRUE
- fallback_fail[used_ref] = 0
-
- if(get_turf(movable_pawn) == next_step || (istype(next_step, /turf/open/openspace) && get_turf(movable_pawn) == GET_TURF_BELOW(next_step)))
- controller.movement_path.Cut(1,2)
- if(length(controller.movement_path))
- var/turf/double_checked = controller.movement_path[1]
- if(get_turf(movable_pawn) == double_checked)
- controller.movement_path.Cut(1,2)
-
- if(!length(controller.movement_path) && falling_back[used_ref])
- falling_back[used_ref] = FALSE
- // Successfully completed A* path - allow basic movement again on next iteration
- controller.pathing_attempts = 0
- else
- // Movement failed in advanced pathing - check if it's due to bounds
- if(location_before_move == get_turf(movable_pawn) && is_blocked_by_bounds(movable_pawn, controller))
- SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "At bounds edge, cancelling action")
- controller.CancelActions()
- stop_moving_towards(controller)
- continue
-
- if(!falling_back[used_ref])
- generate_path = TRUE
- controller.clear_blackboard_key(future_path_blackboard_key)
- else
- fallback_fail[used_ref]++
- if(fallback_fail[used_ref] >= 2)
- fallback_fail[used_ref] = 0
- generate_path = TRUE
- falling_back[used_ref] = FALSE
- controller.clear_blackboard_key(future_path_blackboard_key)
-
- if(!generate_path && remaining_path_length <= repath_anticipation_distance && !future_path && COOLDOWN_FINISHED(controller, repath_cooldown))
- if(QDELETED(controller.current_movement_target) || controller.current_movement_target.loc == movable_pawn)
- continue
- COOLDOWN_START(controller, repath_cooldown, 1 SECONDS)
- var/list/new_future_path = get_path_to(movable_pawn, controller.current_movement_target, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
- max_path_distance + 1, max_path_distance + 1, minimum_distance, id=controller.get_access())
- controller.set_blackboard_key(future_path_blackboard_key, new_future_path)
- SEND_SIGNAL(controller.pawn, COMSIG_AI_FUTURE_PATH_GENERATED, new_future_path)
- else
- generate_path = TRUE
-
- if(generate_path)
- if(!COOLDOWN_FINISHED(controller, repath_cooldown))
- continue
- controller.pathing_attempts++
- if(controller.pathing_attempts >= max_pathing_attempts)
- SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Failed advanced pathfinding cancelling.")
- controller.CancelActions()
- stop_moving_towards(controller)
- continue
- if(QDELETED(controller.current_movement_target) || controller.current_movement_target.loc == movable_pawn)
- stop_moving_towards(controller)
- continue
- COOLDOWN_START(controller, repath_cooldown, 1.5 SECONDS)
- controller.movement_path = get_path_to(movable_pawn, controller.current_movement_target, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
- max_path_distance + 1, max_path_distance + 1, minimum_distance, id=controller.get_access())
- controller.clear_blackboard_key(future_path_blackboard_key)
- SEND_SIGNAL(controller.pawn, COMSIG_AI_PATH_GENERATED, controller.movement_path)
-
-
-/datum/ai_movement/hybrid_pathing/wormhole_aware/proc/is_blocked_by_bounds(atom/movable/pawn, datum/ai_controller/controller)
- // Check if the pawn has a bounded component
- var/datum/component/bounded/bounds = pawn.GetComponent(/datum/component/bounded)
- if(!bounds)
- return FALSE
-
- // Check if the pawn has a movement target
- if(!controller.current_movement_target)
- return FALSE
-
- var/turf/current_loc = get_turf(pawn)
- var/turf/target_loc = get_turf(controller.current_movement_target)
-
- if(!current_loc || !target_loc)
- return FALSE
-
- var/atom/bounds_origin = bounds.master
- if(!bounds_origin)
- return FALSE
-
- var/turf/origin_turf = get_turf(bounds_origin)
- if(!origin_turf)
- return FALSE
-
- var/current_distance = get_dist(current_loc, origin_turf)
- var/max_distance = bounds.max_dist
-
- if(current_distance >= max_distance)
- var/target_distance = get_dist(target_loc, origin_turf)
- if(target_distance > current_distance)
- return TRUE
-
- return FALSE
-
-/datum/ai_movement/hybrid_pathing/wormhole_aware/proc/find_wormhole_shortcut(atom/movable/pawn, atom/target, datum/ai_controller/controller)
- // Find nearby wormholes
- var/turf/pawn_turf = get_turf(pawn)
- var/turf/target_turf = get_turf(target)
-
- if(!pawn_turf || !target_turf)
- return null
-
- var/current_distance = get_dist_3d(pawn_turf, target_turf)
- var/best_wormhole = null
- var/best_savings = 0
-
- // Search for wormholes within range
- for(var/obj/structure/meatvine/intestine_wormhole/wormhole in range(wormhole_check_distance, pawn))
- // Skip if we're already at this wormhole
- if(get_turf(wormhole) == pawn_turf)
- continue
-
- // Check if this wormhole has valid exits
- var/list/network_wormholes = list()
- if(wormhole.master)
- for(var/obj/structure/meatvine/intestine_wormhole/other in wormhole.master.vines)
- if(other.wormhole_id == wormhole.wormhole_id)
- network_wormholes += other
-
- if(length(network_wormholes) <= 1)
- continue
-
- // Find the exit that gets us closest to target
- for(var/obj/structure/meatvine/intestine_wormhole/exit in network_wormholes)
- if(exit == wormhole)
- continue
-
- var/turf/exit_turf = get_turf(exit)
- if(!exit_turf)
- continue
-
- // Calculate distance savings
- var/distance_to_entrance = get_dist_3d(pawn_turf, get_turf(wormhole))
- var/distance_from_exit = get_dist_3d(exit_turf, target_turf)
- var/total_distance = distance_to_entrance + distance_from_exit
- var/savings = current_distance - total_distance
-
- // Must save at least 5 tiles to be worth it
- if(savings > best_savings && savings > 5)
- best_savings = savings
- best_wormhole = wormhole
-
- return best_wormhole
-
-/datum/ai_movement/hybrid_pathing/wormhole_aware/proc/find_best_wormhole_exit(obj/structure/meatvine/intestine_wormhole/entrance, atom/target)
- if(!entrance.master)
- return null
-
- var/turf/target_turf = get_turf(target)
- if(!target_turf)
- return null
-
- var/list/network_wormholes = list()
- for(var/obj/structure/meatvine/intestine_wormhole/wormhole in entrance.master.vines)
- if(wormhole.wormhole_id == entrance.wormhole_id)
- network_wormholes += wormhole
+// /datum/ai_movement/hybrid_pathing/wormhole_aware
+// max_path_distance = 100
+// // Extends the hybrid pathing to consider wormholes as shortcuts
+// var/wormhole_check_distance = 15 // How far to look for wormholes
+// var/wormhole_check_cooldown = 3 SECONDS
+
+// /datum/ai_movement/hybrid_pathing/wormhole_aware/process(delta_time)
+// for(var/datum/ai_controller/controller as anything in moving_controllers)
+// var/datum/weakref/used_ref = WEAKREF(controller)
+// if(!(future_path_blackboard_key in controller.blackboard))
+// controller.add_blackboard_key(future_path_blackboard_key, null)
+// if(!COOLDOWN_FINISHED(controller, movement_cooldown))
+// continue
+// COOLDOWN_START(controller, movement_cooldown, controller.movement_delay)
+
+// if(!controller.can_move())
+// continue
+
+// var/atom/movable/movable_pawn = controller.pawn
+// var/turf/target_turf = get_step_towards(movable_pawn, controller.current_movement_target)
+// var/turf/end_turf = get_turf(controller.current_movement_target)
+// var/advanced = TRUE
+// var/turf/current_turf = get_turf(movable_pawn)
+
+// // Store location before movement attempt
+// var/turf/location_before_move = current_turf
+
+// // Check for wormhole shortcuts periodically
+// var/last_check = controller.blackboard[BB_LAST_WORMHOLE_CHECK]
+// if(!last_check)
+// last_check = 0
+
+// if(world.time > last_check + wormhole_check_cooldown)
+// controller.set_blackboard_key(BB_LAST_WORMHOLE_CHECK, world.time)
+// var/obj/structure/meatvine/intestine_wormhole/shortcut = find_wormhole_shortcut(movable_pawn, controller.current_movement_target, controller)
+// if(shortcut)
+// // Try to path to the wormhole entrance
+// controller.movement_path = get_path_to(movable_pawn, shortcut, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
+// max_path_distance + 1, max_path_distance + 1, 0, id=controller.get_access())
+// if(length(controller.movement_path))
+// controller.set_blackboard_key(BB_WORMHOLE_TARGET, shortcut)
+// SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Routing through wormhole")
+
+// // Check if we've reached a wormhole we were targeting
+// var/obj/structure/meatvine/intestine_wormhole/wormhole_target = controller.blackboard[BB_WORMHOLE_TARGET]
+// if(wormhole_target && get_turf(movable_pawn) == get_turf(wormhole_target))
+// var/obj/structure/meatvine/intestine_wormhole/best_exit = find_best_wormhole_exit(wormhole_target, controller.current_movement_target)
+// if(best_exit && best_exit != wormhole_target)
+// // Teleport through the wormhole
+// movable_pawn.forceMove(get_turf(best_exit))
+// controller.clear_blackboard_key(BB_WORMHOLE_TARGET)
+// controller.movement_path = null // Regenerate path from new position
+// controller.clear_blackboard_key(future_path_blackboard_key)
+// SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Traveled through wormhole")
+// continue
+
+// var/mob/cliented_mob = controller.current_movement_target
+// var/cliented = FALSE
+// if(istype(cliented_mob))
+// if(cliented_mob.client)
+// cliented = TRUE
+
+// // Check if we've moved to a lower Z-level (possibly thrown) and our path expects us to be higher
+// if(length(controller.movement_path) && controller.movement_path[1])
+// var/turf/next_step = controller.movement_path[1]
+
+// if(next_step.z > current_turf.z)
+// var/turf/above = get_step_multiz(current_turf, UP)
+// var/can_go_up = FALSE
+
+// if(above && !above.density)
+// for(var/obj/structure/stairs/S in current_turf.contents)
+// var/turf/dest = get_step(above, S.dir)
+// if(dest == next_step || get_step(dest, S.dir) == next_step)
+// can_go_up = TRUE
+// break
+
+// if(!can_go_up)
+// controller.movement_path = null
+// controller.clear_blackboard_key(future_path_blackboard_key)
+// if(!(used_ref in falling_back))
+// falling_back |= used_ref
+// fallback_fail |= used_ref
+// falling_back[used_ref] = FALSE
+// fallback_fail[used_ref] = 0
+// continue
+
+// // Basic movement for targets on the same z-level with no existing path
+// if(end_turf?.z == movable_pawn?.z && !length(controller.movement_path) && !cliented)
+// advanced = FALSE
+// var/can_move = controller.can_move()
+// var/current_loc = get_turf(movable_pawn)
+
+// if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs) && can_move)
+// step_to(movable_pawn, target_turf, controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE], controller.movement_delay)
+
+// // Check if movement was successful
+// if(current_loc != get_turf(movable_pawn))
+// // Successful basic movement - reset failure counter and clear fallback state
+// controller.pathing_attempts = 0
+// falling_back -= used_ref
+// fallback_fail -= used_ref
+// else
+// // Movement failed - check if it's due to bounds
+// if(is_blocked_by_bounds(movable_pawn, controller))
+// SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "At bounds edge, cancelling action")
+// controller.CancelActions()
+// stop_moving_towards(controller)
+// continue
+
+// // Movement failed - increment failure counter
+// controller.pathing_attempts++
+
+// // Only switch to A* after multiple consecutive failures
+// if(controller.pathing_attempts >= max_basic_failures)
+// advanced = TRUE
+// controller.movement_path = null
+// controller.clear_blackboard_key(future_path_blackboard_key)
+// if(!(used_ref in falling_back))
+// falling_back |= used_ref
+// fallback_fail |= used_ref
+// falling_back[used_ref] = TRUE
+// fallback_fail[used_ref] = 0
+// SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Unable to Basic Move after [max_basic_failures] attempts, swapping to AStar.")
+
+// // Check if we've exceeded maximum pathing attempts
+// if(controller.pathing_attempts >= max_pathing_attempts)
+// controller.CancelActions()
+// SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Failed pathfinding cancelling.")
+
+// if(advanced)
+// var/minimum_distance = controller.max_target_distance
+// for(var/datum/ai_behavior/iter_behavior as anything in controller.current_behaviors)
+// if(iter_behavior.required_distance < minimum_distance)
+// minimum_distance = iter_behavior.required_distance
+
+// if(get_dist(movable_pawn, controller.current_movement_target) <= minimum_distance)
+// continue
+
+// var/generate_path = FALSE
+// var/list/future_path = controller.blackboard[future_path_blackboard_key]
+
+// // Path following logic
+// if(length(controller.movement_path))
+// var/turf/last_turf = controller.movement_path[length(controller.movement_path)]
+// var/turf/next_step = controller.movement_path[1]
+// var/remaining_path_length = length(controller.movement_path)
+
+// if(next_step.z != movable_pawn.z)
+// var/can_transition = FALSE
+
+// if(next_step.z > movable_pawn.z)
+// for(var/obj/structure/stairs/S in current_turf.contents)
+// var/turf/above = get_step_multiz(current_turf, UP)
+// if(above)
+// var/turf/dest = get_step(above, S.dir)
+// if(dest == next_step)
+// can_transition = TRUE
+// break
+
+// else if(next_step.z < movable_pawn.z)
+// var/turf/below = get_step_multiz(current_turf, DOWN)
+// if(below == next_step)
+// can_transition = TRUE
+// else
+// for(var/obj/structure/stairs/S in current_turf.contents)
+// var/turf/dest = get_step(below, turn(S.dir, 180))
+// if(dest == next_step)
+// can_transition = TRUE
+// break
+
+// if(can_transition)
+// movable_pawn.Move(next_step)
+// else
+// generate_path = TRUE
+// controller.clear_blackboard_key(future_path_blackboard_key)
+// else
+// step_to(movable_pawn, next_step, controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE], controller.movement_delay)
+
+// if(last_turf != get_turf(controller.current_movement_target))
+// if(future_path && length(future_path) && future_path[length(future_path)] == get_turf(controller.current_movement_target))
+// controller.movement_path = future_path.Copy()
+// controller.clear_blackboard_key(future_path_blackboard_key)
+// SEND_SIGNAL(controller.pawn, COMSIG_AI_PATH_SWAPPED, controller.movement_path)
+// else
+// generate_path = TRUE
+// controller.clear_blackboard_key(future_path_blackboard_key)
+
+// if(!(used_ref in falling_back))
+// falling_back |= used_ref
+// fallback_fail |= used_ref
+// falling_back[used_ref] = TRUE
+// fallback_fail[used_ref] = 0
+
+// if(get_turf(movable_pawn) == next_step || (istype(next_step, /turf/open/openspace) && get_turf(movable_pawn) == GET_TURF_BELOW(next_step)))
+// controller.movement_path.Cut(1,2)
+// if(length(controller.movement_path))
+// var/turf/double_checked = controller.movement_path[1]
+// if(get_turf(movable_pawn) == double_checked)
+// controller.movement_path.Cut(1,2)
+
+// if(!length(controller.movement_path) && falling_back[used_ref])
+// falling_back[used_ref] = FALSE
+// // Successfully completed A* path - allow basic movement again on next iteration
+// controller.pathing_attempts = 0
+// else
+// // Movement failed in advanced pathing - check if it's due to bounds
+// if(location_before_move == get_turf(movable_pawn) && is_blocked_by_bounds(movable_pawn, controller))
+// SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "At bounds edge, cancelling action")
+// controller.CancelActions()
+// stop_moving_towards(controller)
+// continue
+
+// if(!falling_back[used_ref])
+// generate_path = TRUE
+// controller.clear_blackboard_key(future_path_blackboard_key)
+// else
+// fallback_fail[used_ref]++
+// if(fallback_fail[used_ref] >= 2)
+// fallback_fail[used_ref] = 0
+// generate_path = TRUE
+// falling_back[used_ref] = FALSE
+// controller.clear_blackboard_key(future_path_blackboard_key)
+
+// if(!generate_path && remaining_path_length <= repath_anticipation_distance && !future_path && COOLDOWN_FINISHED(controller, repath_cooldown))
+// if(QDELETED(controller.current_movement_target) || controller.current_movement_target.loc == movable_pawn)
+// continue
+// COOLDOWN_START(controller, repath_cooldown, 1 SECONDS)
+// var/list/new_future_path = get_path_to(movable_pawn, controller.current_movement_target, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
+// max_path_distance + 1, max_path_distance + 1, minimum_distance, id=controller.get_access())
+// controller.set_blackboard_key(future_path_blackboard_key, new_future_path)
+// SEND_SIGNAL(controller.pawn, COMSIG_AI_FUTURE_PATH_GENERATED, new_future_path)
+// else
+// generate_path = TRUE
+
+// if(generate_path)
+// if(!COOLDOWN_FINISHED(controller, repath_cooldown))
+// continue
+// controller.pathing_attempts++
+// if(controller.pathing_attempts >= max_pathing_attempts)
+// SEND_SIGNAL(movable_pawn, COMSIG_AI_GENERAL_CHANGE, "Failed advanced pathfinding cancelling.")
+// controller.CancelActions()
+// stop_moving_towards(controller)
+// continue
+// if(QDELETED(controller.current_movement_target) || controller.current_movement_target.loc == movable_pawn)
+// stop_moving_towards(controller)
+// continue
+// COOLDOWN_START(controller, repath_cooldown, 1.5 SECONDS)
+// controller.movement_path = get_path_to(movable_pawn, controller.current_movement_target, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d),
+// max_path_distance + 1, max_path_distance + 1, minimum_distance, id=controller.get_access())
+// controller.clear_blackboard_key(future_path_blackboard_key)
+// SEND_SIGNAL(controller.pawn, COMSIG_AI_PATH_GENERATED, controller.movement_path)
+
+
+// /datum/ai_movement/hybrid_pathing/wormhole_aware/proc/is_blocked_by_bounds(atom/movable/pawn, datum/ai_controller/controller)
+// // Check if the pawn has a bounded component
+// var/datum/component/bounded/bounds = pawn.GetComponent(/datum/component/bounded)
+// if(!bounds)
+// return FALSE
+
+// // Check if the pawn has a movement target
+// if(!controller.current_movement_target)
+// return FALSE
+
+// var/turf/current_loc = get_turf(pawn)
+// var/turf/target_loc = get_turf(controller.current_movement_target)
+
+// if(!current_loc || !target_loc)
+// return FALSE
+
+// var/atom/bounds_origin = bounds.master
+// if(!bounds_origin)
+// return FALSE
+
+// var/turf/origin_turf = get_turf(bounds_origin)
+// if(!origin_turf)
+// return FALSE
+
+// var/current_distance = get_dist(current_loc, origin_turf)
+// var/max_distance = bounds.max_dist
+
+// if(current_distance >= max_distance)
+// var/target_distance = get_dist(target_loc, origin_turf)
+// if(target_distance > current_distance)
+// return TRUE
+
+// return FALSE
+
+// /datum/ai_movement/hybrid_pathing/wormhole_aware/proc/find_wormhole_shortcut(atom/movable/pawn, atom/target, datum/ai_controller/controller)
+// // Find nearby wormholes
+// var/turf/pawn_turf = get_turf(pawn)
+// var/turf/target_turf = get_turf(target)
+
+// if(!pawn_turf || !target_turf)
+// return null
+
+// var/current_distance = get_dist_3d(pawn_turf, target_turf)
+// var/best_wormhole = null
+// var/best_savings = 0
+
+// // Search for wormholes within range
+// for(var/obj/structure/meatvine/intestine_wormhole/wormhole in range(wormhole_check_distance, pawn))
+// // Skip if we're already at this wormhole
+// if(get_turf(wormhole) == pawn_turf)
+// continue
+
+// // Check if this wormhole has valid exits
+// var/list/network_wormholes = list()
+// if(wormhole.master)
+// for(var/obj/structure/meatvine/intestine_wormhole/other in wormhole.master.vines)
+// if(other.wormhole_id == wormhole.wormhole_id)
+// network_wormholes += other
+
+// if(length(network_wormholes) <= 1)
+// continue
+
+// // Find the exit that gets us closest to target
+// for(var/obj/structure/meatvine/intestine_wormhole/exit in network_wormholes)
+// if(exit == wormhole)
+// continue
+
+// var/turf/exit_turf = get_turf(exit)
+// if(!exit_turf)
+// continue
+
+// // Calculate distance savings
+// var/distance_to_entrance = get_dist_3d(pawn_turf, get_turf(wormhole))
+// var/distance_from_exit = get_dist_3d(exit_turf, target_turf)
+// var/total_distance = distance_to_entrance + distance_from_exit
+// var/savings = current_distance - total_distance
+
+// // Must save at least 5 tiles to be worth it
+// if(savings > best_savings && savings > 5)
+// best_savings = savings
+// best_wormhole = wormhole
+
+// return best_wormhole
+
+// /datum/ai_movement/hybrid_pathing/wormhole_aware/proc/find_best_wormhole_exit(obj/structure/meatvine/intestine_wormhole/entrance, atom/target)
+// if(!entrance.master)
+// return null
+
+// var/turf/target_turf = get_turf(target)
+// if(!target_turf)
+// return null
+
+// var/list/network_wormholes = list()
+// for(var/obj/structure/meatvine/intestine_wormhole/wormhole in entrance.master.vines)
+// if(wormhole.wormhole_id == entrance.wormhole_id)
+// network_wormholes += wormhole
- if(length(network_wormholes) <= 1)
- return null
+// if(length(network_wormholes) <= 1)
+// return null
- var/obj/structure/meatvine/intestine_wormhole/best_exit = null
- var/best_distance = INFINITY
+// var/obj/structure/meatvine/intestine_wormhole/best_exit = null
+// var/best_distance = INFINITY
- for(var/obj/structure/meatvine/intestine_wormhole/exit in network_wormholes)
- if(exit == entrance)
- continue
+// for(var/obj/structure/meatvine/intestine_wormhole/exit in network_wormholes)
+// if(exit == entrance)
+// continue
- var/turf/exit_turf = get_turf(exit)
- if(!exit_turf)
- continue
+// var/turf/exit_turf = get_turf(exit)
+// if(!exit_turf)
+// continue
- var/distance = get_dist_3d(exit_turf, target_turf)
- if(distance < best_distance)
- best_distance = distance
- best_exit = exit
+// var/distance = get_dist_3d(exit_turf, target_turf)
+// if(distance < best_distance)
+// best_distance = distance
+// best_exit = exit
- return best_exit
+// return best_exit
diff --git a/code/datums/ai/controllers/agriopylon.dm b/code/datums/ai/controllers/agriopylon.dm
index 8e71501666d..6960dbabc3c 100644
--- a/code/datums/ai/controllers/agriopylon.dm
+++ b/code/datums/ai/controllers/agriopylon.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/agriopylon
movement_delay = 0.3 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
diff --git a/code/datums/ai/controllers/basic_controllers/chicken.dm b/code/datums/ai/controllers/basic_controllers/chicken.dm
index 12cf33907dc..fa5d9395515 100644
--- a/code/datums/ai/controllers/basic_controllers/chicken.dm
+++ b/code/datums/ai/controllers/basic_controllers/chicken.dm
@@ -9,7 +9,7 @@
)
ai_traits = STOP_MOVING_WHEN_PULLED
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/lay_egg,
@@ -27,7 +27,7 @@
)
ai_traits = STOP_MOVING_WHEN_PULLED
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/find_food,
diff --git a/code/datums/ai/controllers/basic_controllers/cow.dm b/code/datums/ai/controllers/basic_controllers/cow.dm
index 041d4deaea6..38d14120bc6 100644
--- a/code/datums/ai/controllers/basic_controllers/cow.dm
+++ b/code/datums/ai/controllers/basic_controllers/cow.dm
@@ -8,7 +8,7 @@
)
ai_traits = STOP_MOVING_WHEN_PULLED
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/tip_reaction,
@@ -28,7 +28,7 @@
)
ai_traits = STOP_MOVING_WHEN_PULLED
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/tip_reaction,
diff --git a/code/datums/ai/controllers/bat.dm b/code/datums/ai/controllers/bat.dm
index 68ce9e694cd..8234e6cbe05 100644
--- a/code/datums/ai/controllers/bat.dm
+++ b/code/datums/ai/controllers/bat.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/bat
movement_delay = 0.5 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(),
diff --git a/code/datums/ai/controllers/bear.dm b/code/datums/ai/controllers/bear.dm
index eb14815cc05..9f45c78dfc9 100644
--- a/code/datums/ai/controllers/bear.dm
+++ b/code/datums/ai/controllers/bear.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/direbear
movement_delay = 0.4 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(),
diff --git a/code/datums/ai/controllers/behemoth.dm b/code/datums/ai/controllers/behemoth.dm
index 0ef3cefca5b..60929f6f2ae 100644
--- a/code/datums/ai/controllers/behemoth.dm
+++ b/code/datums/ai/controllers/behemoth.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/behemoth
movement_delay = 0.9 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
BB_PET_TARGETING_DATUM = new /datum/targetting_datum/basic/not_friends(),
diff --git a/code/datums/ai/controllers/big_rat.dm b/code/datums/ai/controllers/big_rat.dm
index 24d35b8f6bd..7c924ad5af9 100644
--- a/code/datums/ai/controllers/big_rat.dm
+++ b/code/datums/ai/controllers/big_rat.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/big_rat
movement_delay = 0.5 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items/not_holding_item(),
diff --git a/code/datums/ai/controllers/bog_bug.dm b/code/datums/ai/controllers/bog_bug.dm
index f3957906015..f19277ff6b1 100644
--- a/code/datums/ai/controllers/bog_bug.dm
+++ b/code/datums/ai/controllers/bog_bug.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/bog_bug
movement_delay = 0.2 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items()
diff --git a/code/datums/ai/controllers/cat.dm b/code/datums/ai/controllers/cat.dm
index 31a24663b2b..acde84e08ee 100644
--- a/code/datums/ai/controllers/cat.dm
+++ b/code/datums/ai/controllers/cat.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/cat
movement_delay = 0.4 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(),
BB_PET_TARGETING_DATUM = new /datum/targetting_datum/basic/not_friends(),
@@ -27,7 +27,7 @@
/datum/ai_controller/kitten
movement_delay = 0.4 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(),
BB_PET_TARGETING_DATUM = new /datum/targetting_datum/basic/not_friends(),
diff --git a/code/datums/ai/controllers/collossus.dm b/code/datums/ai/controllers/collossus.dm
index 24a09707f03..faf93710a07 100644
--- a/code/datums/ai/controllers/collossus.dm
+++ b/code/datums/ai/controllers/collossus.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/collossus
movement_delay = 1 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
BB_PET_TARGETING_DATUM = new /datum/targetting_datum/basic/not_friends(),
diff --git a/code/datums/ai/controllers/crawler.dm b/code/datums/ai/controllers/crawler.dm
index eb70bd6e21d..8e90c2af4ff 100644
--- a/code/datums/ai/controllers/crawler.dm
+++ b/code/datums/ai/controllers/crawler.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/crawler
movement_delay = 0.6 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_HELLHOUND_FIRE = 0,
diff --git a/code/datums/ai/controllers/dragger.dm b/code/datums/ai/controllers/dragger.dm
index 38dc3ed7f73..575d79ffc20 100644
--- a/code/datums/ai/controllers/dragger.dm
+++ b/code/datums/ai/controllers/dragger.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/dragger
movement_delay = 0.3 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(),
BB_DARKNESS_THRESHOLD = 3, // Threshold for considering an area "dark"
diff --git a/code/datums/ai/controllers/fiend.dm b/code/datums/ai/controllers/fiend.dm
index 37530c02763..039bbeff28e 100644
--- a/code/datums/ai/controllers/fiend.dm
+++ b/code/datums/ai/controllers/fiend.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/fiend
movement_delay = 1 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
diff --git a/code/datums/ai/controllers/fish_boss.dm b/code/datums/ai/controllers/fish_boss.dm
index 76c7cccbad6..2feb4539993 100644
--- a/code/datums/ai/controllers/fish_boss.dm
+++ b/code/datums/ai/controllers/fish_boss.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/fishboss
movement_delay = 1 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
BB_MINIONS_TO_SPAWN = 2,
diff --git a/code/datums/ai/controllers/fox.dm b/code/datums/ai/controllers/fox.dm
index 0695615617e..702493ef701 100644
--- a/code/datums/ai/controllers/fox.dm
+++ b/code/datums/ai/controllers/fox.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/fox
movement_delay = 0.3 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
BB_PET_TARGETING_DATUM = new /datum/targetting_datum/basic/not_friends(),
diff --git a/code/datums/ai/controllers/frog.dm b/code/datums/ai/controllers/frog.dm
index 43ea2bde113..f9d4fea12f5 100644
--- a/code/datums/ai/controllers/frog.dm
+++ b/code/datums/ai/controllers/frog.dm
@@ -6,7 +6,7 @@
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items/holding_item,
BB_PET_TARGETING_DATUM = new /datum/targetting_datum/basic/not_friends,
)
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/pet_planning,
diff --git a/code/datums/ai/controllers/gator.dm b/code/datums/ai/controllers/gator.dm
index 2d6543cab01..50606faf350 100644
--- a/code/datums/ai/controllers/gator.dm
+++ b/code/datums/ai/controllers/gator.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/gator
movement_delay = 0.8 SECONDS // Gators are slower than rats on land
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(),
BB_PET_TARGETING_DATUM = new /datum/targetting_datum/basic/not_friends(),
diff --git a/code/datums/ai/controllers/glimmerwing.dm b/code/datums/ai/controllers/glimmerwing.dm
index fbc7d026ed8..a139f79b481 100644
--- a/code/datums/ai/controllers/glimmerwing.dm
+++ b/code/datums/ai/controllers/glimmerwing.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/glimmerwing
movement_delay = 0.6 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
BB_PET_TARGETING_DATUM = new /datum/targetting_datum/basic/not_friends(),
diff --git a/code/datums/ai/controllers/gnome.dm b/code/datums/ai/controllers/gnome.dm
index f50872c44da..c5469d3fcf8 100644
--- a/code/datums/ai/controllers/gnome.dm
+++ b/code/datums/ai/controllers/gnome.dm
@@ -45,7 +45,7 @@
)
ai_traits = STOP_MOVING_WHEN_PULLED
- ai_movement = /datum/ai_movement/hybrid_pathing/gnome
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive ///datum/ai_movement/hybrid_pathing/gnome
idle_behavior = /datum/idle_behavior/gnome_enhanced_idle
planning_subtrees = list(
/datum/ai_planning_subtree/pet_planning,
diff --git a/code/datums/ai/controllers/gote.dm b/code/datums/ai/controllers/gote.dm
index fe0ab14b5d2..8f7b0a62aad 100644
--- a/code/datums/ai/controllers/gote.dm
+++ b/code/datums/ai/controllers/gote.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/gote
movement_delay = 0.5 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(),
diff --git a/code/datums/ai/controllers/haunt.dm b/code/datums/ai/controllers/haunt.dm
index 78bffe71b35..43add210a4b 100644
--- a/code/datums/ai/controllers/haunt.dm
+++ b/code/datums/ai/controllers/haunt.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/haunt
movement_delay = 0.5 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
diff --git a/code/datums/ai/controllers/headless.dm b/code/datums/ai/controllers/headless.dm
index 5eac16da3ba..9e1f93cc9ae 100644
--- a/code/datums/ai/controllers/headless.dm
+++ b/code/datums/ai/controllers/headless.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/headless
movement_delay = 0.8 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items()
diff --git a/code/datums/ai/controllers/hellhound.dm b/code/datums/ai/controllers/hellhound.dm
index 8673de8026e..151816b2d2f 100644
--- a/code/datums/ai/controllers/hellhound.dm
+++ b/code/datums/ai/controllers/hellhound.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/hellhound
movement_delay = 0.4 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_HELLHOUND_FIRE = 0,
diff --git a/code/datums/ai/controllers/human_bum.dm b/code/datums/ai/controllers/human_bum.dm
index 8ed2c216337..866a6655435 100644
--- a/code/datums/ai/controllers/human_bum.dm
+++ b/code/datums/ai/controllers/human_bum.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/human_bum
movement_delay = 0.5 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_WEAPON_TYPE = /obj/item/weapon,
@@ -74,7 +74,7 @@
. = ..()
var/mob/living/living_pawn = new_pawn
RegisterSignal(new_pawn, COMSIG_MOB_MOVESPEED_UPDATED, PROC_REF(update_movespeed))
- movement_delay = living_pawn.cached_multiplicative_slowdown
+ movement_delay = living_pawn.cached_multiplicative_slowdown || 1
new_pawn.AddComponent(/datum/component/ai_inventory_manager)
new_pawn.AddElement(/datum/element/interrupt_on_damage)
new_pawn.AddComponent(/datum/component/combat_vocalizer)
@@ -89,4 +89,4 @@
/datum/ai_controller/human_bum/proc/update_movespeed(mob/living/pawn)
SIGNAL_HANDLER
- movement_delay = pawn.cached_multiplicative_slowdown
+ movement_delay = pawn.cached_multiplicative_slowdown || 1
diff --git a/code/datums/ai/controllers/human_npc.dm b/code/datums/ai/controllers/human_npc.dm
index 0c502b33eda..69d2b34ee7b 100644
--- a/code/datums/ai/controllers/human_npc.dm
+++ b/code/datums/ai/controllers/human_npc.dm
@@ -2,7 +2,8 @@
/datum/ai_controller/human_npc
movement_delay = 0.5 SECONDS
max_target_distance = 13
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
+
blackboard = list(
BB_WEAPON_TYPE = /obj/item/weapon,
BB_ARMOR_CLASS = 2,
@@ -51,7 +52,7 @@
. = ..()
var/mob/living/living_pawn = new_pawn
RegisterSignal(new_pawn, COMSIG_MOB_MOVESPEED_UPDATED, PROC_REF(update_movespeed))
- movement_delay = living_pawn.cached_multiplicative_slowdown
+ movement_delay = living_pawn.cached_multiplicative_slowdown || 1
new_pawn.AddComponent(/datum/component/ai_inventory_manager)
new_pawn.AddElement(/datum/element/interrupt_on_damage)
new_pawn.AddComponent(/datum/component/combat_vocalizer)
@@ -64,4 +65,4 @@
/datum/ai_controller/human_npc/proc/update_movespeed(mob/living/pawn)
SIGNAL_HANDLER
- movement_delay = pawn.cached_multiplicative_slowdown
+ movement_delay = pawn.cached_multiplicative_slowdown || 1
diff --git a/code/datums/ai/controllers/imp.dm b/code/datums/ai/controllers/imp.dm
index d63ad148cd3..3a19821d7bb 100644
--- a/code/datums/ai/controllers/imp.dm
+++ b/code/datums/ai/controllers/imp.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/imp
movement_delay = 0.4 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic()
diff --git a/code/datums/ai/controllers/kraken.dm b/code/datums/ai/controllers/kraken.dm
index 0fda050b857..fefc298f7fa 100644
--- a/code/datums/ai/controllers/kraken.dm
+++ b/code/datums/ai/controllers/kraken.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/swamp_kraken
movement_delay = 0.5 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(),
)
@@ -31,7 +31,7 @@
/datum/ai_controller/kraken_tentacle
movement_delay = 0.3 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(),
)
diff --git a/code/datums/ai/controllers/lamia.dm b/code/datums/ai/controllers/lamia.dm
index eb24040c02a..6f8a70903d5 100644
--- a/code/datums/ai/controllers/lamia.dm
+++ b/code/datums/ai/controllers/lamia.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/lamia
movement_delay = 0.4 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic()
diff --git a/code/datums/ai/controllers/lycan.dm b/code/datums/ai/controllers/lycan.dm
index 0493c315d22..9a3e651e43c 100644
--- a/code/datums/ai/controllers/lycan.dm
+++ b/code/datums/ai/controllers/lycan.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/lycan
movement_delay = 0.5 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/leyline(),
BB_LEYLINE_SOURCE = null,
diff --git a/code/datums/ai/controllers/mimic.dm b/code/datums/ai/controllers/mimic.dm
index 6f86dcdbc1f..60b4903127f 100644
--- a/code/datums/ai/controllers/mimic.dm
+++ b/code/datums/ai/controllers/mimic.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/mimic
movement_delay = 0.8 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items()
diff --git a/code/datums/ai/controllers/minoutaur.dm b/code/datums/ai/controllers/minoutaur.dm
index ea43cf25cf3..096465d9282 100644
--- a/code/datums/ai/controllers/minoutaur.dm
+++ b/code/datums/ai/controllers/minoutaur.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/minotaur
movement_delay = 0.7 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(),
BB_MINOTAUR_RAGE_METER = 0,
diff --git a/code/datums/ai/controllers/mirespider.dm b/code/datums/ai/controllers/mirespider.dm
index 112409261e2..c3d9637fe8a 100644
--- a/code/datums/ai/controllers/mirespider.dm
+++ b/code/datums/ai/controllers/mirespider.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/mirespider
movement_delay = 0.4 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic()
@@ -22,7 +22,7 @@
/datum/ai_controller/mirespider_lurker
movement_delay = 0.4 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic()
@@ -41,7 +41,7 @@
/datum/ai_controller/mirespider_paralytic
movement_delay = 0.4 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic()
diff --git a/code/datums/ai/controllers/mole.dm b/code/datums/ai/controllers/mole.dm
index acb194cb509..6110751e85b 100644
--- a/code/datums/ai/controllers/mole.dm
+++ b/code/datums/ai/controllers/mole.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/mole
movement_delay = 0.6 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items()
diff --git a/code/datums/ai/controllers/pig.dm b/code/datums/ai/controllers/pig.dm
index a76d661fd45..b845a068714 100644
--- a/code/datums/ai/controllers/pig.dm
+++ b/code/datums/ai/controllers/pig.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/pig
movement_delay = 0.8 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
ai_traits = STOP_MOVING_WHEN_PULLED
blackboard = list(
diff --git a/code/datums/ai/controllers/poltergeist.dm b/code/datums/ai/controllers/poltergeist.dm
index 42b7539da35..b1ed56b5c77 100644
--- a/code/datums/ai/controllers/poltergeist.dm
+++ b/code/datums/ai/controllers/poltergeist.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/polter
movement_delay = 0.5 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGET_HELD_ITEM = /obj/item/clothing/neck/psycross,
diff --git a/code/datums/ai/controllers/primordials.dm b/code/datums/ai/controllers/primordials.dm
index da3d9041f46..bd6f05509ae 100644
--- a/code/datums/ai/controllers/primordials.dm
+++ b/code/datums/ai/controllers/primordials.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/flame_primordial
movement_delay = 0.15 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic()
)
@@ -16,7 +16,7 @@
/datum/ai_controller/water_primordial
movement_delay = 0.15 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic()
)
@@ -32,7 +32,7 @@
/datum/ai_controller/air_primordial
movement_delay = 0.1 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic()
)
diff --git a/code/datums/ai/controllers/putrid.dm b/code/datums/ai/controllers/putrid.dm
index 86f8e4a3e34..f1c74017a23 100644
--- a/code/datums/ai/controllers/putrid.dm
+++ b/code/datums/ai/controllers/putrid.dm
@@ -2,7 +2,7 @@
/datum/ai_controller/meatvine_defender
movement_delay = 0.5 SECONDS
max_target_distance = 200
- ai_movement = /datum/ai_movement/hybrid_pathing/wormhole_aware
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive ///datum/ai_movement/hybrid_pathing/wormhole_aware
idle_behavior = /datum/idle_behavior/idle_random_walk
can_idle = FALSE
diff --git a/code/datums/ai/controllers/racoon.dm b/code/datums/ai/controllers/racoon.dm
index 544df2093f4..bb63cd3d2f5 100644
--- a/code/datums/ai/controllers/racoon.dm
+++ b/code/datums/ai/controllers/racoon.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/raccoon
movement_delay = 0.4 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(),
BB_PET_TARGETING_DATUM = new /datum/targetting_datum/basic/not_friends(),
diff --git a/code/datums/ai/controllers/saiga.dm b/code/datums/ai/controllers/saiga.dm
index 17452d24281..52add69f063 100644
--- a/code/datums/ai/controllers/saiga.dm
+++ b/code/datums/ai/controllers/saiga.dm
@@ -7,7 +7,7 @@
BB_BASIC_MOB_FLEEING = TRUE,
)
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
@@ -41,7 +41,7 @@
BB_BASIC_MOB_FLEEING = TRUE,
)
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
diff --git a/code/datums/ai/controllers/species_hostile.dm b/code/datums/ai/controllers/species_hostile.dm
index f3eb982561e..df22a6494d1 100644
--- a/code/datums/ai/controllers/species_hostile.dm
+++ b/code/datums/ai/controllers/species_hostile.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/species_hostile
movement_delay = 0.5 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_WEAPON_TYPE = /obj/item/weapon,
@@ -30,7 +30,7 @@
. = ..()
var/mob/living/living_pawn = new_pawn
RegisterSignal(new_pawn, COMSIG_MOB_MOVESPEED_UPDATED, PROC_REF(update_movespeed))
- movement_delay = living_pawn.cached_multiplicative_slowdown
+ movement_delay = living_pawn.cached_multiplicative_slowdown || 1
/datum/ai_controller/species_hostile/UnpossessPawn(destroy)
@@ -43,4 +43,4 @@
/datum/ai_controller/species_hostile/proc/update_movespeed(mob/living/pawn)
SIGNAL_HANDLER
- movement_delay = pawn.cached_multiplicative_slowdown
+ movement_delay = pawn.cached_multiplicative_slowdown || 1
diff --git a/code/datums/ai/controllers/spider.dm b/code/datums/ai/controllers/spider.dm
index 34487fec90e..5313e7347eb 100644
--- a/code/datums/ai/controllers/spider.dm
+++ b/code/datums/ai/controllers/spider.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/spider
movement_delay = 0.4 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_HOME_PATH = /obj/structure/spider/nest,
diff --git a/code/datums/ai/controllers/sprite.dm b/code/datums/ai/controllers/sprite.dm
index 928a6971169..dea7dab30b6 100644
--- a/code/datums/ai/controllers/sprite.dm
+++ b/code/datums/ai/controllers/sprite.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/sprite
movement_delay = 0.3 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
BB_PET_TARGETING_DATUM = new /datum/targetting_datum/basic/not_friends(),
diff --git a/code/datums/ai/controllers/summon.dm b/code/datums/ai/controllers/summon.dm
index d2b8ac233ef..f6369166ada 100644
--- a/code/datums/ai/controllers/summon.dm
+++ b/code/datums/ai/controllers/summon.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/summon
movement_delay = 0.4 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
can_idle = FALSE
blackboard = list(
diff --git a/code/datums/ai/controllers/sylph.dm b/code/datums/ai/controllers/sylph.dm
index 1148883e5cc..4767494cdc1 100644
--- a/code/datums/ai/controllers/sylph.dm
+++ b/code/datums/ai/controllers/sylph.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/sylph
movement_delay = 0.8 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
BB_PET_TARGETING_DATUM = new /datum/targetting_datum/basic/not_friends(),
diff --git a/code/datums/ai/controllers/town_guard.dm b/code/datums/ai/controllers/town_guard.dm
index cf83d720e00..6f63eb7cdf8 100644
--- a/code/datums/ai/controllers/town_guard.dm
+++ b/code/datums/ai/controllers/town_guard.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/guardsman
movement_delay = 0.5 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_WEAPON_TYPE = /obj/item/weapon,
@@ -30,7 +30,7 @@
. = ..()
var/mob/living/living_pawn = new_pawn
RegisterSignal(new_pawn, COMSIG_MOB_MOVESPEED_UPDATED, PROC_REF(update_movespeed))
- movement_delay = living_pawn.cached_multiplicative_slowdown
+ movement_delay = living_pawn.cached_multiplicative_slowdown || 1
/datum/ai_controller/guardsman/UnpossessPawn(destroy)
@@ -43,4 +43,4 @@
/datum/ai_controller/guardsman/proc/update_movespeed(mob/living/pawn)
SIGNAL_HANDLER
- movement_delay = pawn.cached_multiplicative_slowdown
+ movement_delay = pawn.cached_multiplicative_slowdown || 1
diff --git a/code/datums/ai/controllers/trader.dm b/code/datums/ai/controllers/trader.dm
index 7c26238ba38..30c51408712 100644
--- a/code/datums/ai/controllers/trader.dm
+++ b/code/datums/ai/controllers/trader.dm
@@ -6,7 +6,7 @@
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
)
- ai_movement = /datum/ai_movement/hybrid_pathing/gnome
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive ///datum/ai_movement/hybrid_pathing/gnome
idle_behavior = /datum/idle_behavior/idle_random_walk/not_while_on_target/trader
planning_subtrees = list(
/datum/ai_planning_subtree/target_retaliate,
diff --git a/code/datums/ai/controllers/troll.dm b/code/datums/ai/controllers/troll.dm
index 0b6894329e5..35fe8517029 100644
--- a/code/datums/ai/controllers/troll.dm
+++ b/code/datums/ai/controllers/troll.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/troll
movement_delay = 0.7 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items()
@@ -25,7 +25,7 @@
/datum/ai_controller/bog_troll
movement_delay = 0.7 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items()
@@ -47,7 +47,7 @@
/datum/ai_controller/troll/cave
movement_delay = 0.7 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items()
@@ -72,7 +72,7 @@
/datum/ai_controller/troll/ambush
movement_delay = 0.7 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items()
@@ -106,7 +106,7 @@
/datum/ai_controller/troll/sea
movement_delay = 0.7 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items()
)
diff --git a/code/datums/ai/controllers/vengeful_spirit.dm b/code/datums/ai/controllers/vengeful_spirit.dm
index a9471cc6ea5..498fd557158 100644
--- a/code/datums/ai/controllers/vengeful_spirit.dm
+++ b/code/datums/ai/controllers/vengeful_spirit.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/spirit_vengeance
movement_delay = 0.5 SECONDS
- ai_movement = /datum/ai_movement/astar
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic()
diff --git a/code/datums/ai/controllers/void_dragon.dm b/code/datums/ai/controllers/void_dragon.dm
index fa7335d50c5..b4e3fef84bd 100644
--- a/code/datums/ai/controllers/void_dragon.dm
+++ b/code/datums/ai/controllers/void_dragon.dm
@@ -1,6 +1,6 @@
/datum/ai_controller/voiddragon
movement_delay = 0.5 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
BB_DRAGON_ENRAGED = FALSE,
diff --git a/code/datums/ai/controllers/void_obelisk.dm b/code/datums/ai/controllers/void_obelisk.dm
index 185bf17e13d..14f865db807 100644
--- a/code/datums/ai/controllers/void_obelisk.dm
+++ b/code/datums/ai/controllers/void_obelisk.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/void_obelisk
movement_delay = 1.1 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items()
diff --git a/code/datums/ai/controllers/volf.dm b/code/datums/ai/controllers/volf.dm
index 961b256c5a1..fcd4187d1c8 100644
--- a/code/datums/ai/controllers/volf.dm
+++ b/code/datums/ai/controllers/volf.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/volf
movement_delay = 0.3 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
diff --git a/code/datums/ai/controllers/warden.dm b/code/datums/ai/controllers/warden.dm
index 78a9846aabe..07f899004c7 100644
--- a/code/datums/ai/controllers/warden.dm
+++ b/code/datums/ai/controllers/warden.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/warden
movement_delay = 0.8 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_HELLHOUND_FIRE = 0,
diff --git a/code/datums/ai/controllers/watcher.dm b/code/datums/ai/controllers/watcher.dm
index d7524a3dc4d..91cc65fa024 100644
--- a/code/datums/ai/controllers/watcher.dm
+++ b/code/datums/ai/controllers/watcher.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/watcher
movement_delay = 0.7 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic()
diff --git a/code/datums/ai/controllers/zizoid.dm b/code/datums/ai/controllers/zizoid.dm
index 9890096434b..1765a59bf44 100644
--- a/code/datums/ai/controllers/zizoid.dm
+++ b/code/datums/ai/controllers/zizoid.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/zizoid
movement_delay = 0.4 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/zizoid(),
diff --git a/code/datums/ai/controllers/zombie.dm b/code/datums/ai/controllers/zombie.dm
index 7ac9392ca2d..173ec63a820 100644
--- a/code/datums/ai/controllers/zombie.dm
+++ b/code/datums/ai/controllers/zombie.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/zombie
movement_delay = 0.5 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_WEAPON_TYPE = /obj/item/weapon,
@@ -29,7 +29,7 @@
. = ..()
var/mob/living/living_pawn = new_pawn
RegisterSignal(new_pawn, COMSIG_MOB_MOVESPEED_UPDATED, PROC_REF(update_movespeed))
- movement_delay = living_pawn.cached_multiplicative_slowdown
+ movement_delay = living_pawn.cached_multiplicative_slowdown || 1
/datum/ai_controller/zombie/UnpossessPawn(destroy)
@@ -42,4 +42,4 @@
/datum/ai_controller/zombie/proc/update_movespeed(mob/living/pawn)
SIGNAL_HANDLER
- movement_delay = pawn.cached_multiplicative_slowdown
+ movement_delay = pawn.cached_multiplicative_slowdown || 1
diff --git a/code/datums/ai/hostile/testing_orc.dm b/code/datums/ai/hostile/testing_orc.dm
index 1aed38dc874..8b28e6a62d4 100644
--- a/code/datums/ai/hostile/testing_orc.dm
+++ b/code/datums/ai/hostile/testing_orc.dm
@@ -1,7 +1,7 @@
/datum/ai_controller/orc
movement_delay = 0.6 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic()
@@ -17,7 +17,7 @@
/datum/ai_controller/orc_ranged
movement_delay = 0.7 SECONDS
- ai_movement = /datum/ai_movement/hybrid_pathing
+ ai_movement = /datum/ai_movement/basic_avoidance/adaptive
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic()
diff --git a/code/datums/components/conveyor_movement.dm b/code/datums/components/conveyor_movement.dm
index 83cf57178e3..cf17ac92954 100644
--- a/code/datums/components/conveyor_movement.dm
+++ b/code/datums/components/conveyor_movement.dm
@@ -17,7 +17,7 @@
if(!isnum(start_delay))
start_delay = speed
var/atom/movable/moving_parent = parent
- var/datum/move_loop/loop = SSmove_manager.move(moving_parent, direction, delay = start_delay, subsystem = SSconveyors, flags = move_loop_flags)
+ var/datum/move_loop/loop = GLOB.move_manager.move(moving_parent, direction, delay = start_delay, subsystem = SSconveyors, flags = move_loop_flags)
RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(should_move))
RegisterSignal(loop, COMSIG_QDELETING, PROC_REF(loop_ended))
diff --git a/code/datums/components/leash.dm b/code/datums/components/leash.dm
index 4b5ed67dbbd..f73d68d0f35 100644
--- a/code/datums/components/leash.dm
+++ b/code/datums/components/leash.dm
@@ -142,7 +142,7 @@
return
if (beam_icon && beam_icon_state)
- var/list/true_path = get_path_to(parent, get_turf(owner), TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), 33, 250, 1)
+ var/list/true_path = astar_path_to(parent, get_turf(owner), max_steps = 100)
true_path |= list(get_turf(owner))
redraw_beams(true_path)
@@ -162,7 +162,7 @@
current_path_tick += 1
var/our_path_tick = current_path_tick
- var/list/path = get_path_to(parent, get_turf(owner), TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), mintargetdist = distance)
+ var/list/path = astar_path_to(parent, get_turf(owner), max_steps = distance, mintargetdist = distance)
if (last_completed_path_tick > our_path_tick)
return
diff --git a/code/datums/debug/path_debug.dm b/code/datums/debug/path_debug.dm
deleted file mode 100644
index 6f1bd74b062..00000000000
--- a/code/datums/debug/path_debug.dm
+++ /dev/null
@@ -1,160 +0,0 @@
-
-GLOBAL_DATUM_INIT(pathfind_dude, /obj/pathfind_guy, new())
-
-/obj/pathfind_guy
-
-/// Enables testing/visualization of pathfinding work
-/datum/pathfind_debug
- var/datum/admins/owner
-
-/datum/pathfind_debug/New(datum/admins/owner)
- src.owner = owner
- hook_client()
-
-/datum/pathfind_debug/Destroy(force)
- owner = null
- return ..()
-
-/datum/pathfind_debug/proc/hook_client()
- if(!owner.owner)
- return
- var/datum/action/innate/path_debug/jps/jps = new
- jps.Grant(owner.owner.mob)
-
-/datum/action/innate/path_debug
- var/list/image/display_images = list()
- var/toggled = FALSE
-
-/datum/action/innate/path_debug/Trigger(trigger_flags)
- if(!toggled)
- Activate()
- else
- Deactivate()
-
- toggled = !toggled
-
-/datum/action/innate/path_debug/Activate()
- . = ..()
- RegisterSignal(owner, COMSIG_MOB_CLICKON, PROC_REF(clicked_something))
- active = TRUE
-
-/datum/action/innate/path_debug/Deactivate()
- UnregisterSignal(owner, COMSIG_MOB_CLICKON)
- clear_visuals()
- active = FALSE
- return ..()
-
-/datum/action/innate/path_debug/proc/clicked_something(datum/source, atom/clicked, list/modifiers)
- SIGNAL_HANDLER
-
- var/turf/clunked = get_turf(clicked)
- if(!clunked)
- return NONE
-
- if(LAZYACCESS(modifiers, RIGHT_CLICK))
- right_clicked(clunked)
- else
- left_clicked(clunked)
-
- update_visuals()
- if(path_ready())
- pathfind()
-
-/datum/action/innate/path_debug/proc/left_clicked(turf/clicked_on)
- return
-
-/datum/action/innate/path_debug/proc/right_clicked(turf/clicked_on)
- return
-
-/datum/action/innate/path_debug/proc/update_visuals()
- clear_visuals()
- build_visuals()
- owner.client?.images += display_images
-
-/datum/action/innate/path_debug/proc/clear_visuals()
- owner.client?.images -= display_images
- display_images = list()
-
-/datum/action/innate/path_debug/proc/build_visuals()
- return
-
-/datum/action/innate/path_debug/proc/path_ready()
- return FALSE
-
-/datum/action/innate/path_debug/proc/pathfind()
- INVOKE_ASYNC(src, PROC_REF(run_the_path), GLOB.pathfind_dude)
- GLOB.pathfind_dude.moveToNullspace()
-
-/datum/action/innate/path_debug/proc/run_the_path(atom/movable/middle_man)
- return
-
-/datum/action/innate/path_debug/proc/render_path(list/turf/draw_list)
- if(!length(draw_list))
- return list()
-
- var/list/image/turf_images = list()
- // Render everything but the first and last
- for(var/i in 1 to (length(draw_list) - 1))
- var/turf/problem_child = draw_list[i]
- var/turf/next = draw_list[i + 1]
- turf_images += render_turf(problem_child, get_dir(problem_child, next))
-
- return turf_images
-
-/datum/action/innate/path_debug/proc/render_turf(turf/draw, direction)
- var/image/arrow = image('icons/turf/debug.dmi', draw, "arrow", 6, direction)
- arrow.plane = ABOVE_LIGHTING_PLANE
- return arrow
-
-/datum/action/innate/path_debug/jps
- name = "JPS Test"
- button_icon = 'icons/turf/debug.dmi'
- button_icon_state = "jps"
-
- // Mirror vars for jps calls
- var/turf/source_turf
- var/turf/target_turf
- var/max_distance = 250
- var/min_distance = 1
- /// List of turfs we are showing to our owner currently
- var/list/turf/display_turfs
-
-/datum/action/innate/path_debug/jps/Activate()
- . = ..()
- max_distance = input(owner, "How far should we be allowed to try and path", "Max Distance") as num
- min_distance = input(owner, "How close should we try and get to the target before stopping", "Min Distance") as num
-
-/datum/action/innate/path_debug/jps/Deactivate()
- source_turf = null
- target_turf = null
- display_turfs = list()
- return ..()
-
-/datum/action/innate/path_debug/jps/left_clicked(turf/clicked_on)
- source_turf = clicked_on
- display_turfs = list()
-
-/datum/action/innate/path_debug/jps/right_clicked(turf/clicked_on)
- target_turf = clicked_on
- display_turfs = list()
-
-/datum/action/innate/path_debug/jps/build_visuals()
- . = ..()
- if(source_turf)
- var/image/start = image('icons/turf/debug.dmi', source_turf, "start", 6)
- start.plane = ABOVE_LIGHTING_PLANE
- display_images += start
- if(target_turf)
- var/image/end = image('icons/turf/debug.dmi', target_turf, "end", 6)
- end.plane = ABOVE_LIGHTING_PLANE
- display_images += end
-
- display_images += render_path(display_turfs)
-
-/datum/action/innate/path_debug/jps/path_ready()
- return (source_turf && target_turf)
-
-/datum/action/innate/path_debug/jps/run_the_path(atom/movable/middle_man)
- middle_man.forceMove(source_turf)
- display_turfs = get_path_to(middle_man, target_turf, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), 30, max_distance, min_distance)
- update_visuals()
diff --git a/code/datums/rts/building_datums/_base_datum.dm b/code/datums/rts/building_datums/_base_datum.dm
index f7e200d915a..e9b8fee269e 100644
--- a/code/datums/rts/building_datums/_base_datum.dm
+++ b/code/datums/rts/building_datums/_base_datum.dm
@@ -144,7 +144,7 @@ GLOBAL_LIST_INIT(cached_building_images, list())
return FALSE
if(length(needed_broken_turfs))
for(var/turf/turf in needed_broken_turfs)
- if(!length(get_path_to(worker, turf, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), 32 + 1, 250,1)))
+ if(!length(astar_path_to(worker, turf, max_steps = 100)))
continue
worker.controller_mind.set_current_task(/datum/work_order/break_turf, turf, src)
needed_broken_turfs -= turf
diff --git a/code/datums/rts/controller_mob.dm b/code/datums/rts/controller_mob.dm
index 1b0efff9517..3fa5c3af6a2 100644
--- a/code/datums/rts/controller_mob.dm
+++ b/code/datums/rts/controller_mob.dm
@@ -215,21 +215,21 @@
number_img.plane = ABOVE_LIGHTING_PLANE + 0.1
target_mob.controller_mind.patrol_visual_images += number_img
- if(length(target_mob.controller_mind.patrol_points) >= 2)
- var/obj/pathfind_guy/temp_pathfinder = new()
+ // if(length(target_mob.controller_mind.patrol_points) >= 2)
+ // var/obj/pathfind_guy/temp_pathfinder = new()
- for(var/i in 1 to length(target_mob.controller_mind.patrol_points))
- var/turf/current_point = target_mob.controller_mind.patrol_points[i]
- var/turf/next_point = target_mob.controller_mind.patrol_points[(i % length(target_mob.controller_mind.patrol_points)) + 1] // Loop back to start
+ // for(var/i in 1 to length(target_mob.controller_mind.patrol_points))
+ // var/turf/current_point = target_mob.controller_mind.patrol_points[i]
+ // var/turf/next_point = target_mob.controller_mind.patrol_points[(i % length(target_mob.controller_mind.patrol_points)) + 1] // Loop back to start
- temp_pathfinder.forceMove(current_point)
- var/list/turf/path = get_path_to(temp_pathfinder, next_point, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), 250, 250, 1)
- if(length(path))
- target_mob.controller_mind.patrol_visual_images += render_patrol_path(path, target_mob.controller_mind.patrol_setup_active ? "#FFA500" : "#4CAF50")
+ // temp_pathfinder.forceMove(current_point)
+ // var/list/turf/path = astar_path_to(temp_pathfinder, next_point, max_steps = 150)
+ // if(length(path))
+ // target_mob.controller_mind.patrol_visual_images += render_patrol_path(path, target_mob.controller_mind.patrol_setup_active ? "#FFA500" : "#4CAF50")
- // Clean up the temporary pathfinder
- temp_pathfinder.moveToNullspace()
- qdel(temp_pathfinder)
+ // // Clean up the temporary pathfinder
+ // temp_pathfinder.moveToNullspace()
+ // qdel(temp_pathfinder)
// Add to client view
client.images += target_mob.controller_mind.patrol_visual_images
@@ -510,7 +510,7 @@
for(var/datum/queued_workorder/workorder in in_progress_workorders)
if(workorder.arg_1)
- if(!length(get_path_to(mob, workorder.arg_1, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), 32 + 1, 250,1)))
+ if(!length(astar_path_to(mob, workorder.arg_1, max_steps = 100)))
continue
mob.controller_mind.set_current_task(workorder.work_path, workorder.arg_1, workorder.arg_2, workorder.arg_3, workorder.arg_4)
in_progress_workorders -= workorder
diff --git a/code/datums/rts/work_mind.dm b/code/datums/rts/work_mind.dm
index 4b6f501beb7..bd88dca7162 100644
--- a/code/datums/rts/work_mind.dm
+++ b/code/datums/rts/work_mind.dm
@@ -361,7 +361,7 @@
current_task = null
movement_target = null
set_paused_state(FALSE, "work finished")
- walk(worker, 0)
+ GLOB.move_manager.stop_looping(src)
/datum/worker_mind/proc/stop_working()
var/stopped_task = current_task
@@ -372,11 +372,11 @@
if(stopped_task)
SEND_SIGNAL(src, COMSIG_WORKER_TASK_FAILED, stopped_task, "work stopped")
- walk(worker, 0)
+ GLOB.move_manager.stop_looping(src)
/datum/worker_mind/proc/set_movement_target(atom/target)
var/old_target = movement_target
- walk(worker, 0)
+ GLOB.move_manager.stop_looping(src)
movement_target = target
SEND_SIGNAL(src, COMSIG_WORKER_MOVEMENT_SET, old_target, target)
@@ -472,7 +472,7 @@
set_paused_state(TRUE, "task paused for [duration/10] seconds")
/datum/worker_mind/proc/stop_chase()
- walk(worker, 0)
+ GLOB.move_manager.stop_looping(src)
/datum/worker_mind/proc/suppress_attack()
var/old_target = attack_mode?.current_target
@@ -489,6 +489,5 @@
if(!movement_target)
return
- current_path = get_path_to(worker, get_turf(movement_target),
- TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), 32 + 1, 250, 1)
+ current_path = astar_path_to(worker, movement_target, max_steps = 100)
SEND_SIGNAL(src, COMSIG_AI_PATH_GENERATED, current_path)
diff --git a/code/datums/rts/work_orders/_base_task.dm b/code/datums/rts/work_orders/_base_task.dm
index 414aad4a5e5..96c60cd4d41 100644
--- a/code/datums/rts/work_orders/_base_task.dm
+++ b/code/datums/rts/work_orders/_base_task.dm
@@ -62,7 +62,7 @@
/datum/work_order/proc/set_movement_target(atom/target)
if(!target)
stop_work("no target")
- if(!length(get_path_to(worker, get_turf(target), TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), 32 + 1, 250,1)))
+ if(!length(astar_path_to(src, target, max_steps = 100)))
stop_work("unreachable target")
return
worker.controller_mind.set_movement_target(target)
diff --git a/code/datums/rts/work_orders/orders/haul_materials.dm b/code/datums/rts/work_orders/orders/haul_materials.dm
index 25ae0ecbae4..86d1b96f677 100644
--- a/code/datums/rts/work_orders/orders/haul_materials.dm
+++ b/code/datums/rts/work_orders/orders/haul_materials.dm
@@ -12,7 +12,7 @@
stop_work("no material requests")
return
for(var/obj/effect/building_node/stockpile/pile in master.constructed_building_nodes)
- var/list/path = get_path_to(new_worker, get_turf(pile), TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), 32 + 1, 250,1)
+ var/list/path = astar_path_to(src, pile, max_steps = 100)
if(length(path))
stockpile_node = pile
break
diff --git a/code/datums/rts/work_orders/orders/store_materials.dm b/code/datums/rts/work_orders/orders/store_materials.dm
index 1dc419effd8..047fb744735 100644
--- a/code/datums/rts/work_orders/orders/store_materials.dm
+++ b/code/datums/rts/work_orders/orders/store_materials.dm
@@ -12,7 +12,7 @@
stop_work("no store requests")
return
for(var/obj/effect/building_node/stockpile/pile in master.constructed_building_nodes)
- var/list/path = get_path_to(new_worker, get_turf(pile), TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), 32 + 1, 250,1)
+ var/list/path = astar_path_to(new_worker, get_turf(pile), max_steps = 100)
if(length(path))
stockpile_node = pile
break
diff --git a/code/datums/wave_coordinator/_base.dm b/code/datums/wave_coordinator/_base.dm
index e80f5855f55..f3f7e0dea44 100644
--- a/code/datums/wave_coordinator/_base.dm
+++ b/code/datums/wave_coordinator/_base.dm
@@ -26,7 +26,7 @@
stack_trace("wave_defense_coordinator: [wave_mob] has no ai_controller, skipping.")
continue
register_participant(wave_mob.ai_controller)
- wave_mob.ai_controller.change_ai_movement_type(/datum/ai_movement/hybrid_pathing/wave_defense)
+ wave_mob.ai_controller.change_ai_movement_type(/datum/ai_movement/astar/closest_approach)
wave_mob.ai_controller.max_target_distance = 100
wave_mob.ai_controller.blackboard[BB_TARGETTING_DATUM] = new /datum/targetting_datum/basic/allow_structures
diff --git a/code/game/atom/atoms.dm b/code/game/atom/atoms.dm
index 1a043bdfaf6..e43a7c8849d 100644
--- a/code/game/atom/atoms.dm
+++ b/code/game/atom/atoms.dm
@@ -164,12 +164,14 @@
var/resistance_flags = NONE // INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF
+ /// How this atom should react to having its astar blocking checked
+ var/can_astar_pass = CANASTARPASS_DENSITY
+
/**
* Basically the level of dirtiness on an atom, which will spread to wounds and stuff and cause infections
*/
var/germ_level = GERM_LEVEL_AMBIENT
-
/**
* Called when an atom is created in byond (built in engine proc)
*
@@ -1250,3 +1252,23 @@
var/mouseparams = list2params(paramslist)
usr_client.Click(src, loc, null, mouseparams)
return TRUE
+
+/**
+ * This proc is used for telling whether something can pass by this atom in a given direction, for use by the pathfinding system.
+ *
+ * Trying to generate one long path across the station will call this proc on every single object on every single tile that we're seeing if we can move through, likely
+ * multiple times per tile since we're likely checking if we can access said tile from multiple directions, so keep these as lightweight as possible.
+ *
+ * For turfs this will only be used if pathing_pass_method is TURF_PATHING_PASS_PROC
+ *
+ * Arguments:
+ * * to_dir - What direction we're trying to move in, relevant for things like directional windows that only block movement in certain directions
+ * * pass_info - Datum that stores info about the thing that's trying to pass us
+ *
+ * IMPORTANT NOTE: /turf/proc/LinkBlockedWithAccess assumes that overrides of CanAStarPass will always return true if density is FALSE
+ * If this is NOT you, ensure you edit your can_astar_pass variable. Check __DEFINES/path.dm
+ **/
+/atom/proc/CanAStarPass(to_dir, datum/can_pass_info/pass_info)
+ if(pass_info.pass_flags & pass_flags_self)
+ return TRUE
+ . = !density
diff --git a/code/game/atom/atoms_movable.dm b/code/game/atom/atoms_movable.dm
index 14773b5703a..d7c4f21e9f3 100644
--- a/code/game/atom/atoms_movable.dm
+++ b/code/game/atom/atoms_movable.dm
@@ -1197,8 +1197,6 @@
if(!buckled_mob.Move(newloc, direction, glide_size_override)) //If a mob buckled to us can't make the same move as us
Move(buckled_mob.loc, direction) //Move back to its location
last_move = buckled_mob.last_move
- inertia_dir = last_move
- buckled_mob.inertia_dir = last_move
return FALSE
return TRUE
diff --git a/code/game/objects/effects/decals/cleanable/aliens.dm b/code/game/objects/effects/decals/cleanable/aliens.dm
deleted file mode 100644
index 528d8024a67..00000000000
--- a/code/game/objects/effects/decals/cleanable/aliens.dm
+++ /dev/null
@@ -1,81 +0,0 @@
-// Note: BYOND is object oriented. There is no reason for this to be copy/pasted blood code.
-
-/obj/effect/decal/cleanable/xenoblood
- name = "xeno blood"
- desc = ""
- icon = 'icons/effects/blood.dmi'
- icon_state = "xfloor1"
- random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7")
- bloodiness = BLOOD_AMOUNT_PER_DECAL
- blood_state = BLOOD_STATE_XENO
- beauty = -250
- clean_type = CLEAN_TYPE_LIGHT_DECAL
-
-/obj/effect/decal/cleanable/xenoblood/Initialize()
- . = ..()
- add_blood_DNA(list("UNKNOWN DNA" = "X*"))
-
-/obj/effect/decal/cleanable/xenoblood/xsplatter
- random_icon_states = list("xgibbl1", "xgibbl2", "xgibbl3", "xgibbl4", "xgibbl5")
-
-/obj/effect/decal/cleanable/xenoblood/xgibs
- name = "xeno gibs"
- desc = ""
- icon = 'icons/effects/blood.dmi'
- icon_state = "xgib1"
- layer = LOW_OBJ_LAYER
- random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6")
- mergeable_decal = FALSE
-
-/obj/effect/decal/cleanable/xenoblood/xgibs/proc/streak(list/directions)
- set waitfor = 0
- var/direction = pick(directions)
- for(var/i = 0, i < pick(1, 200; 2, 150; 3, 50), i++)
- sleep(2)
- if(i > 0)
- new /obj/effect/decal/cleanable/xenoblood/xsplatter(loc)
- if(!step_to(src, get_step(src, direction), 0))
- break
-
-/obj/effect/decal/cleanable/xenoblood/xgibs/ex_act()
- return
-
-/obj/effect/decal/cleanable/xenoblood/xgibs/up
- icon_state = "xgibup1"
- random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibup1","xgibup1","xgibup1")
-
-/obj/effect/decal/cleanable/xenoblood/xgibs/down
- icon_state = "xgibdown1"
- random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibdown1","xgibdown1","xgibdown1")
-
-/obj/effect/decal/cleanable/xenoblood/xgibs/body
- icon_state = "xgibtorso"
- random_icon_states = list("xgibhead", "xgibtorso")
-
-/obj/effect/decal/cleanable/xenoblood/xgibs/torso
- icon_state = "xgibtorso"
- random_icon_states = list("xgibtorso")
-
-/obj/effect/decal/cleanable/xenoblood/xgibs/limb
- icon_state = "xgibleg"
- random_icon_states = list("xgibleg", "xgibarm")
-
-/obj/effect/decal/cleanable/xenoblood/xgibs/core
- icon_state = "xgibmid1"
- random_icon_states = list("xgibmid1", "xgibmid2", "xgibmid3")
-
-/obj/effect/decal/cleanable/xenoblood/xgibs/larva
- icon_state = "xgiblarva1"
- random_icon_states = list("xgiblarva1", "xgiblarva2")
-
-/obj/effect/decal/cleanable/xenoblood/xgibs/larva/body
- icon_state = "xgiblarvatorso"
- random_icon_states = list("xgiblarvahead", "xgiblarvatorso")
-
-/obj/effect/decal/cleanable/blood/xtracks
- icon_state = "xtracks"
- random_icon_states = null
-
-/obj/effect/decal/cleanable/blood/xtracks/Initialize()
- . = ..()
- add_blood_DNA(list("Unknown DNA" = "X*"))
diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm
index 12032bffd58..c93d3c12095 100644
--- a/code/game/objects/effects/decals/cleanable/humans.dm
+++ b/code/game/objects/effects/decals/cleanable/humans.dm
@@ -185,17 +185,32 @@ GLOBAL_VAR_INIT(dryblood_colormatrix, color_hex2color_matrix("#967c69"))
var/already_rotting = FALSE
-/obj/effect/decal/cleanable/blood/gibs/proc/streak(list/directions)
- set waitfor = FALSE
- var/list/diseases = list()
- SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions, diseases)
+ ///Information about the diseases our streaking spawns
+ var/list/streak_diseases
+
+/obj/effect/decal/cleanable/blood/gibs/proc/streak(list/directions, mapload=FALSE)
+ SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions, streak_diseases)
var/direction = pick(directions)
- for(var/i in 0 to rand(1,3))
- sleep(2)
- if(i > 0)
- new /obj/effect/decal/cleanable/blood/splatter(loc, color)
- if(!step_to(src, get_step(src, direction), 0))
- break
+ streak_diseases = list()
+ var/delay = 2
+ var/range = pick(0, 200; 1, 150; 2, 50; 3, 17; 50)
+ if(!step_to(src, get_step(src, direction), 0))
+ return
+
+ if(mapload)
+ for(var/i in 1 to range)
+ new /obj/effect/decal/cleanable/blood/splatter(loc, streak_diseases)
+ if(!step_to(src, get_step(src, direction), 0))
+ break
+ return
+
+ var/datum/move_loop/loop = GLOB.move_manager.move_to(src, get_step(src, direction), delay = delay, timeout = range * delay, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(spread_movement_effects))
+
+/obj/effect/decal/cleanable/blood/gibs/proc/spread_movement_effects(datum/move_loop/has_target/source)
+ SIGNAL_HANDLER
+
+ new /obj/effect/decal/cleanable/blood/splatter(loc, streak_diseases)
/obj/effect/decal/cleanable/blood/gibs/up
icon_state = "gibup1"
@@ -479,7 +494,7 @@ GLOBAL_VAR_INIT(dryblood_colormatrix, color_hex2color_matrix("#967c69"))
/obj/effect/decal/cleanable/blood/wallsplatter/proc/fly_towards(turf/target_turf, range)
flight_dir = get_dir(src, target_turf)
- var/datum/move_loop/loop = SSmove_manager.move_towards(src, target_turf, splatter_speed, timeout = splatter_speed * range, priority = MOVEMENT_ABOVE_SPACE_PRIORITY, flags = MOVEMENT_LOOP_START_FAST)
+ var/datum/move_loop/loop = GLOB.move_manager.move_towards(src, target_turf, splatter_speed, timeout = splatter_speed * range, priority = MOVEMENT_ABOVE_SPACE_PRIORITY, flags = MOVEMENT_LOOP_START_FAST)
RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move))
RegisterSignal(loop, COMSIG_QDELETING, PROC_REF(loop_done))
diff --git a/code/game/objects/effects/effect_system/effect_system.dm b/code/game/objects/effects/effect_system/effect_system.dm
index c5f8e80a8d9..bf5d5e8d05a 100644
--- a/code/game/objects/effects/effect_system/effect_system.dm
+++ b/code/game/objects/effects/effect_system/effect_system.dm
@@ -53,7 +53,7 @@ would spawn and follow the beaker, even if it is carried or thrown.
var/step_amt = pick(1,2,3)
var/step_delay = 5
- var/datum/move_loop/loop = SSmove_manager.move(effect, direction, step_delay, timeout = step_delay * step_amt, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ var/datum/move_loop/loop = GLOB.move_manager.move(effect, direction, step_delay, timeout = step_delay * step_amt, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
RegisterSignal(loop, COMSIG_QDELETING, PROC_REF(decrement_total_effect))
/datum/effect_system/proc/decrement_total_effect(datum/source)
diff --git a/code/game/objects/effects/effect_system/effects_explosion.dm b/code/game/objects/effects/effect_system/effects_explosion.dm
index f764f9fe241..4e95e72e7de 100644
--- a/code/game/objects/effects/effect_system/effects_explosion.dm
+++ b/code/game/objects/effects/effect_system/effects_explosion.dm
@@ -10,7 +10,7 @@
/obj/effect/particle_effect/expl_particles/LateInitialize()
var/step_amt = pick(25;1,50;2,100;3,200;4)
- var/datum/move_loop/loop = SSmove_manager.move(src, pick(GLOB.alldirs), 1, timeout = step_amt, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ var/datum/move_loop/loop = GLOB.move_manager.move(src, pick(GLOB.alldirs), 1, timeout = step_amt, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
RegisterSignal(loop, COMSIG_QDELETING, PROC_REF(end_particle))
/obj/effect/particle_effect/expl_particles/proc/end_particle(datum/source)
diff --git a/code/game/objects/effects/timestop.dm b/code/game/objects/effects/timestop.dm
index 40dd75fdc2a..5c0cfee785d 100644
--- a/code/game/objects/effects/timestop.dm
+++ b/code/game/objects/effects/timestop.dm
@@ -205,7 +205,7 @@
/datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/victim)
frozen_mobs += victim
victim.apply_status_effect(/datum/status_effect/time_stopped)
- SSmove_manager.stop_looping(victim) //stops them mid pathing even if they're stunimmune //This is really dumb
+ GLOB.move_manager.stop_looping(victim) //stops them mid pathing even if they're stunimmune //This is really dumb
/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mob(mob/living/victim)
victim.remove_status_effect(/datum/status_effect/time_stopped)
diff --git a/code/game/objects/minecart_system/minecart.dm b/code/game/objects/minecart_system/minecart.dm
index b8c91ec5128..bb7b638496d 100644
--- a/code/game/objects/minecart_system/minecart.dm
+++ b/code/game/objects/minecart_system/minecart.dm
@@ -279,7 +279,7 @@
AddElement(/datum/element/give_turf_traits, string_list(list(TRAIT_IMMERSE_STOPPED, TRAIT_CHASM_STOPPED)))
setDir(movedir)
- var/datum/move_loop/loop = SSmove_manager.move(src, dir, delay = calculate_delay(), subsystem = SSminecarts, flags = MOVEMENT_LOOP_START_FAST|MOVEMENT_LOOP_IGNORE_PRIORITY, move_loop_type = /datum/move_loop/minecart)
+ var/datum/move_loop/loop = GLOB.move_manager.move(src, dir, delay = calculate_delay(), subsystem = SSminecarts, flags = MOVEMENT_LOOP_START_FAST|MOVEMENT_LOOP_IGNORE_PRIORITY, move_loop_type = /datum/move_loop/minecart)
RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(check_rail))
RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(decay_momentum))
@@ -288,7 +288,7 @@
if(momentum <= 0)
stack_trace("Mine cart moving on 0 momentum!")
- SSmove_manager.stop_looping(src, SSminecarts)
+ GLOB.move_manager.stop_looping(src, SSminecarts)
obj_flags &= ~BLOCK_Z_OUT_DOWN
RemoveElement(/datum/element/give_turf_traits, string_list(list(TRAIT_IMMERSE_STOPPED, TRAIT_CHASM_STOPPED)))
momentum = 0
@@ -310,7 +310,7 @@
if(!located)
continue
setDir(get_dir(rail, located))
- var/datum/move_loop/minecart/loop = SSmove_manager.processing_on(src, SSminecarts)
+ var/datum/move_loop/minecart/loop = GLOB.move_manager.processing_on(src, SSminecarts)
loop.direction = get_dir(rail, located)
last_travelled_to = travel.aportalid
return MOVELOOP_SKIP_STEP
@@ -343,7 +343,7 @@
return NONE
// Can't go straight and cant turn = STOP
- SSmove_manager.stop_looping(src, SSminecarts)
+ GLOB.move_manager.stop_looping(src, SSminecarts)
obj_flags &= ~BLOCK_Z_OUT_DOWN
RemoveElement(/datum/element/give_turf_traits, string_list(list(TRAIT_IMMERSE_STOPPED, TRAIT_CHASM_STOPPED)))
if(momentum >= 12)
@@ -365,7 +365,7 @@
visible_message(span_notice("[src] comes to a sudden stop."))
else
visible_message(span_notice("[src] comes to a stop."))
- SSmove_manager.stop_looping(src, SSminecarts)
+ GLOB.move_manager.stop_looping(src, SSminecarts)
obj_flags &= ~BLOCK_Z_OUT_DOWN
RemoveElement(/datum/element/give_turf_traits, string_list(list(TRAIT_IMMERSE_STOPPED, TRAIT_CHASM_STOPPED)))
momentum = 0
@@ -375,7 +375,7 @@
// No more momentum = STOP
if(momentum <= 0)
- SSmove_manager.stop_looping(src, SSminecarts)
+ GLOB.move_manager.stop_looping(src, SSminecarts)
obj_flags &= ~BLOCK_Z_OUT_DOWN
RemoveElement(/datum/element/give_turf_traits, string_list(list(TRAIT_IMMERSE_STOPPED, TRAIT_CHASM_STOPPED)))
momentum = 0
@@ -383,7 +383,7 @@
return
// Handles slowing down the move loop / cart
- var/datum/move_loop/loop = SSmove_manager.processing_on(src, SSminecarts)
+ var/datum/move_loop/loop = GLOB.move_manager.processing_on(src, SSminecarts)
loop?.set_delay(calculate_delay())
/// Calculates how fast the cart is going
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index fc1080dddb0..9c326335428 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -202,13 +202,6 @@
/obj/get_dumping_location(datum/component/storage/source,mob/user)
return get_turf(src)
-/obj/proc/CanAStarPass(ID, to_dir, requester)
- if(ismovable(requester))
- var/atom/movable/AM = requester
- if(AM.pass_flags & pass_flags_self)
- return TRUE
- . = !density
-
/obj/proc/check_uplink_validity()
return 1
diff --git a/code/game/objects/structures/bridge.dm b/code/game/objects/structures/bridge.dm
index 34a2df3d600..e8a9562890a 100644
--- a/code/game/objects/structures/bridge.dm
+++ b/code/game/objects/structures/bridge.dm
@@ -8,6 +8,7 @@
max_integrity = 100
layer = ABOVE_OPEN_TURF_LAYER
obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN | BLOCK_Z_IN_UP
+ can_astar_pass = CANASTARPASS_ALWAYS_PROC
attacked_sound = list('sound/combat/hits/onwood/woodimpact (1).ogg','sound/combat/hits/onwood/woodimpact (2).ogg')
/// Remember initial sprite
@@ -76,7 +77,7 @@
leaving.Bump(src)
return COMPONENT_ATOM_BLOCK_EXIT
-/obj/structure/bridge/CanAStarPass(ID, to_dir, requester)
+/obj/structure/bridge/CanAStarPass(to_dir, datum/can_pass_info/pass_info)
if(to_dir != dir && to_dir != REVERSE_DIR(dir))
return FALSE
return TRUE
diff --git a/code/game/objects/structures/doors.dm b/code/game/objects/structures/doors.dm
index c3956c9e63b..bbbb8aba719 100644
--- a/code/game/objects/structures/doors.dm
+++ b/code/game/objects/structures/doors.dm
@@ -273,16 +273,21 @@
else
addtimer(CALLBACK(src, PROC_REF(Close), FALSE), delay)
-/obj/structure/door/CanAStarPass(ID, to_dir, datum/requester)
+/obj/structure/door/CanAStarPass(to_dir, datum/can_pass_info/pass_info)
. = ..()
if(.) // we can already go through it
return TRUE
+
if(!anchored)
return FALSE
- if(HAS_TRAIT(requester, TRAIT_BASHDOORS))
+
+ var/mob/living/requester = pass_info.requester_ref?.resolve()
+ if(istype(requester) && HAS_TRAIT(requester, TRAIT_BASHDOORS))
return TRUE // bash into it!
+
// it's openable
- return ishuman(requester) && !locked()
+
+ return pass_info.is_living && !locked()
/obj/structure/door/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
diff --git a/code/game/objects/structures/plastic_flaps.dm b/code/game/objects/structures/plastic_flaps.dm
index f42bf6ac1aa..6452a24d4aa 100644
--- a/code/game/objects/structures/plastic_flaps.dm
+++ b/code/game/objects/structures/plastic_flaps.dm
@@ -8,6 +8,7 @@
density = FALSE
anchored = TRUE
CanAtmosPass = ATMOS_PASS_NO
+ can_astar_pass = CANASTARPASS_ALWAYS_PROC
/obj/structure/plasticflaps/opaque
opacity = TRUE
@@ -29,14 +30,12 @@
return FALSE
return TRUE
-/obj/structure/plasticflaps/CanAStarPass(ID, to_dir, requester)
- if(isliving(requester))
- var/mob/living/M = requester
- if(!M.ventcrawler && M.mob_size != MOB_SIZE_TINY)
+/obj/structure/plasticflaps/CanAStarPass(to_dir, datum/can_pass_info/pass_info)
+ if(pass_info.is_living)
+ if(pass_info.mob_size != MOB_SIZE_TINY)
return FALSE
- var/atom/movable/M = requester
- if(M && M.pulling)
- return CanAStarPass(ID, to_dir, M.pulling)
+ if(pass_info.pulling_info)
+ return CanAStarPass(to_dir, pass_info.pulling_info)
return TRUE //diseases, stings, etc can pass
/obj/structure/plasticflaps/CanAllowThrough(atom/movable/A, turf/T)
diff --git a/code/game/objects/structures/putrid/actions/mob_actions/charge.dm b/code/game/objects/structures/putrid/actions/mob_actions/charge.dm
index e513c595623..0ab43fd2540 100644
--- a/code/game/objects/structures/putrid/actions/mob_actions/charge.dm
+++ b/code/game/objects/structures/putrid/actions/mob_actions/charge.dm
@@ -45,7 +45,7 @@
return
if(charger in charging)
- SSmove_manager.stop_looping(charger)
+ GLOB.move_manager.stop_looping(charger)
charging += charger
actively_moving = FALSE
@@ -59,7 +59,7 @@
playsound(charger, 'sound/misc/meteorimpact.ogg', 100, TRUE, 8, 0.9)
var/time_to_hit = min(get_dist(charger, target), charge_distance) * charge_speed
- var/datum/move_loop/new_loop = SSmove_manager.home_onto(charger, target, delay = charge_speed, timeout = time_to_hit, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ var/datum/move_loop/new_loop = GLOB.move_manager.home_onto(charger, target, delay = charge_speed, timeout = time_to_hit, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
if(!new_loop)
return
RegisterSignal(new_loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
diff --git a/code/game/objects/structures/putrid/actions/mob_actions/charge_slash.dm b/code/game/objects/structures/putrid/actions/mob_actions/charge_slash.dm
index 08bda92b304..b23051811f2 100644
--- a/code/game/objects/structures/putrid/actions/mob_actions/charge_slash.dm
+++ b/code/game/objects/structures/putrid/actions/mob_actions/charge_slash.dm
@@ -73,7 +73,7 @@
// Check if already charging
if(charger in charging)
- SSmove_manager.stop_looping(charger)
+ GLOB.move_manager.stop_looping(charger)
charging += charger
actively_moving = FALSE
@@ -92,7 +92,7 @@
playsound(charger, 'sound/misc/meteorimpact.ogg', 100, TRUE, 8, 0.9)
var/time_to_hit = min(get_dist(charger, target), charge_distance) * charge_speed
- var/datum/move_loop/new_loop = SSmove_manager.home_onto(charger, target, delay = charge_speed, timeout = time_to_hit, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ var/datum/move_loop/new_loop = GLOB.move_manager.home_onto(charger, target, delay = charge_speed, timeout = time_to_hit, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
if(!new_loop)
cleanup_charge(charger)
return
diff --git a/code/game/objects/structures/putrid/actions/mob_actions/triple_charge.dm b/code/game/objects/structures/putrid/actions/mob_actions/triple_charge.dm
index b85694eb97a..b610eab017c 100644
--- a/code/game/objects/structures/putrid/actions/mob_actions/triple_charge.dm
+++ b/code/game/objects/structures/putrid/actions/mob_actions/triple_charge.dm
@@ -49,7 +49,7 @@
return
if(charger in charging)
- SSmove_manager.stop_looping(charger)
+ GLOB.move_manager.stop_looping(charger)
charging += charger
actively_moving = FALSE
@@ -62,7 +62,7 @@
var/time_to_hit = min(get_dist(charger, target), charge_distance) * charge_speed
- var/datum/move_loop/new_loop = SSmove_manager.home_onto(charger, target, delay = charge_speed, timeout = time_to_hit, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ var/datum/move_loop/new_loop = GLOB.move_manager.home_onto(charger, target, delay = charge_speed, timeout = time_to_hit, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
if(!new_loop)
return
RegisterSignal(new_loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
diff --git a/code/game/objects/structures/putrid/structures/border.dm b/code/game/objects/structures/putrid/structures/border.dm
index 341e1628367..e8cff15ba8e 100644
--- a/code/game/objects/structures/putrid/structures/border.dm
+++ b/code/game/objects/structures/putrid/structures/border.dm
@@ -34,14 +34,3 @@
return TRUE
if(pass_throwing && mover.throwing)
return TRUE
-
-/obj/structure/meatvineborder/CanAStarPass(ID, to_dir, requester)
- if(dir in CORNERDIRS)
- return TRUE
- if(ismovable(requester))
- var/atom/movable/mover = requester
- if(mover.movement_type & (FLOATING|FLYING))
- return TRUE
- if(to_dir == dir)
- return FALSE
- return TRUE
diff --git a/code/game/objects/structures/putrid/structures/meatvine.dm b/code/game/objects/structures/putrid/structures/meatvine.dm
index 9005e7e288b..5c3588e19c7 100644
--- a/code/game/objects/structures/putrid/structures/meatvine.dm
+++ b/code/game/objects/structures/putrid/structures/meatvine.dm
@@ -21,20 +21,10 @@
smoothing_flags = SMOOTH_BITMASK | SMOOTH_OBJ
-
max_integrity = 50
resistance_flags = CAN_BE_HIT
is_full = TRUE
-
-/obj/structure/meatvine/heavy/CanPass(atom/movable/mover, turf/target)
- . = ..()
- return FALSE
-
-/obj/structure/meatvine/heavy/CanAStarPass(ID, to_dir, requester)
- return FALSE
-
-
/obj/structure/meatvine/lair
icon_state = "lair_1"
density = FALSE
diff --git a/code/game/objects/structures/stairs.dm b/code/game/objects/structures/stairs.dm
index 86930d12e01..874f810daff 100644
--- a/code/game/objects/structures/stairs.dm
+++ b/code/game/objects/structures/stairs.dm
@@ -193,7 +193,7 @@
falling.AdjustKnockdown(1.25 SECONDS)
falling.spin(1 SECONDS, 0.25 SECONDS)
falling.apply_damage(rand(4, 8), BRUTE, spread_damage = TRUE)
- SSmove_manager.move_towards(falling, get_ranged_target_turf(src, REVERSE_DIR(dir), 2), delay = 0.2 SECONDS, timeout = 0.5 SECONDS)
+ GLOB.move_manager.move_towards(falling, get_ranged_target_turf(src, REVERSE_DIR(dir), 2), delay = 0.2 SECONDS, timeout = 0.5 SECONDS)
/obj/structure/stairs/proc/isTerminator() //If this is the last stair in a chain and should move mobs up
if(terminator_mode != STAIR_TERMINATOR_AUTOMATIC)
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 9c85bd8007e..9a4142d07ea 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -90,12 +90,6 @@
if(locate(/obj/structure/table) in get_turf(mover))
return TRUE
-/obj/structure/table/CanAStarPass(ID, dir, requester)
- . = !density
- if(ismovableatom(requester))
- var/atom/movable/mover = requester
- . = . || (mover.pass_flags & PASSTABLE)
-
/obj/structure/table/proc/tableplace(mob/living/user, mob/living/pushed_mob)
pushed_mob.forceMove(loc)
pushed_mob.set_resting(TRUE, TRUE)
@@ -427,12 +421,6 @@
attacked_sound = list('sound/combat/hits/onwood/woodimpact (1).ogg','sound/combat/hits/onwood/woodimpact (2).ogg')
blade_dulling = DULLING_BASHCHOP
-/obj/structure/rack/CanAStarPass(ID, dir, requester)
- . = !density
- if(ismovableatom(requester))
- var/atom/movable/mover = requester
- . = . || (mover.pass_flags & PASSTABLE)
-
/obj/structure/rack/MouseDrop_T(obj/O, mob/user)
. = ..()
if ((!( istype(O, /obj/item) ) || user.get_active_held_item() != O))
diff --git a/code/game/rotational_objects/roller.dm b/code/game/rotational_objects/roller.dm
index 6b237fad692..1900fd763c2 100644
--- a/code/game/rotational_objects/roller.dm
+++ b/code/game/rotational_objects/roller.dm
@@ -132,7 +132,7 @@
if(QDELETED(moving))
return
- var/datum/move_loop/move/existing_loop = SSmove_manager.processing_on(moving, SSconveyors)
+ var/datum/move_loop/move/existing_loop = GLOB.move_manager.processing_on(moving, SSconveyors)
if(existing_loop)
existing_loop.direction = movedir
existing_loop.delay = get_move_delay()
@@ -146,7 +146,7 @@
/obj/structure/roller/proc/stop_conveying(atom/movable/thing)
if(!ismovable(thing))
return
- SSmove_manager.stop_looping(thing, SSconveyors)
+ GLOB.move_manager.stop_looping(thing, SSconveyors)
/obj/structure/roller/proc/conveyable_exit(datum/source, atom/convayable, direction)
SIGNAL_HANDLER
diff --git a/code/game/rotational_objects/rotational_speed_data.dm b/code/game/rotational_objects/rotational_speed_data.dm
index e8f05c73129..9f06f8d519e 100644
--- a/code/game/rotational_objects/rotational_speed_data.dm
+++ b/code/game/rotational_objects/rotational_speed_data.dm
@@ -65,10 +65,6 @@
new type(get_turf(src))
qdel(src)
-// You can path over a dense structure if it's climbable.
-/obj/structure/CanAStarPass(ID, to_dir, requester)
- . = climbable || ..()
-
/obj/structure/return_rotation_chat()
if(!rotation_network)
return
diff --git a/code/game/turfs/open/lava.dm b/code/game/turfs/open/lava.dm
index db6705d506c..e015f92d59c 100644
--- a/code/game/turfs/open/lava.dm
+++ b/code/game/turfs/open/lava.dm
@@ -22,6 +22,9 @@
smoothing_list = SMOOTH_GROUP_OPEN_FLOOR
neighborlay_self = "lavedge"
turf_flags = NONE
+
+ pathing_pass_method = TURF_PATHING_PASS_PROC
+
var/flow = FALSE
/// How much fire damage we deal to living mobs stepping on us
var/lava_damage = 50
@@ -104,6 +107,12 @@
/// Burns the target and makes the turf process (depending on the return value of do_burn()).
#define LAVA_BE_BURNING 2
+/turf/open/lava/CanAStarPass(to_dir, datum/can_pass_info/pass_info)
+ var/atom/movable/our_movable = pass_info.requester_ref.resolve()
+ if(!our_movable)
+ return
+ return can_cross_safely(our_movable)
+
/turf/open/lava/can_cross_safely(atom/movable/traveler)
return ..() && !can_burn_stuff(traveler) // can traverse safely if you won't burn in it
diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm
index bcc86cd3206..38abfddec2a 100644
--- a/code/game/turfs/open/openspace.dm
+++ b/code/game/turfs/open/openspace.dm
@@ -14,6 +14,8 @@
smoothing_list = SMOOTH_GROUP_OPEN_FLOOR + SMOOTH_GROUP_CLOSED_WALL
neighborlay_self = "staticedge"
+ pathing_pass_method = TURF_PATHING_PASS_PROC
+
/turf/open/openspace/Initialize() // handle plane and layer here so that they don't cover other obs/turfs in Dream Maker
. = ..()
RegisterSignal(src, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(on_atom_created))
@@ -57,6 +59,12 @@
return
zFall(movable)
+/turf/open/openspace/CanAStarPass(to_dir, datum/can_pass_info/pass_info)
+ var/atom/movable/our_movable = pass_info.requester_ref.resolve()
+ if(!our_movable)
+ return
+ return can_cross_safely(our_movable)
+
/turf/open/openspace/can_cross_safely(atom/movable/crossing)
return HAS_TRAIT(crossing, TRAIT_MOVE_FLYING) || !crossing.can_z_move(DOWN, src, z_move_flags = ZMOVE_FALL_FLAGS)
diff --git a/code/game/turfs/open/water.dm b/code/game/turfs/open/water.dm
index 4577e1c023e..69788eb8b05 100644
--- a/code/game/turfs/open/water.dm
+++ b/code/game/turfs/open/water.dm
@@ -356,14 +356,14 @@
if(conveyable.loc != src) // If we are not on the same turf (order of operations memes) go to hell
return
if(!river_current)
- SSmove_manager.stop_looping(conveyable, SSconveyors)
+ GLOB.move_manager.stop_looping(conveyable, SSconveyors)
return
start_conveying(conveyable)
/turf/open/water/proc/start_conveying(atom/movable/moving)
if(QDELETED(moving))
return
- var/datum/move_loop/move/moving_loop = SSmove_manager.processing_on(moving, SSconveyors)
+ var/datum/move_loop/move/moving_loop = GLOB.move_manager.processing_on(moving, SSconveyors)
var/current_direction = movedir || dir
var/speed = current_speed
if(HAS_TRAIT(moving, TRAIT_SWIMMER)) // more time to swim against the current
@@ -385,12 +385,12 @@
/turf/open/water/proc/conveyable_exit(atom/conveyable, direction)
var/turf/next_turf = get_step(src, direction)
if(conveyable.z != z || !iswaterturf(next_turf) || !isturf(conveyable.loc)) //If you've entered something on us, stop moving
- SSmove_manager.stop_looping(conveyable, SSconveyors)
+ GLOB.move_manager.stop_looping(conveyable, SSconveyors)
/turf/open/water/proc/stop_conveying(atom/movable/thing)
if(!ismovable(thing))
return
- SSmove_manager.stop_looping(thing, SSconveyors)
+ GLOB.move_manager.stop_looping(thing, SSconveyors)
/turf/open/water/examine(mob/user)
. = ..()
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 2eeee43e43e..e85ae923e78 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -3,6 +3,8 @@
level = 1
hover_color = "#607d65"
uses_integrity = TRUE
+ vis_flags = VIS_INHERIT_PLANE|VIS_INHERIT_ID
+ flags_1 = CAN_BE_DIRTY_1
luminosity = 1
var/intact = 1
@@ -20,7 +22,6 @@
var/blocks_air = FALSE
- flags_1 = CAN_BE_DIRTY_1
var/turf_flags = NONE
var/list/image/blueprint_data //for the station blueprints, images of objects eg: pipes
@@ -41,8 +42,6 @@
/// If we were going to smooth with an Atom instead overlay this onto self
var/neighborlay_self
- vis_flags = VIS_INHERIT_PLANE|VIS_INHERIT_ID
-
/// Uses colours defined by the monarch roundstart see [lordcolor.dm]
var/uses_lord_coloring = FALSE
@@ -51,6 +50,10 @@
///The typepath we use for lazy fishing on turfs, to save on world init time.
var/fish_source
+ /// How pathing algorithm will check if this turf is passable by itself (not including content checks). By default it's just density check.
+ /// WARNING: Currently to use a density shortcircuiting this does not support dense turfs with special allow through function
+ var/pathing_pass_method = TURF_PATHING_PASS_DENSITY
+
var/dynamic_lighting = TRUE
var/tmp/lighting_corners_initialised = FALSE
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 305a657d608..a03573d550e 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -1107,19 +1107,6 @@
SSdeath_arena.admin_reset()
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle LOOC", "[GLOB.ooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-/datum/admins/proc/toggle_debug_pathfinding()
- set category = "GameMaster"
- set desc="Pathfinding Debug"
- set name="Pathfinding Debug"
- var/mob/user = usr
- if(!user.client.holder)
- return
-
- if(!user.client.holder.path_debug)
- user.client.holder.path_debug = new(user.client.holder)
- else
- QDEL_NULL(user.client.holder.path_debug)
-
/datum/admins/proc/give_all_triumphs()
set category = "GameMaster.Triumphs"
set desc = "Triumph Giver"
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index a0722714bc1..adf0e7affa0 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -84,7 +84,6 @@ GLOBAL_PROTECT(admin_verbs_admin)
/datum/admins/proc/toggleoocdead, /*toggles ooc on/off for everyone who is dead*/
/datum/admins/proc/togglelooc,
/datum/admins/proc/fix_death_area,
- /datum/admins/proc/toggle_debug_pathfinding,
/datum/admins/proc/give_all_triumphs,
/client/proc/open_wave_creator,
/datum/admins/proc/toggleenter, /*toggles whether people can join the current game*/
@@ -236,6 +235,7 @@ GLOBAL_PROTECT(admin_verbs_debug)
/client/proc/cmd_regenerate_asset_cache,
/client/proc/cmd_clear_smart_asset_cache,
/client/proc/select_job_pack_debug,
+ /client/proc/debug_pathfinding,
)
GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, GLOBAL_PROC_REF(release)))
GLOBAL_PROTECT(admin_verbs_possess)
diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm
index 607789012a4..e9a24358902 100644
--- a/code/modules/admin/holder2.dm
+++ b/code/modules/admin/holder2.dm
@@ -71,7 +71,6 @@ GLOBAL_PROTECT(href_token)
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return QDEL_HINT_LETMELIVE
- QDEL_NULL(path_debug)
. = ..()
/datum/admins/proc/activate()
@@ -99,7 +98,6 @@ GLOBAL_PROTECT(href_token)
if ((C = owner) || (C = GLOB.directory[target]))
disassociate()
add_verb(C, /client/proc/readmin)
- QDEL_NULL(path_debug)
C?.native_say?.refresh_channels()
/datum/admins/proc/associate(client/C)
diff --git a/code/modules/admin/verbs/debug_pathfind.dm b/code/modules/admin/verbs/debug_pathfind.dm
new file mode 100644
index 00000000000..76bf129c6df
--- /dev/null
+++ b/code/modules/admin/verbs/debug_pathfind.dm
@@ -0,0 +1,164 @@
+
+#ifdef DEBUG_PATHFINDING
+
+GLOBAL_LIST(__pathfinding_debug_info)
+GLOBAL_VAR_INIT(__pathfinding_debug_generate, FALSE)
+// Start and clear the pathfinding debug info.
+#define START_PATH_DEBUG GLOB.__pathfinding_debug_generate = TRUE; GLOB.__pathfinding_debug_info = null
+#define STOP_PATH_DEBUG GLOB.__pathfinding_debug_generate = FALSE
+
+#else
+
+#define START_PATH_DEBUG
+#define STOP_PATH_DEBUG
+
+#endif
+
+/client/proc/debug_pathfinding()
+ set name = "Pathfind To..."
+ set category = "Debug"
+
+ if(!check_rights(R_DEBUG))
+ return
+
+ var/turf/T = usr.client.holder.marked_datum
+ if(!T)
+ to_chat(usr, span_warning("No turf marked."))
+ return
+ //If the user's marked datum is a turf, ask to use that instead of the click target.
+ else if(isturf(usr.client.holder.marked_datum))
+ T = (tgui_alert(
+ usr,
+ "Your marked datum, ([usr.client.holder.marked_datum]), is a turf. Do you want to path to that instead?",
+ "Marked Datum Query",
+ list("Yes", "No")) == "Yes") ? usr.client.holder.marked_datum : T
+
+ var/include_diagonals = tgui_alert(usr, "Include diagonals?", "Pathfind To...", list("Yes", "No")) == "Yes"
+
+ var/datum/callback/path_heuristic
+
+ #define CHEBYSHEV "Chebyshev - get_dist"
+ #define EUCLIDEAN "Euclidean - get_dist_euclidean"
+ #define MANHATTAN "Manhattan - get_dist_manhattan"
+ #define OCTILE "Octile - get_dist_octile - Experimental"
+ path_heuristic = tgui_input_list(usr, "Choose Heuristic", "Cost Judgement",
+ list(
+ CHEBYSHEV,
+ EUCLIDEAN,
+ MANHATTAN,
+ OCTILE
+ )
+ )
+ switch(path_heuristic)
+ if(CHEBYSHEV)
+ path_heuristic = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(_get_dist)) //Uses the SDQL2 wrapper for Internal Reasons:tm:
+ if(EUCLIDEAN)
+ path_heuristic = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(get_dist_euclidean))
+ if(MANHATTAN)
+ path_heuristic = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(get_dist_manhattan))
+ if(OCTILE)
+ path_heuristic = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(get_dist_octile))
+ else
+ CRASH("Debug pathfind ended up with invalid heuristic: [path_heuristic]")
+
+ #undef CHEBYSHEV
+ #undef EUCLIDEAN
+ #undef MANHATTAN
+ #undef OCTILE
+
+ var/static/list/images
+ var/list/path
+
+ var/list/access = null
+
+ var/time = world.timeofday
+ START_PATH_DEBUG //Enable node list dumping.
+
+ path = SSpathfinder.astar_pathfind_now(usr, T, 100, 0, access, null, FALSE, include_diagonals, path_heuristic)
+
+ STOP_PATH_DEBUG //Disable node list dumping.
+
+ if(!length(path))
+ to_chat(usr, span_alert("Pathfinding could not be completed."))
+ return
+
+ to_chat(usr, span_info("Path length [length(path)] took [(world.timeofday - time) / 10] seconds to complete."))
+
+ LAZYINITLIST(images)
+ if(length(images))
+ for(var/image/I as anything in images)
+ usr.client.images -= I
+
+ for(var/index in 1 to length(path))
+ var/turf/current_turf = path[index]
+ var/image/path_display
+ if(index == 1)
+ path_display = image('icons/turf/floors.dmi', current_turf, "pure_white")
+ path_display.plane = ABOVE_LIGHTING_PLANE
+ path_display.alpha = 200
+ path_display.color = "#FF0000"
+ path_display.layer = 2 // Render above whiteness, but below maptext.
+
+ else if(index == length(path))
+ path_display = image('icons/turf/floors.dmi', current_turf, "pure_white")
+ path_display.plane = ABOVE_LIGHTING_PLANE
+ path_display.alpha = 200
+ path_display.color = "#00FF00"
+ path_display.layer = 2 // Render above whiteness, but below maptext.
+
+ else
+ path_display = image('icons/effects/navigation.dmi', current_turf)
+ path_display.plane = ABOVE_LIGHTING_PLANE
+ path_display.color = COLOR_BLUE //So it stands out better against the white.
+ path_display.alpha = 200
+
+ var/turf/turf_ahead = path[index+1]
+ var/turf/turf_behind = path[index-1]
+ var/dir_1 = 0
+ var/dir_2 = 0
+
+ dir_1 = turn(angle2dir(get_angle(turf_ahead, current_turf)), 180)
+ dir_2 = turn(angle2dir(get_angle(turf_behind, current_turf)), 180)
+ if(dir_1 > dir_2)
+ dir_1 = dir_2
+ dir_2 = turn(angle2dir(get_angle(turf_ahead, current_turf)), 180)
+
+ path_display.icon_state = "[dir_1]-[dir_2]"
+ #ifndef DEBUG_PATHFINDING //Disables this for hygiene.
+ path_display.maptext = MAPTEXT("[index]|[get_dist_euclidean(current_turf, T)]")
+ #else
+ path_display.alpha = 255 //Make it solid if we're doing debug rendering.
+ path_display.layer = 2 // Render above whiteness, but below maptext.
+ #endif
+
+
+ images += path_display
+ usr.client.images += path_display
+#ifndef DEBUG_PATHFINDING
+ return
+#else
+ if(!GLOB.__pathfinding_debug_info)
+ return
+ var/list/__pathfinding_debug_info = GLOB.__pathfinding_debug_info
+ to_chat(usr, span_info("Debug Enabled: Nodes Passed: [length(__pathfinding_debug_info)]"))
+ //I'm going to just access kapu's stuff by number.
+ for(var/datum/astar_node/current_node in __pathfinding_debug_info)
+ var/image/node_display = image('icons/turf/floors.dmi', current_node.turf, "pure_white")
+ node_display.plane = ABOVE_LIGHTING_PLANE
+ node_display.alpha = 200
+ node_display.layer = 1 //Render on the very bottom.
+
+ images += node_display
+ usr.client.images += node_display
+ // Draw a second, empty image just for the maptext.
+ node_display = image('icons/turf/floors.dmi', current_node.turf, "invisible")
+ node_display.maptext = MAPTEXT("H:[truncate("[current_node.heuristic_h]",5)]
G:[truncate("[current_node.dist_from_start_g]",5)]
F:[truncate("[current_node.total_cost_f]",5)]")
+ node_display.maptext_width = 64
+ node_display.plane = ABOVE_LIGHTING_PLANE
+ node_display.layer = 3 // Render above everything else.
+ images += node_display
+ usr.client.images += node_display
+#endif
+
+#undef START_PATH_DEBUG
+#undef STOP_PATH_DEBUG
diff --git a/code/modules/antagonists/villain/neu_vampires/covens/coven_powers/presence.dm b/code/modules/antagonists/villain/neu_vampires/covens/coven_powers/presence.dm
index 48053a12929..54d08822b3d 100644
--- a/code/modules/antagonists/villain/neu_vampires/covens/coven_powers/presence.dm
+++ b/code/modules/antagonists/villain/neu_vampires/covens/coven_powers/presence.dm
@@ -99,7 +99,7 @@
target.remove_overlay(MUTATIONS_LAYER)
/mob/living/carbon/human/proc/step_away_caster(mob/living/step_from)
- walk(src, 0)
+ GLOB.move_manager.stop_looping(src)
if(!CheckFrenzyMove())
set_glide_size(DELAY_TO_GLIDE_SIZE(total_multiplicative_slowdown()))
step_away(src, step_from, 99)
@@ -207,7 +207,7 @@
target.remove_overlay(MUTATIONS_LAYER)
/mob/living/carbon/human/proc/step_toward_caster(mob/living/step_to)
- walk(src, 0)
+ GLOB.move_manager.stop_looping(src)
if(!CheckFrenzyMove())
set_glide_size(DELAY_TO_GLIDE_SIZE(total_multiplicative_slowdown()))
step_towards(src, step_to, 99)
diff --git a/code/modules/antagonists/villain/neu_vampires/covens/coven_powers/siren.dm b/code/modules/antagonists/villain/neu_vampires/covens/coven_powers/siren.dm
index e7fcdeb6165..57f9dbd8ac1 100644
--- a/code/modules/antagonists/villain/neu_vampires/covens/coven_powers/siren.dm
+++ b/code/modules/antagonists/villain/neu_vampires/covens/coven_powers/siren.dm
@@ -142,13 +142,13 @@
// Only regenerate path if we've moved to a different position or don't have a cached path
if(!walk_to_cached_path || walk_to_last_pos != current_pos)
- walk_to_cached_path = get_path_to(src, target_pos, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), 33, 250, 1)
+ walk_to_cached_path = astar_path_to(src, target_pos, max_steps = 100)
walk_to_last_pos = current_pos
var/moved = FALSE
if(length(walk_to_cached_path))
- walk(src, 0) // Stop any existing walk
+ GLOB.move_manager.stop_looping(src)
set_glide_size(DELAY_TO_GLIDE_SIZE(total_multiplicative_slowdown()))
step_to(src, walk_to_cached_path[1], 0)
face_atom(walk_to_target)
@@ -156,7 +156,7 @@
walk_to_cached_path.Cut(1, 2)
moved = TRUE
else
- walk_towards(src, walk_to_target, 0, total_multiplicative_slowdown())
+ GLOB.move_manager.home_onto(src, walk_to_target, total_multiplicative_slowdown())
moved = TRUE
if(moved)
@@ -166,7 +166,7 @@
/mob/living/carbon/human/proc/remove_walk_to_trait()
REMOVE_TRAIT(src, TRAIT_MOVEMENT_BLOCKED, VAMPIRE_TRAIT)
- walk(src, 0) // Stop any walking
+ GLOB.move_manager.stop_looping(src)
walk_to_target = null
walk_to_duration = 0
walk_to_steps_taken = 0
diff --git a/code/modules/antagonists/villain/neu_vampires/frenzy.dm b/code/modules/antagonists/villain/neu_vampires/frenzy.dm
index 6c72a255a05..0b9ab71e483 100644
--- a/code/modules/antagonists/villain/neu_vampires/frenzy.dm
+++ b/code/modules/antagonists/villain/neu_vampires/frenzy.dm
@@ -202,11 +202,11 @@
// Only regenerate path if we've moved to a different position or don't have a cached path
if(!frenzy_cached_path || frenzy_last_pos != current_pos)
- frenzy_cached_path = get_path_to(src, target_pos, TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), 33, 250, 1)
+ frenzy_cached_path = astar_path_to(src, target_pos, max_steps = 100)
frenzy_last_pos = current_pos
if(length(frenzy_cached_path))
- walk(src, 0) // Stop any existing walk
+ GLOB.move_manager.stop_looping(src)
step_to(src, frenzy_cached_path[1], 0)
frenzy_cached_path.Cut(1, 2)
else
diff --git a/code/modules/crafting/alchemy/essence_machines/essence_gauntlet/spells/breeze.dm b/code/modules/crafting/alchemy/essence_machines/essence_gauntlet/spells/breeze.dm
index ebd208528a3..5b08f7b94bf 100644
--- a/code/modules/crafting/alchemy/essence_machines/essence_gauntlet/spells/breeze.dm
+++ b/code/modules/crafting/alchemy/essence_machines/essence_gauntlet/spells/breeze.dm
@@ -21,7 +21,7 @@
for(var/obj/item/I in target_turf)
if(I.w_class <= WEIGHT_CLASS_SMALL && prob(50))
- SSmove_manager.move_rand(I, timeout = 3 SECONDS)
+ GLOB.move_manager.move_rand(I, timeout = 3 SECONDS)
/datum/action/cooldown/spell/essence/breeze/spell
name = "Breeze"
diff --git a/code/modules/mob/living/carbon/spirit/combat.dm b/code/modules/mob/living/carbon/spirit/combat.dm
deleted file mode 100644
index 4cc1dc78b3a..00000000000
--- a/code/modules/mob/living/carbon/spirit/combat.dm
+++ /dev/null
@@ -1,148 +0,0 @@
-#define MAX_RANGE_FIND 32
-
-/mob/living/carbon/spirit
- var/aggressive=0 // set to 1 using VV for an angry monkey
- var/frustration=0
- var/pickupTimer=0
- var/list/enemies = list()
- var/mob/living/target
- var/obj/item/pickupTarget
- var/mode = MONKEY_IDLE
- var/list/myPath = list()
- var/list/blacklistItems = list()
- var/maxStepsTick = 6
- var/best_force = 0
- var/martial_art = new/datum/martial_art
- var/resisting = FALSE
- var/pickpocketing = FALSE
- var/disposing_body = FALSE
- var/obj/machinery/disposal/bodyDisposal = null
- var/next_battle_screech = 0
- var/battle_screech_cooldown = 50
-
-/mob/living/carbon/spirit/proc/IsStandingStill()
- return resisting || pickpocketing || disposing_body
-
-// blocks
-// taken from /mob/living/carbon/human/interactive/
-/mob/living/carbon/spirit/proc/walk2derpless(target)
- if(!target || IsStandingStill())
- return 0
-
- if(myPath.len <= 0)
- myPath = get_path_to(src, get_turf(target), TYPE_PROC_REF(/turf, Heuristic_cardinal_3d), MAX_RANGE_FIND + 1, 250,1)
-
- if(myPath)
- if(myPath.len > 0)
- for(var/i = 0; i < maxStepsTick; ++i)
- if(!IsDeadOrIncap())
- if(myPath.len >= 1)
- walk_to(src,myPath[1],0,5)
- myPath -= myPath[1]
- return 1
-
- // failed to path correctly so just try to head straight for a bit
- walk_to(src,get_turf(target),0,5)
- sleep(1)
- walk_to(src,0)
-
- return 0
-
-// taken from /mob/living/carbon/human/interactive/
-/mob/living/carbon/spirit/proc/IsDeadOrIncap(checkDead = TRUE)
- // if(!(mobility_flags & MOBILITY_FLAGS_INTERACTION))
- // return 1
- if(health <= 0 && checkDead)
- return 1
- if(HAS_TRAIT(src, TRAIT_INCAPACITATED))
- return 1
- if(stat)
- return 1
- return 0
-/mob/living/carbon/spirit/proc/equip_item(obj/item/I)
- if(I.loc == src)
- return TRUE
-
- if(I.anchored)
- blacklistItems[I] ++
- return FALSE
-
- // WEAPONS
- if(istype(I, /obj/item))
- var/obj/item/W = I
- if(W.force >= best_force)
- put_in_hands(W)
- best_force = W.force
- return TRUE
-
- // CLOTHING
- else if(istype(I, /obj/item/clothing))
- var/obj/item/clothing/C = I
- monkeyDrop(C)
- addtimer(CALLBACK(src, PROC_REF(pickup_and_wear), C), 5)
- return TRUE
-
- // EVERYTHING ELSE
- else
- if(!get_item_for_held_index(1) || !get_item_for_held_index(2))
- put_in_hands(I)
- return TRUE
-
- blacklistItems[I] ++
- return FALSE
-
-/mob/living/carbon/spirit/proc/pickup_and_wear(obj/item/clothing/C)
- if(!equip_to_appropriate_slot(C))
- monkeyDrop(get_item_by_slot(C)) // remove the existing item if worn
- addtimer(CALLBACK(src, PROC_REF(equip_to_appropriate_slot), C), 5)
-
-/mob/living/carbon/spirit/resist_restraints(instant = FALSE)
- var/obj/item/I = null
- if(handcuffed)
- I = handcuffed
- else if(legcuffed)
- I = legcuffed
- if(I)
- changeNext_move(CLICK_CD_BREAKOUT)
- last_special = world.time + CLICK_CD_BREAKOUT
- cuff_resist(I)
-
-/mob/living/carbon/spirit/proc/should_target(mob/living/L)
- if(HAS_TRAIT(src, TRAIT_PACIFISM))
- return FALSE
-
- if(enemies[L])
- return TRUE
-
- // target non-monkey mobs when aggressive, with a small probability of monkey v monkey
- if(aggressive && (!istype(L, /mob/living/carbon/spirit/) || prob(MONKEY_AGGRESSIVE_MVM_PROB)))
- return TRUE
-
- return FALSE
-
-/mob/living/carbon/spirit/proc/back_to_idle()
-
- if(pulling)
- stop_pulling()
-
- mode = MONKEY_IDLE
- target = null
- a_intent = INTENT_HELP
- frustration = 0
- walk_to(src,0)
-
-/mob/living/carbon/spirit/Crossed(atom/movable/AM)
- if(!IsDeadOrIncap() && ismob(AM) && target)
- var/mob/living/carbon/spirit/M = AM
- if(!istype(M) || !M)
- return
- knockOver(M)
- return
- ..()
-
-/mob/living/carbon/spirit/proc/monkeyDrop(obj/item/A)
- if(A)
- dropItemToGround(A, TRUE)
- update_icons()
-
-#undef MAX_RANGE_FIND
diff --git a/code/modules/mob/living/carbon/spirit/death.dm b/code/modules/mob/living/carbon/spirit/death.dm
deleted file mode 100644
index 59c8cf540a1..00000000000
--- a/code/modules/mob/living/carbon/spirit/death.dm
+++ /dev/null
@@ -1,9 +0,0 @@
-/mob/living/carbon/spirit/gib_animation()
- new /obj/effect/temp_visual/gib_animation(loc, "gibbed-m")
-
-/mob/living/carbon/spirit/dust_animation()
- new /obj/effect/temp_visual/dust_animation(loc, "dust-m")
-
-/mob/living/carbon/spirit/death(gibbed)
- walk(src,0) // Stops dead monkeys from fleeing their attacker or climbing out from inside His Grace
- . = ..()
diff --git a/code/modules/mob/living/init_signals.dm b/code/modules/mob/living/init_signals.dm
index ef3f377971b..edbbe268681 100644
--- a/code/modules/mob/living/init_signals.dm
+++ b/code/modules/mob/living/init_signals.dm
@@ -70,7 +70,7 @@
SIGNAL_HANDLER
mobility_flags &= ~MOBILITY_MOVE
if(living_flags & MOVES_ON_ITS_OWN)
- walk(src, 0) //stop mid walk
+ GLOB.move_manager.stop_looping(src)
///Called when TRAIT_IMMOBILIZED is removed from the mob.
/mob/living/proc/on_immobilized_trait_loss(datum/source)
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index aa91c1922ad..5a77ef9e960 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -69,7 +69,7 @@
/mob/living/simple_animal/hostile/Life()
. = ..()
if(!.) //dead
- walk(src, 0) //stops walking
+ GLOB.move_manager.stop_looping(src)
return 0
/mob/living/proc/AttackingTarget(mob/living/passed_target)
diff --git a/icons/effects/navigation.dmi b/icons/effects/navigation.dmi
new file mode 100644
index 00000000000..523947a3e7c
Binary files /dev/null and b/icons/effects/navigation.dmi differ
diff --git a/tools/dme-sorter.com b/tools/dme-sorter.com
new file mode 100644
index 00000000000..96d3a96614e
Binary files /dev/null and b/tools/dme-sorter.com differ
diff --git a/vanderlin.dme b/vanderlin.dme
index b772798c9e2..726c67039f7 100644
--- a/vanderlin.dme
+++ b/vanderlin.dme
@@ -148,6 +148,7 @@
#include "code\__DEFINES\pain.dm"
#include "code\__DEFINES\particle_weather.dm"
#include "code\__DEFINES\particles.dm"
+#include "code\__DEFINES\path.dm"
#include "code\__DEFINES\patreon.dm"
#include "code\__DEFINES\plant_genetics.dm"
#include "code\__DEFINES\plexora.dm"
@@ -359,7 +360,10 @@
#include "code\__HELPERS\verbs.dm"
#include "code\__HELPERS\view.dm"
#include "code\__HELPERS\weakref.dm"
-#include "code\__HELPERS\paths\mapping_helpers.dm"
+#include "code\__HELPERS\paths\_path.dm"
+#include "code\__HELPERS\paths\path_debug.dm"
+#include "code\__HELPERS\paths\path_helpers.dm"
+#include "code\__HELPERS\paths\astar\path_astar.dm"
#include "code\__HELPERS\sorts\helpers.dm"
#include "code\__HELPERS\sorts\sort_instances.dm"
#include "code\_globalvars\admin.dm"
@@ -571,11 +575,15 @@
#include "code\controllers\subsystem\mobs\_mobs.dm"
#include "code\controllers\subsystem\mobs\client_mobs.dm"
#include "code\controllers\subsystem\mobs\tomb_mobs.dm"
+#include "code\controllers\subsystem\movement\ai_movement.dm"
#include "code\controllers\subsystem\movement\conveyors.dm"
#include "code\controllers\subsystem\movement\minecarts.dm"
#include "code\controllers\subsystem\movement\move_manager.dm"
#include "code\controllers\subsystem\movement\movement_loop.dm"
#include "code\controllers\subsystem\movement\movement_types.dm"
+#include "code\controllers\subsystem\movement\movment_loops\astar.dm"
+#include "code\controllers\subsystem\movement\movment_loops\hybrid.dm"
+#include "code\controllers\subsystem\movement\movment_loops\minecart.dm"
#include "code\controllers\subsystem\objective_quests\objective_quests.dm"
#include "code\controllers\subsystem\objective_quests\drivers\_base_quest_driver.dm"
#include "code\controllers\subsystem\objective_quests\drivers\mess.dm"
@@ -587,7 +595,6 @@
#include "code\controllers\subsystem\processing\aggro.dm"
#include "code\controllers\subsystem\processing\ai_avoidance.dm"
#include "code\controllers\subsystem\processing\ai_behaviors.dm"
-#include "code\controllers\subsystem\processing\ai_movement.dm"
#include "code\controllers\subsystem\processing\anvil_minigame.dm"
#include "code\controllers\subsystem\processing\enchantment.dm"
#include "code\controllers\subsystem\processing\faster_object.dm"
@@ -714,11 +721,12 @@
#include "code\datums\ai\_ai_behaviour.dm"
#include "code\datums\ai\_ai_controller.dm"
#include "code\datums\ai\_ai_idle_behavior.dm"
+#include "code\datums\ai\_ai_planning_subtree.dm"
#include "code\datums\ai\ai_movement\_ai_movement.dm"
-#include "code\datums\ai\ai_movement\_ai_planning_subtree.dm"
-#include "code\datums\ai\ai_movement\astar_movement.dm"
-#include "code\datums\ai\ai_movement\basic_avoidance.dm"
-#include "code\datums\ai\ai_movement\dumb_movement.dm"
+#include "code\datums\ai\ai_movement\ai_movement_adaptive.dm"
+#include "code\datums\ai\ai_movement\ai_movement_astar.dm"
+#include "code\datums\ai\ai_movement\ai_movement_basic_advoidance.dm"
+#include "code\datums\ai\ai_movement\ai_movement_dumb.dm"
#include "code\datums\ai\ai_movement\hybrid_movement.dm"
#include "code\datums\ai\ai_movement\hybrid_putrid.dm"
#include "code\datums\ai\behaviours\ambush.dm"
@@ -1279,7 +1287,6 @@
#include "code\datums\culture\_culture.dm"
#include "code\datums\culture\species.dm"
#include "code\datums\culture\universal.dm"
-#include "code\datums\debug\path_debug.dm"
#include "code\datums\dungeon_generator\dungeon_decent.dm"
#include "code\datums\dungeon_generator\dungeon_direction_helpers.dm"
#include "code\datums\dungeon_generator\dungeon_entry_exit.dm"
@@ -2035,7 +2042,6 @@
#include "code\game\objects\effects\decals\decal.dm"
#include "code\game\objects\effects\decals\misc.dm"
#include "code\game\objects\effects\decals\remains.dm"
-#include "code\game\objects\effects\decals\cleanable\aliens.dm"
#include "code\game\objects\effects\decals\cleanable\food.dm"
#include "code\game\objects\effects\decals\cleanable\humans.dm"
#include "code\game\objects\effects\decals\cleanable\misc.dm"
@@ -2513,6 +2519,7 @@
#include "code\modules\admin\verbs\commandreport.dm"
#include "code\modules\admin\verbs\deadsay.dm"
#include "code\modules\admin\verbs\debug.dm"
+#include "code\modules\admin\verbs\debug_pathfind.dm"
#include "code\modules\admin\verbs\diagnostics.dm"
#include "code\modules\admin\verbs\fps.dm"
#include "code\modules\admin\verbs\getlogs.dm"
@@ -3956,8 +3963,6 @@
#include "code\modules\mob\living\carbon\human\voicepacks\other\skeleton.dm"
#include "code\modules\mob\living\carbon\human\voicepacks\other\werewolf.dm"
#include "code\modules\mob\living\carbon\human\voicepacks\other\zombie.dm"
-#include "code\modules\mob\living\carbon\spirit\combat.dm"
-#include "code\modules\mob\living\carbon\spirit\death.dm"
#include "code\modules\mob\living\carbon\spirit\inventory.dm"
#include "code\modules\mob\living\carbon\spirit\life.dm"
#include "code\modules\mob\living\carbon\spirit\spirit.dm"