Petra: complete the rework of starting game to allow more flexibility (i.e. support of nomad maps)

This was SVN commit r16142.
This commit is contained in:
mimo 2015-01-11 22:47:24 +00:00
parent 851d3d964a
commit 320cfa0bfc
8 changed files with 258 additions and 107 deletions

View file

@ -457,6 +457,12 @@ m.Template = m.Class({
return this.get("UnitMotion/WalkSpeed");
},
trainingCategory: function() {
if (!this.get("TrainingRestrictions") || !this.get("TrainingRestrictions/Category"))
return undefined;
return this.get("TrainingRestrictions/Category");
},
buildCategory: function() {
if (!this.get("BuildRestrictions") || !this.get("BuildRestrictions/Category"))
return undefined;

View file

@ -593,14 +593,8 @@ m.GameState.prototype.findTrainableUnits = function(classes, anticlasses)
if (!okay)
continue;
for (let limitedClass in limits)
{
if (!template.hasClass(limitedClass) || current[limitedClass] < limits[limitedClass])
continue;
okay = false;
break;
}
if (!okay)
let category = template.trainingCategory();
if (category && limits[category] && current[category] >= limits[category])
continue;
ret.push( [trainable, template] );

View file

@ -409,24 +409,27 @@ m.BaseManager.prototype.checkResourceLevels = function (gameState, queues)
{
if (type == "food")
{
var count = this.getResourceLevel(gameState, type); // TODO animals are not accounted, may-be we should
var numFarms = gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_field"), true);
var numFound = gameState.countEntitiesByType(gameState.applyCiv("foundation|structures/{civ}_field"), true);
var numQueue = queues.field.countQueuedUnits();
if (gameState.ai.HQ.canBuild(gameState, "structures/{civ}_field")) // let's see if we need to add new farms.
{
var count = this.getResourceLevel(gameState, type); // TODO animals are not accounted, may-be we should
var numFarms = gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_field"), true);
var numFound = gameState.countEntitiesByType(gameState.applyCiv("foundation|structures/{civ}_field"), true);
var numQueue = queues.field.countQueuedUnits();
// TODO if not yet farms, add a check on time used/lost and build farmstead if needed
if (numFarms + numFound + numQueue === 0) // starting game, rely on fruits as long as we have enough of them
{
if (count < 600)
queues.field.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_field", { "base" : this.ID }));
}
else if (gameState.ai.HQ.canBuild(gameState, "structures/{civ}_field")) // let's see if we need to add new farms.
{
let goal = this.Config.Economy.provisionFields;
if (gameState.ai.HQ.saveResources || gameState.ai.HQ.saveSpace)
goal = Math.max(goal-1, 1);
if (numFound + numQueue < goal)
queues.field.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_field", { "base" : this.ID }));
// TODO if not yet farms, add a check on time used/lost and build farmstead if needed
if (numFarms + numFound + numQueue === 0) // starting game, rely on fruits as long as we have enough of them
{
if (count < 600)
queues.field.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_field", { "base" : this.ID }));
}
else
{
let goal = this.Config.Economy.provisionFields;
if (gameState.ai.HQ.saveResources || gameState.ai.HQ.saveSpace)
goal = Math.max(goal-1, 1);
if (numFound + numQueue < goal)
queues.field.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_field", { "base" : this.ID }));
}
}
}
else if (queues.dropsites.length() == 0 && gameState.countFoundationsByType(gameState.applyCiv("structures/{civ}_storehouse"), true) == 0)

View file

@ -530,7 +530,7 @@ m.HQ.prototype.pickMostNeededResources = function(gameState)
// Returns the best position to build a new Civil Centre
// Whose primary function would be to reach new resources of type "resource".
m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource, fromStrategic)
m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource, proximity, fromStrategic)
{
// This builds a map. The procedure is fairly simple. It adds the resource maps
// (which are dynamically updated and are made so that they will facilitate DP placement)
@ -555,6 +555,20 @@ m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource,
var bestIdx = undefined;
var bestVal = undefined;
var radius = Math.ceil(template.obstructionRadius() / obstructions.cellSize);
var scale = 250 * 250;
var proxyAccess = undefined;
var nbShips = this.navalManager.transportShips.length;
if (proximity) // this is our first base
{
// if our first base, ensure room around
radius = Math.ceil((template.obstructionRadius() + 8) / obstructions.cellSize);
// scale is the typical scale at which we want to find a location for our first base
// look for bigger scale if we start from a ship (access < 2) or from a small island
var cellArea = gameState.getMap().cellSize * gameState.getMap().cellSize;
proxyAccess = gameState.ai.accessibility.getAccessValue(proximity);
if (proxyAccess < 2 || cellArea*gameState.ai.accessibility.regionSize[proxyAccess] < 24000)
scale = 400 * 400;
}
var width = this.territoryMap.width;
var cellSize = this.territoryMap.cellSize;
@ -567,6 +581,8 @@ m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource,
var index = gameState.ai.accessibility.landPassMap[j];
if (!this.landRegions[index])
continue;
if (proxyAccess && nbShips === 0 && proxyAccess !== index)
continue;
// and with enough room around to build the cc
var i = API3.getMaxMapIndex(j, this.territoryMap, obstructions);
if (obstructions.map[i] <= radius)
@ -575,61 +591,71 @@ m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource,
var norm = 0.5; // TODO adjust it, knowing that we will sum 5 maps
// checking distance to other cc
var pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
var minDist = Math.min();
for (var cc of ccList)
if (proximity) // this is our first cc, let's do it near our units
{
var dist = API3.SquareVectorDistance(cc.pos, pos);
if (dist < 14000) // Reject if too near from any cc
{
norm = 0
break;
}
if (!cc.ally)
continue;
if (dist < 30000) // Reject if too near from an allied cc
{
norm = 0
break;
}
if (dist < 50000) // Disfavor if quite near an allied cc
norm *= 0.5;
if (dist < minDist)
minDist = dist;
var dist = API3.SquareVectorDistance(proximity, pos);
norm /= (1 + dist/scale);
}
if (norm == 0)
continue;
if (minDist > 170000 && !this.navalMap) // Reject if too far from any allied cc (-> not connected)
else
{
norm = 0;
continue;
}
else if (minDist > 130000) // Disfavor if quite far from any allied cc
{
if (this.navalMap)
var minDist = Math.min();
for (var cc of ccList)
{
if (minDist > 250000)
var dist = API3.SquareVectorDistance(cc.pos, pos);
if (dist < 14000) // Reject if too near from any cc
{
norm = 0
break;
}
if (!cc.ally)
continue;
if (dist < 30000) // Reject if too near from an allied cc
{
norm = 0
break;
}
if (dist < 50000) // Disfavor if quite near an allied cc
norm *= 0.5;
else
norm *= 0.8;
if (dist < minDist)
minDist = dist;
}
else
norm *= 0.5;
}
if (norm == 0)
continue;
for (var dp of dpList)
{
var dist = API3.SquareVectorDistance(dp.pos, pos);
if (dist < 3600)
if (minDist > 170000 && !this.navalMap) // Reject if too far from any allied cc (not connected)
{
norm = 0;
break;
continue;
}
else if (dist < 6400)
norm *= 0.5;
else if (minDist > 130000) // Disfavor if quite far from any allied cc
{
if (this.navalMap)
{
if (minDist > 250000)
norm *= 0.5;
else
norm *= 0.8;
}
else
norm *= 0.5;
}
for (var dp of dpList)
{
var dist = API3.SquareVectorDistance(dp.pos, pos);
if (dist < 3600)
{
norm = 0;
break;
}
else if (dist < 6400)
norm *= 0.5;
}
if (norm == 0)
continue;
}
if (norm == 0)
continue;
if (this.borderMap.map[j] > 0) // disfavor the borders of the map
norm *= 0.5;
@ -649,7 +675,7 @@ m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource,
Engine.ProfileStop();
var cut = 60;
if (fromStrategic) // be less restrictive
if (fromStrategic || proximity) // be less restrictive
cut = 30;
if (this.Config.debug > 1)
API3.warn("we have found a base for " + resource + " with best (cut=" + cut + ") = " + bestVal);
@ -695,7 +721,7 @@ m.HQ.prototype.findStrategicCCLocation = function(gameState, template)
++numAllyCC;
}
if (numAllyCC < 2)
return this.findEconomicCCLocation(gameState, template, "wood", true);
return this.findEconomicCCLocation(gameState, template, "wood", undefined, true);
Engine.ProfileStart("findStrategicCCLocation");
@ -1149,10 +1175,10 @@ m.HQ.prototype.checkBaseExpansion = function(gameState, queues)
{
if (queues.civilCentre.length() > 0)
return;
// first build one cc if none already available
// first build one cc if all have been destroyed
if (this.numActiveBase() < 1)
{
this.buildNewBase(gameState, queues);
this.buildFirstBase(gameState);
return;
}
// then expand if we have not enough room available for buildings
@ -1176,7 +1202,7 @@ m.HQ.prototype.checkBaseExpansion = function(gameState, queues)
}
};
m.HQ.prototype.buildNewBase = function(gameState, queues, type)
m.HQ.prototype.buildNewBase = function(gameState, queues, resource)
{
if (this.numActiveBase() > 0 && gameState.currentPhase() == 1 && !gameState.isResearching(gameState.townPhase()))
return false;
@ -1188,8 +1214,8 @@ m.HQ.prototype.buildNewBase = function(gameState, queues, type)
// base "-1" means new base.
if (this.Config.debug > 1)
API3.warn("new base planned with type " + type);
queues.civilCentre.addItem(new m.ConstructionPlan(gameState, template, { "base": -1, "type": type }));
API3.warn("new base planned with resource " + resource);
queues.civilCentre.addItem(new m.ConstructionPlan(gameState, template, { "base": -1, "resource": resource }));
return true;
};
@ -1520,9 +1546,9 @@ m.HQ.prototype.canBuild = function(gameState, structure)
if (!template || !template.available(gameState))
return false;
var limits = gameState.getEntityLimits();
for (var limitedClass in limits)
if (template.hasClass(limitedClass) && gameState.getEntityCounts()[limitedClass] >= limits[limitedClass])
return false;
var category = template.buildCategory();
if (category && limits[category] && gameState.getEntityCounts()[category] >= limits[category])
return false;
return true;
};

View file

@ -388,6 +388,37 @@ m.NavalManager.prototype.splitTransport = function(gameState, plan)
return (nbUnits !== 0);
};
/**
* create a transport from a garrisoned ship to a land location
* needed at start game when starting with a garrisoned ship
*/
m.NavalManager.prototype.createTransportIfNeeded = function(gameState, fromPos, toPos)
{
let fromAccess = gameState.ai.accessibility.getAccessValue(fromPos);
if (fromAccess !== 1)
return;
let toAccess = gameState.ai.accessibility.getAccessValue(toPos);
if (toAccess < 2)
return;
for (let ship of this.ships.values())
{
if (!ship.isGarrisonHolder() || !ship.garrisoned().length)
continue;
if (ship.getMetadata(PlayerID, "transporter") !== undefined)
continue;
let units = [];
for (let entId of ship.garrisoned())
units.push(gameState.getEntityById(entId));
// TODO check that the garrisoned units have not another purpose
let plan = new m.TransportPlan(gameState, units, fromAccess, toAccess, toPos, ship);
if (plan.failed)
continue;
plan.init(gameState);
this.transportPlans.push(plan);
}
};
// set minimal number of needed ships when a new event (new base or new attack plan)
m.NavalManager.prototype.setMinimalTransportShips = function(gameState, sea, number)
{
@ -606,15 +637,8 @@ m.NavalManager.prototype.getBestShip = function(gameState, sea, goal)
if (!template.available(gameState))
continue;
var aboveLimit = false;
for (var limitedClass in limits)
{
if (!template.hasClass(limitedClass) || current[limitedClass] < limits[limitedClass])
continue;
aboveLimit = true;
break;
}
if (aboveLimit)
var category = template.trainingCategory();
if (category && limits[category] && current[category] >= limits[category])
continue;
var arrows = +(template.getDefaultArrow() || 0);
@ -649,7 +673,7 @@ m.NavalManager.prototype.getBestShip = function(gameState, sea, goal)
m.NavalManager.prototype.update = function(gameState, queues, events)
{
Engine.ProfileStart("Naval Manager update");
this.checkEvents(gameState, queues, events);
// close previous transport plans if finished

View file

@ -86,6 +86,10 @@ m.ConstructionPlan.prototype.start = function(gameState)
}
this.onStart(gameState);
Engine.ProfileStop();
// TODO should have a ConstructionStarted event in case the construct order fails
if (this.metadata && this.metadata.proximity)
gameState.ai.HQ.navalManager.createTransportIfNeeded(gameState, this.metadata.proximity, [pos.x, pos.z]);
};
// TODO for dock, we should allow building them outside territory, and we should check that we are along the right sea
@ -101,8 +105,11 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState)
{
if (template.hasClass("CivCentre"))
{
if (this.metadata.type)
var pos = gameState.ai.HQ.findEconomicCCLocation(gameState, template, this.metadata.type);
if (this.metadata.resource)
{
var proximity = this.metadata.proximity ? this.metadata.proximity : undefined;
var pos = gameState.ai.HQ.findEconomicCCLocation(gameState, template, this.metadata.resource, proximity);
}
else
var pos = gameState.ai.HQ.findStrategicCCLocation(gameState, template);

View file

@ -9,6 +9,10 @@ m.HQ.prototype.gameAnalysis = function(gameState)
// Analysis of the terrain and the different access regions
this.regionAnalysis(gameState);
this.attackManager.init(gameState);
this.navalManager.init(gameState);
this.tradeManager.init(gameState);
// Make a list of buildable structures from the config file
this.structureAnalysis(gameState);
@ -30,7 +34,7 @@ m.HQ.prototype.gameAnalysis = function(gameState)
// Assign entities and resources in the different bases
this.assignStartingEntities(gameState);
// Check if we will ever be able to produce units
// 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")).length)
{
@ -42,12 +46,10 @@ m.HQ.prototype.gameAnalysis = function(gameState)
this.canBuildUnits = false;
this.dispatchUnits(gameState);
}
else
this.buildFirstBase(gameState);
}
this.attackManager.init(gameState);
this.navalManager.init(gameState);
this.tradeManager.init(gameState);
// configure our first base strategy
if (this.baseManagers.length > 1)
this.configFirstBase(gameState);
@ -106,7 +108,7 @@ m.HQ.prototype.assignStartingEntities = function(gameState)
if (!bestbase)
{
// entity outside our territory
var bestbase = m.getBestBase(ent, gameState);
bestbase = m.getBestBase(ent, gameState);
bestbase.assignEntity(ent);
if (bestbase.ID !== this.baseManagers[0].ID && ent.resourceDropsiteTypes() && !ent.hasClass("Elephant"))
bestbase.assignResourceToDropsite(gameState, ent);
@ -114,7 +116,7 @@ m.HQ.prototype.assignStartingEntities = function(gameState)
// now assign entities garrisoned inside this entity
if (ent.isGarrisonHolder() && ent.garrisoned().length)
for (let id of ent.garrisoned())
bestBase.assignEntity(gameState.getEntityByID(id));
bestbase.assignEntity(gameState.getEntityById(id));
}
};
@ -226,6 +228,79 @@ m.HQ.prototype.structureAnalysis = function(gameState)
this.bAdvanced[i] = gameState.applyCiv(this.bAdvanced[i]);
};
/**
* build our first base
*/
m.HQ.prototype.buildFirstBase = function(gameState)
{
var total = gameState.getResources();
var template = gameState.getTemplate(gameState.applyCiv("structures/{civ}_civil_centre"));
if (!total.canAfford(new API3.Resources(template.cost())))
{
/* API3.warn("not enough resource to build a cc, try with a dock");
template = gameState.applyCiv("structures/{civ}_dock");
if (!gameState.isDisabledTemplates(template))
{
template = gameState.getTemplate(template);
if (!total.canAfford(new API3.Resources(template.cost())))
{
API3.warn("not enough resource for dock ... return");
return;
}
API3.warn("but we could still build a dock if it was implemented");
return;
} */
return;
}
// We first choose as startingPoint the point where we have the more units
let startingPoint = [];
for (let ent of gameState.getOwnUnits().values())
{
if (!ent.hasClass("Worker") && !(ent.hasClass("Support") && ent.hasClass("Elephant")))
continue;
if (ent.hasClass("Cavalry"))
continue;
let pos = ent.position();
if (!pos)
{
let holder = m.getHolder(ent, gameState);
if (!holder || !holder.position())
continue;
pos = holder.position();
}
let gamepos = gameState.ai.accessibility.gamePosToMapPos(pos);
let index = gamepos[0] + gamepos[1]*gameState.ai.accessibility.width;
let land = gameState.ai.accessibility.landPassMap[index];
let sea = gameState.ai.accessibility.navalPassMap[index];
let found = false;
for (let point of startingPoint)
{
if (land !== point.land || sea !== point.sea)
continue;
if (API3.SquareVectorDistance(point.pos, pos) > 2500)
continue;
point.weight += 1;
found = true;
break;
}
if (!found)
startingPoint.push({"pos": pos, "land": land, "sea": sea, "weight": 1});
}
if (!startingPoint.length)
{
API3.warn("Petra error in buildFirstBase, can not find a starting position");
return;
}
let imax = 0;
for (let i = 1; i < startingPoint.length; ++i)
if (startingPoint[i].weight > startingPoint[imax].weight)
imax = i;
var template = gameState.applyCiv("structures/{civ}_civil_centre");
gameState.ai.queues.civilCentre.addItem(new m.ConstructionPlan(gameState, template, { "base": -1, "resource": "wood", "proximity": startingPoint[imax].pos }));
};
/**
* set strategy if game without construction:
* - if one of our allies has a cc, affect a small fraction of our army for his defense, the rest will attack

View file

@ -23,7 +23,7 @@ var PETRA = function(m)
transporter = this.ID
*/
m.TransportPlan = function(gameState, units, startIndex, endIndex, endPos)
m.TransportPlan = function(gameState, units, startIndex, endIndex, endPos, ship)
{
this.ID = gameState.ai.uniqueIDs.transports++;
this.debug = gameState.ai.Config.debug;
@ -34,16 +34,32 @@ m.TransportPlan = function(gameState, units, startIndex, endIndex, endPos)
this.startIndex = startIndex;
// TODO only cases with land-sea-land are allowed for the moment
// we could also have land-sea-land-sea-land
this.sea = gameState.ai.HQ.getSeaIndex(gameState, startIndex, endIndex);
if (!this.sea)
if (startIndex === 1)
{
this.failed = true;
if (this.debug > 1)
API3.warn("transport plan with bad path: startIndex " + startIndex + " endIndex " + endIndex);
return false;
// special transport from already garrisoned ship
if (!ship)
{
this.failed = true;
return false;
}
this.sea = ship.getMetadata(PlayerID, "sea");
ship.setMetadata(PlayerID, "transporter", this.ID);
for (let ent of units)
ent.setMetadata(PlayerID, "onBoard", "onBoard");
}
else
{
this.sea = gameState.ai.HQ.getSeaIndex(gameState, startIndex, endIndex);
if (!this.sea)
{
this.failed = true;
if (this.debug > 1)
API3.warn("transport plan with bad path: startIndex " + startIndex + " endIndex " + endIndex);
return false;
}
}
for (var ent of units)
for (let ent of units)
{
ent.setMetadata(PlayerID, "transport", this.ID);
ent.setMetadata(PlayerID, "endPos", endPos);
@ -55,7 +71,7 @@ m.TransportPlan = function(gameState, units, startIndex, endIndex, endPos)
this.state = "boarding";
this.boardingPos = {};
this.needTransportShips = true;
this.needTransportShips = (ship === undefined);
this.nTry = {};
return true;
};