quote: First, focus on myQueue[hash], this is a remake of Spring's build-queues, it list the coordinates and type of build. Remove its content will remove the available job for constructors and also the UI green box.
To make job re-assignable, keep its entry undeleted. The entry will be scanned by GetWorkFor() when assigning job, and emptied by widget:UnitFinished() when build finishes and occasionally by CleanOrders() when obstruction is detected. ( note: new patch that fix CleanOrders() in ZK-test )
10 worker are collected at one time by FindEligibleWorker() to be assigned for jobs. (it is limited to 10 due to performance concern) |
That much I knew. myQueue I didn't really change significantly. widget:UnitFinished() I figured was responsible for keeping items on the queue properly until finished, and I didn't change that either.
quote: The job assignment loop isn't redundant, each loop find something necessary: 1st iteration, find the closest job for all 10 worker (GetWorkFor()) then, it find worker nearest to its job and assign it to that job ( GiveWorkToUnits()) 2nd, it repeat them with next 9 worker... 3rd, repeat for next 8 worker.. then, 4th -> next 7, 5th -> next 6, ect... |
The problem is that the second iteration, ie 'find the worker nearest to its job' is very expensive and redundant. We basically have to find work for each constructor twice instead of once, and although it might lead to constructors being assigned to slightly closer jobs that problem is NP-Complete and also much more accurate than what is actually needed for good behavior. The double-assignment is also probably the main reason why constructors are being assigned in circles.
My version will be more like 'find the lowest-cost job for each worker, assign immediately- end'.
quote: When 1 worker got new job it changes the environment for other worker, so next iteration need to rescan the queues (eg: whether to assist job or create new one). |
That's because the cost model for the first assignment pass doesn't account for that properly. The second pass is an over-optimal and overly expensive solution, that also doesn't seem to produce correct results.
quote: 10 worker are collected at one time by FindEligibleWorker() to be assigned for jobs. (it is limited to 10 due to performance concern)...
A code labelled "--Collect busy worker for reassignment--" (latest version from ZK Github) control the re-assignment. Its currently very simple and by removing "queueType.buildQueue" from "matchAssistOrBuildQueueType", you will at least make 1 worker stay on its job.
Improving this code will give smarter reassignment. |
I did not see that code, so it's possible that I pulled incorrectly from git. :| Technically FindEligibleWorker is already responsible for that, so the changes should have been made to that function instead.
I should note however that the solution you proposed can't work. If a worker is assigned to a job that's very far away and marked as the primary constructor, then it won't be properly reassigned should a new job get queued up nearer than the job it's already been assigned to. That was one of the major improvements that the previous change made.
quote: This is reassignment bug, not Singu! Don't remove the code. More worker to Singu is a correct choice. If worker didn't abandon porc due to re-assignment then everything work perfectly. |
The problem is that you're trying to compare metal cost and distance in the same cost function. It might be possible to do that, but not simple. I also noticed that you try to account for the total fraction of workers assigned to the expensive job, which is kind of irrelevant.
The main problem though is that it's trivial and takes no time in game to manually tell workers to assist on something, but it's literally impossible to tell them to start some other job on the queue that hasn't been started yet, even if that job is much more important towards not losing the game (for example an LLT next to your singu to stop scythes and things). The 'correct' decision is to obey the player, always, without exception. Taking away the player's ability to make important decisions like that is never a good idea.
It would be fairly trivial to add that back in with the new cost model anyway, but actually making it work in a way that's useful would require coming up with a better system for accounting for metal cost.
quote: Another feedback, thanks, it need improvement. You should use Direct command to build in enemy territory, else if you use Queue it obey CBAI rules (such as de-prioritize your Queue or remove it). |
That, on the other hand, is a huge freaking pain that I do not ever want to have to do again in game, which is why I removed it. A single undefended enemy mex has no business cancelling my perfectly good queue, when all my workers needed to do was ignore the mex and build the LLT next to it like I told them to and then it'd be my mex instead of my enemy's.
None of the methods for determining what jobs are 'unsafe' is valid from what I checked out. There's no way to tell whether 'enemy units' are actually capable of attacking or not, or whether a worker got killed by a few raiders or whether it walked into an enemy porc line, nor is there a way to tell when a 'dangerous' zone has become safe, except when the player takes actions to clear it out themselves.
Removing jobs with a single area select would be much easier than having to manually tell workers to individually start each job in a place just because CB decided it was 'unsafe'. That's a lot of wasted time that could have been spent managing combat units instead, and I've seen people lose for less than that.
quote: Certain worker had limited buildOptions, such as Athena. So GUARDing is never obsolete.
Direct command is already stored in myQueue[hash] (this is contrast to the claim). Its hash start with "queueType.buildNew", while hash for Queue start with "queueType.buildQueue".
Removing "assistBuild" tag will loose the core idea/concept of unit interaction, which is "one builder--many helpers".
The tag could be used to code for more behaviour or for UI. |
Athena is a special case, and the only special case that I'm aware of. I'm not so sure that it's a big enough issue to merit all of that extra complexity though, since in general people use athenas for special purposes rather than as normal constructors. Athenas are mostly used for ressurecting things and building units behind enemy lines, neither of which are things that CB can handle correctly anyway.
Getting rid of the leader/assistant distinction is what will allow it to both ensure that jobs that have been started will always retain at least one constructor, while at the same time allowing constructors to be reassigned if closer jobs become available.
It's also a lot simpler than the way it works now, so I'd prefer a more concrete example of just what you could accomplish that would actually be useful that also employs the leader/assistant distinction. None of the features that I've been thinking of adding require it, nor is it required to maintain the widget's core functionality, although I will have to rewrite that entirely in order to make it work.
quote: CBAI don't use pathfinding for job assignment but use 2D distance for cost reason.
Periodic pathfinding check for inaccessible build site (eg: sea <-> inland <-> wall barrier) and de-prioritize them.
It's stored in "myQueueUnreachable[unitID][hash]" as integer 0(clear),1(enemy!),2(blocked) |
myQueueUnreachable is never referenced by GiveWorkToUnits or GetWorkFor, and is only trivially referenced by CleanOrders but never actually used for anything.
I looked it over again and you're right, GetWorkFor and GiveWorkToUnits only use a simplified 2D distance, although they cut it in half for flying workers for no good reason. Actually I don't see anywhere where pathing is applied correctly for job assignment, since correct pathing is per-unit-per-location.
Keeping a queue of unreachable jobs is dumb anyway, if anything you would need a per-worker queue of
reachable jobs, so that only those jobs would be considered by GetWorkFor. I'll have to figure that out when I get to it, I guess.