• You've discovered RedGuides, an EverQuest multi-boxing and scripting community 🧙‍♀️⚙️. We want you to play several EQ characters at once, come join us and say hello! 👋

  • A TLP without truebox has thawed (Very Vanilla ready)
    Frostreaver

Fork / Mod Sort mobs to pull by MQ2Nav pathlength rather than distance from camp

joojoobee

A Member to Remember
Creator
Joined
May 15, 2016
RedCents
4,327¢
When using MQ2Nav to pull, I thought sorting mobs to pull by the distance puller has to run would be more efficient than the distance of the mob from the camp. This should help stop KA going after mobs that are a short distance away but are in fact behind walls or a long way through corridors.

So I made some changes to KissAssist. I've only run it a few times in one pull spot, but it seems to be working. Thanks to Brainiac for the hint on ${Navigation.PathLength[X Y Z]} .

VERSION 2. Stupid copy/paste error.
Starting at line 5260 in kissassist version 9.1.8... insert the following (green is inserted code, red is commented)
Rich (BB code):
  |/for i ${f1} to ${PullCount}

  | Sort by path-lengths for MQ2Nav
    /declare PathArray[50]  		int      	local     0
    /declare PathDistance[50]  	float 		local     0
    /declare Smallsub 					int      	local     0
    /declare tempSortID 				int 			local 		0
    /declare tempSortDist 			float 		local 		0
    /declare i2 int local 0

    /varset PullCount ${SpawnCount[npc loc ${CampXLoc} ${CampYLoc} radius ${MaxRadius} zradius ${MaxZRange} targetable noalert 1]}
    |/echo Pullcount is:  ${PullCount}  range is: ${MaxRadius}

    /for i 1 to ${PullCount}
      /varset PathArray[${i}] ${NearestSpawn[${i}, npc loc ${CampXLoc} ${CampYLoc} radius ${MaxRadius} zradius ${MaxZRange} targetable noalert 1].ID}
      /varset PullMob	${PathArray[${i}]}
      /varset PathDistance[${i}] ${Navigation.PathLength[${Spawn[${PullMob}].X} ${Spawn[${PullMob}].Y} ${Spawn[${PullMob}].Z}]}
    /next i

    /for i 1 to ${PullCount}
      /varset Smallsub ${i}
      /varset i2 ${Math.Calc[${i} + 1]}
      /for j ${i2} to ${PullCount}
        /if (${PathDistance[${j}]} < ${PathDistance[${Smallsub}]}) /varset Smallsub ${j}
      /next j
      /varset tempSortID ${PathArray[${i}]}
      /varset tempSortDist ${PathDistance[${i}]}
      /varset PathArray[${i}] ${PathArray[${Smallsub}]}
      /varset PathArray[${Smallsub}] ${tempSortID}
      /varset PathDistance[${i}] ${PathDistance[${Smallsub}]}
      /varset PathDistance[${Smallsub}] ${tempSortDist}
    /next i

    /for i 1 to ${PullCount}
      /if (${DebugPull}) /echo \atDEBUGPULL FindMobToPull 1.0:  PullFlag: ${PullFlag} Pullcount: ${PullCount} \agLine#: ${Macro.CurLine}
      /if (${Navigation.MeshLoaded} && ${PullMoveUse.Equal[nav]}) {
      |/varset PullMob ${NearestSpawn[${i}, npc loc ${CampXLoc} ${CampYLoc} radius ${MaxRadius} zradius ${MaxZRange} targetable noalert 1].ID}
        /varset PullMob ${PathArray[${i}]}
        /varset PullMobName ${Spawn[id ${PullMob}].CleanName}
 
Last edited:
This is interesting. I didn't even know MQ2Nav could report path length. We will explore this code this week.
 
Will have to give this a shot when I get home! I did notice in droga I will run after a mob quite a bit away from myself when there are ones closer. Only for the simple fact that it was closest via in distance but not distance needing to be traveled.
 
Maskoi-- Right now I am using MQ2 to do the sort. Not that much CPU overhead, but probably best something that is put in the spawnfilter itself in C or C++ as a Spawn and NearestSpawn filter option. For the sorting I used "selection sort", which is reasonably efficient for sorting, though there are better out there (http://faculty.cs.niu.edu/~hutchins/csci241/sorting.htm). Not sure worth investing time in Timsort (https://en.wikipedia.org/wiki/Timsort) or other "supernatural" sort algorithms given the small sort stacks and infrequent calls on this sort (only during deciding which pulls).
 
The only issue here is that MQ2Nav is not 100% perfect on its meshes. While it may show a path you might not necessarily be able to run it.

Occurs mainly with ramps and stairs. MQ2nav almost completely fails in POK trying to traverse to the library.

Also Doors are an issue if the are between you and the mob. This might be able to be compensated for though in the macro. Brainiac is redoing doors so hopefully he add an open door in path ability.

From your other thread though and a large enough array and playing a little bumper cars. We may be able to fine tune areas within the pull radius in the macro by white and black listing x y z points.
 
MQ2Nav does detect doors though right? If it detects a door or obstacle on a path we could just ignore that path. At least until we find a good way to handle it. I will stat creeping on the MQ2Nav github and see what I can find out.
 
It would be good if MQ2nav could OUTPUT the path it plans to use into an available array. Then, each path along the route could be real-time tested for LOS. If there's a door, it could either throw out the path OR it could create a bisected path that stops short of the door... moves to the door... opens any "nearby" doors.. and completes the path.
 
That would be awesome. If MQ2Nav could do that.
It could even be sent along to MQ2moveutils or a custom macro move routine.
 
if I recall rightly there are a few doors MQ2Nav doesn't see ( think Maskoi posted some in DemiPoLife TBM ) unless that got fixed
 
So it looks like we could modify MQ2Nav to do what we want.

At line 434 he begins to draw our path overlay point by point. This could easily be modified for what we want in a new routine. Have it plot the path point by point like it does to draw it but also check LoS to our end target. You could even have it color over the path for debugging. This way you see where it believes LoS is and where he thinks the rest of the path is.

Lines: 392-475
https://github.com/brainiac/MQ2Nav/blob/master/NavigationPath.cpp#L392-L475
 
I am downloading the solution and seeing if I can compile it, then start mucking with it. Have to brush up on my C and C++.

JJB
 
For those who downloaded this or edited, I made a stupid copy/paste error. Inserted back the line:

/varset PullMob ${PathArray[${i}]} in the first for:next loop.

Without that, it basically is functionally equivalent to the "old" pull routine.

"oops"
JJB

Checked this out and it's working. First column below are pathlengths ordered by NearestSpawn... second is ordered by pathlength. There's a small bug in the sort that is zeroing out the last element of the array, but the concept is functional and the last element of the array is irrelevant for this purpose. I will track the bug down.

Rich (BB code):
Pathlength ordered by "as the crow flies"			Pathlength ordered by "Pathlength"
47.85						|			 47.85
52.86						|			 52.86
63.85						|			 63.85
66.05						|			 66.05
287.21						|			132.9
219.34						|			141.47
293.48						|			141.58
150.95						|			143.58
132.9						|			150.95
158.1						|			157.6
141.58						|			158.1
141.47						|			163.27
143.58						|			168.9
157.6						|			192.8
163.27						|			197.97
168.9						|			205.26
192.8						|			211.13
211.13						|			219.34
197.97						|			224.58
224.58						|			224.92
224.92						|			229.79
205.26						|			247.05
247.05						|			254.09
229.79						|			263.8
254.09						|			287.21
263.8						|			293.48
295.25						|			295.25
306.5						|			306.5
331.02						|			315.79
315.79						|			323.95
323.95						|			331.02
331.22						|			331.22
337.54						|			337.54
358.64						|			358.64
364.79						|			364.79
495.89						|			495.89
596.18						|			574.91
596.36						|			589.66
574.91						|			591.01
610.16						|			596.18
589.66						|			596.36
591.01						|				0
 
Last edited:
Uh, you guys lose me when you start talking about C/C++, but even I can see that if this works it'll be huge! Thanks!
 
The TLO based path query shouldn't give you paths lengths for things it can't generate paths to (you or the target/point) are off mesh). I think that's how it should work. Let me know if it isn't.

The TLO query takes the same arguments as the /nav command so there's a bit of freedom there. I can look at adding support for querying individual points of the path as well. There is some rudimentary support doors but it needs a better solution that I'm working on. I think long term plans for doors are best left to the navigation plugin.
 
This was what I was trying to figure out. So the X/Y/Z MQ2Nav is using are relative to the mesh and not X/Y/Z in game?

On another note we could alter the pulling method slightly. I haven't messed with that part of the code yet but.. Couldn't we check in a loop ${Target.Distance} vs Our pull range and ${Target.LineOfSight}? Once we have both of those /nav stop and pull?
 
I have no clue of the minutia of what you guys are talking about in terms of code, but this sounds awesome in terms of laymen terms. I'll be hitting the Thanks buttons soon as I'm able too! Maybe it will convince me to actually migrate to Kiss Assist as well! This sounds amazing.
 
I have incorporated this into Kiss. it seems to working fine. It will be in the next release.
 
Cleaned it up so people won't report errors from console.
Rich (BB code):
Sub FindMobToPull(int PullFlag)
        /if (${ChainPull}==2 && ${PullFlag}) /varset ChainPull 1
        /if ((${DMZ} && ${Me.InInstance}==FALSE) || (${PullFlag} && !${Select[${Role},puller,pullertank,hunter,hunterpettank,pullerpettank]}) || (!${PullFlag} && !${Role.Find[puller]}) || ${Pulled} || (${AggroTargetID}  && !${ChainPull}) || ${ChainPullHold} || ${DPSPaused}) /return 0
            /call MobRadar ${MeleeDistance} FindMobToPull
        /if (${ChainPull} && (${MobCount}>1 || ${Me.XTarget[${XTSlot2}].ID})) /return 0
        /if (${PullFlag}) {
            /if (${ChainPull}) {
                /if (${Target.ID}==${Me.ID}) {
                    /squelch /target clear
                    /delay 10
                }
                /if (${Me.XTarget[${XTSlot}].ID} && ((!${MyTargetID} && !${Target.ID}) || ${Target.PctHPs}>=${ChainPullHP} || (${MyTargetID} && ${Spawn[${MyTargetID}].PctHPs}>=${ChainPullHP}))) /return 0
                /if (${Math.Distance[${CampYLoc},${CampXLoc}:${Spawn[=${MainAssist}].Y},${Spawn[=${MainAssist}].X}]}>75) /return 0
            }
        }
        /if (${PullFlag} && !${Select[${Role},puller,pullertank,hunter,hunterpettank,pullerpettank]} || ${Me.Buff[Resurrection Sickness].ID} || ${Me.Buff[Revival Sickness].ID} || ${IAmDead}) /return 0
        /if (${PullFlag} && ${PetRampPullWait} && !${Me.CombatState.Equal[COMBAT]} && ${Select[${Role},pullerpettank]} && ${Select[${Me.Class.ShortName},MAG,NEC,BST]}) /call CheckRampPets      
        /if (${DebugPull}) /echo \atDEBUGPULL FindMobToPull Enter ${PullFlag} \agLine#: ${Macro.CurLine}
        /call SetPullRange
        /doevents
        /declare i int local
        /declare j int local
		/declare k int local
		/declare l int local
		/declare m int local
		/declare PathArray[50] int local     0
		/declare PathDistance[50] float local     0
		/declare Smallsub int local     0
		/declare tempSortID int local 		0
		/declare tempSortDist float local 		0
        /declare f1 int local 0
        /declare PullMob int local 0
        /declare PullMobName string local 0
        /declare PullMobDistance float local 0
        /declare PullMobValid int local 0
        /declare PullCount int local
        /declare DistanceCheck int local
        /declare MobsNearCamp int local
        /declare PullingTimer timer local 5s
        /declare Start1 int local
		/declare WPMobCount int local
        /varset Pulling 0
        /call GroupWatch
        /if (${ChainPullHold}) /return 0
        /if (${PullFlag}) {
           /echo Looking for Close Range Mobs
        } else {
           /if (!${SpamTimer}) {
              |/echo --- Checking for Close Range Mobs --- ${MyTargetID} ${Target.ID} ${Target.PctHPs} ${MobCount}
              /echo Checking for Close Range Mobs 
              /varset SpamTimer 50
           }   
        }
        | Clear alert list 1, add mobs to ignore alert list1, set timer to keep alert list manageable for pulling no alert 1
        /if (!${PullAlertTimer}) {
            | Add Ignore Mob list to alert list
            /call AlertAddToList 1 "${MobsToIgnore}"
            /varset PullAlertTimer 5m
        }
        :FindMob
		| Advpath searches for mobs along the path using Pullwith radius not Maxradius
		/if (${PullPathWpCount} && ${PullPath.Length} && ${PullMoveUse.Equal[advpath]}) {
			/if (${Target.ID}) /squelch /target clear
            /varset PullMobValid 0 
			| loop through pathwaypoints and check for mobs
			/for k 1 to ${PullPathWpCount}
			/varset WPMobCount 0
			/varset PullMob 0
				/varset WPMobCount ${SpawnCount[npc loc ${PullPathArrayX[${k}]} ${PullPathArrayY[${k}]} radius ${Math.Calc[${PullRange}*.8]} zradius ${MaxZRange} targetable noalert 1]}
                /if (${WPMobCount}) {
                    /varset i 0
					/for l 1 to ${WPMobCount}
						/varset PullMob ${NearestSpawn[${l}, npc loc ${PullPathArrayX[${k}]} ${PullPathArrayY[${k}]} radius ${Math.Calc[${PullRange}*.8]} zradius ${MaxZRange} targetable noalert 1].ID}
							/if (${DebugPull}) /echo \atDEBUGPULL FindMobToPull 1: WP: ${k} MobID ${PullMob} LOS ${LineOfSight[${PullPathArrayY[${k}]},${PullPathArrayX[${k}]},${PullPathArrayZ[${k}]}:${Spawn[id ${PullMob}].Y},${Spawn[id ${PullMob}].X},${Spawn[id ${PullMob}].Z}]} YXZ ${PullPathArrayY[${k}]},${PullPathArrayX[${k}]},${PullPathArrayZ[${k}]}:${Spawn[id ${PullMob}].Y},${Spawn[id ${PullMob}].X},${Spawn[id ${PullMob}].Z} \agLine#: ${Macro.CurLine}
						/if (${DebugPull}) /delay 1
						/if (${PullMob} && ${LineOfSight[${PullPathArrayY[${k}]},${PullPathArrayX[${k}]},${PullPathArrayZ[${k}]}:${Spawn[id ${PullMob}].Y},${Spawn[id ${PullMob}].X},${Spawn[id ${PullMob}].Z}]}) {
								/varset DistanceCheck ${Math.Distance[${PullPathArrayY[${k}]},${PullPathArrayX[${k}]},${PullPathArrayZ[${k}]}:${Spawn[id ${PullMob}].Y},${Spawn[id ${PullMob}].X},${Spawn[id ${PullMob}].Z}]}
								/if (${k}<${PullPathWpCount}) {
									/varcalc i ${k}+1
									/while (${i}<=${PullPathWpCount} && ${DistanceCheck}>${Math.Distance[${PullPathArrayY[${i}]},${PullPathArrayX[${i}]},${PullPathArrayZ[${i}]}:${Spawn[id ${PullMob}].Y},${Spawn[id ${PullMob}].X},${Spawn[id ${PullMob}].Z}]}) {
										/varset DistanceCheck ${Math.Distance[${PullPathArrayY[${i}]},${PullPathArrayX[${i}]},${PullPathArrayZ[${i}]}:${Spawn[id ${PullMob}].Y},${Spawn[id ${PullMob}].X},${Spawn[id ${PullMob}].Z}]}
										/varcalc i ${i}+1
									}
									/varset k ${i}
								}
							/if (${DebugPull}) /echo \atDEBUGPULL FindMobToPull: WP ${k} MobCount ${l} ${WPMobCount} \agLine#: ${Macro.CurLine}
							/varset PullMobName ${Spawn[id ${PullMob}].CleanName}
							/varset PullMobDistance ${Math.Distance[${Spawn[id ${PullMob}].Y},${Spawn[id ${PullMob}].X}:${PullPathArrayY[${k}]},${PullPathArrayX[${k}]}]}
								/varcalc AdvpathPointNum ${k}
							/varset AdvpathPointX ${PullPathArrayX[${k}]}
							/varset AdvpathPointY ${PullPathArrayY[${k}]}
							/varset AdvpathPointZ ${PullPathArrayZ[${k}]}
							/if (${DebugPull}) /echo \atDEBUGPULL FindMobToPull 2: WP: ${k} Pullmob ${PullMob} Waypoint: ${AdvpathPointNum} XY: ${PullPathArrayX[${k}]} ${PullPathArrayY[${k}]} ${Math.Distance[${Spawn[id ${PullMob}].Y},${Spawn[id ${PullMob}].X}:${AdvpathPointY},${AdvpathPointX}]}<${PullRange} \agLine#: ${Macro.CurLine}
							/if (${DebugPull}) /echo \atDEBUGPULL FindMobToPull 2: AdvPull: Pullmob: ${PullMobName} ID ${PullMob} Waypoint: ${PullPathWpCount} XY: ${PullPathArrayX[${k}]} ${PullPathArrayY[${k}]} \agLine#: ${Macro.CurLine}
							/call PullValidate ${PullMob} ${PullFlag}
							/varset PullMobValid ${Macro.Return}
							/if (${PullMobValid}) /goto :FoundMob
						}
					/next l
                }
			/next k		
			/if (${DebugPull}) /echo \atDEBUGPULL FindMobToPull: Mob Not Found. AdvPull: PullFlag: ${PullFlag} Role: ${Role} ID: ${PullMob} PullRange: ${PullRange} \agLine#: ${Macro.CurLine}
            /return 0            
			| Check for mobs in max pull radius for normal,MQ2nav pulling
		} else /if (${Navigation.MeshLoaded} && ${PullMoveUse.Equal[nav]}) {    
            /varset PullCount ${SpawnCount[npc loc ${CampXLoc} ${CampYLoc} radius ${MaxRadius} zradius ${MaxZRange} targetable noalert 1]}
            /varset MobsNearCamp ${SpawnCount[npc loc ${CampXLoc} ${CampYLoc} radius ${MeleeDistance} zradius ${MaxZRange} targetable noalert 1]}
        } else /if (${Select[${Role},hunter,hunterpettank]}) {
            /varset PullCount ${SpawnCount[npc radius ${MaxRadius} zradius ${MaxZRange} targetable noalert 1]}
            /varset MobsNearCamp ${SpawnCount[npc radius ${MeleeDistance} zradius ${MaxZRange} targetable noalert 1]}
		} else {
            /varset PullCount ${SpawnCount[npc los loc ${CampXLoc} ${CampYLoc} radius ${MaxRadius} zradius ${MaxZRange} targetable noalert 1]}
            /varset MobsNearCamp ${SpawnCount[npc los loc ${CampXLoc} ${CampYLoc} radius ${MeleeDistance} zradius ${MaxZRange} targetable noalert 1]}
        }
        /if (${DebugPull}) /echo \atDEBUGPULL FindMobToPull PullFlag:${PullFlag} Pullcount: ${PullCount} MobsNearCamp: ${MobsNearCamp} \agLine#: ${Macro.CurLine}
        /if (!${PullFlag} && ${PullCount}>0 && ${Spawn[${LastMobPullID}].Distance}>=${MeleeDistance}) /return 0
        /if (!${PullFlag}) /varcalc PullCount ${PullCount}-${MobsNearCamp}
        /if (${PullCount}) {
           /if (!${PullFlag} && ${MobsNearCamp}) {
              /varset f1 2
			} else {
				/varset f1 1
			}
				/varset PullCount ${SpawnCount[npc loc ${CampXLoc} ${CampYLoc} radius ${MaxRadius} zradius ${MaxZRange} targetable noalert 1]}

				/for i 1 to ${PullCount}
				  /varset PathArray[${i}] ${NearestSpawn[${i}, npc loc ${CampXLoc} ${CampYLoc} radius ${MaxRadius} zradius ${MaxZRange} targetable noalert 1].ID}
				  /varset PullMob ${PathArray[${i}]}
				  /varset PathDistance[${i}] ${Navigation.PathLength[${Spawn[${PullMob}].X} ${Spawn[${PullMob}].Y} ${Spawn[${PullMob}].Z}]}
				/next i

				/for i 1 to ${PullCount}
				  /varset Smallsub ${i}
				  /varset m ${Math.Calc[${i} + 1]}
					  /for j ${m} to ${PullCount}
						/if (${PathDistance[${j}]} < ${PathDistance[${Smallsub}]}) /varset Smallsub ${j}
					  /next j
				  /varset tempSortID ${PathArray[${i}]}
				  /varset tempSortDist ${PathDistance[${i}]}
				  /varset PathArray[${i}] ${PathArray[${Smallsub}]}
				  /varset PathArray[${Smallsub}] ${tempSortID}
				  /varset PathDistance[${i}] ${PathDistance[${Smallsub}]}
				  /varset PathDistance[${Smallsub}] ${tempSortDist}
				/next i

				/for i 1 to ${PullCount}
				  /if (${DebugPull}) /echo \atDEBUGPULL FindMobToPull 1.0:  PullFlag: ${PullFlag} Pullcount: ${PullCount} \agLine#: ${Macro.CurLine}
				  /if (${Navigation.MeshLoaded} && ${PullMoveUse.Equal[nav]}) {
					/varset PullMob ${PathArray[${i}]}
					/varset PullMobName ${Spawn[id ${PullMob}].CleanName}
 
I found that making the array size 100 rather than 50 helps in crowded dungeons. If there are more mobs than the array size, errors ensue. I've tried various "checks" for finessing the error when it occurs, but for now upping the array size is the only thing that works.
 
Even if it is 50 or 100 I see where there could be a potential issue. I would make 1 of the following changes to keep it from crashing/spamming errors.

First Option:
Rich (BB code):
  |/for i ${f1} to ${PullCount}

  | Sort by path-lengths for MQ2Nav
    /declare PathArray[50]  		int      	local     0
    /declare PathDistance[50]  	float 		local     0
    /declare Smallsub 					int      	local     0
    /declare tempSortID 				int 			local 		0
    /declare tempSortDist 			float 		local 		0
    /declare i2 int local 0

    /varset PullCount ${SpawnCount[npc loc ${CampXLoc} ${CampYLoc} radius ${MaxRadius} zradius ${MaxZRange} targetable noalert 1]}
    |/echo Pullcount is:  ${PullCount}  range is: ${MaxRadius}

    /for i 1 to ${PullCount}
      /varset PathArray[${i}] ${NearestSpawn[${i}, npc loc ${CampXLoc} ${CampYLoc} radius ${MaxRadius} zradius ${MaxZRange} targetable noalert 1].ID}
      /varset PullMob	${PathArray[${i}]}
      /varset PathDistance[${i}] ${Navigation.PathLength[${Spawn[${PullMob}].X} ${Spawn[${PullMob}].Y} ${Spawn[${PullMob}].Z}]}
      /if (${i}==${PathArray.Size}) /break
    /next i

    /for i 1 to ${PullCount}
      /varset Smallsub ${i}
      /varset i2 ${Math.Calc[${i} + 1]}
      /for j ${i2} to ${PullCount}
        /if (${PathDistance[${j}]} < ${PathDistance[${Smallsub}]}) /varset Smallsub ${j}
      /next j
      /varset tempSortID ${PathArray[${i}]}
      /varset tempSortDist ${PathDistance[${i}]}
      /varset PathArray[${i}] ${PathArray[${Smallsub}]}
      /varset PathArray[${Smallsub}] ${tempSortID}
      /varset PathDistance[${i}] ${PathDistance[${Smallsub}]}
      /varset PathDistance[${Smallsub}] ${tempSortDist}
      /if (${i2}==${PathArray.Size}) /break
    /next i

    /for i 1 to ${PullCount}
      /if (${DebugPull}) /echo \atDEBUGPULL FindMobToPull 1.0:  PullFlag: ${PullFlag} Pullcount: ${PullCount} \agLine#: ${Macro.CurLine}
      /if (${Navigation.MeshLoaded} && ${PullMoveUse.Equal[nav]}) {
      |/varset PullMob ${NearestSpawn[${i}, npc loc ${CampXLoc} ${CampYLoc} radius ${MaxRadius} zradius ${MaxZRange} targetable noalert 1].ID}
        /varset PullMob ${PathArray[${i}]}
        /varset PullMobName ${Spawn[id ${PullMob}].CleanName}

Second Option:

or Just above your code you could just check and see if PullCount is greater than PathArray.Size and then set PullCount equalto PathArray.Size. You would still need to do the ${i2} check though.
 
ctaylor! Thanks. I knew it was going to be something like that. Appreciate you finding the bug.
 
Fork / Mod Sort mobs to pull by MQ2Nav pathlength rather than distance from camp

Users who are viewing this thread

Back
Top
Cart