mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-08-15 14:43:32 -07:00
371 lines
7.4 KiB
JavaScript
371 lines
7.4 KiB
JavaScript
import { SquareVectorDistance } from "simulation/ai/common-api/utils.js";
|
|
|
|
export class EntityCollection
|
|
{
|
|
constructor(sharedAI, entities = new Map(), filters = [])
|
|
{
|
|
this._ai = sharedAI;
|
|
this._entities = entities;
|
|
this._filters = filters;
|
|
this.dynamicProp = [];
|
|
for (const filter of this._filters)
|
|
if (filter.dynamicProperties.length)
|
|
this.dynamicProp = this.dynamicProp.concat(filter.dynamicProperties);
|
|
|
|
Object.defineProperty(this, "length", { "get": () => this._entities.size });
|
|
this.frozen = false;
|
|
}
|
|
|
|
Serialize()
|
|
{
|
|
const filters = [];
|
|
for (const f of this._filters)
|
|
filters.push(uneval(f));
|
|
return {
|
|
"ents": this.toIdArray(),
|
|
"frozen": this.frozen,
|
|
"filters": filters
|
|
};
|
|
}
|
|
|
|
Deserialize(data, sharedAI)
|
|
{
|
|
this._ai = sharedAI;
|
|
for (const id of data.ents)
|
|
this._entities.set(id, sharedAI._entities.get(id));
|
|
|
|
for (const f of data.filters)
|
|
this._filters.push(eval(f));
|
|
|
|
if (data.frozen)
|
|
this.freeze();
|
|
else
|
|
this.defreeze();
|
|
}
|
|
|
|
/**
|
|
* If an entitycollection is frozen, it will never automatically add a unit.
|
|
* But can remove one.
|
|
* this makes it easy to create entity collection that will auto-remove dead units
|
|
* but never add new ones.
|
|
*/
|
|
freeze()
|
|
{
|
|
this.frozen = true;
|
|
}
|
|
|
|
defreeze()
|
|
{
|
|
this.frozen = false;
|
|
}
|
|
|
|
toIdArray()
|
|
{
|
|
return Array.from(this._entities.keys());
|
|
}
|
|
|
|
toEntityArray()
|
|
{
|
|
return Array.from(this._entities.values());
|
|
}
|
|
|
|
values()
|
|
{
|
|
return this._entities.values();
|
|
}
|
|
|
|
toString()
|
|
{
|
|
return "[EntityCollection " + this.toEntityArray().join(" ") + "]";
|
|
}
|
|
|
|
filter(filter, thisp)
|
|
{
|
|
if (typeof filter === "function")
|
|
filter = { "func": filter, "dynamicProperties": [] };
|
|
|
|
const ret = new Map();
|
|
for (const [id, ent] of this._entities)
|
|
if (filter.func.call(thisp, ent, id, this))
|
|
ret.set(id, ent);
|
|
|
|
return new EntityCollection(this._ai, ret, this._filters.concat([filter]));
|
|
}
|
|
|
|
/**
|
|
* Returns the (at most) n entities nearest to targetPos.
|
|
*/
|
|
filterNearest(targetPos, n)
|
|
{
|
|
// Compute the distance of each entity
|
|
const data = []; // [ [id, ent, distance], ... ]
|
|
for (const [id, ent] of this._entities)
|
|
if (ent.position())
|
|
data.push([id, ent, SquareVectorDistance(targetPos, ent.position())]);
|
|
|
|
// Sort by increasing distance
|
|
data.sort((a, b) => a[2] - b[2]);
|
|
|
|
if (n === undefined)
|
|
n = data.length;
|
|
else
|
|
n = Math.min(n, data.length);
|
|
|
|
// Extract the first n
|
|
const ret = new Map();
|
|
for (let i = 0; i < n; ++i)
|
|
ret.set(data[i][0], data[i][1]);
|
|
|
|
return new EntityCollection(this._ai, ret);
|
|
}
|
|
|
|
filter_raw(callback, thisp)
|
|
{
|
|
const ret = new Map();
|
|
for (const [id, ent] of this._entities)
|
|
{
|
|
const val = ent._entity;
|
|
if (callback.call(thisp, val, id, this))
|
|
ret.set(id, ent);
|
|
}
|
|
return new EntityCollection(this._ai, ret);
|
|
}
|
|
|
|
forEach(callback)
|
|
{
|
|
for (const ent of this._entities.values())
|
|
callback(ent);
|
|
return this;
|
|
}
|
|
|
|
hasEntities()
|
|
{
|
|
return this._entities.size !== 0;
|
|
}
|
|
|
|
move(x, z, queued = false, pushFront = false)
|
|
{
|
|
Engine.PostCommand(PlayerID, {
|
|
"type": "walk",
|
|
"entities": this.toIdArray(),
|
|
"x": x,
|
|
"z": z,
|
|
"queued": queued,
|
|
"pushFront": pushFront
|
|
});
|
|
return this;
|
|
}
|
|
|
|
moveToRange(x, z, min, max, queued = false, pushFront = false)
|
|
{
|
|
Engine.PostCommand(PlayerID, {
|
|
"type": "walk-to-range",
|
|
"entities": this.toIdArray(),
|
|
"x": x,
|
|
"z": z,
|
|
"min": min,
|
|
"max": max,
|
|
"queued": queued,
|
|
"pushFront": pushFront
|
|
});
|
|
return this;
|
|
}
|
|
|
|
attackMove(x, z, targetClasses, allowCapture = true, queued = false, pushFront = false)
|
|
{
|
|
Engine.PostCommand(PlayerID, {
|
|
"type": "attack-walk",
|
|
"entities": this.toIdArray(),
|
|
"x": x,
|
|
"z": z,
|
|
"targetClasses": targetClasses,
|
|
"allowCapture": allowCapture,
|
|
"queued": queued,
|
|
"pushFront": pushFront
|
|
});
|
|
return this;
|
|
}
|
|
|
|
moveIndiv(x, z, queued = false, pushFront = false)
|
|
{
|
|
for (const id of this._entities.keys())
|
|
Engine.PostCommand(PlayerID, {
|
|
"type": "walk",
|
|
"entities": [id],
|
|
"x": x,
|
|
"z": z,
|
|
"queued": queued,
|
|
"pushFront": pushFront
|
|
});
|
|
return this;
|
|
}
|
|
|
|
garrison(target, queued = false, pushFront = false)
|
|
{
|
|
Engine.PostCommand(PlayerID, {
|
|
"type": "garrison",
|
|
"entities": this.toIdArray(),
|
|
"target": target.id(),
|
|
"queued": queued,
|
|
"pushFront": pushFront
|
|
});
|
|
return this;
|
|
}
|
|
|
|
occupyTurret(target, queued = false, pushFront = false)
|
|
{
|
|
Engine.PostCommand(PlayerID, {
|
|
"type": "occupy-turret",
|
|
"entities": this.toIdArray(),
|
|
"target": target.id(),
|
|
"queued": queued,
|
|
"pushFront": pushFront
|
|
});
|
|
return this;
|
|
}
|
|
|
|
destroy()
|
|
{
|
|
Engine.PostCommand(PlayerID, { "type": "delete-entities", "entities": this.toIdArray() });
|
|
return this;
|
|
}
|
|
|
|
attack(unitId, queued = false, pushFront = false)
|
|
{
|
|
Engine.PostCommand(PlayerID, {
|
|
"type": "attack",
|
|
"entities": this.toIdArray(),
|
|
"target": unitId,
|
|
"queued": queued,
|
|
"pushFront": pushFront
|
|
});
|
|
return this;
|
|
}
|
|
|
|
/** violent, aggressive, defensive, passive, standground */
|
|
setStance(stance)
|
|
{
|
|
Engine.PostCommand(PlayerID, {
|
|
"type": "stance",
|
|
"entities": this.toIdArray(),
|
|
"name": stance
|
|
});
|
|
return this;
|
|
}
|
|
|
|
/** Returns the average position of all units */
|
|
getCentrePosition()
|
|
{
|
|
const sumPos = [0, 0];
|
|
let count = 0;
|
|
for (const ent of this._entities.values())
|
|
{
|
|
if (!ent.position())
|
|
continue;
|
|
sumPos[0] += ent.position()[0];
|
|
sumPos[1] += ent.position()[1];
|
|
count++;
|
|
}
|
|
|
|
return count ? [sumPos[0]/count, sumPos[1]/count] : undefined;
|
|
}
|
|
|
|
/**
|
|
* returns the average position from the sample first units.
|
|
* This might be faster for huge collections, but there's
|
|
* always a risk that it'll be unprecise.
|
|
*/
|
|
getApproximatePosition(sample)
|
|
{
|
|
const sumPos = [0, 0];
|
|
let i = 0;
|
|
for (const ent of this._entities.values())
|
|
{
|
|
if (!ent.position())
|
|
continue;
|
|
sumPos[0] += ent.position()[0];
|
|
sumPos[1] += ent.position()[1];
|
|
i++;
|
|
if (i === sample)
|
|
break;
|
|
}
|
|
|
|
return i ? [sumPos[0]/i, sumPos[1]/i] : undefined;
|
|
}
|
|
|
|
hasEntId(id)
|
|
{
|
|
return this._entities.has(id);
|
|
}
|
|
|
|
/** Removes an entity from the collection, returns true if the entity was a member, false otherwise */
|
|
removeEnt(ent)
|
|
{
|
|
if (!this._entities.has(ent.id()))
|
|
return false;
|
|
this._entities.delete(ent.id());
|
|
return true;
|
|
}
|
|
|
|
/** Adds an entity to the collection, returns true if the entity was not member, false otherwise */
|
|
addEnt(ent)
|
|
{
|
|
if (this._entities.has(ent.id()))
|
|
return false;
|
|
this._entities.set(ent.id(), ent);
|
|
const temp = this.toEntityArray();
|
|
temp.sort((a, b) => a.id() - b.id());
|
|
this._entities.clear();
|
|
for (const e of temp)
|
|
this._entities.set(e.id(), e);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks the entity against the filters, and adds or removes it appropriately, returns true if the
|
|
* entity collection was modified.
|
|
* Force can add a unit despite a freezing.
|
|
* If an entitycollection is frozen, it will never automatically add a unit.
|
|
* But can remove one.
|
|
*/
|
|
updateEnt(ent, force)
|
|
{
|
|
let passesFilters = true;
|
|
for (const filter of this._filters)
|
|
passesFilters = passesFilters && filter.func(ent);
|
|
|
|
if (passesFilters)
|
|
{
|
|
if (!force && this.frozen)
|
|
return false;
|
|
return this.addEnt(ent);
|
|
}
|
|
|
|
return this.removeEnt(ent);
|
|
}
|
|
|
|
registerUpdates()
|
|
{
|
|
this._ai.registerUpdatingEntityCollection(this);
|
|
}
|
|
|
|
unregister()
|
|
{
|
|
this._ai.removeUpdatingEntityCollection(this);
|
|
}
|
|
|
|
dynamicProperties()
|
|
{
|
|
return this.dynamicProp;
|
|
}
|
|
|
|
setUID(id)
|
|
{
|
|
this._UID = id;
|
|
}
|
|
|
|
getUID()
|
|
{
|
|
return this._UID;
|
|
}
|
|
}
|