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:
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user