1
0
forked from mirrors/0ad

Use proper js-modules for ai common-api

The common-api already used a coding pattern which was called modules.
That is replaced with native js-modules.

Refs: #8081
This commit is contained in:
phosit
2025-08-03 13:30:17 +02:00
committed by Phosit
parent 4da78788c8
commit 0a2bd6a5a4
39 changed files with 1226 additions and 945 deletions
@@ -1,10 +1,6 @@
var PlayerID = -1;
globalThis.PlayerID = -1;
var API3 = (function() {
var m = {};
m.BaseAI = function(settings)
export function BaseAI(settings)
{
if (!settings)
return;
@@ -13,10 +9,10 @@ m.BaseAI = function(settings)
// played turn, in case you don't want the AI to play every turn.
this.turn = 0;
};
}
/** Return a simple object (using no classes etc) that will be serialized into saved games */
m.BaseAI.prototype.Serialize = function()
BaseAI.prototype.Serialize = function()
{
return {};
};
@@ -25,12 +21,12 @@ m.BaseAI.prototype.Serialize = function()
* Called after the constructor when loading a saved game, with 'data' being
* whatever Serialize() returned
*/
m.BaseAI.prototype.Deserialize = function(data, sharedScript)
BaseAI.prototype.Deserialize = function(data, sharedScript)
{
this.isDeserialized = true;
};
m.BaseAI.prototype.Init = function(state, playerID, sharedAI)
BaseAI.prototype.Init = function(state, playerID, sharedAI)
{
PlayerID = playerID;
@@ -46,11 +42,11 @@ m.BaseAI.prototype.Init = function(state, playerID, sharedAI)
};
/** AIs override this function */
m.BaseAI.prototype.CustomInit = function()
BaseAI.prototype.CustomInit = function()
{
};
m.BaseAI.prototype.HandleMessage = function(state, playerID, sharedAI)
BaseAI.prototype.HandleMessage = function(state, playerID, sharedAI)
{
PlayerID = playerID;
this.events = sharedAI.events;
@@ -65,16 +61,11 @@ m.BaseAI.prototype.HandleMessage = function(state, playerID, sharedAI)
};
/** AIs override this function */
m.BaseAI.prototype.OnUpdate = function()
BaseAI.prototype.OnUpdate = function()
{
};
m.BaseAI.prototype.chat = function(message)
BaseAI.prototype.chat = function(message)
{
Engine.PostCommand(PlayerID, { "type": "aichat", "message": message });
};
return m;
}());
@@ -1,10 +1,8 @@
API3 = function(m)
{
/**
* Provides a nicer syntax for defining classes,
* with support for OO-style inheritance.
*/
m.Class = function(data)
export function Class(data)
{
let ctor;
if (data._init)
@@ -19,8 +17,4 @@ m.Class = function(data)
ctor.prototype[key] = data[key];
return ctor;
};
return m;
}(API3);
}
@@ -1,8 +1,8 @@
API3 = function(m)
{
import { Class } from "simulation/ai/common-api/class.js";
import { VectorDistance } from "simulation/ai/common-api/utils.js";
// defines a template.
m.Template = m.Class({
export const Template = Class({
"_init": function(sharedAI, templateName, template)
{
@@ -557,8 +557,8 @@ m.Template = m.Class({
// defines an entity, with a super Template.
// also redefines several of the template functions where the only change is applying aura and tech modifications.
m.Entity = m.Class({
"_super": m.Template,
export const Entity = Class({
"_super": Template,
"_init": function(sharedAI, entity)
{
@@ -860,7 +860,7 @@ m.Entity = m.Class({
if (this.position() !== undefined)
{
let direction = [this.position()[0] - point[0], this.position()[1] - point[1]];
const norm = m.VectorDistance(point, this.position());
const norm = VectorDistance(point, this.position());
if (norm === 0)
direction = [1, 0];
else
@@ -1009,7 +1009,3 @@ m.Entity = m.Class({
return this;
}
});
return m;
}(API3);
@@ -1,7 +1,6 @@
API3 = function(m)
{
import { SquareVectorDistance } from "simulation/ai/common-api/utils.js";
m.EntityCollection = function(sharedAI, entities = new Map(), filters = [])
export function EntityCollection(sharedAI, entities = new Map(), filters = [])
{
this._ai = sharedAI;
this._entities = entities;
@@ -13,9 +12,9 @@ m.EntityCollection = function(sharedAI, entities = new Map(), filters = [])
Object.defineProperty(this, "length", { "get": () => this._entities.size });
this.frozen = false;
};
}
m.EntityCollection.prototype.Serialize = function()
EntityCollection.prototype.Serialize = function()
{
const filters = [];
for (const f of this._filters)
@@ -27,7 +26,7 @@ m.EntityCollection.prototype.Serialize = function()
};
};
m.EntityCollection.prototype.Deserialize = function(data, sharedAI)
EntityCollection.prototype.Deserialize = function(data, sharedAI)
{
this._ai = sharedAI;
for (const id of data.ents)
@@ -48,37 +47,37 @@ m.EntityCollection.prototype.Deserialize = function(data, sharedAI)
* this makes it easy to create entity collection that will auto-remove dead units
* but never add new ones.
*/
m.EntityCollection.prototype.freeze = function()
EntityCollection.prototype.freeze = function()
{
this.frozen = true;
};
m.EntityCollection.prototype.defreeze = function()
EntityCollection.prototype.defreeze = function()
{
this.frozen = false;
};
m.EntityCollection.prototype.toIdArray = function()
EntityCollection.prototype.toIdArray = function()
{
return Array.from(this._entities.keys());
};
m.EntityCollection.prototype.toEntityArray = function()
EntityCollection.prototype.toEntityArray = function()
{
return Array.from(this._entities.values());
};
m.EntityCollection.prototype.values = function()
EntityCollection.prototype.values = function()
{
return this._entities.values();
};
m.EntityCollection.prototype.toString = function()
EntityCollection.prototype.toString = function()
{
return "[EntityCollection " + this.toEntityArray().join(" ") + "]";
};
m.EntityCollection.prototype.filter = function(filter, thisp)
EntityCollection.prototype.filter = function(filter, thisp)
{
if (typeof filter === "function")
filter = { "func": filter, "dynamicProperties": [] };
@@ -88,19 +87,19 @@ m.EntityCollection.prototype.filter = function(filter, thisp)
if (filter.func.call(thisp, ent, id, this))
ret.set(id, ent);
return new m.EntityCollection(this._ai, ret, this._filters.concat([filter]));
return new EntityCollection(this._ai, ret, this._filters.concat([filter]));
};
/**
* Returns the (at most) n entities nearest to targetPos.
*/
m.EntityCollection.prototype.filterNearest = function(targetPos, n)
EntityCollection.prototype.filterNearest = function(targetPos, n)
{
// Compute the distance of each entity
const data = []; // [ [id, ent, distance], ... ]
for (const [id, ent] of this._entities)
if (ent.position())
data.push([id, ent, m.SquareVectorDistance(targetPos, ent.position())]);
data.push([id, ent, SquareVectorDistance(targetPos, ent.position())]);
// Sort by increasing distance
data.sort((a, b) => a[2] - b[2]);
@@ -115,10 +114,10 @@ m.EntityCollection.prototype.filterNearest = function(targetPos, n)
for (let i = 0; i < n; ++i)
ret.set(data[i][0], data[i][1]);
return new m.EntityCollection(this._ai, ret);
return new EntityCollection(this._ai, ret);
};
m.EntityCollection.prototype.filter_raw = function(callback, thisp)
EntityCollection.prototype.filter_raw = function(callback, thisp)
{
const ret = new Map();
for (const [id, ent] of this._entities)
@@ -127,22 +126,22 @@ m.EntityCollection.prototype.filter_raw = function(callback, thisp)
if (callback.call(thisp, val, id, this))
ret.set(id, ent);
}
return new m.EntityCollection(this._ai, ret);
return new EntityCollection(this._ai, ret);
};
m.EntityCollection.prototype.forEach = function(callback)
EntityCollection.prototype.forEach = function(callback)
{
for (const ent of this._entities.values())
callback(ent);
return this;
};
m.EntityCollection.prototype.hasEntities = function()
EntityCollection.prototype.hasEntities = function()
{
return this._entities.size !== 0;
};
m.EntityCollection.prototype.move = function(x, z, queued = false, pushFront = false)
EntityCollection.prototype.move = function(x, z, queued = false, pushFront = false)
{
Engine.PostCommand(PlayerID, {
"type": "walk",
@@ -155,7 +154,7 @@ m.EntityCollection.prototype.move = function(x, z, queued = false, pushFront = f
return this;
};
m.EntityCollection.prototype.moveToRange = function(x, z, min, max, queued = false, pushFront = false)
EntityCollection.prototype.moveToRange = function(x, z, min, max, queued = false, pushFront = false)
{
Engine.PostCommand(PlayerID, {
"type": "walk-to-range",
@@ -170,7 +169,7 @@ m.EntityCollection.prototype.moveToRange = function(x, z, min, max, queued = fal
return this;
};
m.EntityCollection.prototype.attackMove = function(x, z, targetClasses, allowCapture = true, queued = false, pushFront = false)
EntityCollection.prototype.attackMove = function(x, z, targetClasses, allowCapture = true, queued = false, pushFront = false)
{
Engine.PostCommand(PlayerID, {
"type": "attack-walk",
@@ -185,7 +184,7 @@ m.EntityCollection.prototype.attackMove = function(x, z, targetClasses, allowCap
return this;
};
m.EntityCollection.prototype.moveIndiv = function(x, z, queued = false, pushFront = false)
EntityCollection.prototype.moveIndiv = function(x, z, queued = false, pushFront = false)
{
for (const id of this._entities.keys())
Engine.PostCommand(PlayerID, {
@@ -199,7 +198,7 @@ m.EntityCollection.prototype.moveIndiv = function(x, z, queued = false, pushFron
return this;
};
m.EntityCollection.prototype.garrison = function(target, queued = false, pushFront = false)
EntityCollection.prototype.garrison = function(target, queued = false, pushFront = false)
{
Engine.PostCommand(PlayerID, {
"type": "garrison",
@@ -211,7 +210,7 @@ m.EntityCollection.prototype.garrison = function(target, queued = false, pushFro
return this;
};
m.EntityCollection.prototype.occupyTurret = function(target, queued = false, pushFront = false)
EntityCollection.prototype.occupyTurret = function(target, queued = false, pushFront = false)
{
Engine.PostCommand(PlayerID, {
"type": "occupy-turret",
@@ -223,13 +222,13 @@ m.EntityCollection.prototype.occupyTurret = function(target, queued = false, pus
return this;
};
m.EntityCollection.prototype.destroy = function()
EntityCollection.prototype.destroy = function()
{
Engine.PostCommand(PlayerID, { "type": "delete-entities", "entities": this.toIdArray() });
return this;
};
m.EntityCollection.prototype.attack = function(unitId, queued = false, pushFront = false)
EntityCollection.prototype.attack = function(unitId, queued = false, pushFront = false)
{
Engine.PostCommand(PlayerID, {
"type": "attack",
@@ -242,7 +241,7 @@ m.EntityCollection.prototype.attack = function(unitId, queued = false, pushFront
};
/** violent, aggressive, defensive, passive, standground */
m.EntityCollection.prototype.setStance = function(stance)
EntityCollection.prototype.setStance = function(stance)
{
Engine.PostCommand(PlayerID, {
"type": "stance",
@@ -253,7 +252,7 @@ m.EntityCollection.prototype.setStance = function(stance)
};
/** Returns the average position of all units */
m.EntityCollection.prototype.getCentrePosition = function()
EntityCollection.prototype.getCentrePosition = function()
{
const sumPos = [0, 0];
let count = 0;
@@ -274,7 +273,7 @@ m.EntityCollection.prototype.getCentrePosition = function()
* This might be faster for huge collections, but there's
* always a risk that it'll be unprecise.
*/
m.EntityCollection.prototype.getApproximatePosition = function(sample)
EntityCollection.prototype.getApproximatePosition = function(sample)
{
const sumPos = [0, 0];
let i = 0;
@@ -292,13 +291,13 @@ m.EntityCollection.prototype.getApproximatePosition = function(sample)
return i ? [sumPos[0]/i, sumPos[1]/i] : undefined;
};
m.EntityCollection.prototype.hasEntId = function(id)
EntityCollection.prototype.hasEntId = function(id)
{
return this._entities.has(id);
};
/** Removes an entity from the collection, returns true if the entity was a member, false otherwise */
m.EntityCollection.prototype.removeEnt = function(ent)
EntityCollection.prototype.removeEnt = function(ent)
{
if (!this._entities.has(ent.id()))
return false;
@@ -307,7 +306,7 @@ m.EntityCollection.prototype.removeEnt = function(ent)
};
/** Adds an entity to the collection, returns true if the entity was not member, false otherwise */
m.EntityCollection.prototype.addEnt = function(ent)
EntityCollection.prototype.addEnt = function(ent)
{
if (this._entities.has(ent.id()))
return false;
@@ -322,7 +321,7 @@ m.EntityCollection.prototype.addEnt = function(ent)
* If an entitycollection is frozen, it will never automatically add a unit.
* But can remove one.
*/
m.EntityCollection.prototype.updateEnt = function(ent, force)
EntityCollection.prototype.updateEnt = function(ent, force)
{
let passesFilters = true;
for (const filter of this._filters)
@@ -338,31 +337,27 @@ m.EntityCollection.prototype.updateEnt = function(ent, force)
return this.removeEnt(ent);
};
m.EntityCollection.prototype.registerUpdates = function()
EntityCollection.prototype.registerUpdates = function()
{
this._ai.registerUpdatingEntityCollection(this);
};
m.EntityCollection.prototype.unregister = function()
EntityCollection.prototype.unregister = function()
{
this._ai.removeUpdatingEntityCollection(this);
};
m.EntityCollection.prototype.dynamicProperties = function()
EntityCollection.prototype.dynamicProperties = function()
{
return this.dynamicProp;
};
m.EntityCollection.prototype.setUID = function(id)
EntityCollection.prototype.setUID = function(id)
{
this._UID = id;
};
m.EntityCollection.prototype.getUID = function()
EntityCollection.prototype.getUID = function()
{
return this._UID;
};
return m;
}(API3);
@@ -1,118 +1,182 @@
API3 = function(m)
export function byType(type)
{
m.Filters = {
"byType": type => ({
return {
"func": ent => ent.templateName() == type,
"dynamicProperties": []
}),
};
}
"byClass": cls => ({
export function byClass(cls)
{
return {
"func": ent => ent.hasClass(cls),
"dynamicProperties": []
}),
};
}
"byClasses": clsList => ({
export function byClasses(clsList)
{
return {
"func": ent => ent.hasClasses(clsList),
"dynamicProperties": []
}),
};
}
"byMetadata": (player, key, value) => ({
export function byMetadata(player, key, value)
{
return {
"func": ent => ent.getMetadata(player, key) == value,
"dynamicProperties": ['metadata.' + key]
}),
};
}
"byHasMetadata": (player, key) => ({
export function byHasMetadata(player, key)
{
return {
"func": ent => ent.getMetadata(player, key) !== undefined,
"dynamicProperties": ['metadata.' + key]
}),
};
}
"and": (filter1, filter2) => ({
export function and(filter1, filter2)
{
return {
"func": ent => filter1.func(ent) && filter2.func(ent),
"dynamicProperties": filter1.dynamicProperties.concat(filter2.dynamicProperties)
}),
};
}
"or": (filter1, filter2) => ({
export function or(filter1, filter2)
{
return {
"func": ent => filter1.func(ent) || filter2.func(ent),
"dynamicProperties": filter1.dynamicProperties.concat(filter2.dynamicProperties)
}),
};
}
"not": (filter) => ({
export function not(filter)
{
return {
"func": ent => !filter.func(ent),
"dynamicProperties": filter.dynamicProperties
}),
};
}
"byOwner": owner => ({
export function byOwner(owner)
{
return {
"func": ent => ent.owner() == owner,
"dynamicProperties": ['owner']
}),
};
}
"byNotOwner": owner => ({
export function byNotOwner(owner)
{
return {
"func": ent => ent.owner() != owner,
"dynamicProperties": ['owner']
}),
};
}
"byOwners": owners => ({
export function byOwners(owners)
{
return {
"func": ent => owners.some(owner => owner == ent.owner()),
"dynamicProperties": ['owner']
}),
};
}
"byCanGarrison": () => ({
export function byCanGarrison()
{
return {
"func": ent => ent.garrisonMax() > 0,
"dynamicProperties": []
}),
};
}
"byTrainingQueue": () => ({
export function byTrainingQueue()
{
return {
"func": ent => ent.trainingQueue(),
"dynamicProperties": ['trainingQueue']
}),
};
}
"byResearchAvailable": (gameState, civ) => ({
export function byResearchAvailable(gameState, civ)
{
return {
"func": ent => ent.researchableTechs(gameState, civ) !== undefined,
"dynamicProperties": []
}),
};
}
"byCanAttackClass": aClass => ({
export function byCanAttackClass(aClass)
{
return {
"func": ent => ent.canAttackClass(aClass),
"dynamicProperties": []
}),
};
}
"byCanAttackTarget": target => ({
export function byCanAttackTarget(target)
{
return {
"func": ent => ent.canAttackTarget(target),
"dynamicProperties": []
}),
};
}
"isGarrisoned": () => ({
export function isGarrisoned()
{
return {
"func": ent => ent.position() === undefined,
"dynamicProperties": []
}),
};
}
"isIdle": () => ({
export function isIdle()
{
return {
"func": ent => ent.isIdle(),
"dynamicProperties": ['idle']
}),
};
}
"isFoundation": () => ({
export function isFoundation()
{
return {
"func": ent => ent.foundationProgress() !== undefined,
"dynamicProperties": []
}),
};
}
"isBuilt": () => ({
export function isBuilt()
{
return {
"func": ent => ent.foundationProgress() === undefined,
"dynamicProperties": []
}),
};
}
"hasDefensiveFire": () => ({
export function hasDefensiveFire()
{
return {
"func": ent => ent.hasDefensiveFire(),
"dynamicProperties": []
}),
};
}
"isDropsite": resourceType => ({
export function isDropsite(resourceType)
{
return {
"func": ent => ent.isResourceDropsite(resourceType),
"dynamicProperties": []
}),
};
}
"isTreasure": () => ({
export function isTreasure()
{
return {
"func": ent => {
if (!ent.isTreasure())
return false;
@@ -124,9 +188,12 @@ m.Filters = {
template != "gaia/treasure/shipwreck";
},
"dynamicProperties": []
}),
};
}
"byResource": resourceType => ({
export function byResource(resourceType)
{
return {
"func": ent => {
if (!ent.resourceSupplyMax())
return false;
@@ -142,23 +209,24 @@ m.Filters = {
return resourceType == type.generic;
},
"dynamicProperties": []
}),
};
}
"isHuntable": () => ({
// Skip targets that are too hard to hunt and don't go for the fish! TODO: better accessibility checks
export function isHuntable()
{
// Skip targets that are too hard to hunt and don't go for the fish! TODO: better accessibility checks
return {
"func": ent => ent.hasClass("Animal") && ent.resourceSupplyMax() &&
ent.isHuntable() && !ent.hasClass("SeaCreature"),
ent.isHuntable() && !ent.hasClass("SeaCreature"),
"dynamicProperties": []
}),
};
}
"isFishable": () => ({
// temporarily do not fish moving fish (i.e. whales)
export function isFishable()
{
// temporarily do not fish moving fish (i.e. whales)
return {
"func": ent => !ent.get("UnitMotion") && ent.hasClass("SeaCreature") && ent.resourceSupplyMax(),
"dynamicProperties": []
})
};
return m;
}(API3);
};
}
@@ -1,15 +1,18 @@
API3 = function(m)
{
import { Template } from "simulation/ai/common-api/entity.js";
import * as filters from "simulation/ai/common-api/filters.js";
import { ResourcesManager } from "simulation/ai/common-api/resources.js";
import { Technology } from "simulation/ai/common-api/technology.js";
/**
* Provides an API for the rest of the AI scripts to query the world state at a
* higher level than the raw data.
*/
m.GameState = function() {
export function GameState()
{
this.ai = null; // must be updated by the AIs.
};
}
m.GameState.prototype.init = function(SharedScript, state, player)
GameState.prototype.init = function(SharedScript, state, player)
{
this.sharedScript = SharedScript;
this.EntCollecNames = SharedScript._entityCollectionsName;
@@ -63,7 +66,7 @@ m.GameState.prototype.init = function(SharedScript, state, player)
}));
};
m.GameState.prototype.update = function(SharedScript)
GameState.prototype.update = function(SharedScript)
{
this.timeElapsed = SharedScript.timeElapsed;
this.playerData = SharedScript.playersData[this.player];
@@ -71,19 +74,19 @@ m.GameState.prototype.update = function(SharedScript)
this.ceasefireTimeRemaining = SharedScript.ceasefireTimeRemaining;
};
m.GameState.prototype.updatingCollection = function(id, filter, parentCollection)
GameState.prototype.updatingCollection = function(id, filter, parentCollection)
{
const gid = "player-" + this.player + "-" + id; // automatically add the player ID
return this.updatingGlobalCollection(gid, filter, parentCollection);
};
m.GameState.prototype.destroyCollection = function(id)
GameState.prototype.destroyCollection = function(id)
{
const gid = "player-" + this.player + "-" + id; // automatically add the player ID
this.destroyGlobalCollection(gid);
};
m.GameState.prototype.updatingGlobalCollection = function(gid, filter, parentCollection)
GameState.prototype.updatingGlobalCollection = function(gid, filter, parentCollection)
{
if (this.EntCollecNames.has(gid))
return this.EntCollecNames.get(gid);
@@ -94,7 +97,7 @@ m.GameState.prototype.updatingGlobalCollection = function(gid, filter, parentCol
return collection;
};
m.GameState.prototype.destroyGlobalCollection = function(gid)
GameState.prototype.destroyGlobalCollection = function(gid)
{
if (!this.EntCollecNames.has(gid))
return;
@@ -106,51 +109,51 @@ m.GameState.prototype.destroyGlobalCollection = function(gid)
/**
* Reset the entities collections which depend on diplomacy
*/
m.GameState.prototype.resetOnDiplomacyChanged = function()
GameState.prototype.resetOnDiplomacyChanged = function()
{
for (const name of this.EntCollecNames.keys())
if (name.startsWith("player-" + this.player + "-diplo"))
this.destroyGlobalCollection(name);
};
m.GameState.prototype.getTimeElapsed = function()
GameState.prototype.getTimeElapsed = function()
{
return this.timeElapsed;
};
m.GameState.prototype.getBarterPrices = function()
GameState.prototype.getBarterPrices = function()
{
return this.playerData.barterPrices;
};
m.GameState.prototype.getVictoryConditions = function()
GameState.prototype.getVictoryConditions = function()
{
return this.victoryConditions;
};
m.GameState.prototype.getAlliedVictory = function()
GameState.prototype.getAlliedVictory = function()
{
return this.alliedVictory;
};
m.GameState.prototype.isCeasefireActive = function()
GameState.prototype.isCeasefireActive = function()
{
return this.ceasefireActive;
};
m.GameState.prototype.getTemplate = function(type)
GameState.prototype.getTemplate = function(type)
{
if (TechnologyTemplates.Has(type))
return new m.Technology(type);
return new Technology(type);
if (this.templates[type] === undefined)
this.sharedScript.GetTemplate(type);
return this.templates[type] ? new m.Template(this.sharedScript, type, this.templates[type]) : null;
return this.templates[type] ? new Template(this.sharedScript, type, this.templates[type]) : null;
};
/** Return the template of the structure built from this foundation */
m.GameState.prototype.getBuiltTemplate = function(foundationName)
GameState.prototype.getBuiltTemplate = function(foundationName)
{
if (!foundationName.startsWith("foundation|"))
{
@@ -160,17 +163,17 @@ m.GameState.prototype.getBuiltTemplate = function(foundationName)
return this.getTemplate(foundationName.substr(11));
};
m.GameState.prototype.applyCiv = function(str)
GameState.prototype.applyCiv = function(str)
{
return str.replace(/\{civ\}/g, this.playerData.civ);
};
m.GameState.prototype.getPlayerCiv = function(player)
GameState.prototype.getPlayerCiv = function(player)
{
return player !== undefined ? this.sharedScript.playersData[player].civ : this.playerData.civ;
};
m.GameState.prototype.currentPhase = function()
GameState.prototype.currentPhase = function()
{
for (let i = this.phases.length; i > 0; --i)
if (this.isResearched(this.phases[i-1].name))
@@ -178,17 +181,17 @@ m.GameState.prototype.currentPhase = function()
return 0;
};
m.GameState.prototype.getNumberOfPhases = function()
GameState.prototype.getNumberOfPhases = function()
{
return this.phases.length;
};
m.GameState.prototype.getPhaseName = function(i)
GameState.prototype.getPhaseName = function(i)
{
return this.phases[i-1] ? this.phases[i-1].name : undefined;
};
m.GameState.prototype.getPhaseEntityRequirements = function(i)
GameState.prototype.getPhaseEntityRequirements = function(i)
{
const entityReqs = [];
@@ -207,18 +210,18 @@ m.GameState.prototype.getPhaseEntityRequirements = function(i)
return entityReqs;
};
m.GameState.prototype.isResearched = function(template)
GameState.prototype.isResearched = function(template)
{
return this.playerData.researchedTechs.has(template);
};
m.GameState.prototype.isResearching = function(template)
GameState.prototype.isResearching = function(template)
{
return this.playerData.researchQueued.has(template);
};
/** this is an "in-absolute" check that doesn't check if we have a building to research from. */
m.GameState.prototype.canResearch = function(techTemplateName, noRequirementCheck)
GameState.prototype.canResearch = function(techTemplateName, noRequirementCheck)
{
if (this.playerData.disabledTechnologies[techTemplateName])
return false;
@@ -251,7 +254,7 @@ m.GameState.prototype.canResearch = function(techTemplateName, noRequirementChec
* Basically copies TechnologyManager, but compares against
* variables only available within the AI
*/
m.GameState.prototype.checkTechRequirements = function(reqs)
GameState.prototype.checkTechRequirements = function(reqs)
{
if (!reqs)
return false;
@@ -291,42 +294,42 @@ m.GameState.prototype.checkTechRequirements = function(reqs)
});
};
m.GameState.prototype.getPassabilityMap = function()
GameState.prototype.getPassabilityMap = function()
{
return this.sharedScript.passabilityMap;
};
m.GameState.prototype.getPassabilityClassMask = function(name)
GameState.prototype.getPassabilityClassMask = function(name)
{
if (!this.sharedScript.passabilityClasses[name])
error("Tried to use invalid passability class name '" + name + "'");
return this.sharedScript.passabilityClasses[name];
};
m.GameState.prototype.getResources = function()
GameState.prototype.getResources = function()
{
return new m.Resources(this.playerData.resourceCounts);
return new ResourcesManager(this.playerData.resourceCounts);
};
m.GameState.prototype.getPopulation = function()
GameState.prototype.getPopulation = function()
{
return this.playerData.popCount;
};
m.GameState.prototype.getPopulationLimit = function() {
GameState.prototype.getPopulationLimit = function() {
return this.playerData.popLimit;
};
m.GameState.prototype.getPopulationMax = function() {
GameState.prototype.getPopulationMax = function() {
return this.playerData.popMax;
};
m.GameState.prototype.getPlayerID = function()
GameState.prototype.getPlayerID = function()
{
return this.player;
};
m.GameState.prototype.hasAllies = function()
GameState.prototype.hasAllies = function()
{
for (const i in this.playerData.isAlly)
if (this.playerData.isAlly[i] && +i !== this.player &&
@@ -335,7 +338,7 @@ m.GameState.prototype.hasAllies = function()
return false;
};
m.GameState.prototype.hasEnemies = function()
GameState.prototype.hasEnemies = function()
{
for (const i in this.playerData.isEnemy)
if (this.playerData.isEnemy[i] && +i !== 0 &&
@@ -344,7 +347,7 @@ m.GameState.prototype.hasEnemies = function()
return false;
};
m.GameState.prototype.hasNeutrals = function()
GameState.prototype.hasNeutrals = function()
{
for (const i in this.playerData.isNeutral)
if (this.playerData.isNeutral[i] &&
@@ -353,28 +356,28 @@ m.GameState.prototype.hasNeutrals = function()
return false;
};
m.GameState.prototype.isPlayerNeutral = function(id)
GameState.prototype.isPlayerNeutral = function(id)
{
return this.playerData.isNeutral[id];
};
m.GameState.prototype.isPlayerAlly = function(id)
GameState.prototype.isPlayerAlly = function(id)
{
return this.playerData.isAlly[id];
};
m.GameState.prototype.isPlayerMutualAlly = function(id)
GameState.prototype.isPlayerMutualAlly = function(id)
{
return this.playerData.isMutualAlly[id];
};
m.GameState.prototype.isPlayerEnemy = function(id)
GameState.prototype.isPlayerEnemy = function(id)
{
return this.playerData.isEnemy[id];
};
/** Return the number of players currently enemies, not including gaia */
m.GameState.prototype.getNumPlayerEnemies = function()
GameState.prototype.getNumPlayerEnemies = function()
{
let num = 0;
for (let i = 1; i < this.playerData.isEnemy.length; ++i)
@@ -384,7 +387,7 @@ m.GameState.prototype.getNumPlayerEnemies = function()
return num;
};
m.GameState.prototype.getEnemies = function()
GameState.prototype.getEnemies = function()
{
const ret = [];
for (const i in this.playerData.isEnemy)
@@ -393,7 +396,7 @@ m.GameState.prototype.getEnemies = function()
return ret;
};
m.GameState.prototype.getNeutrals = function()
GameState.prototype.getNeutrals = function()
{
const ret = [];
for (const i in this.playerData.isNeutral)
@@ -402,7 +405,7 @@ m.GameState.prototype.getNeutrals = function()
return ret;
};
m.GameState.prototype.getAllies = function()
GameState.prototype.getAllies = function()
{
const ret = [];
for (const i in this.playerData.isAlly)
@@ -411,7 +414,7 @@ m.GameState.prototype.getAllies = function()
return ret;
};
m.GameState.prototype.getExclusiveAllies = function()
GameState.prototype.getExclusiveAllies = function()
{ // Player is not included
const ret = [];
for (const i in this.playerData.isAlly)
@@ -420,7 +423,7 @@ m.GameState.prototype.getExclusiveAllies = function()
return ret;
};
m.GameState.prototype.getMutualAllies = function()
GameState.prototype.getMutualAllies = function()
{
const ret = [];
for (const i in this.playerData.isMutualAlly)
@@ -430,165 +433,182 @@ m.GameState.prototype.getMutualAllies = function()
return ret;
};
m.GameState.prototype.isEntityAlly = function(ent)
GameState.prototype.isEntityAlly = function(ent)
{
if (!ent)
return false;
return this.playerData.isAlly[ent.owner()];
};
m.GameState.prototype.isEntityExclusiveAlly = function(ent)
GameState.prototype.isEntityExclusiveAlly = function(ent)
{
if (!ent)
return false;
return this.playerData.isAlly[ent.owner()] && ent.owner() !== this.player;
};
m.GameState.prototype.isEntityEnemy = function(ent)
GameState.prototype.isEntityEnemy = function(ent)
{
if (!ent)
return false;
return this.playerData.isEnemy[ent.owner()];
};
m.GameState.prototype.isEntityOwn = function(ent)
GameState.prototype.isEntityOwn = function(ent)
{
if (!ent)
return false;
return ent.owner() === this.player;
};
m.GameState.prototype.getEntityById = function(id)
GameState.prototype.getEntityById = function(id)
{
return this.entities._entities.get(+id);
};
m.GameState.prototype.getEntities = function(id)
GameState.prototype.getEntities = function(id)
{
if (id === undefined)
return this.entities;
return this.updatingGlobalCollection("player-" + id + "-entities", m.Filters.byOwner(id));
return this.updatingGlobalCollection("player-" + id + "-entities", filters.byOwner(id));
};
m.GameState.prototype.getStructures = function()
GameState.prototype.getStructures = function()
{
return this.updatingGlobalCollection("structures", m.Filters.byClass("Structure"), this.entities);
return this.updatingGlobalCollection("structures", filters.byClass("Structure"), this.entities);
};
m.GameState.prototype.getOwnEntities = function()
GameState.prototype.getOwnEntities = function()
{
return this.updatingGlobalCollection("player-" + this.player + "-entities", m.Filters.byOwner(this.player));
return this.updatingGlobalCollection("player-" + this.player + "-entities",
filters.byOwner(this.player));
};
m.GameState.prototype.getOwnStructures = function()
GameState.prototype.getOwnStructures = function()
{
return this.updatingGlobalCollection("player-" + this.player + "-structures", m.Filters.byClass("Structure"), this.getOwnEntities());
return this.updatingGlobalCollection("player-" + this.player + "-structures",
filters.byClass("Structure"), this.getOwnEntities());
};
m.GameState.prototype.getOwnUnits = function()
GameState.prototype.getOwnUnits = function()
{
return this.updatingGlobalCollection("player-" + this.player + "-units", m.Filters.byClass("Unit"), this.getOwnEntities());
return this.updatingGlobalCollection("player-" + this.player + "-units", filters.byClass("Unit"),
this.getOwnEntities());
};
m.GameState.prototype.getAllyEntities = function()
GameState.prototype.getAllyEntities = function()
{
return this.entities.filter(m.Filters.byOwners(this.getAllies()));
return this.entities.filter(filters.byOwners(this.getAllies()));
};
m.GameState.prototype.getExclusiveAllyEntities = function()
GameState.prototype.getExclusiveAllyEntities = function()
{
return this.entities.filter(m.Filters.byOwners(this.getExclusiveAllies()));
return this.entities.filter(filters.byOwners(this.getExclusiveAllies()));
};
m.GameState.prototype.getAllyStructures = function(allyID)
GameState.prototype.getAllyStructures = function(allyID)
{
if (allyID == undefined)
return this.updatingCollection("diplo-ally-structures", m.Filters.byOwners(this.getAllies()), this.getStructures());
{
return this.updatingCollection("diplo-ally-structures", filters.byOwners(this.getAllies()),
this.getStructures());
}
return this.updatingGlobalCollection("player-" + allyID + "-structures", m.Filters.byOwner(allyID), this.getStructures());
return this.updatingGlobalCollection("player-" + allyID + "-structures", filters.byOwner(allyID),
this.getStructures());
};
m.GameState.prototype.getNeutralStructures = function()
GameState.prototype.getNeutralStructures = function()
{
return this.getStructures().filter(m.Filters.byOwners(this.getNeutrals()));
return this.getStructures().filter(filters.byOwners(this.getNeutrals()));
};
m.GameState.prototype.getEnemyEntities = function()
GameState.prototype.getEnemyEntities = function()
{
return this.entities.filter(m.Filters.byOwners(this.getEnemies()));
return this.entities.filter(filters.byOwners(this.getEnemies()));
};
m.GameState.prototype.getEnemyStructures = function(enemyID)
GameState.prototype.getEnemyStructures = function(enemyID)
{
if (enemyID === undefined)
return this.updatingCollection("diplo-enemy-structures", m.Filters.byOwners(this.getEnemies()), this.getStructures());
{
return this.updatingCollection("diplo-enemy-structures", filters.byOwners(this.getEnemies()),
this.getStructures());
}
return this.updatingGlobalCollection("player-" + enemyID + "-structures", m.Filters.byOwner(enemyID), this.getStructures());
return this.updatingGlobalCollection("player-" + enemyID + "-structures", filters.byOwner(enemyID),
this.getStructures());
};
m.GameState.prototype.getEnemyUnits = function(enemyID)
GameState.prototype.getEnemyUnits = function(enemyID)
{
if (enemyID === undefined)
return this.getEnemyEntities().filter(m.Filters.byClass("Unit"));
return this.getEnemyEntities().filter(filters.byClass("Unit"));
return this.updatingGlobalCollection("player-" + enemyID + "-units", m.Filters.byClass("Unit"), this.getEntities(enemyID));
return this.updatingGlobalCollection("player-" + enemyID + "-units", filters.byClass("Unit"),
this.getEntities(enemyID));
};
/** if maintain is true, this will be stored. Otherwise it's one-shot. */
m.GameState.prototype.getOwnEntitiesByMetadata = function(key, value, maintain)
GameState.prototype.getOwnEntitiesByMetadata = function(key, value, maintain)
{
if (maintain)
return this.updatingCollection(key + "-" + value, m.Filters.byMetadata(this.player, key, value), this.getOwnEntities());
return this.getOwnEntities().filter(m.Filters.byMetadata(this.player, key, value));
{
return this.updatingCollection(key + "-" + value, filters.byMetadata(this.player, key, value),
this.getOwnEntities());
}
return this.getOwnEntities().filter(filters.byMetadata(this.player, key, value));
};
m.GameState.prototype.getOwnEntitiesByRole = function(role, maintain)
GameState.prototype.getOwnEntitiesByRole = function(role, maintain)
{
return this.getOwnEntitiesByMetadata("role", role, maintain);
};
m.GameState.prototype.getOwnEntitiesByType = function(type, maintain)
GameState.prototype.getOwnEntitiesByType = function(type, maintain)
{
const filter = m.Filters.byType(type);
const filter = filters.byType(type);
if (maintain)
return this.updatingCollection("type-" + type, filter, this.getOwnEntities());
return this.getOwnEntities().filter(filter);
};
m.GameState.prototype.getOwnEntitiesByClass = function(cls, maintain)
GameState.prototype.getOwnEntitiesByClass = function(cls, maintain)
{
const filter = m.Filters.byClass(cls);
const filter = filters.byClass(cls);
if (maintain)
return this.updatingCollection("class-" + cls, filter, this.getOwnEntities());
return this.getOwnEntities().filter(filter);
};
m.GameState.prototype.getOwnFoundationsByClass = function(cls, maintain)
GameState.prototype.getOwnFoundationsByClass = function(cls, maintain)
{
const filter = m.Filters.byClass(cls);
const filter = filters.byClass(cls);
if (maintain)
return this.updatingCollection("foundations-class-" + cls, filter, this.getOwnFoundations());
return this.getOwnFoundations().filter(filter);
};
m.GameState.prototype.getOwnTrainingFacilities = function()
GameState.prototype.getOwnTrainingFacilities = function()
{
return this.updatingGlobalCollection("player-" + this.player + "-training-facilities", m.Filters.byTrainingQueue(), this.getOwnEntities());
return this.updatingGlobalCollection("player-" + this.player + "-training-facilities",
filters.byTrainingQueue(), this.getOwnEntities());
};
m.GameState.prototype.getOwnResearchFacilities = function()
GameState.prototype.getOwnResearchFacilities = function()
{
return this.updatingGlobalCollection("player-" + this.player + "-research-facilities", m.Filters.byResearchAvailable(this, this.playerData.civ), this.getOwnEntities());
return this.updatingGlobalCollection("player-" + this.player + "-research-facilities",
filters.byResearchAvailable(this, this.playerData.civ), this.getOwnEntities());
};
m.GameState.prototype.countEntitiesByType = function(type, maintain)
GameState.prototype.countEntitiesByType = function(type, maintain)
{
return this.getOwnEntitiesByType(type, maintain).length;
};
m.GameState.prototype.countEntitiesAndQueuedByType = function(type, maintain)
GameState.prototype.countEntitiesAndQueuedByType = function(type, maintain)
{
const template = this.getTemplate(type);
if (!template)
@@ -615,12 +635,15 @@ m.GameState.prototype.countEntitiesAndQueuedByType = function(type, maintain)
return count;
};
m.GameState.prototype.countFoundationsByType = function(type, maintain)
GameState.prototype.countFoundationsByType = function(type, maintain)
{
const foundationType = "foundation|" + type;
if (maintain)
return this.updatingCollection("foundation-type-" + type, m.Filters.byType(foundationType), this.getOwnFoundations()).length;
{
return this.updatingCollection("foundation-type-" + type, filters.byType(foundationType),
this.getOwnFoundations()).length;
}
let count = 0;
this.getOwnStructures().forEach(function(ent) {
@@ -630,12 +653,12 @@ m.GameState.prototype.countFoundationsByType = function(type, maintain)
return count;
};
m.GameState.prototype.countOwnEntitiesByRole = function(role)
GameState.prototype.countOwnEntitiesByRole = function(role)
{
return this.getOwnEntitiesByRole(role, "true").length;
};
m.GameState.prototype.countOwnEntitiesAndQueuedWithRole = function(role)
GameState.prototype.countOwnEntitiesAndQueuedWithRole = function(role)
{
let count = this.countOwnEntitiesByRole(role);
@@ -648,7 +671,7 @@ m.GameState.prototype.countOwnEntitiesAndQueuedWithRole = function(role)
return count;
};
m.GameState.prototype.countOwnQueuedEntitiesWithMetadata = function(data, value)
GameState.prototype.countOwnQueuedEntitiesWithMetadata = function(data, value)
{
// Count entities in building production queues
let count = 0;
@@ -660,42 +683,47 @@ m.GameState.prototype.countOwnQueuedEntitiesWithMetadata = function(data, value)
return count;
};
m.GameState.prototype.getOwnFoundations = function()
GameState.prototype.getOwnFoundations = function()
{
return this.updatingGlobalCollection("player-" + this.player + "-foundations", m.Filters.isFoundation(), this.getOwnStructures());
return this.updatingGlobalCollection("player-" + this.player + "-foundations",
filters.isFoundation(), this.getOwnStructures());
};
m.GameState.prototype.getOwnDropsites = function(resource)
GameState.prototype.getOwnDropsites = function(resource)
{
if (resource)
return this.updatingCollection("ownDropsite-" + resource, m.Filters.isDropsite(resource), this.getOwnEntities());
return this.updatingCollection("ownDropsite-all", m.Filters.isDropsite(), this.getOwnEntities());
{
return this.updatingCollection("ownDropsite-" + resource, filters.isDropsite(resource),
this.getOwnEntities());
}
return this.updatingCollection("ownDropsite-all", filters.isDropsite(), this.getOwnEntities());
};
m.GameState.prototype.getAnyDropsites = function(resource)
GameState.prototype.getAnyDropsites = function(resource)
{
if (resource)
return this.updatingGlobalCollection("anyDropsite-" + resource, m.Filters.isDropsite(resource), this.getEntities());
return this.updatingGlobalCollection("anyDropsite-all", m.Filters.isDropsite(), this.getEntities());
return this.updatingGlobalCollection("anyDropsite-" + resource, filters.isDropsite(resource), this.getEntities());
return this.updatingGlobalCollection("anyDropsite-all", filters.isDropsite(), this.getEntities());
};
m.GameState.prototype.getResourceSupplies = function(resource)
GameState.prototype.getResourceSupplies = function(resource)
{
return this.updatingGlobalCollection("resource-" + resource, m.Filters.byResource(resource), this.getEntities());
return this.updatingGlobalCollection("resource-" + resource, filters.byResource(resource),
this.getEntities());
};
m.GameState.prototype.getHuntableSupplies = function()
GameState.prototype.getHuntableSupplies = function()
{
return this.updatingGlobalCollection("resource-hunt", m.Filters.isHuntable(), this.getEntities());
return this.updatingGlobalCollection("resource-hunt", filters.isHuntable(), this.getEntities());
};
m.GameState.prototype.getFishableSupplies = function()
GameState.prototype.getFishableSupplies = function()
{
return this.updatingGlobalCollection("resource-fish", m.Filters.isFishable(), this.getEntities());
return this.updatingGlobalCollection("resource-fish", filters.isFishable(), this.getEntities());
};
/** This returns only units from buildings. */
m.GameState.prototype.findTrainableUnits = function(classes, anticlasses)
GameState.prototype.findTrainableUnits = function(classes, anticlasses)
{
const allTrainable = [];
const civ = this.playerData.civ;
@@ -737,7 +765,7 @@ m.GameState.prototype.findTrainableUnits = function(classes, anticlasses)
* Does not factor cost.
* If there are pairs, both techs are returned.
*/
m.GameState.prototype.findAvailableTech = function()
GameState.prototype.findAvailableTech = function()
{
const allResearchable = [];
const civ = this.playerData.civ;
@@ -776,7 +804,7 @@ m.GameState.prototype.findAvailableTech = function()
/**
* Return true if we have a building able to train that template
*/
m.GameState.prototype.hasTrainer = function(template)
GameState.prototype.hasTrainer = function(template)
{
const civ = this.playerData.civ;
for (const ent of this.getOwnTrainingFacilities().values())
@@ -791,7 +819,7 @@ m.GameState.prototype.hasTrainer = function(template)
/**
* Find buildings able to train that template.
*/
m.GameState.prototype.findTrainers = function(template)
GameState.prototype.findTrainers = function(template)
{
const civ = this.playerData.civ;
return this.getOwnTrainingFacilities().filter(function(ent) {
@@ -803,7 +831,7 @@ m.GameState.prototype.findTrainers = function(template)
/**
* Get any unit that is capable of constructing the given building type.
*/
m.GameState.prototype.findBuilder = function(template)
GameState.prototype.findBuilder = function(template)
{
const civ = this.getPlayerCiv();
for (const ent of this.getOwnUnits().values())
@@ -816,7 +844,7 @@ m.GameState.prototype.findBuilder = function(template)
};
/** Return true if one of our buildings is capable of researching the given tech */
m.GameState.prototype.hasResearchers = function(templateName, noRequirementCheck)
GameState.prototype.hasResearchers = function(templateName, noRequirementCheck)
{
// let's check we can research the tech.
if (!this.canResearch(templateName, noRequirementCheck))
@@ -849,7 +877,7 @@ m.GameState.prototype.hasResearchers = function(templateName, noRequirementCheck
};
/** Find buildings that are capable of researching the given tech */
m.GameState.prototype.findResearchers = function(templateName, noRequirementCheck)
GameState.prototype.findResearchers = function(templateName, noRequirementCheck)
{
// let's check we can research the tech.
if (!this.canResearch(templateName, noRequirementCheck))
@@ -877,29 +905,29 @@ m.GameState.prototype.findResearchers = function(templateName, noRequirementChec
});
};
m.GameState.prototype.getEntityLimits = function()
GameState.prototype.getEntityLimits = function()
{
return this.playerData.entityLimits;
};
m.GameState.prototype.getEntityMatchCounts = function()
GameState.prototype.getEntityMatchCounts = function()
{
return this.playerData.matchEntityCounts;
};
m.GameState.prototype.getEntityCounts = function()
GameState.prototype.getEntityCounts = function()
{
return this.playerData.entityCounts;
};
m.GameState.prototype.isTemplateAvailable = function(templateName)
GameState.prototype.isTemplateAvailable = function(templateName)
{
if (this.templates[templateName] === undefined)
this.sharedScript.GetTemplate(templateName);
return this.templates[templateName] && !this.isTemplateDisabled(templateName);
};
m.GameState.prototype.isTemplateDisabled = function(templateName)
GameState.prototype.isTemplateDisabled = function(templateName)
{
if (!this.playerData.disabledTemplates[templateName])
return false;
@@ -907,7 +935,7 @@ m.GameState.prototype.isTemplateDisabled = function(templateName)
};
/** Checks whether the maximum number of buildings have been constructed for a certain catergory */
m.GameState.prototype.isEntityLimitReached = function(category)
GameState.prototype.isEntityLimitReached = function(category)
{
if (this.playerData.entityLimits[category] === undefined ||
this.playerData.entityCounts[category] === undefined)
@@ -915,7 +943,7 @@ m.GameState.prototype.isEntityLimitReached = function(category)
return this.playerData.entityCounts[category] >= this.playerData.entityLimits[category];
};
m.GameState.prototype.getTraderTemplatesGains = function()
GameState.prototype.getTraderTemplatesGains = function()
{
const shipMechantTemplateName = this.applyCiv("units/{civ}/ship_merchant");
const supportTraderTemplateName = this.applyCiv("units/{civ}/support_trader");
@@ -929,8 +957,3 @@ m.GameState.prototype.getTraderTemplatesGains = function()
ret.navalGainMultiplier = norm * shipMerchantTemplate.gainMultiplier();
return ret;
};
return m;
}(API3);
@@ -1,12 +1,8 @@
API3 = function(m)
{
/**
* The map module.
* Copied with changes from QuantumState's original for qBot, it's a component for storing 8 bit values.
*/
// The function needs to be named too because of the copyConstructor functionality
m.Map = function Map(sharedScript, type, originalMap, actualCopy)
export function InfoMap(sharedScript, type, originalMap, actualCopy)
{
// get the correct dimensions according to the map type
const map = type == "territory" || type == "resource" ? sharedScript.territoryMap : sharedScript.passabilityMap;
@@ -31,19 +27,19 @@ m.Map = function Map(sharedScript, type, originalMap, actualCopy)
this.map = originalMap;
else
this.map = new Uint8Array(this.length);
};
}
m.Map.prototype.setMaxVal = function(val)
InfoMap.prototype.setMaxVal = function(val)
{
this.maxVal = val;
};
m.Map.prototype.gamePosToMapPos = function(p)
InfoMap.prototype.gamePosToMapPos = function(p)
{
return [Math.floor(p[0]/this.cellSize), Math.floor(p[1]/this.cellSize)];
};
m.Map.prototype.point = function(p)
InfoMap.prototype.point = function(p)
{
const q = this.gamePosToMapPos(p);
q[0] = q[0] >= this.width ? this.width-1 : q[0] < 0 ? 0 : q[0];
@@ -51,7 +47,7 @@ m.Map.prototype.point = function(p)
return this.map[q[0] + this.width * q[1]];
};
m.Map.prototype.runLoop = function(x0, x1, y0, y1, cx, cy, maxDist2, func)
InfoMap.prototype.runLoop = function(x0, x1, y0, y1, cx, cy, maxDist2, func)
{
for (let y = y0; y < y1; ++y)
{
@@ -69,7 +65,7 @@ m.Map.prototype.runLoop = function(x0, x1, y0, y1, cx, cy, maxDist2, func)
}
};
m.Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type = "linear")
InfoMap.prototype.addInfluence = function(cx, cy, maxDist, strength, type = "linear")
{
strength = strength ? strength : maxDist;
@@ -94,7 +90,7 @@ m.Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type = "linea
};
m.Map.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type = "constant")
InfoMap.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type = "constant")
{
strength = strength ? +strength : +maxDist;
@@ -119,20 +115,20 @@ m.Map.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type = "
};
/** add to current map by the parameter map pixelwise */
m.Map.prototype.add = function(map)
InfoMap.prototype.add = function(map)
{
for (let i = 0; i < this.length; ++i)
this.set(i, this.map[i] + map.map[i]);
};
/** Set the value taking overflow into account */
m.Map.prototype.set = function(i, value)
InfoMap.prototype.set = function(i, value)
{
this.map[i] = value < 0 ? 0 : value > this.maxVal ? this.maxVal : value;
};
/** Find the best non-obstructed tile */
m.Map.prototype.findBestTile = function(radius, obstruction)
InfoMap.prototype.findBestTile = function(radius, obstruction)
{
let bestIdx;
let bestVal = 0;
@@ -151,7 +147,7 @@ m.Map.prototype.findBestTile = function(radius, obstruction)
};
/** return any non obstructed (small) tile inside the (big) tile i from obstruction map */
m.Map.prototype.getNonObstructedTile = function(i, radius, obstruction)
InfoMap.prototype.getNonObstructedTile = function(i, radius, obstruction)
{
const ratio = this.cellSize / obstruction.cellSize;
const ix = (i % this.width) * ratio;
@@ -178,7 +174,7 @@ m.Map.prototype.getNonObstructedTile = function(i, radius, obstruction)
};
/** return true if the area centered on tile kx-ky and with radius is obstructed */
m.Map.prototype.isObstructedTile = function(kx, ky, radius)
InfoMap.prototype.isObstructedTile = function(kx, ky, radius)
{
const w = this.width;
if (kx < radius || kx >= w - radius || ky < radius || ky >= w - radius || this.map[kx+ky*w] == 0)
@@ -210,11 +206,7 @@ m.Map.prototype.isObstructedTile = function(kx, ky, radius)
return null;
};
m.Map.prototype.dumpIm = function(name = "default.png", threshold = this.maxVal)
InfoMap.prototype.dumpIm = function(name = "default.png", threshold = this.maxVal)
{
Engine.DumpImage(name, this.map, this.width, this.height, threshold);
};
return m;
}(API3);
@@ -1,24 +1,21 @@
Resources = new Resources();
API3 = function(m)
{
m.Resources = function(amounts = {}, population = 0)
export function ResourcesManager(amounts = {}, population = 0)
{
for (const key of Resources.GetCodes())
this[key] = amounts[key] || 0;
this.population = population > 0 ? population : 0;
};
}
m.Resources.prototype.reset = function()
ResourcesManager.prototype.reset = function()
{
for (const key of Resources.GetCodes())
this[key] = 0;
this.population = 0;
};
m.Resources.prototype.canAfford = function(that)
ResourcesManager.prototype.canAfford = function(that)
{
for (const key of Resources.GetCodes())
if (this[key] < that[key])
@@ -26,28 +23,28 @@ m.Resources.prototype.canAfford = function(that)
return true;
};
m.Resources.prototype.add = function(that)
ResourcesManager.prototype.add = function(that)
{
for (const key of Resources.GetCodes())
this[key] += that[key];
this.population += that.population;
};
m.Resources.prototype.subtract = function(that)
ResourcesManager.prototype.subtract = function(that)
{
for (const key of Resources.GetCodes())
this[key] -= that[key];
this.population += that.population;
};
m.Resources.prototype.multiply = function(n)
ResourcesManager.prototype.multiply = function(n)
{
for (const key of Resources.GetCodes())
this[key] *= n;
this.population *= n;
};
m.Resources.prototype.Serialize = function()
ResourcesManager.prototype.Serialize = function()
{
const amounts = {};
for (const key of Resources.GetCodes())
@@ -55,13 +52,9 @@ m.Resources.prototype.Serialize = function()
return { "amounts": amounts, "population": this.population };
};
m.Resources.prototype.Deserialize = function(data)
ResourcesManager.prototype.Deserialize = function(data)
{
for (const key in data.amounts)
this[key] = data.amounts[key];
this.population = data.population;
};
return m;
}(API3);
@@ -1,8 +1,11 @@
API3 = function(m)
{
import { Entity } from "simulation/ai/common-api/entity.js";
import { EntityCollection } from "simulation/ai/common-api/entitycollection.js";
import { GameState } from "simulation/ai/common-api/gamestate.js";
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 */
m.SharedScript = function(settings)
export function SharedScript(settings)
{
if (!settings)
return;
@@ -22,10 +25,10 @@ m.SharedScript = function(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 */
m.SharedScript.prototype.Serialize = function()
SharedScript.prototype.Serialize = function()
{
return {
"players": this._players,
@@ -39,7 +42,7 @@ m.SharedScript.prototype.Serialize = function()
* Called after the constructor when loading a saved game, with 'data' being
* whatever Serialize() returned
*/
m.SharedScript.prototype.Deserialize = function(data)
SharedScript.prototype.Deserialize = function(data)
{
this._players = data.players;
this._templatesModifications = data.templatesModifications;
@@ -49,7 +52,7 @@ m.SharedScript.prototype.Deserialize = function(data)
this.isDeserialized = true;
};
m.SharedScript.prototype.GetTemplate = function(name)
SharedScript.prototype.GetTemplate = function(name)
{
if (this._templates[name] === undefined)
this._templates[name] = Engine.GetTemplate(name) || null;
@@ -62,7 +65,7 @@ m.SharedScript.prototype.GetTemplate = function(name)
* 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.
*/
m.SharedScript.prototype.init = function(state, deserialization)
SharedScript.prototype.init = function(state, deserialization)
{
if (!deserialization)
this._entitiesModifications = new Map();
@@ -105,14 +108,14 @@ m.SharedScript.prototype.init = function(state, deserialization)
this._entities = new Map();
if (state.entities)
for (const id in state.entities)
this._entities.set(+id, new m.Entity(this, state.entities[id]));
this._entities.set(+id, new Entity(this, state.entities[id]));
// entity collection updated on create/destroy event.
this.entities = new m.EntityCollection(this, this._entities);
this.entities = new EntityCollection(this, this._entities);
// create the terrain analyzer
this.terrainAnalyzer = new m.TerrainAnalysis();
this.terrainAnalyzer = new TerrainAnalysis();
this.terrainAnalyzer.init(this, state);
this.accessibility = new m.Accessibility();
this.accessibility = new Accessibility();
this.accessibility.init(state, this.terrainAnalyzer);
// Resource types: ignore = not used for resource maps
@@ -130,7 +133,7 @@ m.SharedScript.prototype.init = function(state, deserialization)
this.gameState = {};
for (const player of this._players)
{
this.gameState[player] = new m.GameState();
this.gameState[player] = new GameState();
this.gameState[player].init(this, state, player);
}
};
@@ -139,7 +142,7 @@ m.SharedScript.prototype.init = function(state, deserialization)
* General update of the shared script, before each AI's update
* applies entity deltas, and each gamestate.
*/
m.SharedScript.prototype.onUpdate = function(state)
SharedScript.prototype.onUpdate = function(state)
{
if (this.isDeserialized)
{
@@ -176,7 +179,7 @@ m.SharedScript.prototype.onUpdate = function(state)
Engine.ProfileStop();
};
m.SharedScript.prototype.ApplyEntitiesDelta = function(state)
SharedScript.prototype.ApplyEntitiesDelta = function(state)
{
Engine.ProfileStart("Shared ApplyEntitiesDelta");
@@ -190,7 +193,7 @@ m.SharedScript.prototype.ApplyEntitiesDelta = function(state)
if (!state.entities[evt.entity])
continue; // Sometimes there are things like foundations which get destroyed too fast
const entity = new m.Entity(this, state.entities[evt.entity]);
const entity = new Entity(this, state.entities[evt.entity]);
this._entities.set(evt.entity, entity);
this.entities.addEnt(entity);
@@ -285,7 +288,7 @@ m.SharedScript.prototype.ApplyEntitiesDelta = function(state)
Engine.ProfileStop();
};
m.SharedScript.prototype.ApplyTemplatesDelta = function(state)
SharedScript.prototype.ApplyTemplatesDelta = function(state)
{
Engine.ProfileStart("Shared ApplyTemplatesDelta");
@@ -307,7 +310,7 @@ m.SharedScript.prototype.ApplyTemplatesDelta = function(state)
Engine.ProfileStop();
};
m.SharedScript.prototype.registerUpdatingEntityCollection = function(entCollection)
SharedScript.prototype.registerUpdatingEntityCollection = function(entCollection)
{
entCollection.setUID(this._entityCollectionsUID);
this._entityCollections.set(this._entityCollectionsUID, entCollection);
@@ -320,7 +323,7 @@ m.SharedScript.prototype.registerUpdatingEntityCollection = function(entCollecti
this._entityCollectionsUID++;
};
m.SharedScript.prototype.removeUpdatingEntityCollection = function(entCollection)
SharedScript.prototype.removeUpdatingEntityCollection = function(entCollection)
{
const uid = entCollection.getUID();
@@ -332,7 +335,7 @@ m.SharedScript.prototype.removeUpdatingEntityCollection = function(entCollection
this._entityCollectionsByDynProp[prop].delete(uid);
};
m.SharedScript.prototype.updateEntityCollections = function(property, ent)
SharedScript.prototype.updateEntityCollections = function(property, ent)
{
if (this._entityCollectionsByDynProp[property] === undefined)
return;
@@ -341,7 +344,7 @@ m.SharedScript.prototype.updateEntityCollections = function(property, ent)
entCol.updateEnt(ent);
};
m.SharedScript.prototype.setMetadata = function(player, ent, key, value)
SharedScript.prototype.setMetadata = function(player, ent, key, value)
{
let metadata = this._entityMetadata[player][ent.id()];
if (!metadata)
@@ -355,12 +358,12 @@ m.SharedScript.prototype.setMetadata = function(player, ent, key, value)
this.updateEntityCollections('metadata.' + key, ent);
};
m.SharedScript.prototype.getMetadata = function(player, ent, key)
SharedScript.prototype.getMetadata = function(player, ent, key)
{
return this._entityMetadata[player][ent.id()]?.[key];
};
m.SharedScript.prototype.deleteMetadata = function(player, ent, key)
SharedScript.prototype.deleteMetadata = function(player, ent, key)
{
const metadata = this._entityMetadata[player][ent.id()];
@@ -373,7 +376,7 @@ m.SharedScript.prototype.deleteMetadata = function(player, ent, key)
return true;
};
m.copyPrototype = function(descendant, parent)
export function copyPrototype(descendant, parent)
{
const sConstructor = parent.toString();
const aMatch = sConstructor.match(/\s*function (.*)\(/);
@@ -383,10 +386,10 @@ m.copyPrototype = function(descendant, parent)
for (const p in parent.prototype)
descendant.prototype[p] = parent.prototype[p];
};
}
/** creates a map of resource density */
m.SharedScript.prototype.createResourceMaps = function()
SharedScript.prototype.createResourceMaps = function()
{
for (const resource of Resources.GetCodes())
{
@@ -395,8 +398,8 @@ m.SharedScript.prototype.createResourceMaps = function()
continue;
// We're creating them 8-bit. Things could go above 255 if there are really tons of resources
// But at that point the precision is not really important anyway. And it saves memory.
this.resourceMaps[resource] = new m.Map(this, "resource");
this.ccResourceMaps[resource] = new m.Map(this, "resource");
this.resourceMaps[resource] = new InfoMap(this, "resource");
this.ccResourceMaps[resource] = new InfoMap(this, "resource");
}
for (const ent of this._entities.values())
this.addEntityToResourceMap(ent);
@@ -405,7 +408,7 @@ m.SharedScript.prototype.createResourceMaps = function()
/**
* @param {Object} events - The events from a turn.
*/
m.SharedScript.prototype.updateResourceMaps = function(events)
SharedScript.prototype.updateResourceMaps = function(events)
{
for (const e of events.Destroy)
if (e.entityObj)
@@ -419,7 +422,7 @@ m.SharedScript.prototype.updateResourceMaps = function(events)
/**
* @param {entity} entity - The entity to add to the resource map.
*/
m.SharedScript.prototype.addEntityToResourceMap = function(entity)
SharedScript.prototype.addEntityToResourceMap = function(entity)
{
this.changeEntityInResourceMapHelper(entity, 1);
};
@@ -427,7 +430,7 @@ m.SharedScript.prototype.addEntityToResourceMap = function(entity)
/**
* @param {entity} entity - The entity to remove from the resource map.
*/
m.SharedScript.prototype.removeEntityFromResourceMap = function(entity)
SharedScript.prototype.removeEntityFromResourceMap = function(entity)
{
this.changeEntityInResourceMapHelper(entity, -1);
};
@@ -435,7 +438,7 @@ m.SharedScript.prototype.removeEntityFromResourceMap = function(entity)
/**
* @param {entity} ent - The entity to add to the resource map.
*/
m.SharedScript.prototype.changeEntityInResourceMapHelper = function(ent, multiplication = 1)
SharedScript.prototype.changeEntityInResourceMapHelper = function(ent, multiplication = 1)
{
if (!ent)
return;
@@ -454,8 +457,3 @@ m.SharedScript.prototype.changeEntityInResourceMapHelper = function(ent, multipl
this.resourceMaps[resource].addInfluence(x, y, this.influenceRadius[grp] / cellSize, strength / 2);
this.ccResourceMaps[resource].addInfluence(x, y, this.ccInfluenceRadius[grp] / cellSize, strength, "constant");
};
return m;
}(API3);
@@ -1,10 +1,7 @@
LoadModificationTemplates();
API3 = function(m)
{
/** Wrapper around a technology template */
m.Technology = function(templateName)
export function Technology(templateName)
{
this._templateName = templateName;
const template = TechnologyTemplates.Get(templateName);
@@ -20,10 +17,10 @@ m.Technology = function(templateName)
// check if it only defines a pair:
this._definesPair = template.top !== undefined;
this._template = template;
};
}
/** returns generic, or specific if civ provided. */
m.Technology.prototype.name = function(civ)
Technology.prototype.name = function(civ)
{
if (civ === undefined)
return this._template.genericName;
@@ -33,38 +30,38 @@ m.Technology.prototype.name = function(civ)
return this._template.specificName[civ];
};
m.Technology.prototype.pairDef = function()
Technology.prototype.pairDef = function()
{
return this._definesPair;
};
/** in case this defines a pair only, returns the two paired technologies. */
m.Technology.prototype.getPairedTechs = function()
Technology.prototype.getPairedTechs = function()
{
if (!this._definesPair)
return undefined;
return [
new m.Technology(this._template.top),
new m.Technology(this._template.bottom)
new Technology(this._template.top),
new Technology(this._template.bottom)
];
};
m.Technology.prototype.pair = function()
Technology.prototype.pair = function()
{
if (!this._isPair)
return undefined;
return this._template.pair;
};
m.Technology.prototype.pairedWith = function()
Technology.prototype.pairedWith = function()
{
if (!this._isPair)
return undefined;
return this._pairedWith;
};
m.Technology.prototype.cost = function(researcher)
Technology.prototype.cost = function(researcher)
{
if (!this._template.cost)
return undefined;
@@ -78,7 +75,7 @@ m.Technology.prototype.cost = function(researcher)
return cost;
};
m.Technology.prototype.costSum = function(researcher)
Technology.prototype.costSum = function(researcher)
{
const cost = this.cost(researcher);
if (!cost)
@@ -89,49 +86,45 @@ m.Technology.prototype.costSum = function(researcher)
return ret;
};
m.Technology.prototype.researchTime = function()
Technology.prototype.researchTime = function()
{
return this._template.researchTime || 0;
};
m.Technology.prototype.requirements = function(civ)
Technology.prototype.requirements = function(civ)
{
return DeriveTechnologyRequirements(this._template, civ);
};
m.Technology.prototype.autoResearch = function()
Technology.prototype.autoResearch = function()
{
if (!this._template.autoResearch)
return undefined;
return this._template.autoResearch;
};
m.Technology.prototype.supersedes = function()
Technology.prototype.supersedes = function()
{
if (!this._template.supersedes)
return undefined;
return this._template.supersedes;
};
m.Technology.prototype.modifications = function()
Technology.prototype.modifications = function()
{
if (!this._template.modifications)
return undefined;
return this._template.modifications;
};
m.Technology.prototype.affects = function()
Technology.prototype.affects = function()
{
if (!this._template.affects)
return undefined;
return this._template.affects;
};
m.Technology.prototype.isAffected = function(classes)
Technology.prototype.isAffected = function(classes)
{
return this._template.affects && this._template.affects.some(affect => MatchesClassList(classes, affect));
};
return m;
}(API3);
@@ -1,5 +1,5 @@
API3 = function(m)
{
import { InfoMap } from "simulation/ai/common-api/map-module.js";
import { copyPrototype } from "simulation/ai/common-api/shared.js";
/**
* TerrainAnalysis, inheriting from the Map Component.
@@ -11,27 +11,27 @@ API3 = function(m)
* This is intended for use with 8 bit maps for reduced memory usage.
* Upgraded from QuantumState's original TerrainAnalysis for qBot.
*/
m.TerrainAnalysis = function()
export function TerrainAnalysis()
{
};
}
m.copyPrototype(m.TerrainAnalysis, m.Map);
copyPrototype(TerrainAnalysis, InfoMap);
m.TerrainAnalysis.prototype.IMPASSABLE = 0;
TerrainAnalysis.prototype.IMPASSABLE = 0;
/**
* non-passable by land units
*/
m.TerrainAnalysis.prototype.DEEP_WATER = 200;
TerrainAnalysis.prototype.DEEP_WATER = 200;
/**
* passable by land units and water units
*/
m.TerrainAnalysis.prototype.SHALLOW_WATER = 201;
TerrainAnalysis.prototype.SHALLOW_WATER = 201;
/**
* passable by land units
*/
m.TerrainAnalysis.prototype.LAND = 255;
TerrainAnalysis.prototype.LAND = 255;
m.TerrainAnalysis.prototype.init = function(sharedScript, rawState)
TerrainAnalysis.prototype.init = function(sharedScript, rawState)
{
const passabilityMap = rawState.passabilityMap;
this.width = passabilityMap.width;
@@ -54,7 +54,7 @@ m.TerrainAnalysis.prototype.init = function(sharedScript, rawState)
obstructionTiles[i] = this.SHALLOW_WATER;
}
this.Map(rawState, "passability", obstructionTiles);
this.InfoMap(rawState, "passability", obstructionTiles);
};
/**
@@ -65,15 +65,15 @@ m.TerrainAnalysis.prototype.init = function(sharedScript, rawState)
* for optimizations it's called after the TerrainAnalyser has finished initializing his map
* so this can use the land regions already.
*/
m.Accessibility = function()
export function Accessibility()
{
};
}
m.copyPrototype(m.Accessibility, m.TerrainAnalysis);
copyPrototype(Accessibility, TerrainAnalysis);
m.Accessibility.prototype.init = function(rawState, terrainAnalyser)
Accessibility.prototype.init = function(rawState, terrainAnalyser)
{
this.Map(rawState, "passability", terrainAnalyser.map);
this.InfoMap(rawState, "passability", terrainAnalyser.map);
this.landPassMap = new Uint16Array(terrainAnalyser.length);
this.navalPassMap = new Uint16Array(terrainAnalyser.length);
@@ -146,7 +146,7 @@ m.Accessibility.prototype.init = function(rawState, terrainAnalyser)
// Engine.DumpImage("NavalPassMap.png", this.navalPassMap, this.width, this.height, 255);
};
m.Accessibility.prototype.getAccessValue = function(position, onWater)
Accessibility.prototype.getAccessValue = function(position, onWater)
{
const gamePos = this.gamePosToMapPos(position);
if (onWater)
@@ -170,7 +170,7 @@ m.Accessibility.prototype.getAccessValue = function(position, onWater)
return ret;
};
m.Accessibility.prototype.getTrajectTo = function(start, end)
Accessibility.prototype.getTrajectTo = function(start, end)
{
const pstart = this.gamePosToMapPos(start);
const istart = pstart[0] + pstart[1]*this.width;
@@ -198,7 +198,7 @@ m.Accessibility.prototype.getTrajectTo = function(start, end)
* this can tell you what sea zone you need to have a dock on, for example.
* assumes a land unit unless start point is over deep water.
*/
m.Accessibility.prototype.getTrajectToIndex = function(istart, iend)
Accessibility.prototype.getTrajectToIndex = function(istart, iend)
{
if (istart === iend)
return [istart];
@@ -227,7 +227,7 @@ m.Accessibility.prototype.getTrajectToIndex = function(istart, iend)
return undefined;
};
m.Accessibility.prototype.getRegionSize = function(position, onWater)
Accessibility.prototype.getRegionSize = function(position, onWater)
{
const pos = this.gamePosToMapPos(position);
const index = pos[0] + pos[1]*this.width;
@@ -237,7 +237,7 @@ m.Accessibility.prototype.getRegionSize = function(position, onWater)
return this.regionSize[ID];
};
m.Accessibility.prototype.getRegionSizei = function(index, onWater)
Accessibility.prototype.getRegionSizei = function(index, onWater)
{
if (this.regionSize[this.landPassMap[index]] === undefined && (!onWater || this.regionSize[this.navalPassMap[index]] === undefined))
return 0;
@@ -247,7 +247,7 @@ m.Accessibility.prototype.getRegionSizei = function(index, onWater)
};
/** Implementation of a fast flood fill. Reasonably good performances for JS. */
m.Accessibility.prototype.floodFill = function(startIndex, value, onWater)
Accessibility.prototype.floodFill = function(startIndex, value, onWater)
{
if (value > this.maxRegions)
{
@@ -383,7 +383,3 @@ m.Accessibility.prototype.floodFill = function(startIndex, value, onWater)
}
return true;
};
return m;
}(API3);
@@ -1,31 +1,28 @@
API3 = function(m)
{
m.warn = function(output)
export function warn(output)
{
if (typeof output === "string")
warn("PlayerID " + PlayerID + " | " + output);
else
warn("PlayerID " + PlayerID + " | " + uneval(output));
};
}
/**
* Useful for simulating consecutive AI matches.
*/
m.exit = function()
export function exit()
{
Engine.Exit();
};
}
m.VectorDistance = function(a, b)
export function VectorDistance(a, b)
{
return Math.euclidDistance2D(a[0], a[1], b[0], b[1]);
};
}
m.SquareVectorDistance = function(a, b)
export function SquareVectorDistance(a, b)
{
return Math.euclidDistance2DSquared(a[0], a[1], b[0], b[1]);
};
}
/** Utility functions for conversions of maps of different sizes */
@@ -33,7 +30,7 @@ m.SquareVectorDistance = function(a, b)
* Returns the index of map2 with max content from indices contained inside the cell i of map1
* map1.cellSize must be a multiple of map2.cellSize
*/
m.getMaxMapIndex = function(i, map1, map2)
function getMaxMapIndex(i, map1, map2)
{
const ratio = map1.cellSize / map2.cellSize;
const ix = (i % map1.width) * ratio;
@@ -44,13 +41,13 @@ m.getMaxMapIndex = function(i, map1, map2)
if (!index || map2.map[ix+kx+(iy+ky)*map2.width] > map2.map[index])
index = ix+kx+(iy+ky)*map2.width;
return index;
};
}
/**
* Returns the list of indices of map2 contained inside the cell i of map1
* map1.cellSize must be a multiple of map2.cellSize
*/
m.getMapIndices = function(i, map1, map2)
export function getMapIndices(i, map1, map2)
{
const ratio = map1.cellSize / map2.cellSize; // TODO check that this is integer >= 1 ?
const ix = (i % map1.width) * ratio;
@@ -60,13 +57,13 @@ m.getMapIndices = function(i, map1, map2)
for (let ky = 0; ky < ratio; ++ky)
ret.push(ix+kx+(iy+ky)*map2.width);
return ret;
};
}
/**
* Returns the list of points of map2 contained inside the cell i of map1
* map1.cellSize must be a multiple of map2.cellSize
*/
m.getMapPoints = function(i, map1, map2)
function getMapPoints(i, map1, map2)
{
const ratio = map1.cellSize / map2.cellSize; // TODO check that this is integer >= 1 ?
const ix = (i % map1.width) * ratio;
@@ -76,8 +73,4 @@ m.getMapPoints = function(i, map1, map2)
for (let ky = 0; ky < ratio; ++ky)
ret.push([ix+kx, iy+ky]);
return ret;
};
return m;
}(API3);
}
@@ -1,13 +1,13 @@
import { BaseAI } from "simulation/ai/common-api/baseAI.js";
import { Entity } from "simulation/ai/common-api/entity.js";
import { Config } from "simulation/ai/petra/config.js";
import { Headquather } from "simulation/ai/petra/headquarters.js";
import { Queue } from "simulation/ai/petra/queue.js";
import { QueueManager } from "simulation/ai/petra/queueManager.js";
Engine.IncludeModule("common-api");
export function PetraBot(settings)
{
API3.BaseAI.call(this, settings);
BaseAI.call(this, settings);
this.playedTurn = 0;
this.elapsedTime = 0;
@@ -24,7 +24,7 @@ export function PetraBot(settings)
this.savedEvents = {};
}
PetraBot.prototype = Object.create(API3.BaseAI.prototype);
PetraBot.prototype = Object.create(BaseAI.prototype);
PetraBot.prototype.CustomInit = function(gameState)
{
@@ -46,7 +46,7 @@ PetraBot.prototype.CustomInit = function(gameState)
for (const keyevt in evt)
{
evtmod[keyevt] = evt[keyevt];
evtmod.entityObj = new API3.Entity(gameState.sharedScript, evt.entityObj);
evtmod.entityObj = new Entity(gameState.sharedScript, evt.entityObj);
this.savedEvents[key][i] = evtmod;
}
}
@@ -1,3 +1,5 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { warn as aiWarn, SquareVectorDistance, VectorDistance } from "simulation/ai/common-api/utils.js";
import { AttackPlan } from "simulation/ai/petra/attackPlan.js";
import * as chat from "simulation/ai/petra/chatHelper.js";
import { Config, DIFFICULTY_VERY_EASY } from "simulation/ai/petra/config.js";
@@ -35,7 +37,7 @@ export function AttackManager(config)
/** More initialisation for stuff that needs the gameState */
AttackManager.prototype.init = function(gameState)
{
this.outOfPlan = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "plan", -1));
this.outOfPlan = gameState.getOwnUnits().filter(filters.byMetadata(PlayerID, "plan", -1));
this.outOfPlan.registerUpdates();
};
@@ -164,7 +166,8 @@ AttackManager.prototype.assignBombers = function(gameState)
}
}
const bombers = gameState.updatingCollection("bombers", API3.Filters.byClasses(["BoltShooter", "StoneThrower"]), gameState.getOwnUnits());
const bombers = gameState.updatingCollection("bombers",
filters.byClasses(["BoltShooter", "StoneThrower"]), gameState.getOwnUnits());
for (const ent of bombers.values())
{
if (!ent.position() || !ent.isIdle() || !ent.attackRange("Ranged"))
@@ -206,7 +209,7 @@ AttackManager.prototype.assignBombers = function(gameState)
!gameState.isPlayerEnemy(gameState.ai.HQ.territoryMap.getOwner(structPos)))
continue;
}
const dist = API3.VectorDistance(entPos, structPos);
const dist = VectorDistance(entPos, structPos);
if (dist > range)
{
const safety = struct.footprintRadius() + 30;
@@ -254,15 +257,25 @@ AttackManager.prototype.update = function(gameState, queues, events)
if (this.Config.debug > 2 && gameState.ai.elapsedTime > this.debugTime + 60)
{
this.debugTime = gameState.ai.elapsedTime;
API3.warn(" upcoming attacks =================");
aiWarn(" upcoming attacks =================");
for (const attackType in this.upcomingAttacks)
{
for (const attack of this.upcomingAttacks[attackType])
API3.warn(" plan " + attack.name + " type " + attackType + " state " + attack.state + " units " + attack.unitCollection.length);
API3.warn(" started attacks ==================");
{
aiWarn(" plan " + attack.name + " type " + attackType + " state " + attack.state +
" units " + attack.unitCollection.length);
}
}
aiWarn(" started attacks ==================");
for (const attackType in this.startedAttacks)
{
for (const attack of this.startedAttacks[attackType])
API3.warn(" plan " + attack.name + " type " + attackType + " state " + attack.state + " units " + attack.unitCollection.length);
API3.warn(" ==================================");
{
aiWarn(" plan " + attack.name + " type " + attackType + " state " + attack.state +
" units " + attack.unitCollection.length);
}
}
aiWarn(" ==================================");
}
this.checkEvents(gameState, events);
@@ -280,7 +293,10 @@ AttackManager.prototype.update = function(gameState, queues, events)
attack.checkEvents(gameState, events);
if (attack.isStarted())
API3.warn("Petra problem in attackManager: attack in preparation has already started ???");
{
aiWarn("Petra problem in attackManager: attack in preparation has already " +
"started ???");
}
const updateStep = attack.updatePreparation(gameState);
// now we're gonna check if the preparation time is over
@@ -293,7 +309,10 @@ AttackManager.prototype.update = function(gameState, queues, events)
else if (updateStep === AttackPlan.PREPARATION_FAILED)
{
if (this.Config.debug > 1)
API3.warn("Attack Manager: " + attack.getType() + " plan " + attack.getName() + " aborted.");
{
aiWarn("Attack Manager: " + attack.getType() + " plan " + attack.getName() +
" aborted.");
}
attack.Abort(gameState);
this.upcomingAttacks[attackType].splice(i--, 1);
}
@@ -302,7 +321,10 @@ AttackManager.prototype.update = function(gameState, queues, events)
if (attack.StartAttack(gameState))
{
if (this.Config.debug > 1)
API3.warn("Attack Manager: Starting " + attack.getType() + " plan " + attack.getName());
{
aiWarn("Attack Manager: Starting " + attack.getType() + " plan " +
attack.getName());
}
if (this.Config.chat)
chat.launchAttack(gameState, attack.targetPlayer, attack.getType());
this.startedAttacks[attackType].push(attack);
@@ -327,7 +349,10 @@ AttackManager.prototype.update = function(gameState, queues, events)
if (!remaining)
{
if (this.Config.debug > 1)
API3.warn("Military Manager: " + attack.getType() + " plan " + attack.getName() + " is finished with remaining " + remaining);
{
aiWarn("Military Manager: " + attack.getType() + " plan " +
attack.getName() + " is finished with remaining " + remaining);
}
attack.Abort(gameState);
this.startedAttacks[attackType].splice(i--, 1);
}
@@ -336,7 +361,7 @@ AttackManager.prototype.update = function(gameState, queues, events)
// creating plans after updating because an aborted plan might be reused in that case.
const barracksNb = gameState.getOwnEntitiesByClass("Barracks", true).filter(API3.Filters.isBuilt()).length;
const barracksNb = gameState.getOwnEntitiesByClass("Barracks", true).filter(filters.isBuilt()).length;
if (this.rushNumber < this.maxRushes && barracksNb >= 1)
{
if (unexecutedAttacks[AttackPlan.TYPE_RUSH] === 0)
@@ -348,7 +373,10 @@ AttackManager.prototype.update = function(gameState, queues, events)
if (!attackPlan.failed)
{
if (this.Config.debug > 1)
API3.warn("Military Manager: Rushing plan " + this.totalNumber + " with maxRushes " + this.maxRushes);
{
aiWarn("Military Manager: Rushing plan " + this.totalNumber +
" with maxRushes " + this.maxRushes);
}
this.totalNumber++;
attackPlan.init(gameState);
this.upcomingAttacks[AttackPlan.TYPE_RUSH].push(attackPlan);
@@ -377,7 +405,10 @@ AttackManager.prototype.update = function(gameState, queues, events)
else
{
if (this.Config.debug > 1)
API3.warn("Military Manager: Creating the plan " + type + " " + this.totalNumber);
{
aiWarn("Military Manager: Creating the plan " + type + " " +
this.totalNumber);
}
this.totalNumber++;
attackPlan.init(gameState);
this.upcomingAttacks[type].push(attackPlan);
@@ -521,7 +552,7 @@ AttackManager.prototype.getEnemyPlayer = function(gameState, attack)
let distmin;
let ccmin;
const ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
const ccEnts = gameState.updatingGlobalCollection("allCCs", filters.byClass("CivCentre"));
for (const ourcc of ccEnts.values())
{
if (ourcc.owner() != PlayerID)
@@ -536,7 +567,7 @@ AttackManager.prototype.getEnemyPlayer = function(gameState, attack)
continue;
if (access !== getLandAccess(gameState, enemycc))
continue;
const dist = API3.SquareVectorDistance(ourPos, enemycc.position());
const dist = SquareVectorDistance(ourPos, enemycc.position());
if (distmin && dist > distmin)
continue;
ccmin = enemycc;
@@ -590,7 +621,7 @@ AttackManager.prototype.getWonderEnemyPlayer = function(gameState, attack)
let enemyPlayer;
let enemyWonder;
let moreAdvanced;
for (const wonder of gameState.getEnemyStructures().filter(API3.Filters.byClass("Wonder")).values())
for (const wonder of gameState.getEnemyStructures().filter(filters.byClass("Wonder")).values())
{
if (wonder.owner() == 0)
continue;
@@ -620,7 +651,7 @@ AttackManager.prototype.getWonderEnemyPlayer = function(gameState, attack)
AttackManager.prototype.getRelicEnemyPlayer = function(gameState, attack)
{
let enemyPlayer;
const allRelics = gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic"));
const allRelics = gameState.updatingGlobalCollection("allRelics", filters.byClass("Relic"));
let maxRelicsOwned = 0;
for (let i = 0; i < gameState.sharedScript.playersData.length; ++i)
{
@@ -672,7 +703,7 @@ AttackManager.prototype.raidTargetEntity = function(gameState, ent)
if (attackPlan.failed)
return null;
if (this.Config.debug > 1)
API3.warn("Military Manager: Raiding plan " + this.totalNumber);
aiWarn("Military Manager: Raiding plan " + this.totalNumber);
this.raidNumber++;
this.totalNumber++;
attackPlan.init(gameState);
@@ -691,7 +722,7 @@ AttackManager.prototype.numAttackingUnitsAround = function(pos, dist)
{
if (!attack.position) // this attack may be inside a transport
continue;
if (API3.SquareVectorDistance(pos, attack.position) < dist*dist)
if (SquareVectorDistance(pos, attack.position) < dist*dist)
num += attack.unitCollection.length;
}
return num;
@@ -709,7 +740,7 @@ AttackManager.prototype.switchDefenseToAttack = function(gameState, target, data
return false;
if (!data.range && !data.armyID)
{
API3.warn(" attackManager.switchDefenseToAttack inconsistent data " + uneval(data));
aiWarn(" attackManager.switchDefenseToAttack inconsistent data " + uneval(data));
return false;
}
const attackData = data.uniqueTarget ? { "uniqueTargetId": target.id() } : undefined;
@@ -728,7 +759,7 @@ AttackManager.prototype.switchDefenseToAttack = function(gameState, target, data
if (data.range)
{
army.recalculatePosition(gameState);
if (API3.SquareVectorDistance(pos, army.foePosition) > data.range * data.range)
if (SquareVectorDistance(pos, army.foePosition) > data.range * data.range)
continue;
}
else if (army.ID != +data.armyID)
@@ -1,3 +1,6 @@
import { EntityCollection } from "simulation/ai/common-api/entitycollection.js";
import * as filters from "simulation/ai/common-api/filters.js";
import { SquareVectorDistance, VectorDistance, warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { Config, DIFFICULTY_EASY, DIFFICULTY_MEDIUM } from "simulation/ai/petra/config.js";
import { allowCapture, dumpEntity, getHolder, getLandAccess, isSiegeUnit, returnResources } from
"simulation/ai/petra/entityExtend.js";
@@ -92,7 +95,7 @@ export function AttackPlan(gameState, config, uniqueID, type = AttackPlan.TYPE_D
{
if (this.target)
{
API3.warn("Petra: " + this.type + " " + this.name + " has an inaccessible target " +
aiWarn("Petra: " + this.type + " " + this.name + " has an inaccessible target " +
this.target.templateName() + " indices " + rallyAccess + " " + access);
this.failed = true;
return false;
@@ -259,7 +262,8 @@ AttackPlan.prototype.init = function(gameState)
this.queueChamp = gameState.ai.queues["plan_" + this.name +"_champ"];
this.queueSiege = gameState.ai.queues["plan_" + this.name +"_siege"];
this.unitCollection = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "plan", this.name));
this.unitCollection = gameState.getOwnUnits().filter(
filters.byMetadata(PlayerID, "plan", this.name));
this.unitCollection.registerUpdates();
this.unit = {};
@@ -269,7 +273,7 @@ AttackPlan.prototype.init = function(gameState)
for (const cat in this.unitStat)
{
const Unit = this.unitStat[cat];
this.unit[cat] = this.unitCollection.filter(API3.Filters.byClasses(Unit.classes));
this.unit[cat] = this.unitCollection.filter(filters.byClasses(Unit.classes));
this.unit[cat].registerUpdates();
if (this.canBuildUnits)
this.buildOrders.push([0, Unit.classes, this.unit[cat], Unit, cat]);
@@ -380,7 +384,7 @@ AttackPlan.prototype.addBuildOrder = function(gameState, name, unitStats, resetQ
// no minsize as we don't want the plan to fail at the last minute though.
this.unitStat[name] = unitStats;
const Unit = this.unitStat[name];
this.unit[name] = this.unitCollection.filter(API3.Filters.byClasses(Unit.classes));
this.unit[name] = this.unitCollection.filter(filters.byClasses(Unit.classes));
this.unit[name].registerUpdates();
this.buildOrders.push([0, Unit.classes, this.unit[name], Unit, name]);
if (resetQueue)
@@ -510,11 +514,12 @@ AttackPlan.prototype.updatePreparation = function(gameState)
if (this.Config.debug > 1)
{
const am = gameState.ai.HQ.attackManager;
API3.warn(" attacks upcoming: raid " + am.upcomingAttacks[AttackPlan.TYPE_RAID].length +
aiWarn(" attacks upcoming: raid " +
am.upcomingAttacks[AttackPlan.TYPE_RAID].length +
" rush " + am.upcomingAttacks[AttackPlan.TYPE_RUSH].length +
" attack " + am.upcomingAttacks[AttackPlan.TYPE_DEFAULT].length +
" huge " + am.upcomingAttacks[AttackPlan.TYPE_HUGE_ATTACK].length);
API3.warn(" attacks started: raid " + am.startedAttacks[AttackPlan.TYPE_RAID].length +
aiWarn(" attacks started: raid " + am.startedAttacks[AttackPlan.TYPE_RAID].length +
" rush " + am.startedAttacks[AttackPlan.TYPE_RUSH].length +
" attack " + am.startedAttacks[AttackPlan.TYPE_DEFAULT].length +
" huge " + am.startedAttacks[AttackPlan.TYPE_HUGE_ATTACK].length);
@@ -635,8 +640,8 @@ AttackPlan.prototype.trainMoreUnits = function(gameState)
if (this.Config.debug > 1 && gameState.ai.playedTurn%50 === 0)
{
API3.warn("====================================");
API3.warn("======== build order for plan " + this.name);
aiWarn("====================================");
aiWarn("======== build order for plan " + this.name);
for (const order of this.buildOrders)
{
const specialData = "Plan_"+this.name+"_"+order[4];
@@ -644,10 +649,10 @@ AttackPlan.prototype.trainMoreUnits = function(gameState)
const queue1 = this.queue.countQueuedUnitsWithMetadata("special", specialData);
const queue2 = this.queueChamp.countQueuedUnitsWithMetadata("special", specialData);
const queue3 = this.queueSiege.countQueuedUnitsWithMetadata("special", specialData);
API3.warn(" >>> " + order[4] + " done " + order[2].length + " training " + inTraining +
aiWarn(" >>> " + order[4] + " done " + order[2].length + " training " + inTraining +
" queue " + queue1 + " champ " + queue2 + " siege " + queue3 + " >> need " + order[3].targetSize);
}
API3.warn("====================================");
aiWarn("====================================");
}
const firstOrder = this.buildOrders[0];
@@ -670,14 +675,14 @@ AttackPlan.prototype.trainMoreUnits = function(gameState)
if (template === undefined)
{
if (this.Config.debug > 1)
API3.warn("attack no template found " + firstOrder[1]);
aiWarn("attack no template found " + firstOrder[1]);
delete this.unitStat[firstOrder[4]]; // deleting the associated unitstat.
this.buildOrders.splice(0, 1);
}
else
{
if (this.Config.debug > 2)
API3.warn("attack template " + template + " added for plan " + this.name);
aiWarn("attack template " + template + " added for plan " + this.name);
const max = firstOrder[3].batchSize;
const specialData = "Plan_" + this.name + "_" + firstOrder[4];
const data = { "plan": this.name, "special": specialData, "base": 0 };
@@ -687,8 +692,11 @@ AttackPlan.prototype.trainMoreUnits = function(gameState)
if (trainingPlan.template)
queue.addPlan(trainingPlan);
else if (this.Config.debug > 1)
API3.warn("training plan canceled because no template for " + template + " build1 " + uneval(firstOrder[1]) +
" build3 " + uneval(firstOrder[3].interests));
{
aiWarn("training plan canceled because no template for " + template +
" build1 " + uneval(firstOrder[1]) +
" build3 " + uneval(firstOrder[3].interests));
}
}
}
}
@@ -770,7 +778,7 @@ AttackPlan.prototype.assignUnits = function(gameState)
numbase[baseID] = numbase[baseID] ? ++numbase[baseID] : 1;
else
{
API3.warn("Petra problem ent without base ");
aiWarn("Petra problem ent without base ");
dumpEntity(ent);
continue;
}
@@ -852,7 +860,7 @@ AttackPlan.prototype.chooseTarget = function(gameState)
const anchor = base.anchor;
if (!anchor || !anchor.position())
continue;
let dist = API3.SquareVectorDistance(anchor.position(), this.targetPos);
let dist = SquareVectorDistance(anchor.position(), this.targetPos);
if (base.accessIndex == targetIndex)
{
if (dist >= distminSame)
@@ -887,7 +895,7 @@ AttackPlan.prototype.chooseTarget = function(gameState)
}
else
{
API3.warn("Petra: " + this.type + " " + this.name + " has an inaccessible target" +
aiWarn("Petra: " + this.type + " " + this.name + " has an inaccessible target" +
" with indices " + rallyIndex + " " + targetIndex + " from " + this.target.templateName());
return false;
}
@@ -911,7 +919,7 @@ AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand)
let targets;
if (this.uniqueTargetId)
{
targets = new API3.EntityCollection(gameState.sharedScript);
targets = new EntityCollection(gameState.sharedScript);
const ent = gameState.getEntityById(this.uniqueTargetId);
if (ent)
targets.addEnt(ent);
@@ -943,7 +951,7 @@ AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand)
// Do not bother with some pointless targets
if (!this.isValidTarget(ent))
continue;
let dist = API3.SquareVectorDistance(ent.position(), position);
let dist = SquareVectorDistance(ent.position(), position);
// In normal attacks, disfavor fields
if (this.type !== AttackPlan.TYPE_RUSH && this.type !== AttackPlan.TYPE_RAID && ent.hasClass("Field"))
dist += 100000;
@@ -975,34 +983,51 @@ AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand)
*/
AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy)
{
let targets = new API3.EntityCollection(gameState.sharedScript);
let targets = new EntityCollection(gameState.sharedScript);
if (gameState.getVictoryConditions().has("wonder"))
for (const ent of gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("Wonder")).values())
{
for (const ent of
gameState.getEnemyStructures(playerEnemy).filter(filters.byClass("Wonder")).values())
{
targets.addEnt(ent);
}
}
if (gameState.getVictoryConditions().has("regicide"))
for (const ent of gameState.getEnemyUnits(playerEnemy).filter(API3.Filters.byClass("Hero")).values())
{
for (const ent of gameState.getEnemyUnits(playerEnemy).filter(filters.byClass("Hero"))
.values())
{
targets.addEnt(ent);
}
}
if (gameState.getVictoryConditions().has("capture_the_relic"))
for (const ent of gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic")).filter(relic => relic.owner() == playerEnemy).values())
{
for (const ent of gameState.updatingGlobalCollection("allRelics",
filters.byClass("Relic")).filter(relic => relic.owner() == playerEnemy).values())
{
targets.addEnt(ent);
}
}
targets = targets.filter(this.isValidTarget, this);
if (targets.hasEntities())
return targets;
const validTargets = gameState.getEnemyStructures(playerEnemy).filter(this.isValidTarget, this);
targets = validTargets.filter(API3.Filters.byClass("CivCentre"));
targets = validTargets.filter(filters.byClass("CivCentre"));
if (!targets.hasEntities())
targets = validTargets.filter(API3.Filters.byClass("ConquestCritical"));
targets = validTargets.filter(filters.byClass("ConquestCritical"));
// If there's nothing, attack anything else that's less critical
if (!targets.hasEntities())
targets = validTargets.filter(API3.Filters.byClass("Town"));
targets = validTargets.filter(filters.byClass("Town"));
if (!targets.hasEntities())
targets = validTargets.filter(API3.Filters.byClass("Village"));
targets = validTargets.filter(filters.byClass("Village"));
// No buildings, attack anything conquest critical, units included.
// TODO Should add naval attacks against the last remaining ships.
if (!targets.hasEntities())
targets = gameState.getEntities(playerEnemy).filter(API3.Filters.byClass("ConquestCritical")).
filter(API3.Filters.not(API3.Filters.byClass("Ship")));
{
targets = gameState.getEntities(playerEnemy).filter(filters.byClass("ConquestCritical"))
.filter(filters.not(filters.byClass("Ship")));
}
return targets;
};
@@ -1018,7 +1043,7 @@ AttackPlan.prototype.isValidTarget = function(ent)
/** Rush target finder aims at isolated non-defended buildings */
AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy)
{
let targets = new API3.EntityCollection(gameState.sharedScript);
let targets = new EntityCollection(gameState.sharedScript);
let buildings;
if (playerEnemy !== undefined)
buildings = gameState.getEnemyStructures(playerEnemy).toEntityArray();
@@ -1047,7 +1072,7 @@ AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy)
{
if (!defense.hasDefensiveFire())
continue;
const dist = API3.SquareVectorDistance(pos, defense.position());
const dist = SquareVectorDistance(pos, defense.position());
if (dist < 6400) // TODO check on defense range rather than this fixed 80*80
{
defended = true;
@@ -1056,7 +1081,7 @@ AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy)
}
if (defended)
continue;
const dist = API3.SquareVectorDistance(pos, this.position);
const dist = SquareVectorDistance(pos, this.position);
if (dist > minDist)
continue;
minDist = dist;
@@ -1074,7 +1099,7 @@ AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy)
/** Raid target finder aims at destructing foundations from which our defenseManager has attacked the builders */
AttackPlan.prototype.raidTargetFinder = function(gameState)
{
const targets = new API3.EntityCollection(gameState.sharedScript);
const targets = new EntityCollection(gameState.sharedScript);
for (const targetId of gameState.ai.HQ.defenseManager.targetList)
{
const target = gameState.getEntityById(targetId);
@@ -1103,7 +1128,7 @@ AttackPlan.prototype.checkTargetObstruction = function(gameState, target, positi
return undefined;
const pathPos = [path[0].x, path[0].y];
const dist = API3.VectorDistance(pathPos, targetPos);
const dist = VectorDistance(pathPos, targetPos);
const radius = target.obstructionRadius().max;
for (const struct of gameState.getEnemyStructures().values())
{
@@ -1251,7 +1276,7 @@ AttackPlan.prototype.setRallyPoint = function(gameState)
AttackPlan.prototype.StartAttack = function(gameState)
{
if (this.Config.debug > 1)
API3.warn("start attack " + this.name + " with type " + this.type);
aiWarn("start attack " + this.name + " with type " + this.type);
// if our target was destroyed during preparation, choose a new one
if ((this.targetPlayer === undefined || !this.target || !gameState.getEntityById(this.target.id())) &&
@@ -1369,7 +1394,8 @@ AttackPlan.prototype.update = function(gameState, events)
}
if (isSiegeUnit(ourUnit))
{ // if our siege units are attacked, we'll send some units to deal with enemies.
const collec = this.unitCollection.filter(API3.Filters.not(API3.Filters.byClass("Siege"))).filterNearest(ourUnit.position(), 5);
const collec = this.unitCollection.filter(filters.not(filters.byClass("Siege")))
.filterNearest(ourUnit.position(), 5);
for (const ent of collec.values())
{
if (isSiegeUnit(ent)) // needed as mauryan elephants are not filtered out
@@ -1400,7 +1426,8 @@ AttackPlan.prototype.update = function(gameState, events)
}
else if (isSiegeUnit(attacker))
{ // if our unit is attacked by a siege unit, we'll send some melee units to help it.
const collec = this.unitCollection.filter(API3.Filters.byClass("Melee")).filterNearest(ourUnit.position(), 5);
const collec = this.unitCollection.filter(filters.byClass("Melee"))
.filterNearest(ourUnit.position(), 5);
for (const ent of collec.values())
{
const shouldCapture = allowCapture(gameState, ent, attacker);
@@ -1603,7 +1630,7 @@ AttackPlan.prototype.update = function(gameState, events)
const mStruct = enemyStructures.filter(enemy => {
if (!enemy.position() || !ent.canAttackTarget(enemy, allowCapture(gameState, ent, enemy)))
return false;
if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range)
if (SquareVectorDistance(enemy.position(), ent.position()) > range)
return false;
if (enemy.foundationProgress() == 0)
return false;
@@ -1659,14 +1686,17 @@ AttackPlan.prototype.update = function(gameState, events)
return false;
if (nearby && enemy.hasClass("FemaleCitizen") && enemy.unitAIState().split(".")[1] == "FLEEING")
return false;
const dist = API3.SquareVectorDistance(enemy.position(), ent.position());
const dist = SquareVectorDistance(enemy.position(), ent.position());
if (dist > range)
return false;
if (getLandAccess(gameState, enemy) != entAccess)
return false;
// if already too much units targeting this enemy, let's continue towards our main target
if (veto[enemy.id()] && API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500)
if (veto[enemy.id()] &&
SquareVectorDistance(this.targetPos, ent.position()) > 2500)
{
return false;
}
enemy.setMetadata(PlayerID, "distance", Math.sqrt(dist));
return true;
}, this).toEntityArray();
@@ -1699,7 +1729,7 @@ AttackPlan.prototype.update = function(gameState, events)
// cannot attack. See similar behaviour at #5741.
else if (this.isBlocked && ent.canAttackTarget(this.target, false))
ent.attack(this.target.id(), false);
else if (API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500)
else if (SquareVectorDistance(this.targetPos, ent.position()) > 2500)
{
let targetClasses = targetClassesUnit;
if (maybeUpdate && ent.unitAIState() === "INDIVIDUAL.COMBAT.APPROACHING") // we may be blocked by walls, attack everything
@@ -1720,7 +1750,7 @@ AttackPlan.prototype.update = function(gameState, events)
return false;
if (!enemy.position() || !ent.canAttackTarget(enemy, allowCapture(gameState, ent, enemy)))
return false;
if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range)
if (SquareVectorDistance(enemy.position(), ent.position()) > range)
return false;
if (getLandAccess(gameState, enemy) != entAccess)
return false;
@@ -1762,7 +1792,7 @@ AttackPlan.prototype.update = function(gameState, events)
const target = gameState.getEntityById(unit.unitAIOrderData()[0].target);
if (!target)
return;
const dist = API3.SquareVectorDistance(unit.position(), ent.position());
const dist = SquareVectorDistance(unit.position(), ent.position());
if (dist > distmin)
return;
distmin = dist;
@@ -1863,14 +1893,15 @@ AttackPlan.prototype.UpdateWalking = function(gameState, events)
}
// basically haven't moved an inch: very likely stuck)
if (API3.SquareVectorDistance(this.position, this.position5TurnsAgo) < 10 && this.path.length > 0 && gameState.ai.playedTurn % 5 === 0)
if (SquareVectorDistance(this.position, this.position5TurnsAgo) < 10 &&
this.path.length > 0 && gameState.ai.playedTurn % 5 === 0)
{
// check for stuck siege units
let farthest = 0;
let farthestEnt;
for (const ent of this.unitCollection.filter(API3.Filters.byClass("Siege")).values())
for (const ent of this.unitCollection.filter(filters.byClass("Siege")).values())
{
const dist = API3.SquareVectorDistance(ent.position(), this.position);
const dist = SquareVectorDistance(ent.position(), this.position);
if (dist < farthest)
continue;
farthest = dist;
@@ -1882,27 +1913,35 @@ AttackPlan.prototype.UpdateWalking = function(gameState, events)
if (gameState.ai.playedTurn % 5 === 0)
this.position5TurnsAgo = this.position;
if (this.lastPosition && API3.SquareVectorDistance(this.position, this.lastPosition) < 16 && this.path.length > 0)
if (this.lastPosition && SquareVectorDistance(this.position, this.lastPosition) < 16 &&
this.path.length > 0)
{
if (!this.path[0][0] || !this.path[0][1])
API3.warn("Start: Problem with path " + uneval(this.path));
aiWarn("Start: Problem with path " + uneval(this.path));
// We're stuck, presumably. Check if there are no walls just close to us.
for (const ent of gameState.getEnemyStructures().filter(API3.Filters.byClass(["Palisade", "Wall"])).values())
for (const ent of gameState.getEnemyStructures().filter(filters.byClass(["Palisade", "Wall"]))
.values())
{
if (API3.SquareVectorDistance(this.position, ent.position()) > 800)
if (SquareVectorDistance(this.position, ent.position()) > 800)
continue;
const enemyClass = ent.hasClass("Wall") ? "Wall" : "Palisade";
// there are walls, so check if we can attack
if (this.unitCollection.filter(API3.Filters.byCanAttackClass(enemyClass)).hasEntities())
if (this.unitCollection.filter(filters.byCanAttackClass(enemyClass)).hasEntities())
{
if (this.Config.debug > 1)
API3.warn("Attack Plan " + this.type + " " + this.name + " has met walls and is not happy.");
{
aiWarn("Attack Plan " + this.type + " " + this.name +
" has met walls and is not happy.");
}
this.state = AttackPlan.STATE_ARRIVED;
return true;
}
// abort plan
if (this.Config.debug > 1)
API3.warn("Attack Plan " + this.type + " " + this.name + " has met walls and gives up.");
{
aiWarn("Attack Plan " + this.type + " " + this.name +
" has met walls and gives up.");
}
return false;
}
@@ -1911,14 +1950,14 @@ AttackPlan.prototype.UpdateWalking = function(gameState, events)
}
// check if our units are close enough from the next waypoint.
if (API3.SquareVectorDistance(this.position, this.targetPos) < 10000)
if (SquareVectorDistance(this.position, this.targetPos) < 10000)
{
if (this.Config.debug > 1)
API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination.");
aiWarn("Attack Plan " + this.type + " " + this.name + " has arrived to destination.");
this.state = AttackPlan.STATE_ARRIVED;
return true;
}
else if (this.path.length && API3.SquareVectorDistance(this.position, this.path[0]) < 1600)
else if (this.path.length && SquareVectorDistance(this.position, this.path[0]) < 1600)
{
this.path.shift();
if (this.path.length)
@@ -1926,7 +1965,10 @@ AttackPlan.prototype.UpdateWalking = function(gameState, events)
else
{
if (this.Config.debug > 1)
API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination.");
{
aiWarn("Attack Plan " + this.type + " " + this.name +
" has arrived to destination.");
}
this.state = AttackPlan.STATE_ARRIVED;
return true;
}
@@ -1969,7 +2011,10 @@ AttackPlan.prototype.UpdateTarget = function(gameState)
if (!this.target || !gameState.getEntityById(this.target.id()))
{
if (this.Config.debug > 1)
API3.warn("Seems like our target for plan " + this.name + " has been destroyed or captured. Switching.");
{
aiWarn("Seems like our target for plan " + this.name +
" has been destroyed or captured. Switching.");
}
const accessIndex = this.getAttackAccess(gameState);
this.target = this.getNearestTarget(gameState, this.position, accessIndex);
if (!this.target)
@@ -2010,12 +2055,15 @@ AttackPlan.prototype.UpdateTarget = function(gameState)
if (!this.target)
{
if (this.Config.debug > 1)
API3.warn("No new target found. Remaining units " + this.unitCollection.length);
{
aiWarn("No new target found. Remaining units " +
this.unitCollection.length);
}
return false;
}
}
if (this.Config.debug > 1)
API3.warn("We will help one of our other attacks");
aiWarn("We will help one of our other attacks");
}
this.targetPos = this.target.position();
}
@@ -2037,7 +2085,7 @@ AttackPlan.prototype.Abort = function(gameState)
if (this.rallyPoint && gameState.ai.accessibility.getAccessValue(this.rallyPoint) == access)
{
rallyPoint = this.rallyPoint;
dist = API3.SquareVectorDistance(this.position, rallyPoint);
dist = SquareVectorDistance(this.position, rallyPoint);
}
// Then check if we have a nearer base (in case this attack has captured one)
for (const base of gameState.ai.HQ.baseManagers())
@@ -2046,7 +2094,7 @@ AttackPlan.prototype.Abort = function(gameState)
continue;
if (getLandAccess(gameState, base.anchor) != access)
continue;
const newdist = API3.SquareVectorDistance(this.position, base.anchor.position());
const newdist = SquareVectorDistance(this.position, base.anchor.position());
if (newdist > dist)
continue;
dist = newdist;
@@ -2178,13 +2226,14 @@ AttackPlan.prototype.getAttackAccess = function(gameState)
AttackPlan.prototype.debugAttack = function()
{
API3.warn("---------- attack " + this.name);
aiWarn("---------- attack " + this.name);
for (const unitCat in this.unitStat)
{
const Unit = this.unitStat[unitCat];
API3.warn(unitCat + " num=" + this.unit[unitCat].length + " min=" + Unit.minSize + " need=" + Unit.targetSize);
aiWarn(unitCat + " num=" + this.unit[unitCat].length + " min=" + Unit.minSize + " need=" +
Unit.targetSize);
}
API3.warn("------------------------------");
aiWarn("------------------------------");
};
AttackPlan.prototype.Serialize = function()
@@ -1,3 +1,5 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { SquareVectorDistance, warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { Config, DIFFICULTY_EASY, DIFFICULTY_MEDIUM } from "simulation/ai/petra/config.js";
import { getBestBase, getBuiltEntity, getLandAccess, isFastMoving, isNotWorthBuilding } from
"simulation/ai/petra/entityExtend.js";
@@ -68,10 +70,10 @@ BaseManager.prototype.init = function(gameState, state)
this.neededDefenders = 0;
this.workerObject = new Worker(this);
// entitycollections
this.units = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "base", this.ID));
this.workers = this.units.filter(API3.Filters.byMetadata(PlayerID, "role", Worker.ROLE_WORKER));
this.buildings = gameState.getOwnStructures().filter(API3.Filters.byMetadata(PlayerID, "base", this.ID));
this.mobileDropsites = this.units.filter(API3.Filters.isDropsite());
this.units = gameState.getOwnUnits().filter(filters.byMetadata(PlayerID, "base", this.ID));
this.workers = this.units.filter(filters.byMetadata(PlayerID, "role", Worker.ROLE_WORKER));
this.buildings = gameState.getOwnStructures().filter(filters.byMetadata(PlayerID, "base", this.ID));
this.mobileDropsites = this.units.filter(filters.isDropsite());
this.units.registerUpdates();
this.workers.registerUpdates();
@@ -114,7 +116,10 @@ BaseManager.prototype.assignEntity = function(gameState, ent)
BaseManager.prototype.setAnchor = function(gameState, anchorEntity)
{
if (!anchorEntity.hasClass("CivCentre"))
API3.warn("Error: Petra base " + this.ID + " has been assigned " + ent.templateName() + " as anchor.");
{
aiWarn("Error: Petra base " + this.ID + " has been assigned " + ent.templateName() +
" as anchor.");
}
else
{
this.anchor = anchorEntity;
@@ -143,13 +148,18 @@ BaseManager.prototype.setAnchorlessEntity = function(gameState, ent)
if (!this.buildings.hasEntities())
{
if (!getBuiltEntity(gameState, ent).resourceDropsiteTypes())
API3.warn("Error: Petra base " + this.ID + " has been assigned " + ent.templateName() + " as origin.");
{
aiWarn("Error: Petra base " + this.ID + " has been assigned " + ent.templateName() +
" as origin.");
}
this.accessIndex = getLandAccess(gameState, ent);
}
else if (this.accessIndex !== getLandAccess(gameState, ent))
API3.warn(" Error: Petra base " + this.ID + " with access " + this.accessIndex +
{
aiWarn(" Error: Petra base " + this.ID + " with access " + this.accessIndex +
" has been assigned " + ent.templateName() + " with access" +
getLandAccess(gameState, ent));
}
ent.setMetadata(PlayerID, "base", this.ID);
this.buildings.updateEnt(ent);
@@ -199,7 +209,7 @@ BaseManager.prototype.assignResourceToDropsite = function(gameState, dropsite)
if (getLandAccess(gameState, supply) != accessIndex)
return;
const dist = API3.SquareVectorDistance(supply.position(), dropsitePos);
const dist = SquareVectorDistance(supply.position(), dropsitePos);
if (dist < maxDistResourceSquare)
{
if (dist < maxDistResourceSquare/16) // distmax/4
@@ -291,7 +301,8 @@ BaseManager.prototype.findBestDropsiteAndLocation = function(gameState, resource
"quality": 0,
"pos": [0, 0]
};
for (const templateName of gameState.ai.HQ.buildManager.findStructuresByFilter(gameState, API3.Filters.isDropsite(resource)))
for (const templateName of gameState.ai.HQ.buildManager.findStructuresByFilter(gameState,
filters.isDropsite(resource)))
{
const dp = this.findBestDropsiteLocation(gameState, resource, templateName);
if (dp.quality < bestResult.quality)
@@ -326,7 +337,7 @@ BaseManager.prototype.findBestDropsiteLocation = function(gameState, resource, t
const obstructions = createObstructionMap(gameState, this.accessIndex, template);
const dpEnts = gameState.getOwnStructures().filter(API3.Filters.isDropsite(resource)).toEntityArray();
const dpEnts = gameState.getOwnStructures().filter(filters.isDropsite(resource)).toEntityArray();
// Foundations don't have the dropsite properties yet, so treat them separately.
for (const foundation of gameState.getOwnFoundations().toEntityArray())
@@ -366,7 +377,7 @@ BaseManager.prototype.findBestDropsiteLocation = function(gameState, resource, t
const dpPos = dp.position();
if (!dpPos)
continue;
const dist = API3.SquareVectorDistance(dpPos, pos);
const dist = SquareVectorDistance(dpPos, pos);
if (dist < 3600)
{
total = 0;
@@ -423,7 +434,8 @@ BaseManager.prototype.checkResourceLevels = function(gameState, queues)
if (gameState.ai.HQ.canBuild(gameState, "structures/{civ}/field")) // let's see if we need to add new farms.
{
const count = this.getResourceLevel(gameState, type, prox); // animals are not accounted
const numFarms = gameState.getOwnStructures().filter(API3.Filters.byClass("Field")).length; // including foundations
const numFarms = gameState.getOwnStructures().filter(filters.byClass("Field"))
.length; // including foundations
const numQueue = queues.field.countQueuedUnits();
// TODO if not yet farms, add a check on time used/lost and build farmstead if needed
@@ -438,7 +450,8 @@ BaseManager.prototype.checkResourceLevels = function(gameState, queues)
}
else if (!gameState.ai.HQ.maxFields || numFarms + numQueue < gameState.ai.HQ.maxFields)
{
const numFound = gameState.getOwnFoundations().filter(API3.Filters.byClass("Field")).length;
const numFound = gameState.getOwnFoundations().filter(filters.byClass("Field"))
.length;
let goal = this.Config.Economy.provisionFields;
if (gameState.ai.HQ.saveResources || gameState.ai.HQ.saveSpace || count > 300 || numFarms > 5)
goal = Math.max(goal-1, 1);
@@ -473,7 +486,7 @@ BaseManager.prototype.checkResourceLevels = function(gameState, queues)
}
// Non food stuff
if (!gameState.sharedScript.resourceMaps[type] || queues.dropsites.hasQueuedUnits() ||
gameState.getOwnFoundations().filter(API3.Filters.byClass("Storehouse")).hasEntities())
gameState.getOwnFoundations().filter(filters.byClass("Storehouse")).hasEntities())
{
this.gatherers[type].nextCheck = gameState.ai.playedTurn;
this.gatherers[type].used = 0;
@@ -502,7 +515,9 @@ BaseManager.prototype.checkResourceLevels = function(gameState, queues)
queues.dropsites.addPlan(new ConstructionPlan(gameState, newDP.templateName,
{ "base": this.ID, "type": type }, newDP.pos));
}
else if (!gameState.getOwnFoundations().filter(API3.Filters.byClass("CivCentre")).hasEntities() && !queues.civilCentre.hasQueuedUnits())
else if (!gameState.getOwnFoundations().filter(filters.byClass("CivCentre"))
.hasEntities() &&
!queues.civilCentre.hasQueuedUnits())
{
// No good dropsite, try to build a new base if no base already planned,
// and if not possible, be less strict on dropsite quality.
@@ -567,7 +582,7 @@ BaseManager.prototype.addGatherRates = function(gameState, currentRates)
BaseManager.prototype.assignRolelessUnits = function(gameState, roleless)
{
if (!roleless)
roleless = this.units.filter(API3.Filters.not(API3.Filters.byHasMetadata(PlayerID, "role"))).values();
roleless = this.units.filter(filters.not(filters.byHasMetadata(PlayerID, "role"))).values();
for (const ent of roleless)
{
@@ -644,9 +659,9 @@ BaseManager.prototype.switchGatherer = function(gameState, from, to, number)
let num = number;
let only;
const gatherers = this.gatherersByType(gameState, from);
if (from == "food" && gatherers.filter(API3.Filters.byClass("CitizenSoldier")).hasEntities())
if (from == "food" && gatherers.filter(filters.byClass("CitizenSoldier")).hasEntities())
only = "CitizenSoldier";
else if (to == "food" && gatherers.filter(API3.Filters.byClass("FemaleCitizen")).hasEntities())
else if (to == "food" && gatherers.filter(filters.byClass("FemaleCitizen")).hasEntities())
only = "FemaleCitizen";
for (const ent of gatherers.values())
@@ -670,7 +685,7 @@ BaseManager.prototype.reassignIdleWorkers = function(gameState, idleWorkers)
// Search for idle workers, and tell them to gather resources based on demand
if (!idleWorkers)
{
const filter = API3.Filters.byMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE);
const filter = filters.byMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE);
idleWorkers = gameState.updatingCollection("idle-workers-base-" + this.ID, filter, this.workers).values();
}
@@ -720,13 +735,14 @@ BaseManager.prototype.reassignIdleWorkers = function(gameState, idleWorkers)
BaseManager.prototype.workersBySubrole = function(gameState, subrole)
{
return gameState.updatingCollection("subrole-" + subrole +"-base-" + this.ID, API3.Filters.byMetadata(PlayerID, "subrole", subrole), this.workers);
return gameState.updatingCollection("subrole-" + subrole +"-base-" + this.ID,
filters.byMetadata(PlayerID, "subrole", subrole), this.workers);
};
BaseManager.prototype.gatherersByType = function(gameState, type)
{
return gameState.updatingCollection("workers-gathering-" + type +"-base-" +
this.ID, API3.Filters.byMetadata(PlayerID, "gather-type", type),
this.ID, filters.byMetadata(PlayerID, "gather-type", type),
this.workersBySubrole(gameState, Worker.SUBROLE_GATHERER));
};
@@ -779,7 +795,8 @@ BaseManager.prototype.pickBuilders = function(gameState, workers, number)
*/
BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
{
let foundations = this.buildings.filter(API3.Filters.and(API3.Filters.isFoundation(), API3.Filters.not(API3.Filters.byClass("Field"))));
let foundations = this.buildings.filter(filters.and(filters.isFoundation(),
filters.not(filters.byClass("Field"))));
const damagedBuildings = this.buildings.filter(ent => ent.foundationProgress() === undefined && ent.needsRepair());
@@ -789,12 +806,13 @@ BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
const workers = this.workers.filter(ent => ent.isBuilder());
const builderWorkers = this.workersBySubrole(gameState, Worker.SUBROLE_BUILDER);
const idleBuilderWorkers = builderWorkers.filter(API3.Filters.isIdle());
const idleBuilderWorkers = builderWorkers.filter(filters.isIdle());
// if we're constructing and we have the foundations to our base anchor, only try building that.
if (this.constructing && foundations.filter(API3.Filters.byMetadata(PlayerID, "baseAnchor", true)).hasEntities())
if (this.constructing && foundations.filter(filters.byMetadata(PlayerID, "baseAnchor", true))
.hasEntities())
{
foundations = foundations.filter(API3.Filters.byMetadata(PlayerID, "baseAnchor", true));
foundations = foundations.filter(filters.byMetadata(PlayerID, "baseAnchor", true));
const tID = foundations.toEntityArray()[0].id();
workers.forEach(ent => {
const target = ent.getMetadata(PlayerID, "target-foundation");
@@ -887,8 +905,10 @@ BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
if (ent.getMetadata(PlayerID, "target-foundation") !== undefined)
return;
if (assigned >= targetNB || !ent.position() ||
API3.SquareVectorDistance(ent.position(), target.position()) > 40000)
SquareVectorDistance(ent.position(), target.position()) > 40000)
{
return;
}
++assigned;
++builderTot;
ent.setMetadata(PlayerID, "target-foundation", target.id());
@@ -908,13 +928,13 @@ BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
}).toEntityArray();
const time = target.buildTime();
nonBuilderWorkers.sort((workerA, workerB) => {
let coeffA = API3.SquareVectorDistance(target.position(), workerA.position());
let coeffA = SquareVectorDistance(target.position(), workerA.position());
// elephant moves slowly, so when far away they are only useful if build time is long
if (workerA.hasClass("Elephant"))
coeffA *= 0.5 * (1 + Math.sqrt(coeffA)/5/time);
else if (workerA.getMetadata(PlayerID, "gather-type") == "food")
coeffA *= 3;
let coeffB = API3.SquareVectorDistance(target.position(), workerB.position());
let coeffB = SquareVectorDistance(target.position(), workerB.position());
if (workerB.hasClass("Elephant"))
coeffB *= 0.5 * (1 + Math.sqrt(coeffB)/5/time);
else if (workerB.getMetadata(PlayerID, "gather-type") == "food")
@@ -975,7 +995,7 @@ BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
if (ent.getMetadata(PlayerID, "target-foundation") !== undefined)
return;
if (assigned >= targetNB || !ent.position() ||
API3.SquareVectorDistance(ent.position(), target.position()) > 40000)
SquareVectorDistance(ent.position(), target.position()) > 40000)
return;
++assigned;
++builderTot;
@@ -1028,7 +1048,10 @@ BaseManager.prototype.update = function(gameState, queues, events)
if (!bestBase)
{
if (ent.hasClass("Dock"))
API3.warn("Petra: dock in 'noBase' baseManager. It may be useful to do an anchorless base for " + ent.templateName());
{
aiWarn("Petra: dock in 'noBase' baseManager. It may be useful to " +
"do an anchorless base for " + ent.templateName());
}
continue;
}
if (ent.resourceDropsiteTypes())
@@ -1111,12 +1134,13 @@ BaseManager.prototype.update = function(gameState, queues, events)
if (owner != 0 && !gameState.isPlayerAlly(owner))
{
// we're in enemy territory. If we're too close from the enemy, destroy us.
const ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
const ccEnts = gameState.updatingGlobalCollection("allCCs",
filters.byClass("CivCentre"));
for (const cc of ccEnts.values())
{
if (cc.owner() != owner)
continue;
if (API3.SquareVectorDistance(cc.position(), this.anchor.position()) > 8000)
if (SquareVectorDistance(cc.position(), this.anchor.position()) > 8000)
continue;
this.anchor.destroy();
this.basesManager.resetBaseCache();
@@ -1,3 +1,7 @@
import { EntityCollection } from "simulation/ai/common-api/entitycollection.js";
import * as filters from "simulation/ai/common-api/filters.js";
import { InfoMap } from "simulation/ai/common-api/map-module.js";
import { SquareVectorDistance, warn as aiWarn, getMapIndices } from "simulation/ai/common-api/utils.js";
import { BaseManager } from "simulation/ai/petra/baseManager.js";
import { getBestBase, getLandAccess } from "simulation/ai/petra/entityExtend.js";
import { Worker } from "simulation/ai/petra/worker.js";
@@ -26,13 +30,13 @@ export function BasesManager(Config)
BasesManager.prototype.init = function(gameState)
{
// Initialize base map. Each pixel is a base ID, or 0 if not or not accessible.
this.basesMap = new API3.Map(gameState.sharedScript, "territory");
this.basesMap = new InfoMap(gameState.sharedScript, "territory");
this.noBase = new BaseManager(gameState, this);
this.noBase.init(gameState, BaseManager.STATE_WITH_ANCHOR);
this.noBase.accessIndex = 0;
for (const cc of gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).values())
for (const cc of gameState.getOwnStructures().filter(filters.byClass("CivCentre")).values())
if (cc.foundationProgress() === undefined)
this.createBase(gameState, cc, BaseManager.STATE_WITH_ANCHOR);
else
@@ -45,7 +49,7 @@ BasesManager.prototype.init = function(gameState)
BasesManager.prototype.postinit = function(gameState)
{
// Rebuild the base maps from the territory indices of each base.
this.basesMap = new API3.Map(gameState.sharedScript, "territory");
this.basesMap = new InfoMap(gameState.sharedScript, "territory");
for (const base of this.baseManagers)
for (const j of base.territoryIndices)
this.basesMap.map[j] = base.ID;
@@ -100,9 +104,9 @@ BasesManager.prototype.createBase = function(gameState, ent, type = BaseManager.
if (this.Config.debug > 0)
{
API3.warn(" ----------------------------------------------------------");
API3.warn(" BasesManager createBase entrance avec access " + access + " and type " + type);
API3.warn(" with access " + uneval(this.baseManagers.map(base => base.accessIndex)) +
aiWarn(" ----------------------------------------------------------");
aiWarn(" BasesManager createBase entrance avec access " + access + " and type " + type);
aiWarn(" with access " + uneval(this.baseManagers.map(base => base.accessIndex)) +
" and base nbr " + uneval(this.baseManagers.map(base => base.ID)) +
" and anchor " + uneval(this.baseManagers.map(base => !!base.anchor)));
}
@@ -327,7 +331,7 @@ BasesManager.prototype.checkEvents = function(gameState, events)
{
if (!dropsite.position() || getLandAccess(gameState, dropsite) != access)
continue;
const dist = API3.SquareVectorDistance(pos, dropsite.position());
const dist = SquareVectorDistance(pos, dropsite.position());
if (dist > distmin)
continue;
distmin = dist;
@@ -364,7 +368,7 @@ BasesManager.prototype.bulkPickWorkers = function(gameState, baseRef, number)
});
let needed = number;
const workers = new API3.EntityCollection(gameState.sharedScript);
const workers = new EntityCollection(gameState.sharedScript);
for (const base of baseBest)
{
if (base.ID == baseRef.ID)
@@ -659,10 +663,10 @@ BasesManager.prototype.removeBaseFromTerritoryIndex = function(territoryIndex)
if (index != -1)
base.territoryIndices.splice(index, 1);
else
API3.warn(" problem in headquarters::updateTerritories for base " + baseID);
aiWarn(" problem in headquarters::updateTerritories for base " + baseID);
}
else
API3.warn(" problem in headquarters::updateTerritories without base " + baseID);
aiWarn(" problem in headquarters::updateTerritories without base " + baseID);
this.basesMap.map[territoryIndex] = 0;
};
@@ -674,7 +678,7 @@ BasesManager.prototype.addTerritoryIndexToBase = function(gameState, territoryIn
if (this.baseAtIndex(territoryIndex) != 0)
return false;
let landPassable = false;
const ind = API3.getMapIndices(territoryIndex, gameState.ai.HQ.territoryMap, passabilityMap);
const ind = getMapIndices(territoryIndex, gameState.ai.HQ.territoryMap, passabilityMap);
let access;
for (const k of ind)
{
@@ -695,7 +699,7 @@ BasesManager.prototype.addTerritoryIndexToBase = function(gameState, territoryIn
continue;
if (base.accessIndex != access)
continue;
const dist = API3.SquareVectorDistance(base.anchor.position(), pos);
const dist = SquareVectorDistance(base.anchor.position(), pos);
if (dist >= distmin)
continue;
distmin = dist;
@@ -719,7 +723,7 @@ BasesManager.prototype.reassignTerritories = function(deletedBase, territoryMap)
continue;
if (territoryMap.getOwnerIndex(j) != PlayerID)
{
API3.warn("Petra reassignTerritories: should never happen");
aiWarn("Petra reassignTerritories: should never happen");
this.basesMap.map[j] = 0;
continue;
}
@@ -733,7 +737,7 @@ BasesManager.prototype.reassignTerritories = function(deletedBase, territoryMap)
continue;
if (base.accessIndex != deletedBase.accessIndex)
continue;
const dist = API3.SquareVectorDistance(base.anchor.position(), pos);
const dist = SquareVectorDistance(base.anchor.position(), pos);
if (dist >= distmin)
continue;
distmin = dist;
@@ -1,3 +1,6 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { warn as 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.
@@ -30,7 +33,8 @@ BuildManager.prototype.incrementBuilderCounters = function(civ, ent, increment)
const count = this.builderCounters.get(buildable) + increment;
if (count < 0)
{
API3.warn(" Petra error in incrementBuilderCounters for " + buildable + " with count < 0");
aiWarn(" Petra error in incrementBuilderCounters for " + buildable +
" with count < 0");
continue;
}
this.builderCounters.set(buildable, count);
@@ -38,7 +42,7 @@ BuildManager.prototype.incrementBuilderCounters = function(civ, ent, increment)
else if (increment > 0)
this.builderCounters.set(buildable, increment);
else
API3.warn(" Petra error in incrementBuilderCounters for " + buildable + " not yet set");
aiWarn(" Petra error in incrementBuilderCounters for " + buildable + " not yet set");
}
};
@@ -121,7 +125,7 @@ BuildManager.prototype.findStructuresByFilter = function(gameState, filter)
*/
BuildManager.prototype.findStructureWithClass = function(gameState, classes)
{
return this.findStructuresByFilter(gameState, API3.Filters.byClasses(classes))[0];
return this.findStructuresByFilter(gameState, filters.byClasses(classes))[0];
};
BuildManager.prototype.hasBuilder = function(template)
@@ -1,3 +1,5 @@
import { warn as aiWarn } from "simulation/ai/common-api/utils.js";
// These integers must be sequential
/* eslint-disable prefer-const -- Mods should be able to change them */
export let DIFFICULTY_SANDBOX = 0;
@@ -326,7 +328,7 @@ Config.prototype.setConfig = function(gameState)
if (this.debug < 2)
return;
API3.warn(" >>> Petra bot: personality = " + uneval(this.personality));
aiWarn(" >>> Petra bot: personality = " + uneval(this.personality));
};
Config.prototype.Cheat = function(gameState)
@@ -1,3 +1,4 @@
import { SquareVectorDistance } from "simulation/ai/common-api/utils.js";
import { allowCapture, getLandAccess, getMaxStrength, isSiegeUnit, returnResources } from
"simulation/ai/petra/entityExtend.js";
import { TransportPlan } from "simulation/ai/petra/transportPlan.js";
@@ -62,7 +63,7 @@ DefenseArmy.prototype.addFoe = function(gameState, enemyId, force)
return false;
// check distance
if (!force && API3.SquareVectorDistance(ent.position(), this.foePosition) > this.compactSize)
if (!force && SquareVectorDistance(ent.position(), this.foePosition) > this.compactSize)
return false;
this.foeEntities.push(enemyId);
@@ -257,7 +258,7 @@ DefenseArmy.prototype.clear = function(gameState)
const defensiveStruct = struct.hasDefensiveFire();
if (defensiveFound && !defensiveStruct)
continue;
const dist = API3.SquareVectorDistance(posOther, pos);
const dist = SquareVectorDistance(posOther, pos);
if (distmin && dist > distmin && (defensiveFound || !defensiveStruct))
continue;
if (defensiveStruct)
@@ -332,7 +333,7 @@ DefenseArmy.prototype.assignUnit = function(gameState, entID)
this.assignedAgainst[id].length > 5 && !eEnt.hasClass("Hero") && !isSiegeUnit(eEnt))
continue;
const dist = API3.SquareVectorDistance(ent.position(), eEnt.position());
const dist = SquareVectorDistance(ent.position(), eEnt.position());
if (idMinAll === undefined || dist < distMinAll)
{
idMinAll = id;
@@ -620,7 +621,7 @@ DefenseArmy.prototype.update = function(gameState)
const ent = gameState.getEntityById(id);
if (!ent || !ent.position())
continue;
if (API3.SquareVectorDistance(ent.position(), this.foePosition) > this.breakawaySize)
if (SquareVectorDistance(ent.position(), this.foePosition) > this.breakawaySize)
{
breakaways.push(id);
if (this.removeFoe(gameState, id))
@@ -1,3 +1,5 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { SquareVectorDistance, warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { AttackPlan } from "simulation/ai/petra/attackPlan.js";
import { DefenseArmy } from "simulation/ai/petra/defenseArmy.js";
import { allowCapture, getLandAccess, getMaxStrength, isSiegeUnit } from
@@ -121,7 +123,7 @@ DefenseManager.prototype.isDangerous = function(gameState, entity)
{
if (building.foundationProgress() == 0)
continue;
if (API3.SquareVectorDistance(building.position(), entity.position()) > 30000)
if (SquareVectorDistance(building.position(), entity.position()) > 30000)
continue;
this.targetList.push(targetId);
return true;
@@ -143,11 +145,11 @@ DefenseManager.prototype.isDangerous = function(gameState, entity)
// The enemy base is either destroyed or built.
if (!target || !target.position())
continue;
if (API3.SquareVectorDistance(target.position(), entity.position()) < dist2Min)
if (SquareVectorDistance(target.position(), entity.position()) < dist2Min)
return true;
}
const ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
const ccEnts = gameState.updatingGlobalCollection("allCCs", filters.byClass("CivCentre"));
for (const cc of ccEnts.values())
{
if (!gameState.isEntityExclusiveAlly(cc) || cc.foundationProgress() == 0)
@@ -155,15 +157,17 @@ DefenseManager.prototype.isDangerous = function(gameState, entity)
const cooperation = this.GetCooperationLevel(cc.owner());
if (cooperation < 0.3 || cooperation < 0.6 && !!cc.foundationProgress())
continue;
if (API3.SquareVectorDistance(cc.position(), entity.position()) < dist2Min)
if (SquareVectorDistance(cc.position(), entity.position()) < dist2Min)
return true;
}
for (const building of gameState.getOwnStructures().values())
{
if (building.foundationProgress() == 0 ||
API3.SquareVectorDistance(building.position(), entity.position()) > dist2Min)
SquareVectorDistance(building.position(), entity.position()) > dist2Min)
{
continue;
}
if (!this.territoryMap.isBlinking(building.position()) || gameState.ai.HQ.isDefendable(building))
return true;
}
@@ -178,8 +182,10 @@ DefenseManager.prototype.isDangerous = function(gameState, entity)
for (const building of gameState.getAllyStructures(territoryOwner).values())
{
if (building.foundationProgress() == 0 ||
API3.SquareVectorDistance(building.position(), entity.position()) > dist2Min)
SquareVectorDistance(building.position(), entity.position()) > dist2Min)
{
continue;
}
if (!this.territoryMap.isBlinking(building.position()))
return true;
}
@@ -302,8 +308,10 @@ DefenseManager.prototype.checkEnemyArmies = function(gameState)
{
const otherArmy = this.armies[j];
if (otherArmy.getType() != "default" ||
API3.SquareVectorDistance(army.foePosition, otherArmy.foePosition) > this.armyMergeSize)
SquareVectorDistance(army.foePosition, otherArmy.foePosition) > this.armyMergeSize)
{
continue;
}
// No need to clear here.
army.merge(gameState, otherArmy);
this.armies.splice(j--, 1);
@@ -351,7 +359,7 @@ DefenseManager.prototype.checkEnemyArmies = function(gameState)
// Army in neutral territory.
// TODO check smaller distance with all our buildings instead of only ccs with big distance.
let stillDangerous = false;
const bases = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
const bases = gameState.updatingGlobalCollection("allCCs", filters.byClass("CivCentre"));
for (const base of bases.values())
{
if (!gameState.isEntityAlly(base))
@@ -359,19 +367,19 @@ DefenseManager.prototype.checkEnemyArmies = function(gameState)
const cooperation = this.GetCooperationLevel(base.owner());
if (cooperation < 0.3 && !gameState.isEntityOwn(base))
continue;
if (API3.SquareVectorDistance(base.position(), army.foePosition) > 40000)
if (SquareVectorDistance(base.position(), army.foePosition) > 40000)
continue;
if (this.Config.debug > 1)
API3.warn("army in neutral territory, but still near one of our CC");
aiWarn("army in neutral territory, but still near one of our CC");
stillDangerous = true;
break;
}
if (stillDangerous)
continue;
// Need to also check docks because of oversea bases.
for (const dock of gameState.getOwnStructures().filter(API3.Filters.byClass("Dock")).values())
for (const dock of gameState.getOwnStructures().filter(filters.byClass("Dock")).values())
{
if (API3.SquareVectorDistance(dock.position(), army.foePosition) > 10000)
if (SquareVectorDistance(dock.position(), army.foePosition) > 10000)
continue;
stillDangerous = true;
break;
@@ -409,7 +417,7 @@ DefenseManager.prototype.assignDefenders = function(gameState)
break;
}
if (!armyAccess)
API3.warn(" Petra error: attacking army " + army.ID + " without access");
aiWarn(" Petra error: attacking army " + army.ID + " without access");
army.recalculatePosition(gameState);
armiesNeeding.push({ "army": army, "access": armyAccess, "need": needsDef });
}
@@ -472,7 +480,8 @@ DefenseManager.prototype.assignDefenders = function(gameState)
}))
continue;
const dist = API3.SquareVectorDistance(ent.position(), armiesNeeding[a].army.foePosition);
const dist = SquareVectorDistance(ent.position(),
armiesNeeding[a].army.foePosition);
if (aMin !== undefined && dist > distMin)
continue;
aMin = a;
@@ -584,7 +593,7 @@ DefenseManager.prototype.checkEvents = function(gameState, events)
{
const pos = attacker.position();
const range = attacker.attackRange("Ranged") ? attacker.attackRange("Ranged").max + 15 : 25;
if (range * range > API3.SquareVectorDistance(pos, target.position()))
if (range * range > SquareVectorDistance(pos, target.position()))
target.moveToRange(pos[0], pos[1], range, range + 5);
}
continue;
@@ -727,7 +736,7 @@ DefenseManager.prototype.checkEvents = function(gameState, events)
if (!entOrderData || !entOrderData.length || !entOrderData[0].target ||
entOrderData[0].target != orderData[0].target)
continue;
const dist = API3.SquareVectorDistance(pos, ent.position());
const dist = SquareVectorDistance(pos, ent.position());
if (minEnt && dist > minDist)
continue;
minDist = dist;
@@ -759,7 +768,7 @@ DefenseManager.prototype.garrisonUnitsInside = function(gameState, target, data)
const attackTypes = target.attackTypes();
if (!attackTypes || attackTypes.indexOf("Ranged") == -1)
return false;
const dist = API3.SquareVectorDistance(data.attacker.position(), target.position());
const dist = SquareVectorDistance(data.attacker.position(), target.position());
const range = target.attackRange("Ranged").max;
if (dist >= range*range)
return false;
@@ -847,7 +856,7 @@ DefenseManager.prototype.garrisonSiegeUnit = function(gameState, unit)
continue;
if (getLandAccess(gameState, ent) != unitAccess)
continue;
const dist = API3.SquareVectorDistance(ent.position(), unit.position());
const dist = SquareVectorDistance(ent.position(), unit.position());
if (dist > distmin)
continue;
distmin = dist;
@@ -884,7 +893,7 @@ DefenseManager.prototype.garrisonAttackedUnit = function(gameState, unit, emerge
continue;
if (getLandAccess(gameState, ent) != unitAccess)
continue;
const dist = API3.SquareVectorDistance(ent.position(), unit.position());
const dist = SquareVectorDistance(ent.position(), unit.position());
if (dist > distmin)
continue;
distmin = dist;
@@ -936,7 +945,7 @@ DefenseManager.prototype.switchToAttack = function(gameState, army)
const ent = gameState.getEntityById(entId);
if (!ent || !ent.position() || getLandAccess(gameState, ent) != targetAccess)
continue;
if (API3.SquareVectorDistance(targetPos, ent.position()) > 14400)
if (SquareVectorDistance(targetPos, ent.position()) > 14400)
continue;
gameState.ai.HQ.attackManager.switchDefenseToAttack(gameState, target, { "armyID": army.ID, "uniqueTarget": true });
return;
@@ -1,3 +1,4 @@
import { warn as aiWarn } from "simulation/ai/common-api/utils.js";
import * as chat from "simulation/ai/petra/chatHelper.js";
/**
@@ -108,7 +109,7 @@ DiplomacyManager.prototype.tributes = function(gameState)
this.nextTributeRequest.set(res, gameState.ai.elapsedTime + 240);
chat.requestTribute(gameState, res);
if (this.Config.debug > 1)
API3.warn("Tribute on " + res + " requested to player " + i);
aiWarn("Tribute on " + res + " requested to player " + i);
break;
}
}
@@ -117,7 +118,7 @@ DiplomacyManager.prototype.tributes = function(gameState)
if (!toSend)
continue;
if (this.Config.debug > 1)
API3.warn("Tribute " + uneval(tribute) + " sent to player " + i);
aiWarn("Tribute " + uneval(tribute) + " sent to player " + i);
if (this.Config.chat)
chat.sentTribute(gameState, i);
Engine.PostCommand(PlayerID, { "type": "tribute", "player": i, "amounts": tribute });
@@ -140,7 +141,10 @@ DiplomacyManager.prototype.checkEvents = function(gameState, events)
if (request.wanted <= 0)
{
if (this.Config.debug > 1)
API3.warn("Player " + uneval(evt.from) + " has sent the required tribute amount");
{
aiWarn("Player " + uneval(evt.from) +
" has sent the required tribute amount");
}
this.changePlayerDiplomacy(gameState, evt.from, request.requestType);
request.status = "accepted";
@@ -239,7 +243,10 @@ DiplomacyManager.prototype.checkEvents = function(gameState, events)
this.handleDiplomacyRequest(gameState, evt.source, evt.to);
const request = this.receivedDiplomacyRequests.get(evt.source);
if (this.Config.debug > 0)
API3.warn("Responding to diplomacy request from AI player " + evt.source + " with " + uneval(request));
{
aiWarn("Responding to diplomacy request from AI player " + evt.source + " with " +
uneval(request));
}
// Our diplomacy will have changed already if the response was "accept"
if (request.status === "waitingForTribute")
@@ -267,7 +274,10 @@ DiplomacyManager.prototype.checkEvents = function(gameState, events)
const responseTribute = {};
responseTribute[evt.resourceType] = evt.resourceWanted;
if (this.Config.debug > 0)
API3.warn("Responding to tribute request from AI player " + evt.source + " with " + uneval(responseTribute));
{
aiWarn("Responding to tribute request from AI player " + evt.source + " with " +
uneval(responseTribute));
}
Engine.PostCommand(PlayerID, { "type": "tribute", "player": evt.source, "amounts": responseTribute });
this.nextTributeUpdate = gameState.ai.elapsedTime + 15;
}
@@ -324,7 +334,7 @@ DiplomacyManager.prototype.lastManStandingCheck = function(gameState)
if (gameState.getVictoryConditions().has("wonder"))
{
const wonder = gameState.getEnemyStructures(i).filter(API3.Filters.byClass("Wonder"))[0];
const wonder = gameState.getEnemyStructures(i).filter(filters.byClass("Wonder"))[0];
if (wonder)
{
const wonderProgess = wonder.foundationProgress();
@@ -339,8 +349,8 @@ DiplomacyManager.prototype.lastManStandingCheck = function(gameState)
if (gameState.getVictoryConditions().has("capture_the_relic"))
{
const relicsCount = gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic"))
.filter(relic => relic.owner() === i).length;
const relicsCount = gameState.updatingGlobalCollection("allRelics",
filters.byClass("Relic")).filter(relic => relic.owner() === i).length;
turnFactor += relicsCount * this.betrayWeighting;
}
@@ -442,7 +452,7 @@ DiplomacyManager.prototype.changePlayerDiplomacy = function(gameState, player, n
gameState.ai.HQ.attackManager.cancelAttacksAgainstPlayer(gameState, player);
Engine.PostCommand(PlayerID, { "type": "diplomacy", "player": player, "to": newDiplomaticStance });
if (this.Config.debug > 1)
API3.warn("diplomacy stance with player " + player + " is now " + newDiplomaticStance);
aiWarn("diplomacy stance with player " + player + " is now " + newDiplomaticStance);
if (this.Config.chat)
chat.newDiplomacy(gameState, player, newDiplomaticStance);
};
@@ -510,7 +520,7 @@ DiplomacyManager.prototype.sendDiplomacyRequest = function(gameState)
});
if (this.Config.debug > 0)
API3.warn("Sending diplomacy request to player " + player + " with " + requestType);
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");
};
@@ -1,3 +1,5 @@
import { SquareVectorDistance, warn as aiWarn } from "simulation/ai/common-api/utils.js";
/** returns true if this unit should be considered as a siege unit */
export function isSiegeUnit(ent)
{
@@ -34,7 +36,10 @@ export function getMaxStrength(ent, debugLevel, DamageTypeImportance, againstCla
if (DamageTypeImportance[str])
strength += DamageTypeImportance[str] * val / damageTypes.length;
else if (debugLevel > 0)
API3.warn("Petra: " + str + " unknown attackStrength in getMaxStrength (please add " + str + " to config.js).");
{
aiWarn("Petra: " + str + " unknown attackStrength in getMaxStrength (please add " +
str + " to config.js).");
}
}
const attackRange = ent.attackRange(type);
@@ -54,7 +59,7 @@ export function getMaxStrength(ent, debugLevel, DamageTypeImportance, againstCla
strength -= val / 100000;
break;
default:
API3.warn("Petra: " + str + " unknown attackTimes in getMaxStrength");
aiWarn("Petra: " + str + " unknown attackTimes in getMaxStrength");
}
}
}
@@ -68,7 +73,8 @@ export function getMaxStrength(ent, debugLevel, DamageTypeImportance, againstCla
if (DamageTypeImportance[str])
strength += DamageTypeImportance[str] * val / damageTypes.length;
else if (debugLevel > 0)
API3.warn("Petra: " + str + " unknown resistanceStrength in getMaxStrength (please add " + str + " to config.js).");
aiWarn("Petra: " + str + " unknown resistanceStrength in getMaxStrength " +
"(please add " + str + " to config.js).");
}
// ToDo: Add support for StatusEffects and Capture.
@@ -88,7 +94,7 @@ export function getLandAccess(gameState, ent)
if (holder)
return getLandAccess(gameState, holder);
API3.warn("Petra error: entity without position, but not garrisoned");
aiWarn("Petra error: entity without position, but not garrisoned");
dumpEntity(ent);
return undefined;
}
@@ -242,7 +248,7 @@ export function returnResources(gameState, ent)
continue;
if (getLandAccess(gameState, dropsite) != access)
continue;
const dist = API3.SquareVectorDistance(ent.position(), dropsite.position());
const dist = SquareVectorDistance(ent.position(), dropsite.position());
if (dist > distmin)
continue;
distmin = dist;
@@ -277,7 +283,7 @@ export function getBestBase(gameState, ent, onlyConstructedBase = false, exclude
const holder = getHolder(gameState, ent);
if (!holder || !holder.position())
{
API3.warn("Petra error: entity without position, but not garrisoned");
aiWarn("Petra error: entity without position, but not garrisoned");
dumpEntity(ent);
return gameState.ai.HQ.basesManager.baselessBase();
}
@@ -299,7 +305,7 @@ export function getBestBase(gameState, ent, onlyConstructedBase = false, exclude
if (ent.hasClass("Structure") && base.accessIndex != accessIndex)
continue;
if (base.anchor && base.anchor.position())
dist = API3.SquareVectorDistance(base.anchor.position(), pos);
dist = SquareVectorDistance(base.anchor.position(), pos);
else
{
let found = false;
@@ -307,7 +313,7 @@ export function getBestBase(gameState, ent, onlyConstructedBase = false, exclude
{
if (!structure.position())
continue;
dist = API3.SquareVectorDistance(structure.position(), pos);
dist = SquareVectorDistance(structure.position(), pos);
found = true;
break;
}
@@ -367,7 +373,7 @@ export function isNotWorthBuilding(gameState, ent)
*/
export function isLineInsideEnemyTerritory(gameState, pos1, pos2, step=70)
{
const n = Math.floor(Math.sqrt(API3.SquareVectorDistance(pos1, pos2))/step) + 1;
const n = Math.floor(Math.sqrt(SquareVectorDistance(pos1, pos2))/step) + 1;
const stepx = (pos2[0] - pos1[0]) / n;
const stepy = (pos2[1] - pos1[1]) / n;
for (let i = 1; i < n; ++i)
@@ -404,7 +410,7 @@ export function gatherTreasure(gameState, ent, water = false)
const territoryOwner = gameState.ai.HQ.territoryMap.getOwner(treasure.position());
if (territoryOwner != 0 && !gameState.isPlayerAlly(territoryOwner))
continue;
const dist = API3.SquareVectorDistance(ent.position(), treasure.position());
const dist = SquareVectorDistance(ent.position(), treasure.position());
if (dist > 120000 || territoryOwner != PlayerID && dist > 14000) // AI has no LOS, so restrict it a bit
continue;
if (dist > distmin)
@@ -424,17 +430,17 @@ export function dumpEntity(ent)
{
if (!ent)
return;
API3.warn(" >>> id " + ent.id() + " name " + ent.genericName() + " pos " + ent.position() +
" state " + ent.unitAIState());
API3.warn(" base " + ent.getMetadata(PlayerID, "base") + " >>> role " + ent.getMetadata(PlayerID, "role") +
" subrole " + ent.getMetadata(PlayerID, "subrole"));
API3.warn("owner " + ent.owner() + " health " + ent.hitpoints() + " healthMax " + ent.maxHitpoints() +
" foundationProgress " + ent.foundationProgress());
API3.warn(" garrisoning " + ent.getMetadata(PlayerID, "garrisoning") +
" garrisonHolder " + ent.getMetadata(PlayerID, "garrisonHolder") +
" plan " + ent.getMetadata(PlayerID, "plan") + " transport " + ent.getMetadata(PlayerID, "transport"));
API3.warn(" stance " + ent.getStance() + " transporter " + ent.getMetadata(PlayerID, "transporter") +
" gather-type " + ent.getMetadata(PlayerID, "gather-type") +
" target-foundation " + ent.getMetadata(PlayerID, "target-foundation") +
" PartOfArmy " + ent.getMetadata(PlayerID, "PartOfArmy"));
aiWarn(" >>> id " + ent.id() + " name " + ent.genericName() + " pos " + ent.position() + " state " +
ent.unitAIState());
aiWarn(" base " + ent.getMetadata(PlayerID, "base") + " >>> role " +
ent.getMetadata(PlayerID, "role") + " subrole " + ent.getMetadata(PlayerID, "subrole"));
aiWarn("owner " + ent.owner() + " health " + ent.hitpoints() + " healthMax " + ent.maxHitpoints() +
" foundationProgress " + ent.foundationProgress());
aiWarn(" garrisoning " + ent.getMetadata(PlayerID, "garrisoning") + " garrisonHolder " +
ent.getMetadata(PlayerID, "garrisonHolder") + " plan " + ent.getMetadata(PlayerID, "plan") +
" transport " + ent.getMetadata(PlayerID, "transport"));
aiWarn(" stance " + ent.getStance() + " transporter " + ent.getMetadata(PlayerID, "transporter") +
" gather-type " + ent.getMetadata(PlayerID, "gather-type") + " target-foundation " +
ent.getMetadata(PlayerID, "target-foundation") + " PartOfArmy " +
ent.getMetadata(PlayerID, "PartOfArmy"));
}
@@ -1,3 +1,4 @@
import { SquareVectorDistance, warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { dumpEntity, isSiegeUnit } from "simulation/ai/petra/entityExtend.js";
import { Worker } from "simulation/ai/petra/worker.js";
@@ -115,9 +116,10 @@ GarrisonManager.prototype.update = function(gameState, events)
{
if (gameState.ai.Config.debug > 0)
{
API3.warn("Petra garrison error: unit " + ent.id() + " (" + ent.genericName() +
") is expected to garrison in " + id + " (" + holder.genericName() +
"), but has no such garrison order " + uneval(ent.unitAIOrderData()));
aiWarn("Petra garrison error: unit " + ent.id() + " (" +
ent.genericName() + ") is expected to garrison in " + id + " (" +
holder.genericName() + "), but has no such garrison order " +
uneval(ent.unitAIOrderData()));
dumpEntity(ent);
}
list.splice(j--, 1);
@@ -149,7 +151,7 @@ GarrisonManager.prototype.update = function(gameState, events)
continue;
if (!ent.position())
continue;
const dist = API3.SquareVectorDistance(ent.position(), holder.position());
const dist = SquareVectorDistance(ent.position(), holder.position());
if (dist > range*range)
continue;
if (ent.hasClass("Structure"))
@@ -332,9 +334,9 @@ GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around)
default:
if (ent.getMetadata(PlayerID, "onBoard") === "onBoard") // transport is not (yet ?) managed by garrisonManager
return true;
API3.warn("unknown type in garrisonManager " + ent.getMetadata(PlayerID, "garrisonType") +
" for " + ent.genericName() + " id " + ent.id() +
" inside " + holder.genericName() + " id " + holder.id());
aiWarn("unknown type in garrisonManager " + ent.getMetadata(PlayerID, "garrisonType") +
" for " + ent.genericName() + " id " + ent.id() + " inside " + holder.genericName() +
" id " + holder.id());
ent.setMetadata(PlayerID, "garrisonType", GarrisonManager.TYPE_PROTECTION);
return true;
}
@@ -1,3 +1,6 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { ResourcesManager } from "simulation/ai/common-api/resources.js";
import { SquareVectorDistance, warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { AttackManager } from "simulation/ai/petra/attackManager.js";
import { AttackPlan } from "simulation/ai/petra/attackPlan.js";
import { BasesManager } from "simulation/ai/petra/basesManager.js";
@@ -115,9 +118,9 @@ Headquather.prototype.getSeaBetweenIndices = function(gameState, index1, index2)
if (this.Config.debug > 1)
{
API3.warn("bad path from " + index1 + " to " + index2 + " ??? " + uneval(path));
API3.warn(" regionLinks start " + uneval(gameState.ai.accessibility.regionLinks[index1]));
API3.warn(" regionLinks end " + uneval(gameState.ai.accessibility.regionLinks[index2]));
aiWarn("bad path from " + index1 + " to " + index2 + " ??? " + uneval(path));
aiWarn(" regionLinks start " + uneval(gameState.ai.accessibility.regionLinks[index1]));
aiWarn(" regionLinks end " + uneval(gameState.ai.accessibility.regionLinks[index2]));
}
return undefined;
};
@@ -575,7 +578,7 @@ Headquather.prototype.findBestTrainableUnit = function(gameState, classes, requi
bValue *= param[1];
}
else
API3.warn(" trainMoreUnits avec non prevu " + uneval(param));
aiWarn(" trainMoreUnits avec non prevu " + uneval(param));
}
return -aValue/aCost + bValue/bCost;
});
@@ -675,8 +678,9 @@ Headquather.prototype.findEconomicCCLocation = function(gameState, template, res
else if (template.get("Footprint/Circle"))
halfSize = +template.get("Footprint/Circle/@radius");
const ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
const dpEnts = gameState.getOwnDropsites().filter(API3.Filters.not(API3.Filters.byClasses(["CivCentre", "Unit"])));
const ccEnts = gameState.updatingGlobalCollection("allCCs", filters.byClass("CivCentre"));
const dpEnts = gameState.getOwnDropsites().filter(
filters.not(filters.byClasses(["CivCentre", "Unit"])));
const ccList = [];
for (const cc of ccEnts.values())
ccList.push({ "ent": cc, "pos": cc.position(), "ally": gameState.isPlayerAlly(cc.owner()) });
@@ -740,7 +744,7 @@ Headquather.prototype.findEconomicCCLocation = function(gameState, template, res
let oversea = false;
if (proximity) // This is our first cc, let's do it near our units
norm /= 1 + API3.SquareVectorDistance(proximity, pos) / scale;
norm /= 1 + SquareVectorDistance(proximity, pos) / scale;
else
{
let minDist = Math.min();
@@ -748,7 +752,7 @@ Headquather.prototype.findEconomicCCLocation = function(gameState, template, res
for (const cc of ccList)
{
const dist = API3.SquareVectorDistance(cc.pos, pos);
const dist = SquareVectorDistance(cc.pos, pos);
if (dist < nearbyRejected)
{
norm = 0;
@@ -793,7 +797,7 @@ Headquather.prototype.findEconomicCCLocation = function(gameState, template, res
{
for (const dp of dpList)
{
const dist = API3.SquareVectorDistance(dp.pos, pos);
const dist = SquareVectorDistance(dp.pos, pos);
if (dist < 3600)
{
norm = 0;
@@ -833,7 +837,7 @@ Headquather.prototype.findEconomicCCLocation = function(gameState, template, res
if (bestVal === undefined)
return false;
if (this.Config.debug > 1)
API3.warn("we have found a base for " + resource + " with best (cut=" + cut + ") = " + bestVal);
aiWarn("we have found a base for " + resource + " with best (cut=" + cut + ") = " + bestVal);
// not good enough.
if (bestVal < cut)
return false;
@@ -866,7 +870,7 @@ Headquather.prototype.findStrategicCCLocation = function(gameState, template)
// with the constraints that all CC have dist > 200 and at least one have dist < 400
// This needs at least 2 CC. Otherwise, go back to economic CC.
const ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
const ccEnts = gameState.updatingGlobalCollection("allCCs", filters.byClass("CivCentre"));
const ccList = [];
let numAllyCC = 0;
for (const cc of ccEnts.values())
@@ -919,7 +923,7 @@ Headquather.prototype.findStrategicCCLocation = function(gameState, template)
for (const cc of ccList)
{
const dist = API3.SquareVectorDistance(cc.pos, pos);
const dist = SquareVectorDistance(cc.pos, pos);
if (dist < 14000) // Reject if too near from any cc
{
minDist = 0;
@@ -974,7 +978,7 @@ Headquather.prototype.findStrategicCCLocation = function(gameState, template)
}
if (this.Config.debug > 1)
API3.warn("We've found a strategic base with bestVal = " + bestVal);
aiWarn("We've found a strategic base with bestVal = " + bestVal);
Engine.ProfileStop();
@@ -1007,9 +1011,10 @@ Headquather.prototype.findStrategicCCLocation = function(gameState, template)
*/
Headquather.prototype.findMarketLocation = function(gameState, template)
{
let markets = gameState.updatingCollection("diplo-ExclusiveAllyMarkets", API3.Filters.byClass("Trade"), gameState.getExclusiveAllyEntities()).toEntityArray();
let markets = gameState.updatingCollection("diplo-ExclusiveAllyMarkets", filters.byClass("Trade"),
gameState.getExclusiveAllyEntities()).toEntityArray();
if (!markets.length)
markets = gameState.updatingCollection("OwnMarkets", API3.Filters.byClass("Trade"), gameState.getOwnStructures()).toEntityArray();
markets = gameState.updatingCollection("OwnMarkets", filters.byClass("Trade"), gameState.getOwnStructures()).toEntityArray();
if (!markets.length) // this is the first market. For the time being, place it arbitrarily by the ConstructionPlan
return [-1, -1, -1, 0];
@@ -1074,7 +1079,7 @@ Headquather.prototype.findMarketLocation = function(gameState, template)
continue;
if (!gainMultiplier)
continue;
const distSq = API3.SquareVectorDistance(market.position(), pos);
const distSq = SquareVectorDistance(market.position(), pos);
if (gainMultiplier * distSq > maxVal)
{
maxVal = gainMultiplier * distSq;
@@ -1096,13 +1101,13 @@ Headquather.prototype.findMarketLocation = function(gameState, template)
}
if (this.Config.debug > 1)
API3.warn("We found a market position with bestVal = " + bestVal);
aiWarn("We found a market position with bestVal = " + bestVal);
if (bestVal === undefined) // no constraints. For the time being, place it arbitrarily by the ConstructionPlan
return [-1, -1, -1, 0];
const expectedGain = Math.round(bestGainMult * TradeGain(bestDistSq, gameState.sharedScript.mapSize));
if (this.Config.debug > 1)
API3.warn("this would give a trading gain of " + expectedGain);
aiWarn("this would give a trading gain of " + expectedGain);
// Do not keep it if gain is too small, except if this is our first Market.
let idx;
if (expectedGain < this.tradeManager.minimalGain)
@@ -1131,16 +1136,20 @@ Headquather.prototype.findDefensiveLocation = function(gameState, template)
// but requiring a minimal distance with our other defensive structures
// and not in range of any enemy defensive structure to avoid building under fire.
const ownStructures = gameState.getOwnStructures().filter(API3.Filters.byClasses(["Fortress", "Tower"])).toEntityArray();
let enemyStructures = gameState.getEnemyStructures().filter(API3.Filters.not(API3.Filters.byOwner(0))).
filter(API3.Filters.byClasses(["CivCentre", "Fortress", "Tower"]));
const ownStructures = gameState.getOwnStructures().filter(filters.byClasses(["Fortress", "Tower"]))
.toEntityArray();
let enemyStructures = gameState.getEnemyStructures().filter(filters.not(filters.byOwner(0)))
.filter(filters.byClasses(["CivCentre", "Fortress", "Tower"]));
if (!enemyStructures.hasEntities()) // we may be in cease fire mode, build defense against neutrals
{
enemyStructures = gameState.getNeutralStructures().filter(API3.Filters.not(API3.Filters.byOwner(0))).
filter(API3.Filters.byClasses(["CivCentre", "Fortress", "Tower"]));
enemyStructures = gameState.getNeutralStructures().filter(filters.not(filters.byOwner(0)))
.filter(filters.byClasses(["CivCentre", "Fortress", "Tower"]));
if (!enemyStructures.hasEntities() && !gameState.getAlliedVictory())
enemyStructures = gameState.getAllyStructures().filter(API3.Filters.not(API3.Filters.byOwner(PlayerID))).
filter(API3.Filters.byClasses(["CivCentre", "Fortress", "Tower"]));
{
enemyStructures = gameState.getAllyStructures().filter(
filters.not(filters.byOwner(PlayerID))).filter(
filters.byClasses(["CivCentre", "Fortress", "Tower"]));
}
if (!enemyStructures.hasEntities())
return undefined;
}
@@ -1151,7 +1160,7 @@ Headquather.prototype.findDefensiveLocation = function(gameState, template)
let wonders;
if (wonderMode)
{
wonders = gameState.getOwnStructures().filter(API3.Filters.byClass("Wonder")).toEntityArray();
wonders = gameState.getOwnStructures().filter(filters.byClass("Wonder")).toEntityArray();
wonderMode = wonders.length != 0;
if (wonderMode)
wonderDistmin = (50 + wonders[0].footprintRadius()) * (50 + wonders[0].footprintRadius());
@@ -1203,7 +1212,7 @@ Headquather.prototype.findDefensiveLocation = function(gameState, template)
let dista = 0;
if (wonderMode)
{
dista = API3.SquareVectorDistance(wonders[0].position(), pos);
dista = SquareVectorDistance(wonders[0].position(), pos);
if (dista < wonderDistmin)
continue;
dista *= 200; // empirical factor (TODO should depend on map size) to stay near the wonder
@@ -1216,7 +1225,7 @@ Headquather.prototype.findDefensiveLocation = function(gameState, template)
const strPos = str.position();
if (!strPos)
continue;
const dist = API3.SquareVectorDistance(strPos, pos);
const dist = SquareVectorDistance(strPos, pos);
if (dist < 6400) // TODO check on true attack range instead of this 80×80
{
minDist = -1;
@@ -1234,7 +1243,7 @@ Headquather.prototype.findDefensiveLocation = function(gameState, template)
const strPos = str.position();
if (!strPos)
continue;
if (API3.SquareVectorDistance(strPos, pos) < cutDist)
if (SquareVectorDistance(strPos, pos) < cutDist)
{
minDist = -1;
break;
@@ -1446,11 +1455,12 @@ Headquather.prototype.buildMoreHouses = function(gameState, queues)
if (!houseTemplate.hasClass(entityReq.class))
continue;
const count = gameState.getOwnStructures().filter(API3.Filters.byClass(entityReq.class)).length;
const count = gameState.getOwnStructures().filter(filters.byClass(entityReq.class))
.length;
if (count < entityReq.count && this.buildManager.isUnbuildable(gameState, houseTemplateName))
{
if (this.Config.debug > 1)
API3.warn("no room to place a house ... try to be less restrictive");
aiWarn("no room to place a house ... try to be less restrictive");
this.buildManager.setBuildable(houseTemplateName);
this.requireHouses = true;
}
@@ -1472,15 +1482,18 @@ Headquather.prototype.buildMoreHouses = function(gameState, queues)
{
const houseTemplate = gameState.getTemplate(gameState.applyCiv(houseTemplateString));
if (!this.phasing || gameState.getPhaseEntityRequirements(this.phasing).every(req =>
!houseTemplate.hasClass(req.class) || gameState.getOwnStructures().filter(API3.Filters.byClass(req.class)).length >= req.count))
!houseTemplate.hasClass(req.class) ||
gameState.getOwnStructures().filter(filters.byClass(req.class)).length >= req.count))
{
this.requireHouses = undefined;
}
}
// When population limit too tight
// - if no room to build, try to improve with technology
// - otherwise increase temporarily the priority of houses
const house = gameState.applyCiv(houseTemplateString);
const HouseNb = gameState.getOwnFoundations().filter(API3.Filters.byClass("House")).length;
const HouseNb = gameState.getOwnFoundations().filter(filters.byClass("House")).length;
const popBonus = gameState.getTemplate(house).getPopulationBonus();
const freeSlots = gameState.getPopulationLimit() + HouseNb*popBonus - this.getAccountedPopulation(gameState);
let priority;
@@ -1489,7 +1502,7 @@ Headquather.prototype.buildMoreHouses = function(gameState, queues)
if (this.buildManager.isUnbuildable(gameState, house))
{
if (this.Config.debug > 1)
API3.warn("no room to place a house ... try to improve with technology");
aiWarn("no room to place a house ... try to improve with technology");
this.researchManager.researchPopulationBonus(gameState, queues);
}
else
@@ -1517,7 +1530,7 @@ Headquather.prototype.checkBaseExpansion = function(gameState, queues)
if (this.buildManager.numberMissingRoom(gameState) > 1)
{
if (this.Config.debug > 2)
API3.warn("try to build a new base because not enough room to build ");
aiWarn("try to build a new base because not enough room to build ");
this.buildNewBase(gameState, queues);
return;
}
@@ -1531,7 +1544,10 @@ Headquather.prototype.checkBaseExpansion = function(gameState, queues)
if (numUnits > activeBases * (65 + numvar + (10 + numvar)*(activeBases-1)) || this.saveResources && numUnits > 50)
{
if (this.Config.debug > 2)
API3.warn("try to build a new base because of population " + numUnits + " for " + activeBases + " CCs");
{
aiWarn("try to build a new base because of population " + numUnits + " for " +
activeBases + " CCs");
}
this.buildNewBase(gameState, queues);
}
};
@@ -1540,13 +1556,13 @@ Headquather.prototype.buildNewBase = function(gameState, queues, resource)
{
if (this.hasPotentialBase() && this.currentPhase == 1 && !gameState.isResearching(gameState.getPhaseName(2)))
return false;
if (gameState.getOwnFoundations().filter(API3.Filters.byClass("CivCentre")).hasEntities() || queues.civilCentre.hasQueuedUnits())
if (gameState.getOwnFoundations().filter(filters.byClass("CivCentre")).hasEntities() || queues.civilCentre.hasQueuedUnits())
return false;
let template;
// We require at least one of this civ civCentre as they may allow specific units or techs
let hasOwnCC = false;
for (const ent of gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")).values())
for (const ent of gameState.updatingGlobalCollection("allCCs", filters.byClass("CivCentre")).values())
{
if (ent.owner() != PlayerID || ent.templateName() != gameState.applyCiv("structures/{civ}/civil_centre"))
continue;
@@ -1564,7 +1580,7 @@ Headquather.prototype.buildNewBase = function(gameState, queues, resource)
// base "-1" means new base.
if (this.Config.debug > 1)
API3.warn("new base " + gameState.applyCiv(template) + " planned with resource " + resource);
aiWarn("new base " + gameState.applyCiv(template) + " planned with resource " + resource);
queues.civilCentre.addPlan(new ConstructionPlan(gameState, template, { "base": -1, "resource": resource }));
return true;
};
@@ -1664,7 +1680,7 @@ Headquather.prototype.constructTrainingBuildings = function(gameState, queues)
const numStables = gameState.getOwnEntitiesByClass("Stable", true).length;
if (this.getAccountedPopulation(gameState) > this.Config.Military.popForBarracks1 ||
this.phasing == 2 && gameState.getOwnStructures().filter(API3.Filters.byClass("Village")).length < 5)
this.phasing == 2 && gameState.getOwnStructures().filter(filters.byClass("Village")).length < 5)
{
// First barracks/range and stable.
if (numBarracks + numRanges == 0)
@@ -1763,7 +1779,7 @@ Headquather.prototype.constructTrainingBuildings = function(gameState, queues)
*/
Headquather.prototype.findBestBaseForMilitary = function(gameState)
{
const ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")).toEntityArray();
const ccEnts = gameState.updatingGlobalCollection("allCCs", filters.byClass("CivCentre")).toEntityArray();
let bestBase;
let enemyFound = false;
let distMin = Math.min();
@@ -1781,7 +1797,7 @@ Headquather.prototype.findBestBaseForMilitary = function(gameState)
continue;
if (getLandAccess(gameState, cc) != access)
continue;
const dist = API3.SquareVectorDistance(cc.position(), cce.position());
const dist = SquareVectorDistance(cc.position(), cce.position());
if (!enemyFound && isEnemy)
enemyFound = true;
else if (dist > distMin)
@@ -1829,7 +1845,7 @@ Headquather.prototype.trainEmergencyUnits = function(gameState, positions)
if (time/1000 > 5)
continue;
}
const dist = API3.SquareVectorDistance(base.anchor.position(), pos);
const dist = SquareVectorDistance(base.anchor.position(), pos);
if (nearestAnchor && dist > distmin)
continue;
distmin = dist;
@@ -1869,7 +1885,7 @@ Headquather.prototype.trainEmergencyUnits = function(gameState, positions)
continue;
if (autogarrison && !template.hasClasses(garrisonArrowClasses))
continue;
if (!total.canAfford(new API3.Resources(template.cost())))
if (!total.canAfford(new ResourcesManager(template.cost())))
continue;
templateFound = [trainable, template];
if (template.hasClass("Ranged") == rangedWanted)
@@ -1882,7 +1898,7 @@ Headquather.prototype.trainEmergencyUnits = function(gameState, positions)
// and if not, take some of other accounted resources
// TODO sort the queues to be substracted
const queueManager = gameState.ai.queueManager;
const cost = new API3.Resources(templateFound[1].cost());
const cost = new ResourcesManager(templateFound[1].cost());
queueManager.setAccounts(gameState, cost, "emergency");
if (!queueManager.canAfford("emergency", cost))
{
@@ -2070,7 +2086,7 @@ Headquather.prototype.isDangerousLocation = function(gameState, pos, radius)
Headquather.prototype.isNearInvadingArmy = function(pos)
{
for (const army of this.defenseManager.armies)
if (army.foePosition && API3.SquareVectorDistance(army.foePosition, pos) < 12000)
if (army.foePosition && SquareVectorDistance(army.foePosition, pos) < 12000)
return true;
return false;
};
@@ -2078,11 +2094,14 @@ Headquather.prototype.isNearInvadingArmy = function(pos)
Headquather.prototype.isUnderEnemyFire = function(gameState, pos, radius = 0)
{
if (!this.turnCache.firingStructures)
this.turnCache.firingStructures = gameState.updatingCollection("diplo-FiringStructures", API3.Filters.hasDefensiveFire(), gameState.getEnemyStructures());
{
this.turnCache.firingStructures = gameState.updatingCollection("diplo-FiringStructures",
filters.hasDefensiveFire(), gameState.getEnemyStructures());
}
for (const ent of this.turnCache.firingStructures.values())
{
const range = radius + ent.attackRange("Ranged").max;
if (API3.SquareVectorDistance(ent.position(), pos) < range*range)
if (SquareVectorDistance(ent.position(), pos) < range*range)
return true;
}
return false;
@@ -2221,14 +2240,15 @@ Headquather.prototype.update = function(gameState, queues, events)
this.emergencyManager.update(gameState);
this.turnCache = {};
this.territoryMap = createTerritoryMap(gameState);
this.canBarter = gameState.getOwnEntitiesByClass("Market", true).filter(API3.Filters.isBuilt()).hasEntities();
this.canBarter = gameState.getOwnEntitiesByClass("Market", true).filter(filters.isBuilt())
.hasEntities();
// TODO find a better way to update
if (this.currentPhase != gameState.currentPhase())
{
if (this.Config.debug > 0)
API3.warn(" civ " + gameState.getPlayerCiv() + " has phasedUp from " + this.currentPhase +
" to " + gameState.currentPhase() + " at time " + gameState.ai.elapsedTime +
" phasing " + this.phasing);
aiWarn(" civ " + gameState.getPlayerCiv() + " has phasedUp from " + this.currentPhase +
" to " + gameState.currentPhase() + " at time " + gameState.ai.elapsedTime +
" phasing " + this.phasing);
this.currentPhase = gameState.currentPhase();
// In principle, this.phasing should be already reset to 0 when starting the research
@@ -2354,19 +2374,19 @@ Headquather.prototype.Serialize = function()
if (this.Config.debug == -100)
{
API3.warn(" HQ serialization ---------------------");
API3.warn(" properties " + uneval(properties));
API3.warn(" basesManager " + uneval(this.basesManager.Serialize()));
API3.warn(" attackManager " + uneval(this.attackManager.Serialize()));
API3.warn(" buildManager " + uneval(this.buildManager.Serialize()));
API3.warn(" defenseManager " + uneval(this.defenseManager.Serialize()));
API3.warn(" tradeManager " + uneval(this.tradeManager.Serialize()));
API3.warn(" navalManager " + uneval(this.navalManager.Serialize()));
API3.warn(" researchManager " + uneval(this.researchManager.Serialize()));
API3.warn(" diplomacyManager " + uneval(this.diplomacyManager.Serialize()));
API3.warn(" garrisonManager " + uneval(this.garrisonManager.Serialize()));
API3.warn(" victoryManager " + uneval(this.victoryManager.Serialize()));
API3.warn(" emergencyManager " + uneval(this.emergencyManager.Serialize()));
aiWarn(" HQ serialization ---------------------");
aiWarn(" properties " + uneval(properties));
aiWarn(" basesManager " + uneval(this.basesManager.Serialize()));
aiWarn(" attackManager " + uneval(this.attackManager.Serialize()));
aiWarn(" buildManager " + uneval(this.buildManager.Serialize()));
aiWarn(" defenseManager " + uneval(this.defenseManager.Serialize()));
aiWarn(" tradeManager " + uneval(this.tradeManager.Serialize()));
aiWarn(" navalManager " + uneval(this.navalManager.Serialize()));
aiWarn(" researchManager " + uneval(this.researchManager.Serialize()));
aiWarn(" diplomacyManager " + uneval(this.diplomacyManager.Serialize()));
aiWarn(" garrisonManager " + uneval(this.garrisonManager.Serialize()));
aiWarn(" victoryManager " + uneval(this.victoryManager.Serialize()));
aiWarn(" emergencyManager " + uneval(this.emergencyManager.Serialize()));
}
return {
@@ -1,3 +1,7 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { InfoMap } from "simulation/ai/common-api/map-module.js";
import { getMapIndices } from "simulation/ai/common-api/utils.js";
/** map functions */
/* eslint-disable prefer-const -- Mods should be able to change them */
@@ -81,7 +85,7 @@ export function createObstructionMap(gameState, accessIndex, template)
}
}
const map = new API3.Map(gameState.sharedScript, "passability", obstructionTiles);
const map = new InfoMap(gameState.sharedScript, "passability", obstructionTiles);
map.setMaxVal(255);
if (template && template.buildDistance())
@@ -96,7 +100,7 @@ export function createObstructionMap(gameState, accessIndex, template)
const fromClass = distance.FromClass;
const cellSize = passabilityMap.cellSize;
const cellDist = 1 + minDist / cellSize;
const structures = gameState.getOwnStructures().filter(API3.Filters.byClass(fromClass));
const structures = gameState.getOwnStructures().filter(filters.byClass(fromClass));
for (const ent of structures.values())
{
if (!ent.position())
@@ -117,7 +121,7 @@ export function createTerritoryMap(gameState)
{
const map = gameState.ai.territoryMap;
const ret = new API3.Map(gameState.sharedScript, "territory", map.data);
const ret = new InfoMap(gameState.sharedScript, "territory", map.data);
ret.getOwner = function(p) { return this.point(p) & TERRITORY_PLAYER_MASK; };
ret.getOwnerIndex = function(p) { return this.map[p] & TERRITORY_PLAYER_MASK; };
ret.isBlinking = function(p) { return (this.point(p) & TERRITORY_BLINKING_MASK) != 0; };
@@ -145,7 +149,7 @@ export let fullFrontier_Mask = narrowFrontier_Mask | largeFrontier_Mask;
export function createBorderMap(gameState)
{
const map = new API3.Map(gameState.sharedScript, "territory");
const map = new InfoMap(gameState.sharedScript, "territory");
const width = map.width;
const border = Math.round(80 / map.cellSize);
const passabilityMap = gameState.getPassabilityMap();
@@ -162,7 +166,7 @@ export function createBorderMap(gameState)
if (radius < radcut)
continue;
map.map[j] = outside_Mask;
const ind = API3.getMapIndices(j, map, passabilityMap);
const ind = getMapIndices(j, map, passabilityMap);
for (const k of ind)
{
if (passabilityMap.data[k] & obstructionMask)
@@ -182,7 +186,7 @@ export function createBorderMap(gameState)
if (ix < border || ix >= borderCut || iy < border || iy >= borderCut)
{
map.map[j] = outside_Mask;
const ind = API3.getMapIndices(j, map, passabilityMap);
const ind = getMapIndices(j, map, passabilityMap);
for (const k of ind)
{
if (passabilityMap.data[k] & obstructionMask)
@@ -1,3 +1,5 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { SquareVectorDistance, warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { gatherTreasure, getLandAccess, getSeaAccess, isSiegeUnit, setSeaAccess } from
"simulation/ai/petra/entityExtend.js";
import { ConstructionPlan } from "simulation/ai/petra/queueplanBuilding.js";
@@ -42,15 +44,16 @@ export function NavalManager(Config)
NavalManager.prototype.init = function(gameState, deserializing)
{
// docks
this.docks = gameState.getOwnStructures().filter(API3.Filters.byClasses(["Dock", "Shipyard"]));
this.docks = gameState.getOwnStructures().filter(filters.byClasses(["Dock", "Shipyard"]));
this.docks.registerUpdates();
this.ships = gameState.getOwnUnits().filter(API3.Filters.and(API3.Filters.byClass("Ship"),
API3.Filters.not(API3.Filters.byMetadata(PlayerID, "role", Worker.ROLE_TRADER))));
this.ships = gameState.getOwnUnits().filter(filters.and(filters.byClass("Ship"),
filters.not(filters.byMetadata(PlayerID, "role", Worker.ROLE_TRADER))));
// note: those two can overlap (some transport ships are warships too and vice-versa).
this.transportShips = this.ships.filter(API3.Filters.and(API3.Filters.byCanGarrison(), API3.Filters.not(API3.Filters.byClass("FishingBoat"))));
this.warShips = this.ships.filter(API3.Filters.byClass("Warship"));
this.fishShips = this.ships.filter(API3.Filters.byClass("FishingBoat"));
this.transportShips = this.ships.filter(filters.and(filters.byCanGarrison(),
filters.not(filters.byClass("FishingBoat"))));
this.warShips = this.ships.filter(filters.byClass("Warship"));
this.fishShips = this.ships.filter(filters.byClass("FishingBoat"));
this.ships.registerUpdates();
this.transportShips.registerUpdates();
@@ -84,16 +87,16 @@ NavalManager.prototype.init = function(gameState, deserializing)
}
else
{
let collec = this.ships.filter(API3.Filters.byMetadata(PlayerID, "sea", i));
let collec = this.ships.filter(filters.byMetadata(PlayerID, "sea", i));
collec.registerUpdates();
this.seaShips.push(collec);
collec = this.transportShips.filter(API3.Filters.byMetadata(PlayerID, "sea", i));
collec = this.transportShips.filter(filters.byMetadata(PlayerID, "sea", i));
collec.registerUpdates();
this.seaTransportShips.push(collec);
collec = this.warShips.filter(API3.Filters.byMetadata(PlayerID, "sea", i));
collec = this.warShips.filter(filters.byMetadata(PlayerID, "sea", i));
collec.registerUpdates();
this.seaWarShips.push(collec);
collec = this.fishShips.filter(API3.Filters.byMetadata(PlayerID, "sea", i));
collec = this.fishShips.filter(filters.byMetadata(PlayerID, "sea", i));
collec.registerUpdates();
this.seaFishShips.push(collec);
this.wantedTransportShips.push(0);
@@ -292,7 +295,10 @@ NavalManager.prototype.checkEvents = function(gameState, queues, events)
const shipId = evt.entityObj.id();
if (this.Config.debug > 1)
API3.warn("one ship " + shipId + " from plan " + plan.ID + " destroyed during " + plan.state);
{
aiWarn("one ship " + shipId + " from plan " + plan.ID + " destroyed during " +
plan.state);
}
if (plan.state === TransportPlan.BOARDING)
{
// just reset the units onBoard metadata and wait for a new ship to be assigned to this plan
@@ -362,7 +368,10 @@ NavalManager.prototype.requireTransport = function(gameState, ent, startIndex, e
if (ent.getMetadata(PlayerID, "transport") !== undefined)
{
if (this.Config.debug > 0)
API3.warn("Petra naval manager error: unit " + ent.id() + " has already required a transport");
{
aiWarn("Petra naval manager error: unit " + ent.id() +
" has already required a transport");
}
return false;
}
@@ -391,7 +400,7 @@ NavalManager.prototype.requireTransport = function(gameState, ent, startIndex, e
if (plan.failed)
{
if (this.Config.debug > 1)
API3.warn(">>>> transport plan aborted <<<<");
aiWarn(">>>> transport plan aborted <<<<");
return false;
}
plan.init(gameState);
@@ -403,12 +412,12 @@ NavalManager.prototype.requireTransport = function(gameState, ent, startIndex, e
NavalManager.prototype.splitTransport = function(gameState, plan)
{
if (this.Config.debug > 1)
API3.warn(">>>> split of transport plan started <<<<");
aiWarn(">>>> split of transport plan started <<<<");
const newplan = new TransportPlan(gameState, [], plan.startIndex, plan.endIndex, plan.endPos);
if (newplan.failed)
{
if (this.Config.debug > 1)
API3.warn(">>>> split of transport plan aborted <<<<");
aiWarn(">>>> split of transport plan aborted <<<<");
return false;
}
newplan.init(gameState);
@@ -499,7 +508,7 @@ NavalManager.prototype.maintainFleet = function(gameState, queues)
{
if (queues.ships.hasQueuedUnits())
return;
if (!this.docks.filter(API3.Filters.isBuilt()).hasEntities())
if (!this.docks.filter(filters.isBuilt()).hasEntities())
return;
// check if we have enough transport ships per region.
for (let sea = 0; sea < this.seaShips.length; ++sea)
@@ -549,7 +558,7 @@ NavalManager.prototype.isShipBoarding = function(ship)
const plan = this.getPlan(ship.getMetadata(PlayerID, "transporter"));
if (!plan || !plan.boardingPos[ship.id()])
return false;
return API3.SquareVectorDistance(plan.boardingPos[ship.id()], ship.position()) < plan.boardingRange;
return SquareVectorDistance(plan.boardingPos[ship.id()], ship.position()) < plan.boardingRange;
};
/** let blocking ships move apart from active ships (waiting for a better pathfinder)
@@ -607,11 +616,12 @@ NavalManager.prototype.moveApart = function(gameState)
continue;
// Do not stay idle near a dock to not disturb other ships
const sea = ship.getMetadata(PlayerID, "sea");
for (const dock of gameState.getAllyStructures().filter(API3.Filters.byClass("Dock")).values())
for (const dock of
gameState.getAllyStructures().filter(filters.byClass("Dock")).values())
{
if (getSeaAccess(gameState, dock) != sea)
continue;
if (API3.SquareVectorDistance(shipPosition, dock.position()) > 4900)
if (SquareVectorDistance(shipPosition, dock.position()) > 4900)
continue;
ship.moveToRange(dock.position()[0], dock.position()[1], 70, 75);
}
@@ -619,7 +629,7 @@ NavalManager.prototype.moveApart = function(gameState)
}
}
for (const ship of gameState.ai.HQ.tradeManager.traders.filter(API3.Filters.byClass("Ship")).values())
for (const ship of gameState.ai.HQ.tradeManager.traders.filter(filters.byClass("Ship")).values())
{
const shipPosition = ship.position();
if (!shipPosition)
@@ -664,11 +674,11 @@ NavalManager.prototype.moveApart = function(gameState)
continue;
// Do not stay idle near a dock to not disturb other ships
const sea = ship.getMetadata(PlayerID, "sea");
for (const dock of gameState.getAllyStructures().filter(API3.Filters.byClass("Dock")).values())
for (const dock of gameState.getAllyStructures().filter(filters.byClass("Dock")).values())
{
if (getSeaAccess(gameState, dock) != sea)
continue;
if (API3.SquareVectorDistance(shipPosition, dock.position()) > 4900)
if (SquareVectorDistance(shipPosition, dock.position()) > 4900)
continue;
ship.moveToRange(dock.position()[0], dock.position()[1], 70, 75);
}
@@ -683,7 +693,7 @@ NavalManager.prototype.moveApart = function(gameState)
{
if (blockedIds.indexOf(blockingShip.id()) != -1 || !blockingShip.position())
continue;
const distSquare = API3.SquareVectorDistance(shipPosition, blockingShip.position());
const distSquare = SquareVectorDistance(shipPosition, blockingShip.position());
const unitAIState = blockingShip.unitAIState();
if (blockingShip.getMetadata(PlayerID, "transporter") === undefined &&
unitAIState != "INDIVIDUAL.GATHER.APPROACHING" &&
@@ -696,7 +706,8 @@ NavalManager.prototype.moveApart = function(gameState)
blockingShip.moveToRange(shipPosition[0], shipPosition[1], 30, 35);
}
for (const blockingShip of gameState.ai.HQ.tradeManager.traders.filter(API3.Filters.byClass("Ship")).values())
for (const blockingShip of
gameState.ai.HQ.tradeManager.traders.filter(filters.byClass("Ship")).values())
{
if (blockingShip.getMetadata(PlayerID, "sea") != sea)
continue;
@@ -705,7 +716,7 @@ NavalManager.prototype.moveApart = function(gameState)
const role = blockingShip.getMetadata(PlayerID, "role");
if (role === undefined || role !== Worker.ROLE_TRADER) // already accounted before
continue;
const distSquare = API3.SquareVectorDistance(shipPosition, blockingShip.position());
const distSquare = SquareVectorDistance(shipPosition, blockingShip.position());
const unitAIState = blockingShip.unitAIState();
if (unitAIState != "INDIVIDUAL.TRADE.APPROACHINGMARKET")
{
@@ -726,7 +737,8 @@ NavalManager.prototype.buildNavalStructures = function(gameState, queues)
if (gameState.ai.HQ.getAccountedPopulation(gameState) > this.Config.Economy.popForDock)
{
if (queues.dock.countQueuedUnitsWithClass("Dock") === 0 &&
!gameState.getOwnStructures().filter(API3.Filters.and(API3.Filters.byClass("Dock"), API3.Filters.isFoundation())).hasEntities() &&
!gameState.getOwnStructures().filter(filters.and(filters.byClass("Dock"),
filters.isFoundation())).hasEntities() &&
gameState.ai.HQ.canBuild(gameState, "structures/{civ}/dock"))
{
let dockStarted = false;
@@ -755,9 +767,11 @@ NavalManager.prototype.buildNavalStructures = function(gameState, queues)
if (gameState.currentPhase() < 2 || gameState.ai.HQ.getAccountedPopulation(gameState) < this.Config.Economy.popPhase2 + 15 ||
queues.militaryBuilding.hasQueuedUnits())
return;
if (!this.docks.filter(API3.Filters.byClass("Dock")).hasEntities() ||
this.docks.filter(API3.Filters.byClass("Shipyard")).hasEntities())
if (!this.docks.filter(filters.byClass("Dock")).hasEntities() ||
this.docks.filter(filters.byClass("Shipyard")).hasEntities())
{
return;
}
// Use in priority resources to build a Market.
if (!gameState.getOwnEntitiesByClass("Market", true).hasEntities() &&
gameState.ai.HQ.canBuild(gameState, "structures/{civ}/market"))
@@ -783,17 +797,18 @@ NavalManager.prototype.getBestShip = function(gameState, sea, goal)
{
const civ = gameState.getPlayerCiv();
const trainableShips = [];
gameState.getOwnTrainingFacilities().filter(API3.Filters.byMetadata(PlayerID, "sea", sea)).forEach(function(ent) {
const trainables = ent.trainableEntities(civ);
for (const trainable of trainables)
{
if (gameState.isTemplateDisabled(trainable))
continue;
const template = gameState.getTemplate(trainable);
if (template && template.hasClass("Ship") && trainableShips.indexOf(trainable) === -1)
trainableShips.push(trainable);
}
});
gameState.getOwnTrainingFacilities().filter(filters.byMetadata(PlayerID, "sea", sea)).forEach(
function(ent) {
const trainables = ent.trainableEntities(civ);
for (const trainable of trainables)
{
if (gameState.isTemplateDisabled(trainable))
continue;
const template = gameState.getTemplate(trainable);
if (template && template.hasClass("Ship") && trainableShips.indexOf(trainable) === -1)
trainableShips.push(trainable);
}
});
let best = 0;
let bestShip;
@@ -847,7 +862,7 @@ NavalManager.prototype.update = function(gameState, queues, events)
if (remaining)
continue;
if (this.Config.debug > 1)
API3.warn("no more units on transport plan " + this.transportPlans[i].ID);
aiWarn("no more units on transport plan " + this.transportPlans[i].ID);
this.transportPlans[i].releaseAll();
this.transportPlans.splice(i--, 1);
}
@@ -1,3 +1,5 @@
import { ResourcesManager } from "simulation/ai/common-api/resources.js";
import { warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { ConstructionPlan } from "simulation/ai/petra/queueplanBuilding.js";
import { ResearchPlan } from "simulation/ai/petra/queueplanResearch.js";
import { TrainingPlan } from "simulation/ai/petra/queueplanTraining.js";
@@ -69,7 +71,7 @@ Queue.prototype.startNext = function(gameState)
*/
Queue.prototype.maxAccountWanted = function(gameState, fraction)
{
const cost = new API3.Resources();
const cost = new ResourcesManager();
if (this.plans.length > 0 && this.plans[0].isGo(gameState))
cost.add(this.plans[0].getCost());
if (this.plans.length > 1 && this.plans[1].isGo(gameState) && fraction > 0)
@@ -83,7 +85,7 @@ Queue.prototype.maxAccountWanted = function(gameState, fraction)
Queue.prototype.queueCost = function()
{
const cost = new API3.Resources();
const cost = new ResourcesManager();
for (const plan of this.plans)
cost.add(plan.getCost());
return cost;
@@ -155,7 +157,7 @@ Queue.prototype.Deserialize = function(gameState, data)
plan = new ResearchPlan(gameState, dataPlan.type);
else
{
API3.warn("Petra deserialization error: plan unknown " + uneval(dataPlan));
aiWarn("Petra deserialization error: plan unknown " + uneval(dataPlan));
continue;
}
plan.Deserialize(gameState, dataPlan);
@@ -1,3 +1,6 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { ResourcesManager } from "simulation/ai/common-api/resources.js";
import { warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { Queue } from "simulation/ai/petra/queue.js";
import { Worker } from "simulation/ai/petra/worker.js";
@@ -35,7 +38,7 @@ export function QueueManager(Config, queues)
this.queueArrays = [];
for (const q in this.queues)
{
this.accounts[q] = new API3.Resources();
this.accounts[q] = new ResourcesManager();
this.queueArrays.push([q, this.queues[q]]);
}
const priorities = this.priorities;
@@ -52,7 +55,7 @@ QueueManager.prototype.getAvailableResources = function(gameState)
QueueManager.prototype.getTotalAccountedResources = function()
{
const resources = new API3.Resources();
const resources = new ResourcesManager();
for (const key in this.queues)
resources.add(this.accounts[key]);
return resources;
@@ -60,7 +63,7 @@ QueueManager.prototype.getTotalAccountedResources = function()
QueueManager.prototype.currentNeeds = function(gameState)
{
const needed = new API3.Resources();
const needed = new ResourcesManager();
// queueArrays because it's faster.
for (const q of this.queueArrays)
{
@@ -167,14 +170,16 @@ QueueManager.prototype.printQueues = function(gameState)
numWorkers++;
}
});
API3.warn("---------- QUEUES ------------ with pop " + gameState.getPopulation() + " and workers " + numWorkers);
aiWarn("---------- QUEUES ------------ with pop " + gameState.getPopulation() + " and workers " +
numWorkers);
for (const i in this.queues)
{
const q = this.queues[i];
if (q.hasQueuedUnits())
{
API3.warn(i + ": ( with priority " + this.priorities[i] +" and accounts " + uneval(this.accounts[i]) +")");
API3.warn(" while maxAccountWanted(0.6) is " + uneval(q.maxAccountWanted(gameState, 0.6)));
aiWarn(i + ": ( with priority " + this.priorities[i] +" and accounts " +
uneval(this.accounts[i]) +")");
aiWarn(" while maxAccountWanted(0.6) is " + uneval(q.maxAccountWanted(gameState, 0.6)));
}
for (const plan of q.plans)
{
@@ -182,18 +187,18 @@ QueueManager.prototype.printQueues = function(gameState)
if (plan.number)
qStr += "x" + plan.number;
qStr += " isGo " + plan.isGo(gameState);
API3.warn(qStr);
aiWarn(qStr);
}
}
API3.warn("Accounts");
aiWarn("Accounts");
for (const p in this.accounts)
API3.warn(p + ": " + uneval(this.accounts[p]));
API3.warn("Current Resources: " + uneval(gameState.getResources()));
API3.warn("Available Resources: " + uneval(this.getAvailableResources(gameState)));
API3.warn("Wanted Gather Rates: " + uneval(gameState.ai.HQ.GetWantedGatherRates(gameState)));
API3.warn("Current Gather Rates: " + uneval(gameState.ai.HQ.GetCurrentGatherRates(gameState)));
API3.warn("Most needed resources: " + uneval(gameState.ai.HQ.pickMostNeededResources(gameState)));
API3.warn("------------------------------------");
aiWarn(p + ": " + uneval(this.accounts[p]));
aiWarn("Current Resources: " + uneval(gameState.getResources()));
aiWarn("Available Resources: " + uneval(this.getAvailableResources(gameState)));
aiWarn("Wanted Gather Rates: " + uneval(gameState.ai.HQ.GetWantedGatherRates(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()
@@ -319,7 +324,7 @@ QueueManager.prototype.distributeResources = function(gameState)
}
}
if (available < 0)
API3.warn("Petra: problem with remaining " + res + " in queueManager " + available);
aiWarn("Petra: problem with remaining " + res + " in queueManager " + available);
}
};
@@ -353,7 +358,10 @@ QueueManager.prototype.switchResource = function(gameState, res)
this.accounts[i][res] -= diff;
++otherQueue.switched;
if (this.Config.debug > 2)
API3.warn ("switching queue " + res + " from " + i + " to " + j + " in amount " + diff);
{
aiWarn("switching queue " + res + " from " + i + " to " + j + " in amount " +
diff);
}
break;
}
}
@@ -398,7 +406,8 @@ QueueManager.prototype.update = function(gameState)
this.queues[i].check(gameState); // do basic sanity checks on the queue
if (this.priorities[i] > 0)
continue;
API3.warn("QueueManager received bad priorities, please report this error: " + uneval(this.priorities));
aiWarn("QueueManager received bad priorities, please report this error: " +
uneval(this.priorities));
this.priorities[i] = 1; // TODO: make the Queue Manager not die when priorities are zero.
}
@@ -440,17 +449,26 @@ QueueManager.prototype.checkPausedQueues = function(gameState)
if (toBePaused)
{
if (q == "field" && gameState.ai.HQ.needFarm &&
!gameState.getOwnStructures().filter(API3.Filters.byClass("Field")).hasEntities())
!gameState.getOwnStructures().filter(filters.byClass("Field")).hasEntities())
{
toBePaused = false;
}
if (q == "corral" && gameState.ai.HQ.needCorral &&
!gameState.getOwnStructures().filter(API3.Filters.byClass("Field")).hasEntities())
!gameState.getOwnStructures().filter(filters.byClass("Field")).hasEntities())
{
toBePaused = false;
}
if (q == "dock" && gameState.ai.HQ.needFish &&
!gameState.getOwnStructures().filter(API3.Filters.byClass("Dock")).hasEntities())
!gameState.getOwnStructures().filter(filters.byClass("Dock")).hasEntities())
{
toBePaused = false;
}
if (q == "ships" && gameState.ai.HQ.needFish &&
!gameState.ai.HQ.navalManager.ships.filter(API3.Filters.byClass("FishingBoat")).hasEntities())
!gameState.ai.HQ.navalManager.ships.filter(filters.byClass("FishingBoat"))
.hasEntities())
{
toBePaused = false;
}
}
const queue = this.queues[q];
@@ -521,7 +539,7 @@ QueueManager.prototype.addQueue = function(queueName, priority)
this.queues[queueName] = new Queue();
this.priorities[queueName] = priority;
this.accounts[queueName] = new API3.Resources();
this.accounts[queueName] = new ResourcesManager();
this.queueArrays = [];
for (const q in this.queues)
@@ -554,7 +572,10 @@ QueueManager.prototype.getPriority = function(queueName)
QueueManager.prototype.changePriority = function(queueName, newPriority)
{
if (this.Config.debug > 1)
API3.warn(">>> Priority of queue " + queueName + " changed from " + this.priorities[queueName] + " to " + newPriority);
{
aiWarn(">>> Priority of queue " + queueName + " changed from " + this.priorities[queueName] +
" to " + newPriority);
}
if (this.queues[queueName] !== undefined)
this.priorities[queueName] = newPriority;
const priorities = this.priorities;
@@ -570,8 +591,10 @@ QueueManager.prototype.Serialize = function()
queues[q] = this.queues[q].Serialize();
accounts[q] = this.accounts[q].Serialize();
if (this.Config.debug == -100)
API3.warn("queueManager serialization: queue " + q + " >>> " +
uneval(queues[q]) + " with accounts " + uneval(accounts[q]));
{
aiWarn("queueManager serialization: queue " + q + " >>> " + uneval(queues[q]) +
" with accounts " + uneval(accounts[q]));
}
}
return {
@@ -593,7 +616,7 @@ QueueManager.prototype.Deserialize = function(gameState, data)
{
this.queues[q] = new Queue();
this.queues[q].Deserialize(gameState, data.queues[q]);
this.accounts[q] = new API3.Resources();
this.accounts[q] = new ResourcesManager();
this.accounts[q].Deserialize(data.accounts[q]);
this.queueArrays.push([q, this.queues[q]]);
}
@@ -1,3 +1,6 @@
import { ResourcesManager } from "simulation/ai/common-api/resources.js";
import { warn as aiWarn } from "simulation/ai/common-api/utils.js";
/**
* Common functions and variables to all queue plans.
*/
@@ -10,11 +13,11 @@ export function QueuePlan(gameState, type, metadata)
this.template = gameState.getTemplate(this.type);
if (!this.template)
{
API3.warn("Tried to add the inexisting template " + this.type + " to Petra.");
aiWarn("Tried to add the inexisting template " + this.type + " to Petra.");
return false;
}
this.ID = gameState.ai.uniqueIDs.plans++;
this.cost = new API3.Resources(this.template.cost());
this.cost = new ResourcesManager(this.template.cost());
this.number = 1;
this.category = "";
@@ -47,7 +50,7 @@ QueuePlan.prototype.start = function(gameState)
QueuePlan.prototype.getCost = function()
{
const costs = new API3.Resources();
const costs = new ResourcesManager();
costs.add(this.cost);
if (this.number !== 1)
costs.multiply(this.number);
@@ -1,3 +1,7 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { InfoMap } from "simulation/ai/common-api/map-module.js";
import { ResourcesManager } from "simulation/ai/common-api/resources.js";
import { SquareVectorDistance, VectorDistance, warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { getBuiltEntity, getLandAccess, getSeaAccess } from "simulation/ai/petra/entityExtend.js";
import { border_Mask, createObstructionMap, fullBorder_Mask, outside_Mask } from
"simulation/ai/petra/mapModule.js";
@@ -46,7 +50,7 @@ ConstructionPlan.prototype.start = function(gameState)
const builder = gameState.findBuilder(this.type);
if (!builder)
{
API3.warn("petra error: builder not found when starting construction.");
aiWarn("petra error: builder not found when starting construction.");
Engine.ProfileStop();
return;
}
@@ -152,8 +156,11 @@ ConstructionPlan.prototype.findGoodPosition = function(gameState)
if (gameState.ai.HQ.canBuild(gameState, templateName) && !gameState.isTemplateDisabled(templateName))
{
template = gameState.getTemplate(templateName);
if (template && gameState.getResources().canAfford(new API3.Resources(template.cost())))
if (template && gameState.getResources().canAfford(
new ResourcesManager(template.cost())))
{
this.buildOverseaDock(gameState, template);
}
}
return false;
}
@@ -183,7 +190,7 @@ ConstructionPlan.prototype.findGoodPosition = function(gameState)
// Compute each tile's closeness to friendly structures:
const placement = new API3.Map(gameState.sharedScript, "territory");
const placement = new InfoMap(gameState.sharedScript, "territory");
const cellSize = placement.cellSize; // size of each tile
let alreadyHasHouses = false;
@@ -416,8 +423,9 @@ ConstructionPlan.prototype.findDockPosition = function(gameState)
// water is a measure of the water space around, and maxWater is the max value that can be returned by checkDockPlacement
const maxRes = 10;
const maxWater = 16;
const ccEnts = oversea ? gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")) : null;
const docks = oversea ? gameState.getOwnStructures().filter(API3.Filters.byClass("Dock")) : null;
const ccEnts = oversea ? gameState.updatingGlobalCollection("allCCs", filters.byClass("CivCentre")) :
null;
const docks = oversea ? gameState.getOwnStructures().filter(filters.byClass("Dock")) : null;
// Normalisation factors (only guessed, no attempt to optimize them)
const factor = proxyAccess ? 1 : oversea ? 0.2 : 40;
for (let j = 0; j < territoryMap.length; ++j)
@@ -444,7 +452,7 @@ ConstructionPlan.prototype.findDockPosition = function(gameState)
// If proximity is given, we look for the nearest point
if (proxyAccess)
score = API3.VectorDistance(this.metadata.proximity, pos);
score = VectorDistance(this.metadata.proximity, pos);
// Bonus for resources
score += 20 * (maxRes - res);
@@ -459,7 +467,7 @@ ConstructionPlan.prototype.findDockPosition = function(gameState)
const owner = cc.owner();
if (owner != PlayerID && !gameState.isPlayerEnemy(owner))
continue;
const dist = API3.SquareVectorDistance(pos, cc.position());
const dist = SquareVectorDistance(pos, cc.position());
if (owner == PlayerID && (!ownDist || dist < ownDist))
ownDist = dist;
if (gameState.isPlayerEnemy(owner) && (!enemyDist || dist < enemyDist))
@@ -474,7 +482,7 @@ ConstructionPlan.prototype.findDockPosition = function(gameState)
{
if (getSeaAccess(gameState, dock) != navalPassMap[i])
continue;
const dist = API3.SquareVectorDistance(pos, dock.position());
const dist = SquareVectorDistance(pos, dock.position());
if (dist > dockDist)
dockDist = dist;
}
@@ -541,13 +549,13 @@ ConstructionPlan.prototype.findDockPosition = function(gameState)
*/
ConstructionPlan.prototype.buildOverseaDock = function(gameState, template)
{
const docks = gameState.getOwnStructures().filter(API3.Filters.byClass("Dock"));
const docks = gameState.getOwnStructures().filter(filters.byClass("Dock"));
if (!docks.hasEntities())
return;
const passabilityMap = gameState.getPassabilityMap();
const cellArea = passabilityMap.cellSize * passabilityMap.cellSize;
const ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
const ccEnts = gameState.updatingGlobalCollection("allCCs", filters.byClass("CivCentre"));
const land = {};
let found;
@@ -591,7 +599,7 @@ ConstructionPlan.prototype.buildOverseaDock = function(gameState, template)
if (!found)
return;
if (!gameState.ai.HQ.navalMap)
API3.warn("petra.findOverseaLand on a non-naval map??? we should never go there ");
aiWarn("petra.findOverseaLand on a non-naval map??? we should never go there ");
const oldTemplate = this.template;
const oldMetadata = this.metadata;
@@ -944,6 +952,6 @@ ConstructionPlan.prototype.Deserialize = function(gameState, data)
for (const key in data)
this[key] = data[key];
this.cost = new API3.Resources();
this.cost = new ResourcesManager();
this.cost.Deserialize(data.cost);
};
@@ -1,3 +1,4 @@
import { ResourcesManager } from "simulation/ai/common-api/resources.js";
import { QueuePlan } from "simulation/ai/petra/queueplan.js";
export function ResearchPlan(gameState, type, rush = false)
@@ -11,7 +12,7 @@ export function ResearchPlan(gameState, type, rush = false)
// Refine the estimated cost
const researchers = this.getBestResearchers(gameState, true);
if (researchers)
this.cost = new API3.Resources(this.template.cost(researchers[0]));
this.cost = new ResourcesManager(this.template.cost(researchers[0]));
this.category = "technology";
this.rush = rush;
@@ -26,7 +27,7 @@ ResearchPlan.prototype.canStart = function(gameState)
this.researchers = this.getBestResearchers(gameState);
if (!this.researchers)
return false;
this.cost = new API3.Resources(this.template.cost(this.researchers[0]));
this.cost = new ResourcesManager(this.template.cost(this.researchers[0]));
return true;
};
@@ -104,6 +105,6 @@ ResearchPlan.prototype.Deserialize = function(gameState, data)
for (const key in data)
this[key] = data[key];
this.cost = new API3.Resources();
this.cost = new ResourcesManager();
this.cost.Deserialize(data.cost);
};
@@ -1,3 +1,6 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { ResourcesManager } from "simulation/ai/common-api/resources.js";
import { warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { QueuePlan } from "simulation/ai/petra/queueplan.js";
import { Worker } from "simulation/ai/petra/worker.js";
@@ -5,14 +8,14 @@ export function TrainingPlan(gameState, type, metadata, number = 1, maxMerge = 5
{
if (!QueuePlan.call(this, gameState, type, metadata))
{
API3.warn(" Plan training " + type + " canceled");
aiWarn(" Plan training " + type + " canceled");
return false;
}
// Refine the estimated cost and add pop cost
const trainers = this.getBestTrainers(gameState);
const trainer = trainers ? trainers[0] : undefined;
this.cost = new API3.Resources(this.template.cost(trainer), +this.template._template.Cost.Population);
this.cost = new ResourcesManager(this.template.cost(trainer), +this.template._template.Cost.Population);
this.category = "unit";
this.number = number;
@@ -28,7 +31,7 @@ TrainingPlan.prototype.canStart = function(gameState)
this.trainers = this.getBestTrainers(gameState);
if (!this.trainers)
return false;
this.cost = new API3.Resources(this.template.cost(this.trainers[0]), +this.template._template.Cost.Population);
this.cost = new ResourcesManager(this.template.cost(this.trainers[0]), +this.template._template.Cost.Population);
return true;
};
@@ -43,9 +46,9 @@ TrainingPlan.prototype.getBestTrainers = function(gameState)
let allTrainers = gameState.findTrainers(this.type);
if (this.metadata && this.metadata.sea)
allTrainers = allTrainers.filter(API3.Filters.byMetadata(PlayerID, "sea", this.metadata.sea));
allTrainers = allTrainers.filter(filters.byMetadata(PlayerID, "sea", this.metadata.sea));
if (this.metadata && this.metadata.base)
allTrainers = allTrainers.filter(API3.Filters.byMetadata(PlayerID, "base", this.metadata.base));
allTrainers = allTrainers.filter(filters.byMetadata(PlayerID, "base", this.metadata.base));
if (!allTrainers || !allTrainers.hasEntities())
return undefined;
@@ -156,6 +159,6 @@ TrainingPlan.prototype.Deserialize = function(gameState, data)
for (const key in data)
this[key] = data[key];
this.cost = new API3.Resources();
this.cost = new ResourcesManager();
this.cost.Deserialize(data.cost);
};
@@ -1,3 +1,6 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { ResourcesManager } from "simulation/ai/common-api/resources.js";
import { SquareVectorDistance, warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { Config, DIFFICULTY_SANDBOX } from "simulation/ai/petra/config.js";
import { gatherTreasure, getLandAccess, isFastMoving } from "simulation/ai/petra/entityExtend.js";
import { Headquather } from "simulation/ai/petra/headquarters.js";
@@ -35,13 +38,13 @@ Headquather.prototype.gameAnalysis = function(gameState)
this.canExpand = this.Config.difficulty != DIFFICULTY_SANDBOX;
// If no base yet, check if we can construct one. If not, dispatch our units to possible tasks/attacks
this.canBuildUnits = true;
if (!gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).hasEntities())
if (!gameState.getOwnStructures().filter(filters.byClass("CivCentre")).hasEntities())
{
const template = gameState.applyCiv("structures/{civ}/civil_centre");
if (!gameState.isTemplateAvailable(template) || !gameState.getTemplate(template).available(gameState))
{
if (this.Config.debug > 1)
API3.warn(" this AI is unable to produce any units");
aiWarn(" this AI is unable to produce any units");
this.canBuildUnits = false;
this.dispatchUnits(gameState);
}
@@ -70,7 +73,10 @@ Headquather.prototype.assignStartingEntities = function(gameState)
{
// TODO should support recursive garrisoning. Make a warning for now
if (ent.isGarrisonHolder() && ent.garrisoned().length)
API3.warn("Petra warning: support for garrisoned units inside garrisoned holders not yet implemented");
{
aiWarn("Petra warning: support for garrisoned units inside garrisoned holders " +
"not yet implemented");
}
continue;
}
@@ -106,7 +112,7 @@ Headquather.prototype.regionAnalysis = function(gameState)
const accessibility = gameState.ai.accessibility;
let landIndex;
let seaIndex;
const ccEnts = gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre"));
const ccEnts = gameState.getOwnStructures().filter(filters.byClass("CivCentre"));
for (const cc of ccEnts.values())
{
const land = accessibility.getAccessValue(cc.position());
@@ -136,7 +142,7 @@ Headquather.prototype.regionAnalysis = function(gameState)
}
if (!landIndex && !seaIndex)
{
API3.warn("Petra error: it does not know how to interpret this map");
aiWarn("Petra error: it does not know how to interpret this map");
return false;
}
@@ -184,10 +190,13 @@ Headquather.prototype.regionAnalysis = function(gameState)
if (this.Config.debug < 3)
return true;
for (const region in this.landRegions)
API3.warn(" >>> zone " + region + " taille " + cellArea*gameState.ai.accessibility.regionSize[region]);
API3.warn(" navalMap " + this.navalMap);
API3.warn(" landRegions " + uneval(this.landRegions));
API3.warn(" navalRegions " + uneval(this.navalRegions));
{
aiWarn(" >>> zone " + region + " taille " +
cellArea * gameState.ai.accessibility.regionSize[region]);
}
aiWarn(" navalMap " + this.navalMap);
aiWarn(" landRegions " + uneval(this.landRegions));
aiWarn(" navalRegions " + uneval(this.navalRegions));
return true;
};
@@ -221,7 +230,7 @@ Headquather.prototype.buildFirstBase = function(gameState)
return;
const total = gameState.getResources();
let goal = "civil_centre";
if (!total.canAfford(new API3.Resources(template.cost())))
if (!total.canAfford(new ResourcesManager(template.cost())))
{
const totalExpected = gameState.getResources();
// Check for treasures around available in some maps at startup
@@ -244,12 +253,12 @@ Headquather.prototype.buildFirstBase = function(gameState)
if (type in totalExpected)
totalExpected[type] += types[type];
// If we can collect enough resources from these treasures, wait for them.
if (totalExpected.canAfford(new API3.Resources(template.cost())))
if (totalExpected.canAfford(new ResourcesManager(template.cost())))
return;
}
// not enough resource to build a cc, try with a dock to accumulate resources if none yet
if (!this.navalManager.docks.filter(API3.Filters.byClass("Dock")).hasEntities())
if (!this.navalManager.docks.filter(filters.byClass("Dock")).hasEntities())
{
if (gameState.ai.queues.dock.hasQueuedUnits())
return;
@@ -257,7 +266,7 @@ Headquather.prototype.buildFirstBase = function(gameState)
if (gameState.isTemplateDisabled(templateName))
return;
template = gameState.getTemplate(templateName);
if (!template || !total.canAfford(new API3.Resources(template.cost())))
if (!template || !total.canAfford(new ResourcesManager(template.cost())))
return;
goal = "dock";
}
@@ -290,7 +299,7 @@ Headquather.prototype.buildFirstBase = function(gameState)
{
if (land !== point.land || sea !== point.sea)
continue;
if (API3.SquareVectorDistance(point.pos, pos) > 2500)
if (SquareVectorDistance(point.pos, pos) > 2500)
continue;
point.weight += 1;
found = true;
@@ -328,17 +337,21 @@ Headquather.prototype.buildFirstBase = function(gameState)
*/
Headquather.prototype.dispatchUnits = function(gameState)
{
const allycc = gameState.getExclusiveAllyEntities().filter(API3.Filters.byClass("CivCentre")).toEntityArray();
const allycc = gameState.getExclusiveAllyEntities().filter(filters.byClass("CivCentre"))
.toEntityArray();
if (allycc.length)
{
if (this.Config.debug > 1)
API3.warn(" We have allied cc " + allycc.length + " and " + gameState.getOwnUnits().length + " units ");
{
aiWarn(" We have allied cc " + allycc.length + " and " + gameState.getOwnUnits().length +
" units ");
}
const units = gameState.getOwnUnits();
let num = Math.max(Math.min(Math.round(0.08*(1+this.Config.personality.cooperative)*units.length), 20), 5);
let num1 = Math.floor(num / 2);
let num2 = num1;
// first pass to affect ranged infantry
units.filter(API3.Filters.byClasses(["Infantry+Ranged"])).forEach(ent => {
units.filter(filters.byClasses(["Infantry+Ranged"])).forEach(ent => {
if (!num || !num1)
return;
if (ent.getMetadata(PlayerID, "allied"))
@@ -357,7 +370,7 @@ Headquather.prototype.dispatchUnits = function(gameState)
}
});
// second pass to affect melee infantry
units.filter(API3.Filters.byClasses(["Infantry+Melee"])).forEach(ent => {
units.filter(filters.byClasses(["Infantry+Melee"])).forEach(ent => {
if (!num || !num2)
return;
if (ent.getMetadata(PlayerID, "allied"))
@@ -425,7 +438,7 @@ Headquather.prototype.configFirstBase = function(gameState)
const cell = gameState.getPassabilityMap().cellSize;
startingSize = startingSize * cell * cell;
if (this.Config.debug > 1)
API3.warn("starting size " + startingSize + "(cut at 24000 for fish pushing)");
aiWarn("starting size " + startingSize + "(cut at 24000 for fish pushing)");
if (startingSize < 25000)
{
this.saveSpace = true;
@@ -464,7 +477,10 @@ Headquather.prototype.configFirstBase = function(gameState)
startingWood += this.getTotalResourceLevel(gameState, ["wood"], ["nearby", "medium", "faraway"]).wood;
if (this.Config.debug > 1)
API3.warn("startingWood: " + startingWood + " (cut at 8500 for no rush and 6000 for saveResources)");
{
aiWarn("startingWood: " + startingWood +
" (cut at 8500 for no rush and 6000 for saveResources)");
}
if (startingWood < 6000)
{
this.saveResources = true;
@@ -498,11 +514,11 @@ Headquather.prototype.configFirstBase = function(gameState)
{
// if we start with enough workers, put our available resources in this first dropsite
// same thing if our pop exceed the allowed one, as we will need several houses
const numWorkers = gameState.getOwnUnits().filter(API3.Filters.byClass("Worker")).length;
const numWorkers = gameState.getOwnUnits().filter(filters.byClass("Worker")).length;
if (numWorkers > 12 && newDP.quality > 60 ||
gameState.getPopulation() > gameState.getPopulationLimit() + 20)
{
const cost = new API3.Resources(gameState.getTemplate(newDP.templateName).cost());
const cost = new ResourcesManager(gameState.getTemplate(newDP.templateName).cost());
gameState.ai.queueManager.setAccounts(gameState, cost, "dropsites");
}
gameState.ai.queues.dropsites.addPlan(new ConstructionPlan(gameState, newDP.templateName,
@@ -1,3 +1,5 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { SquareVectorDistance, warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { newTradeRoute as chatNewTradeRoute } from "simulation/ai/petra/chatHelper.js";
import { Config, DIFFICULTY_VERY_EASY } from "simulation/ai/petra/config.js";
import { gatherTreasure, getBestBase, getLandAccess, getSeaAccess, isLineInsideEnemyTerritory } from
@@ -22,7 +24,7 @@ export function TradeManager(config)
TradeManager.prototype.init = function(gameState)
{
this.traders = gameState.getOwnUnits().filter(
API3.Filters.byMetadata(PlayerID, "role", Worker.ROLE_TRADER));
filters.byMetadata(PlayerID, "role", Worker.ROLE_TRADER));
this.traders.registerUpdates();
this.minimalGain = gameState.ai.HQ.navalMap ? 3 : 5;
};
@@ -44,7 +46,7 @@ TradeManager.prototype.trainMoreTraders = function(gameState, queues)
return;
let numTraders = this.traders.length;
let numSeaTraders = this.traders.filter(API3.Filters.byClass("Ship")).length;
let numSeaTraders = this.traders.filter(filters.byClass("Ship")).length;
let numLandTraders = numTraders - numSeaTraders;
// add traders already in training
gameState.getOwnTrainingFacilities().forEach(function(ent) {
@@ -109,8 +111,10 @@ TradeManager.prototype.trainMoreTraders = function(gameState, queues)
if (!gameState.getTemplate(template))
{
if (this.Config.debug > 0)
API3.warn("Petra error: trying to train " + template + " for civ " +
gameState.getPlayerCiv() + " but no template found.");
{
aiWarn("Petra error: trying to train " + template + " for civ " +
gameState.getPlayerCiv() + " but no template found.");
}
return;
}
queues.trader.addPlan(new TrainingPlan(gameState, template, metadata, 1, 1));
@@ -139,14 +143,17 @@ TradeManager.prototype.updateTrader = function(gameState, ent)
{
// TODO try to garrison land trader inside merchant ship when only sea routes available
if (this.Config.debug > 0)
API3.warn(" no available route for " + ent.genericName() + " " + ent.id());
aiWarn(" no available route for " + ent.genericName() + " " + ent.id());
Engine.ProfileStop();
return;
}
let nearerSource = true;
if (API3.SquareVectorDistance(route.target.position(), ent.position()) < API3.SquareVectorDistance(route.source.position(), ent.position()))
if (SquareVectorDistance(route.target.position(), ent.position()) <
SquareVectorDistance(route.source.position(), ent.position()))
{
nearerSource = false;
}
if (!ent.hasClass("Ship") && route.land != access)
{
@@ -216,7 +223,7 @@ TradeManager.prototype.setTradingGoods = function(gameState)
tradingGoods[mostNeeded[0].type] += nextNeed;
Engine.PostCommand(PlayerID, { "type": "set-trading-goods", "tradingGoods": tradingGoods });
if (this.Config.debug > 2)
API3.warn(" trading goods set to " + uneval(tradingGoods));
aiWarn(" trading goods set to " + uneval(tradingGoods));
};
/**
@@ -225,7 +232,8 @@ TradeManager.prototype.setTradingGoods = function(gameState)
*/
TradeManager.prototype.performBarter = function(gameState)
{
const barterers = gameState.getOwnEntitiesByClass("Barter", true).filter(API3.Filters.isBuilt()).toEntityArray();
const barterers = gameState.getOwnEntitiesByClass("Barter", true).filter(filters.isBuilt())
.toEntityArray();
if (barterers.length == 0)
return false;
const resBarterCodes = Resources.GetBarterableCodes();
@@ -294,11 +302,13 @@ TradeManager.prototype.performBarter = function(gameState)
const amount = available[bestToSell] > 5000 ? 500 : 100;
barterers[0].barter(buy, bestToSell, amount);
if (this.Config.debug > 2)
API3.warn("Necessity bartering: sold " + bestToSell +" for " + buy +
" >> need sell " + needs[bestToSell] + " need buy " + needs[buy] +
" rate buy " + rates[buy] + " available sell " + available[bestToSell] +
" available buy " + available[buy] + " barterRate " + bestRate +
" amount " + amount);
{
aiWarn("Necessity bartering: sold " + bestToSell +" for " + buy +
" >> need sell " + needs[bestToSell] + " need buy " + needs[buy] +
" rate buy " + rates[buy] + " available sell " + available[bestToSell] +
" available buy " + available[buy] + " barterRate " + bestRate +
" amount " + amount);
}
return true;
}
}
@@ -330,10 +340,11 @@ TradeManager.prototype.performBarter = function(gameState)
const amount = available.food > 5000 ? 500 : 100;
barterers[0].barter(bestToBuy, "food", amount);
if (this.Config.debug > 2)
API3.warn("Contingency bartering: sold food for " + bestToBuy +
" available sell " + available.food + " available buy " + available[bestToBuy] +
" barterRate " + getBarterRate(barterPrices, bestToBuy, "food") +
" amount " + amount);
{
aiWarn("Contingency bartering: sold food for " + bestToBuy + " available sell " +
available.food + " available buy " + available[bestToBuy] + " barterRate " +
getBarterRate(barterPrices, bestToBuy, "food") + " amount " + amount);
}
return true;
}
@@ -430,8 +441,9 @@ TradeManager.prototype.checkRoutes = function(gameState, accessIndex)
return false;
}
const market1 = gameState.updatingCollection("OwnMarkets", API3.Filters.byClass("Trade"), gameState.getOwnStructures());
let market2 = gameState.updatingCollection("diplo-ExclusiveAllyMarkets", API3.Filters.byClass("Trade"), gameState.getExclusiveAllyEntities());
const market1 = gameState.updatingCollection("OwnMarkets", filters.byClass("Trade"), gameState.getOwnStructures());
let market2 = gameState.updatingCollection("diplo-ExclusiveAllyMarkets", filters.byClass("Trade"),
gameState.getExclusiveAllyEntities());
if (market1.length + market2.length < 2) // We have to wait ... markets will be built soon
{
this.tradeRoute = undefined;
@@ -477,7 +489,8 @@ TradeManager.prototype.checkRoutes = function(gameState, accessIndex)
gainMultiplier = traderTemplatesGains.navalGainMultiplier;
else
continue;
const gain = Math.round(gainMultiplier * TradeGain(API3.SquareVectorDistance(m1.position(), m2.position()), mapSize));
const gain = Math.round(gainMultiplier *
TradeGain(SquareVectorDistance(m1.position(), m2.position()), mapSize));
if (gain < this.minimalGain)
continue;
if (m1.foundationProgress() === undefined && m2.foundationProgress() === undefined)
@@ -521,7 +534,7 @@ TradeManager.prototype.checkRoutes = function(gameState, accessIndex)
if (candidate.gain < 1)
{
if (this.Config.debug > 2)
API3.warn("no better trade route possible");
aiWarn("no better trade route possible");
this.tradeRoute = undefined;
return false;
}
@@ -529,10 +542,13 @@ TradeManager.prototype.checkRoutes = function(gameState, accessIndex)
if (this.Config.debug > 1 && this.tradeRoute)
{
if (candidate.gain > this.tradeRoute.gain)
API3.warn("one better trade route set with gain " + candidate.gain + " instead of " + this.tradeRoute.gain);
{
aiWarn("one better trade route set with gain " + candidate.gain + " instead of " +
this.tradeRoute.gain);
}
}
else if (this.Config.debug > 1)
API3.warn("one trade route set with gain " + candidate.gain);
aiWarn("one trade route set with gain " + candidate.gain);
this.tradeRoute = candidate;
if (this.Config.chat)
@@ -601,9 +617,13 @@ TradeManager.prototype.prospectForNewMarket = function(gameState, queues)
return;
if (!gameState.ai.HQ.canBuild(gameState, "structures/{civ}/market"))
return;
if (!gameState.updatingCollection("OwnMarkets", API3.Filters.byClass("Trade"), gameState.getOwnStructures()).hasEntities() &&
!gameState.updatingCollection("diplo-ExclusiveAllyMarkets", API3.Filters.byClass("Trade"), gameState.getExclusiveAllyEntities()).hasEntities())
if (!gameState.updatingCollection("OwnMarkets", filters.byClass("Trade"),
gameState.getOwnStructures()).hasEntities() &&
!gameState.updatingCollection("diplo-ExclusiveAllyMarkets", filters.byClass("Trade"),
gameState.getExclusiveAllyEntities()).hasEntities())
{
return;
}
const template = gameState.getTemplate(gameState.applyCiv("structures/{civ}/market"));
if (!template)
return;
@@ -624,11 +644,15 @@ TradeManager.prototype.prospectForNewMarket = function(gameState, queues)
if (this.Config.debug > 1)
{
if (this.potentialTradeRoute)
API3.warn("turn " + gameState.ai.playedTurn + "we could have a new route with gain " +
{
aiWarn("turn " + gameState.ai.playedTurn + "we could have a new route with gain " +
marketPos[3] + " instead of the present " + this.potentialTradeRoute.gain);
}
else
API3.warn("turn " + gameState.ai.playedTurn + "we could have a first route with gain " +
{
aiWarn("turn " + gameState.ai.playedTurn + "we could have a first route with gain " +
marketPos[3]);
}
}
if (!this.tradeRoute)
@@ -1,3 +1,5 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { SquareVectorDistance, VectorDistance, warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { getLandAccess } from "simulation/ai/petra/entityExtend.js";
import { Worker } from "simulation/ai/petra/worker.js";
@@ -55,7 +57,10 @@ export function TransportPlan(gameState, units, startIndex, endIndex, endPos, sh
{
this.failed = true;
if (this.debug > 1)
API3.warn("transport plan with bad path: startIndex " + startIndex + " endIndex " + endIndex);
{
aiWarn("transport plan with bad path: startIndex " + startIndex + " endIndex " +
endIndex);
}
return false;
}
}
@@ -67,8 +72,10 @@ export function TransportPlan(gameState, units, startIndex, endIndex, endPos, sh
}
if (this.debug > 1)
API3.warn("Starting a new transport plan with ID " + this.ID +
" to index " + endIndex + " with units length " + units.length);
{
aiWarn("Starting a new transport plan with ID " + this.ID + " to index " + endIndex +
" with units length " + units.length);
}
this.state = TransportPlan.BOARDING;
this.boardingPos = {};
@@ -88,9 +95,11 @@ TransportPlan.SAILING = "sailing";
TransportPlan.prototype.init = function(gameState)
{
this.units = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "transport", this.ID));
this.ships = gameState.ai.HQ.navalManager.ships.filter(API3.Filters.byMetadata(PlayerID, "transporter", this.ID));
this.transportShips = gameState.ai.HQ.navalManager.transportShips.filter(API3.Filters.byMetadata(PlayerID, "transporter", this.ID));
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));
this.transportShips = gameState.ai.HQ.navalManager.transportShips.filter(filters.byMetadata(PlayerID,
"transporter", this.ID));
this.units.registerUpdates();
this.ships.registerUpdates();
@@ -113,7 +122,7 @@ TransportPlan.prototype.countFreeSlotsOnShip = function(ship)
if (ship.hitpoints() < ship.garrisonEjectHealth() * ship.maxHitpoints())
return 0;
const occupied = ship.garrisoned().length +
this.units.filter(API3.Filters.byMetadata(PlayerID, "onBoard", ship.id())).length;
this.units.filter(filters.byMetadata(PlayerID, "onBoard", ship.id())).length;
return Math.max(ship.garrisonMax() - occupied, 0);
};
@@ -168,7 +177,7 @@ TransportPlan.prototype.assignShip = function(gameState)
return;
if (pos)
{
const dist = API3.SquareVectorDistance(pos, ship.position());
const dist = SquareVectorDistance(pos, ship.position());
if (dist > distmin)
return;
distmin = dist;
@@ -212,7 +221,7 @@ TransportPlan.prototype.removeUnit = function(gameState, unit)
unit.setMetadata(PlayerID, "onBoard", undefined);
const ship = gameState.getEntityById(shipId);
if (ship && !ship.garrisoned().length &&
!this.units.filter(API3.Filters.byMetadata(PlayerID, "onBoard", shipId)).length)
!this.units.filter(filters.byMetadata(PlayerID, "onBoard", shipId)).length)
{
this.releaseShip(ship);
this.ships.updateEnt(ship);
@@ -225,8 +234,9 @@ TransportPlan.prototype.releaseShip = function(ship)
{
if (ship.getMetadata(PlayerID, "transporter") != this.ID)
{
API3.warn(" Petra: try removing a transporter ship with " + ship.getMetadata(PlayerID, "transporter") +
" from " + this.ID + " and stance " + ship.getStance());
aiWarn(" Petra: try removing a transporter ship with " +
ship.getMetadata(PlayerID, "transporter") + " from " + this.ID + " and stance " +
ship.getStance());
return;
}
@@ -334,7 +344,7 @@ TransportPlan.prototype.onBoarding = function(gameState)
ent.setMetadata(PlayerID, "onBoard", undefined);
continue;
}
const distShip = API3.SquareVectorDistance(this.boardingPos[shipId], ship.position());
const distShip = SquareVectorDistance(this.boardingPos[shipId], ship.position());
if (!shipTested[shipId] && distShip > this.boardingRange)
{
shipTested[shipId] = true;
@@ -372,7 +382,7 @@ TransportPlan.prototype.onBoarding = function(gameState)
{
this.nTry[shipId] = 0;
if (this.debug > 1)
API3.warn("ship " + shipId + " new attempt for a landing point ");
aiWarn("ship " + shipId + " new attempt for a landing point ");
this.boardingPos[shipId] = this.getBoardingPos(gameState, ship, this.startIndex, this.sea, undefined, false);
}
ship.move(this.boardingPos[shipId][0], this.boardingPos[shipId][1]);
@@ -395,7 +405,10 @@ TransportPlan.prototype.onBoarding = function(gameState)
if (this.nTry[ent.id()] > 5)
{
if (this.debug > 1)
API3.warn("unit blocked, but no ways out of the trap ... destroy it");
{
aiWarn("unit blocked, but no ways out of the trap ... " +
"destroy it");
}
this.resetUnit(gameState, ent);
ent.destroy();
continue;
@@ -404,7 +417,7 @@ TransportPlan.prototype.onBoarding = function(gameState)
ent.moveToRange(newPos[0], newPos[1], 30, 35);
ent.garrison(ship, true);
}
else if (API3.SquareVectorDistance(this.boardingPos[shipId], newPos) > 225)
else if (SquareVectorDistance(this.boardingPos[shipId], newPos) > 225)
ent.moveToRange(this.boardingPos[shipId][0], this.boardingPos[shipId][1], 0, 15);
}
else
@@ -455,12 +468,12 @@ TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex, se
{
if (!gameState.ai.HQ.navalManager.landingZones[landIndex])
{
API3.warn(" >>> no landing zone for land " + landIndex);
aiWarn(" >>> no landing zone for land " + landIndex);
return destination;
}
else if (!gameState.ai.HQ.navalManager.landingZones[landIndex][seaIndex])
{
API3.warn(" >>> no landing zone for land " + landIndex + " and sea " + seaIndex);
aiWarn(" >>> no landing zone for land " + landIndex + " and sea " + seaIndex);
return destination;
}
@@ -469,15 +482,15 @@ TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex, se
let posmin = destination;
const width = gameState.getPassabilityMap().width;
const cell = gameState.getPassabilityMap().cellSize;
const alliedDocks = gameState.getAllyStructures().filter(API3.Filters.and(
API3.Filters.byClass("Dock"), API3.Filters.byMetadata(PlayerID, "sea", seaIndex))).toEntityArray();
const alliedDocks = gameState.getAllyStructures().filter(filters.and(filters.byClass("Dock"),
filters.byMetadata(PlayerID, "sea", seaIndex))).toEntityArray();
for (const i of gameState.ai.HQ.navalManager.landingZones[landIndex][seaIndex])
{
let pos = [i%width+0.5, Math.floor(i/width)+0.5];
pos = [cell*pos[0], cell*pos[1]];
let dist = API3.VectorDistance(startPos, pos);
let dist = VectorDistance(startPos, pos);
if (destination)
dist += API3.VectorDistance(pos, destination);
dist += VectorDistance(pos, destination);
if (avoidEnnemy)
{
const territoryOwner = gameState.ai.HQ.territoryMap.getOwner(pos);
@@ -487,9 +500,13 @@ TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex, se
// require a small distance between all ships of the transport plan to avoid path finder problems
// this is also used when the ship is blocked and we want to find a new boarding point
for (const shipId in this.boardingPos)
{
if (this.boardingPos[shipId] !== undefined &&
API3.SquareVectorDistance(this.boardingPos[shipId], pos) < this.boardingRange)
SquareVectorDistance(this.boardingPos[shipId], pos) < this.boardingRange)
{
dist += 1000000;
}
}
// and not too near our allied docks to not disturb naval traffic
let distSquare;
for (const dock of alliedDocks)
@@ -498,7 +515,7 @@ TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex, se
distSquare = 900;
else
distSquare = 4900;
const dockDist = API3.SquareVectorDistance(dock.position(), pos);
const dockDist = SquareVectorDistance(dock.position(), pos);
if (dockDist < distSquare)
dist += 100000 * (distSquare - dockDist) / distSquare;
}
@@ -530,13 +547,13 @@ TransportPlan.prototype.onSailing = function(gameState)
continue;
}
if (this.debug > 1)
API3.warn(">>> transport " + this.ID + " reloading failed ... <<<");
aiWarn(">>> transport " + this.ID + " reloading failed ... <<<");
// destroy the unit if inaccessible otherwise leave it there
const index = getLandAccess(gameState, ent);
if (gameState.ai.HQ.landRegions[index])
{
if (this.debug > 1)
API3.warn(" recovered entity kept " + ent.id());
aiWarn(" recovered entity kept " + ent.id());
this.resetUnit(gameState, ent);
// TODO we should not destroy it, but now the unit could still be reloaded on the next turn
// and mess everything
@@ -545,7 +562,7 @@ TransportPlan.prototype.onSailing = function(gameState)
else
{
if (this.debug > 1)
API3.warn("recovered entity destroyed " + ent.id());
aiWarn("recovered entity destroyed " + ent.id());
this.resetUnit(gameState, ent);
ent.destroy();
}
@@ -568,14 +585,14 @@ TransportPlan.prototype.onSailing = function(gameState)
ent.setMetadata(PlayerID, "onBoard", "onBoard");
else
{
API3.warn("Petra transportPlan problem: unit not on ship without position ???");
aiWarn("Petra transportPlan problem: unit not on ship without position ???");
this.resetUnit(gameState, ent);
ent.destroy();
}
}
else
{
API3.warn("Petra transportPlan problem: unit on ship, but no ship ???");
aiWarn("Petra transportPlan problem: unit on ship, but no ship ???");
this.resetUnit(gameState, ent);
ent.destroy();
}
@@ -584,7 +601,7 @@ TransportPlan.prototype.onSailing = function(gameState)
{
// unit unloaded on a wrong region - try to regarrison it and move a bit the ship
if (this.debug > 1)
API3.warn(">>> unit unloaded on a wrong region ! try to garrison it again <<<");
aiWarn(">>> unit unloaded on a wrong region ! try to garrison it again <<<");
const ship = gameState.getEntityById(ent.getMetadata(PlayerID, "onBoard"));
if (ship && !this.canceled)
{
@@ -596,7 +613,7 @@ TransportPlan.prototype.onSailing = function(gameState)
else
{
if (this.debug > 1)
API3.warn("no way ... we destroy it");
aiWarn("no way ... we destroy it");
this.resetUnit(gameState, ent);
ent.destroy();
}
@@ -606,7 +623,7 @@ TransportPlan.prototype.onSailing = function(gameState)
// And make some room for other units
const pos = ent.position();
const goal = ent.getMetadata(PlayerID, "endPos");
const dist = goal ? API3.VectorDistance(pos, goal) : 0;
const dist = goal ? VectorDistance(pos, goal) : 0;
if (dist > 30)
ent.moveToRange(goal[0], goal[1], dist-25, dist-20);
else
@@ -639,7 +656,7 @@ TransportPlan.prototype.onSailing = function(gameState)
if (ship.unitAIState() == "INDIVIDUAL.WALKING")
continue;
const shipId = ship.id();
const dist = API3.SquareVectorDistance(ship.position(), this.boardingPos[shipId]);
const dist = SquareVectorDistance(ship.position(), this.boardingPos[shipId]);
let remaining = 0;
for (const entId of ship.garrisoned())
{
@@ -676,7 +693,7 @@ TransportPlan.prototype.onSailing = function(gameState)
{
this.nTry[shipId] = 0;
if (this.debug > 1)
API3.warn(shipId + " new attempt for a landing point ");
aiWarn(shipId + " new attempt for a landing point ");
this.boardingPos[shipId] = this.getBoardingPos(gameState, ship, this.endIndex, this.sea, undefined, true);
}
ship.move(this.boardingPos[shipId][0], this.boardingPos[shipId][1]);
@@ -1,3 +1,5 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { SquareVectorDistance } from "simulation/ai/common-api/utils.js";
import { AttackPlan } from "simulation/ai/petra/attackPlan.js";
import { getAttackBonus, getBestBase, getLandAccess, returnResources } from
"simulation/ai/petra/entityExtend.js";
@@ -53,7 +55,8 @@ VictoryManager.prototype.init = function(gameState)
if (gameState.getVictoryConditions().has("capture_the_relic"))
{
for (const relic of gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic")).values())
for (const relic of
gameState.updatingGlobalCollection("allRelics", filters.byClass("Relic")).values())
{
if (relic.owner() == PlayerID)
this.criticalEnts.set(relic.id(), { "guardsAssigned": 0, "guards": new Map() });
@@ -622,7 +625,8 @@ VictoryManager.prototype.update = function(gameState, events, queues)
}
// And look for some new gaia relics visible by any of our units
// or that may be on our territory
const allGaiaRelics = gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic")).filter(relic => relic.owner() == 0);
const allGaiaRelics = gameState.updatingGlobalCollection("allRelics", filters.byClass("Relic"))
.filter(relic => relic.owner() == 0);
for (const relic of allGaiaRelics.values())
{
const relicPosition = relic.position();
@@ -643,7 +647,7 @@ VictoryManager.prototype.update = function(gameState, events, queues)
{
if (!ent.position() || !ent.visionRange())
continue;
if (API3.SquareVectorDistance(ent.position(), relicPosition) > Math.square(ent.visionRange()))
if (SquareVectorDistance(ent.position(), relicPosition) > Math.square(ent.visionRange()))
continue;
this.targetedGaiaRelics.set(relic.id(), []);
this.captureGaiaRelic(gameState, relic);
@@ -1,3 +1,5 @@
import * as filters from "simulation/ai/common-api/filters.js";
import { SquareVectorDistance, warn as aiWarn } from "simulation/ai/common-api/utils.js";
import { allowCapture, gatherTreasure, getBuiltEntity, getLandAccess, getSeaAccess, isFastMoving,
isSupplyFull, returnResources } from "simulation/ai/petra/entityExtend.js";
import { TransportPlan } from "simulation/ai/petra/transportPlan.js";
@@ -68,7 +70,7 @@ Worker.prototype.update = function(gameState, ent)
}
if (!hasDropsite)
{
for (const unit of gameState.getOwnUnits().filter(API3.Filters.byClass("Support")).values())
for (const unit of gameState.getOwnUnits().filter(filters.byClass("Support")).values())
{
if (!unit.position() || getLandAccess(gameState, unit) != plan.endIndex)
continue;
@@ -149,7 +151,7 @@ Worker.prototype.update = function(gameState, ent)
continue;
if (targetAccess != getLandAccess(gameState, dropsite))
continue;
if (API3.SquareVectorDistance(target.position(), dropsite.position()) < distanceSquare)
if (SquareVectorDistance(target.position(), dropsite.position()) < distanceSquare)
{
hasFoodDropsiteWithinDistance = true;
break;
@@ -750,7 +752,7 @@ Worker.prototype.startGathering = function(gameState)
// If we are here, we have nothing left to gather ... certainly no more resources of this type
gameState.ai.HQ.lastFailedGather[resource] = gameState.ai.elapsedTime;
if (gameState.ai.Config.debug > 2)
API3.warn(" >>>>> worker with gather-type " + resource + " with nothing to gather ");
aiWarn(" >>>>> worker with gather-type " + resource + " with nothing to gather ");
this.ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE);
return false;
};
@@ -790,7 +792,7 @@ Worker.prototype.startHunting = function(gameState, position)
continue;
if (supplyAccess != getLandAccess(gameState, dropsite))
continue;
if (API3.SquareVectorDistance(supplyPosition, dropsite.position()) < distSquare)
if (SquareVectorDistance(supplyPosition, dropsite.position()) < distSquare)
return true;
}
return false;
@@ -827,7 +829,7 @@ Worker.prototype.startHunting = function(gameState, position)
continue;
// measure the distance to the resource.
const dist = API3.SquareVectorDistance(entPosition, supply.position());
const dist = SquareVectorDistance(entPosition, supply.position());
if (dist > nearestSupplyDist)
continue;
@@ -884,8 +886,8 @@ Worker.prototype.startFishing = function(gameState)
let nearestSupply;
const fisherSea = getSeaAccess(gameState, this.ent);
const fishDropsites = (gameState.playerData.hasSharedDropsites ? gameState.getAnyDropsites("food") : gameState.getOwnDropsites("food")).
filter(API3.Filters.byClass("Dock")).toEntityArray();
const fishDropsites = (gameState.playerData.hasSharedDropsites ? gameState.getAnyDropsites("food") :
gameState.getOwnDropsites("food")).filter(filters.byClass("Dock")).toEntityArray();
const nearestDropsiteDist = function(supply) {
let distMin = 1000000;
@@ -900,7 +902,7 @@ Worker.prototype.startFishing = function(gameState)
continue;
if (fisherSea != getSeaAccess(gameState, dropsite))
continue;
distMin = Math.min(distMin, API3.SquareVectorDistance(pos, dropsite.position()));
distMin = Math.min(distMin, SquareVectorDistance(pos, dropsite.position()));
}
return distMin;
};
@@ -963,7 +965,8 @@ Worker.prototype.startFishing = function(gameState)
Worker.prototype.gatherNearestField = function(gameState, baseID)
{
const ownFields = gameState.getOwnEntitiesByClass("Field", true).filter(API3.Filters.isBuilt()).filter(API3.Filters.byMetadata(PlayerID, "base", baseID));
const ownFields = gameState.getOwnEntitiesByClass("Field", true).filter(filters.isBuilt())
.filter(filters.byMetadata(PlayerID, "base", baseID));
let bestFarm;
const gatherRates = this.ent.resourceGatherRates();
@@ -984,14 +987,17 @@ Worker.prototype.gatherNearestField = function(gameState, baseID)
rate = Math.pow(diminishing, num);
}
// Add a penalty distance depending on rate
const dist = API3.SquareVectorDistance(field.position(), this.ent.position()) + (1 - rate) * 160000;
const dist = SquareVectorDistance(field.position(), this.ent.position()) + (1 - rate) * 160000;
if (!bestFarm || dist < bestFarm.dist)
bestFarm = { "ent": field, "dist": dist, "rate": rate };
}
// If other field foundations available, better build them when rate becomes too small
if (!bestFarm || bestFarm.rate < 0.70 &&
gameState.getOwnFoundations().filter(API3.Filters.byClass("Field")).filter(API3.Filters.byMetadata(PlayerID, "base", baseID)).hasEntities())
if (!bestFarm || bestFarm.rate < 0.70 && gameState.getOwnFoundations()
.filter(filters.byClass("Field")).filter(filters.byMetadata(PlayerID, "base", baseID))
.hasEntities())
{
return false;
}
this.base.AddTCGatherer(bestFarm.ent.id());
this.ent.setMetadata(PlayerID, "supply", bestFarm.ent.id());
return bestFarm.ent;
@@ -1016,7 +1022,7 @@ Worker.prototype.buildAnyField = function(gameState, baseID)
if (current === undefined ||
current >= gameState.getBuiltTemplate(found.templateName()).maxGatherers())
continue;
const dist = API3.SquareVectorDistance(found.position(), pos);
const dist = SquareVectorDistance(found.position(), pos);
if (dist > bestFarmDist)
continue;
bestFarmEnt = found;
@@ -1052,7 +1058,7 @@ Worker.prototype.moveToGatherer = function(gameState, ent, forced)
{
continue;
}
const distance = API3.SquareVectorDistance(pos, gatherer.position());
const distance = SquareVectorDistance(pos, gatherer.position());
if (distance > dist)
continue;
dist = distance;
@@ -297,7 +297,6 @@ public:
ScriptFunction::Register<&CAIWorker::func, ScriptInterface::ObjectFromCBData<CAIWorker>>(rq, name);
REGISTER_FUNC_NAME(PostCommand, "PostCommand");
REGISTER_FUNC_NAME(LoadScripts, "IncludeModule");
ScriptFunction::Register<QuitEngine>(rq, "Exit");
REGISTER_FUNC_NAME(ComputePathScript, "ComputePath");
@@ -316,35 +315,6 @@ public:
bool HasLoadedEntityTemplates() const { return m_HasLoadedEntityTemplates; }
bool LoadScripts(const std::wstring& moduleName)
{
// Ignore modules that are already loaded
if (m_LoadedModules.find(moduleName) != m_LoadedModules.end())
return true;
// Mark this as loaded, to prevent it recursively loading itself
m_LoadedModules.insert(moduleName);
// Load and execute *.js
VfsPaths pathnames;
if (vfs::GetPathnames(g_VFS, L"simulation/ai/" + moduleName + L"/", L"*.js", pathnames) < 0)
{
LOGERROR("Failed to load AI scripts for module %s", utf8_from_wstring(moduleName));
return false;
}
for (const VfsPath& path : pathnames)
{
if (!m_ScriptInterface->LoadGlobalScriptFile(path))
{
LOGERROR("Failed to load script %s", path.string8());
return false;
}
}
return true;
}
void PostCommand(int playerid, JS::HandleValue cmd)
{
ScriptRequest rq(m_ScriptInterface);
@@ -442,30 +412,21 @@ public:
ScriptRequest rq(m_ScriptInterface);
// we don't need to load it.
if (!m_HasSharedComponent)
if (!std::exchange(m_HasSharedComponent, true))
return false;
// reset the value so it can be used to determine if we actually initialized it.
m_HasSharedComponent = false;
auto result = m_ScriptInterface->GetModuleLoader().LoadModule(rq,
"simulation/ai/common-api/shared.js");
if (LoadScripts(L"common-api"))
m_HasSharedComponent = true;
else
return false;
g_ScriptContext->RunJobs();
// mainly here for the error messages
OsPath path = L"simulation/ai/common-api/";
// Constructor name is SharedScript, it's in the module API3
// TODO: Hardcoding this is bad, we need a smarter way.
JS::RootedValue AIModule(rq.cx);
JS::RootedValue global(rq.cx, rq.globalValue());
JS::RootedValue AIModule(rq.cx, JS::ObjectValue(*result.begin()->Get()));
JS::RootedValue ctor(rq.cx);
if (!Script::GetProperty(rq, global, "API3", &AIModule) || AIModule.isUndefined())
{
LOGERROR("Failed to create shared AI component: %s: can't find module '%s'", path.string8(), "API3");
return false;
}
if (!Script::GetProperty(rq, AIModule, "SharedScript", &ctor)
|| ctor.isUndefined())