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"},
{"name": "Cédric Houbart"},
{"nick": "Chakakhan", "name": "Kenny Long"},
{"nick": "Clockwork-Muse", "name": "Stephen A. Imhoff"},
{"nick": "Cracker78", "name": "Chad Heim"},
{"nick": "Crynux", "name": "Stephen J. Fewer"},
{"nick": "cwprogger"},

View File

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

View File

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

View File

@ -1645,34 +1645,93 @@ GuiInterface.prototype.PlaySound = function(player, data)
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)
{
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 = [];
for (let ent of playerEntities)
// The general case is that only the 'first' idle unit is required; filtering would examine every unit.
// 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;
idleUnits.push(ent);
if (data.limit && idleUnits.length >= data.limit)
break;
// If the entity is in the 'current' (first, 0) bucket on a resumed search, it must be after the "previous" unit, if any.
// 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)
@ -1899,6 +1958,7 @@ let exposedFunctions = {
"GetFoundationSnapData": 1,
"PlaySound": 1,
"FindIdleUnits": 1,
"HasIdleUnits": 1,
"GetTradingRouteGain": 1,
"GetTradingDetails": 1,
"CanCapture": 1,