forked from 0ad/0ad
wraitii
d23b7deb98
Aegis should mostly respect tech limitations so I'll ref #1964. I'm going to go with #2364 is fixed, the AI should be more efficient in early-game, and late-game is a known problem. Fixes #2274 and fixes #2379. Refs #2372 as it should fix several of those warnings for AIs. Fixes #2256 with a new bartering system, in parts taken from mimo's patch. This was SVN commit r14582.
291 lines
8.8 KiB
JavaScript
291 lines
8.8 KiB
JavaScript
var AEGIS = function(m)
|
|
{
|
|
|
|
/* Naval Manager
|
|
Will deal with anything ships.
|
|
-Basically trade over water (with fleets and goals commissioned by the economy manager)
|
|
-Defence over water (commissioned by the defense manager)
|
|
-subtask being patrols, escort, naval superiority.
|
|
-Transport of units over water (a few units).
|
|
-Scouting, ultimately.
|
|
Also deals with handling docks, making sure we have access and stuffs like that.
|
|
Does not build them though, that's for the base manager to handle.
|
|
*/
|
|
|
|
m.NavalManager = function() {
|
|
// accessibility zones for which we have a dock.
|
|
// Connexion is described as [landindex] = [seaIndexes];
|
|
// technically they also exist for sea zones but I don't care.
|
|
this.landZoneDocked = [];
|
|
|
|
// list of seas I have a dock on.
|
|
this.accessibleSeas = [];
|
|
|
|
// ship subCollections. Also exist for land zones, idem, not caring.
|
|
this.seaShips = [];
|
|
this.seaTpShips = [];
|
|
this.seaWarships = [];
|
|
|
|
// wanted NB per zone.
|
|
this.wantedTpShips = [];
|
|
this.wantedWarships = [];
|
|
|
|
this.transportPlans = [];
|
|
this.askedPlans = [];
|
|
};
|
|
|
|
// More initialisation for stuff that needs the gameState
|
|
m.NavalManager.prototype.init = function(gameState, queues) {
|
|
// finished docks
|
|
this.docks = gameState.getOwnStructures().filter(API3.Filters.and(API3.Filters.byClass("Dock"), API3.Filters.not(API3.Filters.isFoundation())));
|
|
this.docks.allowQuickIter();
|
|
this.docks.registerUpdates();
|
|
|
|
this.ships = gameState.getOwnEntities().filter(API3.Filters.byClass("Ship"));
|
|
// note: those two can overlap (some transport ships are warships too and vice-versa).
|
|
this.tpShips = this.ships.filter(API3.Filters.byCanGarrison());
|
|
this.warships = this.ships.filter(API3.Filters.byClass("Warship"));
|
|
|
|
this.ships.registerUpdates();
|
|
this.tpShips.registerUpdates();
|
|
this.warships.registerUpdates();
|
|
|
|
for (var i = 0; i < gameState.ai.accessibility.regionSize.length; ++i)
|
|
{
|
|
if (gameState.ai.accessibility.regionType[i] !== "water")
|
|
{
|
|
// push dummies
|
|
this.seaShips.push(new API3.EntityCollection(gameState.sharedScript));
|
|
this.seaTpShips.push(new API3.EntityCollection(gameState.sharedScript));
|
|
this.seaWarships.push(new API3.EntityCollection(gameState.sharedScript));
|
|
this.wantedTpShips.push(0);
|
|
this.wantedWarships.push(0);
|
|
} else {
|
|
var collec = this.ships.filter(API3.Filters.byStaticMetadata(PlayerID, "sea", i));
|
|
collec.registerUpdates();
|
|
this.seaShips.push(collec);
|
|
collec = this.tpShips.filter(API3.Filters.byStaticMetadata(PlayerID, "sea", i));
|
|
collec.registerUpdates();
|
|
this.seaTpShips.push(collec);
|
|
var collec = this.warships.filter(API3.Filters.byStaticMetadata(PlayerID, "sea", i));
|
|
collec.registerUpdates();
|
|
this.seaWarships.push(collec);
|
|
|
|
this.wantedTpShips.push(1);
|
|
this.wantedWarships.push(1);
|
|
}
|
|
|
|
this.landZoneDocked.push([]);
|
|
}
|
|
};
|
|
|
|
m.NavalManager.prototype.getUnconnectedSeas = function (gameState, region) {
|
|
var seas = gameState.ai.accessibility.regionLinks[region]
|
|
if (seas.length === 0)
|
|
return [];
|
|
for (var i = 0; i < seas.length; ++i)
|
|
{
|
|
if (this.landZoneDocked[region].indexOf(seas[i]) !== -1)
|
|
seas.splice(i--,1);
|
|
}
|
|
return seas;
|
|
};
|
|
|
|
// returns true if there is a path from A to B and we have docks.
|
|
m.NavalManager.prototype.canReach = function (gameState, regionA, regionB) {
|
|
var path = gameState.ai.accessibility.getTrajectToIndex(regionA, regionB);
|
|
if (!path)
|
|
{
|
|
return false;
|
|
}
|
|
for (var i = 0; i < path.length - 1; ++i)
|
|
{
|
|
if (gameState.ai.accessibility.regionType[path[i]] == "land")
|
|
if (this.accessibleSeas.indexOf(path[i+1]) === -1)
|
|
{
|
|
m.debug ("cannot reach because of " + path[i+1]);
|
|
return false; // we wn't be able to board on that sea
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
|
|
m.NavalManager.prototype.checkEvents = function (gameState, queues, events) {
|
|
var evts = events["ConstructionFinished"];
|
|
// TODO: probably check stuffs like a base destruction.
|
|
for (var i in evts)
|
|
{
|
|
var evt = evts[i];
|
|
if (evt && evt.newentity)
|
|
{
|
|
var entity = gameState.getEntityById(evt.newentity);
|
|
if (entity && entity.hasClass("Dock") && entity.isOwn(PlayerID))
|
|
{
|
|
// okay we have a dock whose construction is finished.
|
|
// let's assign it to us.
|
|
var pos = entity.position();
|
|
var li = gameState.ai.accessibility.getAccessValue(pos);
|
|
var ni = entity.getMetadata(PlayerID, "sea");
|
|
if (this.landZoneDocked[li].indexOf(ni) === -1)
|
|
this.landZoneDocked[li].push(ni);
|
|
if (this.accessibleSeas.indexOf(ni) === -1)
|
|
this.accessibleSeas.push(ni);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
m.NavalManager.prototype.addPlan = function(plan) {
|
|
this.transportPlans.push(plan);
|
|
};
|
|
|
|
// will create a plan at the end of the turn.
|
|
// many units can call this separately and end up in the same plan
|
|
// which can be useful.
|
|
m.NavalManager.prototype.askForTransport = function(entity, startPos, endPos) {
|
|
this.askedPlans.push([entity, startPos, endPos]);
|
|
};
|
|
|
|
// creates aforementionned plans
|
|
m.NavalManager.prototype.createPlans = function(gameState) {
|
|
var startID = {};
|
|
|
|
for (var i in this.askedPlans)
|
|
{
|
|
var plan = this.askedPlans[i];
|
|
var startIndex = gameState.ai.accessibility.getAccessValue(plan[1]);
|
|
var endIndex = gameState.ai.accessibility.getAccessValue(plan[2]);
|
|
if (startIndex === 1 || endIndex === -1)
|
|
continue;
|
|
if (!startID[startIndex])
|
|
{
|
|
startID[startIndex] = {};
|
|
startID[startIndex][endIndex] = { "dest" : plan[2], "units": [plan[0]]};
|
|
}
|
|
else if (!startID[startIndex][endIndex])
|
|
startID[startIndex][endIndex] = { "dest" : plan[2], "units": [plan[0]]};
|
|
else
|
|
startID[startIndex][endIndex].units.push(plan[0]);
|
|
}
|
|
for (var i in startID)
|
|
for (var k in startID[i])
|
|
{
|
|
var tpPlan = new m.TransportPlan(gameState, startID[i][k].units, startID[i][k].dest, false)
|
|
this.transportPlans.push (tpPlan);
|
|
}
|
|
};
|
|
|
|
// TODO: work on this.
|
|
m.NavalManager.prototype.maintainFleet = function(gameState, queues, events) {
|
|
// check if we have enough transport ships.
|
|
// check per region.
|
|
for (var i = 0; i < this.seaShips.length; ++i)
|
|
{
|
|
var tpNb = gameState.countOwnQueuedEntitiesWithMetadata("sea", i);
|
|
if (this.accessibleSeas.indexOf(i) !== -1 && this.seaTpShips[i].length < this.wantedTpShips[i]
|
|
&& tpNb + queues.ships.length() === 0 && gameState.getTemplate(gameState.applyCiv("units/{civ}_ship_bireme")).available(gameState))
|
|
{
|
|
// TODO: check our dock can build the wanted ship types, for Carthage.
|
|
queues.ships.addItem(new m.TrainingPlan(gameState, "units/{civ}_ship_bireme", { "sea" : i }, 1, 1 ));
|
|
}
|
|
}
|
|
};
|
|
|
|
// bumps up the number of ships we want if we need more.
|
|
m.NavalManager.prototype.checkLevels = function(gameState, queues) {
|
|
if (queues.ships.length() !== 0)
|
|
return;
|
|
for (var i = 0; i < this.transportPlans.length; ++i)
|
|
{
|
|
var plan = this.transportPlans[i];
|
|
if (plan.needTpShips())
|
|
{
|
|
var zone = plan.neededShipsZone();
|
|
if (zone && gameState.countOwnQueuedEntitiesWithMetadata("sea", zone) > 0)
|
|
continue;
|
|
if (zone && this.wantedTpShips[i] === 0)
|
|
this.wantedTpShips[i]++;
|
|
else if (zone && plan.allAtOnce)
|
|
this.wantedTpShips[i]++;
|
|
}
|
|
}
|
|
};
|
|
|
|
// assigns free ships to plans that need some
|
|
m.NavalManager.prototype.assignToPlans = function(gameState, queues, events) {
|
|
for (var i = 0; i < this.transportPlans.length; ++i)
|
|
{
|
|
var plan = this.transportPlans[i];
|
|
if (plan.needTpShips())
|
|
{
|
|
// assign one per go.
|
|
var zone = plan.neededShipsZone();
|
|
if (zone)
|
|
{
|
|
for each (var ship in this.seaTpShips[zone]._entities)
|
|
{
|
|
if (!ship.getMetadata(PlayerID, "tpplan"))
|
|
{
|
|
m.debug ("Assigning ship " + ship.id() + " to plan" + plan.ID);
|
|
plan.assignShip(gameState, ship);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
m.NavalManager.prototype.checkActivePlan = function(ID) {
|
|
for (var i = 0; i < this.transportPlans.length; ++i)
|
|
if (this.transportPlans[i].ID === ID)
|
|
return true;
|
|
|
|
return false;
|
|
};
|
|
|
|
// Some functions are run every turn
|
|
// Others once in a while
|
|
m.NavalManager.prototype.update = function(gameState, queues, events) {
|
|
Engine.ProfileStart("Naval Manager update");
|
|
|
|
this.checkEvents(gameState, queues, events);
|
|
|
|
if (gameState.ai.playedTurn % 10 === 0)
|
|
{
|
|
this.maintainFleet(gameState, queues, events);
|
|
this.checkLevels(gameState, queues);
|
|
}
|
|
|
|
for (var i = 0; i < this.transportPlans.length; ++i)
|
|
if (!this.transportPlans[i].carryOn(gameState, this))
|
|
{
|
|
// whatever the reason, this plan needs to be ended
|
|
// it could be that it's finished though.
|
|
var seaZone = this.transportPlans[i].neededShipsZone();
|
|
|
|
var rallyPos = [];
|
|
this.docks.forEach(function (dock) {
|
|
if (dock.getMetadata(PlayerID,"sea") == seaZone)
|
|
rallyPos = dock.position();
|
|
});
|
|
this.transportPlans[i].ships.move(rallyPos);
|
|
this.transportPlans[i].releaseAll(gameState);
|
|
this.transportPlans.splice(i,1);
|
|
--i;
|
|
}
|
|
|
|
this.assignToPlans(gameState, queues, events);
|
|
if (gameState.ai.playedTurn % 10 === 2)
|
|
{
|
|
this.createPlans(gameState);
|
|
this.askedPlans = [];
|
|
}
|
|
Engine.ProfileStop();
|
|
};
|
|
|
|
return m;
|
|
}(AEGIS);
|