mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-08-15 14:43:32 -07:00
parent
09671b49a2
commit
8b1e78ae1e
27 changed files with 16452 additions and 16378 deletions
|
|
@ -1,29 +1,31 @@
|
|||
globalThis.PlayerID = -1;
|
||||
|
||||
export function BaseAI(settings)
|
||||
export class BaseAI
|
||||
{
|
||||
constructor(settings)
|
||||
{
|
||||
if (!settings)
|
||||
return;
|
||||
|
||||
this.player = settings.player;
|
||||
}
|
||||
}
|
||||
|
||||
/** Return a simple object (using no classes etc) that will be serialized into saved games */
|
||||
BaseAI.prototype.Serialize = function()
|
||||
{
|
||||
/** Return a simple object (using no classes etc) that will be serialized into saved games */
|
||||
Serialize()
|
||||
{
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Called after the constructor when loading a saved game, with 'data' being
|
||||
* whatever Serialize() returned
|
||||
*/
|
||||
BaseAI.prototype.Deserialize = function(data, sharedScript)
|
||||
{
|
||||
};
|
||||
Deserialize(data, sharedScript)
|
||||
{
|
||||
}
|
||||
|
||||
BaseAI.prototype.Init = function(playerID, sharedAI)
|
||||
{
|
||||
Init(playerID, sharedAI)
|
||||
{
|
||||
PlayerID = playerID;
|
||||
|
||||
this.territoryMap = sharedAI.territoryMap;
|
||||
|
|
@ -35,26 +37,27 @@ BaseAI.prototype.Init = function(playerID, sharedAI)
|
|||
this.timeElapsed = sharedAI.timeElapsed;
|
||||
|
||||
this.CustomInit(this.gameState);
|
||||
};
|
||||
}
|
||||
|
||||
/** AIs override this function */
|
||||
BaseAI.prototype.CustomInit = function()
|
||||
{
|
||||
};
|
||||
/** AIs override this function */
|
||||
CustomInit()
|
||||
{
|
||||
}
|
||||
|
||||
BaseAI.prototype.HandleMessage = function(state, playerID, sharedAI)
|
||||
{
|
||||
HandleMessage(state, playerID, sharedAI)
|
||||
{
|
||||
PlayerID = playerID;
|
||||
this.territoryMap = sharedAI.territoryMap;
|
||||
this.OnUpdate(sharedAI);
|
||||
};
|
||||
}
|
||||
|
||||
/** AIs override this function */
|
||||
BaseAI.prototype.OnUpdate = function()
|
||||
{
|
||||
};
|
||||
/** AIs override this function */
|
||||
OnUpdate()
|
||||
{
|
||||
}
|
||||
|
||||
BaseAI.prototype.chat = function(message)
|
||||
{
|
||||
chat(message)
|
||||
{
|
||||
Engine.PostCommand(PlayerID, { "type": "aichat", "message": message });
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { SquareVectorDistance } from "simulation/ai/common-api/utils.js";
|
||||
|
||||
export function EntityCollection(sharedAI, entities = new Map(), filters = [])
|
||||
export class EntityCollection
|
||||
{
|
||||
constructor(sharedAI, entities = new Map(), filters = [])
|
||||
{
|
||||
this._ai = sharedAI;
|
||||
this._entities = entities;
|
||||
this._filters = filters;
|
||||
|
|
@ -12,10 +14,10 @@ export function EntityCollection(sharedAI, entities = new Map(), filters = [])
|
|||
|
||||
Object.defineProperty(this, "length", { "get": () => this._entities.size });
|
||||
this.frozen = false;
|
||||
}
|
||||
}
|
||||
|
||||
EntityCollection.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
const filters = [];
|
||||
for (const f of this._filters)
|
||||
filters.push(uneval(f));
|
||||
|
|
@ -24,10 +26,10 @@ EntityCollection.prototype.Serialize = function()
|
|||
"frozen": this.frozen,
|
||||
"filters": filters
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.Deserialize = function(data, sharedAI)
|
||||
{
|
||||
Deserialize(data, sharedAI)
|
||||
{
|
||||
this._ai = sharedAI;
|
||||
for (const id of data.ents)
|
||||
this._entities.set(id, sharedAI._entities.get(id));
|
||||
|
|
@ -39,46 +41,46 @@ EntityCollection.prototype.Deserialize = function(data, sharedAI)
|
|||
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.
|
||||
*/
|
||||
EntityCollection.prototype.freeze = function()
|
||||
{
|
||||
freeze()
|
||||
{
|
||||
this.frozen = true;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.defreeze = function()
|
||||
{
|
||||
defreeze()
|
||||
{
|
||||
this.frozen = false;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.toIdArray = function()
|
||||
{
|
||||
toIdArray()
|
||||
{
|
||||
return Array.from(this._entities.keys());
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.toEntityArray = function()
|
||||
{
|
||||
toEntityArray()
|
||||
{
|
||||
return Array.from(this._entities.values());
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.values = function()
|
||||
{
|
||||
values()
|
||||
{
|
||||
return this._entities.values();
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.toString = function()
|
||||
{
|
||||
toString()
|
||||
{
|
||||
return "[EntityCollection " + this.toEntityArray().join(" ") + "]";
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.filter = function(filter, thisp)
|
||||
{
|
||||
filter(filter, thisp)
|
||||
{
|
||||
if (typeof filter === "function")
|
||||
filter = { "func": filter, "dynamicProperties": [] };
|
||||
|
||||
|
|
@ -88,13 +90,13 @@ EntityCollection.prototype.filter = function(filter, thisp)
|
|||
ret.set(id, ent);
|
||||
|
||||
return new EntityCollection(this._ai, ret, this._filters.concat([filter]));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns the (at most) n entities nearest to targetPos.
|
||||
*/
|
||||
EntityCollection.prototype.filterNearest = function(targetPos, n)
|
||||
{
|
||||
filterNearest(targetPos, n)
|
||||
{
|
||||
// Compute the distance of each entity
|
||||
const data = []; // [ [id, ent, distance], ... ]
|
||||
for (const [id, ent] of this._entities)
|
||||
|
|
@ -115,10 +117,10 @@ EntityCollection.prototype.filterNearest = function(targetPos, n)
|
|||
ret.set(data[i][0], data[i][1]);
|
||||
|
||||
return new EntityCollection(this._ai, ret);
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.filter_raw = function(callback, thisp)
|
||||
{
|
||||
filter_raw(callback, thisp)
|
||||
{
|
||||
const ret = new Map();
|
||||
for (const [id, ent] of this._entities)
|
||||
{
|
||||
|
|
@ -127,22 +129,22 @@ EntityCollection.prototype.filter_raw = function(callback, thisp)
|
|||
ret.set(id, ent);
|
||||
}
|
||||
return new EntityCollection(this._ai, ret);
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.forEach = function(callback)
|
||||
{
|
||||
forEach(callback)
|
||||
{
|
||||
for (const ent of this._entities.values())
|
||||
callback(ent);
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.hasEntities = function()
|
||||
{
|
||||
hasEntities()
|
||||
{
|
||||
return this._entities.size !== 0;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.move = function(x, z, queued = false, pushFront = false)
|
||||
{
|
||||
move(x, z, queued = false, pushFront = false)
|
||||
{
|
||||
Engine.PostCommand(PlayerID, {
|
||||
"type": "walk",
|
||||
"entities": this.toIdArray(),
|
||||
|
|
@ -152,10 +154,10 @@ EntityCollection.prototype.move = function(x, z, queued = false, pushFront = fal
|
|||
"pushFront": pushFront
|
||||
});
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.moveToRange = function(x, z, min, max, queued = false, pushFront = false)
|
||||
{
|
||||
moveToRange(x, z, min, max, queued = false, pushFront = false)
|
||||
{
|
||||
Engine.PostCommand(PlayerID, {
|
||||
"type": "walk-to-range",
|
||||
"entities": this.toIdArray(),
|
||||
|
|
@ -167,10 +169,10 @@ EntityCollection.prototype.moveToRange = function(x, z, min, max, queued = false
|
|||
"pushFront": pushFront
|
||||
});
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.attackMove = function(x, z, targetClasses, allowCapture = true, queued = false, pushFront = false)
|
||||
{
|
||||
attackMove(x, z, targetClasses, allowCapture = true, queued = false, pushFront = false)
|
||||
{
|
||||
Engine.PostCommand(PlayerID, {
|
||||
"type": "attack-walk",
|
||||
"entities": this.toIdArray(),
|
||||
|
|
@ -182,10 +184,10 @@ EntityCollection.prototype.attackMove = function(x, z, targetClasses, allowCaptu
|
|||
"pushFront": pushFront
|
||||
});
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.moveIndiv = function(x, z, queued = false, pushFront = false)
|
||||
{
|
||||
moveIndiv(x, z, queued = false, pushFront = false)
|
||||
{
|
||||
for (const id of this._entities.keys())
|
||||
Engine.PostCommand(PlayerID, {
|
||||
"type": "walk",
|
||||
|
|
@ -196,10 +198,10 @@ EntityCollection.prototype.moveIndiv = function(x, z, queued = false, pushFront
|
|||
"pushFront": pushFront
|
||||
});
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.garrison = function(target, queued = false, pushFront = false)
|
||||
{
|
||||
garrison(target, queued = false, pushFront = false)
|
||||
{
|
||||
Engine.PostCommand(PlayerID, {
|
||||
"type": "garrison",
|
||||
"entities": this.toIdArray(),
|
||||
|
|
@ -208,10 +210,10 @@ EntityCollection.prototype.garrison = function(target, queued = false, pushFront
|
|||
"pushFront": pushFront
|
||||
});
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.occupyTurret = function(target, queued = false, pushFront = false)
|
||||
{
|
||||
occupyTurret(target, queued = false, pushFront = false)
|
||||
{
|
||||
Engine.PostCommand(PlayerID, {
|
||||
"type": "occupy-turret",
|
||||
"entities": this.toIdArray(),
|
||||
|
|
@ -220,16 +222,16 @@ EntityCollection.prototype.occupyTurret = function(target, queued = false, pushF
|
|||
"pushFront": pushFront
|
||||
});
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.destroy = function()
|
||||
{
|
||||
destroy()
|
||||
{
|
||||
Engine.PostCommand(PlayerID, { "type": "delete-entities", "entities": this.toIdArray() });
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.attack = function(unitId, queued = false, pushFront = false)
|
||||
{
|
||||
attack(unitId, queued = false, pushFront = false)
|
||||
{
|
||||
Engine.PostCommand(PlayerID, {
|
||||
"type": "attack",
|
||||
"entities": this.toIdArray(),
|
||||
|
|
@ -238,22 +240,22 @@ EntityCollection.prototype.attack = function(unitId, queued = false, pushFront =
|
|||
"pushFront": pushFront
|
||||
});
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
/** violent, aggressive, defensive, passive, standground */
|
||||
EntityCollection.prototype.setStance = function(stance)
|
||||
{
|
||||
/** 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 */
|
||||
EntityCollection.prototype.getCentrePosition = function()
|
||||
{
|
||||
/** Returns the average position of all units */
|
||||
getCentrePosition()
|
||||
{
|
||||
const sumPos = [0, 0];
|
||||
let count = 0;
|
||||
for (const ent of this._entities.values())
|
||||
|
|
@ -266,15 +268,15 @@ EntityCollection.prototype.getCentrePosition = function()
|
|||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
EntityCollection.prototype.getApproximatePosition = function(sample)
|
||||
{
|
||||
getApproximatePosition(sample)
|
||||
{
|
||||
const sumPos = [0, 0];
|
||||
let i = 0;
|
||||
for (const ent of this._entities.values())
|
||||
|
|
@ -289,25 +291,25 @@ EntityCollection.prototype.getApproximatePosition = function(sample)
|
|||
}
|
||||
|
||||
return i ? [sumPos[0]/i, sumPos[1]/i] : undefined;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.hasEntId = function(id)
|
||||
{
|
||||
hasEntId(id)
|
||||
{
|
||||
return this._entities.has(id);
|
||||
};
|
||||
}
|
||||
|
||||
/** Removes an entity from the collection, returns true if the entity was a member, false otherwise */
|
||||
EntityCollection.prototype.removeEnt = function(ent)
|
||||
{
|
||||
/** 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 */
|
||||
EntityCollection.prototype.addEnt = function(ent)
|
||||
{
|
||||
/** 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);
|
||||
|
|
@ -317,17 +319,17 @@ EntityCollection.prototype.addEnt = function(ent)
|
|||
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.
|
||||
*/
|
||||
EntityCollection.prototype.updateEnt = function(ent, force)
|
||||
{
|
||||
updateEnt(ent, force)
|
||||
{
|
||||
let passesFilters = true;
|
||||
for (const filter of this._filters)
|
||||
passesFilters = passesFilters && filter.func(ent);
|
||||
|
|
@ -340,29 +342,30 @@ EntityCollection.prototype.updateEnt = function(ent, force)
|
|||
}
|
||||
|
||||
return this.removeEnt(ent);
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.registerUpdates = function()
|
||||
{
|
||||
registerUpdates()
|
||||
{
|
||||
this._ai.registerUpdatingEntityCollection(this);
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.unregister = function()
|
||||
{
|
||||
unregister()
|
||||
{
|
||||
this._ai.removeUpdatingEntityCollection(this);
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.dynamicProperties = function()
|
||||
{
|
||||
dynamicProperties()
|
||||
{
|
||||
return this.dynamicProp;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.setUID = function(id)
|
||||
{
|
||||
setUID(id)
|
||||
{
|
||||
this._UID = id;
|
||||
};
|
||||
}
|
||||
|
||||
EntityCollection.prototype.getUID = function()
|
||||
{
|
||||
getUID()
|
||||
{
|
||||
return this._UID;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,60 +1,63 @@
|
|||
Resources = new Resources();
|
||||
|
||||
export function ResourcesManager(amounts = {}, population = 0)
|
||||
export class ResourcesManager
|
||||
{
|
||||
constructor(amounts = {}, population = 0)
|
||||
{
|
||||
for (const key of Resources.GetCodes())
|
||||
this[key] = amounts[key] || 0;
|
||||
|
||||
this.population = population > 0 ? population : 0;
|
||||
}
|
||||
}
|
||||
|
||||
ResourcesManager.prototype.reset = function()
|
||||
{
|
||||
reset()
|
||||
{
|
||||
for (const key of Resources.GetCodes())
|
||||
this[key] = 0;
|
||||
this.population = 0;
|
||||
};
|
||||
}
|
||||
|
||||
ResourcesManager.prototype.canAfford = function(that)
|
||||
{
|
||||
canAfford(that)
|
||||
{
|
||||
for (const key of Resources.GetCodes())
|
||||
if (this[key] < that[key])
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
ResourcesManager.prototype.add = function(that)
|
||||
{
|
||||
add(that)
|
||||
{
|
||||
for (const key of Resources.GetCodes())
|
||||
this[key] += that[key];
|
||||
this.population += that.population;
|
||||
};
|
||||
}
|
||||
|
||||
ResourcesManager.prototype.subtract = function(that)
|
||||
{
|
||||
subtract(that)
|
||||
{
|
||||
for (const key of Resources.GetCodes())
|
||||
this[key] -= that[key];
|
||||
this.population += that.population;
|
||||
};
|
||||
}
|
||||
|
||||
ResourcesManager.prototype.multiply = function(n)
|
||||
{
|
||||
multiply(n)
|
||||
{
|
||||
for (const key of Resources.GetCodes())
|
||||
this[key] *= n;
|
||||
this.population *= n;
|
||||
};
|
||||
}
|
||||
|
||||
ResourcesManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
const amounts = {};
|
||||
for (const key of Resources.GetCodes())
|
||||
amounts[key] = this[key];
|
||||
return { "amounts": amounts, "population": this.population };
|
||||
};
|
||||
}
|
||||
|
||||
ResourcesManager.prototype.Deserialize = function(data)
|
||||
{
|
||||
Deserialize(data)
|
||||
{
|
||||
for (const key in data.amounts)
|
||||
this[key] = data.amounts[key];
|
||||
this.population = data.population;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import { InfoMap } from "simulation/ai/common-api/map-module.js";
|
|||
import { Accessibility, TerrainAnalysis } from "simulation/ai/common-api/terrain-analysis.js";
|
||||
|
||||
/** Shared script handling templates and basic terrain analysis */
|
||||
export function SharedScript(settings)
|
||||
export class SharedScript
|
||||
{
|
||||
constructor(settings)
|
||||
{
|
||||
if (!settings)
|
||||
return;
|
||||
|
||||
|
|
@ -25,48 +27,48 @@ export function SharedScript(settings)
|
|||
this._entityCollectionsName = new Map();
|
||||
this._entityCollectionsByDynProp = {};
|
||||
this._entityCollectionsUID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** Return a simple object (using no classes etc) that will be serialized into saved games */
|
||||
SharedScript.prototype.Serialize = function()
|
||||
{
|
||||
/** Return a simple object (using no classes etc) that will be serialized into saved games */
|
||||
Serialize()
|
||||
{
|
||||
return {
|
||||
"players": this._players,
|
||||
"templatesModifications": this._templatesModifications,
|
||||
"entitiesModifications": this._entitiesModifications,
|
||||
"metadata": this._entityMetadata
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Called after the constructor when loading a saved game, with 'data' being
|
||||
* whatever Serialize() returned
|
||||
*/
|
||||
SharedScript.prototype.Deserialize = function(data)
|
||||
{
|
||||
Deserialize(data)
|
||||
{
|
||||
this._players = data.players;
|
||||
this._templatesModifications = data.templatesModifications;
|
||||
this._entitiesModifications = data.entitiesModifications;
|
||||
this._entityMetadata = data.metadata;
|
||||
|
||||
this.isDeserialized = true;
|
||||
};
|
||||
}
|
||||
|
||||
SharedScript.prototype.GetTemplate = function(name)
|
||||
{
|
||||
GetTemplate(name)
|
||||
{
|
||||
if (this._templates[name] === undefined)
|
||||
this._templates[name] = Engine.GetTemplate(name) || null;
|
||||
|
||||
return this._templates[name];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Initialize the shared component.
|
||||
* We need to know the initial state of the game for this, as we will use it.
|
||||
* This is called right at the end of the map generation.
|
||||
*/
|
||||
SharedScript.prototype.init = function(state, deserialization)
|
||||
{
|
||||
init(state, deserialization)
|
||||
{
|
||||
if (!deserialization)
|
||||
this._entitiesModifications = new Map();
|
||||
|
||||
|
|
@ -103,7 +105,7 @@ SharedScript.prototype.init = function(state, deserialization)
|
|||
}
|
||||
Engine.DumpImage("LandPassMap.png", landPassMap, this.passabilityMap.width, this.passabilityMap.height, 255);
|
||||
Engine.DumpImage("WaterPassMap.png", waterPassMap, this.passabilityMap.width, this.passabilityMap.height, 255);
|
||||
*/
|
||||
*/
|
||||
|
||||
this._entities = new Map();
|
||||
if (state.entities)
|
||||
|
|
@ -136,14 +138,14 @@ SharedScript.prototype.init = function(state, deserialization)
|
|||
this.gameState[player] = new GameState();
|
||||
this.gameState[player].init(this, state, player);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* General update of the shared script, before each AI's update
|
||||
* applies entity deltas, and each gamestate.
|
||||
*/
|
||||
SharedScript.prototype.onUpdate = function(state)
|
||||
{
|
||||
onUpdate(state)
|
||||
{
|
||||
if (this.isDeserialized)
|
||||
{
|
||||
this.init(state, true);
|
||||
|
|
@ -177,10 +179,10 @@ SharedScript.prototype.onUpdate = function(state)
|
|||
this.updateResourceMaps(this.events);
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
}
|
||||
|
||||
SharedScript.prototype.ApplyEntitiesDelta = function(state)
|
||||
{
|
||||
ApplyEntitiesDelta(state)
|
||||
{
|
||||
Engine.ProfileStart("Shared ApplyEntitiesDelta");
|
||||
|
||||
const foundationFinished = {};
|
||||
|
|
@ -285,10 +287,10 @@ SharedScript.prototype.ApplyEntitiesDelta = function(state)
|
|||
modif.set(change.variable, change.value);
|
||||
}
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
}
|
||||
|
||||
SharedScript.prototype.ApplyTemplatesDelta = function(state)
|
||||
{
|
||||
ApplyTemplatesDelta(state)
|
||||
{
|
||||
Engine.ProfileStart("Shared ApplyTemplatesDelta");
|
||||
|
||||
for (const player in state.changedTemplateInfo)
|
||||
|
|
@ -309,10 +311,10 @@ SharedScript.prototype.ApplyTemplatesDelta = function(state)
|
|||
this._templatesModifications =
|
||||
Object.fromEntries(Object.entries(this._templatesModifications).sort());
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
}
|
||||
|
||||
SharedScript.prototype.registerUpdatingEntityCollection = function(entCollection)
|
||||
{
|
||||
registerUpdatingEntityCollection(entCollection)
|
||||
{
|
||||
entCollection.setUID(this._entityCollectionsUID);
|
||||
this._entityCollections.set(this._entityCollectionsUID, entCollection);
|
||||
for (const prop of entCollection.dynamicProperties())
|
||||
|
|
@ -322,10 +324,10 @@ SharedScript.prototype.registerUpdatingEntityCollection = function(entCollection
|
|||
this._entityCollectionsByDynProp[prop].set(this._entityCollectionsUID, entCollection);
|
||||
}
|
||||
this._entityCollectionsUID++;
|
||||
};
|
||||
}
|
||||
|
||||
SharedScript.prototype.removeUpdatingEntityCollection = function(entCollection)
|
||||
{
|
||||
removeUpdatingEntityCollection(entCollection)
|
||||
{
|
||||
const uid = entCollection.getUID();
|
||||
|
||||
if (this._entityCollections.has(uid))
|
||||
|
|
@ -334,19 +336,19 @@ SharedScript.prototype.removeUpdatingEntityCollection = function(entCollection)
|
|||
for (const prop of entCollection.dynamicProperties())
|
||||
if (this._entityCollectionsByDynProp[prop].has(uid))
|
||||
this._entityCollectionsByDynProp[prop].delete(uid);
|
||||
};
|
||||
}
|
||||
|
||||
SharedScript.prototype.updateEntityCollections = function(property, ent)
|
||||
{
|
||||
updateEntityCollections(property, ent)
|
||||
{
|
||||
if (this._entityCollectionsByDynProp[property] === undefined)
|
||||
return;
|
||||
|
||||
for (const entCol of this._entityCollectionsByDynProp[property].values())
|
||||
entCol.updateEnt(ent);
|
||||
};
|
||||
}
|
||||
|
||||
SharedScript.prototype.setMetadata = function(player, ent, key, value)
|
||||
{
|
||||
setMetadata(player, ent, key, value)
|
||||
{
|
||||
let metadata = this._entityMetadata[player][ent.id()];
|
||||
if (!metadata)
|
||||
{
|
||||
|
|
@ -357,15 +359,15 @@ SharedScript.prototype.setMetadata = function(player, ent, key, value)
|
|||
|
||||
this.updateEntityCollections('metadata', ent);
|
||||
this.updateEntityCollections('metadata.' + key, ent);
|
||||
};
|
||||
}
|
||||
|
||||
SharedScript.prototype.getMetadata = function(player, ent, key)
|
||||
{
|
||||
getMetadata(player, ent, key)
|
||||
{
|
||||
return this._entityMetadata[player][ent.id()]?.[key];
|
||||
};
|
||||
}
|
||||
|
||||
SharedScript.prototype.deleteMetadata = function(player, ent, key)
|
||||
{
|
||||
deleteMetadata(player, ent, key)
|
||||
{
|
||||
const metadata = this._entityMetadata[player][ent.id()];
|
||||
|
||||
if (!metadata || !(key in metadata))
|
||||
|
|
@ -375,11 +377,11 @@ SharedScript.prototype.deleteMetadata = function(player, ent, key)
|
|||
this.updateEntityCollections('metadata', ent);
|
||||
this.updateEntityCollections('metadata.' + key, ent);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** creates a map of resource density */
|
||||
SharedScript.prototype.createResourceMaps = function()
|
||||
{
|
||||
/** creates a map of resource density */
|
||||
createResourceMaps()
|
||||
{
|
||||
for (const resource of Resources.GetCodes())
|
||||
{
|
||||
if (this.resourceMaps[resource] ||
|
||||
|
|
@ -392,13 +394,13 @@ SharedScript.prototype.createResourceMaps = function()
|
|||
}
|
||||
for (const ent of this._entities.values())
|
||||
this.addEntityToResourceMap(ent);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @param {Object} events - The events from a turn.
|
||||
*/
|
||||
SharedScript.prototype.updateResourceMaps = function(events)
|
||||
{
|
||||
updateResourceMaps(events)
|
||||
{
|
||||
if (events.Destroy.some(e => this.resources.includes(e.entity)))
|
||||
{
|
||||
this.resources = [];
|
||||
|
|
@ -409,30 +411,30 @@ SharedScript.prototype.updateResourceMaps = function(events)
|
|||
for (const e of events.Create)
|
||||
if (e.entity && this._entities.has(e.entity))
|
||||
this.addEntityToResourceMap(this._entities.get(e.entity));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @param {entity} entity - The entity to add to the resource map.
|
||||
*/
|
||||
SharedScript.prototype.addEntityToResourceMap = function(entity)
|
||||
{
|
||||
addEntityToResourceMap(entity)
|
||||
{
|
||||
this.changeEntityInResourceMapHelper(entity, 1);
|
||||
this.resources.push(entity.id());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @param {entity} entity - The entity to remove from the resource map.
|
||||
*/
|
||||
SharedScript.prototype.removeEntityFromResourceMap = function(entity)
|
||||
{
|
||||
removeEntityFromResourceMap(entity)
|
||||
{
|
||||
this.changeEntityInResourceMapHelper(entity, -1);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @param {entity} ent - The entity to add to the resource map.
|
||||
*/
|
||||
SharedScript.prototype.changeEntityInResourceMapHelper = function(ent, multiplication = 1)
|
||||
{
|
||||
changeEntityInResourceMapHelper(ent, multiplication = 1)
|
||||
{
|
||||
if (!ent)
|
||||
return;
|
||||
const entPos = ent.position();
|
||||
|
|
@ -449,4 +451,5 @@ SharedScript.prototype.changeEntityInResourceMapHelper = function(ent, multiplic
|
|||
this.resourceMaps[resource].addInfluence(x, y, this.influenceRadius[grp] / cellSize, strength / 2, "constant");
|
||||
this.resourceMaps[resource].addInfluence(x, y, this.influenceRadius[grp] / cellSize, strength / 2);
|
||||
this.ccResourceMaps[resource].addInfluence(x, y, this.ccInfluenceRadius[grp] / cellSize, strength, "constant");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
LoadModificationTemplates();
|
||||
|
||||
/** Wrapper around a technology template */
|
||||
export function Technology(templateName)
|
||||
export class Technology
|
||||
{
|
||||
constructor(templateName)
|
||||
{
|
||||
this._templateName = templateName;
|
||||
const template = TechnologyTemplates.Get(templateName);
|
||||
|
||||
|
|
@ -16,47 +18,47 @@ export function Technology(templateName)
|
|||
// check if it only defines a pair:
|
||||
this._definesPair = !!template.pair;
|
||||
this._template = template;
|
||||
}
|
||||
}
|
||||
|
||||
/** returns generic, or specific if civ provided. */
|
||||
Technology.prototype.name = function(civ)
|
||||
{
|
||||
/** returns generic, or specific if civ provided. */
|
||||
name(civ)
|
||||
{
|
||||
if (civ === undefined)
|
||||
return this._template.genericName;
|
||||
|
||||
if (this._template.specificName === undefined || this._template.specificName[civ] === undefined)
|
||||
return undefined;
|
||||
return this._template.specificName[civ];
|
||||
};
|
||||
}
|
||||
|
||||
Technology.prototype.pairDef = function()
|
||||
{
|
||||
pairDef()
|
||||
{
|
||||
return this._definesPair;
|
||||
};
|
||||
}
|
||||
|
||||
/** in case this defines a pair only, returns the two paired technologies. */
|
||||
Technology.prototype.getPairedTechs = function()
|
||||
{
|
||||
/** in case this defines a pair only, returns the two paired technologies. */
|
||||
getPairedTechs()
|
||||
{
|
||||
if (!this._definesPair)
|
||||
return undefined;
|
||||
|
||||
return this._template.pair.map(name => new Technology(name));
|
||||
};
|
||||
}
|
||||
|
||||
Technology.prototype.pair = function()
|
||||
{
|
||||
pair()
|
||||
{
|
||||
return this._template.partOfPair;
|
||||
};
|
||||
}
|
||||
|
||||
Technology.prototype.pairedWith = function()
|
||||
{
|
||||
pairedWith()
|
||||
{
|
||||
if (!this._template.partOfPair)
|
||||
return undefined;
|
||||
return this._pairedWith;
|
||||
};
|
||||
}
|
||||
|
||||
Technology.prototype.cost = function(researcher)
|
||||
{
|
||||
cost(researcher)
|
||||
{
|
||||
if (!this._template.cost)
|
||||
return undefined;
|
||||
const cost = {};
|
||||
|
|
@ -67,10 +69,10 @@ Technology.prototype.cost = function(researcher)
|
|||
cost[type] *= researcher.techCostMultiplier(type);
|
||||
}
|
||||
return cost;
|
||||
};
|
||||
}
|
||||
|
||||
Technology.prototype.costSum = function(researcher)
|
||||
{
|
||||
costSum(researcher)
|
||||
{
|
||||
const cost = this.cost(researcher);
|
||||
if (!cost)
|
||||
return 0;
|
||||
|
|
@ -78,47 +80,48 @@ Technology.prototype.costSum = function(researcher)
|
|||
for (const type in cost)
|
||||
ret += cost[type];
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
Technology.prototype.researchTime = function()
|
||||
{
|
||||
researchTime()
|
||||
{
|
||||
return this._template.researchTime || 0;
|
||||
};
|
||||
}
|
||||
|
||||
Technology.prototype.requirements = function(civ)
|
||||
{
|
||||
requirements(civ)
|
||||
{
|
||||
return DeriveTechnologyRequirements(this._template, civ);
|
||||
};
|
||||
}
|
||||
|
||||
Technology.prototype.autoResearch = function()
|
||||
{
|
||||
autoResearch()
|
||||
{
|
||||
if (!this._template.autoResearch)
|
||||
return undefined;
|
||||
return this._template.autoResearch;
|
||||
};
|
||||
}
|
||||
|
||||
Technology.prototype.supersedes = function()
|
||||
{
|
||||
supersedes()
|
||||
{
|
||||
if (!this._template.supersedes)
|
||||
return undefined;
|
||||
return this._template.supersedes;
|
||||
};
|
||||
}
|
||||
|
||||
Technology.prototype.modifications = function()
|
||||
{
|
||||
modifications()
|
||||
{
|
||||
if (!this._template.modifications)
|
||||
return undefined;
|
||||
return this._template.modifications;
|
||||
};
|
||||
}
|
||||
|
||||
Technology.prototype.affects = function()
|
||||
{
|
||||
affects()
|
||||
{
|
||||
if (!this._template.affects)
|
||||
return undefined;
|
||||
return this._template.affects;
|
||||
};
|
||||
}
|
||||
|
||||
Technology.prototype.isAffected = function(classes)
|
||||
{
|
||||
isAffected(classes)
|
||||
{
|
||||
return this._template.affects && this._template.affects.some(affect => MatchesClassList(classes, affect));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ import { Queue } from "simulation/ai/petra/queue.js";
|
|||
import { QueueManager } from "simulation/ai/petra/queueManager.js";
|
||||
import { gameAnalysis } from "simulation/ai/petra/startingStrategy.js";
|
||||
|
||||
export function PetraBot(settings)
|
||||
export class PetraBot extends BaseAI
|
||||
{
|
||||
BaseAI.call(this, settings);
|
||||
constructor(settings)
|
||||
{
|
||||
super(settings);
|
||||
|
||||
// played turn, because Petra doesn't play every turn.
|
||||
this.turn = 0;
|
||||
|
|
@ -25,12 +27,10 @@ export function PetraBot(settings)
|
|||
this.Config = new Config(settings.difficulty, settings.behavior);
|
||||
|
||||
this.savedEvents = {};
|
||||
}
|
||||
}
|
||||
|
||||
PetraBot.prototype = Object.create(BaseAI.prototype);
|
||||
|
||||
PetraBot.prototype.CustomInit = function(gameState)
|
||||
{
|
||||
CustomInit(gameState)
|
||||
{
|
||||
if (this.isDeserialized)
|
||||
{
|
||||
// WARNING: the deserializations should not modify the metadatas infos inside their init functions
|
||||
|
|
@ -88,10 +88,10 @@ PetraBot.prototype.CustomInit = function(gameState)
|
|||
// Try to analyze our starting position and set a strategy.
|
||||
this.canPlay = gameAnalysis(this.HQ, gameState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
PetraBot.prototype.OnUpdate = function(sharedScript)
|
||||
{
|
||||
OnUpdate(sharedScript)
|
||||
{
|
||||
if (this.isDeserialized)
|
||||
this.Init(PlayerID, sharedScript);
|
||||
|
||||
|
|
@ -133,10 +133,10 @@ PetraBot.prototype.OnUpdate = function(sharedScript)
|
|||
}
|
||||
|
||||
this.turn++;
|
||||
};
|
||||
}
|
||||
|
||||
PetraBot.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
if (this.isDeserialized)
|
||||
return this.data;
|
||||
|
||||
|
|
@ -167,10 +167,12 @@ PetraBot.prototype.Serialize = function()
|
|||
"queueManager": this.queueManager.Serialize(),
|
||||
"HQ": this.HQ.Serialize()
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
PetraBot.prototype.Deserialize = function(data, sharedScript)
|
||||
{
|
||||
Deserialize(data, sharedScript)
|
||||
{
|
||||
this.isDeserialized = true;
|
||||
this.data = data;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,43 +7,45 @@ import * as difficulty from "simulation/ai/petra/difficultyLevel.js";
|
|||
import { allowCapture, getLandAccess } from "simulation/ai/petra/entityExtend.js";
|
||||
import { Worker } from "simulation/ai/petra/worker.js";
|
||||
|
||||
export function AttackManager(config)
|
||||
export class AttackManager
|
||||
{
|
||||
totalNumber = 0;
|
||||
attackNumber = 0;
|
||||
rushNumber = 0;
|
||||
raidNumber = 0;
|
||||
upcomingAttacks = {
|
||||
[AttackPlan.TYPE_RUSH]: [],
|
||||
[AttackPlan.TYPE_RAID]: [],
|
||||
[AttackPlan.TYPE_DEFAULT]: [],
|
||||
[AttackPlan.TYPE_HUGE_ATTACK]: []
|
||||
};
|
||||
startedAttacks = {
|
||||
[AttackPlan.TYPE_RUSH]: [],
|
||||
[AttackPlan.TYPE_RAID]: [],
|
||||
[AttackPlan.TYPE_DEFAULT]: [],
|
||||
[AttackPlan.TYPE_HUGE_ATTACK]: []
|
||||
};
|
||||
bombingAttacks = new Map();// Temporary attacks for siege units while waiting their current attack to start
|
||||
debugTime = 0;
|
||||
maxRushes = 0;
|
||||
rushSize = [];
|
||||
currentEnemyPlayer = undefined; // enemy player we are currently targeting
|
||||
defeated = {};
|
||||
|
||||
constructor(config)
|
||||
{
|
||||
this.Config = config;
|
||||
}
|
||||
|
||||
this.totalNumber = 0;
|
||||
this.attackNumber = 0;
|
||||
this.rushNumber = 0;
|
||||
this.raidNumber = 0;
|
||||
this.upcomingAttacks = {
|
||||
[AttackPlan.TYPE_RUSH]: [],
|
||||
[AttackPlan.TYPE_RAID]: [],
|
||||
[AttackPlan.TYPE_DEFAULT]: [],
|
||||
[AttackPlan.TYPE_HUGE_ATTACK]: []
|
||||
};
|
||||
this.startedAttacks = {
|
||||
[AttackPlan.TYPE_RUSH]: [],
|
||||
[AttackPlan.TYPE_RAID]: [],
|
||||
[AttackPlan.TYPE_DEFAULT]: [],
|
||||
[AttackPlan.TYPE_HUGE_ATTACK]: []
|
||||
};
|
||||
this.bombingAttacks = new Map();// Temporary attacks for siege units while waiting their current attack to start
|
||||
this.debugTime = 0;
|
||||
this.maxRushes = 0;
|
||||
this.rushSize = [];
|
||||
this.currentEnemyPlayer = undefined; // enemy player we are currently targeting
|
||||
this.defeated = {};
|
||||
}
|
||||
|
||||
/** More initialisation for stuff that needs the gameState */
|
||||
AttackManager.prototype.init = function(gameState)
|
||||
{
|
||||
/** More initialisation for stuff that needs the gameState */
|
||||
init(gameState)
|
||||
{
|
||||
this.outOfPlan = gameState.getOwnUnits().filter(filters.byMetadata(PlayerID, "plan", -1));
|
||||
this.outOfPlan.registerUpdates();
|
||||
};
|
||||
}
|
||||
|
||||
AttackManager.prototype.setRushes = function(allowed)
|
||||
{
|
||||
setRushes(allowed)
|
||||
{
|
||||
if (this.Config.personality.aggressive > this.Config.personalityCut.strong && allowed > 2)
|
||||
{
|
||||
this.maxRushes = 3;
|
||||
|
|
@ -59,10 +61,10 @@ AttackManager.prototype.setRushes = function(allowed)
|
|||
this.maxRushes = 1;
|
||||
this.rushSize = [ 20 ];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AttackManager.prototype.checkEvents = function(gameState, events)
|
||||
{
|
||||
checkEvents(gameState, events)
|
||||
{
|
||||
for (const evt of events.PlayerDefeated)
|
||||
this.defeated[evt.playerId] = true;
|
||||
|
||||
|
|
@ -134,13 +136,13 @@ AttackManager.prototype.checkEvents = function(gameState, events)
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check for any structure in range from within our territory, and bomb it
|
||||
*/
|
||||
AttackManager.prototype.assignBombers = function(gameState)
|
||||
{
|
||||
assignBombers(gameState)
|
||||
{
|
||||
// First some cleaning of current bombing attacks
|
||||
for (const [targetId, unitIds] of this.bombingAttacks)
|
||||
{
|
||||
|
|
@ -247,14 +249,14 @@ AttackManager.prototype.assignBombers = function(gameState)
|
|||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Some functions are run every turn
|
||||
* Others once in a while
|
||||
*/
|
||||
AttackManager.prototype.update = function(gameState, queues, events)
|
||||
{
|
||||
update(gameState, queues, events)
|
||||
{
|
||||
if (this.Config.debug > 2 && gameState.ai.elapsedTime > this.debugTime + 60)
|
||||
{
|
||||
this.debugTime = gameState.ai.elapsedTime;
|
||||
|
|
@ -439,10 +441,10 @@ AttackManager.prototype.update = function(gameState, queues, events)
|
|||
// Check if we have some unused ranged siege unit which could do something useful while waiting
|
||||
if (this.Config.difficulty > difficulty.VERY_EASY && gameState.ai.playedTurn % 5 == 0)
|
||||
this.assignBombers(gameState);
|
||||
};
|
||||
}
|
||||
|
||||
AttackManager.prototype.getPlan = function(planName)
|
||||
{
|
||||
getPlan(planName)
|
||||
{
|
||||
for (const attackType in this.upcomingAttacks)
|
||||
{
|
||||
for (const attack of this.upcomingAttacks[attackType])
|
||||
|
|
@ -456,24 +458,24 @@ AttackManager.prototype.getPlan = function(planName)
|
|||
return attack;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
AttackManager.prototype.pausePlan = function(planName)
|
||||
{
|
||||
pausePlan(planName)
|
||||
{
|
||||
const attack = this.getPlan(planName);
|
||||
if (attack)
|
||||
attack.setPaused(true);
|
||||
};
|
||||
}
|
||||
|
||||
AttackManager.prototype.unpausePlan = function(planName)
|
||||
{
|
||||
unpausePlan(planName)
|
||||
{
|
||||
const attack = this.getPlan(planName);
|
||||
if (attack)
|
||||
attack.setPaused(false);
|
||||
};
|
||||
}
|
||||
|
||||
AttackManager.prototype.pauseAllPlans = function()
|
||||
{
|
||||
pauseAllPlans()
|
||||
{
|
||||
for (const attackType in this.upcomingAttacks)
|
||||
for (const attack of this.upcomingAttacks[attackType])
|
||||
attack.setPaused(true);
|
||||
|
|
@ -481,10 +483,10 @@ AttackManager.prototype.pauseAllPlans = function()
|
|||
for (const attackType in this.startedAttacks)
|
||||
for (const attack of this.startedAttacks[attackType])
|
||||
attack.setPaused(true);
|
||||
};
|
||||
}
|
||||
|
||||
AttackManager.prototype.unpauseAllPlans = function()
|
||||
{
|
||||
unpauseAllPlans()
|
||||
{
|
||||
for (const attackType in this.upcomingAttacks)
|
||||
for (const attack of this.upcomingAttacks[attackType])
|
||||
attack.setPaused(false);
|
||||
|
|
@ -492,20 +494,20 @@ AttackManager.prototype.unpauseAllPlans = function()
|
|||
for (const attackType in this.startedAttacks)
|
||||
for (const attack of this.startedAttacks[attackType])
|
||||
attack.setPaused(false);
|
||||
};
|
||||
}
|
||||
|
||||
AttackManager.prototype.getAttackInPreparation = function(type)
|
||||
{
|
||||
getAttackInPreparation(type)
|
||||
{
|
||||
return this.upcomingAttacks[type].length ? this.upcomingAttacks[type][0] : undefined;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Determine which player should be attacked: when called when starting the attack,
|
||||
* attack.targetPlayer is undefined and in that case, we keep track of the chosen target
|
||||
* for future attacks.
|
||||
*/
|
||||
AttackManager.prototype.getEnemyPlayer = function(gameState, attack)
|
||||
{
|
||||
getEnemyPlayer(gameState, attack)
|
||||
{
|
||||
let enemyPlayer;
|
||||
|
||||
// First check if there is a preferred enemy based on our victory conditions.
|
||||
|
|
@ -612,14 +614,14 @@ AttackManager.prototype.getEnemyPlayer = function(gameState, attack)
|
|||
if (attack.targetPlayer === undefined)
|
||||
this.currentEnemyPlayer = enemyPlayer;
|
||||
return enemyPlayer;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Target the player with the most advanced wonder.
|
||||
* TODO currently the first built wonder is kept, should chek on the minimum wonderDuration left instead.
|
||||
*/
|
||||
AttackManager.prototype.getWonderEnemyPlayer = function(gameState, attack)
|
||||
{
|
||||
getWonderEnemyPlayer(gameState, attack)
|
||||
{
|
||||
let enemyPlayer;
|
||||
let enemyWonder;
|
||||
let moreAdvanced;
|
||||
|
|
@ -645,13 +647,13 @@ AttackManager.prototype.getWonderEnemyPlayer = function(gameState, attack)
|
|||
this.currentEnemyPlayer = enemyPlayer;
|
||||
}
|
||||
return enemyPlayer;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Target the player with the most relics (including gaia).
|
||||
*/
|
||||
AttackManager.prototype.getRelicEnemyPlayer = function(gameState, attack)
|
||||
{
|
||||
getRelicEnemyPlayer(gameState, attack)
|
||||
{
|
||||
let enemyPlayer;
|
||||
const allRelics = gameState.updatingGlobalCollection("allRelics", filters.byClass("Relic"));
|
||||
let maxRelicsOwned = 0;
|
||||
|
|
@ -675,11 +677,11 @@ AttackManager.prototype.getRelicEnemyPlayer = function(gameState, attack)
|
|||
gameState.ai.HQ.victoryManager.resetCaptureGaiaRelic(gameState);
|
||||
}
|
||||
return enemyPlayer;
|
||||
};
|
||||
}
|
||||
|
||||
/** f.e. if we have changed diplomacy with another player. */
|
||||
AttackManager.prototype.cancelAttacksAgainstPlayer = function(gameState, player)
|
||||
{
|
||||
/** f.e. if we have changed diplomacy with another player. */
|
||||
cancelAttacksAgainstPlayer(gameState, player)
|
||||
{
|
||||
for (const attackType in this.upcomingAttacks)
|
||||
for (const attack of this.upcomingAttacks[attackType])
|
||||
if (attack.targetPlayer === player)
|
||||
|
|
@ -695,10 +697,10 @@ AttackManager.prototype.cancelAttacksAgainstPlayer = function(gameState, player)
|
|||
this.startedAttacks[attackType].splice(i--, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AttackManager.prototype.raidTargetEntity = function(gameState, ent)
|
||||
{
|
||||
raidTargetEntity(gameState, ent)
|
||||
{
|
||||
const data = { "target": ent };
|
||||
const attackPlan = new AttackPlan(gameState, this.Config, this.totalNumber,
|
||||
AttackPlan.TYPE_RAID, data, false);
|
||||
|
|
@ -711,13 +713,13 @@ AttackManager.prototype.raidTargetEntity = function(gameState, ent)
|
|||
attackPlan.init(gameState);
|
||||
this.upcomingAttacks[AttackPlan.TYPE_RAID].push(attackPlan);
|
||||
return attackPlan;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Return the number of units from any of our attacking armies around this position
|
||||
*/
|
||||
AttackManager.prototype.numAttackingUnitsAround = function(pos, dist)
|
||||
{
|
||||
numAttackingUnitsAround(pos, dist)
|
||||
{
|
||||
let num = 0;
|
||||
for (const attackType in this.startedAttacks)
|
||||
for (const attack of this.startedAttacks[attackType])
|
||||
|
|
@ -728,16 +730,16 @@ AttackManager.prototype.numAttackingUnitsAround = function(pos, dist)
|
|||
num += attack.unitCollection.length;
|
||||
}
|
||||
return num;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Switch defense armies into an attack one against the given target
|
||||
* data.range: transform all defense armies inside range of the target into a new attack
|
||||
* data.armyID: transform only the defense army ID into a new attack
|
||||
* data.uniqueTarget: the attack will stop when the target is destroyed or captured
|
||||
*/
|
||||
AttackManager.prototype.switchDefenseToAttack = function(gameState, target, data)
|
||||
{
|
||||
switchDefenseToAttack(gameState, target, data)
|
||||
{
|
||||
if (!target || !target.position())
|
||||
return false;
|
||||
if (!data.range && !data.armyID)
|
||||
|
|
@ -797,10 +799,10 @@ AttackManager.prototype.switchDefenseToAttack = function(gameState, target, data
|
|||
attackPlan.target = target;
|
||||
attackPlan.state = AttackPlan.STATE_ARRIVED;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
AttackManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
const properties = {
|
||||
"totalNumber": this.totalNumber,
|
||||
"attackNumber": this.attackNumber,
|
||||
|
|
@ -830,10 +832,10 @@ AttackManager.prototype.Serialize = function()
|
|||
}
|
||||
|
||||
return { "properties": properties, "upcomingAttacks": upcomingAttacks, "startedAttacks": startedAttacks };
|
||||
};
|
||||
}
|
||||
|
||||
AttackManager.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
Deserialize(gameState, data)
|
||||
{
|
||||
for (const key in data.properties)
|
||||
this[key] = data.properties[key];
|
||||
|
||||
|
|
@ -864,4 +866,5 @@ AttackManager.prototype.Deserialize = function(gameState, data)
|
|||
this.startedAttacks[key].push(attack);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@ import { Worker } from "simulation/ai/petra/worker.js";
|
|||
* When @c deserialized is true, do not call any random function inside constructor
|
||||
* as that would cause oos.
|
||||
*/
|
||||
export function AttackPlan(gameState, config, uniqueID, type = AttackPlan.TYPE_DEFAULT, data, deserialized)
|
||||
export class AttackPlan
|
||||
{
|
||||
constructor(gameState, config, uniqueID, type = AttackPlan.TYPE_DEFAULT, data, deserialized)
|
||||
{
|
||||
this.Config = config;
|
||||
this.name = uniqueID;
|
||||
this.type = type;
|
||||
|
|
@ -239,31 +241,31 @@ export function AttackPlan(gameState, config, uniqueID, type = AttackPlan.TYPE_D
|
|||
this.isBlocked = false; // true when this attack faces walls
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
AttackPlan.PREPARATION_FAILED = 0;
|
||||
AttackPlan.PREPARATION_KEEP_GOING = 1;
|
||||
AttackPlan.PREPARATION_START = 2;
|
||||
static PREPARATION_FAILED = 0;
|
||||
static PREPARATION_KEEP_GOING = 1;
|
||||
static PREPARATION_START = 2;
|
||||
|
||||
AttackPlan.SIEGE_NOT_TESTED = 0;
|
||||
AttackPlan.SIEGE_NO_TRAINER = 1;
|
||||
static SIEGE_NOT_TESTED = 0;
|
||||
static SIEGE_NO_TRAINER = 1;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Siege added in build orders
|
||||
*/
|
||||
AttackPlan.SIEGE_ADDED = 2;
|
||||
static SIEGE_ADDED = 2;
|
||||
|
||||
AttackPlan.STATE_UNEXECUTED = "unexecuted";
|
||||
AttackPlan.STATE_COMPLETING = "completing";
|
||||
AttackPlan.STATE_ARRIVED = "arrived";
|
||||
static STATE_UNEXECUTED = "unexecuted";
|
||||
static STATE_COMPLETING = "completing";
|
||||
static STATE_ARRIVED = "arrived";
|
||||
|
||||
AttackPlan.TYPE_DEFAULT = "Attack";
|
||||
AttackPlan.TYPE_HUGE_ATTACK = "HugeAttack";
|
||||
AttackPlan.TYPE_RAID = "Raid";
|
||||
AttackPlan.TYPE_RUSH = "Rush";
|
||||
static TYPE_DEFAULT = "Attack";
|
||||
static TYPE_HUGE_ATTACK = "HugeAttack";
|
||||
static TYPE_RAID = "Raid";
|
||||
static TYPE_RUSH = "Rush";
|
||||
|
||||
AttackPlan.prototype.init = function(gameState)
|
||||
{
|
||||
init(gameState)
|
||||
{
|
||||
this.queue = gameState.ai.queues["plan_" + this.name];
|
||||
this.queueChamp = gameState.ai.queues["plan_" + this.name +"_champ"];
|
||||
this.queueSiege = gameState.ai.queues["plan_" + this.name +"_siege"];
|
||||
|
|
@ -284,39 +286,39 @@ AttackPlan.prototype.init = function(gameState)
|
|||
if (this.canBuildUnits)
|
||||
this.buildOrders.push([0, Unit.classes, this.unit[cat], Unit, cat]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.getName = function()
|
||||
{
|
||||
getName()
|
||||
{
|
||||
return this.name;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.getType = function()
|
||||
{
|
||||
getType()
|
||||
{
|
||||
return this.type;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.isStarted = function()
|
||||
{
|
||||
isStarted()
|
||||
{
|
||||
return this.state !== AttackPlan.STATE_UNEXECUTED && this.state !== AttackPlan.STATE_COMPLETING;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.isPaused = function()
|
||||
{
|
||||
isPaused()
|
||||
{
|
||||
return this.paused;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.setPaused = function(boolValue)
|
||||
{
|
||||
setPaused(boolValue)
|
||||
{
|
||||
this.paused = boolValue;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns true if the attack can be executed at the current time
|
||||
* Basically it checks we have enough units.
|
||||
*/
|
||||
AttackPlan.prototype.canStart = function()
|
||||
{
|
||||
canStart()
|
||||
{
|
||||
if (!this.canBuildUnits)
|
||||
return true;
|
||||
|
||||
|
|
@ -325,10 +327,10 @@ AttackPlan.prototype.canStart = function()
|
|||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.mustStart = function()
|
||||
{
|
||||
mustStart()
|
||||
{
|
||||
if (this.isPaused())
|
||||
return false;
|
||||
|
||||
|
|
@ -355,10 +357,10 @@ AttackPlan.prototype.mustStart = function()
|
|||
return this.type === AttackPlan.TYPE_RAID && this.target && this.target.foundationProgress() &&
|
||||
this.target.foundationProgress() > 50;
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.forceStart = function()
|
||||
{
|
||||
forceStart()
|
||||
{
|
||||
for (const unitCat in this.unitStat)
|
||||
{
|
||||
const Unit = this.unitStat[unitCat];
|
||||
|
|
@ -366,25 +368,25 @@ AttackPlan.prototype.forceStart = function()
|
|||
Unit.minSize = 0;
|
||||
}
|
||||
this.forced = true;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.emptyQueues = function()
|
||||
{
|
||||
emptyQueues()
|
||||
{
|
||||
this.queue.empty();
|
||||
this.queueChamp.empty();
|
||||
this.queueSiege.empty();
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.removeQueues = function(gameState)
|
||||
{
|
||||
removeQueues(gameState)
|
||||
{
|
||||
gameState.ai.queueManager.removeQueue("plan_" + this.name);
|
||||
gameState.ai.queueManager.removeQueue("plan_" + this.name + "_champ");
|
||||
gameState.ai.queueManager.removeQueue("plan_" + this.name + "_siege");
|
||||
};
|
||||
}
|
||||
|
||||
/** Adds a build order. If resetQueue is true, this will reset the queue. */
|
||||
AttackPlan.prototype.addBuildOrder = function(gameState, name, unitStats, resetQueue)
|
||||
{
|
||||
/** Adds a build order. If resetQueue is true, this will reset the queue. */
|
||||
addBuildOrder(gameState, name, unitStats, resetQueue)
|
||||
{
|
||||
if (!this.isStarted())
|
||||
{
|
||||
// no minsize as we don't want the plan to fail at the last minute though.
|
||||
|
|
@ -396,10 +398,10 @@ AttackPlan.prototype.addBuildOrder = function(gameState, name, unitStats, resetQ
|
|||
if (resetQueue)
|
||||
this.emptyQueues();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.addSiegeUnits = function(gameState)
|
||||
{
|
||||
addSiegeUnits(gameState)
|
||||
{
|
||||
if (this.siegeState === AttackPlan.SIEGE_ADDED || this.state !== AttackPlan.STATE_UNEXECUTED)
|
||||
return false;
|
||||
|
||||
|
|
@ -450,11 +452,11 @@ AttackPlan.prototype.addSiegeUnits = function(gameState)
|
|||
"classes": classes[i], "interests": [ ["siegeStrength", 3] ] };
|
||||
this.addBuildOrder(gameState, "Siege", stat, true);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** Three returns possible: 1 is "keep going", 0 is "failed plan", 2 is "start". */
|
||||
AttackPlan.prototype.updatePreparation = function(gameState)
|
||||
{
|
||||
/** Three returns possible: 1 is "keep going", 0 is "failed plan", 2 is "start". */
|
||||
updatePreparation(gameState)
|
||||
{
|
||||
// the completing step is used to return resources and regroup the units
|
||||
// so we check that we have no more forced order before starting the attack
|
||||
if (this.state === AttackPlan.STATE_COMPLETING)
|
||||
|
|
@ -618,10 +620,10 @@ AttackPlan.prototype.updatePreparation = function(gameState)
|
|||
// reset all queued units
|
||||
this.removeQueues(gameState);
|
||||
return AttackPlan.PREPARATION_KEEP_GOING;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.trainMoreUnits = function(gameState)
|
||||
{
|
||||
trainMoreUnits(gameState)
|
||||
{
|
||||
// let's sort by training advancement, ie 'current size / target size'
|
||||
// count the number of queued units too.
|
||||
// substract priority.
|
||||
|
|
@ -716,10 +718,10 @@ AttackPlan.prototype.trainMoreUnits = function(gameState)
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.assignUnits = function(gameState)
|
||||
{
|
||||
assignUnits(gameState)
|
||||
{
|
||||
const plan = this.name;
|
||||
let added = false;
|
||||
// If we can not build units, assign all available except those affected to allied defense to the current attack.
|
||||
|
|
@ -807,10 +809,10 @@ AttackPlan.prototype.assignUnits = function(gameState)
|
|||
added = true;
|
||||
}
|
||||
return added;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.isAvailableUnit = function(gameState, ent)
|
||||
{
|
||||
isAvailableUnit(gameState, ent)
|
||||
{
|
||||
if (!ent.position())
|
||||
return false;
|
||||
if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1 ||
|
||||
|
|
@ -819,11 +821,11 @@ AttackPlan.prototype.isAvailableUnit = function(gameState, ent)
|
|||
if (gameState.ai.HQ.victoryManager.criticalEnts.has(ent.id()) && (this.overseas || ent.healthLevel() < 0.8))
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** Reassign one (at each turn) FastMoving unit to fasten raid preparation. */
|
||||
AttackPlan.prototype.reassignFastUnit = function(gameState)
|
||||
{
|
||||
/** Reassign one (at each turn) FastMoving unit to fasten raid preparation. */
|
||||
reassignFastUnit(gameState)
|
||||
{
|
||||
for (const ent of this.unitCollection.values())
|
||||
{
|
||||
if (!ent.position() || ent.getMetadata(PlayerID, "transport") !== undefined)
|
||||
|
|
@ -836,10 +838,10 @@ AttackPlan.prototype.reassignFastUnit = function(gameState)
|
|||
raid.unitCollection.updateEnt(ent);
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.chooseTarget = function(gameState)
|
||||
{
|
||||
chooseTarget(gameState)
|
||||
{
|
||||
if (this.targetPlayer === undefined)
|
||||
{
|
||||
this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this);
|
||||
|
|
@ -921,12 +923,12 @@ AttackPlan.prototype.chooseTarget = function(gameState)
|
|||
this.overseas = 0;
|
||||
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
}
|
||||
/**
|
||||
* sameLand true means that we look for a target for which we do not need to take a transport
|
||||
*/
|
||||
AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand)
|
||||
{
|
||||
getNearestTarget(gameState, position, sameLand)
|
||||
{
|
||||
this.isBlocked = false;
|
||||
// Temporary variables needed by isValidTarget
|
||||
this.gameState = gameState;
|
||||
|
|
@ -991,14 +993,14 @@ AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand)
|
|||
// Obstruction also can change the enemy target
|
||||
this.targetPlayer = target.owner();
|
||||
return target;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Default target finder aims for conquest critical targets
|
||||
* We must apply the *same* selection (isValidTarget) as done in getNearestTarget
|
||||
*/
|
||||
AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy)
|
||||
{
|
||||
defaultTargetFinder(gameState, playerEnemy)
|
||||
{
|
||||
let targets = new EntityCollection(gameState.sharedScript);
|
||||
if (gameState.getVictoryConditions().has("wonder"))
|
||||
{
|
||||
|
|
@ -1045,20 +1047,20 @@ AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy)
|
|||
.filter(filters.not(filters.byClass("Ship")));
|
||||
}
|
||||
return targets;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.isValidTarget = function(ent)
|
||||
{
|
||||
isValidTarget(ent)
|
||||
{
|
||||
if (!ent.position())
|
||||
return false;
|
||||
if (this.sameLand && getLandAccess(this.gameState, ent) != this.sameLand)
|
||||
return false;
|
||||
return !ent.decaying() || ent.getDefaultArrow() || ent.isGarrisonHolder() && ent.garrisoned().length;
|
||||
};
|
||||
}
|
||||
|
||||
/** Rush target finder aims at isolated non-defended buildings */
|
||||
AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy)
|
||||
{
|
||||
/** Rush target finder aims at isolated non-defended buildings */
|
||||
rushTargetFinder(gameState, playerEnemy)
|
||||
{
|
||||
let targets = new EntityCollection(gameState.sharedScript);
|
||||
let buildings;
|
||||
if (playerEnemy !== undefined)
|
||||
|
|
@ -1110,11 +1112,11 @@ AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy)
|
|||
targets = this.rushTargetFinder(gameState);
|
||||
|
||||
return targets;
|
||||
};
|
||||
}
|
||||
|
||||
/** Raid target finder aims at destructing foundations from which our defenseManager has attacked the builders */
|
||||
AttackPlan.prototype.raidTargetFinder = function(gameState)
|
||||
{
|
||||
/** Raid target finder aims at destructing foundations from which our defenseManager has attacked the builders */
|
||||
raidTargetFinder(gameState)
|
||||
{
|
||||
const targets = new EntityCollection(gameState.sharedScript);
|
||||
for (const targetId of gameState.ai.HQ.defenseManager.targetList)
|
||||
{
|
||||
|
|
@ -1123,15 +1125,15 @@ AttackPlan.prototype.raidTargetFinder = function(gameState)
|
|||
targets.addEnt(target);
|
||||
}
|
||||
return targets;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check that we can have a path to this target
|
||||
* otherwise we may be blocked by walls and try to react accordingly
|
||||
* This is done only when attacker and target are on the same land
|
||||
*/
|
||||
AttackPlan.prototype.checkTargetObstruction = function(gameState, target, position)
|
||||
{
|
||||
checkTargetObstruction(gameState, target, position)
|
||||
{
|
||||
if (getLandAccess(gameState, target) != gameState.ai.accessibility.getAccessValue(position))
|
||||
return target;
|
||||
|
||||
|
|
@ -1234,10 +1236,10 @@ AttackPlan.prototype.checkTargetObstruction = function(gameState, target, positi
|
|||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.getPathToTarget = function(gameState, fixedRallyPoint = false)
|
||||
{
|
||||
getPathToTarget(gameState, fixedRallyPoint = false)
|
||||
{
|
||||
const startAccess = gameState.ai.accessibility.getAccessValue(this.rallyPoint);
|
||||
const endAccess = getLandAccess(gameState, this.target);
|
||||
if (startAccess != endAccess)
|
||||
|
|
@ -1259,11 +1261,11 @@ AttackPlan.prototype.getPathToTarget = function(gameState, fixedRallyPoint = fal
|
|||
Engine.ProfileStop();
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** Set rally point at the border of our territory */
|
||||
AttackPlan.prototype.setRallyPoint = function(gameState)
|
||||
{
|
||||
/** Set rally point at the border of our territory */
|
||||
setRallyPoint(gameState)
|
||||
{
|
||||
for (let i = 0; i < this.path.length; ++i)
|
||||
{
|
||||
if (gameState.ai.HQ.territoryMap.getOwner(this.path[i]) === PlayerID)
|
||||
|
|
@ -1283,14 +1285,14 @@ AttackPlan.prototype.setRallyPoint = function(gameState)
|
|||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Executes the attack plan, after this is executed the update function will be run every turn
|
||||
* If we're here, it's because we have enough units.
|
||||
*/
|
||||
AttackPlan.prototype.StartAttack = function(gameState)
|
||||
{
|
||||
StartAttack(gameState)
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
aiWarn("start attack " + this.name + " with type " + this.type);
|
||||
|
||||
|
|
@ -1334,11 +1336,11 @@ AttackPlan.prototype.StartAttack = function(gameState)
|
|||
gameState.ai.HQ.navalManager.requireTransport(gameState, ent, rallyAccess, targetAccess, this.targetPos);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** Runs every turn after the attack is executed */
|
||||
AttackPlan.prototype.update = function(gameState, events)
|
||||
{
|
||||
/** Runs every turn after the attack is executed */
|
||||
update(gameState, events)
|
||||
{
|
||||
if (!this.unitCollection.hasEntities())
|
||||
return 0;
|
||||
|
||||
|
|
@ -1841,10 +1843,10 @@ AttackPlan.prototype.update = function(gameState, events)
|
|||
Engine.ProfileStop();
|
||||
|
||||
return this.unitCollection.length;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.UpdateTransporting = function(gameState, events)
|
||||
{
|
||||
UpdateTransporting(gameState, events)
|
||||
{
|
||||
let done = true;
|
||||
for (const ent of this.unitCollection.values())
|
||||
{
|
||||
|
|
@ -1884,10 +1886,10 @@ AttackPlan.prototype.UpdateTransporting = function(gameState, events)
|
|||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.UpdateWalking = function(gameState, events)
|
||||
{
|
||||
UpdateWalking(gameState, events)
|
||||
{
|
||||
// we're marching towards the target
|
||||
// Let's check if any of our unit has been attacked.
|
||||
// In case yes, we'll determine if we're simply off against an enemy army, a lone unit/building
|
||||
|
|
@ -1999,10 +2001,10 @@ AttackPlan.prototype.UpdateWalking = function(gameState, events)
|
|||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.UpdateTarget = function(gameState)
|
||||
{
|
||||
UpdateTarget(gameState)
|
||||
{
|
||||
// First update the target position in case it's a unit (and check if it has garrisoned)
|
||||
if (this.target && this.target.hasClass("Unit"))
|
||||
{
|
||||
|
|
@ -2092,11 +2094,11 @@ AttackPlan.prototype.UpdateTarget = function(gameState)
|
|||
this.targetPos = this.target.position();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** reset any units */
|
||||
AttackPlan.prototype.Abort = function(gameState)
|
||||
{
|
||||
/** reset any units */
|
||||
Abort(gameState)
|
||||
{
|
||||
this.unitCollection.unregister();
|
||||
if (this.unitCollection.hasEntities())
|
||||
{
|
||||
|
|
@ -2140,10 +2142,10 @@ AttackPlan.prototype.Abort = function(gameState)
|
|||
this.unit[unitCat].unregister();
|
||||
|
||||
this.removeQueues(gameState);
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.removeUnit = function(ent, update)
|
||||
{
|
||||
removeUnit(ent, update)
|
||||
{
|
||||
if (ent.getMetadata(PlayerID, "role") === Worker.ROLE_ATTACK)
|
||||
{
|
||||
if (ent.hasClass("CitizenSoldier"))
|
||||
|
|
@ -2155,10 +2157,10 @@ AttackPlan.prototype.removeUnit = function(ent, update)
|
|||
ent.setMetadata(PlayerID, "plan", -1);
|
||||
if (update)
|
||||
this.unitCollection.updateEnt(ent);
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.checkEvents = function(gameState, events)
|
||||
{
|
||||
checkEvents(gameState, events)
|
||||
{
|
||||
for (const evt of events.EntityRenamed)
|
||||
{
|
||||
if (!this.target || this.target.id() != evt.entity)
|
||||
|
|
@ -2208,26 +2210,26 @@ AttackPlan.prototype.checkEvents = function(gameState, events)
|
|||
this.rallyPoint = base.anchor.position();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.waitingForTransport = function()
|
||||
{
|
||||
waitingForTransport()
|
||||
{
|
||||
for (const ent of this.unitCollection.values())
|
||||
if (ent.getMetadata(PlayerID, "transport") !== undefined)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.hasSiegeUnits = function()
|
||||
{
|
||||
hasSiegeUnits()
|
||||
{
|
||||
for (const ent of this.unitCollection.values())
|
||||
if (isSiegeUnit(ent))
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.hasForceOrder = function(data, value)
|
||||
{
|
||||
hasForceOrder(data, value)
|
||||
{
|
||||
for (const ent of this.unitCollection.values())
|
||||
{
|
||||
if (data && +ent.getMetadata(PlayerID, data) !== value)
|
||||
|
|
@ -2238,22 +2240,22 @@ AttackPlan.prototype.hasForceOrder = function(data, value)
|
|||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* The center position of this attack may be in an inaccessible area. So we use the access
|
||||
* of the unit nearest to this center position.
|
||||
*/
|
||||
AttackPlan.prototype.getAttackAccess = function(gameState)
|
||||
{
|
||||
getAttackAccess(gameState)
|
||||
{
|
||||
for (const ent of this.unitCollection.filterNearest(this.position, 1).values())
|
||||
return getLandAccess(gameState, ent);
|
||||
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.debugAttack = function()
|
||||
{
|
||||
debugAttack()
|
||||
{
|
||||
aiWarn("---------- attack " + this.name);
|
||||
for (const unitCat in this.unitStat)
|
||||
{
|
||||
|
|
@ -2262,10 +2264,10 @@ AttackPlan.prototype.debugAttack = function()
|
|||
Unit.targetSize);
|
||||
}
|
||||
aiWarn("------------------------------");
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
const properties = {
|
||||
"name": this.name,
|
||||
"type": this.type,
|
||||
|
|
@ -2291,10 +2293,10 @@ AttackPlan.prototype.Serialize = function()
|
|||
};
|
||||
|
||||
return { "properties": properties };
|
||||
};
|
||||
}
|
||||
|
||||
AttackPlan.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
Deserialize(gameState, data)
|
||||
{
|
||||
for (const key in data.properties)
|
||||
this[key] = data.properties[key];
|
||||
|
||||
|
|
@ -2302,4 +2304,5 @@ AttackPlan.prototype.Deserialize = function(gameState, data)
|
|||
this.target = gameState.getEntityById(this.target);
|
||||
|
||||
this.failed = undefined;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ import { Worker } from "simulation/ai/petra/worker.js";
|
|||
* -updating whatever needs updating, keeping track of stuffs (rebuilding needs…)
|
||||
*/
|
||||
|
||||
export function BaseManager(gameState, basesManager)
|
||||
export class BaseManager
|
||||
{
|
||||
constructor(gameState, basesManager)
|
||||
{
|
||||
this.Config = basesManager.Config;
|
||||
this.ID = gameState.ai.uniqueIDs.bases++;
|
||||
this.basesManager = basesManager;
|
||||
|
|
@ -43,28 +45,28 @@ export function BaseManager(gameState, basesManager)
|
|||
this.territoryIndices = [];
|
||||
|
||||
this.timeNextIdleCheck = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BaseManager.STATE_WITH_ANCHOR = "anchored";
|
||||
static STATE_WITH_ANCHOR = "anchored";
|
||||
|
||||
/**
|
||||
/**
|
||||
* New base with a foundation anchor.
|
||||
*/
|
||||
BaseManager.STATE_UNCONSTRUCTED = "unconstructed";
|
||||
static STATE_UNCONSTRUCTED = "unconstructed";
|
||||
|
||||
/**
|
||||
/**
|
||||
* Captured base with an anchor.
|
||||
*/
|
||||
BaseManager.STATE_CAPTURED = "captured";
|
||||
static STATE_CAPTURED = "captured";
|
||||
|
||||
/**
|
||||
/**
|
||||
* Anchorless base, currently with dock.
|
||||
*/
|
||||
BaseManager.STATE_ANCHORLESS = "anchorless";
|
||||
static STATE_ANCHORLESS = "anchorless";
|
||||
|
||||
BaseManager.prototype.init = function(gameState, state)
|
||||
{
|
||||
init(gameState, state)
|
||||
{
|
||||
if (state === BaseManager.STATE_UNCONSTRUCTED)
|
||||
this.constructing = true;
|
||||
else if (state !== BaseManager.STATE_CAPTURED)
|
||||
|
|
@ -90,10 +92,10 @@ BaseManager.prototype.init = function(gameState, state)
|
|||
this.dropsiteSupplies[res] = { "nearby": [], "medium": [], "faraway": [] };
|
||||
this.gatherers[res] = { "nextCheck": 0, "used": 0, "lost": 0 };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.reset = function(gameState, state)
|
||||
{
|
||||
reset(gameState, state)
|
||||
{
|
||||
if (state === BaseManager.STATE_UNCONSTRUCTED)
|
||||
this.constructing = true;
|
||||
else
|
||||
|
|
@ -102,20 +104,20 @@ BaseManager.prototype.reset = function(gameState, state)
|
|||
this.neededDefenders = 0;
|
||||
else
|
||||
this.neededDefenders = 3 + 2 * (this.Config.difficulty - 3);
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.assignEntity = function(gameState, ent)
|
||||
{
|
||||
assignEntity(gameState, ent)
|
||||
{
|
||||
ent.setMetadata(PlayerID, "base", this.ID);
|
||||
this.units.updateEnt(ent);
|
||||
this.workers.updateEnt(ent);
|
||||
this.buildings.updateEnt(ent);
|
||||
if (ent.resourceDropsiteTypes() && !ent.hasClass("Unit"))
|
||||
this.assignResourceToDropsite(gameState, ent, false);
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.setAnchor = function(gameState, anchorEntity, deserialize = false)
|
||||
{
|
||||
setAnchor(gameState, anchorEntity, deserialize = false)
|
||||
{
|
||||
if (!anchorEntity.hasClass("CivCentre"))
|
||||
{
|
||||
aiWarn("Error: Petra base " + this.ID + " has been assigned " + ent.templateName() +
|
||||
|
|
@ -134,20 +136,20 @@ BaseManager.prototype.setAnchor = function(gameState, anchorEntity, deserialize
|
|||
this.buildings.updateEnt(anchorEntity);
|
||||
this.accessIndex = getLandAccess(gameState, anchorEntity);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/* we lost our anchor. Let's reassign our units and buildings */
|
||||
BaseManager.prototype.anchorLost = function(gameState)
|
||||
{
|
||||
/* we lost our anchor. Let's reassign our units and buildings */
|
||||
anchorLost(gameState)
|
||||
{
|
||||
this.anchor = undefined;
|
||||
this.anchorId = undefined;
|
||||
this.neededDefenders = 0;
|
||||
this.basesManager.resetBaseCache();
|
||||
};
|
||||
}
|
||||
|
||||
/** Set a building of an anchorless base */
|
||||
BaseManager.prototype.setAnchorlessEntity = function(gameState, ent, deserialize = false)
|
||||
{
|
||||
/** Set a building of an anchorless base */
|
||||
setAnchorlessEntity(gameState, ent, deserialize = false)
|
||||
{
|
||||
if (!this.buildings.hasEntities())
|
||||
{
|
||||
if (!getBuiltEntity(gameState, ent).resourceDropsiteTypes())
|
||||
|
|
@ -168,14 +170,14 @@ BaseManager.prototype.setAnchorlessEntity = function(gameState, ent, deserialize
|
|||
ent.setMetadata(PlayerID, "base", this.ID);
|
||||
this.buildings.updateEnt(ent);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Assign the resources around the dropsites of this basis in three areas according to distance, and sort them in each area.
|
||||
* Moving resources (animals) and buildable resources (fields) are treated elsewhere.
|
||||
*/
|
||||
BaseManager.prototype.assignResourceToDropsite = function(gameState, dropsite, deserialized)
|
||||
{
|
||||
assignResourceToDropsite(gameState, dropsite, deserialized)
|
||||
{
|
||||
if (this.dropsites[dropsite.id()])
|
||||
{
|
||||
if (this.Config.debug > 0)
|
||||
|
|
@ -256,10 +258,10 @@ BaseManager.prototype.assignResourceToDropsite = function(gameState, dropsite, d
|
|||
"entities": [dropsiteId],
|
||||
"shared": dropsiteId != this.anchorId
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.removeFromAssignedDropsite = function(entityID)
|
||||
{
|
||||
removeFromAssignedDropsite(entityID)
|
||||
{
|
||||
for (const type in this.dropsiteSupplies)
|
||||
for (const proxim in this.dropsiteSupplies[type])
|
||||
{
|
||||
|
|
@ -270,11 +272,11 @@ BaseManager.prototype.removeFromAssignedDropsite = function(entityID)
|
|||
resourcesList.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// completely remove the dropsite resources from our list.
|
||||
BaseManager.prototype.removeDropsite = function(gameState, entityID)
|
||||
{
|
||||
// completely remove the dropsite resources from our list.
|
||||
removeDropsite(gameState, entityID)
|
||||
{
|
||||
if (!entityID)
|
||||
return;
|
||||
|
||||
|
|
@ -299,14 +301,14 @@ BaseManager.prototype.removeDropsite = function(gameState, entityID)
|
|||
}
|
||||
|
||||
this.dropsites[entityID] = undefined;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @return {Object} - The position of the best place to build a new dropsite for the specified resource,
|
||||
* its quality and its template name.
|
||||
*/
|
||||
BaseManager.prototype.findBestDropsiteAndLocation = function(gameState, resource)
|
||||
{
|
||||
findBestDropsiteAndLocation(gameState, resource)
|
||||
{
|
||||
let bestResult = {
|
||||
"quality": 0,
|
||||
"pos": [0, 0]
|
||||
|
|
@ -321,13 +323,13 @@ BaseManager.prototype.findBestDropsiteAndLocation = function(gameState, resource
|
|||
bestResult.templateName = templateName;
|
||||
}
|
||||
return bestResult;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns the position of the best place to build a new dropsite for the specified resource and dropsite template.
|
||||
*/
|
||||
BaseManager.prototype.findBestDropsiteLocation = function(gameState, resource, templateName)
|
||||
{
|
||||
findBestDropsiteLocation(gameState, resource, templateName)
|
||||
{
|
||||
const template = gameState.getTemplate(gameState.applyCiv(templateName));
|
||||
|
||||
// CCs and Docks are handled elsewhere.
|
||||
|
|
@ -414,10 +416,10 @@ BaseManager.prototype.findBestDropsiteLocation = function(gameState, resource, t
|
|||
const x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize;
|
||||
const z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize;
|
||||
return { "quality": bestVal, "pos": [x, z] };
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.getResourceLevel = function(gameState, type, distances = ["nearby", "medium", "faraway"])
|
||||
{
|
||||
getResourceLevel(gameState, type, distances = ["nearby", "medium", "faraway"])
|
||||
{
|
||||
let count = 0;
|
||||
const check = {};
|
||||
for (const proxim of distances)
|
||||
|
|
@ -431,11 +433,11 @@ BaseManager.prototype.getResourceLevel = function(gameState, type, distances = [
|
|||
count += supplyEntity.resourceSupplyAmount();
|
||||
}
|
||||
return count;
|
||||
};
|
||||
}
|
||||
|
||||
/** check our resource levels and react accordingly */
|
||||
BaseManager.prototype.checkResourceLevels = function(gameState, queues)
|
||||
{
|
||||
/** check our resource levels and react accordingly */
|
||||
checkResourceLevels(gameState, queues)
|
||||
{
|
||||
for (const type of Resources.GetCodes())
|
||||
{
|
||||
if (type == "food")
|
||||
|
|
@ -550,12 +552,11 @@ BaseManager.prototype.checkResourceLevels = function(gameState, queues)
|
|||
else if (total == 0)
|
||||
this.gatherers[type].nextCheck = gameState.ai.playedTurn + 10;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/** Adds the estimated gather rates from this base to the currentRates */
|
||||
BaseManager.prototype.addGatherRates = function(gameState, currentRates)
|
||||
{
|
||||
/** Adds the estimated gather rates from this base to the currentRates */
|
||||
addGatherRates(gameState, currentRates)
|
||||
{
|
||||
for (const res in currentRates)
|
||||
{
|
||||
// I calculate the exact gathering rate for each unit.
|
||||
|
|
@ -592,10 +593,10 @@ BaseManager.prototype.addGatherRates = function(gameState, currentRates)
|
|||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.assignRolelessUnits = function(gameState, roleless)
|
||||
{
|
||||
assignRolelessUnits(gameState, roleless)
|
||||
{
|
||||
if (!roleless)
|
||||
roleless = this.units.filter(filters.not(filters.byHasMetadata(PlayerID, "role"))).values();
|
||||
|
||||
|
|
@ -606,15 +607,15 @@ BaseManager.prototype.assignRolelessUnits = function(gameState, roleless)
|
|||
else if (ent.hasClass("Support") && ent.hasClass("Elephant"))
|
||||
ent.setMetadata(PlayerID, "role", Worker.ROLE_WORKER);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* If the numbers of workers on the resources is unbalanced then set some of workers to idle so
|
||||
* they can be reassigned by reassignIdleWorkers.
|
||||
* TODO: actually this probably should be in the HQ.
|
||||
*/
|
||||
BaseManager.prototype.setWorkersIdleByPriority = function(gameState)
|
||||
{
|
||||
setWorkersIdleByPriority(gameState)
|
||||
{
|
||||
this.timeNextIdleCheck = gameState.ai.elapsedTime + 8;
|
||||
// change resource only towards one which is more needed, and if changing will not change this order
|
||||
let nb = 1; // no more than 1 change per turn (otherwise we should update the rates)
|
||||
|
|
@ -662,15 +663,15 @@ BaseManager.prototype.setWorkersIdleByPriority = function(gameState)
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Switch some gatherers (limited to number) from resource "from" to resource "to"
|
||||
* and return remaining number of possible switches.
|
||||
* Prefer Civilian for food and CitizenSoldier for other resources.
|
||||
*/
|
||||
BaseManager.prototype.switchGatherer = function(gameState, from, to, number)
|
||||
{
|
||||
switchGatherer(gameState, from, to, number)
|
||||
{
|
||||
let num = number;
|
||||
let only;
|
||||
const gatherers = this.gatherersByType(gameState, from);
|
||||
|
|
@ -693,10 +694,10 @@ BaseManager.prototype.switchGatherer = function(gameState, from, to, number)
|
|||
this.basesManager.AddTCResGatherer(to);
|
||||
}
|
||||
return num;
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.reassignIdleWorkers = function(gameState, idleWorkers)
|
||||
{
|
||||
reassignIdleWorkers(gameState, idleWorkers)
|
||||
{
|
||||
// Search for idle workers, and tell them to gather resources based on demand
|
||||
if (!idleWorkers)
|
||||
{
|
||||
|
|
@ -746,27 +747,27 @@ BaseManager.prototype.reassignIdleWorkers = function(gameState, idleWorkers)
|
|||
else if (ent.hasClass("FishingBoat"))
|
||||
ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_FISHER);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.workersBySubrole = function(gameState, subrole)
|
||||
{
|
||||
workersBySubrole(gameState, subrole)
|
||||
{
|
||||
return gameState.updatingCollection("subrole-" + subrole +"-base-" + this.ID,
|
||||
filters.byMetadata(PlayerID, "subrole", subrole), this.workers);
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.gatherersByType = function(gameState, type)
|
||||
{
|
||||
gatherersByType(gameState, type)
|
||||
{
|
||||
return gameState.updatingCollection("workers-gathering-" + type +"-base-" +
|
||||
this.ID, filters.byMetadata(PlayerID, "gather-type", type),
|
||||
this.workersBySubrole(gameState, Worker.SUBROLE_GATHERER));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* returns an entity collection of workers.
|
||||
* They are idled immediatly and their subrole set to idle.
|
||||
*/
|
||||
BaseManager.prototype.pickBuilders = function(gameState, workers, number)
|
||||
{
|
||||
pickBuilders(gameState, workers, number)
|
||||
{
|
||||
const availableWorkers = this.workers.filter(ent =>
|
||||
{
|
||||
if (!ent.position() || !ent.isBuilder())
|
||||
|
|
@ -803,15 +804,15 @@ BaseManager.prototype.pickBuilders = function(gameState, workers, number)
|
|||
workers.addEnt(availableWorkers[i]);
|
||||
}
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* If we have some foundations, and we don't have enough builder-workers,
|
||||
* try reassigning some other workers who are nearby
|
||||
* AI tries to use builders sensibly, not completely stopping its econ.
|
||||
*/
|
||||
BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
|
||||
{
|
||||
assignToFoundations(gameState, noRepair)
|
||||
{
|
||||
let foundations = this.buildings.filter(filters.and(filters.isFoundation(),
|
||||
filters.not(filters.byClass("Field"))));
|
||||
|
||||
|
|
@ -1050,11 +1051,11 @@ BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
|
|||
ent.setMetadata(PlayerID, "target-foundation", target.id());
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Return false when the base is not active (no workers on it) */
|
||||
BaseManager.prototype.update = function(gameState, queues, events)
|
||||
{
|
||||
/** Return false when the base is not active (no workers on it) */
|
||||
update(gameState, queues, events)
|
||||
{
|
||||
if (this.ID == this.basesManager.baselessBase().ID)
|
||||
{
|
||||
// if some active base, reassigns the workers/buildings
|
||||
|
|
@ -1190,25 +1191,25 @@ BaseManager.prototype.update = function(gameState, queues, events)
|
|||
|
||||
Engine.ProfileStop();
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.AddTCGatherer = function(supplyID)
|
||||
{
|
||||
AddTCGatherer(supplyID)
|
||||
{
|
||||
return this.basesManager.AddTCGatherer(supplyID);
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.RemoveTCGatherer = function(supplyID)
|
||||
{
|
||||
RemoveTCGatherer(supplyID)
|
||||
{
|
||||
this.basesManager.RemoveTCGatherer(supplyID);
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.GetTCGatherer = function(supplyID)
|
||||
{
|
||||
GetTCGatherer(supplyID)
|
||||
{
|
||||
return this.basesManager.GetTCGatherer(supplyID);
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
return {
|
||||
"ID": this.ID,
|
||||
"anchorId": this.anchorId,
|
||||
|
|
@ -1221,12 +1222,13 @@ BaseManager.prototype.Serialize = function()
|
|||
"timeNextIdleCheck": this.timeNextIdleCheck,
|
||||
"dropsiteSupplies": this.dropsiteSupplies
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
BaseManager.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
Deserialize(gameState, data)
|
||||
{
|
||||
for (const key in data)
|
||||
this[key] = data[key];
|
||||
|
||||
this.anchor = this.anchorId !== undefined ? gameState.getEntityById(this.anchorId) : undefined;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,23 +12,25 @@ import { Worker } from "simulation/ai/petra/worker.js";
|
|||
* Only one base is run every turn.
|
||||
*/
|
||||
|
||||
export function BasesManager(Config)
|
||||
export class BasesManager
|
||||
{
|
||||
this.Config = Config;
|
||||
|
||||
this.currentBase = 0;
|
||||
currentBase = 0;
|
||||
|
||||
// Cache some quantities for performance.
|
||||
this.turnCache = {};
|
||||
turnCache = {};
|
||||
|
||||
// Deals with unit/structure without base.
|
||||
this.noBase = undefined;
|
||||
noBase = undefined;
|
||||
|
||||
this.baseManagers = [];
|
||||
}
|
||||
baseManagers = [];
|
||||
|
||||
BasesManager.prototype.init = function(gameState, deserialize = false)
|
||||
{
|
||||
constructor(Config)
|
||||
{
|
||||
this.Config = Config;
|
||||
}
|
||||
|
||||
init(gameState, deserialize = false)
|
||||
{
|
||||
// Initialize base map. Each pixel is a base ID, or 0 if not or not accessible.
|
||||
this.basesMap = new InfoMap(gameState.sharedScript, "territory");
|
||||
|
||||
|
|
@ -41,13 +43,13 @@ BasesManager.prototype.init = function(gameState, deserialize = false)
|
|||
this.createBase(gameState, cc, BaseManager.STATE_WITH_ANCHOR, deserialize);
|
||||
else
|
||||
this.createBase(gameState, cc, BaseManager.STATE_UNCONSTRUCTED, deserialize);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Initialization needed after deserialization (only called when deserialising).
|
||||
*/
|
||||
BasesManager.prototype.postinit = function(gameState)
|
||||
{
|
||||
postinit(gameState)
|
||||
{
|
||||
// Rebuild the base maps from the territory indices of each base.
|
||||
this.basesMap = new InfoMap(gameState.sharedScript, "territory");
|
||||
for (const base of this.baseManagers)
|
||||
|
|
@ -65,17 +67,16 @@ BasesManager.prototype.postinit = function(gameState)
|
|||
continue;
|
||||
const base = this.getBaseByID(baseID);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Create a new base in the baseManager:
|
||||
* If an existing one without anchor already exist, use it.
|
||||
* Otherwise create a new one.
|
||||
* TODO when buildings, criteria should depend on distance
|
||||
*/
|
||||
BasesManager.prototype.createBase = function(gameState, ent, type = BaseManager.STATE_WITH_ANCHOR,
|
||||
deserialize = false)
|
||||
{
|
||||
createBase(gameState, ent, type = BaseManager.STATE_WITH_ANCHOR, deserialize = false)
|
||||
{
|
||||
const access = getLandAccess(gameState, ent);
|
||||
let newbase;
|
||||
for (const base of this.baseManagers)
|
||||
|
|
@ -126,11 +127,11 @@ BasesManager.prototype.createBase = function(gameState, ent, type = BaseManager.
|
|||
newbase.setAnchorlessEntity(gameState, ent, deserialize);
|
||||
|
||||
return newbase;
|
||||
};
|
||||
}
|
||||
|
||||
/** TODO check if the new anchorless bases should be added to addBase */
|
||||
BasesManager.prototype.checkEvents = function(gameState, events)
|
||||
{
|
||||
/** TODO check if the new anchorless bases should be added to addBase */
|
||||
checkEvents(gameState, events)
|
||||
{
|
||||
this.turnCache = {};
|
||||
let addBase = false;
|
||||
|
||||
|
|
@ -346,14 +347,14 @@ BasesManager.prototype.checkEvents = function(gameState, events)
|
|||
|
||||
if (addBase)
|
||||
gameState.ai.HQ.handleNewBase(gameState);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* returns an entity collection of workers through BaseManager.pickBuilders
|
||||
* TODO: when same accessIndex, sort by distance
|
||||
*/
|
||||
BasesManager.prototype.bulkPickWorkers = function(gameState, baseRef, number)
|
||||
{
|
||||
bulkPickWorkers(gameState, baseRef, number)
|
||||
{
|
||||
const accessIndex = baseRef.accessIndex;
|
||||
if (!accessIndex)
|
||||
return false;
|
||||
|
|
@ -383,13 +384,13 @@ BasesManager.prototype.bulkPickWorkers = function(gameState, baseRef, number)
|
|||
if (!workers.length)
|
||||
return false;
|
||||
return workers;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @return {Object} - Resources (estimation) still gatherable in our territory.
|
||||
*/
|
||||
BasesManager.prototype.getTotalResourceLevel = function(gameState, resources = Resources.GetCodes(), proximity = ["nearby", "medium"])
|
||||
{
|
||||
getTotalResourceLevel(gameState, resources = Resources.GetCodes(), proximity = ["nearby", "medium"])
|
||||
{
|
||||
const total = {};
|
||||
for (const res of resources)
|
||||
total[res] = 0;
|
||||
|
|
@ -398,14 +399,14 @@ BasesManager.prototype.getTotalResourceLevel = function(gameState, resources = R
|
|||
total[res] += base.getResourceLevel(gameState, res, proximity);
|
||||
|
||||
return total;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns the current gather rate
|
||||
* This is not per-se exact, it performs a few adjustments ad-hoc to account for travel distance, stuffs like that.
|
||||
*/
|
||||
BasesManager.prototype.GetCurrentGatherRates = function(gameState)
|
||||
{
|
||||
GetCurrentGatherRates(gameState)
|
||||
{
|
||||
if (!this.turnCache.currentRates)
|
||||
{
|
||||
const currentRates = {};
|
||||
|
|
@ -421,13 +422,13 @@ BasesManager.prototype.GetCurrentGatherRates = function(gameState)
|
|||
}
|
||||
|
||||
return this.turnCache.currentRates;
|
||||
};
|
||||
}
|
||||
|
||||
/** Some functions that register that we assigned a gatherer to a resource this turn */
|
||||
/** Some functions that register that we assigned a gatherer to a resource this turn */
|
||||
|
||||
/** Add a gatherer to the turn cache for this supply. */
|
||||
BasesManager.prototype.AddTCGatherer = function(supplyID)
|
||||
{
|
||||
/** Add a gatherer to the turn cache for this supply. */
|
||||
AddTCGatherer(supplyID)
|
||||
{
|
||||
if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID] !== undefined)
|
||||
++this.turnCache.resourceGatherer[supplyID];
|
||||
else
|
||||
|
|
@ -436,11 +437,11 @@ BasesManager.prototype.AddTCGatherer = function(supplyID)
|
|||
this.turnCache.resourceGatherer = {};
|
||||
this.turnCache.resourceGatherer[supplyID] = 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Remove a gatherer from the turn cache for this supply. */
|
||||
BasesManager.prototype.RemoveTCGatherer = function(supplyID)
|
||||
{
|
||||
/** Remove a gatherer from the turn cache for this supply. */
|
||||
RemoveTCGatherer(supplyID)
|
||||
{
|
||||
if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID])
|
||||
--this.turnCache.resourceGatherer[supplyID];
|
||||
else
|
||||
|
|
@ -449,19 +450,19 @@ BasesManager.prototype.RemoveTCGatherer = function(supplyID)
|
|||
this.turnCache.resourceGatherer = {};
|
||||
this.turnCache.resourceGatherer[supplyID] = -1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
BasesManager.prototype.GetTCGatherer = function(supplyID)
|
||||
{
|
||||
GetTCGatherer(supplyID)
|
||||
{
|
||||
if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID])
|
||||
return this.turnCache.resourceGatherer[supplyID];
|
||||
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
/** The next two are to register that we assigned a gatherer to a resource this turn. */
|
||||
BasesManager.prototype.AddTCResGatherer = function(resource)
|
||||
{
|
||||
/** The next two are to register that we assigned a gatherer to a resource this turn. */
|
||||
AddTCResGatherer(resource)
|
||||
{
|
||||
const check = "resourceGatherer-" + resource;
|
||||
if (this.turnCache[check])
|
||||
++this.turnCache[check];
|
||||
|
|
@ -470,53 +471,53 @@ BasesManager.prototype.AddTCResGatherer = function(resource)
|
|||
|
||||
if (this.turnCache.currentRates)
|
||||
this.turnCache.currentRates[resource] += 0.5;
|
||||
};
|
||||
}
|
||||
|
||||
BasesManager.prototype.GetTCResGatherer = function(resource)
|
||||
{
|
||||
GetTCResGatherer(resource)
|
||||
{
|
||||
const check = "resourceGatherer-" + resource;
|
||||
if (this.turnCache[check])
|
||||
return this.turnCache[check];
|
||||
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* returns the number of bases with a cc
|
||||
* ActiveBases includes only those with a built cc
|
||||
* PotentialBases includes also those with a cc in construction
|
||||
*/
|
||||
BasesManager.prototype.numActiveBases = function()
|
||||
{
|
||||
numActiveBases()
|
||||
{
|
||||
if (!this.turnCache.base)
|
||||
this.updateBaseCache();
|
||||
return this.turnCache.base.active;
|
||||
};
|
||||
}
|
||||
|
||||
BasesManager.prototype.hasActiveBase = function()
|
||||
{
|
||||
hasActiveBase()
|
||||
{
|
||||
return !!this.numActiveBases();
|
||||
};
|
||||
}
|
||||
|
||||
BasesManager.prototype.numPotentialBases = function()
|
||||
{
|
||||
numPotentialBases()
|
||||
{
|
||||
if (!this.turnCache.base)
|
||||
this.updateBaseCache();
|
||||
return this.turnCache.base.potential;
|
||||
};
|
||||
}
|
||||
|
||||
BasesManager.prototype.hasPotentialBase = function()
|
||||
{
|
||||
hasPotentialBase()
|
||||
{
|
||||
return !!this.numPotentialBases();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Updates the number of active and potential bases.
|
||||
* .potential {number} - Bases that may or may not still be a foundation.
|
||||
* .active {number} - Usable bases.
|
||||
*/
|
||||
BasesManager.prototype.updateBaseCache = function()
|
||||
{
|
||||
updateBaseCache()
|
||||
{
|
||||
this.turnCache.base = { "active": 0, "potential": 0 };
|
||||
for (const base of this.baseManagers)
|
||||
{
|
||||
|
|
@ -526,46 +527,46 @@ BasesManager.prototype.updateBaseCache = function()
|
|||
if (base.anchor.foundationProgress() === undefined)
|
||||
++this.turnCache.base.active;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
BasesManager.prototype.resetBaseCache = function()
|
||||
{
|
||||
resetBaseCache()
|
||||
{
|
||||
this.turnCache.base = undefined;
|
||||
};
|
||||
}
|
||||
|
||||
BasesManager.prototype.baselessBase = function()
|
||||
{
|
||||
baselessBase()
|
||||
{
|
||||
return this.noBase;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @param {number} baseID
|
||||
* @return {Object} - The base corresponding to baseID.
|
||||
*/
|
||||
BasesManager.prototype.getBaseByID = function(baseID)
|
||||
{
|
||||
getBaseByID(baseID)
|
||||
{
|
||||
if (this.noBase.ID === baseID)
|
||||
return this.noBase;
|
||||
return this.baseManagers.find(base => base.ID === baseID);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* flag a resource as exhausted
|
||||
*/
|
||||
BasesManager.prototype.isResourceExhausted = function(resource)
|
||||
{
|
||||
isResourceExhausted(resource)
|
||||
{
|
||||
return this.baseManagers.every(base =>
|
||||
!base.dropsiteSupplies[resource].nearby.length &&
|
||||
!base.dropsiteSupplies[resource].medium.length &&
|
||||
!base.dropsiteSupplies[resource].faraway.length);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Count gatherers returning resources in the number of gatherers of resourceSupplies
|
||||
* to prevent the AI always reassigning idle workers to these resourceSupplies (specially in naval maps).
|
||||
*/
|
||||
BasesManager.prototype.assignGatherers = function()
|
||||
{
|
||||
assignGatherers()
|
||||
{
|
||||
for (const base of this.baseManagers)
|
||||
for (const worker of base.workers.values())
|
||||
{
|
||||
|
|
@ -576,14 +577,14 @@ BasesManager.prototype.assignGatherers = function()
|
|||
continue;
|
||||
this.AddTCGatherer(orders[1].target);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Assign an entity to the closest base.
|
||||
* Used by the starting strategy.
|
||||
*/
|
||||
BasesManager.prototype.assignEntity = function(gameState, ent, territoryIndex)
|
||||
{
|
||||
assignEntity(gameState, ent, territoryIndex)
|
||||
{
|
||||
let bestbase;
|
||||
for (const base of this.baseManagers)
|
||||
{
|
||||
|
|
@ -616,33 +617,33 @@ BasesManager.prototype.assignEntity = function(gameState, ent, territoryIndex)
|
|||
bestbase.workerObject.update(gameState, ent);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Adds the gather rates of individual bases to a shared object.
|
||||
* @param {Object} gameState
|
||||
* @param {Object} rates - The rates to add the gather rates to.
|
||||
*/
|
||||
BasesManager.prototype.addGatherRates = function(gameState, rates)
|
||||
{
|
||||
addGatherRates(gameState, rates)
|
||||
{
|
||||
for (const base of this.baseManagers)
|
||||
base.addGatherRates(gameState, rates);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @param {number} territoryIndex
|
||||
* @return {number} - The ID of the base at the given territory index.
|
||||
*/
|
||||
BasesManager.prototype.baseAtIndex = function(territoryIndex)
|
||||
{
|
||||
baseAtIndex(territoryIndex)
|
||||
{
|
||||
return this.basesMap.map[territoryIndex];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @param {number} territoryIndex
|
||||
*/
|
||||
BasesManager.prototype.removeBaseFromTerritoryIndex = function(territoryIndex)
|
||||
{
|
||||
removeBaseFromTerritoryIndex(territoryIndex)
|
||||
{
|
||||
const baseID = this.basesMap.map[territoryIndex];
|
||||
if (baseID == 0)
|
||||
return;
|
||||
|
|
@ -658,13 +659,13 @@ BasesManager.prototype.removeBaseFromTerritoryIndex = function(territoryIndex)
|
|||
else
|
||||
aiWarn(" problem in headquarters::updateTerritories without base " + baseID);
|
||||
this.basesMap.map[territoryIndex] = 0;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @return {boolean} - Whether the index was added to a base.
|
||||
*/
|
||||
BasesManager.prototype.addTerritoryIndexToBase = function(gameState, territoryIndex, passabilityMap)
|
||||
{
|
||||
addTerritoryIndexToBase(gameState, territoryIndex, passabilityMap)
|
||||
{
|
||||
if (this.baseAtIndex(territoryIndex) != 0)
|
||||
return false;
|
||||
let landPassable = false;
|
||||
|
|
@ -700,11 +701,11 @@ BasesManager.prototype.addTerritoryIndexToBase = function(gameState, territoryIn
|
|||
this.getBaseByID(baseID).territoryIndices.push(territoryIndex);
|
||||
this.basesMap.map[territoryIndex] = baseID;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** Reassign territories when a base is going to be deleted */
|
||||
BasesManager.prototype.reassignTerritories = function(deletedBase, territoryMap)
|
||||
{
|
||||
/** Reassign territories when a base is going to be deleted */
|
||||
reassignTerritories(deletedBase, territoryMap)
|
||||
{
|
||||
const cellSize = territoryMap.cellSize;
|
||||
const width = territoryMap.width;
|
||||
for (let j = 0; j < territoryMap.length; ++j)
|
||||
|
|
@ -741,13 +742,13 @@ BasesManager.prototype.reassignTerritories = function(deletedBase, territoryMap)
|
|||
else
|
||||
this.basesMap.map[j] = 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* We will loop only on one active base per turn.
|
||||
*/
|
||||
BasesManager.prototype.update = function(gameState, queues, events)
|
||||
{
|
||||
update(gameState, queues, events)
|
||||
{
|
||||
Engine.ProfileStart("BasesManager update");
|
||||
|
||||
this.assignGatherers();
|
||||
|
|
@ -763,10 +764,10 @@ BasesManager.prototype.update = function(gameState, queues, events)
|
|||
}
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
}
|
||||
|
||||
BasesManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
const properties = {
|
||||
"currentBase": this.currentBase
|
||||
};
|
||||
|
|
@ -781,10 +782,10 @@ BasesManager.prototype.Serialize = function()
|
|||
"noBase": this.noBase?.Serialize(),
|
||||
"baseManagers": baseManagers
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
BasesManager.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
Deserialize(gameState, data)
|
||||
{
|
||||
for (const key in data.properties)
|
||||
this[key] = data.properties[key];
|
||||
|
||||
|
|
@ -806,4 +807,5 @@ BasesManager.prototype.Deserialize = function(gameState, data)
|
|||
newbase.Deserialize(gameState, basedata);
|
||||
this.baseManagers.push(newbase);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,6 @@
|
|||
import * as filters from "simulation/ai/common-api/filters.js";
|
||||
import { aiWarn } from "simulation/ai/common-api/utils.js";
|
||||
|
||||
/**
|
||||
* One task of this manager is to cache the list of structures we have builders for,
|
||||
* to avoid having to loop on all entities each time.
|
||||
* It also takes care of the structures we can't currently build and should not try to build endlessly.
|
||||
*/
|
||||
|
||||
export function BuildManager()
|
||||
{
|
||||
// List of buildings we have builders for, with number of possible builders.
|
||||
this.builders = new Map();
|
||||
// List of buildings we can't currently build (because no room, no builder or whatever),
|
||||
// with time we should wait before trying again to build it.
|
||||
this.unbuildables = new Map();
|
||||
}
|
||||
|
||||
function addBuilder(builders, civ, entity)
|
||||
{
|
||||
for (const buildable of entity.buildableEntities(civ))
|
||||
|
|
@ -36,17 +21,31 @@ function removeBuilder(builders, entityId)
|
|||
}
|
||||
}
|
||||
|
||||
/** Initialization at start of game */
|
||||
BuildManager.prototype.init = function(gameState)
|
||||
/**
|
||||
* One task of this manager is to cache the list of structures we have builders for,
|
||||
* to avoid having to loop on all entities each time.
|
||||
* It also takes care of the structures we can't currently build and should not try to build endlessly.
|
||||
*/
|
||||
|
||||
export class BuildManager
|
||||
{
|
||||
// List of buildings we have builders for, with number of possible builders.
|
||||
builders = new Map();
|
||||
// List of buildings we can't currently build (because no room, no builder or whatever),
|
||||
// with time we should wait before trying again to build it.
|
||||
unbuildables = new Map();
|
||||
|
||||
/** Initialization at start of game */
|
||||
init(gameState)
|
||||
{
|
||||
const civ = gameState.getPlayerCiv();
|
||||
for (const ent of gameState.getOwnUnits().values())
|
||||
addBuilder(this.builders, civ, ent);
|
||||
};
|
||||
}
|
||||
|
||||
/** Update the builders counters */
|
||||
BuildManager.prototype.checkEvents = function(gameState, events)
|
||||
{
|
||||
/** Update the builders counters */
|
||||
checkEvents(gameState, events)
|
||||
{
|
||||
this.elapsedTime = gameState.ai.elapsedTime;
|
||||
const civ = gameState.getPlayerCiv();
|
||||
|
||||
|
|
@ -90,14 +89,14 @@ BuildManager.prototype.checkEvents = function(gameState, events)
|
|||
for (const ent of gameState.getOwnUnits().values())
|
||||
addBuilder(this.builders, civ, ent);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* Get the buildable structures passing a filter.
|
||||
*/
|
||||
BuildManager.prototype.findStructuresByFilter = function(gameState, filter)
|
||||
{
|
||||
findStructuresByFilter(gameState, filter)
|
||||
{
|
||||
const result = [];
|
||||
for (const [templateName, entities] of this.builders)
|
||||
{
|
||||
|
|
@ -110,39 +109,43 @@ BuildManager.prototype.findStructuresByFilter = function(gameState, filter)
|
|||
result.push(templateName);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Get the first buildable structure with a given class
|
||||
* TODO when several available, choose the best one
|
||||
*/
|
||||
BuildManager.prototype.findStructureWithClass = function(gameState, classes)
|
||||
{
|
||||
findStructureWithClass(gameState, classes)
|
||||
{
|
||||
return this.findStructuresByFilter(gameState, filters.byClasses(classes))[0];
|
||||
};
|
||||
}
|
||||
|
||||
BuildManager.prototype.hasBuilder = function(template)
|
||||
{
|
||||
hasBuilder(template)
|
||||
{
|
||||
const numBuilders = this.builders.get(template);
|
||||
return numBuilders && numBuilders.length > 0;
|
||||
};
|
||||
}
|
||||
|
||||
BuildManager.prototype.isUnbuildable = function(gameState, template)
|
||||
{
|
||||
return this.unbuildables.has(template) && this.unbuildables.get(template).time > gameState.ai.elapsedTime;
|
||||
};
|
||||
isUnbuildable(gameState, template)
|
||||
{
|
||||
return this.unbuildables.has(template) &&
|
||||
this.unbuildables.get(template).time > gameState.ai.elapsedTime;
|
||||
}
|
||||
|
||||
BuildManager.prototype.setBuildable = function(template)
|
||||
{
|
||||
setBuildable(template)
|
||||
{
|
||||
if (this.unbuildables.has(template))
|
||||
this.unbuildables.delete(template);
|
||||
};
|
||||
}
|
||||
|
||||
/** Time is the duration in second that we will wait before checking again if it is buildable */
|
||||
BuildManager.prototype.setUnbuildable = function(gameState, template, time = 90, reason = "room")
|
||||
{
|
||||
/** Time is the duration in second that we will wait before checking again if it is buildable */
|
||||
setUnbuildable(gameState, template, time = 90, reason = "room")
|
||||
{
|
||||
if (!this.unbuildables.has(template))
|
||||
this.unbuildables.set(template, { "reason": reason, "time": gameState.ai.elapsedTime + time });
|
||||
{
|
||||
this.unbuildables.set(template,
|
||||
{ "reason": reason, "time": gameState.ai.elapsedTime + time });
|
||||
}
|
||||
else
|
||||
{
|
||||
const unbuildable = this.unbuildables.get(template);
|
||||
|
|
@ -152,37 +155,37 @@ BuildManager.prototype.setUnbuildable = function(gameState, template, time = 90,
|
|||
unbuildable.time = gameState.ai.elapsedTime + time;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Return the number of unbuildables due to missing room */
|
||||
BuildManager.prototype.numberMissingRoom = function(gameState)
|
||||
{
|
||||
/** Return the number of unbuildables due to missing room */
|
||||
numberMissingRoom(gameState)
|
||||
{
|
||||
let num = 0;
|
||||
for (const unbuildable of this.unbuildables.values())
|
||||
if (unbuildable.reason == "room" && unbuildable.time > gameState.ai.elapsedTime)
|
||||
++num;
|
||||
return num;
|
||||
};
|
||||
}
|
||||
|
||||
/** Reset the unbuildables due to missing room */
|
||||
BuildManager.prototype.resetMissingRoom = function(gameState)
|
||||
{
|
||||
/** Reset the unbuildables due to missing room */
|
||||
resetMissingRoom(gameState)
|
||||
{
|
||||
for (const [key, unbuildable] of this.unbuildables)
|
||||
if (unbuildable.reason == "room")
|
||||
this.unbuildables.delete(key);
|
||||
};
|
||||
}
|
||||
|
||||
BuildManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
return {
|
||||
"builders": this.builders,
|
||||
"unbuildables": this.unbuildables
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
BuildManager.prototype.Deserialize = function(data)
|
||||
{
|
||||
Deserialize(data)
|
||||
{
|
||||
for (const key in data)
|
||||
this[key] = data[key];
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,16 @@
|
|||
import { aiWarn } from "simulation/ai/common-api/utils.js";
|
||||
import * as difficultyLevel from "simulation/ai/petra/difficultyLevel.js";
|
||||
|
||||
export function Config(difficulty = difficultyLevel.MEDIUM, behavior)
|
||||
export class Config
|
||||
{
|
||||
this.difficulty = difficulty;
|
||||
|
||||
// for instance "balanced", "aggressive" or "defensive"
|
||||
this.behavior = behavior || "random";
|
||||
|
||||
// debug level: 0=none, 1=sanity checks, 2=debug, 3=detailed debug, -100=serializatio debug
|
||||
this.debug = 0;
|
||||
debug = 0;
|
||||
|
||||
this.chat = true; // false to prevent AI's chats
|
||||
chat = true; // false to prevent AI's chats
|
||||
|
||||
this.popScaling = 1; // scale factor depending on the max population
|
||||
popScaling = 1; // scale factor depending on the max population
|
||||
|
||||
this.Military = {
|
||||
Military = {
|
||||
"towerLapseTime": 360, // Time to wait between building 2 towers
|
||||
"fortressLapseTime": 390, // Time to wait between building 2 fortresses
|
||||
"popForBarracks1": 25,
|
||||
|
|
@ -24,14 +19,14 @@ export function Config(difficulty = difficultyLevel.MEDIUM, behavior)
|
|||
"numSentryTowers": 1
|
||||
};
|
||||
|
||||
this.DamageTypeImportance = {
|
||||
DamageTypeImportance = {
|
||||
"Hack": 0.075,
|
||||
"Pierce": 0.085,
|
||||
"Crush": 0.045,
|
||||
"Fire": 0.001
|
||||
};
|
||||
|
||||
this.Economy = {
|
||||
Economy = {
|
||||
"popPhase2": 150, // How many units we want before aging to phase2.
|
||||
"workPhase3": 180, // How many workers we want before aging to phase3.
|
||||
"workPhase4": 200, // How many workers we want before aging to phase4 or higher.
|
||||
|
|
@ -45,7 +40,7 @@ export function Config(difficulty = difficultyLevel.MEDIUM, behavior)
|
|||
|
||||
// Note: attack settings are set directly in attack_plan.js
|
||||
// defense
|
||||
this.Defense =
|
||||
Defense =
|
||||
{
|
||||
"defenseRatio": { "ally": 1.4, "neutral": 1.8, "own": 2 }, // ratio of defenders/attackers.
|
||||
"armyCompactSize": 2000, // squared. Half-diameter of an army.
|
||||
|
|
@ -55,7 +50,7 @@ export function Config(difficulty = difficultyLevel.MEDIUM, behavior)
|
|||
|
||||
// Additional buildings that the AI does not yet know when to build
|
||||
// and that it will try to build on phase 3 when enough resources.
|
||||
this.buildings =
|
||||
buildings =
|
||||
{
|
||||
"default": [],
|
||||
"achae": [
|
||||
|
|
@ -112,7 +107,7 @@ export function Config(difficulty = difficultyLevel.MEDIUM, behavior)
|
|||
]
|
||||
};
|
||||
|
||||
this.priorities =
|
||||
priorities =
|
||||
{
|
||||
"villager": 300, // should be slightly lower than the citizen soldier one to not get all the food
|
||||
"citizenSoldier": 600,
|
||||
|
|
@ -135,7 +130,7 @@ export function Config(difficulty = difficultyLevel.MEDIUM, behavior)
|
|||
};
|
||||
|
||||
// Default personality (will be updated in setConfig)
|
||||
this.personality =
|
||||
personality =
|
||||
{
|
||||
"aggressive": 0.5,
|
||||
"cooperative": 0.5,
|
||||
|
|
@ -143,7 +138,7 @@ export function Config(difficulty = difficultyLevel.MEDIUM, behavior)
|
|||
};
|
||||
|
||||
// See QueueManager.prototype.wantedGatherRates()
|
||||
this.queues =
|
||||
queues =
|
||||
{
|
||||
"firstTurn": {
|
||||
"food": 10,
|
||||
|
|
@ -163,15 +158,15 @@ export function Config(difficulty = difficultyLevel.MEDIUM, behavior)
|
|||
}
|
||||
};
|
||||
|
||||
this.garrisonHealthLevel = { "low": 0.4, "medium": 0.55, "high": 0.7 };
|
||||
garrisonHealthLevel = { "low": 0.4, "medium": 0.55, "high": 0.7 };
|
||||
|
||||
this.unusedNoAllyTechs = [
|
||||
unusedNoAllyTechs = [
|
||||
"Player/sharedLos",
|
||||
"Market/InternationalBonus",
|
||||
"Player/sharedDropsites"
|
||||
];
|
||||
|
||||
this.criticalPopulationFactors = [
|
||||
criticalPopulationFactors = [
|
||||
0.8,
|
||||
0.8,
|
||||
0.7,
|
||||
|
|
@ -180,7 +175,7 @@ export function Config(difficulty = difficultyLevel.MEDIUM, behavior)
|
|||
0.35
|
||||
];
|
||||
|
||||
this.criticalStructureFactors = [
|
||||
criticalStructureFactors = [
|
||||
0.8,
|
||||
0.8,
|
||||
0.7,
|
||||
|
|
@ -189,7 +184,7 @@ export function Config(difficulty = difficultyLevel.MEDIUM, behavior)
|
|||
0.35
|
||||
];
|
||||
|
||||
this.criticalRootFactors = [
|
||||
criticalRootFactors = [
|
||||
0.8,
|
||||
0.8,
|
||||
0.67,
|
||||
|
|
@ -197,10 +192,17 @@ export function Config(difficulty = difficultyLevel.MEDIUM, behavior)
|
|||
0.35,
|
||||
0.2
|
||||
];
|
||||
}
|
||||
|
||||
Config.prototype.setConfig = function(gameState)
|
||||
{
|
||||
constructor(difficulty = difficultyLevel.MEDIUM, behavior)
|
||||
{
|
||||
this.difficulty = difficulty;
|
||||
|
||||
// for instance "balanced", "aggressive" or "defensive"
|
||||
this.behavior = behavior || "random";
|
||||
}
|
||||
|
||||
setConfig(gameState)
|
||||
{
|
||||
if (this.difficulty > difficultyLevel.SANDBOX)
|
||||
{
|
||||
// Setup personality traits according to the user choice:
|
||||
|
|
@ -320,10 +322,10 @@ Config.prototype.setConfig = function(gameState)
|
|||
if (this.debug < 2)
|
||||
return;
|
||||
aiWarn(" >>> Petra bot: personality = " + uneval(this.personality));
|
||||
};
|
||||
}
|
||||
|
||||
Config.prototype.Cheat = function(gameState)
|
||||
{
|
||||
Cheat(gameState)
|
||||
{
|
||||
// Sandbox, Very Easy, Easy, Medium, Hard, Very Hard
|
||||
// rate apply on resource stockpiling as gathering and trading
|
||||
// time apply on building, upgrading, packing, training and technologies
|
||||
|
|
@ -335,19 +337,20 @@ Config.prototype.Cheat = function(gameState)
|
|||
"Trader/GainMultiplier": [{ "affects": ["Unit", "Structure"], "multiply": rate[AIDiff] }],
|
||||
"Cost/BuildTime": [{ "affects": ["Unit", "Structure"], "multiply": time[AIDiff] }],
|
||||
}, gameState.playerData.entity);
|
||||
};
|
||||
}
|
||||
|
||||
Config.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
var data = {};
|
||||
for (const key in this)
|
||||
if (Object.hasOwn(this, key) && key != "debug")
|
||||
data[key] = this[key];
|
||||
return data;
|
||||
};
|
||||
}
|
||||
|
||||
Config.prototype.Deserialize = function(data)
|
||||
{
|
||||
Deserialize(data)
|
||||
{
|
||||
for (const key in data)
|
||||
this[key] = data[key];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@ import { Worker } from "simulation/ai/petra/worker.js";
|
|||
* "capturing": army set to capture a gaia building or recover capture points to one of its own structures
|
||||
* It must contain only one foe (the building to capture) and never be merged
|
||||
*/
|
||||
export function DefenseArmy(gameState, foeEntities, type)
|
||||
export class DefenseArmy
|
||||
{
|
||||
constructor(gameState, foeEntities, type)
|
||||
{
|
||||
this.ID = gameState.ai.uniqueIDs.armies++;
|
||||
this.type = type || "default";
|
||||
|
||||
|
|
@ -46,16 +48,16 @@ export function DefenseArmy(gameState, foeEntities, type)
|
|||
this.recalculatePosition(gameState, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* add an entity to the enemy army
|
||||
* Will return true if the entity was added and false otherwise.
|
||||
* won't recalculate our position but will dirty it.
|
||||
* force is true at army creation or when merging armies, so in this case we should add it even if far
|
||||
*/
|
||||
DefenseArmy.prototype.addFoe = function(gameState, enemyId, force)
|
||||
{
|
||||
addFoe(gameState, enemyId, force)
|
||||
{
|
||||
if (this.foeEntities.indexOf(enemyId) !== -1)
|
||||
return false;
|
||||
const ent = gameState.getEntityById(enemyId);
|
||||
|
|
@ -73,14 +75,14 @@ DefenseArmy.prototype.addFoe = function(gameState, enemyId, force)
|
|||
ent.setMetadata(PlayerID, "PartOfArmy", this.ID);
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* returns true if the entity was removed and false otherwise.
|
||||
* TODO: when there is a technology update, we should probably recompute the strengths, or weird stuffs will happen.
|
||||
*/
|
||||
DefenseArmy.prototype.removeFoe = function(gameState, enemyId, enemyEntity)
|
||||
{
|
||||
removeFoe(gameState, enemyId, enemyEntity)
|
||||
{
|
||||
const idx = this.foeEntities.indexOf(enemyId);
|
||||
if (idx === -1)
|
||||
return false;
|
||||
|
|
@ -100,14 +102,14 @@ DefenseArmy.prototype.removeFoe = function(gameState, enemyId, enemyEntity)
|
|||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* adds a defender but doesn't assign him yet.
|
||||
* force is true when merging armies, so in this case we should add it even if no position as it can be in a ship
|
||||
*/
|
||||
DefenseArmy.prototype.addOwn = function(gameState, id, force)
|
||||
{
|
||||
addOwn(gameState, id, force)
|
||||
{
|
||||
if (this.ownEntities.indexOf(id) !== -1)
|
||||
return false;
|
||||
const ent = gameState.getEntityById(id);
|
||||
|
|
@ -129,10 +131,10 @@ DefenseArmy.prototype.addOwn = function(gameState, id, force)
|
|||
ent.setMetadata(PlayerID, "formerSubrole", subrole);
|
||||
ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_DEFENDER);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
DefenseArmy.prototype.removeOwn = function(gameState, id, Entity)
|
||||
{
|
||||
removeOwn(gameState, id, Entity)
|
||||
{
|
||||
const idx = this.ownEntities.indexOf(id);
|
||||
if (idx === -1)
|
||||
return false;
|
||||
|
|
@ -188,17 +190,17 @@ DefenseArmy.prototype.removeOwn = function(gameState, id, Entity)
|
|||
plan.cancelTransport(gameState);
|
||||
}
|
||||
}
|
||||
*/
|
||||
*/
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* resets the army properly.
|
||||
* assumes we already cleared dead units.
|
||||
*/
|
||||
DefenseArmy.prototype.clear = function(gameState)
|
||||
{
|
||||
clear(gameState)
|
||||
{
|
||||
while (this.foeEntities.length > 0)
|
||||
this.removeFoe(gameState, this.foeEntities[0]);
|
||||
|
||||
|
|
@ -294,10 +296,10 @@ DefenseArmy.prototype.clear = function(gameState)
|
|||
|
||||
this.recalculateStrengths(gameState);
|
||||
this.recalculatePosition(gameState);
|
||||
};
|
||||
}
|
||||
|
||||
DefenseArmy.prototype.assignUnit = function(gameState, entID)
|
||||
{
|
||||
assignUnit(gameState, entID)
|
||||
{
|
||||
// we'll assume this defender is ours already.
|
||||
// we'll also override any previous assignment
|
||||
|
||||
|
|
@ -369,27 +371,27 @@ DefenseArmy.prototype.assignUnit = function(gameState, entID)
|
|||
else
|
||||
gameState.ai.HQ.navalManager.requireTransport(gameState, ent, ownIndex, foeIndex, foePosition);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
DefenseArmy.prototype.getType = function()
|
||||
{
|
||||
getType()
|
||||
{
|
||||
return this.type;
|
||||
};
|
||||
}
|
||||
|
||||
DefenseArmy.prototype.getState = function()
|
||||
{
|
||||
getState()
|
||||
{
|
||||
if (!this.foeEntities.length)
|
||||
return 0;
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* merge this army with another properly.
|
||||
* assumes units are in only one army.
|
||||
* also assumes that all have been properly cleaned up (no dead units).
|
||||
*/
|
||||
DefenseArmy.prototype.merge = function(gameState, otherArmy)
|
||||
{
|
||||
merge(gameState, otherArmy)
|
||||
{
|
||||
// copy over all parameters.
|
||||
for (const i in otherArmy.assignedAgainst)
|
||||
{
|
||||
|
|
@ -411,10 +413,10 @@ DefenseArmy.prototype.merge = function(gameState, otherArmy)
|
|||
this.recalculateStrengths(gameState);
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
DefenseArmy.prototype.needsDefenders = function(gameState)
|
||||
{
|
||||
needsDefenders(gameState)
|
||||
{
|
||||
let defenseRatio;
|
||||
const territoryOwner = gameState.ai.HQ.territoryMap.getOwner(this.foePosition);
|
||||
if (territoryOwner == PlayerID)
|
||||
|
|
@ -438,12 +440,12 @@ DefenseArmy.prototype.needsDefenders = function(gameState)
|
|||
if (this.foeStrength * defenseRatio <= this.ownStrength)
|
||||
return false;
|
||||
return this.foeStrength * defenseRatio - this.ownStrength;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/** if not forced, will only recalculate if on a different turn. */
|
||||
DefenseArmy.prototype.recalculatePosition = function(gameState, force)
|
||||
{
|
||||
/** if not forced, will only recalculate if on a different turn. */
|
||||
recalculatePosition(gameState, force)
|
||||
{
|
||||
if (!force && this.positionLastUpdate === gameState.ai.elapsedTime)
|
||||
return;
|
||||
|
||||
|
|
@ -467,10 +469,10 @@ DefenseArmy.prototype.recalculatePosition = function(gameState, force)
|
|||
}
|
||||
|
||||
this.positionLastUpdate = gameState.ai.elapsedTime;
|
||||
};
|
||||
}
|
||||
|
||||
DefenseArmy.prototype.recalculateStrengths = function(gameState)
|
||||
{
|
||||
recalculateStrengths(gameState)
|
||||
{
|
||||
this.ownStrength = 0;
|
||||
this.foeStrength = 0;
|
||||
|
||||
|
|
@ -478,11 +480,11 @@ DefenseArmy.prototype.recalculateStrengths = function(gameState)
|
|||
this.evaluateStrength(gameState.getEntityById(id));
|
||||
for (const id of this.ownEntities)
|
||||
this.evaluateStrength(gameState.getEntityById(id), true);
|
||||
};
|
||||
}
|
||||
|
||||
/** adds or remove the strength of the entity either to the enemy or to our units. */
|
||||
DefenseArmy.prototype.evaluateStrength = function(ent, isOwn, remove)
|
||||
{
|
||||
/** adds or remove the strength of the entity either to the enemy or to our units. */
|
||||
evaluateStrength(ent, isOwn, remove)
|
||||
{
|
||||
if (!ent)
|
||||
return;
|
||||
|
||||
|
|
@ -509,10 +511,10 @@ DefenseArmy.prototype.evaluateStrength = function(ent, isOwn, remove)
|
|||
this.ownStrength += entStrength;
|
||||
else
|
||||
this.foeStrength += entStrength;
|
||||
};
|
||||
}
|
||||
|
||||
DefenseArmy.prototype.checkEvents = function(gameState, events)
|
||||
{
|
||||
checkEvents(gameState, events)
|
||||
{
|
||||
// Warning the metadata is already cloned in shared.js. Futhermore, changes should be done before destroyEvents
|
||||
// otherwise it would remove the old entity from this army list
|
||||
// TODO we should may-be reevaluate the strength
|
||||
|
|
@ -567,10 +569,10 @@ DefenseArmy.prototype.checkEvents = function(gameState, events)
|
|||
this.removeOwn(gameState, evt.entity);
|
||||
this.removeFoe(gameState, evt.entity);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
DefenseArmy.prototype.update = function(gameState)
|
||||
{
|
||||
update(gameState)
|
||||
{
|
||||
for (const entId of this.ownEntities)
|
||||
{
|
||||
const ent = gameState.getEntityById(entId);
|
||||
|
|
@ -632,10 +634,10 @@ DefenseArmy.prototype.update = function(gameState)
|
|||
}
|
||||
|
||||
return breakaways;
|
||||
};
|
||||
}
|
||||
|
||||
DefenseArmy.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
return {
|
||||
"ID": this.ID,
|
||||
"type": this.type,
|
||||
|
|
@ -648,10 +650,11 @@ DefenseArmy.prototype.Serialize = function()
|
|||
"ownEntities": this.ownEntities,
|
||||
"ownStrength": this.ownStrength
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
DefenseArmy.prototype.Deserialize = function(data)
|
||||
{
|
||||
Deserialize(data)
|
||||
{
|
||||
for (const key in data)
|
||||
this[key] = data[key];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ import { allowCapture, getLandAccess, getMaxStrength, isSiegeUnit } from
|
|||
import { GarrisonManager } from "simulation/ai/petra/garrisonManager.js";
|
||||
import { Worker } from "simulation/ai/petra/worker.js";
|
||||
|
||||
export function DefenseManager(Config)
|
||||
export class DefenseManager
|
||||
{
|
||||
constructor(Config)
|
||||
{
|
||||
// Array of "army" Objects.
|
||||
this.armies = [];
|
||||
this.Config = Config;
|
||||
|
|
@ -21,10 +23,10 @@ export function DefenseManager(Config)
|
|||
this.attackingArmies = {};
|
||||
this.attackingUnits = {};
|
||||
this.attackedAllies = {};
|
||||
}
|
||||
}
|
||||
|
||||
DefenseManager.prototype.update = function(gameState, events)
|
||||
{
|
||||
update(gameState, events)
|
||||
{
|
||||
Engine.ProfileStart("Defense Manager");
|
||||
|
||||
this.territoryMap = gameState.ai.HQ.territoryMap;
|
||||
|
|
@ -72,10 +74,10 @@ DefenseManager.prototype.update = function(gameState, events)
|
|||
this.assignDefenders(gameState);
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
}
|
||||
|
||||
DefenseManager.prototype.makeIntoArmy = function(gameState, entityID, type = "default")
|
||||
{
|
||||
makeIntoArmy(gameState, entityID, type = "default")
|
||||
{
|
||||
if (type == "default")
|
||||
{
|
||||
for (const army of this.armies)
|
||||
|
|
@ -84,15 +86,15 @@ DefenseManager.prototype.makeIntoArmy = function(gameState, entityID, type = "de
|
|||
}
|
||||
|
||||
this.armies.push(new DefenseArmy(gameState, [entityID], type));
|
||||
};
|
||||
}
|
||||
|
||||
DefenseManager.prototype.getArmy = function(partOfArmy)
|
||||
{
|
||||
getArmy(partOfArmy)
|
||||
{
|
||||
return this.armies.find(army => army.ID == partOfArmy);
|
||||
};
|
||||
}
|
||||
|
||||
DefenseManager.prototype.isDangerous = function(gameState, entity)
|
||||
{
|
||||
isDangerous(gameState, entity)
|
||||
{
|
||||
if (!entity.position())
|
||||
return false;
|
||||
|
||||
|
|
@ -201,10 +203,10 @@ DefenseManager.prototype.isDangerous = function(gameState, entity)
|
|||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
DefenseManager.prototype.checkEnemyUnits = function(gameState)
|
||||
{
|
||||
checkEnemyUnits(gameState)
|
||||
{
|
||||
const nbPlayers = gameState.sharedScript.playersData.length;
|
||||
const i = gameState.ai.playedTurn % nbPlayers;
|
||||
this.attackingUnits[i] = undefined;
|
||||
|
|
@ -277,10 +279,10 @@ DefenseManager.prototype.checkEnemyUnits = function(gameState)
|
|||
if (owner == PlayerID)
|
||||
this.makeIntoArmy(gameState, ent.id(), "capturing");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
DefenseManager.prototype.checkEnemyArmies = function(gameState)
|
||||
{
|
||||
checkEnemyArmies(gameState)
|
||||
{
|
||||
for (let i = 0; i < this.armies.length; ++i)
|
||||
{
|
||||
const army = this.armies[i];
|
||||
|
|
@ -392,10 +394,10 @@ DefenseManager.prototype.checkEnemyArmies = function(gameState)
|
|||
army.clear(gameState);
|
||||
this.armies.splice(i--, 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
DefenseManager.prototype.assignDefenders = function(gameState)
|
||||
{
|
||||
assignDefenders(gameState)
|
||||
{
|
||||
if (!this.armies.length)
|
||||
return;
|
||||
|
||||
|
|
@ -520,10 +522,10 @@ DefenseManager.prototype.assignDefenders = function(gameState)
|
|||
for (let a = 0; a < armiesNeeding.length; ++a)
|
||||
armiesPos.push(armiesNeeding[a].army.foePosition);
|
||||
gameState.ai.HQ.trainEmergencyUnits(gameState, armiesPos);
|
||||
};
|
||||
}
|
||||
|
||||
DefenseManager.prototype.abortArmy = function(gameState, army)
|
||||
{
|
||||
abortArmy(gameState, army)
|
||||
{
|
||||
army.clear(gameState);
|
||||
for (let i = 0; i < this.armies.length; ++i)
|
||||
{
|
||||
|
|
@ -532,16 +534,16 @@ DefenseManager.prototype.abortArmy = function(gameState, army)
|
|||
this.armies.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* If our defense structures are attacked, garrison soldiers inside when possible
|
||||
* and if a support unit is attacked and has less than 55% health, garrison it inside the nearest healing structure
|
||||
* and if a ranged siege unit (not used for defense) is attacked, garrison it in the nearest fortress.
|
||||
* If our hero is attacked with regicide victory condition, the victoryManager will handle it.
|
||||
*/
|
||||
DefenseManager.prototype.checkEvents = function(gameState, events)
|
||||
{
|
||||
checkEvents(gameState, events)
|
||||
{
|
||||
// Must be called every turn for all armies.
|
||||
for (const army of this.armies)
|
||||
army.checkEvents(gameState, events);
|
||||
|
|
@ -756,10 +758,10 @@ DefenseManager.prototype.checkEvents = function(gameState, events)
|
|||
target.attack(attacker.id(), shouldCapture);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
DefenseManager.prototype.garrisonUnitsInside = function(gameState, target, data)
|
||||
{
|
||||
garrisonUnitsInside(gameState, target, data)
|
||||
{
|
||||
if (target.hitpoints() < target.garrisonEjectHealth() * target.maxHitpoints())
|
||||
return false;
|
||||
const minGarrison = data.min || target.garrisonMax();
|
||||
|
|
@ -838,11 +840,11 @@ DefenseManager.prototype.garrisonUnitsInside = function(gameState, target, data)
|
|||
ret = true;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
/** Garrison a attacked siege ranged unit inside the nearest fortress. */
|
||||
DefenseManager.prototype.garrisonSiegeUnit = function(gameState, unit)
|
||||
{
|
||||
/** Garrison a attacked siege ranged unit inside the nearest fortress. */
|
||||
garrisonSiegeUnit(gameState, unit)
|
||||
{
|
||||
let distmin = Math.min();
|
||||
let nearest;
|
||||
const unitAccess = getLandAccess(gameState, unit);
|
||||
|
|
@ -868,15 +870,15 @@ DefenseManager.prototype.garrisonSiegeUnit = function(gameState, unit)
|
|||
if (nearest)
|
||||
garrisonManager.garrison(gameState, unit, nearest, GarrisonManager.TYPE_PROTECTION);
|
||||
return nearest !== undefined;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Garrison a hurt unit inside a player-owned or allied structure.
|
||||
* If emergency is true, the unit will be garrisoned in the closest possible structure.
|
||||
* Otherwise, it will garrison in the closest healing structure.
|
||||
*/
|
||||
DefenseManager.prototype.garrisonAttackedUnit = function(gameState, unit, emergency = false)
|
||||
{
|
||||
garrisonAttackedUnit(gameState, unit, emergency = false)
|
||||
{
|
||||
let distmin = Math.min();
|
||||
let nearest;
|
||||
const unitAccess = getLandAccess(gameState, unit);
|
||||
|
|
@ -916,24 +918,24 @@ DefenseManager.prototype.garrisonAttackedUnit = function(gameState, unit, emerge
|
|||
garrisonManager.garrison(gameState, unit, nearest,
|
||||
nearest.buffHeal() ? GarrisonManager.TYPE_PROTECTION : GarrisonManager.TYPE_EMERGENCY);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Be more inclined to help an ally attacked by several enemies.
|
||||
*/
|
||||
DefenseManager.prototype.GetCooperationLevel = function(ally)
|
||||
{
|
||||
GetCooperationLevel(ally)
|
||||
{
|
||||
let cooperation = this.Config.personality.cooperative;
|
||||
if (this.attackedAllies[ally] && this.attackedAllies[ally] > 1)
|
||||
cooperation += 0.2 * (this.attackedAllies[ally] - 1);
|
||||
return cooperation;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Switch a defense army into an attack if needed.
|
||||
*/
|
||||
DefenseManager.prototype.switchToAttack = function(gameState, army)
|
||||
{
|
||||
switchToAttack(gameState, army)
|
||||
{
|
||||
if (!army)
|
||||
return;
|
||||
for (const targetId of this.targetList)
|
||||
|
|
@ -954,10 +956,10 @@ DefenseManager.prototype.switchToAttack = function(gameState, army)
|
|||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
DefenseManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
const properties = {
|
||||
"targetList": this.targetList,
|
||||
"armyMergeSize": this.armyMergeSize,
|
||||
|
|
@ -971,10 +973,10 @@ DefenseManager.prototype.Serialize = function()
|
|||
armies.push(army.Serialize());
|
||||
|
||||
return { "properties": properties, "armies": armies };
|
||||
};
|
||||
}
|
||||
|
||||
DefenseManager.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
Deserialize(gameState, data)
|
||||
{
|
||||
for (const key in data.properties)
|
||||
this[key] = data.properties[key];
|
||||
|
||||
|
|
@ -985,4 +987,5 @@ DefenseManager.prototype.Deserialize = function(gameState, data)
|
|||
army.Deserialize(dataArmy);
|
||||
this.armies.push(army);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,10 @@ import * as chat from "simulation/ai/petra/chatHelper.js";
|
|||
* that we suggested within a period of time, or else the request will be deleted from this.sentDiplomacyRequests.
|
||||
* When @c deserialized is true, do not call any random function inside constructor as that would cause oos.
|
||||
*/
|
||||
export function DiplomacyManager(Config, deserialized)
|
||||
export class DiplomacyManager
|
||||
{
|
||||
constructor(Config, deserialized)
|
||||
{
|
||||
this.Config = Config;
|
||||
this.nextTributeUpdate = 90;
|
||||
this.nextTributeRequest = new Map();
|
||||
|
|
@ -36,14 +38,14 @@ export function DiplomacyManager(Config, deserialized)
|
|||
this.receivedDiplomacyRequests = new Map();
|
||||
this.sentDiplomacyRequests = new Map();
|
||||
this.sentDiplomacyRequestLapseTime = deserialized ? 175 : randFloat(130, 220);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* If there are any players that are allied/neutral with us but we are not allied/neutral with them,
|
||||
* treat this situation like an ally/neutral request.
|
||||
*/
|
||||
DiplomacyManager.prototype.init = function(gameState)
|
||||
{
|
||||
init(gameState)
|
||||
{
|
||||
this.lastManStandingCheck(gameState);
|
||||
|
||||
for (let i = 1; i < gameState.sharedScript.playersData.length; ++i)
|
||||
|
|
@ -58,14 +60,14 @@ DiplomacyManager.prototype.init = function(gameState)
|
|||
else if (gameState.sharedScript.playersData[i].isNeutral[PlayerID] && gameState.isPlayerEnemy(i))
|
||||
this.handleDiplomacyRequest(gameState, i, "neutral");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if any allied needs help (tribute) and sent it if we have enough resource
|
||||
* or ask for a tribute if we are in need and one ally can help
|
||||
*/
|
||||
DiplomacyManager.prototype.tributes = function(gameState)
|
||||
{
|
||||
tributes(gameState)
|
||||
{
|
||||
this.nextTributeUpdate = gameState.ai.elapsedTime + 30;
|
||||
const resTribCodes = Resources.GetTributableCodes();
|
||||
if (!resTribCodes.length)
|
||||
|
|
@ -124,10 +126,10 @@ DiplomacyManager.prototype.tributes = function(gameState)
|
|||
chat.sentTribute(gameState, i);
|
||||
Engine.PostCommand(PlayerID, { "type": "tribute", "player": i, "amounts": tribute });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
DiplomacyManager.prototype.checkEvents = function(gameState, events)
|
||||
{
|
||||
checkEvents(gameState, events)
|
||||
{
|
||||
// Increase slowly the cooperative personality trait either when we receive tribute from our allies
|
||||
// or if our allies attack enemies inside our territory
|
||||
for (const evt of events.TributeExchanged)
|
||||
|
|
@ -283,14 +285,14 @@ DiplomacyManager.prototype.checkEvents = function(gameState, events)
|
|||
this.nextTributeUpdate = gameState.ai.elapsedTime + 15;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* If the "Last Man Standing" option is enabled, check if the only remaining players are allies or neutral.
|
||||
* If so, turn against the strongest first, but be more likely to first turn against neutral players, if there are any.
|
||||
*/
|
||||
DiplomacyManager.prototype.lastManStandingCheck = function(gameState)
|
||||
{
|
||||
lastManStandingCheck(gameState)
|
||||
{
|
||||
if (gameState.sharedScript.playersData[PlayerID].teamsLocked || gameState.isCeasefireActive() ||
|
||||
gameState.getAlliedVictory() && gameState.hasAllies())
|
||||
return;
|
||||
|
|
@ -377,14 +379,14 @@ DiplomacyManager.prototype.lastManStandingCheck = function(gameState)
|
|||
}
|
||||
this.betrayLapseTime = -1;
|
||||
this.waitingToBetray = false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Do not become allies with a player if the game would be over.
|
||||
* Overall, be reluctant to become allies with any one player, but be more likely to accept neutral requests.
|
||||
*/
|
||||
DiplomacyManager.prototype.handleDiplomacyRequest = function(gameState, player, requestType)
|
||||
{
|
||||
handleDiplomacyRequest(gameState, player, requestType)
|
||||
{
|
||||
if (gameState.sharedScript.playersData[PlayerID].teamsLocked)
|
||||
return;
|
||||
let response;
|
||||
|
|
@ -445,10 +447,10 @@ DiplomacyManager.prototype.handleDiplomacyRequest = function(gameState, player,
|
|||
}
|
||||
}
|
||||
chat.answerRequestDiplomacy(gameState, player, requestType, response, requiredTribute);
|
||||
};
|
||||
}
|
||||
|
||||
DiplomacyManager.prototype.changePlayerDiplomacy = function(gameState, player, newDiplomaticStance)
|
||||
{
|
||||
changePlayerDiplomacy(gameState, player, newDiplomaticStance)
|
||||
{
|
||||
if (gameState.isPlayerEnemy(player) && (newDiplomaticStance === "ally" || newDiplomaticStance === "neutral"))
|
||||
gameState.ai.HQ.attackManager.cancelAttacksAgainstPlayer(gameState, player);
|
||||
Engine.PostCommand(PlayerID, { "type": "diplomacy", "player": player, "to": newDiplomaticStance });
|
||||
|
|
@ -456,10 +458,10 @@ DiplomacyManager.prototype.changePlayerDiplomacy = function(gameState, player, n
|
|||
aiWarn("diplomacy stance with player " + player + " is now " + newDiplomaticStance);
|
||||
if (this.Config.chat)
|
||||
chat.newDiplomacy(gameState, player, newDiplomaticStance);
|
||||
};
|
||||
}
|
||||
|
||||
DiplomacyManager.prototype.checkRequestedTributes = function(gameState)
|
||||
{
|
||||
checkRequestedTributes(gameState)
|
||||
{
|
||||
for (const [player, data] of this.receivedDiplomacyRequests)
|
||||
if (data.status === "waitingForTribute" && gameState.ai.elapsedTime > data.warnTime)
|
||||
{
|
||||
|
|
@ -478,14 +480,14 @@ DiplomacyManager.prototype.checkRequestedTributes = function(gameState)
|
|||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Try to become allies with a player who has a lot of mutual enemies in common with us.
|
||||
* TODO: Possibly let human players demand tributes from AIs who send diplomacy requests.
|
||||
*/
|
||||
DiplomacyManager.prototype.sendDiplomacyRequest = function(gameState)
|
||||
{
|
||||
sendDiplomacyRequest(gameState)
|
||||
{
|
||||
let player;
|
||||
let max = 0;
|
||||
for (let i = 1; i < gameState.sharedScript.playersData.length; ++i)
|
||||
|
|
@ -524,10 +526,10 @@ DiplomacyManager.prototype.sendDiplomacyRequest = function(gameState)
|
|||
aiWarn("Sending diplomacy request to player " + player + " with " + requestType);
|
||||
Engine.PostCommand(PlayerID, { "type": "diplomacy-request", "source": PlayerID, "player": player, "to": requestType });
|
||||
chat.newRequestDiplomacy(gameState, player, requestType, "sendRequest");
|
||||
};
|
||||
}
|
||||
|
||||
DiplomacyManager.prototype.checkSentDiplomacyRequests = function(gameState)
|
||||
{
|
||||
checkSentDiplomacyRequests(gameState)
|
||||
{
|
||||
for (const [player, data] of this.sentDiplomacyRequests)
|
||||
if (gameState.ai.elapsedTime > data.timeSent + 60 && !gameState.ai.HQ.saveResources &&
|
||||
gameState.getPopulation() > 70)
|
||||
|
|
@ -535,10 +537,10 @@ DiplomacyManager.prototype.checkSentDiplomacyRequests = function(gameState)
|
|||
chat.newRequestDiplomacy(gameState, player, data.requestType, "requestExpired");
|
||||
this.sentDiplomacyRequests.delete(player);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
DiplomacyManager.prototype.update = function(gameState, events)
|
||||
{
|
||||
update(gameState, events)
|
||||
{
|
||||
this.checkEvents(gameState, events);
|
||||
|
||||
if (Resources.GetTributableCodes().length && !gameState.ai.HQ.saveResources && gameState.ai.elapsedTime > this.nextTributeUpdate)
|
||||
|
|
@ -563,10 +565,10 @@ DiplomacyManager.prototype.update = function(gameState, events)
|
|||
}
|
||||
|
||||
this.checkSentDiplomacyRequests(gameState);
|
||||
};
|
||||
}
|
||||
|
||||
DiplomacyManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
return {
|
||||
"nextTributeUpdate": this.nextTributeUpdate,
|
||||
"nextTributeRequest": this.nextTributeRequest,
|
||||
|
|
@ -577,10 +579,11 @@ DiplomacyManager.prototype.Serialize = function()
|
|||
"sentDiplomacyRequests": this.sentDiplomacyRequests,
|
||||
"sentDiplomacyRequestLapseTime": this.sentDiplomacyRequestLapseTime
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
DiplomacyManager.prototype.Deserialize = function(data)
|
||||
{
|
||||
Deserialize(data)
|
||||
{
|
||||
for (const key in data)
|
||||
this[key] = data[key];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,24 +3,27 @@ import { emergency as chatEmergency } from "simulation/ai/petra/chatHelper.js";
|
|||
/**
|
||||
* Checks for emergencies and acts accordingly
|
||||
*/
|
||||
export function EmergencyManager(Config)
|
||||
export class EmergencyManager
|
||||
{
|
||||
this.Config = Config;
|
||||
this.referencePopulation = 0;
|
||||
this.referenceStructureCount = 0;
|
||||
this.numRoots = 0;
|
||||
this.hasEmergency = false;
|
||||
}
|
||||
referencePopulation = 0;
|
||||
referenceStructureCount = 0;
|
||||
numRoots = 0;
|
||||
hasEmergency = false;
|
||||
|
||||
EmergencyManager.prototype.init = function(gameState)
|
||||
{
|
||||
constructor(Config)
|
||||
{
|
||||
this.Config = Config;
|
||||
}
|
||||
|
||||
init(gameState)
|
||||
{
|
||||
this.referencePopulation = gameState.getPopulation();
|
||||
this.referenceStructureCount = gameState.getOwnStructures().length;
|
||||
this.numRoots = this.rootCount(gameState);
|
||||
};
|
||||
}
|
||||
|
||||
EmergencyManager.prototype.update = function(gameState)
|
||||
{
|
||||
update(gameState)
|
||||
{
|
||||
if (this.hasEmergency)
|
||||
{
|
||||
this.emergencyUpdate(gameState);
|
||||
|
|
@ -42,10 +45,10 @@ EmergencyManager.prototype.update = function(gameState)
|
|||
this.referenceStructureCount = nStructures;
|
||||
if (nRoots > this.numRoots || this.hasEmergency)
|
||||
this.numRoots = nRoots;
|
||||
};
|
||||
}
|
||||
|
||||
EmergencyManager.prototype.emergencyUpdate = function(gameState)
|
||||
{
|
||||
emergencyUpdate(gameState)
|
||||
{
|
||||
const pop = gameState.getPopulation();
|
||||
const nStructures = gameState.getOwnStructures().length;
|
||||
const nRoots = this.rootCount(gameState);
|
||||
|
|
@ -60,10 +63,10 @@ EmergencyManager.prototype.emergencyUpdate = function(gameState)
|
|||
this.referenceStructureCount = nStructures;
|
||||
this.numRoots = nRoots;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
EmergencyManager.prototype.rootCount = function(gameState)
|
||||
{
|
||||
rootCount(gameState)
|
||||
{
|
||||
let roots = 0;
|
||||
gameState.getOwnStructures().toEntityArray().forEach(ent =>
|
||||
{
|
||||
|
|
@ -71,26 +74,27 @@ EmergencyManager.prototype.rootCount = function(gameState)
|
|||
roots++;
|
||||
});
|
||||
return roots;
|
||||
};
|
||||
}
|
||||
|
||||
EmergencyManager.prototype.setEmergency = function(gameState, enable)
|
||||
{
|
||||
setEmergency(gameState, enable)
|
||||
{
|
||||
this.hasEmergency = enable;
|
||||
chatEmergency(gameState, enable);
|
||||
};
|
||||
}
|
||||
|
||||
EmergencyManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
return {
|
||||
"referencePopulation": this.referencePopulation,
|
||||
"referenceStructureCount": this.referenceStructureCount,
|
||||
"numRoots": this.numRoots,
|
||||
"hasEmergency": this.hasEmergency
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
EmergencyManager.prototype.Deserialize = function(data)
|
||||
{
|
||||
Deserialize(data)
|
||||
{
|
||||
for (const key in data)
|
||||
this[key] = data[key];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,21 +10,24 @@ import { Worker } from "simulation/ai/petra/worker.js";
|
|||
* Futhermore garrison units have a metadata garrisonType describing its reason (protection, transport, ...)
|
||||
*/
|
||||
|
||||
export function GarrisonManager(Config)
|
||||
export class GarrisonManager
|
||||
{
|
||||
holders = new Map();
|
||||
decayingStructures = new Map();
|
||||
|
||||
constructor(Config)
|
||||
{
|
||||
this.Config = Config;
|
||||
this.holders = new Map();
|
||||
this.decayingStructures = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
GarrisonManager.TYPE_FORCE = "force";
|
||||
GarrisonManager.TYPE_TRADE = "trade";
|
||||
GarrisonManager.TYPE_PROTECTION = "protection";
|
||||
GarrisonManager.TYPE_DECAY = "decay";
|
||||
GarrisonManager.TYPE_EMERGENCY = "emergency";
|
||||
static TYPE_FORCE = "force";
|
||||
static TYPE_TRADE = "trade";
|
||||
static TYPE_PROTECTION = "protection";
|
||||
static TYPE_DECAY = "decay";
|
||||
static TYPE_EMERGENCY = "emergency";
|
||||
|
||||
GarrisonManager.prototype.update = function(gameState, events)
|
||||
{
|
||||
update(gameState, events)
|
||||
{
|
||||
// First check for possible upgrade of a structure
|
||||
for (const evt of events.EntityRenamed)
|
||||
{
|
||||
|
|
@ -207,37 +210,37 @@ GarrisonManager.prototype.update = function(gameState, events)
|
|||
else if (this.numberOfGarrisonedSlots(ent) < gmin)
|
||||
gameState.ai.HQ.defenseManager.garrisonUnitsInside(gameState, ent, { "min": gmin, "type": GarrisonManager.TYPE_DECAY });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** TODO should add the units garrisoned inside garrisoned units */
|
||||
GarrisonManager.prototype.numberOfGarrisonedUnits = function(holder)
|
||||
{
|
||||
/** TODO should add the units garrisoned inside garrisoned units */
|
||||
numberOfGarrisonedUnits(holder)
|
||||
{
|
||||
if (!this.holders.has(holder.id()))
|
||||
return holder.garrisoned().length;
|
||||
|
||||
return holder.garrisoned().length + this.holders.get(holder.id()).list.length;
|
||||
};
|
||||
}
|
||||
|
||||
/** TODO should add the units garrisoned inside garrisoned units */
|
||||
GarrisonManager.prototype.numberOfGarrisonedSlots = function(holder)
|
||||
{
|
||||
/** TODO should add the units garrisoned inside garrisoned units */
|
||||
numberOfGarrisonedSlots(holder)
|
||||
{
|
||||
if (!this.holders.has(holder.id()))
|
||||
return holder.garrisonedSlots();
|
||||
|
||||
return holder.garrisonedSlots() + this.holders.get(holder.id()).list.length;
|
||||
};
|
||||
}
|
||||
|
||||
GarrisonManager.prototype.allowMelee = function(holder)
|
||||
{
|
||||
allowMelee(holder)
|
||||
{
|
||||
if (!this.holders.has(holder.id()))
|
||||
return undefined;
|
||||
|
||||
return this.holders.get(holder.id()).allowMelee;
|
||||
};
|
||||
}
|
||||
|
||||
/** This is just a pre-garrison state, while the entity walk to the garrison holder */
|
||||
GarrisonManager.prototype.garrison = function(gameState, ent, holder, type)
|
||||
{
|
||||
/** This is just a pre-garrison state, while the entity walk to the garrison holder */
|
||||
garrison(gameState, ent, holder, type)
|
||||
{
|
||||
if (this.numberOfGarrisonedSlots(holder) >= holder.garrisonMax() || !ent.canGarrison())
|
||||
return;
|
||||
|
||||
|
|
@ -259,27 +262,27 @@ GarrisonManager.prototype.garrison = function(gameState, ent, holder, type)
|
|||
ent.setMetadata(PlayerID, "garrisonHolder", holder.id());
|
||||
ent.setMetadata(PlayerID, "garrisonType", type);
|
||||
ent.garrison(holder);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
This is the end of the pre-garrison state, either because the entity is really garrisoned
|
||||
or because it has changed its order (i.e. because the garrisonHolder was destroyed)
|
||||
This function is for internal use inside garrisonManager. From outside, you should also update
|
||||
the holder and then using cancelGarrison should be the preferred solution
|
||||
*/
|
||||
GarrisonManager.prototype.leaveGarrison = function(ent)
|
||||
{
|
||||
leaveGarrison(ent)
|
||||
{
|
||||
ent.setMetadata(PlayerID, "subrole", undefined);
|
||||
if (ent.getMetadata(PlayerID, "plan") === -2)
|
||||
ent.setMetadata(PlayerID, "plan", -1);
|
||||
else
|
||||
ent.setMetadata(PlayerID, "plan", undefined);
|
||||
ent.setMetadata(PlayerID, "garrisonHolder", undefined);
|
||||
};
|
||||
}
|
||||
|
||||
/** Cancel a pre-garrison state */
|
||||
GarrisonManager.prototype.cancelGarrison = function(ent)
|
||||
{
|
||||
/** Cancel a pre-garrison state */
|
||||
cancelGarrison(ent)
|
||||
{
|
||||
ent.stopMoving();
|
||||
this.leaveGarrison(ent);
|
||||
const holderId = ent.getMetadata(PlayerID, "garrisonHolder");
|
||||
|
|
@ -289,10 +292,10 @@ GarrisonManager.prototype.cancelGarrison = function(ent)
|
|||
const index = list.indexOf(ent.id());
|
||||
if (index !== -1)
|
||||
list.splice(index, 1);
|
||||
};
|
||||
}
|
||||
|
||||
GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around)
|
||||
{
|
||||
keepGarrisoned(ent, holder, around)
|
||||
{
|
||||
switch (ent.getMetadata(PlayerID, "garrisonType"))
|
||||
{
|
||||
case GarrisonManager.TYPE_FORCE: // force the ungarrisoning
|
||||
|
|
@ -340,24 +343,24 @@ GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around)
|
|||
ent.setMetadata(PlayerID, "garrisonType", GarrisonManager.TYPE_PROTECTION);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Add this holder in the list managed by the garrisonManager */
|
||||
GarrisonManager.prototype.registerHolder = function(gameState, holder)
|
||||
{
|
||||
/** Add this holder in the list managed by the garrisonManager */
|
||||
registerHolder(gameState, holder)
|
||||
{
|
||||
if (this.holders.has(holder.id())) // already registered
|
||||
return;
|
||||
this.holders.set(holder.id(), { "list": [], "allowMelee": true });
|
||||
holder.setMetadata(PlayerID, "holderTimeUpdate", gameState.ai.elapsedTime);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Garrison units in decaying structures to stop their decay
|
||||
* do it only for structures useful for defense, except if we are expanding (justCaptured=true)
|
||||
* in which case we also do it for structures useful for unit trainings (TODO only Barracks are done)
|
||||
*/
|
||||
GarrisonManager.prototype.addDecayingStructure = function(gameState, entId, justCaptured)
|
||||
{
|
||||
addDecayingStructure(gameState, entId, justCaptured)
|
||||
{
|
||||
if (this.decayingStructures.has(entId))
|
||||
return true;
|
||||
const ent = gameState.getEntityById(entId);
|
||||
|
|
@ -368,22 +371,23 @@ GarrisonManager.prototype.addDecayingStructure = function(gameState, entId, just
|
|||
const gmin = Math.ceil((ent.territoryDecayRate() - ent.defaultRegenRate()) / ent.garrisonRegenRate());
|
||||
this.decayingStructures.set(entId, gmin);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
GarrisonManager.prototype.removeDecayingStructure = function(entId)
|
||||
{
|
||||
removeDecayingStructure(entId)
|
||||
{
|
||||
if (!this.decayingStructures.has(entId))
|
||||
return;
|
||||
this.decayingStructures.delete(entId);
|
||||
};
|
||||
}
|
||||
|
||||
GarrisonManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
return { "holders": this.holders, "decayingStructures": this.decayingStructures };
|
||||
};
|
||||
}
|
||||
|
||||
GarrisonManager.prototype.Deserialize = function(data)
|
||||
{
|
||||
Deserialize(data)
|
||||
{
|
||||
for (const key in data)
|
||||
this[key] = data[key];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,8 +39,10 @@ import { Worker } from "simulation/ai/petra/worker.js";
|
|||
* -picking new CC locations.
|
||||
*/
|
||||
|
||||
export function Headquarters(config, deserialized)
|
||||
export class Headquarters
|
||||
{
|
||||
constructor(config, deserialized)
|
||||
{
|
||||
this.Config = config;
|
||||
this.phasing = 0; // existing values: 0 means no, i > 0 means phasing towards phase i
|
||||
|
||||
|
|
@ -76,11 +78,11 @@ export function Headquarters(config, deserialized)
|
|||
|
||||
this.capturableTargets = new Map();
|
||||
this.capturableTargetsTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** More initialisation for stuff that needs the gameState */
|
||||
Headquarters.prototype.init = function(gameState, queues)
|
||||
{
|
||||
/** More initialisation for stuff that needs the gameState */
|
||||
init(gameState, queues)
|
||||
{
|
||||
this.territoryMap = createTerritoryMap(gameState);
|
||||
// create borderMap: flag cells on the border of the map
|
||||
// then this map will be completed with our frontier in updateTerritories
|
||||
|
|
@ -96,23 +98,23 @@ Headquarters.prototype.init = function(gameState, queues)
|
|||
this.currentPhase = gameState.currentPhase();
|
||||
this.decayingStructures = new Set();
|
||||
this.emergencyManager.init(gameState);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* initialization needed after deserialization (only called when deserialization)
|
||||
*/
|
||||
Headquarters.prototype.postinit = function(gameState)
|
||||
{
|
||||
postinit(gameState)
|
||||
{
|
||||
this.basesManager.postinit(gameState);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* returns the sea index linking regions 1 and region 2 (supposed to be different land region)
|
||||
* otherwise return undefined
|
||||
* for the moment, only the case land-sea-land is supported
|
||||
*/
|
||||
Headquarters.prototype.getSeaBetweenIndices = function(gameState, index1, index2)
|
||||
{
|
||||
getSeaBetweenIndices(gameState, index1, index2)
|
||||
{
|
||||
const path = gameState.ai.accessibility.getTrajectToIndex(index1, index2);
|
||||
if (path && path.length == 3 && gameState.ai.accessibility.regionType[path[1]] == "water")
|
||||
return path[1];
|
||||
|
|
@ -124,10 +126,10 @@ Headquarters.prototype.getSeaBetweenIndices = function(gameState, index1, index2
|
|||
aiWarn(" regionLinks end " + uneval(gameState.ai.accessibility.regionLinks[index2]));
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.checkEvents = function(gameState, events)
|
||||
{
|
||||
checkEvents(gameState, events)
|
||||
{
|
||||
this.buildManager.checkEvents(gameState, events);
|
||||
this.updateTerritories(gameState);
|
||||
|
||||
|
|
@ -297,10 +299,10 @@ Headquarters.prototype.checkEvents = function(gameState, events)
|
|||
}
|
||||
this.decayingStructures.delete(entId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.handleNewBase = function(gameState)
|
||||
{
|
||||
handleNewBase(gameState)
|
||||
{
|
||||
if (!this.firstBaseConfig)
|
||||
// This is our first base, let us configure our starting resources.
|
||||
configFirstBase(this, gameState);
|
||||
|
|
@ -311,11 +313,11 @@ Headquarters.prototype.handleNewBase = function(gameState)
|
|||
this.saveSpace = undefined;
|
||||
this.maxFields = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Ensure that all requirements are met when phasing up*/
|
||||
Headquarters.prototype.checkPhaseRequirements = function(gameState, queues)
|
||||
{
|
||||
/** Ensure that all requirements are met when phasing up*/
|
||||
checkPhaseRequirements(gameState, queues)
|
||||
{
|
||||
if (gameState.getNumberOfPhases() == this.currentPhase)
|
||||
return;
|
||||
|
||||
|
|
@ -386,16 +388,16 @@ Headquarters.prototype.checkPhaseRequirements = function(gameState, queues)
|
|||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Called by any "phase" research plan once it's started */
|
||||
Headquarters.prototype.OnPhaseUp = function(gameState, phase)
|
||||
{
|
||||
};
|
||||
/** Called by any "phase" research plan once it's started */
|
||||
OnPhaseUp(gameState, phase)
|
||||
{
|
||||
}
|
||||
|
||||
/** This code trains citizen workers, trying to keep close to a ratio of worker/soldiers */
|
||||
Headquarters.prototype.trainMoreWorkers = function(gameState, queues)
|
||||
{
|
||||
/** This code trains citizen workers, trying to keep close to a ratio of worker/soldiers */
|
||||
trainMoreWorkers(gameState, queues)
|
||||
{
|
||||
// default template
|
||||
const requirementsDef = [ ["costsResource", 1, "food"] ];
|
||||
const classesDef = ["Support+Worker"];
|
||||
|
|
@ -502,11 +504,11 @@ Headquarters.prototype.trainMoreWorkers = function(gameState, queues)
|
|||
queues.villager.addPlan(new TrainingPlan(gameState, templateDef, { "role": Worker.ROLE_WORKER, "base": 0, "support": true }, size, size));
|
||||
else if (template)
|
||||
queues.citizenSoldier.addPlan(new TrainingPlan(gameState, template, { "role": Worker.ROLE_WORKER, "base": 0 }, size, size));
|
||||
};
|
||||
}
|
||||
|
||||
/** picks the best template based on parameters and classes */
|
||||
Headquarters.prototype.findBestTrainableUnit = function(gameState, classes, requirements)
|
||||
{
|
||||
/** picks the best template based on parameters and classes */
|
||||
findBestTrainableUnit(gameState, classes, requirements)
|
||||
{
|
||||
let units;
|
||||
if (classes.indexOf("Hero") != -1)
|
||||
units = gameState.findTrainableUnits(classes, []);
|
||||
|
|
@ -585,43 +587,43 @@ Headquarters.prototype.findBestTrainableUnit = function(gameState, classes, requ
|
|||
return -aValue/aCost + bValue/bCost;
|
||||
});
|
||||
return units[0][0];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* returns an entity collection of workers through BaseManager.pickBuilders
|
||||
* TODO: when same accessIndex, sort by distance
|
||||
*/
|
||||
Headquarters.prototype.bulkPickWorkers = function(gameState, baseRef, number)
|
||||
{
|
||||
bulkPickWorkers(gameState, baseRef, number)
|
||||
{
|
||||
return this.basesManager.bulkPickWorkers(gameState, baseRef, number);
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.getTotalResourceLevel = function(gameState, resources, proximity)
|
||||
{
|
||||
getTotalResourceLevel(gameState, resources, proximity)
|
||||
{
|
||||
return this.basesManager.getTotalResourceLevel(gameState, resources, proximity);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns the current gather rate
|
||||
* This is not per-se exact, it performs a few adjustments ad-hoc to account for travel distance, stuffs like that.
|
||||
*/
|
||||
Headquarters.prototype.GetCurrentGatherRates = function(gameState)
|
||||
{
|
||||
GetCurrentGatherRates(gameState)
|
||||
{
|
||||
return this.basesManager.GetCurrentGatherRates(gameState);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns the wanted gather rate.
|
||||
*/
|
||||
Headquarters.prototype.GetWantedGatherRates = function(gameState)
|
||||
{
|
||||
GetWantedGatherRates(gameState)
|
||||
{
|
||||
if (!this.turnCache.wantedRates)
|
||||
this.turnCache.wantedRates = gameState.ai.queueManager.wantedGatherRates(gameState);
|
||||
|
||||
return this.turnCache.wantedRates;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Pick the resource which most needs another worker
|
||||
* How this works:
|
||||
* We get the rates we would want to have to be able to deal with our plans
|
||||
|
|
@ -629,8 +631,8 @@ Headquarters.prototype.GetWantedGatherRates = function(gameState)
|
|||
* We compare; we pick the one where the discrepancy is highest.
|
||||
* Need to balance long-term needs and possible short-term needs.
|
||||
*/
|
||||
Headquarters.prototype.pickMostNeededResources = function(gameState, allowedResources = [])
|
||||
{
|
||||
pickMostNeededResources(gameState, allowedResources = [])
|
||||
{
|
||||
const wantedRates = this.GetWantedGatherRates(gameState);
|
||||
const currentRates = this.GetCurrentGatherRates(gameState);
|
||||
if (!allowedResources.length)
|
||||
|
|
@ -659,14 +661,14 @@ Headquarters.prototype.pickMostNeededResources = function(gameState, allowedReso
|
|||
return a.current - a.wanted - b.current + b.wanted;
|
||||
});
|
||||
return needed;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns the best position to build a new Civil Center
|
||||
* Whose primary function would be to reach new resources of type "resource".
|
||||
*/
|
||||
Headquarters.prototype.findEconomicCCLocation = function(gameState, template, resource, proximity, fromStrategic)
|
||||
{
|
||||
findEconomicCCLocation(gameState, template, resource, proximity, fromStrategic)
|
||||
{
|
||||
// This builds a map. The procedure is fairly simple. It adds the resource maps
|
||||
// (which are dynamically updated and are made so that they will facilitate DP placement)
|
||||
// Then look for a good spot.
|
||||
|
|
@ -860,14 +862,14 @@ Headquarters.prototype.findEconomicCCLocation = function(gameState, template, re
|
|||
}
|
||||
|
||||
return [x, z];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns the best position to build a new Civil Center
|
||||
* Whose primary function would be to assure territorial continuity with our allies
|
||||
*/
|
||||
Headquarters.prototype.findStrategicCCLocation = function(gameState, template)
|
||||
{
|
||||
findStrategicCCLocation(gameState, template)
|
||||
{
|
||||
// This builds a map. The procedure is fairly simple.
|
||||
// We minimize the Sum((dist - 300)^2) where the sum is on the three nearest allied CC
|
||||
// with the constraints that all CC have dist > 200 and at least one have dist < 400
|
||||
|
|
@ -1003,17 +1005,17 @@ Headquarters.prototype.findStrategicCCLocation = function(gameState, template)
|
|||
}
|
||||
|
||||
return [x, z];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns the best position to build a new market: if the allies already have a market, build it as far as possible
|
||||
* from it, although not in our border to be able to defend it easily. If no allied market, our second market will
|
||||
* follow the same logic.
|
||||
* To do so, we suppose that the gain/distance is an increasing function of distance and look for the max distance
|
||||
* for performance reasons.
|
||||
*/
|
||||
Headquarters.prototype.findMarketLocation = function(gameState, template)
|
||||
{
|
||||
findMarketLocation(gameState, template)
|
||||
{
|
||||
let markets = gameState.updatingCollection("diplo-ExclusiveAllyMarkets", filters.byClass("Trade"),
|
||||
gameState.getExclusiveAllyEntities()).toEntityArray();
|
||||
if (!markets.length)
|
||||
|
|
@ -1127,14 +1129,14 @@ Headquarters.prototype.findMarketLocation = function(gameState, template)
|
|||
const x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize;
|
||||
const z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize;
|
||||
return [x, z, idx, expectedGain];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns the best position to build defensive buildings (fortress and towers)
|
||||
* Whose primary function is to defend our borders
|
||||
*/
|
||||
Headquarters.prototype.findDefensiveLocation = function(gameState, template)
|
||||
{
|
||||
findDefensiveLocation(gameState, template)
|
||||
{
|
||||
// We take the point in our territory which is the nearest to any enemy cc
|
||||
// but requiring a minimal distance with our other defensive structures
|
||||
// and not in range of any enemy defensive structure to avoid building under fire.
|
||||
|
|
@ -1269,10 +1271,10 @@ Headquarters.prototype.findDefensiveLocation = function(gameState, template)
|
|||
const x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize;
|
||||
const z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize;
|
||||
return [x, z, this.baseAtIndex(bestJdx)];
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.buildTemple = function(gameState, queues)
|
||||
{
|
||||
buildTemple(gameState, queues)
|
||||
{
|
||||
// at least one market (which have the same queue) should be build before any temple
|
||||
if (queues.economicBuilding.hasQueuedUnits() ||
|
||||
gameState.getOwnEntitiesByClass("Temple", true).hasEntities() ||
|
||||
|
|
@ -1288,10 +1290,10 @@ Headquarters.prototype.buildTemple = function(gameState, queues)
|
|||
else if (!this.canBuild(gameState, templateName))
|
||||
return;
|
||||
queues.economicBuilding.addPlan(new ConstructionPlan(gameState, templateName));
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.buildMarket = function(gameState, queues)
|
||||
{
|
||||
buildMarket(gameState, queues)
|
||||
{
|
||||
if (gameState.getOwnEntitiesByClass("Market", true).hasEntities() ||
|
||||
!this.canBuild(gameState, "structures/{civ}/market"))
|
||||
return;
|
||||
|
|
@ -1323,11 +1325,11 @@ Headquarters.prototype.buildMarket = function(gameState, queues)
|
|||
const plan = new ConstructionPlan(gameState, "structures/{civ}/market");
|
||||
plan.queueToReset = "economicBuilding";
|
||||
queues.economicBuilding.addPlan(plan);
|
||||
};
|
||||
}
|
||||
|
||||
/** Build a farmstead */
|
||||
Headquarters.prototype.buildFarmstead = function(gameState, queues)
|
||||
{
|
||||
/** Build a farmstead */
|
||||
buildFarmstead(gameState, queues)
|
||||
{
|
||||
// Only build one farmstead for the time being ("DropsiteFood" does not refer to CCs)
|
||||
if (gameState.getOwnEntitiesByClass("Farmstead", true).hasEntities())
|
||||
return;
|
||||
|
|
@ -1342,14 +1344,14 @@ Headquarters.prototype.buildFarmstead = function(gameState, queues)
|
|||
return;
|
||||
|
||||
queues.economicBuilding.addPlan(new ConstructionPlan(gameState, "structures/{civ}/farmstead"));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Try to build a wonder when required
|
||||
* force = true when called from the victoryManager in case of Wonder victory condition.
|
||||
*/
|
||||
Headquarters.prototype.buildWonder = function(gameState, queues, force = false)
|
||||
{
|
||||
buildWonder(gameState, queues, force = false)
|
||||
{
|
||||
if (queues.wonder && queues.wonder.hasQueuedUnits() ||
|
||||
gameState.getOwnEntitiesByClass("Wonder", true).hasEntities() ||
|
||||
!this.canBuild(gameState, "structures/{civ}/wonder"))
|
||||
|
|
@ -1375,11 +1377,11 @@ Headquarters.prototype.buildWonder = function(gameState, queues, force = false)
|
|||
}
|
||||
|
||||
queues.wonder.addPlan(new ConstructionPlan(gameState, "structures/{civ}/wonder"));
|
||||
};
|
||||
}
|
||||
|
||||
/** Build a corral, and train animals there */
|
||||
Headquarters.prototype.manageCorral = function(gameState, queues)
|
||||
{
|
||||
/** Build a corral, and train animals there */
|
||||
manageCorral(gameState, queues)
|
||||
{
|
||||
if (queues.corral.hasQueuedUnits())
|
||||
return;
|
||||
|
||||
|
|
@ -1419,14 +1421,14 @@ Headquarters.prototype.manageCorral = function(gameState, queues)
|
|||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* build more houses if needed.
|
||||
* kinda ugly, lots of special cases to both build enough houses but not tooo many…
|
||||
*/
|
||||
Headquarters.prototype.buildMoreHouses = function(gameState, queues)
|
||||
{
|
||||
buildMoreHouses(gameState, queues)
|
||||
{
|
||||
let houseTemplateString = "structures/{civ}/apartment";
|
||||
if (!gameState.isTemplateAvailable(gameState.applyCiv(houseTemplateString)) ||
|
||||
!this.canBuild(gameState, houseTemplateString))
|
||||
|
|
@ -1516,17 +1518,17 @@ Headquarters.prototype.buildMoreHouses = function(gameState, queues)
|
|||
|
||||
if (priority && priority != gameState.ai.queueManager.getPriority("house"))
|
||||
gameState.ai.queueManager.changePriority("house", priority);
|
||||
};
|
||||
}
|
||||
|
||||
/** Checks the status of the territory expansion. If no new economic bases created, build some strategic ones. */
|
||||
Headquarters.prototype.checkBaseExpansion = function(gameState, queues)
|
||||
{
|
||||
/** Checks the status of the territory expansion. If no new economic bases created, build some strategic ones. */
|
||||
checkBaseExpansion(gameState, queues)
|
||||
{
|
||||
if (queues.civilCentre.hasQueuedUnits())
|
||||
return;
|
||||
// First build one cc if all have been destroyed
|
||||
if (!this.hasPotentialBase())
|
||||
{
|
||||
buildFirstBase(this, gameState);
|
||||
this.buildFirstBase(gameState);
|
||||
return;
|
||||
}
|
||||
// Then expand if we have not enough room available for buildings
|
||||
|
|
@ -1553,10 +1555,10 @@ Headquarters.prototype.checkBaseExpansion = function(gameState, queues)
|
|||
}
|
||||
this.buildNewBase(gameState, queues);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.buildNewBase = function(gameState, queues, resource)
|
||||
{
|
||||
buildNewBase(gameState, queues, resource)
|
||||
{
|
||||
if (this.hasPotentialBase() && this.currentPhase == 1 && !gameState.isResearching(gameState.getPhaseName(2)))
|
||||
return false;
|
||||
if (gameState.getOwnFoundations().filter(filters.byClass("CivCentre")).hasEntities() || queues.civilCentre.hasQueuedUnits())
|
||||
|
|
@ -1586,11 +1588,11 @@ Headquarters.prototype.buildNewBase = function(gameState, queues, resource)
|
|||
aiWarn("new base " + gameState.applyCiv(template) + " planned with resource " + resource);
|
||||
queues.civilCentre.addPlan(new ConstructionPlan(gameState, template, { "base": -1, "resource": resource }));
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** Deals with building fortresses and towers along our border with enemies. */
|
||||
Headquarters.prototype.buildDefenses = function(gameState, queues)
|
||||
{
|
||||
/** Deals with building fortresses and towers along our border with enemies. */
|
||||
buildDefenses(gameState, queues)
|
||||
{
|
||||
if (this.saveResources && !this.canBarter || queues.defenseBuilding.hasQueuedUnits())
|
||||
return;
|
||||
|
||||
|
|
@ -1646,10 +1648,10 @@ Headquarters.prototype.buildDefenses = function(gameState, queues)
|
|||
plan.queueToReset = "defenseBuilding";
|
||||
queues.defenseBuilding.addPlan(plan);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.buildForge = function(gameState, queues)
|
||||
{
|
||||
buildForge(gameState, queues)
|
||||
{
|
||||
if (this.getAccountedPopulation(gameState) < this.Config.Military.popForForge ||
|
||||
queues.militaryBuilding.hasQueuedUnits() || gameState.getOwnEntitiesByClass("Forge", true).length)
|
||||
return;
|
||||
|
|
@ -1659,14 +1661,14 @@ Headquarters.prototype.buildForge = function(gameState, queues)
|
|||
|
||||
if (this.canBuild(gameState, "structures/{civ}/forge"))
|
||||
queues.militaryBuilding.addPlan(new ConstructionPlan(gameState, "structures/{civ}/forge"));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Deals with constructing military buildings (e.g. barracks, stable).
|
||||
* They are mostly defined by Config.js. This is unreliable since changes could be done easily.
|
||||
*/
|
||||
Headquarters.prototype.constructTrainingBuildings = function(gameState, queues)
|
||||
{
|
||||
constructTrainingBuildings(gameState, queues)
|
||||
{
|
||||
if (this.saveResources && !this.canBarter || queues.militaryBuilding.hasQueuedUnits())
|
||||
return;
|
||||
|
||||
|
|
@ -1775,13 +1777,13 @@ Headquarters.prototype.constructTrainingBuildings = function(gameState, queues)
|
|||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Find base nearest to ennemies for military buildings.
|
||||
*/
|
||||
Headquarters.prototype.findBestBaseForMilitary = function(gameState)
|
||||
{
|
||||
findBestBaseForMilitary(gameState)
|
||||
{
|
||||
const ccEnts = gameState.updatingGlobalCollection("allCCs", filters.byClass("CivCentre")).toEntityArray();
|
||||
let bestBase;
|
||||
let enemyFound = false;
|
||||
|
|
@ -1810,14 +1812,14 @@ Headquarters.prototype.findBestBaseForMilitary = function(gameState)
|
|||
}
|
||||
}
|
||||
return bestBase;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* train with highest priority ranged infantry in the nearest civil center from a given set of positions
|
||||
* and garrison them there for defense
|
||||
*/
|
||||
Headquarters.prototype.trainEmergencyUnits = function(gameState, positions)
|
||||
{
|
||||
trainEmergencyUnits(gameState, positions)
|
||||
{
|
||||
if (gameState.ai.queues.emergency.hasQueuedUnits())
|
||||
return false;
|
||||
|
||||
|
|
@ -1919,10 +1921,10 @@ Headquarters.prototype.trainEmergencyUnits = function(gameState, positions)
|
|||
metadata.garrisonType = GarrisonManager.TYPE_PROTECTION;
|
||||
gameState.ai.queues.emergency.addPlan(new TrainingPlan(gameState, templateFound[0], metadata, 1, 1));
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.canBuild = function(gameState, structure)
|
||||
{
|
||||
canBuild(gameState, structure)
|
||||
{
|
||||
const type = gameState.applyCiv(structure);
|
||||
if (this.buildManager.isUnbuildable(gameState, type))
|
||||
return false;
|
||||
|
|
@ -1973,10 +1975,10 @@ Headquarters.prototype.canBuild = function(gameState, structure)
|
|||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.updateTerritories = function(gameState)
|
||||
{
|
||||
updateTerritories(gameState)
|
||||
{
|
||||
const around = [ [-0.7, 0.7], [0, 1], [0.7, 0.7], [1, 0], [0.7, -0.7], [0, -1], [-0.7, -0.7], [-1, 0] ];
|
||||
const alliedVictory = gameState.getAlliedVictory();
|
||||
const passabilityMap = gameState.getPassabilityMap();
|
||||
|
|
@ -2045,57 +2047,57 @@ Headquarters.prototype.updateTerritories = function(gameState)
|
|||
const cellArea = this.territoryMap.cellSize * this.territoryMap.cellSize;
|
||||
if (expansion * cellArea > 960)
|
||||
this.tradeManager.routeProspection = true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* returns the base corresponding to baseID
|
||||
*/
|
||||
Headquarters.prototype.getBaseByID = function(baseID)
|
||||
{
|
||||
getBaseByID(baseID)
|
||||
{
|
||||
return this.basesManager.getBaseByID(baseID);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* returns the number of bases with a cc
|
||||
* ActiveBases includes only those with a built cc
|
||||
* PotentialBases includes also those with a cc in construction
|
||||
*/
|
||||
Headquarters.prototype.numActiveBases = function()
|
||||
{
|
||||
numActiveBases()
|
||||
{
|
||||
return this.basesManager.numActiveBases();
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.hasActiveBase = function()
|
||||
{
|
||||
hasActiveBase()
|
||||
{
|
||||
return this.basesManager.hasActiveBase();
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.numPotentialBases = function()
|
||||
{
|
||||
numPotentialBases()
|
||||
{
|
||||
return this.basesManager.numPotentialBases();
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.hasPotentialBase = function()
|
||||
{
|
||||
hasPotentialBase()
|
||||
{
|
||||
return this.basesManager.hasPotentialBase();
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.isDangerousLocation = function(gameState, pos, radius)
|
||||
{
|
||||
isDangerousLocation(gameState, pos, radius)
|
||||
{
|
||||
return this.isNearInvadingArmy(pos) || this.isUnderEnemyFire(gameState, pos, radius);
|
||||
};
|
||||
}
|
||||
|
||||
/** Check that the chosen position is not too near from an invading army */
|
||||
Headquarters.prototype.isNearInvadingArmy = function(pos)
|
||||
{
|
||||
/** Check that the chosen position is not too near from an invading army */
|
||||
isNearInvadingArmy(pos)
|
||||
{
|
||||
for (const army of this.defenseManager.armies)
|
||||
if (army.foePosition && SquareVectorDistance(army.foePosition, pos) < 12000)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.isUnderEnemyFire = function(gameState, pos, radius = 0)
|
||||
{
|
||||
isUnderEnemyFire(gameState, pos, radius = 0)
|
||||
{
|
||||
if (!this.turnCache.firingStructures)
|
||||
{
|
||||
this.turnCache.firingStructures = gameState.updatingCollection("diplo-FiringStructures",
|
||||
|
|
@ -2108,11 +2110,11 @@ Headquarters.prototype.isUnderEnemyFire = function(gameState, pos, radius = 0)
|
|||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/** Compute the capture strength of all units attacking a capturable target */
|
||||
Headquarters.prototype.updateCaptureStrength = function(gameState)
|
||||
{
|
||||
/** Compute the capture strength of all units attacking a capturable target */
|
||||
updateCaptureStrength(gameState)
|
||||
{
|
||||
this.capturableTargets.clear();
|
||||
for (const ent of gameState.getOwnUnits().values())
|
||||
{
|
||||
|
|
@ -2159,25 +2161,25 @@ Headquarters.prototype.updateCaptureStrength = function(gameState)
|
|||
}
|
||||
|
||||
this.capturableTargetsTime = gameState.ai.elapsedTime;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if a structure in blinking territory should/can be defended (currently if it has some attacking armies around)
|
||||
*/
|
||||
Headquarters.prototype.isDefendable = function(ent)
|
||||
{
|
||||
isDefendable(ent)
|
||||
{
|
||||
if (!this.turnCache.numAround)
|
||||
this.turnCache.numAround = {};
|
||||
if (this.turnCache.numAround[ent.id()] === undefined)
|
||||
this.turnCache.numAround[ent.id()] = this.attackManager.numAttackingUnitsAround(ent.position(), 130);
|
||||
return +this.turnCache.numAround[ent.id()] > 8;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Get the number of population already accounted for
|
||||
*/
|
||||
Headquarters.prototype.getAccountedPopulation = function(gameState)
|
||||
{
|
||||
getAccountedPopulation(gameState)
|
||||
{
|
||||
if (this.turnCache.accountedPopulation == undefined)
|
||||
{
|
||||
let pop = gameState.getPopulation();
|
||||
|
|
@ -2195,13 +2197,13 @@ Headquarters.prototype.getAccountedPopulation = function(gameState)
|
|||
this.turnCache.accountedPopulation = pop;
|
||||
}
|
||||
return this.turnCache.accountedPopulation;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Get the number of workers already accounted for
|
||||
*/
|
||||
Headquarters.prototype.getAccountedWorkers = function(gameState)
|
||||
{
|
||||
getAccountedWorkers(gameState)
|
||||
{
|
||||
if (this.turnCache.accountedWorkers == undefined)
|
||||
{
|
||||
let workers = gameState.getOwnEntitiesByRole(Worker.ROLE_WORKER, true).length;
|
||||
|
|
@ -2217,28 +2219,28 @@ Headquarters.prototype.getAccountedWorkers = function(gameState)
|
|||
this.turnCache.accountedWorkers = workers;
|
||||
}
|
||||
return this.turnCache.accountedWorkers;
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.baseManagers = function()
|
||||
{
|
||||
baseManagers()
|
||||
{
|
||||
return this.basesManager.baseManagers;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @param {number} territoryIndex - The index to get the map for.
|
||||
* @return {number} - The ID of the base at the given territory index.
|
||||
*/
|
||||
Headquarters.prototype.baseAtIndex = function(territoryIndex)
|
||||
{
|
||||
baseAtIndex(territoryIndex)
|
||||
{
|
||||
return this.basesManager.baseAtIndex(territoryIndex);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Some functions are run every turn
|
||||
* Others once in a while
|
||||
*/
|
||||
Headquarters.prototype.update = function(gameState, queues, events)
|
||||
{
|
||||
update(gameState, queues, events)
|
||||
{
|
||||
Engine.ProfileStart("Headquarters update");
|
||||
this.emergencyManager.update(gameState);
|
||||
this.turnCache = {};
|
||||
|
|
@ -2345,10 +2347,10 @@ Headquarters.prototype.update = function(gameState, queues, events)
|
|||
this.updateCaptureStrength(gameState);
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
const properties = {
|
||||
"phasing": this.phasing,
|
||||
"lastFailedGather": this.lastFailedGather,
|
||||
|
|
@ -2407,10 +2409,10 @@ Headquarters.prototype.Serialize = function()
|
|||
"victoryManager": this.victoryManager.Serialize(),
|
||||
"emergencyManager": this.emergencyManager.Serialize(),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Headquarters.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
Deserialize(gameState, data)
|
||||
{
|
||||
for (const key in data.properties)
|
||||
this[key] = data.properties[key];
|
||||
|
||||
|
|
@ -2452,4 +2454,5 @@ Headquarters.prototype.Deserialize = function(gameState, data)
|
|||
|
||||
this.emergencyManager = new EmergencyManager(this.Config);
|
||||
this.emergencyManager.Deserialize(data.emergencyManager);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,33 +16,35 @@ import { Worker } from "simulation/ai/petra/worker.js";
|
|||
* -Scouting, ultimately.
|
||||
* Also deals with handling docks, making sure we have access and stuffs like that.
|
||||
*/
|
||||
export function NavalManager(Config)
|
||||
export class NavalManager
|
||||
{
|
||||
this.Config = Config;
|
||||
|
||||
// ship subCollections. Also exist for land zones, idem, not caring.
|
||||
this.seaShips = [];
|
||||
this.seaTransportShips = [];
|
||||
this.seaWarShips = [];
|
||||
this.seaFishShips = [];
|
||||
seaShips = [];
|
||||
seaTransportShips = [];
|
||||
seaWarShips = [];
|
||||
seaFishShips = [];
|
||||
|
||||
// wanted NB per zone.
|
||||
this.wantedTransportShips = [];
|
||||
this.wantedWarShips = [];
|
||||
this.wantedFishShips = [];
|
||||
wantedTransportShips = [];
|
||||
wantedWarShips = [];
|
||||
wantedFishShips = [];
|
||||
// needed NB per zone.
|
||||
this.neededTransportShips = [];
|
||||
this.neededWarShips = [];
|
||||
neededTransportShips = [];
|
||||
neededWarShips = [];
|
||||
|
||||
this.transportPlans = [];
|
||||
transportPlans = [];
|
||||
|
||||
// shore-line regions where we can load and unload units
|
||||
this.landingZones = {};
|
||||
}
|
||||
landingZones = {};
|
||||
|
||||
/** More initialisation for stuff that needs the gameState */
|
||||
NavalManager.prototype.init = function(gameState, deserializing)
|
||||
{
|
||||
constructor(Config)
|
||||
{
|
||||
this.Config = Config;
|
||||
}
|
||||
|
||||
/** More initialisation for stuff that needs the gameState */
|
||||
init(gameState, deserializing)
|
||||
{
|
||||
// docks
|
||||
this.docks = gameState.getOwnStructures().filter(filters.byClasses(["Dock", "Shipyard"]));
|
||||
this.docks.registerUpdates();
|
||||
|
|
@ -166,25 +168,25 @@ NavalManager.prototype.init = function(gameState, deserializing)
|
|||
setSeaAccess(gameState, ship);
|
||||
for (const dock of this.docks.values())
|
||||
setSeaAccess(gameState, dock);
|
||||
};
|
||||
}
|
||||
|
||||
NavalManager.prototype.updateFishingBoats = function(sea, num)
|
||||
{
|
||||
updateFishingBoats(sea, num)
|
||||
{
|
||||
if (this.wantedFishShips[sea])
|
||||
this.wantedFishShips[sea] = num;
|
||||
};
|
||||
}
|
||||
|
||||
NavalManager.prototype.resetFishingBoats = function(gameState, sea)
|
||||
{
|
||||
resetFishingBoats(gameState, sea)
|
||||
{
|
||||
if (sea !== undefined)
|
||||
this.wantedFishShips[sea] = 0;
|
||||
else
|
||||
this.wantedFishShips.fill(0);
|
||||
};
|
||||
}
|
||||
|
||||
/** Get the sea, cache it if not yet done and check if in opensea */
|
||||
NavalManager.prototype.getFishSea = function(gameState, fish)
|
||||
{
|
||||
/** Get the sea, cache it if not yet done and check if in opensea */
|
||||
getFishSea(gameState, fish)
|
||||
{
|
||||
let sea = fish.getMetadata(PlayerID, "sea");
|
||||
if (sea)
|
||||
return sea;
|
||||
|
|
@ -218,11 +220,11 @@ NavalManager.prototype.getFishSea = function(gameState, fish)
|
|||
}))
|
||||
fish.setMetadata(PlayerID, "opensea", true);
|
||||
return sea;
|
||||
};
|
||||
}
|
||||
|
||||
/** check if we can safely fish at the fish position */
|
||||
NavalManager.prototype.canFishSafely = function(gameState, fish)
|
||||
{
|
||||
/** check if we can safely fish at the fish position */
|
||||
canFishSafely(gameState, fish)
|
||||
{
|
||||
if (fish.getMetadata(PlayerID, "opensea"))
|
||||
return true;
|
||||
const ntry = 2;
|
||||
|
|
@ -245,11 +247,11 @@ NavalManager.prototype.canFishSafely = function(gameState, fish)
|
|||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** get the list of seas (or lands) around this region not connected by a dock */
|
||||
NavalManager.prototype.getUnconnectedSeas = function(gameState, region)
|
||||
{
|
||||
/** get the list of seas (or lands) around this region not connected by a dock */
|
||||
getUnconnectedSeas(gameState, region)
|
||||
{
|
||||
const seas = gameState.ai.accessibility.regionLinks[region].slice();
|
||||
this.docks.forEach(dock =>
|
||||
{
|
||||
|
|
@ -260,10 +262,10 @@ NavalManager.prototype.getUnconnectedSeas = function(gameState, region)
|
|||
seas.splice(i, 1);
|
||||
});
|
||||
return seas;
|
||||
};
|
||||
}
|
||||
|
||||
NavalManager.prototype.checkEvents = function(gameState, queues, events)
|
||||
{
|
||||
checkEvents(gameState, queues, events)
|
||||
{
|
||||
for (const evt of events.Create)
|
||||
{
|
||||
if (!evt.entity)
|
||||
|
|
@ -342,29 +344,29 @@ NavalManager.prototype.checkEvents = function(gameState, queues, events)
|
|||
if (ent && ent.hasClasses(["Dock", "Shipyard"]))
|
||||
setSeaAccess(gameState, ent);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
NavalManager.prototype.getPlan = function(ID)
|
||||
{
|
||||
getPlan(ID)
|
||||
{
|
||||
for (const plan of this.transportPlans)
|
||||
if (plan.ID === ID)
|
||||
return plan;
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
NavalManager.prototype.addPlan = function(plan)
|
||||
{
|
||||
addPlan(plan)
|
||||
{
|
||||
this.transportPlans.push(plan);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* complete already existing plan or create a new one for this requirement
|
||||
* (many units can then call this separately and end up in the same plan)
|
||||
* TODO check garrison classes
|
||||
*/
|
||||
NavalManager.prototype.requireTransport = function(gameState, ent, startIndex, endIndex, endPos)
|
||||
{
|
||||
requireTransport(gameState, ent, startIndex, endIndex, endPos)
|
||||
{
|
||||
if (!ent.canGarrison())
|
||||
return false;
|
||||
|
||||
|
|
@ -409,11 +411,11 @@ NavalManager.prototype.requireTransport = function(gameState, ent, startIndex, e
|
|||
plan.init(gameState);
|
||||
this.transportPlans.push(plan);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** split a transport plan in two, moving all entities not yet affected to a ship in the new plan */
|
||||
NavalManager.prototype.splitTransport = function(gameState, plan)
|
||||
{
|
||||
/** split a transport plan in two, moving all entities not yet affected to a ship in the new plan */
|
||||
splitTransport(gameState, plan)
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
aiWarn(">>>> split of transport plan started <<<<");
|
||||
const newplan = new TransportPlan(gameState, [], plan.startIndex, plan.endIndex, plan.endPos);
|
||||
|
|
@ -436,14 +438,14 @@ NavalManager.prototype.splitTransport = function(gameState, plan)
|
|||
if (newplan.units.length)
|
||||
this.transportPlans.push(newplan);
|
||||
return newplan.units.length != 0;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* create a transport from a garrisoned ship to a land location
|
||||
* needed at start game when starting with a garrisoned ship
|
||||
*/
|
||||
NavalManager.prototype.createTransportIfNeeded = function(gameState, fromPos, toPos, toAccess)
|
||||
{
|
||||
createTransportIfNeeded(gameState, fromPos, toPos, toAccess)
|
||||
{
|
||||
const fromAccess = gameState.ai.accessibility.getAccessValue(fromPos);
|
||||
if (fromAccess !== 1)
|
||||
return;
|
||||
|
|
@ -466,20 +468,20 @@ NavalManager.prototype.createTransportIfNeeded = function(gameState, fromPos, to
|
|||
plan.init(gameState);
|
||||
this.transportPlans.push(plan);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// set minimal number of needed ships when a new event (new base or new attack plan)
|
||||
NavalManager.prototype.setMinimalTransportShips = function(gameState, sea, number)
|
||||
{
|
||||
// set minimal number of needed ships when a new event (new base or new attack plan)
|
||||
setMinimalTransportShips(gameState, sea, number)
|
||||
{
|
||||
if (!sea)
|
||||
return;
|
||||
if (this.wantedTransportShips[sea] < number)
|
||||
this.wantedTransportShips[sea] = number;
|
||||
};
|
||||
}
|
||||
|
||||
// bumps up the number of ships we want if we need more.
|
||||
NavalManager.prototype.checkLevels = function(gameState, queues)
|
||||
{
|
||||
// bumps up the number of ships we want if we need more.
|
||||
checkLevels(gameState, queues)
|
||||
{
|
||||
if (queues.ships.hasQueuedUnits())
|
||||
return;
|
||||
|
||||
|
|
@ -505,10 +507,10 @@ NavalManager.prototype.checkLevels = function(gameState, queues)
|
|||
for (let sea = 0; sea < this.neededTransportShips.length; sea++)
|
||||
if (this.neededTransportShips[sea] > 2)
|
||||
++this.wantedTransportShips[sea];
|
||||
};
|
||||
}
|
||||
|
||||
NavalManager.prototype.maintainFleet = function(gameState, queues)
|
||||
{
|
||||
maintainFleet(gameState, queues)
|
||||
{
|
||||
if (queues.ships.hasQueuedUnits())
|
||||
return;
|
||||
if (!this.docks.filter(filters.isBuilt()).hasEntities())
|
||||
|
|
@ -543,33 +545,33 @@ NavalManager.prototype.maintainFleet = function(gameState, queues)
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** assigns free ships to plans that need some */
|
||||
NavalManager.prototype.assignShipsToPlans = function(gameState)
|
||||
{
|
||||
/** assigns free ships to plans that need some */
|
||||
assignShipsToPlans(gameState)
|
||||
{
|
||||
for (const plan of this.transportPlans)
|
||||
if (plan.needTransportShips)
|
||||
plan.assignShip(gameState);
|
||||
};
|
||||
}
|
||||
|
||||
/** Return true if this ship is likeky (un)garrisoning units */
|
||||
NavalManager.prototype.isShipBoarding = function(ship)
|
||||
{
|
||||
/** Return true if this ship is likeky (un)garrisoning units */
|
||||
isShipBoarding(ship)
|
||||
{
|
||||
if (!ship.position())
|
||||
return false;
|
||||
const plan = this.getPlan(ship.getMetadata(PlayerID, "transporter"));
|
||||
if (!plan || !plan.boardingPos[ship.id()])
|
||||
return false;
|
||||
return SquareVectorDistance(plan.boardingPos[ship.id()], ship.position()) < plan.boardingRange;
|
||||
};
|
||||
}
|
||||
|
||||
/** let blocking ships move apart from active ships (waiting for a better pathfinder)
|
||||
/** let blocking ships move apart from active ships (waiting for a better pathfinder)
|
||||
* TODO Ships entity collections are currently in two parts as the trader ships are dealt with
|
||||
* in the tradeManager. That should be modified to avoid dupplicating all the code here.
|
||||
*/
|
||||
NavalManager.prototype.moveApart = function(gameState)
|
||||
{
|
||||
moveApart(gameState)
|
||||
{
|
||||
const blockedShips = [];
|
||||
const blockedIds = [];
|
||||
|
||||
|
|
@ -730,10 +732,10 @@ NavalManager.prototype.moveApart = function(gameState)
|
|||
blockingShip.moveToRange(shipPosition[0], shipPosition[1], 30, 35);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
NavalManager.prototype.buildNavalStructures = function(gameState, queues)
|
||||
{
|
||||
buildNavalStructures(gameState, queues)
|
||||
{
|
||||
if (!gameState.ai.HQ.navalMap || !gameState.ai.HQ.hasPotentialBase())
|
||||
return;
|
||||
|
||||
|
|
@ -793,11 +795,11 @@ NavalManager.prototype.buildNavalStructures = function(gameState, queues)
|
|||
const sea = this.docks.toEntityArray()[0].getMetadata(PlayerID, "sea");
|
||||
queues.militaryBuilding.addPlan(
|
||||
new ConstructionPlan(gameState, template, { "land": wantedLand, "sea": sea }));
|
||||
};
|
||||
}
|
||||
|
||||
/** goal can be either attack (choose ship with best arrowCount) or transport (choose ship with best capacity) */
|
||||
NavalManager.prototype.getBestShip = function(gameState, sea, goal)
|
||||
{
|
||||
/** goal can be either attack (choose ship with best arrowCount) or transport (choose ship with best capacity) */
|
||||
getBestShip(gameState, sea, goal)
|
||||
{
|
||||
const civ = gameState.getPlayerCiv();
|
||||
const trainableShips = [];
|
||||
gameState.getOwnTrainingFacilities().filter(filters.byMetadata(PlayerID, "sea", sea)).forEach(
|
||||
|
|
@ -853,10 +855,10 @@ NavalManager.prototype.getBestShip = function(gameState, sea, goal)
|
|||
bestShip = trainable;
|
||||
}
|
||||
return bestShip;
|
||||
};
|
||||
}
|
||||
|
||||
NavalManager.prototype.update = function(gameState, queues, events)
|
||||
{
|
||||
update(gameState, queues, events)
|
||||
{
|
||||
Engine.ProfileStart("Naval Manager update");
|
||||
|
||||
// close previous transport plans if finished
|
||||
|
|
@ -884,10 +886,10 @@ NavalManager.prototype.update = function(gameState, queues, events)
|
|||
this.moveApart(gameState);
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
}
|
||||
|
||||
NavalManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
const properties = {
|
||||
"wantedTransportShips": this.wantedTransportShips,
|
||||
"wantedWarShips": this.wantedWarShips,
|
||||
|
|
@ -902,10 +904,10 @@ NavalManager.prototype.Serialize = function()
|
|||
transports[plan] = this.transportPlans[plan].Serialize();
|
||||
|
||||
return { "properties": properties, "transports": transports };
|
||||
};
|
||||
}
|
||||
|
||||
NavalManager.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
Deserialize(gameState, data)
|
||||
{
|
||||
for (const key in data.properties)
|
||||
this[key] = data.properties[key];
|
||||
|
||||
|
|
@ -918,4 +920,5 @@ NavalManager.prototype.Deserialize = function(gameState, data)
|
|||
plan.init(gameState);
|
||||
this.transportPlans.push(plan);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,20 +7,19 @@ import { TrainingPlan } from "simulation/ai/petra/queueplanTraining.js";
|
|||
/**
|
||||
* Holds a list of wanted plans to train or construct
|
||||
*/
|
||||
export function Queue()
|
||||
export class Queue
|
||||
{
|
||||
this.plans = [];
|
||||
this.paused = false;
|
||||
this.switched = 0;
|
||||
}
|
||||
plans = [];
|
||||
paused = false;
|
||||
switched = 0;
|
||||
|
||||
Queue.prototype.empty = function()
|
||||
{
|
||||
empty()
|
||||
{
|
||||
this.plans = [];
|
||||
};
|
||||
}
|
||||
|
||||
Queue.prototype.addPlan = function(newPlan)
|
||||
{
|
||||
addPlan(newPlan)
|
||||
{
|
||||
if (!newPlan)
|
||||
return;
|
||||
for (const plan of this.plans)
|
||||
|
|
@ -34,10 +33,10 @@ Queue.prototype.addPlan = function(newPlan)
|
|||
return;
|
||||
}
|
||||
this.plans.push(newPlan);
|
||||
};
|
||||
}
|
||||
|
||||
Queue.prototype.check= function(gameState)
|
||||
{
|
||||
check(gameState)
|
||||
{
|
||||
while (this.plans.length > 0)
|
||||
{
|
||||
if (!this.plans[0].isInvalid(gameState))
|
||||
|
|
@ -46,31 +45,31 @@ Queue.prototype.check= function(gameState)
|
|||
if (plan.queueToReset)
|
||||
gameState.ai.queueManager.changePriority(plan.queueToReset, gameState.ai.Config.priorities[plan.queueToReset]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Queue.prototype.getNext = function()
|
||||
{
|
||||
getNext()
|
||||
{
|
||||
if (this.plans.length > 0)
|
||||
return this.plans[0];
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
Queue.prototype.startNext = function(gameState)
|
||||
{
|
||||
startNext(gameState)
|
||||
{
|
||||
if (this.plans.length > 0)
|
||||
{
|
||||
this.plans.shift().start(gameState);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* returns the maximal account we'll accept for this queue.
|
||||
* Currently all the cost of the first element and fraction of that of the second
|
||||
*/
|
||||
Queue.prototype.maxAccountWanted = function(gameState, fraction)
|
||||
{
|
||||
maxAccountWanted(gameState, fraction)
|
||||
{
|
||||
const cost = new ResourcesManager();
|
||||
if (this.plans.length > 0 && this.plans[0].isGo(gameState))
|
||||
cost.add(this.plans[0].getCost());
|
||||
|
|
@ -81,68 +80,68 @@ Queue.prototype.maxAccountWanted = function(gameState, fraction)
|
|||
cost.add(costs);
|
||||
}
|
||||
return cost;
|
||||
};
|
||||
}
|
||||
|
||||
Queue.prototype.queueCost = function()
|
||||
{
|
||||
queueCost()
|
||||
{
|
||||
const cost = new ResourcesManager();
|
||||
for (const plan of this.plans)
|
||||
cost.add(plan.getCost());
|
||||
return cost;
|
||||
};
|
||||
}
|
||||
|
||||
Queue.prototype.length = function()
|
||||
{
|
||||
length()
|
||||
{
|
||||
return this.plans.length;
|
||||
};
|
||||
}
|
||||
|
||||
Queue.prototype.hasQueuedUnits = function()
|
||||
{
|
||||
hasQueuedUnits()
|
||||
{
|
||||
return this.plans.length > 0;
|
||||
};
|
||||
}
|
||||
|
||||
Queue.prototype.countQueuedUnits = function()
|
||||
{
|
||||
countQueuedUnits()
|
||||
{
|
||||
let count = 0;
|
||||
for (const plan of this.plans)
|
||||
count += plan.number;
|
||||
return count;
|
||||
};
|
||||
}
|
||||
|
||||
Queue.prototype.hasQueuedUnitsWithClass = function(classe)
|
||||
{
|
||||
hasQueuedUnitsWithClass(classe)
|
||||
{
|
||||
return this.plans.some(plan => plan.template && plan.template.hasClass(classe));
|
||||
};
|
||||
}
|
||||
|
||||
Queue.prototype.countQueuedUnitsWithClass = function(classe)
|
||||
{
|
||||
countQueuedUnitsWithClass(classe)
|
||||
{
|
||||
let count = 0;
|
||||
for (const plan of this.plans)
|
||||
if (plan.template && plan.template.hasClass(classe))
|
||||
count += plan.number;
|
||||
return count;
|
||||
};
|
||||
}
|
||||
|
||||
Queue.prototype.countQueuedUnitsWithMetadata = function(data, value)
|
||||
{
|
||||
countQueuedUnitsWithMetadata(data, value)
|
||||
{
|
||||
let count = 0;
|
||||
for (const plan of this.plans)
|
||||
if (plan.metadata[data] && plan.metadata[data] == value)
|
||||
count += plan.number;
|
||||
return count;
|
||||
};
|
||||
}
|
||||
|
||||
Queue.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
const plans = [];
|
||||
for (const plan of this.plans)
|
||||
plans.push(plan.Serialize());
|
||||
|
||||
return { "plans": plans, "paused": this.paused, "switched": this.switched };
|
||||
};
|
||||
}
|
||||
|
||||
Queue.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
Deserialize(gameState, data)
|
||||
{
|
||||
this.paused = data.paused;
|
||||
this.switched = data.switched;
|
||||
this.plans = [];
|
||||
|
|
@ -163,4 +162,5 @@ Queue.prototype.Deserialize = function(gameState, data)
|
|||
plan.Deserialize(gameState, dataPlan);
|
||||
this.plans.push(plan);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,10 @@ function sortQueues(queueArrays, priorities)
|
|||
});
|
||||
}
|
||||
|
||||
export function QueueManager(Config, queues)
|
||||
export class QueueManager
|
||||
{
|
||||
constructor(Config, queues)
|
||||
{
|
||||
this.Config = Config;
|
||||
this.queues = queues;
|
||||
this.priorities = {};
|
||||
|
|
@ -57,26 +59,26 @@ export function QueueManager(Config, queues)
|
|||
this.queueArrays.push([q, this.queues[q]]);
|
||||
}
|
||||
sortQueues(this.queueArrays, this.priorities);
|
||||
}
|
||||
}
|
||||
|
||||
QueueManager.prototype.getAvailableResources = function(gameState)
|
||||
{
|
||||
getAvailableResources(gameState)
|
||||
{
|
||||
const resources = gameState.getResources();
|
||||
for (const key in this.queues)
|
||||
resources.subtract(this.accounts[key]);
|
||||
return resources;
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.getTotalAccountedResources = function()
|
||||
{
|
||||
getTotalAccountedResources()
|
||||
{
|
||||
const resources = new ResourcesManager();
|
||||
for (const key in this.queues)
|
||||
resources.add(this.accounts[key]);
|
||||
return resources;
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.currentNeeds = function(gameState)
|
||||
{
|
||||
currentNeeds(gameState)
|
||||
{
|
||||
const needed = new ResourcesManager();
|
||||
// queueArrays because it's faster.
|
||||
for (const q of this.queueArrays)
|
||||
|
|
@ -93,12 +95,12 @@ QueueManager.prototype.currentNeeds = function(gameState)
|
|||
needed[res] = Math.max(0, needed[res] - current[res]);
|
||||
|
||||
return needed;
|
||||
};
|
||||
}
|
||||
|
||||
// calculate the gather rates we'd want to be able to start all elements in our queues
|
||||
// TODO: many things.
|
||||
QueueManager.prototype.wantedGatherRates = function(gameState)
|
||||
{
|
||||
// calculate the gather rates we'd want to be able to start all elements in our queues
|
||||
// TODO: many things.
|
||||
wantedGatherRates(gameState)
|
||||
{
|
||||
// default values for first turn when we have not yet set our queues.
|
||||
if (gameState.ai.playedTurn === 0)
|
||||
{
|
||||
|
|
@ -172,10 +174,10 @@ QueueManager.prototype.wantedGatherRates = function(gameState)
|
|||
}
|
||||
|
||||
return rates;
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.printQueues = function(gameState)
|
||||
{
|
||||
printQueues(gameState)
|
||||
{
|
||||
let numWorkers = 0;
|
||||
gameState.getOwnUnits().forEach(ent =>
|
||||
{
|
||||
|
|
@ -214,19 +216,19 @@ QueueManager.prototype.printQueues = function(gameState)
|
|||
aiWarn("Current Gather Rates: " + uneval(gameState.ai.HQ.GetCurrentGatherRates(gameState)));
|
||||
aiWarn("Most needed resources: " + uneval(gameState.ai.HQ.pickMostNeededResources(gameState)));
|
||||
aiWarn("------------------------------------");
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.clear = function()
|
||||
{
|
||||
clear()
|
||||
{
|
||||
for (const i in this.queues)
|
||||
this.queues[i].empty();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* set accounts of queue i from the unaccounted resources
|
||||
*/
|
||||
QueueManager.prototype.setAccounts = function(gameState, cost, i)
|
||||
{
|
||||
setAccounts(gameState, cost, i)
|
||||
{
|
||||
const available = this.getAvailableResources(gameState);
|
||||
for (const res of Resources.GetCodes())
|
||||
{
|
||||
|
|
@ -234,13 +236,13 @@ QueueManager.prototype.setAccounts = function(gameState, cost, i)
|
|||
continue;
|
||||
this.accounts[i][res] += Math.min(available[res], cost[res] - this.accounts[i][res]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* transfer accounts from queue i to queue j
|
||||
*/
|
||||
QueueManager.prototype.transferAccounts = function(cost, i, j)
|
||||
{
|
||||
transferAccounts(cost, i, j)
|
||||
{
|
||||
for (const res of Resources.GetCodes())
|
||||
{
|
||||
if (this.accounts[j][res] >= cost[res])
|
||||
|
|
@ -249,13 +251,13 @@ QueueManager.prototype.transferAccounts = function(cost, i, j)
|
|||
this.accounts[i][res] -= diff;
|
||||
this.accounts[j][res] += diff;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* distribute the resources between the different queues according to their priorities
|
||||
*/
|
||||
QueueManager.prototype.distributeResources = function(gameState)
|
||||
{
|
||||
distributeResources(gameState)
|
||||
{
|
||||
const availableRes = this.getAvailableResources(gameState);
|
||||
for (const res of Resources.GetCodes())
|
||||
{
|
||||
|
|
@ -341,10 +343,10 @@ QueueManager.prototype.distributeResources = function(gameState)
|
|||
if (available < 0)
|
||||
aiWarn("Petra: problem with remaining " + res + " in queueManager " + available);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.switchResource = function(gameState, res)
|
||||
{
|
||||
switchResource(gameState, res)
|
||||
{
|
||||
// We have no available resources, see if we can't "compact" them in one queue.
|
||||
// compare queues 2 by 2, and if one with a higher priority could be completed by our amount, give it.
|
||||
// TODO: this isn't perfect compression.
|
||||
|
|
@ -380,11 +382,11 @@ QueueManager.prototype.switchResource = function(gameState, res)
|
|||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Start the next item in the queue if we can afford it.
|
||||
QueueManager.prototype.startNextItems = function(gameState)
|
||||
{
|
||||
// Start the next item in the queue if we can afford it.
|
||||
startNextItems(gameState)
|
||||
{
|
||||
for (const q of this.queueArrays)
|
||||
{
|
||||
const name = q[0];
|
||||
|
|
@ -410,10 +412,10 @@ QueueManager.prototype.startNextItems = function(gameState)
|
|||
queue.switched = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.update = function(gameState)
|
||||
{
|
||||
update(gameState)
|
||||
{
|
||||
Engine.ProfileStart("Queue Manager");
|
||||
|
||||
for (const i in this.queues)
|
||||
|
|
@ -439,11 +441,11 @@ QueueManager.prototype.update = function(gameState)
|
|||
this.printQueues(gameState);
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
}
|
||||
|
||||
// Recovery system: if short of workers after an attack, pause (and reset) some queues to favor worker training
|
||||
QueueManager.prototype.checkPausedQueues = function(gameState)
|
||||
{
|
||||
// Recovery system: if short of workers after an attack, pause (and reset) some queues to favor worker training
|
||||
checkPausedQueues(gameState)
|
||||
{
|
||||
const numWorkers = gameState.countOwnEntitiesAndQueuedWithRole(Worker.ROLE_WORKER);
|
||||
const workersMin = Math.min(Math.max(12, 24 * this.Config.popScaling), this.Config.Economy.popPhase2);
|
||||
for (const q in this.queues)
|
||||
|
|
@ -503,32 +505,32 @@ QueueManager.prototype.checkPausedQueues = function(gameState)
|
|||
queue.plans[1].number = 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.canAfford = function(queue, cost)
|
||||
{
|
||||
canAfford(queue, cost)
|
||||
{
|
||||
if (!this.accounts[queue])
|
||||
return false;
|
||||
return this.accounts[queue].canAfford(cost);
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.pauseQueue = function(queue, scrapAccounts)
|
||||
{
|
||||
pauseQueue(queue, scrapAccounts)
|
||||
{
|
||||
if (!this.queues[queue])
|
||||
return;
|
||||
this.queues[queue].paused = true;
|
||||
if (scrapAccounts)
|
||||
this.accounts[queue].reset();
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.unpauseQueue = function(queue)
|
||||
{
|
||||
unpauseQueue(queue)
|
||||
{
|
||||
if (this.queues[queue])
|
||||
this.queues[queue].paused = false;
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.pauseAll = function(scrapAccounts, but)
|
||||
{
|
||||
pauseAll(scrapAccounts, but)
|
||||
{
|
||||
for (const q in this.queues)
|
||||
{
|
||||
if (q == but)
|
||||
|
|
@ -537,18 +539,18 @@ QueueManager.prototype.pauseAll = function(scrapAccounts, but)
|
|||
this.accounts[q].reset();
|
||||
this.queues[q].paused = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.unpauseAll = function(but)
|
||||
{
|
||||
unpauseAll(but)
|
||||
{
|
||||
for (const q in this.queues)
|
||||
if (q != but)
|
||||
this.queues[q].paused = false;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
QueueManager.prototype.addQueue = function(queueName, priority)
|
||||
{
|
||||
addQueue(queueName, priority)
|
||||
{
|
||||
if (this.queues[queueName] !== undefined)
|
||||
return;
|
||||
|
||||
|
|
@ -560,10 +562,10 @@ QueueManager.prototype.addQueue = function(queueName, priority)
|
|||
for (const q in this.queues)
|
||||
this.queueArrays.push([q, this.queues[q]]);
|
||||
sortQueues(this.queueArrays, this.priorities);
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.removeQueue = function(queueName)
|
||||
{
|
||||
removeQueue(queueName)
|
||||
{
|
||||
if (this.queues[queueName] === undefined)
|
||||
return;
|
||||
|
||||
|
|
@ -575,15 +577,15 @@ QueueManager.prototype.removeQueue = function(queueName)
|
|||
for (const q in this.queues)
|
||||
this.queueArrays.push([q, this.queues[q]]);
|
||||
sortQueues(this.queueArrays, this.priorities);
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.getPriority = function(queueName)
|
||||
{
|
||||
getPriority(queueName)
|
||||
{
|
||||
return this.priorities[queueName];
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.changePriority = function(queueName, newPriority)
|
||||
{
|
||||
changePriority(queueName, newPriority)
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
{
|
||||
aiWarn(">>> Priority of queue " + queueName + " changed from " + this.priorities[queueName] +
|
||||
|
|
@ -592,10 +594,10 @@ QueueManager.prototype.changePriority = function(queueName, newPriority)
|
|||
if (this.queues[queueName] !== undefined)
|
||||
this.priorities[queueName] = newPriority;
|
||||
sortQueues(this.queueArrays, this.priorities);
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
const accounts = {};
|
||||
const queues = {};
|
||||
for (const q in this.queues)
|
||||
|
|
@ -614,10 +616,10 @@ QueueManager.prototype.Serialize = function()
|
|||
"queues": queues,
|
||||
"accounts": accounts
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
QueueManager.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
Deserialize(gameState, data)
|
||||
{
|
||||
this.priorities = data.priorities;
|
||||
this.queues = {};
|
||||
this.accounts = {};
|
||||
|
|
@ -633,4 +635,5 @@ QueueManager.prototype.Deserialize = function(gameState, data)
|
|||
this.queueArrays.push([q, this.queues[q]]);
|
||||
}
|
||||
sortQueues(this.queueArrays, data.priorities);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,18 @@ import { Worker } from "simulation/ai/petra/worker.js";
|
|||
/**
|
||||
* Manage the research
|
||||
*/
|
||||
export function ResearchManager(Config)
|
||||
export class ResearchManager
|
||||
{
|
||||
constructor(Config)
|
||||
{
|
||||
this.Config = Config;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if we can go to the next phase
|
||||
*/
|
||||
ResearchManager.prototype.checkPhase = function(gameState, queues)
|
||||
{
|
||||
checkPhase(gameState, queues)
|
||||
{
|
||||
if (queues.majorTech.hasQueuedUnits())
|
||||
return;
|
||||
// Don't try to phase up if already trying to gather resources for a civil-centre or wonder
|
||||
|
|
@ -36,10 +38,10 @@ ResearchManager.prototype.checkPhase = function(gameState, queues)
|
|||
gameState.ai.queueManager.changePriority("majorTech", gameState.ai.Config.priorities.majorTech);
|
||||
queues.majorTech.addPlan(new ResearchPlan(gameState, nextPhaseName, true));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ResearchManager.prototype.researchPopulationBonus = function(gameState, queues)
|
||||
{
|
||||
researchPopulationBonus(gameState, queues)
|
||||
{
|
||||
if (queues.minorTech.hasQueuedUnits())
|
||||
return;
|
||||
|
||||
|
|
@ -54,10 +56,10 @@ ResearchManager.prototype.researchPopulationBonus = function(gameState, queues)
|
|||
queues.minorTech.addPlan(new ResearchPlan(gameState, tech[0]));
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ResearchManager.prototype.researchTradeBonus = function(gameState, queues)
|
||||
{
|
||||
researchTradeBonus(gameState, queues)
|
||||
{
|
||||
if (queues.minorTech.hasQueuedUnits())
|
||||
return;
|
||||
|
||||
|
|
@ -75,11 +77,11 @@ ResearchManager.prototype.researchTradeBonus = function(gameState, queues)
|
|||
queues.minorTech.addPlan(new ResearchPlan(gameState, tech[0]));
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Techs to be searched for as soon as they are available */
|
||||
ResearchManager.prototype.researchWantedTechs = function(gameState, techs)
|
||||
{
|
||||
/** Techs to be searched for as soon as they are available */
|
||||
researchWantedTechs(gameState, techs)
|
||||
{
|
||||
const phase1 = gameState.currentPhase() === 1;
|
||||
const available = phase1 ? gameState.ai.queueManager.getAvailableResources(gameState) : null;
|
||||
const numWorkers = phase1 ? gameState.getOwnEntitiesByRole(Worker.ROLE_WORKER, true).length : 0;
|
||||
|
|
@ -119,11 +121,11 @@ ResearchManager.prototype.researchWantedTechs = function(gameState, techs)
|
|||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/** Techs to be searched for as soon as they are available, but only after phase 2 */
|
||||
ResearchManager.prototype.researchPreferredTechs = function(gameState, techs)
|
||||
{
|
||||
/** Techs to be searched for as soon as they are available, but only after phase 2 */
|
||||
researchPreferredTechs(gameState, techs)
|
||||
{
|
||||
const phase2 = gameState.currentPhase() === 2;
|
||||
const available = phase2 ? gameState.ai.queueManager.getAvailableResources(gameState) : null;
|
||||
const numWorkers = phase2 ? gameState.getOwnEntitiesByRole(Worker.ROLE_WORKER, true).length : 0;
|
||||
|
|
@ -156,10 +158,10 @@ ResearchManager.prototype.researchPreferredTechs = function(gameState, techs)
|
|||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
ResearchManager.prototype.update = function(gameState, queues)
|
||||
{
|
||||
update(gameState, queues)
|
||||
{
|
||||
if (queues.minorTech.hasQueuedUnits() || queues.majorTech.hasQueuedUnits())
|
||||
return;
|
||||
|
||||
|
|
@ -225,21 +227,22 @@ ResearchManager.prototype.update = function(gameState, queues)
|
|||
|
||||
// randomly pick one. No worries about pairs in that case.
|
||||
queues.minorTech.addPlan(new ResearchPlan(gameState, pickRandom(techs)[0]));
|
||||
};
|
||||
}
|
||||
|
||||
ResearchManager.prototype.CostSum = function(cost)
|
||||
{
|
||||
CostSum(cost)
|
||||
{
|
||||
let costSum = 0;
|
||||
for (const res in cost)
|
||||
costSum += cost[res];
|
||||
return costSum;
|
||||
};
|
||||
}
|
||||
|
||||
ResearchManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
ResearchManager.prototype.Deserialize = function(data)
|
||||
{
|
||||
};
|
||||
Deserialize(data)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,37 +12,39 @@ import { Worker } from "simulation/ai/petra/worker.js";
|
|||
/**
|
||||
* Manage the trade
|
||||
*/
|
||||
export function TradeManager(config)
|
||||
export class TradeManager
|
||||
{
|
||||
constructor(config)
|
||||
{
|
||||
this.Config = config;
|
||||
this.tradeRoute = undefined;
|
||||
this.potentialTradeRoute = undefined;
|
||||
this.routeProspection = false;
|
||||
this.targetNumTraders = this.Config.Economy.targetNumTraders;
|
||||
this.warnedAllies = {};
|
||||
}
|
||||
}
|
||||
|
||||
TradeManager.prototype.init = function(gameState)
|
||||
{
|
||||
init(gameState)
|
||||
{
|
||||
this.traders = gameState.getOwnUnits().filter(
|
||||
filters.byMetadata(PlayerID, "role", Worker.ROLE_TRADER));
|
||||
this.traders.registerUpdates();
|
||||
this.minimalGain = gameState.ai.HQ.navalMap ? 3 : 5;
|
||||
};
|
||||
}
|
||||
|
||||
TradeManager.prototype.hasTradeRoute = function()
|
||||
{
|
||||
hasTradeRoute()
|
||||
{
|
||||
return this.tradeRoute !== undefined;
|
||||
};
|
||||
}
|
||||
|
||||
TradeManager.prototype.assignTrader = function(ent)
|
||||
{
|
||||
assignTrader(ent)
|
||||
{
|
||||
ent.setMetadata(PlayerID, "role", Worker.ROLE_TRADER);
|
||||
this.traders.updateEnt(ent);
|
||||
};
|
||||
}
|
||||
|
||||
TradeManager.prototype.trainMoreTraders = function(gameState, queues)
|
||||
{
|
||||
trainMoreTraders(gameState, queues)
|
||||
{
|
||||
if (!this.hasTradeRoute() || queues.trader.hasQueuedUnits())
|
||||
return;
|
||||
|
||||
|
|
@ -122,10 +124,10 @@ TradeManager.prototype.trainMoreTraders = function(gameState, queues)
|
|||
return;
|
||||
}
|
||||
queues.trader.addPlan(new TrainingPlan(gameState, template, metadata, 1, 1));
|
||||
};
|
||||
}
|
||||
|
||||
TradeManager.prototype.updateTrader = function(gameState, ent)
|
||||
{
|
||||
updateTrader(gameState, ent)
|
||||
{
|
||||
if (ent.hasClass("Ship") && gameState.ai.playedTurn % 5 == 0 &&
|
||||
!ent.unitAIState().startsWith("INDIVIDUAL.COLLECTTREASURE") &&
|
||||
gatherTreasure(gameState, ent, true))
|
||||
|
|
@ -177,10 +179,10 @@ TradeManager.prototype.updateTrader = function(gameState, ent)
|
|||
ent.tradeRoute(routeSource, routeTarget);
|
||||
ent.setMetadata(PlayerID, "route", route);
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
}
|
||||
|
||||
TradeManager.prototype.setTradingGoods = function(gameState)
|
||||
{
|
||||
setTradingGoods(gameState)
|
||||
{
|
||||
const resTradeCodes = Resources.GetTradableCodes();
|
||||
if (!resTradeCodes.length)
|
||||
return;
|
||||
|
|
@ -230,14 +232,14 @@ TradeManager.prototype.setTradingGoods = function(gameState)
|
|||
Engine.PostCommand(PlayerID, { "type": "set-trading-goods", "tradingGoods": tradingGoods });
|
||||
if (this.Config.debug > 2)
|
||||
aiWarn(" trading goods set to " + uneval(tradingGoods));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Try to barter unneeded resources for needed resources.
|
||||
* only once per turn because the info is not updated within a turn
|
||||
*/
|
||||
TradeManager.prototype.performBarter = function(gameState)
|
||||
{
|
||||
performBarter(gameState)
|
||||
{
|
||||
const barterers = gameState.getOwnEntitiesByClass("Barter", true).filter(filters.isBuilt())
|
||||
.toEntityArray();
|
||||
if (barterers.length == 0)
|
||||
|
|
@ -355,10 +357,10 @@ TradeManager.prototype.performBarter = function(gameState)
|
|||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
TradeManager.prototype.checkEvents = function(gameState, events)
|
||||
{
|
||||
checkEvents(gameState, events)
|
||||
{
|
||||
// check if one market from a traderoute is renamed, change the route accordingly
|
||||
for (const evt of events.EntityRenamed)
|
||||
{
|
||||
|
|
@ -425,21 +427,21 @@ TradeManager.prototype.checkEvents = function(gameState, events)
|
|||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
TradeManager.prototype.activateProspection = function(gameState)
|
||||
{
|
||||
activateProspection(gameState)
|
||||
{
|
||||
this.routeProspection = true;
|
||||
gameState.ai.HQ.buildManager.setBuildable(gameState.applyCiv("structures/{civ}/market"));
|
||||
gameState.ai.HQ.buildManager.setBuildable(gameState.applyCiv("structures/{civ}/dock"));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* fills the best trade route in this.tradeRoute and the best potential route in this.potentialTradeRoute
|
||||
* If an index is given, it returns the best route with this index or the best land route if index is a land index
|
||||
*/
|
||||
TradeManager.prototype.checkRoutes = function(gameState, accessIndex)
|
||||
{
|
||||
checkRoutes(gameState, accessIndex)
|
||||
{
|
||||
// If we cannot trade, do not bother checking routes.
|
||||
if (!Resources.GetTradableCodes().length)
|
||||
{
|
||||
|
|
@ -579,11 +581,11 @@ TradeManager.prototype.checkRoutes = function(gameState, accessIndex)
|
|||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** Called when a market was built or destroyed, and checks if trader orders should be changed */
|
||||
TradeManager.prototype.checkTrader = function(gameState, ent)
|
||||
{
|
||||
/** Called when a market was built or destroyed, and checks if trader orders should be changed */
|
||||
checkTrader(gameState, ent)
|
||||
{
|
||||
const presentRoute = ent.getMetadata(PlayerID, "route");
|
||||
if (!presentRoute)
|
||||
return;
|
||||
|
|
@ -616,10 +618,10 @@ TradeManager.prototype.checkTrader = function(gameState, ent)
|
|||
}
|
||||
ent.stopMoving();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
TradeManager.prototype.prospectForNewMarket = function(gameState, queues)
|
||||
{
|
||||
prospectForNewMarket(gameState, queues)
|
||||
{
|
||||
if (queues.economicBuilding.hasQueuedUnitsWithClass("Trade") || queues.dock.hasQueuedUnitsWithClass("Trade"))
|
||||
return;
|
||||
if (!gameState.ai.HQ.canBuild(gameState, "structures/{civ}/market"))
|
||||
|
|
@ -668,10 +670,10 @@ TradeManager.prototype.prospectForNewMarket = function(gameState, queues)
|
|||
if (!this.tradeRoute)
|
||||
plan.queueToReset = "economicBuilding";
|
||||
queues.economicBuilding.addPlan(plan);
|
||||
};
|
||||
}
|
||||
|
||||
TradeManager.prototype.isNewMarketWorth = function(expectedGain)
|
||||
{
|
||||
isNewMarketWorth(expectedGain)
|
||||
{
|
||||
if (!Resources.GetTradableCodes().length)
|
||||
return false;
|
||||
if (expectedGain < this.minimalGain)
|
||||
|
|
@ -680,10 +682,10 @@ TradeManager.prototype.isNewMarketWorth = function(expectedGain)
|
|||
expectedGain < this.potentialTradeRoute.gain + 20)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
TradeManager.prototype.update = function(gameState, events, queues)
|
||||
{
|
||||
update(gameState, events, queues)
|
||||
{
|
||||
if (gameState.ai.HQ.canBarter && Resources.GetBarterableCodes().length)
|
||||
this.performBarter(gameState);
|
||||
|
||||
|
|
@ -709,10 +711,10 @@ TradeManager.prototype.update = function(gameState, events, queues)
|
|||
|
||||
if (this.routeProspection)
|
||||
this.prospectForNewMarket(gameState, queues);
|
||||
};
|
||||
}
|
||||
|
||||
TradeManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
return {
|
||||
"tradeRoute": this.tradeRoute,
|
||||
"potentialTradeRoute": this.potentialTradeRoute,
|
||||
|
|
@ -720,10 +722,11 @@ TradeManager.prototype.Serialize = function()
|
|||
"targetNumTraders": this.targetNumTraders,
|
||||
"warnedAllies": this.warnedAllies
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
TradeManager.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
Deserialize(gameState, data)
|
||||
{
|
||||
for (const key in data)
|
||||
this[key] = data[key];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@ import { Worker } from "simulation/ai/petra/worker.js";
|
|||
* transporter = this.ID
|
||||
*/
|
||||
|
||||
export function TransportPlan(gameState, units, startIndex, endIndex, endPos, ship)
|
||||
export class TransportPlan
|
||||
{
|
||||
constructor(gameState, units, startIndex, endIndex, endPos, ship)
|
||||
{
|
||||
this.ID = gameState.ai.uniqueIDs.transports++;
|
||||
this.debug = gameState.ai.Config.debug;
|
||||
this.flotilla = false; // when false, only one ship per transport ... not yet tested when true
|
||||
|
|
@ -82,19 +84,19 @@ export function TransportPlan(gameState, units, startIndex, endIndex, endPos, sh
|
|||
this.needTransportShips = ship === undefined;
|
||||
this.nTry = {};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* We're trying to board units onto our ships.
|
||||
*/
|
||||
TransportPlan.BOARDING = "boarding";
|
||||
/**
|
||||
static BOARDING = "boarding";
|
||||
/**
|
||||
* We're moving ships and eventually unload units.
|
||||
*/
|
||||
TransportPlan.SAILING = "sailing";
|
||||
static SAILING = "sailing";
|
||||
|
||||
TransportPlan.prototype.init = function(gameState)
|
||||
{
|
||||
init(gameState)
|
||||
{
|
||||
this.units = gameState.getOwnUnits().filter(filters.byMetadata(PlayerID, "transport", this.ID));
|
||||
this.ships = gameState.ai.HQ.navalManager.ships.filter(filters.byMetadata(PlayerID, "transporter",
|
||||
this.ID));
|
||||
|
|
@ -106,28 +108,28 @@ TransportPlan.prototype.init = function(gameState)
|
|||
this.transportShips.registerUpdates();
|
||||
|
||||
this.boardingRange = 18*18; // TODO compute it from the ship clearance and garrison range
|
||||
};
|
||||
}
|
||||
|
||||
/** count available slots */
|
||||
TransportPlan.prototype.countFreeSlots = function()
|
||||
{
|
||||
/** count available slots */
|
||||
countFreeSlots()
|
||||
{
|
||||
let slots = 0;
|
||||
for (const ship of this.transportShips.values())
|
||||
slots += this.countFreeSlotsOnShip(ship);
|
||||
return slots;
|
||||
};
|
||||
}
|
||||
|
||||
TransportPlan.prototype.countFreeSlotsOnShip = function(ship)
|
||||
{
|
||||
countFreeSlotsOnShip(ship)
|
||||
{
|
||||
if (ship.hitpoints() < ship.garrisonEjectHealth() * ship.maxHitpoints())
|
||||
return 0;
|
||||
const occupied = ship.garrisoned().length +
|
||||
this.units.filter(filters.byMetadata(PlayerID, "onBoard", ship.id())).length;
|
||||
return Math.max(ship.garrisonMax() - occupied, 0);
|
||||
};
|
||||
}
|
||||
|
||||
TransportPlan.prototype.assignUnitToShip = function(gameState, ent)
|
||||
{
|
||||
assignUnitToShip(gameState, ent)
|
||||
{
|
||||
if (this.needTransportShips)
|
||||
return;
|
||||
|
||||
|
|
@ -156,10 +158,10 @@ TransportPlan.prototype.assignUnitToShip = function(gameState, ent)
|
|||
this.needSplit = [ent];
|
||||
else
|
||||
this.needSplit.push(ent);
|
||||
};
|
||||
}
|
||||
|
||||
TransportPlan.prototype.assignShip = function(gameState)
|
||||
{
|
||||
assignShip(gameState)
|
||||
{
|
||||
let pos;
|
||||
// choose a unit of this plan not yet assigned to a ship
|
||||
for (const ent of this.units.values())
|
||||
|
|
@ -196,19 +198,19 @@ TransportPlan.prototype.assignShip = function(gameState)
|
|||
this.transportShips.updateEnt(nearest);
|
||||
this.needTransportShips = false;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** add a unit to this plan */
|
||||
TransportPlan.prototype.addUnit = function(unit, endPos)
|
||||
{
|
||||
/** add a unit to this plan */
|
||||
addUnit(unit, endPos)
|
||||
{
|
||||
unit.setMetadata(PlayerID, "transport", this.ID);
|
||||
unit.setMetadata(PlayerID, "endPos", endPos);
|
||||
this.units.updateEnt(unit);
|
||||
};
|
||||
}
|
||||
|
||||
/** remove a unit from this plan, if not yet on board */
|
||||
TransportPlan.prototype.removeUnit = function(gameState, unit)
|
||||
{
|
||||
/** remove a unit from this plan, if not yet on board */
|
||||
removeUnit(gameState, unit)
|
||||
{
|
||||
const shipId = unit.getMetadata(PlayerID, "onBoard");
|
||||
if (shipId == "onBoard")
|
||||
return; // too late, already onBoard
|
||||
|
|
@ -229,10 +231,10 @@ TransportPlan.prototype.removeUnit = function(gameState, unit)
|
|||
this.transportShips.updateEnt(ship);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
TransportPlan.prototype.releaseShip = function(ship)
|
||||
{
|
||||
releaseShip(ship)
|
||||
{
|
||||
if (ship.getMetadata(PlayerID, "transporter") != this.ID)
|
||||
{
|
||||
aiWarn(" Petra: try removing a transporter ship with " +
|
||||
|
|
@ -248,10 +250,10 @@ TransportPlan.prototype.releaseShip = function(ship)
|
|||
ship.setMetadata(PlayerID, "transporter", undefined);
|
||||
if (ship.getMetadata(PlayerID, "role") === Worker.ROLE_SWITCH_TO_TRADER)
|
||||
ship.setMetadata(PlayerID, "role", Worker.ROLE_TRADER);
|
||||
};
|
||||
}
|
||||
|
||||
TransportPlan.prototype.releaseAll = function()
|
||||
{
|
||||
releaseAll()
|
||||
{
|
||||
for (const ship of this.ships.values())
|
||||
this.releaseShip(ship);
|
||||
|
||||
|
|
@ -267,11 +269,11 @@ TransportPlan.prototype.releaseAll = function()
|
|||
this.transportShips.unregister();
|
||||
this.ships.unregister();
|
||||
this.units.unregister();
|
||||
};
|
||||
}
|
||||
|
||||
/** TODO not currently used ... to be fixed */
|
||||
TransportPlan.prototype.cancelTransport = function(gameState)
|
||||
{
|
||||
/** TODO not currently used ... to be fixed */
|
||||
cancelTransport(gameState)
|
||||
{
|
||||
const ent = this.units.toEntityArray()[0];
|
||||
let base = gameState.ai.HQ.getBaseByID(ent.getMetadata(PlayerID, "base"));
|
||||
if (!base.anchor || !base.anchor.position())
|
||||
|
|
@ -292,24 +294,24 @@ TransportPlan.prototype.cancelTransport = function(gameState)
|
|||
this.endPos = base.anchor.position();
|
||||
this.canceled = true;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* Try to move on and then clear the plan.
|
||||
*/
|
||||
TransportPlan.prototype.update = function(gameState)
|
||||
{
|
||||
update(gameState)
|
||||
{
|
||||
if (this.state === TransportPlan.BOARDING)
|
||||
this.onBoarding(gameState);
|
||||
else if (this.state === TransportPlan.SAILING)
|
||||
this.onSailing(gameState);
|
||||
|
||||
return this.units.length;
|
||||
};
|
||||
}
|
||||
|
||||
TransportPlan.prototype.onBoarding = function(gameState)
|
||||
{
|
||||
onBoarding(gameState)
|
||||
{
|
||||
let ready = true;
|
||||
const time = gameState.ai.elapsedTime;
|
||||
const shipTested = {};
|
||||
|
|
@ -448,11 +450,11 @@ TransportPlan.prototype.onBoarding = function(gameState)
|
|||
this.nTry = {};
|
||||
this.unloaded = [];
|
||||
this.recovered = [];
|
||||
};
|
||||
}
|
||||
|
||||
/** tell if a unit is garrisoned in one of the ships of this plan, and update its metadata if yes */
|
||||
TransportPlan.prototype.isOnBoard = function(ent)
|
||||
{
|
||||
/** tell if a unit is garrisoned in one of the ships of this plan, and update its metadata if yes */
|
||||
isOnBoard(ent)
|
||||
{
|
||||
for (const ship of this.transportShips.values())
|
||||
{
|
||||
if (ship.garrisoned().indexOf(ent.id()) == -1)
|
||||
|
|
@ -461,12 +463,12 @@ TransportPlan.prototype.isOnBoard = function(ent)
|
|||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/** when avoidEnnemy is true, we try to not board/unboard in ennemy territory */
|
||||
TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex, seaIndex, destination,
|
||||
/** when avoidEnnemy is true, we try to not board/unboard in ennemy territory */
|
||||
getBoardingPos(gameState, ship, landIndex, seaIndex, destination,
|
||||
avoidEnnemy)
|
||||
{
|
||||
{
|
||||
if (!gameState.ai.HQ.navalManager.landingZones[landIndex])
|
||||
{
|
||||
aiWarn(" >>> no landing zone for land " + landIndex);
|
||||
|
|
@ -530,10 +532,10 @@ TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex, se
|
|||
if (!posmin && this.boardingPos[ship.id()])
|
||||
posmin = this.boardingPos[ship.id()];
|
||||
return posmin;
|
||||
};
|
||||
}
|
||||
|
||||
TransportPlan.prototype.onSailing = function(gameState)
|
||||
{
|
||||
onSailing(gameState)
|
||||
{
|
||||
// Check that the units recovered on the previous turn have been reloaded
|
||||
for (const recov of this.recovered)
|
||||
{
|
||||
|
|
@ -700,10 +702,10 @@ TransportPlan.prototype.onSailing = function(gameState)
|
|||
ship.move(this.boardingPos[shipId][0], this.boardingPos[shipId][1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
TransportPlan.prototype.resetUnit = function(gameState, ent)
|
||||
{
|
||||
resetUnit(gameState, ent)
|
||||
{
|
||||
ent.setMetadata(PlayerID, "transport", undefined);
|
||||
ent.setMetadata(PlayerID, "onBoard", undefined);
|
||||
ent.setMetadata(PlayerID, "endPos", undefined);
|
||||
|
|
@ -720,10 +722,10 @@ TransportPlan.prototype.resetUnit = function(gameState, ent)
|
|||
if (army)
|
||||
army.removeOwn(gameState, ent.id());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
TransportPlan.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
return {
|
||||
"ID": this.ID,
|
||||
"flotilla": this.flotilla,
|
||||
|
|
@ -739,12 +741,13 @@ TransportPlan.prototype.Serialize = function()
|
|||
"unloaded": this.unloaded,
|
||||
"recovered": this.recovered
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
TransportPlan.prototype.Deserialize = function(data)
|
||||
{
|
||||
Deserialize(data)
|
||||
{
|
||||
for (const key in data)
|
||||
this[key] = data[key];
|
||||
|
||||
this.failed = false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@ import { Worker } from "simulation/ai/petra/worker.js";
|
|||
* in wonder, train military guards.
|
||||
*/
|
||||
|
||||
export function VictoryManager(Config)
|
||||
export class VictoryManager
|
||||
{
|
||||
constructor(Config)
|
||||
{
|
||||
this.Config = Config;
|
||||
this.criticalEnts = new Map();
|
||||
// Holds ids of all ents who are (or can be) guarding and if the ent is currently guarding
|
||||
|
|
@ -24,13 +26,13 @@ export function VictoryManager(Config)
|
|||
this.tryCaptureGaiaRelicLapseTime = -1;
|
||||
// Gaia relics which we are targeting currently and have not captured yet
|
||||
this.targetedGaiaRelics = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Cache the ids of any inital victory-critical entities.
|
||||
*/
|
||||
VictoryManager.prototype.init = function(gameState)
|
||||
{
|
||||
init(gameState)
|
||||
{
|
||||
if (gameState.getVictoryConditions().has("wonder"))
|
||||
{
|
||||
for (const wonder of gameState.getOwnEntitiesByClass("Wonder", true).values())
|
||||
|
|
@ -62,15 +64,15 @@ VictoryManager.prototype.init = function(gameState)
|
|||
this.criticalEnts.set(relic.id(), { "guardsAssigned": new Set(), "guards": new Map() });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* In regicide victory condition, if the hero has less than 70% health, try to garrison it in a healing structure
|
||||
* If it is less than 40%, try to garrison in the closest possible structure
|
||||
* If the hero cannot garrison, retreat it to the closest base
|
||||
*/
|
||||
VictoryManager.prototype.checkEvents = function(gameState, events)
|
||||
{
|
||||
checkEvents(gameState, events)
|
||||
{
|
||||
if (gameState.getVictoryConditions().has("wonder"))
|
||||
{
|
||||
for (const evt of events.Create)
|
||||
|
|
@ -303,10 +305,10 @@ VictoryManager.prototype.checkEvents = function(gameState, events)
|
|||
this.pickCriticalEntRetreatLocation(gameState, ent, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
VictoryManager.prototype.removeCriticalEnt = function(gameState, criticalEntId)
|
||||
{
|
||||
removeCriticalEnt(gameState, criticalEntId)
|
||||
{
|
||||
for (const [guardId, role] of this.criticalEnts.get(criticalEntId).guards)
|
||||
{
|
||||
const guardEnt = gameState.getEntityById(guardId);
|
||||
|
|
@ -326,13 +328,13 @@ VictoryManager.prototype.removeCriticalEnt = function(gameState, criticalEntId)
|
|||
guardEnt.setMetadata(PlayerID, "guardedEnt", undefined);
|
||||
}
|
||||
this.criticalEnts.delete(criticalEntId);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Train more healers to be later affected to critical entities if needed
|
||||
*/
|
||||
VictoryManager.prototype.manageCriticalEntHealers = function(gameState, queues)
|
||||
{
|
||||
manageCriticalEntHealers(gameState, queues)
|
||||
{
|
||||
if (gameState.ai.HQ.saveResources || queues.healer.hasQueuedUnits() ||
|
||||
!gameState.getOwnEntitiesByClass("Temple", true).hasEntities() ||
|
||||
this.guardEnts.size > Math.min(gameState.getPopulationMax() / 10, gameState.getPopulation() / 4))
|
||||
|
|
@ -350,15 +352,15 @@ VictoryManager.prototype.manageCriticalEntHealers = function(gameState, queues)
|
|||
{ "role": Worker.ROLE_CRITICAL_ENT_HEALER, "base": 0 }, 1, 1));
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Try to keep some military units guarding any criticalEnts, if we can afford it.
|
||||
* If we have too low a population and require units for other needs, remove guards so they can be reassigned.
|
||||
* TODO: Swap citizen soldier guards with champions if they become available.
|
||||
*/
|
||||
VictoryManager.prototype.manageCriticalEntGuards = function(gameState)
|
||||
{
|
||||
manageCriticalEntGuards(gameState)
|
||||
{
|
||||
let numWorkers = gameState.getOwnEntitiesByRole(Worker.ROLE_WORKER, true).length;
|
||||
if (numWorkers < 20)
|
||||
{
|
||||
|
|
@ -460,10 +462,10 @@ VictoryManager.prototype.manageCriticalEntGuards = function(gameState)
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
VictoryManager.prototype.tryAssignMilitaryGuard = function(gameState, guardEnt, criticalEnt, checkForSameAccess)
|
||||
{
|
||||
tryAssignMilitaryGuard(gameState, guardEnt, criticalEnt, checkForSameAccess)
|
||||
{
|
||||
if (guardEnt.getMetadata(PlayerID, "plan") !== undefined ||
|
||||
guardEnt.getMetadata(PlayerID, "transport") !== undefined || this.criticalEnts.has(guardEnt.id()) ||
|
||||
checkForSameAccess && (!guardEnt.position() || !criticalEnt.position() ||
|
||||
|
|
@ -476,10 +478,10 @@ VictoryManager.prototype.tryAssignMilitaryGuard = function(gameState, guardEnt,
|
|||
guardEnt.setMetadata(PlayerID, "plan", -2);
|
||||
guardEnt.setMetadata(PlayerID, "role", Worker.ROLE_CRITICAL_ENT_GUARD);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
VictoryManager.prototype.pickCriticalEntRetreatLocation = function(gameState, criticalEnt, emergency)
|
||||
{
|
||||
pickCriticalEntRetreatLocation(gameState, criticalEnt, emergency)
|
||||
{
|
||||
gameState.ai.HQ.defenseManager.garrisonAttackedUnit(gameState, criticalEnt, emergency);
|
||||
const plan = criticalEnt.getMetadata(PlayerID, "plan");
|
||||
|
||||
|
|
@ -500,9 +502,9 @@ VictoryManager.prototype.pickCriticalEntRetreatLocation = function(gameState, cr
|
|||
criticalEnt.moveToRange(bestBasePos[0], bestBasePos[1],
|
||||
0, bestBase.anchor.obstructionRadius().max);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Only send the guard command if the guard's accessIndex is the same as the critical ent
|
||||
* and the critical ent has a position (i.e. not garrisoned).
|
||||
* Request a transport if the accessIndex value is different, and if a transport is needed,
|
||||
|
|
@ -510,8 +512,8 @@ VictoryManager.prototype.pickCriticalEntRetreatLocation = function(gameState, cr
|
|||
* which will be used once its transport has finished.
|
||||
* Return false if the guardEnt is not a valid guard unit (i.e. cannot guard or is being transported).
|
||||
*/
|
||||
VictoryManager.prototype.assignGuardToCriticalEnt = function(gameState, guardEnt, criticalEntId)
|
||||
{
|
||||
assignGuardToCriticalEnt(gameState, guardEnt, criticalEntId)
|
||||
{
|
||||
if (guardEnt.getMetadata(PlayerID, "transport") !== undefined || !guardEnt.canGuard())
|
||||
return false;
|
||||
|
||||
|
|
@ -580,17 +582,17 @@ VictoryManager.prototype.assignGuardToCriticalEnt = function(gameState, guardEnt
|
|||
|
||||
this.guardEnts.set(guardEnt.id(), guardEntAccess == criticalEntAccess);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
VictoryManager.prototype.resetCaptureGaiaRelic = function(gameState)
|
||||
{
|
||||
resetCaptureGaiaRelic(gameState)
|
||||
{
|
||||
// Do not capture gaia relics too frequently as the ai has access to the entire map
|
||||
this.tryCaptureGaiaRelicLapseTime = gameState.ai.elapsedTime + 240 - 30 * (this.Config.difficulty - 3);
|
||||
this.tryCaptureGaiaRelic = false;
|
||||
};
|
||||
}
|
||||
|
||||
VictoryManager.prototype.update = function(gameState, events, queues)
|
||||
{
|
||||
update(gameState, events, queues)
|
||||
{
|
||||
// Wait a turn for trigger scripts to spawn any critical ents (i.e. in regicide)
|
||||
if (gameState.ai.playedTurn == 1)
|
||||
this.init(gameState);
|
||||
|
|
@ -665,13 +667,13 @@ VictoryManager.prototype.update = function(gameState, events, queues)
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Send an expedition to capture a gaia relic, or reinforce an existing one.
|
||||
*/
|
||||
VictoryManager.prototype.captureGaiaRelic = function(gameState, relic)
|
||||
{
|
||||
captureGaiaRelic(gameState, relic)
|
||||
{
|
||||
let capture = -relic.defaultRegenRate();
|
||||
const sumCapturePoints = relic.capturePoints().reduce((a, b) => a + b);
|
||||
const plans = this.targetedGaiaRelics.get(relic.id());
|
||||
|
|
@ -736,10 +738,10 @@ VictoryManager.prototype.captureGaiaRelic = function(gameState, relic)
|
|||
}
|
||||
attack.forceStart();
|
||||
this.targetedGaiaRelics.get(relic.id()).push(plan);
|
||||
};
|
||||
}
|
||||
|
||||
VictoryManager.prototype.abortCaptureGaiaRelic = function(gameState, relicId)
|
||||
{
|
||||
abortCaptureGaiaRelic(gameState, relicId)
|
||||
{
|
||||
for (const plan of this.targetedGaiaRelics.get(relicId))
|
||||
{
|
||||
const attack = gameState.ai.HQ.attackManager.getPlan(plan);
|
||||
|
|
@ -747,10 +749,10 @@ VictoryManager.prototype.abortCaptureGaiaRelic = function(gameState, relicId)
|
|||
attack.Abort(gameState);
|
||||
}
|
||||
this.targetedGaiaRelics.delete(relicId);
|
||||
};
|
||||
}
|
||||
|
||||
VictoryManager.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
return {
|
||||
"criticalEnts": this.criticalEnts,
|
||||
"guardEnts": this.guardEnts,
|
||||
|
|
@ -759,10 +761,11 @@ VictoryManager.prototype.Serialize = function()
|
|||
"tryCaptureGaiaRelicLapseTime": this.tryCaptureGaiaRelicLapseTime,
|
||||
"targetedGaiaRelics": this.targetedGaiaRelics
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
VictoryManager.prototype.Deserialize = function(data)
|
||||
{
|
||||
Deserialize(data)
|
||||
{
|
||||
for (const key in data)
|
||||
this[key] = data[key];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,33 +7,35 @@ import { TransportPlan } from "simulation/ai/petra/transportPlan.js";
|
|||
/**
|
||||
* This class makes a worker do as instructed by the economy manager
|
||||
*/
|
||||
export function Worker(base)
|
||||
export class Worker
|
||||
{
|
||||
constructor(base)
|
||||
{
|
||||
this.ent = undefined;
|
||||
this.base = base;
|
||||
this.baseID = base.ID;
|
||||
}
|
||||
}
|
||||
|
||||
Worker.ROLE_ATTACK = "attack";
|
||||
Worker.ROLE_TRADER = "trader";
|
||||
Worker.ROLE_SWITCH_TO_TRADER = "switchToTrader";
|
||||
Worker.ROLE_WORKER = "worker";
|
||||
Worker.ROLE_CRITICAL_ENT_GUARD = "criticalEntGuard";
|
||||
Worker.ROLE_CRITICAL_ENT_HEALER = "criticalEntHealer";
|
||||
static ROLE_ATTACK = "attack";
|
||||
static ROLE_TRADER = "trader";
|
||||
static ROLE_SWITCH_TO_TRADER = "switchToTrader";
|
||||
static ROLE_WORKER = "worker";
|
||||
static ROLE_CRITICAL_ENT_GUARD = "criticalEntGuard";
|
||||
static ROLE_CRITICAL_ENT_HEALER = "criticalEntHealer";
|
||||
|
||||
Worker.SUBROLE_DEFENDER = "defender";
|
||||
Worker.SUBROLE_IDLE = "idle";
|
||||
Worker.SUBROLE_BUILDER = "builder";
|
||||
Worker.SUBROLE_COMPLETING = "completing";
|
||||
Worker.SUBROLE_WALKING = "walking";
|
||||
Worker.SUBROLE_ATTACKING = "attacking";
|
||||
Worker.SUBROLE_GATHERER = "gatherer";
|
||||
Worker.SUBROLE_HUNTER = "hunter";
|
||||
Worker.SUBROLE_FISHER = "fisher";
|
||||
Worker.SUBROLE_GARRISONING = "garrisoning";
|
||||
static SUBROLE_DEFENDER = "defender";
|
||||
static SUBROLE_IDLE = "idle";
|
||||
static SUBROLE_BUILDER = "builder";
|
||||
static SUBROLE_COMPLETING = "completing";
|
||||
static SUBROLE_WALKING = "walking";
|
||||
static SUBROLE_ATTACKING = "attacking";
|
||||
static SUBROLE_GATHERER = "gatherer";
|
||||
static SUBROLE_HUNTER = "hunter";
|
||||
static SUBROLE_FISHER = "fisher";
|
||||
static SUBROLE_GARRISONING = "garrisoning";
|
||||
|
||||
Worker.prototype.update = function(gameState, ent)
|
||||
{
|
||||
update(gameState, ent)
|
||||
{
|
||||
if (!ent.position() || ent.getMetadata(PlayerID, "plan") == -2 || ent.getMetadata(PlayerID, "plan") == -3)
|
||||
return;
|
||||
|
||||
|
|
@ -439,10 +441,10 @@ Worker.prototype.update = function(gameState, ent)
|
|||
this.startFishing(gameState);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Worker.prototype.retryWorking = function(gameState, subrole)
|
||||
{
|
||||
retryWorking(gameState, subrole)
|
||||
{
|
||||
switch (subrole)
|
||||
{
|
||||
case Worker.SUBROLE_GATHERER:
|
||||
|
|
@ -456,10 +458,10 @@ Worker.prototype.retryWorking = function(gameState, subrole)
|
|||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Worker.prototype.startBuilding = function(gameState)
|
||||
{
|
||||
startBuilding(gameState)
|
||||
{
|
||||
const target = gameState.getEntityById(this.ent.getMetadata(PlayerID, "target-foundation"));
|
||||
if (!target || target.foundationProgress() === undefined && target.needsRepair() == false)
|
||||
return false;
|
||||
|
|
@ -467,10 +469,10 @@ Worker.prototype.startBuilding = function(gameState)
|
|||
return false;
|
||||
this.ent.repair(target, target.hasClass("House")); // autocontinue=true for houses
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
Worker.prototype.startGathering = function(gameState)
|
||||
{
|
||||
startGathering(gameState)
|
||||
{
|
||||
// First look for possible treasure if any
|
||||
if (gatherTreasure(gameState, this.ent))
|
||||
return true;
|
||||
|
|
@ -761,14 +763,14 @@ Worker.prototype.startGathering = function(gameState)
|
|||
aiWarn(" >>>>> worker with gather-type " + resource + " with nothing to gather ");
|
||||
this.ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* if position is given, we only check if we could hunt from this position but do nothing
|
||||
* otherwise the position of the entity is taken, and if something is found, we directly start the hunt
|
||||
*/
|
||||
Worker.prototype.startHunting = function(gameState, position)
|
||||
{
|
||||
startHunting(gameState, position)
|
||||
{
|
||||
// First look for possible treasure if any
|
||||
if (!position && gatherTreasure(gameState, this.ent))
|
||||
return true;
|
||||
|
|
@ -873,10 +875,10 @@ Worker.prototype.startHunting = function(gameState, position)
|
|||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
Worker.prototype.startFishing = function(gameState)
|
||||
{
|
||||
startFishing(gameState)
|
||||
{
|
||||
if (!this.ent.position())
|
||||
return false;
|
||||
|
||||
|
|
@ -969,10 +971,10 @@ Worker.prototype.startFishing = function(gameState)
|
|||
if (this.ent.getMetadata(PlayerID, "subrole") === Worker.SUBROLE_FISHER)
|
||||
this.ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
Worker.prototype.gatherNearestField = function(gameState, baseID)
|
||||
{
|
||||
gatherNearestField(gameState, baseID)
|
||||
{
|
||||
const ownFields = gameState.getOwnEntitiesByClass("Field", true).filter(filters.isBuilt())
|
||||
.filter(filters.byMetadata(PlayerID, "base", baseID));
|
||||
let bestFarm;
|
||||
|
|
@ -1009,14 +1011,14 @@ Worker.prototype.gatherNearestField = function(gameState, baseID)
|
|||
this.base.AddTCGatherer(bestFarm.ent.id());
|
||||
this.ent.setMetadata(PlayerID, "supply", bestFarm.ent.id());
|
||||
return bestFarm.ent;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* WARNING with the present options of AI orders, the unit will not gather after building the farm.
|
||||
* This is done by calling the gatherNearestField function when construction is completed.
|
||||
*/
|
||||
Worker.prototype.buildAnyField = function(gameState, baseID)
|
||||
{
|
||||
buildAnyField(gameState, baseID)
|
||||
{
|
||||
if (!this.ent.isBuilder())
|
||||
return false;
|
||||
let bestFarmEnt = false;
|
||||
|
|
@ -1037,15 +1039,15 @@ Worker.prototype.buildAnyField = function(gameState, baseID)
|
|||
bestFarmDist = dist;
|
||||
}
|
||||
return bestFarmEnt;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Workers elephant should move away from the buildings they've built to avoid being trapped in between constructions.
|
||||
* For the time being, we move towards the nearest gatherer (providing him a dropsite).
|
||||
* BaseManager does also use that function to deal with its mobile dropsites.
|
||||
*/
|
||||
Worker.prototype.moveToGatherer = function(gameState, ent, forced)
|
||||
{
|
||||
moveToGatherer(gameState, ent, forced)
|
||||
{
|
||||
const pos = ent.position();
|
||||
if (!pos || ent.getMetadata(PlayerID, "target-foundation") !== undefined)
|
||||
return;
|
||||
|
|
@ -1075,15 +1077,15 @@ Worker.prototype.moveToGatherer = function(gameState, ent, forced)
|
|||
ent.setMetadata(PlayerID, "nextMoveToGatherer", gameState.ai.elapsedTime + (destination ? 12 : 5));
|
||||
if (destination && dist > 10)
|
||||
ent.move(destination[0], destination[1]);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check accessibility of the target when in approach (in RMS maps, we quite often have chicken or bushes
|
||||
* inside obstruction of other entities). The resource will be flagged as inaccessible during 10 mn (in case
|
||||
* it will be cleared later).
|
||||
*/
|
||||
Worker.prototype.isInaccessibleSupply = function(gameState)
|
||||
{
|
||||
isInaccessibleSupply(gameState)
|
||||
{
|
||||
if (!this.ent.unitAIOrderData()[0] || !this.ent.unitAIOrderData()[0].target)
|
||||
return false;
|
||||
const targetId = this.ent.unitAIOrderData()[0].target;
|
||||
|
|
@ -1147,4 +1149,5 @@ Worker.prototype.isInaccessibleSupply = function(gameState)
|
|||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue