1
0
forked from 0ad/0ad

Improve findIdleUnit and hasIdleUnit code. Patch by Clockwork-Muse. Fixes #3826

This was SVN commit r18139.
This commit is contained in:
sanderd17 2016-05-08 09:32:16 +00:00
parent 09fd6da38e
commit cc3d7d58ea
4 changed files with 124 additions and 87 deletions

View File

@ -39,6 +39,7 @@
{"nick": "Calvinh", "name": "Carl-Johan Höiby"}, {"nick": "Calvinh", "name": "Carl-Johan Höiby"},
{"name": "Cédric Houbart"}, {"name": "Cédric Houbart"},
{"nick": "Chakakhan", "name": "Kenny Long"}, {"nick": "Chakakhan", "name": "Kenny Long"},
{"nick": "Clockwork-Muse", "name": "Stephen A. Imhoff"},
{"nick": "Cracker78", "name": "Chad Heim"}, {"nick": "Cracker78", "name": "Chad Heim"},
{"nick": "Crynux", "name": "Stephen J. Fewer"}, {"nick": "Crynux", "name": "Stephen J. Fewer"},
{"nick": "cwprogger"}, {"nick": "cwprogger"},

View File

@ -1683,14 +1683,14 @@ function setCameraFollow(entity)
} }
var lastIdleUnit = 0; var lastIdleUnit = 0;
var currIdleClass = 0; var currIdleClassIndex = 0;
var lastIdleType = undefined; var lastIdleClasses = [];
function resetIdleUnit() function resetIdleUnit()
{ {
lastIdleUnit = 0; lastIdleUnit = 0;
currIdleClass = 0; currIdleClassIndex = 0;
lastIdleType = undefined; lastIdleClasses = [];
} }
function findIdleUnit(classes) function findIdleUnit(classes)
@ -1699,61 +1699,46 @@ function findIdleUnit(classes)
var selectall = Engine.HotkeyIsPressed("selection.offscreen"); var selectall = Engine.HotkeyIsPressed("selection.offscreen");
// Reset the last idle unit, etc., if the selection type has changed. // Reset the last idle unit, etc., if the selection type has changed.
var type = classes.join(); if (selectall || classes.length != lastIdleClasses.length || !classes.every((v,i) => v === lastIdleClasses[i]))
if (selectall || type != lastIdleType)
resetIdleUnit(); resetIdleUnit();
lastIdleType = type; lastIdleClasses = classes;
// If selectall is true, there is no limit and it's necessary to iterate var data = {
// over all of the classes, resetting only when the first match is found. "viewedPlayer": g_ViewedPlayer,
var matched = false; "excludeUnits": append ? g_Selection.toList() : [],
// If the current idle class index is not 0, put the class at that index first.
for (var i = 0; i < classes.length; ++i) "idleClasses": classes.slice(currIdleClassIndex, classes.length).concat(classes.slice(0, currIdleClassIndex))
};
if (!selectall)
{ {
var data = { data.limit = 1;
"idleClass": classes[currIdleClass], data.prevUnit = lastIdleUnit;
"prevUnit": lastIdleUnit,
"limit": 1,
"excludeUnits": []
};
if (append)
data.excludeUnits = g_Selection.toList();
if (selectall)
data = { "idleClass": classes[currIdleClass] };
data.viewedPlayer = g_ViewedPlayer;
// Check if we have new valid entity
var idleUnits = Engine.GuiInterfaceCall("FindIdleUnits", data);
if (idleUnits.length && idleUnits[0] != lastIdleUnit)
{
lastIdleUnit = idleUnits[0];
if (!append && (!selectall || selectall && !matched))
g_Selection.reset();
if (selectall)
g_Selection.addList(idleUnits);
else
{
g_Selection.addList([lastIdleUnit]);
var position = GetEntityState(lastIdleUnit).position;
if (position)
Engine.CameraMoveTo(position.x, position.z);
return;
}
matched = true;
}
lastIdleUnit = 0;
currIdleClass = (currIdleClass + 1) % classes.length;
} }
// TODO: display a message or play a sound to indicate no more idle units, or something var idleUnits = Engine.GuiInterfaceCall("FindIdleUnits", data);
// Reset for next cycle if (!idleUnits.length)
resetIdleUnit(); {
// TODO: display a message or play a sound to indicate no more idle units, or something
// Reset for next cycle
resetIdleUnit();
return;
}
if (!append)
g_Selection.reset();
g_Selection.addList(idleUnits);
if (selectall)
return;
lastIdleUnit = idleUnits[0];
var entityState = GetEntityState(lastIdleUnit);
var position = entityState.position;
if (position)
Engine.CameraMoveTo(position.x, position.z);
// Move the idle class index to the first class an idle unit was found for.
var indexChange = data.idleClasses.findIndex(elem => hasClass(entityState, elem));
currIdleClassIndex = (currIdleClassIndex + indexChange) % classes.length;
} }
function stopUnits(entities) function stopUnits(entities)

View File

@ -650,20 +650,11 @@ function changeGameSpeed(speed)
function hasIdleWorker() function hasIdleWorker()
{ {
for (let workerType of g_WorkerTypes) return Engine.GuiInterfaceCall("HasIdleUnits", {
{
let idleUnits = Engine.GuiInterfaceCall("FindIdleUnits", {
"viewedPlayer": g_ViewedPlayer, "viewedPlayer": g_ViewedPlayer,
"idleClass": workerType, "idleClasses": g_WorkerTypes,
"prevUnit": undefined,
"limit": 1,
"excludeUnits": [] "excludeUnits": []
}); });
if (idleUnits.length > 0)
return true;
}
return false;
} }
function updateIdleWorkerButton() function updateIdleWorkerButton()

View File

@ -1645,34 +1645,93 @@ GuiInterface.prototype.PlaySound = function(player, data)
PlaySound(data.name, data.entity); PlaySound(data.name, data.entity);
}; };
/**
* Find any idle units.
*
* @param data.viewedPlayer The player for which to find idle units.
* @param data.idleClasses Array of class names to include.
* @param data.prevUnit The previous idle unit, if calling a second time to iterate through units. May be left undefined.
* @param data.limit The number of idle units to return. May be left undefined (will return all idle units).
* @param data.excludeUnits Array of units to exclude.
*
* Returns an array of idle units.
* If multiple classes were supplied, and multiple items will be returned, the items will be sorted by class.
*/
GuiInterface.prototype.FindIdleUnits = function(player, data) GuiInterface.prototype.FindIdleUnits = function(player, data)
{ {
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
let playerEntities = cmpRangeManager.GetEntitiesByPlayer(data.viewedPlayer).filter(entity => {
let cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
if (!cmpUnitAI || !cmpUnitAI.IsIdle() || cmpUnitAI.IsGarrisoned())
return false;
let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
if (!cmpIdentity || !cmpIdentity.HasClass(data.idleClass))
return false;
return true;
});
let idleUnits = []; let idleUnits = [];
// The general case is that only the 'first' idle unit is required; filtering would examine every unit.
for (let ent of playerEntities) // This loop imitates a grouping/aggregation on the first matching idle class.
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
for (let entity of cmpRangeManager.GetEntitiesByPlayer(data.viewedPlayer))
{ {
if (ent <= data.prevUnit|0 || data.excludeUnits.indexOf(ent) > -1) let filtered = this.IdleUnitFilter(entity, data.idleClasses, data.excludeUnits);
if (!filtered.idle)
continue; continue;
idleUnits.push(ent);
if (data.limit && idleUnits.length >= data.limit) // If the entity is in the 'current' (first, 0) bucket on a resumed search, it must be after the "previous" unit, if any.
break; // By adding to the 'end', there is no pause if the series of units loops.
var bucket = filtered.bucket;
if(bucket == 0 && data.prevUnit && entity <= data.prevUnit)
bucket = data.idleClasses.length;
if (!idleUnits[bucket])
idleUnits[bucket] = [];
idleUnits[bucket].push(entity);
// If enough units have been collected in the first bucket, go ahead and return them.
if (data.limit && bucket == 0 && idleUnits[0].length == data.limit)
return idleUnits[0];
} }
return idleUnits; let reduced = idleUnits.reduce((prev, curr) => prev.concat(curr), []);
if (data.limit && reduced.length > data.limit)
return reduced.slice(0, data.limit);
return reduced;
};
/**
* Discover if the player has idle units.
*
* @param data.viewedPlayer The player for which to find idle units.
* @param data.idleClasses Array of class names to include.
* @param data.excludeUnits Array of units to exclude.
*
* Returns a boolean of whether the player has any idle units
*/
GuiInterface.prototype.HasIdleUnits = function(player, data)
{
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
return cmpRangeManager.GetEntitiesByPlayer(data.viewedPlayer).some(unit => this.IdleUnitFilter(unit, data.idleClasses, data.excludeUnits).idle);
};
/**
* Whether to filter an idle unit
*
* @param unit The unit to filter.
* @param idleclasses Array of class names to include.
* @param excludeUnits Array of units to exclude.
*
* Returns an object with the following fields:
* - idle - true if the unit is considered idle by the filter, false otherwise.
* - bucket - if idle, set to the index of the first matching idle class, undefined otherwise.
*/
GuiInterface.prototype.IdleUnitFilter = function(unit, idleClasses, excludeUnits)
{
let cmpUnitAI = Engine.QueryInterface(unit, IID_UnitAI);
if (!cmpUnitAI || !cmpUnitAI.IsIdle() || cmpUnitAI.IsGarrisoned())
return { "idle": false };
let cmpIdentity = Engine.QueryInterface(unit, IID_Identity);
if(!cmpIdentity)
return { "idle": false };
let bucket = idleClasses.findIndex(elem => cmpIdentity.HasClass(elem));
if (bucket == -1 || excludeUnits.indexOf(unit) > -1)
return { "idle": false };
return { "idle": true, "bucket": bucket };
}; };
GuiInterface.prototype.GetTradingRouteGain = function(player, data) GuiInterface.prototype.GetTradingRouteGain = function(player, data)
@ -1899,6 +1958,7 @@ let exposedFunctions = {
"GetFoundationSnapData": 1, "GetFoundationSnapData": 1,
"PlaySound": 1, "PlaySound": 1,
"FindIdleUnits": 1, "FindIdleUnits": 1,
"HasIdleUnits": 1,
"GetTradingRouteGain": 1, "GetTradingRouteGain": 1,
"GetTradingDetails": 1, "GetTradingDetails": 1,
"CanCapture": 1, "CanCapture": 1,