2011-12-17 22:59:27 +01:00
|
|
|
function Defence(){
|
2012-01-02 22:46:09 +01:00
|
|
|
this.ACQUIRE_DIST = Config.defence.acquireDistance;
|
|
|
|
this.RELEASE_DIST = Config.defence.releaseDistance;
|
2011-12-17 22:59:27 +01:00
|
|
|
|
2012-01-02 22:46:09 +01:00
|
|
|
this.GROUP_RADIUS = Config.defence.groupRadius; // units will be added to a group if they are within this radius
|
|
|
|
this.GROUP_BREAK_RADIUS = Config.defence.groupBreakRadius; // units will leave a group if they are outside of this radius
|
|
|
|
this.GROUP_MERGE_RADIUS = Config.defence.groupMergeRadius; // Two groups with centres this far apart will be merged
|
2011-12-17 22:59:27 +01:00
|
|
|
|
2012-01-02 22:46:09 +01:00
|
|
|
this.DEFENCE_RATIO = Config.defence.defenderRatio; // How many defenders we want per attacker. Need to balance fewer losses vs. lost economy
|
2011-12-17 22:59:27 +01:00
|
|
|
|
|
|
|
// These are objects with the keys being entity ids and values being the entity objects
|
|
|
|
// NOTE: It is assumed that all attackers have a valid position, the attackers list must be kept up to date so this
|
|
|
|
// property is maintained
|
|
|
|
this.attackers = {}; // Enemy soldiers which are attacking our base
|
|
|
|
this.defenders = {}; // Our soldiers currently being used for defence
|
|
|
|
|
|
|
|
// A list of groups, enemy soldiers are clumped together in groups.
|
|
|
|
this.groups = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
Defence.prototype.update = function(gameState, events, militaryManager){
|
|
|
|
Engine.ProfileStart("Defence Manager");
|
|
|
|
var enemyTroops = militaryManager.getEnemySoldiers();
|
|
|
|
|
|
|
|
this.updateAttackers(gameState, events, enemyTroops);
|
|
|
|
this.updateGroups();
|
|
|
|
var unassignedDefenders = this.updateDefenders(gameState);
|
|
|
|
this.assignDefenders(gameState, militaryManager, unassignedDefenders);
|
|
|
|
|
|
|
|
Engine.ProfileStop();
|
|
|
|
};
|
|
|
|
|
|
|
|
Defence.prototype.assignDefenders = function(gameState, militaryManager, unassignedDefenders){
|
|
|
|
var numAttackers = Object.keys(this.attackers).length;
|
|
|
|
var numDefenders = Object.keys(this.defenders).length;
|
|
|
|
var numUnassignedDefenders = unassignedDefenders.length;
|
|
|
|
var numAssignedDefenders = numDefenders - numUnassignedDefenders;
|
|
|
|
|
|
|
|
// TODO: this is non optimal, we may have unevenly distributed defenders
|
|
|
|
// Unassign defenders which aren't needed
|
|
|
|
if (numAttackers * this.DEFENCE_RATIO <= numAssignedDefenders){
|
|
|
|
militaryManager.unassignUnits(unassignedDefenders);
|
|
|
|
|
|
|
|
var CCs = gameState.getOwnEntities().filter(Filters.byClass("CivCentre"));
|
|
|
|
|
|
|
|
for (var i in unassignedDefenders){
|
|
|
|
var pos = this.defenders[unassignedDefenders[i]].position();
|
|
|
|
|
|
|
|
// Move back to nearest CC
|
|
|
|
if (pos){
|
|
|
|
var nearestCCArray = CCs.filterNearest(pos, 1).toEntityArray();
|
|
|
|
if (nearestCCArray.length > 0){
|
|
|
|
var movePos = nearestCCArray[0].position();
|
|
|
|
this.defenders[unassignedDefenders[i]].move(movePos[0], movePos[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete this.defenders[unassignedDefenders[i]];
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check to see if we need to recruit more defenders
|
|
|
|
if (numAttackers * this.DEFENCE_RATIO > numDefenders){
|
|
|
|
var numNeeded = Math.ceil(numAttackers * this.DEFENCE_RATIO - numDefenders);
|
|
|
|
var numIdleAvailable = militaryManager.countAvailableUnits(Filters.isIdle());
|
|
|
|
|
|
|
|
if (numIdleAvailable > numNeeded){
|
|
|
|
var newUnits = militaryManager.getAvailableUnits(numNeeded, Filters.isIdle());
|
|
|
|
for (var i in newUnits){
|
|
|
|
var ent = gameState.getEntityById(newUnits[i]);
|
|
|
|
}
|
|
|
|
unassignedDefenders = unassignedDefenders.concat(newUnits);
|
|
|
|
}else{
|
|
|
|
var newUnits = militaryManager.getAvailableUnits(numNeeded);
|
|
|
|
for (var i in newUnits){
|
|
|
|
var ent = gameState.getEntityById(newUnits[i]);
|
|
|
|
ent.setMetadata("initialPosition", ent.position());
|
|
|
|
}
|
|
|
|
unassignedDefenders = unassignedDefenders.concat(newUnits);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now distribute the unassigned defenders among the attacking groups.
|
|
|
|
for (var i in unassignedDefenders){
|
|
|
|
var id = unassignedDefenders[i];
|
|
|
|
var ent = gameState.getEntityById(id);
|
|
|
|
if (!ent.position()){
|
|
|
|
debug("Defender with no position! (shouldn't happen)");
|
|
|
|
debug(ent);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
var minDist = Math.min();
|
|
|
|
var closestGroup = undefined;
|
|
|
|
for (var j in this.groups){
|
|
|
|
var dist = VectorDistance(this.groups[j].position, ent.position());
|
|
|
|
if (dist < minDist && this.groups[j].members.length * this.DEFENCE_RATIO > this.groups[j].defenders.length){
|
|
|
|
minDist = dist;
|
|
|
|
closestGroup = this.groups[j];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (closestGroup !== undefined){
|
|
|
|
var rand = Math.floor(Math.random()*closestGroup.members.length);
|
|
|
|
ent.attack(closestGroup.members[rand]);
|
|
|
|
this.defenders[id] = ent;
|
|
|
|
closestGroup.defenders.push(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Defence.prototype.updateDefenders = function(gameState){
|
|
|
|
var newDefenders = {};
|
|
|
|
var unassignedDefenders = [];
|
|
|
|
|
|
|
|
for (var i in this.groups){
|
|
|
|
this.removeDestroyed(gameState, this.groups[i].defenders);
|
2012-05-19 00:27:08 +02:00
|
|
|
for (var j in this.groups[i].defenders){
|
2011-12-17 22:59:27 +01:00
|
|
|
var id = this.groups[i].defenders[j];
|
|
|
|
newDefenders[id] = this.defenders[id];
|
|
|
|
var ent = gameState.getEntityById(id);
|
|
|
|
|
|
|
|
// If the defender is idle then set it to attack another member of the group it is targetting
|
|
|
|
if (ent && ent.isIdle()){
|
|
|
|
var rand = Math.floor(Math.random()*this.groups[i].members.length);
|
|
|
|
ent.attack(this.groups[i].members[rand]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var id in this.defenders){
|
|
|
|
if (!gameState.getEntityById(id)){
|
|
|
|
delete this.defenders[id];
|
|
|
|
} else if (!newDefenders[id]){
|
|
|
|
unassignedDefenders.push(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return unassignedDefenders;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Returns an entity collection of key buildings which should be defended.
|
|
|
|
// Currently just returns civ centres
|
|
|
|
Defence.prototype.getKeyBuildings = function(gameState){
|
|
|
|
return gameState.getOwnEntities().filter(Filters.byClass("CivCentre"));
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This function puts all attacking enemy troops into this.attackers, the list from the turn before is put into
|
|
|
|
* this.oldAttackers, also any new attackers have their id's listed in this.newAttackers.
|
|
|
|
*/
|
|
|
|
Defence.prototype.updateAttackers = function(gameState, events, enemyTroops){
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
var keyBuildings = this.getKeyBuildings(gameState);
|
|
|
|
|
|
|
|
this.newAttackers = [];
|
|
|
|
this.oldAttackers = this.attackers;
|
|
|
|
this.attackers = {};
|
|
|
|
|
|
|
|
enemyTroops.forEach(function(ent){
|
|
|
|
if (ent.position()){
|
|
|
|
var minDist = Math.min();
|
|
|
|
keyBuildings.forEach(function(building){
|
|
|
|
if (building.position() && VectorDistance(ent.position(), building.position()) < minDist){
|
|
|
|
minDist = VectorDistance(ent.position(), building.position());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (self.oldAttackers[ent.id()]){
|
|
|
|
if (minDist < self.RELEASE_DIST){
|
|
|
|
self.attackers[ent.id()] = ent;
|
|
|
|
}
|
|
|
|
}else{
|
2012-01-02 22:46:09 +01:00
|
|
|
if (minDist < self.ACQUIRE_DIST){
|
2011-12-17 22:59:27 +01:00
|
|
|
self.attackers[ent.id()] = ent;
|
|
|
|
self.newAttackers.push(ent.id());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
Defence.prototype.removeDestroyed = function(gameState, entList){
|
|
|
|
for (var i = 0; i < entList.length; i++){
|
|
|
|
if (!gameState.getEntityById(entList[i])){
|
|
|
|
entList.splice(i, 1);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Defence.prototype.updateGroups = function(){
|
|
|
|
// clean up groups by removing members and removing empty groups
|
|
|
|
for (var i = 0; i < this.groups.length; i++){
|
|
|
|
var group = this.groups[i];
|
|
|
|
// remove members which are no longer attackers
|
|
|
|
for (var j = 0; j < group.members.length; j++){
|
|
|
|
if (!this.attackers[group.members[j]]){
|
|
|
|
group.members.splice(j, 1);
|
|
|
|
j--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// recalculate centre of group
|
|
|
|
group.sumPosition = [0,0];
|
|
|
|
for (var j = 0; j < group.members.length; j++){
|
|
|
|
group.sumPosition[0] += this.attackers[group.members[j]].position()[0];
|
|
|
|
group.sumPosition[1] += this.attackers[group.members[j]].position()[1];
|
|
|
|
}
|
|
|
|
group.position[0] = group.sumPosition[0]/group.members.length;
|
|
|
|
group.position[1] = group.sumPosition[1]/group.members.length;
|
|
|
|
|
|
|
|
// remove members that are too far away
|
|
|
|
for (var j = 0; j < group.members.length; j++){
|
|
|
|
if ( VectorDistance(this.attackers[group.members[j]].position(), group.position) > this.GROUP_BREAK_RADIUS){
|
|
|
|
this.newAttackers.push(group.members[j]);
|
|
|
|
group.sumPosition[0] -= this.attackers[group.members[j]].position()[0];
|
|
|
|
group.sumPosition[1] -= this.attackers[group.members[j]].position()[1];
|
|
|
|
group.members.splice(j, 1);
|
|
|
|
j--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (group.members.length === 0){
|
|
|
|
this.groups.splice(i, 1);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
|
|
|
|
group.position[0] = group.sumPosition[0]/group.members.length;
|
|
|
|
group.position[1] = group.sumPosition[1]/group.members.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
// add ungrouped attackers to groups
|
|
|
|
for (var j in this.newAttackers){
|
|
|
|
var ent = this.attackers[this.newAttackers[j]];
|
|
|
|
var foundGroup = false;
|
|
|
|
for (var i in this.groups){
|
|
|
|
if (VectorDistance(ent.position(), this.groups[i].position) <= this.GROUP_RADIUS){
|
|
|
|
this.groups[i].members.push(ent.id());
|
|
|
|
|
|
|
|
this.groups[i].sumPosition[0] += ent.position()[0];
|
|
|
|
this.groups[i].sumPosition[1] += ent.position()[1];
|
|
|
|
this.groups[i].position[0] = this.groups[i].sumPosition[0]/this.groups[i].members.length;
|
|
|
|
this.groups[i].position[1] = this.groups[i].sumPosition[1]/this.groups[i].members.length;
|
|
|
|
|
|
|
|
foundGroup = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!foundGroup){
|
|
|
|
this.groups.push({"members": [ent.id()],
|
|
|
|
"position": [ent.position()[0], ent.position()[1]],
|
|
|
|
"sumPosition": [ent.position()[0], ent.position()[1]],
|
|
|
|
"defenders": []});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// merge groups which are close together
|
|
|
|
for (var i = 0; i < this.groups.length; i++){
|
|
|
|
for (var j = 0; j < this.groups.length; j++){
|
|
|
|
if (this.groups[i].members.length < this.groups[j].members.length){
|
|
|
|
if (VectorDistance(this.groups[i].position, this.groups[j].position) < this.GROUP_MERGE_RADIUS){
|
|
|
|
this.groups[j].members = this.groups[i].members.concat(this.groups[j].members);
|
|
|
|
this.groups[j].defenders = this.groups[i].defenders.concat(this.groups[j].defenders);
|
|
|
|
|
|
|
|
this.groups[j].sumPosition[0] += this.groups[i].sumPosition[0];
|
|
|
|
this.groups[j].sumPosition[1] += this.groups[i].sumPosition[1];
|
|
|
|
this.groups[j].position[0] = this.groups[j].sumPosition[0]/this.groups[j].members.length;
|
|
|
|
this.groups[j].position[1] = this.groups[j].sumPosition[1]/this.groups[j].members.length;
|
|
|
|
|
|
|
|
this.groups.splice(i, 1);
|
|
|
|
i--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|