0ad/binaries/data/mods/public/simulation/ai/aegis/naval-manager.js
wraitii 9d02495a96 Fix a few bugs.
Improve the AI gamestate to make better use of entity collections,
should be very slightly faster, and it's cleaner.
Remove enemy watchers that were no longer used.

This was SVN commit r14574.
2014-01-12 01:07:07 +00:00

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, events, 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, 0, -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);