Petra: improve use of garrisoning for defense + some cleanings

This was SVN commit r15552.
This commit is contained in:
mimo
2014-07-23 17:57:32 +00:00
parent 2eb12c13ec
commit a574ca2e8f
7 changed files with 130 additions and 213 deletions
@@ -406,7 +406,7 @@ m.Template = m.Class({
buffHeal: function() {
if (!this.get("GarrisonHolder"))
return undefined;
return this.get("GarrisonHolder/BuffHeal");
return +this.get("GarrisonHolder/BuffHeal");
},
promotion: function() {
@@ -324,13 +324,19 @@ m.GameState.prototype.getEnemies = function(){
return ret;
};
m.GameState.prototype.getAllies = function(){ // Player is not included
m.GameState.prototype.getAllies = function(){
var ret = [];
for (var i in this.playerData.isAlly){
if (this.playerData.isAlly[i] && +i !== this.player){
for (var i in this.playerData.isAlly)
if (this.playerData.isAlly[i])
ret.push(i);
return ret;
};
m.GameState.prototype.getExclusiveAllies = function(){ // Player is not included
var ret = [];
for (var i in this.playerData.isAlly)
if (this.playerData.isAlly[i] && +i !== this.player)
ret.push(i);
}
}
return ret;
};
@@ -388,6 +394,10 @@ m.GameState.prototype.getAllyEntities = function() {
return this.entities.filter(m.Filters.byOwners(this.getAllies()));
};
m.GameState.prototype.getExclusiveAllyEntities = function() {
return this.entities.filter(m.Filters.byOwners(this.getExclusiveAllies()));
};
// Try to use a parameter for those three, it'll be a lot faster.
m.GameState.prototype.getEnemyEntities = function(enemyID) {
@@ -107,7 +107,7 @@ m.DefenseManager.prototype.isDangerous = function(gameState, entity)
if (this.Config.personality.cooperative > 0.3)
{
var allyCC = gameState.getAllyEntities().filter(API3.Filters.byClass("CivCentre"));
var allyCC = gameState.getExclusiveAllyEntities().filter(API3.Filters.byClass("CivCentre"));
for (var i in allyCC._entities)
{
if (this.Config.personality.cooperative < 0.6 && allyCC._entities[i].foundationProgress() !== undefined)
@@ -217,18 +217,16 @@ m.DefenseManager.prototype.checkEnemyArmies = function(gameState, events)
// army in neutral territory // TODO check smaller distance with all our buildings instead of only ccs with big distance
var stillDangerous = false;
var bases = gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).toEntityArray();
if (this.Config.personality.cooperative > 0.3)
{
var allyCC = gameState.getAllyEntities().filter(API3.Filters.byClass("CivCentre")).toEntityArray();
bases = bases.concat(allyCC);
}
var bases = gameState.getAllyEntities().filter(API3.Filters.byClass("CivCentre")).toEntityArray();
else
var bases = gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).toEntityArray();
for (var i in bases)
{
if (API3.SquareVectorDistance(bases[i].position(), army.foePosition) < 40000)
{
if(this.Config.debug > 0)
warn("army in neutral territory, but still near one of our CC");
API3.warn("army in neutral territory, but still near one of our CC");
stillDangerous = true;
break;
}
@@ -307,7 +305,7 @@ m.DefenseManager.prototype.assignDefenders = function(gameState)
if (aMin === undefined)
{
for (var a = 0; a < armiesNeeding.length; ++a)
warn(" defense/armiesNeeding " + uneval(armiesNeeding[a]["need"]));
API3.warn(" defense/armiesNeeding " + uneval(armiesNeeding[a]["need"]));
}
var str = m.getMaxStrength(ent);
@@ -331,7 +329,7 @@ m.DefenseManager.prototype.assignDefenders = function(gameState)
};
// If our defense structures are attacked, garrison soldiers inside when possible
// TODO transfer most of that code in a garrisonManager
// and if a support unit is attacked and has less than 30% health, garrison it inside the nearest cc
m.DefenseManager.prototype.checkEvents = function(gameState, events)
{
var self = this;
@@ -339,186 +337,98 @@ m.DefenseManager.prototype.checkEvents = function(gameState, events)
for (var evt of attackedEvents)
{
var target = gameState.getEntityById(evt.target);
if (!target || !gameState.isEntityOwn(target) || !target.getArrowMultiplier())
continue;
if (!target.isGarrisonHolder() || gameState.ai.HQ.garrisonManager.numberOfGarrisonedUnits(target) >= target.garrisonMax())
if (!target || !gameState.isEntityOwn(target) || !target.position())
continue;
if (target.hasClass("Ship")) // TODO integrate ships later need to be sure it is accessible
continue;
if (target.hasClass("Support") && target.healthLevel() < 0.4)
{
this.garrisonUnitForHealing(gameState, target);
continue;
}
var attacker = gameState.getEntityById(evt.attacker);
if (!attacker || !attacker.position())
continue;
var attackTypes = target.attackTypes();
if (!attackTypes || attackTypes.indexOf("Ranged") === -1)
continue;
var dist = API3.SquareVectorDistance(attacker.position(), target.position());
var range = target.attackRange("Ranged").max;
if (dist >= range*range)
continue;
var index = gameState.ai.accessibility.getAccessValue(target.position());
var garrisonManager = gameState.ai.HQ.garrisonManager;
gameState.getOwnUnits().filter(API3.Filters.byClassesAnd(["Infantry", "Ranged"])).filterNearest(target.position()).forEach(function(ent) {
if (garrisonManager.numberOfGarrisonedUnits(target) >= target.garrisonMax())
return;
if (!ent.position())
return;
if (ent.getMetadata(PlayerID, "transport") !== undefined)
return;
if (ent.getMetadata(PlayerID, "plan") === -2 || ent.getMetadata(PlayerID, "plan") === -3)
return;
if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1)
{
var subrole = ent.getMetadata(PlayerID, "subrole");
if (subrole && (subrole === "completing" || subrole === "walking" || subrole === "attacking"))
return;
}
if (gameState.ai.accessibility.getAccessValue(ent.position()) !== index)
return;
var army = ent.getMetadata(PlayerID, "PartOfArmy");
if (army !== undefined)
{
army = self.getArmy(army);
if (army !== undefined)
army.removeOwn(gameState, ent.id(), ent);
}
garrisonManager.garrison(gameState, ent, target, "protection");
});
if (target.isGarrisonHolder() && target.getArrowMultiplier())
this.garrisonRangedUnitsInside(gameState, target, attacker);
}
};
// this processes the attackmessages
// So that a unit that gets attacked will not be completely dumb.
// warning: big levels of indentation coming.
//m.DefenseManager.prototype.MessageProcess = function(gameState,events) {
/* var self = this;
var attackedEvents = events["Attacked"];
for (var key in attackedEvents){
var e = attackedEvents[key];
if (gameState.isEntityOwn(gameState.getEntityById(e.target))) {
var attacker = gameState.getEntityById(e.attacker);
var ourUnit = gameState.getEntityById(e.target);
// the attacker must not be already dead, and it must not be me (think catapults that miss).
if (attacker === undefined || attacker.owner() === PlayerID || attacker.position() === undefined)
continue;
var mapPos = this.dangerMap.gamePosToMapPos(attacker.position());
this.dangerMap.addInfluence(mapPos[0], mapPos[1], 4, 1, 'constant');
// disregard units from attack plans and defense.
if (ourUnit.getMetadata(PlayerID, "role") == "defense" || ourUnit.getMetadata(PlayerID, "role") == "attack")
continue;
var territory = this.territoryMap.getOwner(attacker.position());
if (attacker.owner() == 0)
{
if (ourUnit !== undefined && ourUnit.hasClass("Unit") && !ourUnit.hasClass("Support"))
ourUnit.attack(e.attacker);
else
{
ourUnit.flee(attacker);
ourUnit.setMetadata(PlayerID,"fleeing", gameState.getTimeElapsed());
}
if (territory === PlayerID)
{
// anyway we'll register the animal as dangerous, and attack it (note: only on our territory. Don't care otherwise).
this.listOfWantedUnits[attacker.id()] = new EntityCollection(gameState.sharedScript);
this.listOfWantedUnits[attacker.id()].addEnt(attacker);
this.listOfWantedUnits[attacker.id()].freeze();
this.listOfWantedUnits[attacker.id()].registerUpdates();
var filter = Filters.byTargetedEntity(attacker.id());
this.WantedUnitsAttacker[attacker.id()] = this.myUnits.filter(filter);
this.WantedUnitsAttacker[attacker.id()].registerUpdates();
}
} // Disregard military units except in our territory. Disregard all calls in enemy territory.
else if (territory == PlayerID || (territory != attacker.owner() && ourUnit.hasClass("Support")))
{
// TODO: this does not differentiate with buildings...
// These ought to be treated differently.
// units in attack plans will react independently, but we still list the attacks here.
if (attacker.hasClass("Structure")) {
// todo: we ultimately have to check wether it's a danger point or an isolated area, and if it's a danger point, mark it as so.
// Right now, to make the AI less gameable, we'll mark any surrounding resource as inaccessible.
// usual tower range is 80. Be on the safe side.
var close = gameState.getResourceSupplies("wood").filter(Filters.byDistance(attacker.position(), 90));
close.forEach(function (supply) { //}){
supply.setMetadata(PlayerID, "inaccessible", true);
});
} else {
// TODO: right now a soldier always retaliate... Perhaps it should be set in "Defense" mode.
// TODO: handle the ship case
if (attacker.hasClass("Ship"))
continue;
// This unit is dangerous. if it's in an army, it's being dealt with.
// if it's not in an army, it means it's either a lone raider, or it has got friends.
// In which case we must check for other dangerous units around, and perhaps armify them.
// TODO: perhaps someday army detection will have improved and this will require change.
var armyID = attacker.getMetadata(PlayerID, "inArmy");
if (armyID == undefined || !this.enemyArmy[attacker.owner()] || !this.enemyArmy[attacker.owner()][armyID]) {
if (this.reevaluateEntity(gameState, attacker))
{
var position = attacker.position();
var close = HQ.enemyWatchers[attacker.owner()].enemySoldiers.filter(Filters.byDistance(position, self.armyCompactSize));
if (close.length > 2 || ourUnit.hasClass("Support") || attacker.hasClass("Siege"))
{
// armify it, then armify units close to him.
this.armify(gameState,attacker);
armyID = attacker.getMetadata(PlayerID, "inArmy");
close.forEach(function (ent) { //}){
if (API3.SquareVectorDistance(position, ent.position()) < self.armyCompactSize)
{
ent.setMetadata(PlayerID, "inArmy", armyID);
self.enemyArmy[ent.owner()][armyID].addEnt(ent);
}
});
return; // don't use too much processing power. If there are other cases, they'll be processed soon enough.
}
}
// Defensemanager will deal with them in the next turn.
}
if (ourUnit !== undefined && ourUnit.hasClass("Unit")) {
if (ourUnit.hasClass("Support")) {
// let's try to garrison this support unit.
if (ourUnit.position())
{
var buildings = gameState.getOwnEntities().filter(Filters.byCanGarrison()).filterNearest(ourUnit.position(),4).toEntityArray();
var garrisoned = false;
for (var i in buildings)
{
var struct = buildings[i];
if (struct.garrisoned() && struct.garrisonMax() - struct.garrisoned().length > 0)
{
garrisoned = true;
ourUnit.garrison(struct);
break;
}
}
if (!garrisoned) {
ourUnit.flee(attacker);
ourUnit.setMetadata(PlayerID,"fleeing", gameState.getTimeElapsed());
}
}
} else {
// It's a soldier. Right now we'll retaliate
// TODO: check for stronger units against this type, check for fleeing options, etc.
// Check also for neighboring towers and garrison there perhaps?
ourUnit.attack(e.attacker);
}
}
}
}
m.DefenseManager.prototype.garrisonRangedUnitsInside = function(gameState, target, attacker)
{
if (gameState.ai.HQ.garrisonManager.numberOfGarrisonedUnits(target) >= target.garrisonMax())
return;
var attackTypes = target.attackTypes();
if (!attackTypes || attackTypes.indexOf("Ranged") === -1)
return;
var dist = API3.SquareVectorDistance(attacker.position(), target.position());
var range = target.attackRange("Ranged").max;
if (dist >= range*range)
return;
var index = gameState.ai.accessibility.getAccessValue(target.position());
var garrisonManager = gameState.ai.HQ.garrisonManager;
gameState.getOwnUnits().filter(API3.Filters.byClassesAnd(["Infantry", "Ranged"])).filterNearest(target.position()).forEach(function(ent) {
if (garrisonManager.numberOfGarrisonedUnits(target) >= target.garrisonMax())
return;
if (!ent.position())
return;
if (ent.getMetadata(PlayerID, "transport") !== undefined)
return;
if (ent.getMetadata(PlayerID, "plan") === -2 || ent.getMetadata(PlayerID, "plan") === -3)
return;
if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1)
{
var subrole = ent.getMetadata(PlayerID, "subrole");
if (subrole && (subrole === "completing" || subrole === "walking" || subrole === "attacking"))
return;
}
}
*/
//}; // nice sets of closing brackets, isn't it?
if (gameState.ai.accessibility.getAccessValue(ent.position()) !== index)
return;
var army = ent.getMetadata(PlayerID, "PartOfArmy");
if (army !== undefined)
{
army = self.getArmy(army);
if (army !== undefined)
army.removeOwn(gameState, ent.id(), ent);
}
garrisonManager.garrison(gameState, ent, target, "protection");
});
};
// garrison a hurt unit inside the nearest healing structure
m.DefenseManager.prototype.garrisonUnitForHealing = function(gameState, unit)
{
var distmin = Math.min();
var nearest = undefined;
var unitAccess = gameState.ai.accessibility.getAccessValue(unit.position());
var garrisonManager = gameState.ai.HQ.garrisonManager;
gameState.getAllyEntities().filter(API3.Filters.byClass("Structure")).forEach(function(ent) {
if (!ent.buffHeal())
return;
if (!MatchesClassList(ent.garrisonableClasses(), unit.classes()))
return;
if (garrisonManager.numberOfGarrisonedUnits(ent) >= ent.garrisonMax())
return;
var entAccess = ent.getMetadata(PlayerID, "access");
if (!entAccess)
{
entAccess = gameState.ai.accessibility.getAccessValue(ent.position());
ent.setMetadata(PlayerID, "access", entAccess);
}
if (entAccess !== unitAccess)
return;
var dist = API3.SquareVectorDistance(ent.position(), unit.position());
if (dist > distmin)
return;
distmin = dist;
nearest = ent;
});
if (nearest)
garrisonManager.garrison(gameState, unit, nearest, "protection");
};
return m;
}(PETRA);
@@ -66,8 +66,6 @@ m.GarrisonManager.prototype.update = function(gameState, queues)
return false;
});
var healer = holder.buffHeal();
for (var entId of holder._entity.garrisoned)
{
var ent = gameState.getEntityById(entId);
@@ -147,8 +145,7 @@ m.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, enemiesAround
case 'trade': // trader garrisoned in ship
return true;
case 'protection': // hurt unit for healing or ranged infantry for defense
var healer = holder.buffHeal();
if (healer && healer > 0 && ent.isHurt())
if (ent.isHurt() && holder.buffHeal())
return true;
if (enemiesAround && (ent.hasClass("Support") || (ent.hasClass("Ranged") && ent.hasClass("Infantry"))))
return true;
@@ -936,7 +936,7 @@ m.HQ.prototype.findStrategicCCLocation = function(gameState, template)
// TODO check that it is on same accessIndex
m.HQ.prototype.findMarketLocation = function(gameState, template)
{
var markets = gameState.getAllyEntities().filter(API3.Filters.byClass("Market")).toEntityArray();
var markets = gameState.getExclusiveAllyEntities().filter(API3.Filters.byClass("Market")).toEntityArray();
if (!markets.length)
markets = gameState.getOwnStructures().filter(API3.Filters.byClass("Market")).toEntityArray();
@@ -149,15 +149,15 @@ m.QueueManager.prototype.printQueues = function(gameState)
if (ent.getMetadata(PlayerID, "role") == "worker" && ent.getMetadata(PlayerID, "plan") == undefined)
numWorkers++;
});
warn("---------- QUEUES ------------ with pop " + gameState.getPopulation() + " and workers " + numWorkers);
API3.warn("---------- QUEUES ------------ with pop " + gameState.getPopulation() + " and workers " + numWorkers);
for (var i in this.queues)
{
var qStr = "";
var q = this.queues[i];
if (q.queue.length > 0)
{
warn(i + ": ( with priority " + this.priorities[i] +" and accounts " + uneval(this.accounts[i]) +")");
warn(" while maxAccountWanted(0.6) is " + uneval(q.maxAccountWanted(gameState, 0.6)));
API3.warn(i + ": ( with priority " + this.priorities[i] +" and accounts " + uneval(this.accounts[i]) +")");
API3.warn(" while maxAccountWanted(0.6) is " + uneval(q.maxAccountWanted(gameState, 0.6)));
}
for (var j in q.queue)
{
@@ -165,18 +165,18 @@ m.QueueManager.prototype.printQueues = function(gameState)
if (q.queue[j].number)
qStr += "x" + q.queue[j].number;
qStr += " isGo " + q.queue[j].isGo(gameState);
warn(qStr);
API3.warn(qStr);
}
}
warn("Accounts");
API3.warn("Accounts");
for (var p in this.accounts)
warn(p + ": " + uneval(this.accounts[p]));
warn("Current Resources: " + uneval(gameState.getResources()));
warn("Available Resources: " + uneval(this.getAvailableResources(gameState)));
warn("Wanted Gather Rates: " + uneval(this.wantedGatherRates(gameState)));
warn("Current Gather Rates: " + uneval(gameState.ai.HQ.GetCurrentGatherRates(gameState)));
warn("Most needed resources: " + uneval(gameState.ai.HQ.pickMostNeededResources(gameState)));
warn("------------------------------------");
API3.warn(p + ": " + uneval(this.accounts[p]));
API3.warn("Current Resources: " + uneval(gameState.getResources()));
API3.warn("Available Resources: " + uneval(this.getAvailableResources(gameState)));
API3.warn("Wanted Gather Rates: " + uneval(this.wantedGatherRates(gameState)));
API3.warn("Current Gather Rates: " + uneval(gameState.ai.HQ.GetCurrentGatherRates(gameState)));
API3.warn("Most needed resources: " + uneval(gameState.ai.HQ.pickMostNeededResources(gameState)));
API3.warn("------------------------------------");
};
// nice readable HTML version.
@@ -344,7 +344,7 @@ m.QueueManager.prototype.distributeResources = function(gameState)
}
}
if (available < 0)
warn("Petra: problem with remaining " + res + " in queueManager " + available);
API3.warn("Petra: problem with remaining " + res + " in queueManager " + available);
}
};
@@ -378,7 +378,7 @@ m.QueueManager.prototype.switchResource = function(gameState, res)
this.accounts[i][res] -= diff;
++otherQueue.switched;
if (this.Config.debug > 1)
warn ("switching queue " + res + " from " + i + " to " + j + " in amount " + diff);
API3.warn ("switching queue " + res + " from " + i + " to " + j + " in amount " + diff);
break;
}
}
@@ -419,7 +419,7 @@ m.QueueManager.prototype.update = function(gameState)
this.queues[i].check(gameState); // do basic sanity checks on the queue
if (this.priorities[i] > 0)
continue;
warn("QueueManager received bad priorities, please report this error: " + uneval(this.priorities));
API3.warn("QueueManager received bad priorities, please report this error: " + uneval(this.priorities));
this.priorities[i] = 1; // TODO: make the Queue Manager not die when priorities are zero.
}
@@ -512,7 +512,7 @@ m.QueueManager.prototype.getPriority = function(queueName)
m.QueueManager.prototype.changePriority = function(queueName, newPriority)
{
if (this.Config.debug > 0)
warn(">>> Priority of queue " + queueName + " changed from " + this.priorities[queueName] + " to " + newPriority);
API3.warn(">>> Priority of queue " + queueName + " changed from " + this.priorities[queueName] + " to " + newPriority);
var self = this;
if (this.queues[queueName] !== undefined)
this.priorities[queueName] = newPriority;
@@ -72,7 +72,7 @@ m.TradeManager.prototype.buildTradeRoute = function(gameState, queues)
{
var filter = API3.Filters.and(API3.Filters.byClass("Market"), API3.Filters.not(API3.Filters.isFoundation()));
var market1 = gameState.getOwnStructures().filter(filter).toEntityArray();
var market2 = gameState.getAllyEntities().filter(filter).toEntityArray();
var market2 = gameState.getExclusiveAllyEntities().filter(filter).toEntityArray();
if (market1.length + market2.length < 1) // We have to wait ... a first market will be built soon
return false;
@@ -110,7 +110,7 @@ m.TradeManager.prototype.buildTradeRoute = function(gameState, queues)
if (distmax > 0)
{
if (this.Config.debug > 1)
warn(" a second market will be built in base " + base);
API3.warn(" a second market will be built in base " + base);
// TODO build also docks when better
queues.economicBuilding.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_market", { "base": base }));
}
@@ -153,11 +153,11 @@ m.TradeManager.prototype.buildTradeRoute = function(gameState, queues)
if (distmax < 0)
{
if (this.Config.debug > 1)
warn("no trade route possible");
API3.warn("no trade route possible");
return false;
}
if (this.Config.debug > 0)
warn("one trade route set with gain " + Math.round(distmax / this.Config.distUnitGain));
API3.warn("one trade route set with gain " + Math.round(distmax / this.Config.distUnitGain));
return true;
};
@@ -202,7 +202,7 @@ m.TradeManager.prototype.setTradingGoods = function(gameState)
tradingGoods[mostNeeded[0].type] += nextNeed;
Engine.PostCommand(PlayerID, {"type": "set-trading-goods", "tradingGoods": tradingGoods});
if (this.Config.debug == 2)
warn(" trading goods set to " + uneval(tradingGoods));
API3.warn(" trading goods set to " + uneval(tradingGoods));
};
// Try to barter unneeded resources for needed resources.
@@ -269,7 +269,7 @@ m.TradeManager.prototype.performBarter = function(gameState)
{
barterers[0].barter(buy, bestToSell, 100);
if (this.Config.debug > 1)
warn("Necessity bartering: sold " + bestToSell +" for " + buy + " >> need sell " + needs[bestToSell]
API3.warn("Necessity bartering: sold " + bestToSell +" for " + buy + " >> need sell " + needs[bestToSell]
+ " need buy " + needs[buy] + " rate buy " + rates[buy] + " available sell " + available[bestToSell]
+ " available buy " + available[buy] + " barterRate " + bestRate);
return true;
@@ -302,7 +302,7 @@ m.TradeManager.prototype.performBarter = function(gameState)
{
barterers[0].barter(bestToBuy, "food", 100);
if (this.Config.debug > 1)
warn("Contingency bartering: sold food for " + bestToBuy + " available sell " + available["food"]
API3.warn("Contingency bartering: sold food for " + bestToBuy + " available sell " + available["food"]
+ " available buy " + available[bestToBuy] + " barterRate " + getBarterRate(prices, bestToBuy, "food"));
return true;
}
@@ -322,7 +322,7 @@ m.TradeManager.prototype.update = function(gameState, queues)
if (!source || !target || !gameState.getEntityById(source.id()) || !gameState.getEntityById(target.id()))
{
if (this.Config.debug > 1)
warn("We have lost our trade route");
API3.warn("We have lost our trade route");
this.tradeRoute = undefined;
return;
}