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"