1
0
forked from 0ad/0ad

Use new FastMoving class instead of Cavalry in AI/petra.

This class is an AI hint that such units are to be treated as moving
fast, e.g. war dogs, or cavalry.
This makes it easier to introduce camels and chariots correctly.

Further work is required to make the AI unit choices less hardcoded.

Patch by: Nescio
Reviewed By: Angen, wraitii
Differential Revision: https://code.wildfiregames.com/D2251
This was SVN commit r23916.
This commit is contained in:
wraitii 2020-08-01 10:35:44 +00:00
parent 17e6d8b24b
commit 164af0742a
10 changed files with 47 additions and 38 deletions

View File

@ -119,7 +119,7 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
priority = 250;
this.unitStat.Infantry = { "priority": 1, "minSize": 10, "targetSize": 20, "batchSize": 2, "classes": ["Infantry"],
"interests": [["strength", 1], ["costsResource", 0.5, "stone"], ["costsResource", 0.6, "metal"]] };
this.unitStat.Cavalry = { "priority": 1, "minSize": 2, "targetSize": 4, "batchSize": 2, "classes": ["Cavalry", "CitizenSoldier"],
this.unitStat.FastMoving = { "priority": 1, "minSize": 2, "targetSize": 4, "batchSize": 2, "classes": ["FastMoving", "CitizenSoldier"],
"interests": [["strength", 1]] };
if (data && data.targetSize)
this.unitStat.Infantry.targetSize = data.targetSize;
@ -128,7 +128,7 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
else if (type == "Raid")
{
priority = 150;
this.unitStat.Cavalry = { "priority": 1, "minSize": 3, "targetSize": 4, "batchSize": 2, "classes": ["Cavalry", "CitizenSoldier"],
this.unitStat.FastMoving = { "priority": 1, "minSize": 3, "targetSize": 4, "batchSize": 2, "classes": ["FastMoving", "CitizenSoldier"],
"interests": [ ["strength", 1] ] };
this.neededShips = 1;
}
@ -144,13 +144,13 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
"interests": [["strength", 3]] };
this.unitStat.ChampMeleeInfantry = { "priority": 1, "minSize": 3, "targetSize": 18, "batchSize": 3, "classes": ["Infantry", "Melee", "Champion"],
"interests": [["strength", 3]] };
this.unitStat.RangedCavalry = { "priority": 0.7, "minSize": 4, "targetSize": 20, "batchSize": 4, "classes": ["Cavalry", "Ranged", "CitizenSoldier"],
this.unitStat.RangedFastMoving = { "priority": 0.7, "minSize": 4, "targetSize": 20, "batchSize": 4, "classes": ["FastMoving", "Ranged", "CitizenSoldier"],
"interests": [["strength", 2]] };
this.unitStat.MeleeCavalry = { "priority": 0.7, "minSize": 4, "targetSize": 20, "batchSize": 4, "classes": ["Cavalry", "Melee", "CitizenSoldier"],
this.unitStat.MeleeFastMoving = { "priority": 0.7, "minSize": 4, "targetSize": 20, "batchSize": 4, "classes": ["FastMoving", "Melee", "CitizenSoldier"],
"interests": [["strength", 2]] };
this.unitStat.ChampRangedCavalry = { "priority": 1, "minSize": 3, "targetSize": 15, "batchSize": 3, "classes": ["Cavalry", "Ranged", "Champion"],
this.unitStat.ChampRangedFastMoving = { "priority": 1, "minSize": 3, "targetSize": 15, "batchSize": 3, "classes": ["FastMoving", "Ranged", "Champion"],
"interests": [["strength", 3]] };
this.unitStat.ChampMeleeCavalry = { "priority": 1, "minSize": 3, "targetSize": 15, "batchSize": 3, "classes": ["Cavalry", "Melee", "Champion"],
this.unitStat.ChampMeleeFastMoving = { "priority": 1, "minSize": 3, "targetSize": 15, "batchSize": 3, "classes": ["FastMoving", "Melee", "Champion"],
"interests": [["strength", 2]] };
this.unitStat.Hero = { "priority": 1, "minSize": 0, "targetSize": 1, "batchSize": 1, "classes": ["Hero"],
"interests": [["strength", 2]] };
@ -163,7 +163,7 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
"interests": [["canGather", 1], ["strength", 1.6], ["costsResource", 0.3, "stone"], ["costsResource", 0.3, "metal"]] };
this.unitStat.MeleeInfantry = { "priority": 1, "minSize": 6, "targetSize": 16, "batchSize": 3, "classes": ["Infantry", "Melee"],
"interests": [["canGather", 1], ["strength", 1.6], ["costsResource", 0.3, "stone"], ["costsResource", 0.3, "metal"]] };
this.unitStat.Cavalry = { "priority": 1, "minSize": 2, "targetSize": 6, "batchSize": 2, "classes": ["Cavalry", "CitizenSoldier"],
this.unitStat.FastMoving = { "priority": 1, "minSize": 2, "targetSize": 6, "batchSize": 2, "classes": ["FastMoving", "CitizenSoldier"],
"interests": [["strength", 1]] };
this.neededShips = 3;
}
@ -435,7 +435,7 @@ PETRA.AttackPlan.prototype.updatePreparation = function(gameState)
if (this.type != "Raid" || !this.forced) // Forced Raids have special purposes (as relic capture)
this.assignUnits(gameState);
if (this.type != "Raid" && gameState.ai.HQ.attackManager.getAttackInPreparation("Raid") !== undefined)
this.reassignCavUnit(gameState); // reassign some cav (if any) to fasten raid preparations
this.reassignFastUnit(gameState); // reassign some fast units (if any) to fasten raid preparations
// Fasten the end game.
if (gameState.ai.playedTurn % 5 == 0 && this.hasSiegeUnits())
@ -655,7 +655,7 @@ PETRA.AttackPlan.prototype.assignUnits = function(gameState)
{
let plan = this.name;
let added = false;
// If we can not build units, assign all available except those affected to allied defense to the current attack
// If we can not build units, assign all available except those affected to allied defense to the current attack.
if (!this.canBuildUnits)
{
for (let ent of gameState.getOwnUnits().values())
@ -671,11 +671,11 @@ PETRA.AttackPlan.prototype.assignUnits = function(gameState)
if (this.type == "Raid")
{
// Raid are fast cavalry attack: assign all cav except some for hunting
// Raids are quick attacks: assign all FastMoving soldiers except some for hunting.
let num = 0;
for (let ent of gameState.getOwnUnits().values())
{
if (!ent.hasClass("Cavalry") || !this.isAvailableUnit(gameState, ent))
if (!ent.hasClass("FastMoving") || !this.isAvailableUnit(gameState, ent))
continue;
if (num++ < 2)
continue;
@ -686,7 +686,7 @@ PETRA.AttackPlan.prototype.assignUnits = function(gameState)
return added;
}
// Assign all units without specific role
// Assign all units without specific role.
for (let ent of gameState.getOwnEntitiesByRole(undefined, true).values())
{
if (!ent.hasClass("Unit") || !this.isAvailableUnit(gameState, ent))
@ -697,7 +697,7 @@ PETRA.AttackPlan.prototype.assignUnits = function(gameState)
this.unitCollection.updateEnt(ent);
added = true;
}
// Add units previously in a plan, but which left it because needed for defense or attack finished
// Add units previously in a plan, but which left it because needed for defense or attack finished.
for (let ent of gameState.ai.HQ.attackManager.outOfPlan.values())
{
if (!this.isAvailableUnit(gameState, ent))
@ -751,14 +751,14 @@ PETRA.AttackPlan.prototype.isAvailableUnit = function(gameState, ent)
return true;
};
/** Reassign one (at each turn) Cav unit to fasten raid preparation. */
PETRA.AttackPlan.prototype.reassignCavUnit = function(gameState)
/** Reassign one (at each turn) FastMoving unit to fasten raid preparation. */
PETRA.AttackPlan.prototype.reassignFastUnit = function(gameState)
{
for (let ent of this.unitCollection.values())
{
if (!ent.position() || ent.getMetadata(PlayerID, "transport") !== undefined)
continue;
if (!ent.hasClass("Cavalry") || !ent.hasClass("CitizenSoldier"))
if (!ent.hasClass("FastMoving") || !ent.hasClass("CitizenSoldier"))
continue;
let raid = gameState.ai.HQ.attackManager.getAttackInPreparation("Raid");
ent.setMetadata(PlayerID, "plan", raid.name);
@ -1510,7 +1510,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
maybeUpdate = true;
else if (attackedByStructure[ent.id()] && target.hasClass("Field"))
maybeUpdate = true;
else if (!ent.hasClass("Cavalry") && !ent.hasClass("Ranged") &&
else if (!ent.hasClass("FastMoving") && !ent.hasClass("Ranged") &&
target.hasClass("FemaleCitizen") && target.unitAIState().split(".")[1] == "FLEEING")
maybeUpdate = true;
}
@ -1539,7 +1539,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
}
else if (attackTypes && attackTypes.indexOf("Ranged") !== -1)
range = 30 + ent.attackRange("Ranged").max;
else if (ent.hasClass("Cavalry"))
else if (ent.hasClass("FastMoving"))
range += 30;
range *= range;
let entAccess = PETRA.getLandAccess(gameState, ent);
@ -1597,7 +1597,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
}
else
{
let nearby = !ent.hasClass("Cavalry") && !ent.hasClass("Ranged");
let nearby = !ent.hasClass("FastMoving") && !ent.hasClass("Ranged");
let mUnit = enemyUnits.filter(enemy => {
if (!enemy.position() || !ent.canAttackTarget(enemy, PETRA.allowCapture(gameState, ent, enemy)))
return false;

View File

@ -650,7 +650,7 @@ PETRA.BaseManager.prototype.reassignIdleWorkers = function(gameState, idleWorker
}
}
}
else if (ent.hasClass("Cavalry"))
else if (PETRA.isFastMoving(ent))
ent.setMetadata(PlayerID, "subrole", "hunter");
else if (ent.hasClass("FishingBoat"))
ent.setMetadata(PlayerID, "subrole", "fisher");

View File

@ -4,6 +4,13 @@ PETRA.isSiegeUnit = function(ent)
return ent.hasClass("Siege") || ent.hasClass("Elephant") && ent.hasClass("Melee") && ent.hasClass("Champion");
};
/** returns true if this unit should be considered as "fast". */
PETRA.isFastMoving = function(ent)
{
// TODO: use clever logic based on walkspeed comparisons.
return ent.hasClass("FastMoving");
};
/** returns some sort of DPS * health factor. If you specify a class, it'll use the modifiers against that class too. */
PETRA.getMaxStrength = function(ent, debugLevel, DamageTypeImportance, againstClass)
{

View File

@ -306,7 +306,7 @@ PETRA.HQ.prototype.buildFirstBase = function(gameState)
{
if (!ent.hasClass("Worker") && !(ent.hasClass("Support") && ent.hasClass("Elephant")))
continue;
if (ent.hasClass("Cavalry"))
if (PETRA.isFastMoving(ent))
continue;
let pos = ent.position();
if (!pos)

View File

@ -112,7 +112,7 @@ PETRA.Worker.prototype.update = function(gameState, ent)
}
else if (!gameState.isPlayerAlly(territoryOwner))
{
let distanceSquare = ent.hasClass("Cavalry") ? 90000 : 30000;
let distanceSquare = ent.isFastMoving() ? 90000 : 30000;
let targetAccess = PETRA.getLandAccess(gameState, target);
let foodDropsites = gameState.playerData.hasSharedDropsites ?
gameState.getAnyDropsites("food") : gameState.getOwnDropsites("food");
@ -744,7 +744,7 @@ PETRA.Worker.prototype.startHunting = function(gameState, position)
let nearestSupplyDist = Math.min();
let nearestSupply;
let isCavalry = this.ent.hasClass("Cavalry");
let isFastMoving = PETRA.isFastMoving(this.ent);
let isRanged = this.ent.hasClass("Ranged");
let entPosition = position ? position : this.ent.position();
let foodDropsites = gameState.playerData.hasSharedDropsites ?
@ -784,41 +784,41 @@ PETRA.Worker.prototype.startHunting = function(gameState, position)
if (PETRA.IsSupplyFull(gameState, supply))
continue;
// check if available resource is worth one additionnal gatherer (except for farms)
// Check if available resource is worth one additionnal gatherer (except for farms).
let nbGatherers = supply.resourceSupplyNumGatherers() + gameState.ai.HQ.GetTCGatherer(supply.id());
if (nbGatherers > 0 && supply.resourceSupplyAmount()/(1+nbGatherers) < 30)
continue;
let canFlee = !supply.hasClass("Domestic") && supply.templateName().indexOf("resource|") == -1;
// Only cavalry and range units should hunt fleeing animals
if (canFlee && !isCavalry && !isRanged)
// Only FastMoving and Ranged units should hunt fleeing animals.
if (canFlee && !isFastMoving && !isRanged)
continue;
let supplyAccess = PETRA.getLandAccess(gameState, supply);
if (supplyAccess != this.entAccess)
continue;
// measure the distance to the resource
// measure the distance to the resource.
let dist = API3.SquareVectorDistance(entPosition, supply.position());
if (dist > nearestSupplyDist)
continue;
// Only cavalry should hunt faraway
if (!isCavalry && dist > 25000)
// Only FastMoving should hunt faraway.
if (!isFastMoving && dist > 25000)
continue;
// Avoid ennemy territory
// Avoid enemy territory.
let territoryOwner = gameState.ai.HQ.territoryMap.getOwner(supply.position());
if (territoryOwner != 0 && !gameState.isPlayerAlly(territoryOwner)) // player is its own ally
if (territoryOwner != 0 && !gameState.isPlayerAlly(territoryOwner)) // Player is its own ally.
continue;
// And if in ally territory, don't hunt this ally's cattle
// And if in ally territory, don't hunt this ally's cattle.
if (territoryOwner != 0 && territoryOwner != PlayerID && supply.owner() == territoryOwner)
continue;
// Only cavalry should hunt far from dropsite (specially for non domestic animals which flee)
if (!isCavalry && canFlee && territoryOwner == 0)
// Only FastMoving should hunt far from dropsite (specially for non-Domestic animals which flee).
if (!isFastMoving && canFlee && territoryOwner == 0)
continue;
let distanceSquare = isCavalry ? 35000 : (canFlee ? 7000 : 12000);
let distanceSquare = isFastMoving ? 35000 : (canFlee ? 7000 : 12000);
if (!hasFoodDropsiteWithinDistance(supply.position(), supplyAccess, distanceSquare))
continue;

View File

@ -57,7 +57,7 @@ Identity.prototype.Schema =
"</element>" +
"</optional>" +
"<optional>" +
"<element name='Classes' a:help='Optional list of space-separated classes applying to this entity. Choices include: AfricanElephant, AmunGuard, Animal, ApedemakGuard, Ashoka, Barter, Celt, CitizenSoldier, CivCentre, ConquestCritical, Domestic, DropsiteFood, DropsiteMetal, DropsiteStone, DropsiteWood, FemaleCitizen, ForestPlant, GarrisonFortress, Human, Iberian, Immortal, IndianElephant, Italian, Juggernaut, KushTrireme, MercenaryCamp, Organic, Player, PtolemyIV, SeaCreature, Spy, Structure, Unit, WallLong, WallMedium, WallShort, WallTower.'>" +
"<element name='Classes' a:help='Optional list of space-separated classes applying to this entity. Choices include: AfricanElephant, AmunGuard, Animal, ApedemakGuard, Ashoka, Barter, Celt, CitizenSoldier, CivCentre, ConquestCritical, Domestic, DropsiteFood, DropsiteMetal, DropsiteStone, DropsiteWood, FastMoving, FemaleCitizen, ForestPlant, GarrisonFortress, Human, Iberian, Immortal, IndianElephant, Italian, Juggernaut, KushTrireme, MercenaryCamp, Organic, Player, PtolemyIV, SeaCreature, Spy, Structure, Unit, WallLong, WallMedium, WallShort, WallTower.'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +

View File

@ -35,7 +35,7 @@
<Identity>
<GenericName>Cavalry</GenericName>
<Rank>Basic</Rank>
<Classes datatype="tokens">Human CitizenSoldier</Classes>
<Classes datatype="tokens">Human FastMoving CitizenSoldier</Classes>
<VisibleClasses datatype="tokens">Citizen Soldier Cavalry</VisibleClasses>
<Formations datatype="tokens">
special/formations/wedge

View File

@ -22,6 +22,7 @@
<Max>240</Max>
</Health>
<Identity>
<Classes datatype="tokens">FastMoving</Classes>
<VisibleClasses datatype="tokens">Cavalry</VisibleClasses>
<GenericName>Champion Cavalry</GenericName>
<Formations datatype="tokens">

View File

@ -35,7 +35,7 @@
<Identity>
<GenericName>War Dog</GenericName>
<Tooltip>Cannot attack Structures, Ships, or Siege Engines.</Tooltip>
<Classes datatype="tokens">Human</Classes>
<Classes datatype="tokens">Human FastMoving</Classes>
<VisibleClasses datatype="tokens">Dog Melee</VisibleClasses>
</Identity>
<Loot>

View File

@ -21,6 +21,7 @@
<Max>1500</Max>
</Health>
<Identity>
<Classes datatype="tokens">FastMoving</Classes>
<VisibleClasses datatype="tokens">Cavalry</VisibleClasses>
<GenericName>Hero Cavalry</GenericName>
<Formations datatype="tokens">