mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-08-15 14:43:32 -07:00
Use JS classes for QueuePlan
Classes derifed from `QueuePlan` used manual inherintance. Refs: #6285
This commit is contained in:
parent
327e70ea82
commit
09671b49a2
4 changed files with 1130 additions and 1141 deletions
|
|
@ -5,64 +5,62 @@ import { aiWarn } from "simulation/ai/common-api/utils.js";
|
|||
* Common functions and variables to all queue plans.
|
||||
*/
|
||||
|
||||
export function QueuePlan(gameState, type, metadata)
|
||||
export class QueuePlan
|
||||
{
|
||||
constructor(gameState, type, metadata)
|
||||
{
|
||||
this.type = gameState.applyCiv(type);
|
||||
this.metadata = metadata;
|
||||
|
||||
this.template = gameState.getTemplate(this.type);
|
||||
if (!this.template)
|
||||
{
|
||||
aiWarn("Tried to add the inexisting template " + this.type + " to Petra.");
|
||||
return false;
|
||||
}
|
||||
throw new Error("Tried to add the inexisting template " + this.type + " to Petra.");
|
||||
this.ID = gameState.ai.uniqueIDs.plans++;
|
||||
this.cost = new ResourcesManager(this.template.cost());
|
||||
this.number = 1;
|
||||
this.category = "";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Check the content of this queue */
|
||||
QueuePlan.prototype.isInvalid = function(gameState)
|
||||
{
|
||||
/** Check the content of this queue */
|
||||
isInvalid(gameState)
|
||||
{
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/** if true, the queue manager will begin increasing this plan's account. */
|
||||
QueuePlan.prototype.isGo = function(gameState)
|
||||
{
|
||||
/** if true, the queue manager will begin increasing this plan's account. */
|
||||
isGo(gameState)
|
||||
{
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** can we start this plan immediately? */
|
||||
QueuePlan.prototype.canStart = function(gameState)
|
||||
{
|
||||
/** can we start this plan immediately? */
|
||||
canStart(gameState)
|
||||
{
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/** process the plan. */
|
||||
QueuePlan.prototype.start = function(gameState)
|
||||
{
|
||||
/** process the plan. */
|
||||
start(gameState)
|
||||
{
|
||||
// should call onStart.
|
||||
};
|
||||
}
|
||||
|
||||
QueuePlan.prototype.getCost = function()
|
||||
{
|
||||
getCost()
|
||||
{
|
||||
const costs = new ResourcesManager();
|
||||
costs.add(this.cost);
|
||||
if (this.number !== 1)
|
||||
costs.multiply(this.number);
|
||||
return costs;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* On Event functions.
|
||||
* Can be used to do some specific stuffs
|
||||
* Need to be updated to actually do something if you want them to.
|
||||
* this is called by "Start" if it succeeds.
|
||||
*/
|
||||
QueuePlan.prototype.onStart = function(gameState)
|
||||
{
|
||||
};
|
||||
onStart(gameState)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,22 +12,19 @@ import { QueuePlan } from "simulation/ai/petra/queueplan.js";
|
|||
* We'll try to fing a good position if non has been provided
|
||||
*/
|
||||
|
||||
export function ConstructionPlan(gameState, type, metadata, position)
|
||||
export class ConstructionPlan extends QueuePlan
|
||||
{
|
||||
if (!QueuePlan.call(this, gameState, type, metadata))
|
||||
return false;
|
||||
constructor(gameState, type, metadata, position)
|
||||
{
|
||||
super(gameState, type, metadata);
|
||||
|
||||
this.position = position ? position : 0;
|
||||
|
||||
this.category = "building";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ConstructionPlan.prototype = Object.create(QueuePlan.prototype);
|
||||
|
||||
ConstructionPlan.prototype.canStart = function(gameState)
|
||||
{
|
||||
canStart(gameState)
|
||||
{
|
||||
if (gameState.ai.HQ.turnCache.buildingBuilt) // do not start another building if already one this turn
|
||||
return false;
|
||||
|
||||
|
|
@ -38,10 +35,10 @@ ConstructionPlan.prototype.canStart = function(gameState)
|
|||
return false;
|
||||
|
||||
return gameState.ai.HQ.buildManager.hasBuilder(this.type);
|
||||
};
|
||||
}
|
||||
|
||||
ConstructionPlan.prototype.start = function(gameState)
|
||||
{
|
||||
start(gameState)
|
||||
{
|
||||
Engine.ProfileStart("Building construction start");
|
||||
|
||||
// We don't care which builder we assign, since they won't actually do
|
||||
|
|
@ -113,10 +110,10 @@ ConstructionPlan.prototype.start = function(gameState)
|
|||
|
||||
if (this.metadata && this.metadata.proximity)
|
||||
gameState.ai.HQ.navalManager.createTransportIfNeeded(gameState, this.metadata.proximity, [pos.x, pos.z], this.metadata.access);
|
||||
};
|
||||
}
|
||||
|
||||
ConstructionPlan.prototype.findGoodPosition = function(gameState)
|
||||
{
|
||||
findGoodPosition(gameState)
|
||||
{
|
||||
let template = this.template;
|
||||
|
||||
if (template.buildPlacementType() == "shore")
|
||||
|
|
@ -360,9 +357,9 @@ ConstructionPlan.prototype.findGoodPosition = function(gameState)
|
|||
const territoryIndex = territorypos[0] + territorypos[1]*placement.width;
|
||||
// default angle = 3*Math.PI/4;
|
||||
return { "x": x, "z": z, "angle": 3*Math.PI/4, "base": HQ.baseAtIndex(territoryIndex) };
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Placement of buildings with Dock build category
|
||||
* metadata.proximity is defined when first dock without any territory
|
||||
* => we try to minimize distance from our current point
|
||||
|
|
@ -372,8 +369,8 @@ ConstructionPlan.prototype.findGoodPosition = function(gameState)
|
|||
* => we try not to be too far from our territory
|
||||
* In all cases, we add a bonus for nearby resources, and when a large extend of water in front ot it.
|
||||
*/
|
||||
ConstructionPlan.prototype.findDockPosition = function(gameState)
|
||||
{
|
||||
findDockPosition(gameState)
|
||||
{
|
||||
const template = this.template;
|
||||
const territoryMap = gameState.ai.HQ.territoryMap;
|
||||
|
||||
|
|
@ -543,13 +540,13 @@ ConstructionPlan.prototype.findDockPosition = function(gameState)
|
|||
baseIndex = -2; // We'll do an anchorless base around it
|
||||
|
||||
return { "x": x, "z": z, "angle": bestAngle, "base": baseIndex, "access": bestLand };
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Find a good island to build a dock.
|
||||
*/
|
||||
ConstructionPlan.prototype.buildOverseaDock = function(gameState, template)
|
||||
{
|
||||
buildOverseaDock(gameState, template)
|
||||
{
|
||||
const docks = gameState.getOwnStructures().filter(filters.byClass("Dock"));
|
||||
if (!docks.hasEntities())
|
||||
return;
|
||||
|
|
@ -625,11 +622,11 @@ ConstructionPlan.prototype.buildOverseaDock = function(gameState, template)
|
|||
}
|
||||
this.template = oldTemplate;
|
||||
this.metadata = oldMetadata;
|
||||
};
|
||||
}
|
||||
|
||||
/** Algorithm taken from the function GetDockAngle in simulation/helpers/Commands.js */
|
||||
ConstructionPlan.prototype.getDockAngle = function(gameState, x, z, size)
|
||||
{
|
||||
/** Algorithm taken from the function GetDockAngle in simulation/helpers/Commands.js */
|
||||
getDockAngle(gameState, x, z, size)
|
||||
{
|
||||
let pos = gameState.ai.accessibility.gamePosToMapPos([x, z]);
|
||||
const k = pos[0] + pos[1]*gameState.ai.accessibility.width;
|
||||
const seaRef = gameState.ai.accessibility.navalPassMap[k];
|
||||
|
|
@ -683,15 +680,15 @@ ConstructionPlan.prototype.getDockAngle = function(gameState, x, z, size)
|
|||
return -((waterPoints[start] + consec[start]/2) % numPoints)/numPoints*2*Math.PI;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Algorithm taken from checkPlacement in simulation/components/BuildRestriction.js
|
||||
* to determine the special dock requirements
|
||||
* returns {"land": land index for this dock, "water": amount of water around this spot}
|
||||
*/
|
||||
ConstructionPlan.prototype.checkDockPlacement = function(gameState, x, z, halfDepth, halfWidth, angle)
|
||||
{
|
||||
checkDockPlacement(gameState, x, z, halfDepth, halfWidth, angle)
|
||||
{
|
||||
const sz = halfDepth * Math.sin(angle);
|
||||
const cz = halfDepth * Math.cos(angle);
|
||||
// center back position
|
||||
|
|
@ -745,18 +742,18 @@ ConstructionPlan.prototype.checkDockPlacement = function(gameState, x, z, halfDe
|
|||
water += 4;
|
||||
}
|
||||
return { "land": land, "water": water };
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* fast check if we can build a dock: returns false if nearest land is farther than the dock dimension
|
||||
* if the (object) wantedLand is given, this nearest land should have one of these accessibility
|
||||
* if wantedSea is given, this tile should be inside this sea
|
||||
*/
|
||||
ConstructionPlan.prototype.around = [[ 1.0, 0.0], [ 0.87, 0.50], [ 0.50, 0.87], [ 0.0, 1.0], [-0.50, 0.87], [-0.87, 0.50],
|
||||
static around = [[ 1.0, 0.0], [ 0.87, 0.50], [ 0.50, 0.87], [ 0.0, 1.0], [-0.50, 0.87], [-0.87, 0.50],
|
||||
[-1.0, 0.0], [-0.87, -0.50], [-0.50, -0.87], [ 0.0, -1.0], [ 0.50, -0.87], [ 0.87, -0.50]];
|
||||
|
||||
ConstructionPlan.prototype.isDockLocation = function(gameState, j, dimension, wantedLand, wantedSea)
|
||||
{
|
||||
isDockLocation(gameState, j, dimension, wantedLand, wantedSea)
|
||||
{
|
||||
const width = gameState.ai.HQ.territoryMap.width;
|
||||
const cellSize = gameState.ai.HQ.territoryMap.cellSize;
|
||||
const dimLand = dimension + 1.5 * cellSize;
|
||||
|
|
@ -772,7 +769,7 @@ ConstructionPlan.prototype.isDockLocation = function(gameState, j, dimension, wa
|
|||
landPass < 2 && accessibility.navalPassMap[k] < 2)
|
||||
return false;
|
||||
|
||||
for (const a of this.around)
|
||||
for (const a of ConstructionPlan.around)
|
||||
{
|
||||
pos = accessibility.gamePosToMapPos([x + dimLand*a[0], z + dimLand*a[1]]);
|
||||
if (pos[0] < 0 || pos[0] >= accessibility.width)
|
||||
|
|
@ -796,14 +793,14 @@ ConstructionPlan.prototype.isDockLocation = function(gameState, j, dimension, wa
|
|||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* return a measure of the proximity to our frontier (including our allies)
|
||||
* 0=inside, 1=less than 24m, 2= less than 48m, 3= less than 72m, 4=less than 96m, 5=above 96m
|
||||
*/
|
||||
ConstructionPlan.prototype.getFrontierProximity = function(gameState, j)
|
||||
{
|
||||
getFrontierProximity(gameState, j)
|
||||
{
|
||||
const alliedVictory = gameState.getAlliedVictory();
|
||||
const territoryMap = gameState.ai.HQ.territoryMap;
|
||||
let territoryOwner = territoryMap.getOwnerIndex(j);
|
||||
|
|
@ -816,7 +813,7 @@ ConstructionPlan.prototype.getFrontierProximity = function(gameState, j)
|
|||
const ix = j % width;
|
||||
const iz = Math.floor(j / width);
|
||||
let best = 5;
|
||||
for (const a of this.around)
|
||||
for (const a of ConstructionPlan.around)
|
||||
{
|
||||
for (let i = 1; i < 5; ++i)
|
||||
{
|
||||
|
|
@ -840,14 +837,14 @@ ConstructionPlan.prototype.getFrontierProximity = function(gameState, j)
|
|||
}
|
||||
|
||||
return best;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* get the sum of the resources (except food) around, inside a given radius
|
||||
* resources have a weight (1 if dist=0 and 0 if dist=size) doubled for wood
|
||||
*/
|
||||
ConstructionPlan.prototype.getResourcesAround = function(gameState, types, i, radius)
|
||||
{
|
||||
getResourcesAround(gameState, types, i, radius)
|
||||
{
|
||||
const resourceMaps = gameState.sharedScript.resourceMaps;
|
||||
const w = resourceMaps.wood.width;
|
||||
const cellSize = resourceMaps.wood.cellSize;
|
||||
|
|
@ -897,10 +894,10 @@ ConstructionPlan.prototype.getResourcesAround = function(gameState, types, i, ra
|
|||
}
|
||||
}
|
||||
return nbcell ? total / nbcell : 0;
|
||||
};
|
||||
}
|
||||
|
||||
ConstructionPlan.prototype.isGo = function(gameState)
|
||||
{
|
||||
isGo(gameState)
|
||||
{
|
||||
if (this.goRequirement && this.goRequirement == "houseNeeded")
|
||||
{
|
||||
if (!gameState.ai.HQ.canBuild(gameState, "structures/{civ}/house") &&
|
||||
|
|
@ -925,16 +922,16 @@ ConstructionPlan.prototype.isGo = function(gameState)
|
|||
return freeSlots <= 10;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
ConstructionPlan.prototype.onStart = function(gameState)
|
||||
{
|
||||
onStart(gameState)
|
||||
{
|
||||
if (this.queueToReset)
|
||||
gameState.ai.queueManager.changePriority(this.queueToReset, gameState.ai.Config.priorities[this.queueToReset]);
|
||||
};
|
||||
}
|
||||
|
||||
ConstructionPlan.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
return {
|
||||
"category": this.category,
|
||||
"type": this.type,
|
||||
|
|
@ -946,13 +943,14 @@ ConstructionPlan.prototype.Serialize = function()
|
|||
"goRequirement": this.goRequirement || undefined,
|
||||
"queueToReset": this.queueToReset || undefined
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
ConstructionPlan.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
Deserialize(gameState, data)
|
||||
{
|
||||
for (const key in data)
|
||||
this[key] = data[key];
|
||||
|
||||
this.cost = new ResourcesManager();
|
||||
this.cost.Deserialize(data.cost);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { ResourcesManager } from "simulation/ai/common-api/resources.js";
|
||||
import { QueuePlan } from "simulation/ai/petra/queueplan.js";
|
||||
|
||||
export function ResearchPlan(gameState, type, rush = false)
|
||||
export class ResearchPlan extends QueuePlan
|
||||
{
|
||||
if (!QueuePlan.call(this, gameState, type, {}))
|
||||
return false;
|
||||
constructor(gameState, type, rush = false)
|
||||
{
|
||||
super(gameState, type, {});
|
||||
|
||||
if (this.template.researchTime === undefined)
|
||||
return false;
|
||||
throw new Error();
|
||||
|
||||
// Refine the estimated cost
|
||||
const researchers = this.getBestResearchers(gameState, true);
|
||||
|
|
@ -16,23 +17,19 @@ export function ResearchPlan(gameState, type, rush = false)
|
|||
|
||||
this.category = "technology";
|
||||
this.rush = rush;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ResearchPlan.prototype = Object.create(QueuePlan.prototype);
|
||||
|
||||
ResearchPlan.prototype.canStart = function(gameState)
|
||||
{
|
||||
canStart(gameState)
|
||||
{
|
||||
this.researchers = this.getBestResearchers(gameState);
|
||||
if (!this.researchers)
|
||||
return false;
|
||||
this.cost = new ResourcesManager(this.template.cost(this.researchers[0]));
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
ResearchPlan.prototype.getBestResearchers = function(gameState, noRequirementCheck = false)
|
||||
{
|
||||
getBestResearchers(gameState, noRequirementCheck = false)
|
||||
{
|
||||
const allResearchers = gameState.findResearchers(this.type, noRequirementCheck);
|
||||
if (!allResearchers || !allResearchers.hasEntities())
|
||||
return undefined;
|
||||
|
|
@ -52,15 +49,15 @@ ResearchPlan.prototype.getBestResearchers = function(gameState, noRequirementChe
|
|||
}
|
||||
}
|
||||
return researchers;
|
||||
};
|
||||
}
|
||||
|
||||
ResearchPlan.prototype.isInvalid = function(gameState)
|
||||
{
|
||||
isInvalid(gameState)
|
||||
{
|
||||
return gameState.isResearched(this.type) || gameState.isResearching(this.type);
|
||||
};
|
||||
}
|
||||
|
||||
ResearchPlan.prototype.start = function(gameState)
|
||||
{
|
||||
start(gameState)
|
||||
{
|
||||
// Prefer researcher with shortest queues (no need to serialize this.researchers
|
||||
// as the functions canStart and start are always called on the same turn)
|
||||
this.researchers.sort((a, b) => a.trainingQueueTime() - b.trainingQueueTime());
|
||||
|
|
@ -69,10 +66,10 @@ ResearchPlan.prototype.start = function(gameState)
|
|||
this.researchers[0].stopAllProduction(0.45);
|
||||
this.researchers[0].research(this.type);
|
||||
this.onStart(gameState);
|
||||
};
|
||||
}
|
||||
|
||||
ResearchPlan.prototype.onStart = function(gameState)
|
||||
{
|
||||
onStart(gameState)
|
||||
{
|
||||
if (this.queueToReset)
|
||||
gameState.ai.queueManager.changePriority(this.queueToReset, gameState.ai.Config.priorities[this.queueToReset]);
|
||||
|
||||
|
|
@ -84,10 +81,10 @@ ResearchPlan.prototype.onStart = function(gameState)
|
|||
gameState.ai.HQ.OnPhaseUp(gameState, i);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ResearchPlan.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
return {
|
||||
"category": this.category,
|
||||
"type": this.type,
|
||||
|
|
@ -98,13 +95,14 @@ ResearchPlan.prototype.Serialize = function()
|
|||
"rush": this.rush,
|
||||
"queueToReset": this.queueToReset || undefined
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
ResearchPlan.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
Deserialize(gameState, data)
|
||||
{
|
||||
for (const key in data)
|
||||
this[key] = data[key];
|
||||
|
||||
this.cost = new ResourcesManager();
|
||||
this.cost.Deserialize(data.cost);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,11 @@ import { aiWarn } from "simulation/ai/common-api/utils.js";
|
|||
import { QueuePlan } from "simulation/ai/petra/queueplan.js";
|
||||
import { Worker } from "simulation/ai/petra/worker.js";
|
||||
|
||||
export function TrainingPlan(gameState, type, metadata, number = 1, maxMerge = 5)
|
||||
export class TrainingPlan extends QueuePlan
|
||||
{
|
||||
if (!QueuePlan.call(this, gameState, type, metadata))
|
||||
constructor(gameState, type, metadata, number = 1, maxMerge = 5)
|
||||
{
|
||||
aiWarn(" Plan training " + type + " canceled");
|
||||
return false;
|
||||
}
|
||||
super(gameState, type, metadata);
|
||||
|
||||
// Refine the estimated cost and add pop cost
|
||||
const trainers = this.getBestTrainers(gameState);
|
||||
|
|
@ -20,23 +18,19 @@ export function TrainingPlan(gameState, type, metadata, number = 1, maxMerge = 5
|
|||
this.category = "unit";
|
||||
this.number = number;
|
||||
this.maxMerge = maxMerge;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TrainingPlan.prototype = Object.create(QueuePlan.prototype);
|
||||
|
||||
TrainingPlan.prototype.canStart = function(gameState)
|
||||
{
|
||||
canStart(gameState)
|
||||
{
|
||||
this.trainers = this.getBestTrainers(gameState);
|
||||
if (!this.trainers)
|
||||
return false;
|
||||
this.cost = new ResourcesManager(this.template.cost(this.trainers[0]), +this.template._template.Cost.Population);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
TrainingPlan.prototype.getBestTrainers = function(gameState)
|
||||
{
|
||||
getBestTrainers(gameState)
|
||||
{
|
||||
if (this.metadata && this.metadata.trainer)
|
||||
{
|
||||
const trainer = gameState.getEntityById(this.metadata.trainer);
|
||||
|
|
@ -67,10 +61,10 @@ TrainingPlan.prototype.getBestTrainers = function(gameState)
|
|||
}
|
||||
}
|
||||
return trainers;
|
||||
};
|
||||
}
|
||||
|
||||
TrainingPlan.prototype.start = function(gameState)
|
||||
{
|
||||
start(gameState)
|
||||
{
|
||||
if (this.metadata && this.metadata.trainer)
|
||||
{
|
||||
const metadata = {};
|
||||
|
|
@ -135,15 +129,15 @@ TrainingPlan.prototype.start = function(gameState)
|
|||
this.trainers[0].train(gameState.getPlayerCiv(), this.type, this.number, this.metadata);
|
||||
|
||||
this.onStart(gameState);
|
||||
};
|
||||
}
|
||||
|
||||
TrainingPlan.prototype.addItem = function(amount = 1)
|
||||
{
|
||||
addItem(amount = 1)
|
||||
{
|
||||
this.number += amount;
|
||||
};
|
||||
}
|
||||
|
||||
TrainingPlan.prototype.Serialize = function()
|
||||
{
|
||||
Serialize()
|
||||
{
|
||||
return {
|
||||
"category": this.category,
|
||||
"type": this.type,
|
||||
|
|
@ -153,13 +147,14 @@ TrainingPlan.prototype.Serialize = function()
|
|||
"number": this.number,
|
||||
"maxMerge": this.maxMerge
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
TrainingPlan.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
Deserialize(gameState, data)
|
||||
{
|
||||
for (const key in data)
|
||||
this[key] = data[key];
|
||||
|
||||
this.cost = new ResourcesManager();
|
||||
this.cost.Deserialize(data.cost);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue