1
0
forked from 0ad/0ad

petra: make the ai able to build docks in islands in which it has no cc, and use them for trade.

This was SVN commit r21461.
This commit is contained in:
mimo 2018-03-07 18:45:16 +00:00
parent 061371da92
commit 212b74f7e5
3 changed files with 173 additions and 42 deletions

View File

@ -145,6 +145,14 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState)
if (pos)
return { "x": pos[0], "z": pos[1], "angle": 3*Math.PI/4, "base": 0 };
// No possible location, try to build instead a dock in a not-enemy island
let templateName = gameState.applyCiv("structures/{civ}_dock");
if (gameState.ai.HQ.canBuild(gameState, templateName) && !gameState.isTemplateDisabled(templateName))
{
template = gameState.getTemplate(templateName);
if (template && gameState.getResources().canAfford(new API3.Resources(template.cost())))
this.buildOverseaDock(gameState, template);
}
return false;
}
else if (template.hasClass("DefenseTower") || template.hasClass("Fortress") || template.hasClass("ArmyCamp"))
@ -347,6 +355,12 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState)
/**
* 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
* metadata.oversea is defined for dock in oversea islands
* => we try to maximize distance to our current docks (for trade)
* otherwise standard dock on an island where we already have a cc
* => 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.
*/
m.ConstructionPlan.prototype.findDockPosition = function(gameState)
{
@ -371,7 +385,8 @@ m.ConstructionPlan.prototype.findDockPosition = function(gameState)
let wantedLand = this.metadata && this.metadata.land ? this.metadata.land : null;
let wantedSea = this.metadata && this.metadata.sea ? this.metadata.sea : null;
let proxyAccess = this.metadata && this.metadata.proximity ? gameState.ai.accessibility.getAccessValue(this.metadata.proximity) : null;
if (nbShips === 0 && proxyAccess && proxyAccess > 1)
let oversea = this.metadata && this.metadata.oversea ? this.metadata.oversea : null;
if (nbShips == 0 && proxyAccess && proxyAccess > 1)
{
wantedLand = {};
wantedLand[proxyAccess] = true;
@ -399,66 +414,108 @@ m.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;
let ccEnts = oversea ? gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")) : null;
let docks = oversea ? gameState.getOwnStructures().filter(API3.Filters.byClass("Dock")) : null;
// Normalisation factors (only guessed, no attempt to optimize them)
let factor = proxyAccess ? 1 : oversea ? 0.2 : 40;
for (let j = 0; j < territoryMap.length; ++j)
{
if (!this.isDockLocation(gameState, j, halfDepth, wantedLand, wantedSea))
continue;
let dist;
if (!proxyAccess)
let score = 0;
if (!proxyAccess && !oversea)
{
// if not in our (or allied) territory, we do not want it too far to be able to defend it
dist = this.getFrontierProximity(gameState, j);
if (dist > 4)
score = this.getFrontierProximity(gameState, j);
if (score > 4)
continue;
score *= factor;
}
let i = territoryMap.getNonObstructedTile(j, radius, obstructions);
if (i < 0)
continue;
if (wantedSea && navalPassMap[i] !== wantedSea)
if (wantedSea && navalPassMap[i] != wantedSea)
continue;
let res = dropsiteTypes ? Math.min(maxRes, this.getResourcesAround(gameState, dropsiteTypes, j, 80)) : maxRes;
let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
// If proximity is given, we look for the nearest point
if (proxyAccess)
score = API3.VectorDistance(this.metadata.proximity, pos);
// Bonus for resources
score += 20 * (maxRes - res);
if (oversea)
{
// if proximity is given, we look for the nearest point
dist = API3.SquareVectorDistance(this.metadata.proximity, pos);
dist = Math.sqrt(dist) + 20 * (maxRes - res);
// Not much farther to one of our cc than to enemy ones
let enemyDist;
let ownDist;
for (let cc of ccEnts.values())
{
let owner = cc.owner();
if (owner != PlayerID && !gameState.isPlayerEnemy(owner))
continue;
let dist = API3.SquareVectorDistance(pos, cc.position());
if (owner == PlayerID && (!ownDist || dist < ownDist))
ownDist = dist;
if (gameState.isPlayerEnemy(owner) && (!enemyDist || dist < enemyDist))
enemyDist = dist;
}
if (ownDist && enemyDist && enemyDist < 0.5 * ownDist)
continue;
// And maximize distance for trade.
let dockDist = 0;
for (let dock of docks.values())
{
if (m.getSeaAccess(gameState, dock) != navalPassMap[i])
continue;
let dist = API3.SquareVectorDistance(pos, dock.position());
if (dist > dockDist)
dockDist = dist;
}
if (dockDist > 0)
{
dockDist = Math.sqrt(dockDist);
if (dockDist > width * cellSize) // Could happen only on square maps, but anyway we don't want to be too far away
continue;
score += factor * (width * cellSize - dockDist);
}
}
else
dist += 0.6 * (maxRes - res);
// Add a penalty if on the map border as ship movement will be difficult
if (gameState.ai.HQ.borderMap.map[j] & m.fullBorder_Mask)
dist += 2;
// do a pre-selection, supposing we will have the best possible water
if (bestIdx !== undefined && dist > bestVal + maxWater)
score += 20;
// Do a pre-selection, supposing we will have the best possible water
if (bestIdx !== undefined && score > bestVal + 5 * maxWater && !oversea)
continue;
let x = (i % obstructions.width + 0.5) * obstructions.cellSize;
let z = (Math.floor(i / obstructions.width) + 0.5) * obstructions.cellSize;
let angle = this.getDockAngle(gameState, x, z, halfSize);
if (angle === false)
if (angle == false)
continue;
let ret = this.checkDockPlacement(gameState, x, z, halfDepth, halfWidth, angle);
if (!ret || !gameState.ai.HQ.landRegions[ret.land])
continue;
// final selection now that the checkDockPlacement water is known
if (bestIdx !== undefined && dist + maxWater - ret.water > bestVal)
// Final selection now that the checkDockPlacement water is known
if (bestIdx !== undefined && score + 5 * (maxWater - ret.water) > bestVal)
continue;
if (this.metadata.proximity && gameState.ai.accessibility.regionSize[ret.land] < 4000)
continue;
if (gameState.ai.HQ.isDangerousLocation(gameState, pos, halfSize))
continue;
bestVal = dist + maxWater - ret.water;
bestVal = score + maxWater - ret.water;
bestIdx = i;
bestJdx = j;
bestAngle = angle;
bestLand = ret.land;
bestWater = ret.water;
}
if (bestVal < 0)
return false;
@ -472,26 +529,95 @@ m.ConstructionPlan.prototype.findDockPosition = function(gameState)
// Assign this dock to a base
let baseIndex = gameState.ai.HQ.basesMap.map[bestJdx];
if (!baseIndex)
{
for (let base of gameState.ai.HQ.baseManagers)
{
if (base.accessIndex !== bestLand)
continue;
baseIndex = base.ID;
break;
}
if (!baseIndex)
{
if (gameState.ai.HQ.numActiveBases() > 0)
API3.warn("Petra: dock constructed without base index " + baseIndex);
else
baseIndex = gameState.ai.HQ.baseManagers[0].ID;
}
}
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.
*/
m.ConstructionPlan.prototype.buildOverseaDock = function(gameState, template)
{
let docks = gameState.getOwnStructures().filter(API3.Filters.byClass("Dock"));
if (!docks.hasEntities())
return;
let passabilityMap = gameState.getPassabilityMap();
let cellArea = passabilityMap.cellSize * passabilityMap.cellSize;
let ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
let land = {};
let found;
for (let i = 0; i < gameState.ai.accessibility.regionSize.length; ++i)
{
if (gameState.ai.accessibility.regionType[i] != "land" ||
cellArea * gameState.ai.accessibility.regionSize[i] < 3600)
continue;
let keep = true;
for (let dock of docks.values())
{
if (m.getLandAccess(gameState, dock) != i)
continue;
keep = false;
break;
}
if (!keep)
continue;
let sea;
for (let cc of ccEnts.values())
{
let ccAccess = m.getLandAccess(gameState, cc);
if (ccAccess != i)
{
if (cc.owner() == PlayerID && !sea)
sea = gameState.ai.HQ.getSeaBetweenIndices(gameState, ccAccess, i);
continue;
}
// Docks on island where we have a cc are already done elsewhere
if (cc.owner() == PlayerID || gameState.isPlayerEnemy(cc.owner()))
{
keep = false;
break;
}
}
if (!keep || !sea)
continue;
land[i] = true;
found = true;
}
if (!found)
return;
if (!gameState.ai.HQ.navalMap)
API3.warn("petra.findOverseaLand on a non-naval map??? we should never go there ");
let oldTemplate = this.template;
let oldMetadata = this.metadata;
this.template = template;
let pos;
this.metadata = { "land": land, "oversea": true };
pos = this.findDockPosition(gameState);
if (pos)
{
let type = template.templateName();
let builder = gameState.findBuilder(type);
this.metadata.base = pos.base;
// Adjust a bit the position if needed
let cosa = Math.cos(pos.angle);
let sina = Math.sin(pos.angle);
let shiftMax = gameState.ai.HQ.territoryMap.cellSize;
for (let shift = 0; shift <= shiftMax; shift += 2)
{
builder.construct(type, pos.x-shift*sina, pos.z-shift*cosa, pos.angle, this.metadata);
if (shift > 0)
builder.construct(type, pos.x+shift*sina, pos.z+shift*cosa, pos.angle, this.metadata);
}
}
this.template = oldTemplate;
this.metadata = oldMetadata;
return;
};
/** Algorithm taken from the function GetDockAngle in simulation/helpers/Commands.js */
m.ConstructionPlan.prototype.getDockAngle = function(gameState, x, z, size)
{
@ -513,7 +639,7 @@ m.ConstructionPlan.prototype.getDockAngle = function(gameState, x, z, size)
pos[1] < 0 || pos[1] >= gameState.ai.accessibility.height)
continue;
let j = pos[0] + pos[1]*gameState.ai.accessibility.width;
if (gameState.ai.accessibility.navalPassMap[j] === seaRef)
if (gameState.ai.accessibility.navalPassMap[j] == seaRef)
waterPoints.push(i);
}
let length = waterPoints.length;
@ -645,7 +771,7 @@ m.ConstructionPlan.prototype.isDockLocation = function(gameState, j, dimension,
if (pos[1] < 0 || pos[1] >= gameState.ai.accessibility.height)
continue;
k = pos[0] + pos[1]*gameState.ai.accessibility.width;
if (wantedSea && gameState.ai.accessibility.navalPassMap[k] !== wantedSea)
if (wantedSea && gameState.ai.accessibility.navalPassMap[k] != wantedSea)
continue;
else if (!wantedSea && gameState.ai.accessibility.navalPassMap[k] < 2)
continue;

View File

@ -12,7 +12,6 @@ m.TradeManager = function(Config)
this.potentialTradeRoute = undefined;
this.routeProspection = false;
this.targetNumTraders = this.Config.Economy.targetNumTraders;
this.minimalGain = 5;
this.warnedAllies = {};
};
@ -20,6 +19,7 @@ m.TradeManager.prototype.init = function(gameState)
{
this.traders = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "role", "trader"));
this.traders.registerUpdates();
this.minimalGain = gameState.ai.HQ.navalMap ? 3 : 5;
};
m.TradeManager.prototype.hasTradeRoute = function()

View File

@ -419,9 +419,9 @@ m.TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex,
{
let pos = [i%width+0.5, Math.floor(i/width)+0.5];
pos = [cell*pos[0], cell*pos[1]];
let dist = API3.SquareVectorDistance(startPos, pos);
let dist = API3.VectorDistance(startPos, pos);
if (destination)
dist += API3.SquareVectorDistance(pos, destination);
dist += API3.VectorDistance(pos, destination);
if (avoidEnnemy)
{
let territoryOwner = gameState.ai.HQ.territoryMap.getOwner(pos);
@ -435,11 +435,16 @@ m.TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex,
API3.SquareVectorDistance(this.boardingPos[shipId], pos) < this.boardingRange)
dist += 1000000;
// and not too near our allied docks to not disturb naval traffic
let distSquare;
for (let dock of alliedDocks)
{
if (dock.foundationProgress() !== undefined)
distSquare = 900;
else
distSquare = 4900;
let dockDist = API3.SquareVectorDistance(dock.position(), pos);
if (dockDist < 4900)
dist += 100000 * (4900 - dockDist) / 4900;
if (dockDist < distSquare)
dist += 100000 * (distSquare - dockDist) / distSquare;
}
if (dist > distmin)
continue;