Change the defense system used by Aegis to use more modular armies. This should be faster and easier to extend, though right now it might not be as efficient as before.

Fix a few bugs, including a few bad ones in the economy.
Change the way messages are handled, should be marginally faster in the
later game.
Makes gatherers count limit be per-player (refs #1387 and #643).

This was SVN commit r14552.
This commit is contained in:
wraitii 2014-01-10 01:46:27 +00:00
parent 22a85b0eb4
commit ede4f32bf2
22 changed files with 1633 additions and 1326 deletions

View File

@ -30,7 +30,7 @@ m.AegisBot = function AegisBot(settings) {
this.firstTime = true;
this.savedEvents = [];
this.savedEvents = {};
this.defcon = 5;
this.defconChangeTime = -10000000;
@ -44,6 +44,7 @@ m.AegisBot.prototype.CustomInit = function(gameState, sharedScript) {
m.playerGlobals[PlayerID].uniqueIDBOPlans = 0; // training/building/research plans
m.playerGlobals[PlayerID].uniqueIDBases = 1; // base manager ID. Starts at one because "0" means "no base" on the map
m.playerGlobals[PlayerID].uniqueIDTPlans = 1; // transport plans. starts at 1 because 0 might be used as none.
m.playerGlobals[PlayerID].uniqueIDDefManagerArmy = 0;
this.HQ.init(gameState,sharedScript.events,this.queues);
m.debug ("Initialized with the difficulty " + this.Config.difficulty);
@ -90,16 +91,18 @@ m.AegisBot.prototype.CustomInit = function(gameState, sharedScript) {
}
m.AegisBot.prototype.OnUpdate = function(sharedScript) {
if (this.gameFinished){
return;
}
if (this.events.length > 0 && this.turn !== 0){
this.savedEvents = this.savedEvents.concat(this.events);
for (var i in this.events)
{
if(this.savedEvents[i] !== undefined)
this.savedEvents[i] = this.savedEvents[i].concat(this.events[i]);
else
this.savedEvents[i] = this.events[i];
}
// Run the update every n turns, offset depending on player ID to balance the load
if ((this.turn + this.player) % 8 == 5) {
@ -206,17 +209,17 @@ m.AegisBot.prototype.OnUpdate = function(sharedScript) {
// Generate some entropy in the random numbers (against humans) until the engine gets random initialised numbers
// TODO: remove this when the engine gives a random seed
var n = this.savedEvents.length % 29;
var n = this.savedEvents["Create"].length % 29;
for (var i = 0; i < n; i++){
Math.random();
}
delete this.savedEvents;
this.savedEvents = [];
for (var i in this.savedEvents)
this.savedEvents[i] = [];
Engine.ProfileStop();
}
this.turn++;
};

View File

@ -745,31 +745,32 @@ m.CityAttack.prototype.update = function(gameState, HQ, events){
var armyToProcess = {};
// Let's check if any of our unit has been attacked. In case yes, we'll determine if we're simply off against an enemy army, a lone unit/builing
// or if we reached the enemy base. Different plans may react differently.
for (var key in events) {
var e = events[key];
if (e.type === "Attacked" && e.msg) {
if (IDs.indexOf(e.msg.target) !== -1) {
var attacker = gameState.getEntityById(e.msg.attacker);
var ourUnit = gameState.getEntityById(e.msg.target);
if (attacker && attacker.position() && attacker.hasClass("Unit") && attacker.owner() != 0 && attacker.owner() != PlayerID) {
var territoryMap = m.createTerritoryMap(gameState);
if ( +territoryMap.point(attacker.position()) - 64 === +this.targetPlayer)
{
attackedNB++;
}
//if (HQ.enemyWatchers[attacker.owner()]) {
//toProcess[attacker.id()] = attacker;
//var armyID = HQ.enemyWatchers[attacker.owner()].getArmyFromMember(attacker.id());
//armyToProcess[armyID[0]] = armyID[1];
//}
}
// if we're being attacked by a building, flee.
if (attacker && ourUnit && attacker.hasClass("Structure")) {
ourUnit.flee(attacker);
var attackedEvents = events["Attacked"];
for (var key in attackedEvents) {
var e = attackedEvents[key];
if (IDs.indexOf(e.target) !== -1) {
var attacker = gameState.getEntityById(e.attacker);
var ourUnit = gameState.getEntityById(e.target);
if (attacker && attacker.position() && attacker.hasClass("Unit") && attacker.owner() != 0 && attacker.owner() != PlayerID) {
var territoryMap = m.createTerritoryMap(gameState);
if ( +territoryMap.point(attacker.position()) - 64 === +this.targetPlayer)
{
attackedNB++;
}
//if (HQ.enemyWatchers[attacker.owner()]) {
//toProcess[attacker.id()] = attacker;
//var armyID = HQ.enemyWatchers[attacker.owner()].getArmyFromMember(attacker.id());
//armyToProcess[armyID[0]] = armyID[1];
//}
}
// if we're being attacked by a building, flee.
if (attacker && ourUnit && attacker.hasClass("Structure")) {
ourUnit.flee(attacker);
}
}
}
if (attackedNB > 4) {
@ -897,13 +898,6 @@ m.CityAttack.prototype.update = function(gameState, HQ, events){
&& API3.SquareVectorDistance(this.unitCollection.filter(API3.Filters.byClass("Siege")).getCentrePosition(), this.path[0][0]) >= 650)
{
} else {
for (var i = 0; i < this.path.length; ++i)
{
m.debug ("path waypoint " + i + "," + this.path[i][1] + " at " + uneval(this.path[i][0]));
}
m.debug ("position is " + this.unitCollection.getCentrePosition());
// okay so here basically two cases. The first one is "we need a boat at this point".
// the second one is "we need to unload at this point". The third is "normal".
if (this.path[0][1] !== true)
@ -983,32 +977,31 @@ m.CityAttack.prototype.update = function(gameState, HQ, events){
if (this.state === "") {
// Units attacked will target their attacker unless they're siege. Then we take another non-siege unit to attack them.
for (var key in events) {
var e = events[key];
if (e.type === "Attacked" && e.msg) {
if (IDs.indexOf(e.msg.target) !== -1) {
var attacker = gameState.getEntityById(e.msg.attacker);
var ourUnit = gameState.getEntityById(e.msg.target);
if (attacker && attacker.position() && attacker.hasClass("Unit") && attacker.owner() != 0 && attacker.owner() != PlayerID) {
if (ourUnit.hasClass("Siege"))
var attackedEvents = events["Attacked"];
for (var key in attackedEvents) {
var e = attackedEvents[key];
if (IDs.indexOf(e.target) !== -1) {
var attacker = gameState.getEntityById(e.attacker);
var ourUnit = gameState.getEntityById(e.target);
if (attacker && attacker.position() && attacker.hasClass("Unit") && attacker.owner() != 0 && attacker.owner() != PlayerID) {
if (ourUnit.hasClass("Siege"))
{
var collec = this.unitCollection.filterNearest(ourUnit.position(), 8).filter(Filters.not(Filters.byClass("Siege"))).toEntityArray();
if (collec.length !== 0)
{
var collec = this.unitCollection.filterNearest(ourUnit.position(), 8).filter(API3.Filters.not(API3.Filters.byClass("Siege"))).toEntityArray();
if (collec.length !== 0)
collec[0].attack(attacker.id());
if (collec.length !== 1)
{
collec[0].attack(attacker.id());
if (collec.length !== 1)
collec[1].attack(attacker.id());
if (collec.length !== 2)
{
collec[1].attack(attacker.id());
if (collec.length !== 2)
{
collec[2].attack(attacker.id());
}
collec[2].attack(attacker.id());
}
}
} else {
ourUnit.attack(attacker.id());
}
} else {
ourUnit.attack(attacker.id());
}
}
}

View File

@ -161,81 +161,81 @@ m.BaseManager.prototype.initGatheringFunctions = function(HQ, gameState, specTyp
}
m.BaseManager.prototype.checkEvents = function (gameState, events, queues) {
for (var i in events)
var destEvents = events["Destroy"];
var createEvents = events["Create"];
var cFinishedEvents = events["ConstructionFinished"];
for (var i in destEvents)
{
if (events[i].type == "Destroy")
var evt = destEvents[i];
// let's check we haven't lost an important building here.
if (evt != undefined && !evt.SuccessfulFoundation && evt.entityObj != undefined && evt.metadata !== undefined && evt.metadata[PlayerID] &&
evt.metadata[PlayerID]["base"] !== undefined && evt.metadata[PlayerID]["base"] == this.ID)
{
// let's check we haven't lost an important building here.
var evt = events[i];
if (evt.msg != undefined && !evt.msg.SuccessfulFoundation && evt.msg.entityObj != undefined && evt.msg.metadata !== undefined && evt.msg.metadata[PlayerID] &&
evt.msg.metadata[PlayerID]["base"] !== undefined && evt.msg.metadata[PlayerID]["base"] == this.ID)
var ent = evt.entityObj;
if (ent.hasTerritoryInfluence())
this.territoryBuildings.splice(this.territoryBuildings.indexOf(ent.id()),1);
if (ent.resourceDropsiteTypes())
this.scrapDropsite(gameState, ent);
if (evt.metadata[PlayerID]["baseAnchor"] && evt.metadata[PlayerID]["baseAnchor"] == true)
{
var ent = evt.msg.entityObj;
if (ent.hasTerritoryInfluence())
this.territoryBuildings.splice(this.territoryBuildings.indexOf(ent.id()),1);
if (ent.resourceDropsiteTypes())
this.scrapDropsite(gameState, ent);
if (evt.msg.metadata[PlayerID]["baseAnchor"] && evt.msg.metadata[PlayerID]["baseAnchor"] == true)
// sounds like we lost our anchor. Let's try rebuilding it.
// TODO: currently the HQ manager sets us as initgathering, we probably ouht to do it
this.anchor = undefined;
this.constructing = true; // let's switch mode.
this.workers.forEach( function (worker) {
worker.stopMoving();
});
if (ent.hasClass("CivCentre"))
{
// sounds like we lost our anchor. Let's try rebuilding it.
// TODO: currently the HQ manager sets us as initgathering, we probably ouht to do it
this.anchor = undefined;
this.constructing = true; // let's switch mode.
this.workers.forEach( function (worker) {
worker.stopMoving();
});
if (ent.hasClass("CivCentre"))
{
// TODO: might want to tell the queue manager to pause other stuffs if we are the only base.
queues.civilCentre.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_civil_centre", { "base" : this.ID, "baseAnchor" : true }, 0 , -1,ent.position()));
} else {
// TODO
queues.civilCentre.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_civil_centre", { "base" : this.ID, "baseAnchor" : true },0,-1,ent.position()));
}
// TODO: might want to tell the queue manager to pause other stuffs if we are the only base.
queues.civilCentre.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_civil_centre", { "base" : this.ID, "baseAnchor" : true }, 0 , -1,ent.position()));
} else {
// TODO
queues.civilCentre.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_civil_centre", { "base" : this.ID, "baseAnchor" : true },0,-1,ent.position()));
}
}
}
}
for (var i in cFinishedEvents)
{
var evt = cFinishedEvents[i];
if (evt && evt.newentity)
{
// TODO: we ought to add new resources or do something.
var ent = gameState.getEntityById(evt.newentity);
if (ent === undefined)
continue;
if (ent.getMetadata(PlayerID,"base") == this.ID)
{
if(ent.hasTerritoryInfluence())
this.territoryBuildings.push(ent.id());
if (ent.resourceDropsiteTypes())
for (ress in ent.resourceDropsiteTypes())
this.initializeDropsite(gameState, ent, ent.resourceDropsiteTypes()[ress]);
if (ent.resourceSupplyAmount() && ent.resourceSupplyType()["specific"] == "grain")
this.assignResourceToDP(gameState,ent);
}
}
}
for (var i in events)
for (var i in createEvents)
{
if (events[i].type == "ConstructionFinished")
var evt = createEvents[i];
// Checking for resources.
var evt = events[i];
if (evt && evt.entity)
{
// let's check we haven't lost an important building here.
var evt = events[i];
if (evt.msg && evt.msg.newentity)
{
// TODO: we ought to add new resources or do something.
var ent = gameState.getEntityById(evt.msg.newentity);
if (ent === undefined)
continue;
if (ent.getMetadata(PlayerID,"base") == this.ID)
{
if(ent.hasTerritoryInfluence())
this.territoryBuildings.push(ent.id());
if (ent.resourceDropsiteTypes())
for (var ress in ent.resourceDropsiteTypes())
this.initializeDropsite(gameState, ent, ent.resourceDropsiteTypes()[ress]);
if (ent.resourceSupplyAmount() && ent.resourceSupplyType()["specific"] == "grain")
this.assignResourceToDP(gameState,ent);
}
}
} else if (events[i].type == "Create")
{
// Checking for resources.
var evt = events[i];
if (evt.msg && evt.msg.entity)
{
var ent = gameState.getEntityById(evt.msg.entity);
if (ent === undefined)
continue;
if (ent.resourceSupplyAmount() && ent.owner() == 0)
this.assignResourceToDP(gameState,ent);
}
var ent = gameState.getEntityById(evt.entity);
if (ent === undefined)
continue;
if (ent.resourceSupplyAmount() && ent.owner() == 0)
this.assignResourceToDP(gameState,ent);
}
}
};
@ -461,6 +461,8 @@ m.BaseManager.prototype.findBestDropsiteLocation = function(gameState, resource)
var best = friendlyTiles.findBestTile(2, obstructions); // try to find a spot to place a DP.
var bestIdx = best[0];
m.debug ("for dropsite best is " +best[1] + " at " + gameState.getTimeElapsed());
// tell the dropsite builder we haven't found anything satisfactory.
if (best[1] < 60)
return false;
@ -644,7 +646,21 @@ m.BaseManager.prototype.checkResourceLevels = function (gameState,queues) {
// let's return the estimated gather rates.
m.BaseManager.prototype.getGatherRates = function(gameState, currentRates) {
for (var i in currentRates)
{
// I calculate the exact gathering rate for each unit.
// I must then lower that to account for travel time.
// Given that the faster you gather, the more travel time matters,
// I use some logarithms.
// TODO: this should take into account for unit speed and/or distance to target
var units = this.gatherersByType(gameState, i);
units.forEach(function (ent) {
var gRate = ent.currentGatherRate()
if (gRate !== undefined)
currentRates[i] += Math.log(1+gRate)/1.1;
});
}
};
m.BaseManager.prototype.assignRolelessUnits = function(gameState) {
@ -774,7 +790,6 @@ m.BaseManager.prototype.pickBuilders = function(gameState, number) {
}
m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) {
// If we have some foundations, and we don't have enough builder-workers,
// try reassigning some other workers who are nearby
@ -782,6 +797,7 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) {
var self = this;
// TODO: this is not perfect performance-wise.
var foundations = this.buildings.filter(API3.Filters.and(API3.Filters.isFoundation(),API3.Filters.not(API3.Filters.byClass("Field")))).toEntityArray();
var damagedBuildings = this.buildings.filter(function (ent) { if (ent.needsRepair() && ent.getMetadata(PlayerID, "plan") == undefined) { return true; } return false; }).toEntityArray();
@ -914,6 +930,13 @@ m.BaseManager.prototype.update = function(gameState, queues, events) {
Engine.ProfileStart("Assign builders");
this.assignToFoundations(gameState);
Engine.ProfileStop()
if (this.constructing && this.anchor)
{
var terrMap = m.createTerritoryMap(gameState);
if(terrMap.getOwner(this.anchor.position()) !== 0 && terrMap.getOwner(this.anchor.position()) !== PlayerID)
this.anchor.destroy();
}
// if (!this.constructing)
// {

View File

@ -3,8 +3,8 @@ var AEGIS = function(m)
m.Config = function() {
this.difficulty = 2; // 0 is sandbox, 1 is easy, 2 is medium, 3 is hard, 4 is very hard.
// overriden by the GUI
// overriden by the GUI, this defines the base difficulty.
this.Military = {
"fortressLapseTime" : 540, // Time to wait between building 2 fortresses
"defenceBuildingTime" : 600, // Time to wait before building towers or fortresses
@ -31,9 +31,12 @@ m.Config = function() {
// defence
this.Defence =
{
"defenceRatio" : 5, // see defence.js for more info.
"armyCompactSize" : 700, // squared. Half-diameter of an army.
"armyBreakawaySize" : 900 // squared.
"defenceRatio" : 2, // see defence.js for more info.
"armyCompactSize" : 2000, // squared. Half-diameter of an army.
"armyBreakawaySize" : 3500, // squared.
"armyMergeSize" : 1400, // squared.
"armyStrengthWariness" : 2, // Representation of how important army strength is for its "watch level" (see defense-helper.js).
"prudence" : 1 // Representation of how quickly we'll forget about a dangerous army.
};
// military

View File

@ -0,0 +1,559 @@
var AEGIS = function(m)
{
// Defines an army for the defence manager's use.
/*
An army is a collection of an enemy player's units
And of my own defenders against those units.
This doesn't use entity collections are they aren't really useful
and it would probably slow the rest of the system down too much.
All entities are therefore lists of ID
*/
m.Army = function(gameState, defManager, entities, ownEntities, alliedEntities)
{
this.Config = defManager.Config;
this.defenceRatio = this.Config.Defence.defenceRatio;
this.compactSize = this.Config.Defence.armyCompactSize;
this.breakawaySize = this.Config.Defence.armyBreakawaySize;
this.watchTSMultiplicator = this.Config.Defence.armyStrengthWariness;
this.watchDecrement = this.Config.Defence.prudence;
this.defenceManager = defManager;
if (!entities.length)
{
warn ("An army was created with no enemy units");
return zgsgf; // should error out to give the "stacktrace".
}
this.ID = m.playerGlobals[PlayerID].uniqueIDDefManagerArmy++;
this.position = [0,0];
this.positionLastUpdate = gameState.getTimeElapsed();
this.state = 2; // 1 or 2, 1 for "watch", 2 for "attack"
// representation of how much attention we're giving to this army
// If the state is 1 (watch), and this gets too high, we'll start attacking it (-> state = 2)
this.watchLevel = 0;
this.timeOfFoundation = gameState.getTimeElapsed();
// target particularly units with this Class
this.priorityTarget = "";
// target particularly those units (takes precedence over the Class one).
this.priorityTargets = [];
// Some caching
// A list of our defenders (and allies?) that were tasked with attacking a particular unit
// This doesn't mean that they actually are since they could move on to something else on their own.
this.assignedAgainst = {};
// a list of who we assigned ourx defenders too.
this.assignedTo = {};
this.entities = [];
this.totalStrength = 0;
this.strength = {
"spear" : 0,
"sword" : 0,
"ranged" : 0,
"meleeCav" : 0,
"rangedCav" : 0,
"elephant" : 0,
"rangedSiege" : 0,
"meleeSiege" : 0
};
this.ownEntities = [];
this.ownTotalStrenght = 0;
this.ownStrenght = {
"spear" : 0,
"sword" : 0,
"ranged" : 0,
"meleeCav" : 0,
"rangedCav" : 0,
"elephant" : 0,
"rangedSiege" : 0,
"meleeSiege" : 0
};
/*
// TODO: allies
if (alliedEntities === undefined)
return true;
this.ownEntities = ownEntities;
*/
// actually add units
for (var i in entities)
this.addEnemy(gameState,entities[i], true);
for (var i in ownEntities)
this.addDefender(gameState,ownEntities[i]);
// let's now calculate some sensible default values
// no sanity check here, shouldn't be needed.
this.owner = gameState.getEntityById(this.entities[0]).owner();
for (var i in this.entities)
{
var ent = gameState.getEntityById(this.entities[i]);
if (!ent)
{
warn("Tried to create an army with unusable units, crashing for stacktrace");
warn("debug : " + uneval(this.entities) + ", " + uneval(this.entities[i]));
return sofgkjs;
}
this.evaluateStrength(ent);
var pos = ent.position();
this.position[0] += pos[0];
this.position[1] += pos[1];
ent.setMetadata(PlayerID, "DefManagerArmy", this.ID);
}
this.position[0] /= this.entities.length;
this.position[1] /= this.entities.length;
for (var i in this.ownEntities)
{
var ent = gameState.getEntityById(this.ownEntities[i]);
if (!ent)
{
warn("Tried to create an army with unusable units, crashing for stacktrace");
warn("debug : " + uneval(this.entities) + ", " + uneval(this.entities[i]));
return sofgkjs;
}
this.evaluateStrength(ent, true);
}
// TODO: allies
this.checkDangerosity(gameState); // might push us to 1.
this.watchLevel = this.totalStrength * this.watchTSMultiplicator;
return true;
}
m.Army.prototype.recalculatePosition = function(gameState, force)
{
if (!force && this.positionLastUpdate === gameState.getTimeElapsed())
return;
var pos = [0,0];
for (var i in this.entities)
{
var ent = gameState.getEntityById(this.entities[i]);
var epos = ent.position();
pos[0] += epos[0];
pos[1] += epos[1];
}
this.position[0] = pos[0]/this.entities.length;
this.position[1] = pos[1]/this.entities.length;
}
m.Army.prototype.recalculateStrength = function (gameState)
{
for (var i in this.entities)
this.evaluateStrength(gameState.getEntityById(this.entities[i]));
for (var i in this.ownEntities)
this.evaluateStrength(gameState.getEntityById(this.ownEntities[i], true));
}
m.Army.prototype.evaluateStrength = function (ent, isOwn, remove)
{
var entStrength = m.getMaxStrength(ent);
if (remove)
entStrength *= -1;
if (isOwn)
this.ownTotalStrenght += entStrength;
else
this.totalStrength += entStrength;
if (ent.hasClass("Infantry"))
{
// maybe don't use "else" for multiple attacks later?
if (ent.hasClass("Spear"))
this.strength.spear += entStrength;
else if (ent.hasClass("Sword"))
this.strength.sword += entStrength;
else if (ent.hasClass("Ranged"))
this.strength.ranged += entStrength;
} else if (ent.hasClass("Elephant"))
this.strength.elephant += entStrength;
else if (ent.hasClass("Cavalry"))
{
if (ent.hasClass("Ranged"))
this.strength.rangedCav += entStrength;
else
this.strength.meleeCav += entStrength;
}
else if (ent.hasClass("Siege"))
{
if (ent.hasClass("Ranged"))
this.strength.rangedSiege += entStrength;
else
this.strength.meleeSiege += entStrength;
}
}
// add an entity to the army
// Will return true if the entity was added and false otherwise.
m.Army.prototype.addEnemy = function (gameState, enemyID, force)
{
if (this.entities.indexOf(enemyID) !== -1)
return false;
var ent = gameState.getEntityById(enemyID);
if (ent === undefined)
return false;
if (ent.position() === undefined)
return false;
// check distance
if (!force)
{
this.recalculatePosition(gameState);
if (API3.SquareVectorDistance(ent.position(),this.position) > this.compactSize)
return false;
}
this.entities.push(enemyID);
this.assignedAgainst[enemyID] = [];
this.recalculatePosition(gameState, true);
this.evaluateStrength(ent);
ent.setMetadata(PlayerID, "DefManagerArmy", this.ID);
return true;
}
// returns true if the entity was removed and false otherwise.
// TODO: when there is a technology update, we should probably recompute the strengths, or weird stuffs might happen.
m.Army.prototype.removeEnemy = function (gameState, enemyID, enemyEntity)
{
var idx = this.entities.indexOf(enemyID);
if (idx === -1)
return false;
var ent = enemyEntity === undefined ? gameState.getEntityById(enemyID) : enemyEntity;
if (ent === undefined)
return false;
this.entities.splice(idx, 1);
this.position[0] = (this.position[0] * (this.entities.length+1) - ent.position()[0])/this.entities.length;
this.position[0] = (this.position[1] * (this.entities.length+1) - ent.position()[1])/this.entities.length;
this.evaluateStrength(ent, false, true);
ent.setMetadata(PlayerID, "DefManagerArmy", undefined);
for (var i in this.assignedAgainst[enemyID])
this.assignDefender(gameState,this.assignedAgainst[enemyID][i]);
delete this.assignedAgainst[enemyID];
// TODO: reassign defenders assigned to it.
return true;
}
m.Army.prototype.addDefender = function (gameState, defenderID)
{
if (this.ownEntities.indexOf(defenderID) !== -1)
return false;
var ent = gameState.getEntityById(defenderID);
if (ent === undefined)
return false;
if (ent.position() === undefined)
return false;
this.ownEntities.push(defenderID);
this.evaluateStrength(ent, true);
ent.setMetadata(PlayerID, "DefManagerArmy", this.ID);
this.assignedTo[defenderID] = 0;
var formerRole = ent.getMetadata(PlayerID, "role");
var formerSubRole = ent.getMetadata(PlayerID, "subrole");
if (formerRole !== undefined)
ent.setMetadata(PlayerID,"formerRole", formerRole);
if (formerSubRole !== undefined)
ent.setMetadata(PlayerID,"formerSubRole", formerSubRole);
ent.setMetadata(PlayerID, "role", "defense");
ent.setMetadata(PlayerID, "subrole", "defending");
ent.stopMoving();
this.assignDefender(gameState,defenderID);
return true;
}
m.Army.prototype.removeDefender = function (gameState, defenderID, defenderObj)
{
var idx = this.ownEntities.indexOf(defenderID);
if (idx === -1)
return false;
var ent = defenderObj === undefined ? gameState.getEntityById(defenderID) : defenderObj;
if (ent === undefined)
return false;
this.ownEntities.splice(idx, 1);
this.evaluateStrength(ent, true, true);
if (this.assignedTo[defenderID])
{
var temp = this.assignedAgainst[this.assignedTo[defenderID]];
if (temp)
temp.splice(temp.indexOf(defenderID), 1);
}
delete this.assignedTo[defenderID];
if (defenderObj !== undefined || ent.owner() !== PlayerID)
return; // assume this means dead.
ent.setMetadata(PlayerID, "DefManagerArmy", undefined);
var formerRole = ent.getMetadata(PlayerID, "formerRole");
var formerSubRole = ent.getMetadata(PlayerID, "formerSubRole");
if (formerRole !== undefined)
ent.setMetadata(PlayerID,"role", formerRole);
if (formerSubRole !== undefined)
ent.setMetadata(PlayerID,"subrole", formerSubRole);
// tell the defence manager this unit has been released for reusage.
this.defenceManager.releasedDefenders.push(defenderID);
return true;
}
// this one is "undefined entity" proof because it's called at odd times.
m.Army.prototype.assignDefender = function (gameState, entID)
{
// we'll assume this defender is ours already.
// we'll also override any previous assignment
var ent = gameState.getEntityById(entID);
if (!ent)
return;
// TODO: improve the logic in there.
var maxVal = 1000000;
var maxEnt = -1;
for (var i in this.entities)
{
var id = this.entities[i];
var eEnt = gameState.getEntityById(id);
if (!eEnt)
continue;
// antigarrisonCheck
if (eEnt.position() === undefined)
{
this.removeEnemy(gameState, id);
return false; // need to wait one turn or it'll act weird
}
if (maxVal > this.assignedAgainst[id].length)
{
maxVal = this.assignedAgainst[id].length;
maxEnt = id;
}
}
if (maxEnt === -1)
return;
// let's attack id
this.assignedAgainst[maxEnt].push(entID);
this.assignedTo[entID] = maxEnt;
ent.attack(id);
return true;
}
m.Army.prototype.clear = function (gameState, events)
{
// release all units by deleting metadata about them, defenders are released
for (var i in this.entities)
gameState.getEntityById(this.entities[i]).setMetadata(PlayerID, "DefManagerArmy", undefined);
for (var i in this.ownEntities)
{
var ent = gameState.getEntityById(this.ownEntities[i]);
ent.setMetadata(PlayerID, "DefManagerArmy", undefined);
var formerRole = ent.getMetadata(PlayerID, "formerRole");
var formerSubRole = ent.getMetadata(PlayerID, "formerSubRole");
if (formerRole !== undefined)
ent.setMetadata(PlayerID,"role", formerRole);
if (formerSubRole !== undefined)
ent.setMetadata(PlayerID,"subrole", formerSubRole);
if (ent.owner() === PlayerID)
this.defenceManager.releasedDefenders.push(this.ownEntities[i]);
}
}
m.Army.prototype.merge = function (gameState, otherArmy)
{
if (this.owner !== otherArmy.owner)
{
warn("Tried to merge armies of different players, crashing for stacktrace");
return sofgkjs;
}
// basically the other army will get destroyed.
for (var i in otherArmy.assignedAgainst)
this.assignedAgainst[i] = otherArmy.assignedAgainst[i];
for (var i in otherArmy.assignedTo)
this.assignedTo[i] = otherArmy.assignedTo[i];
if (this.priorityTarget === "" && otherArmy.priorityTarget !== "")
this.priorityTarget = otherArmy.priorityTarget;
this.priorityTargets = this.priorityTargets.concat(otherArmy.priorityTargets);
this.watchLevel += otherArmy.watchLevel;
// I'm not using addEnemy because it'd do needless checks and recalculations
for (var i in otherArmy.entities)
{
var ent = gameState.getEntityById(otherArmy.entities[i]);
this.entities.push(otherArmy.entities[i]);
this.evaluateStrength(ent);
ent.setMetadata(PlayerID, "DefManagerArmy", this.ID);
}
this.recalculatePosition(gameState, true);
// TODO: reassign those ?
for (var i in otherArmy.ownEntities)
{
var ent = gameState.getEntityById(otherArmy.ownEntities[i]);
this.ownEntities.push(otherArmy.ownEntities[i]);
this.evaluateStrength(ent, true);
ent.setMetadata(PlayerID, "DefManagerArmy", this.ID);
}
return true;
}
// TODO: this should return cleverer results ("needs anti-elephant"…)
m.Army.prototype.needsDefenders = function (gameState, events)
{
// some preliminary checks because we don't update for tech
if (this.totalStrength < 0 || this.ownTotalStrenght < 0)
this.recalculateStrength(gameState);
if (this.totalStrength * this.defenceRatio < this.ownTotalStrenght)
return false;
return this.totalStrength * this.defenceRatio - this.ownTotalStrenght;
}
m.Army.prototype.getState = function (gameState)
{
if (this.entities.length === 0)
return 0;
if (this.state === 2)
return 2;
if (this.watchLevel > 0)
return 1;
return 0;
}
// check if we should remain at state 2 or drift away
m.Army.prototype.checkDangerosity = function (gameState)
{
this.territoryMap = Map.createTerritoryMap(gameState);
// right now we'll check if our position is "enemy" or not.
if (this.territoryMap.getOwner(this.position) !== PlayerID)
this.state = 1;
else if (this.state === 1)
this.state = 2;
}
// TODO: when there is a technology update, we should probably recompute the strengths, or weird stuffs might happen.
m.Army.prototype.checkEvents = function (gameState, events)
{
var destroyEvents = events["Destroy"];
var convEvents = events["OwnershipChanged"];
for (var i in destroyEvents)
{
var msg = destroyEvents[i];
if (msg.entityObj && msg.entityObj.owner() === this.owner)
this.removeEnemy(gameState, msg.entity, msg.entityObj);
else if (msg.entityObj)
this.removeDefender(gameState, msg.entity, msg.entityObj);
}
for (var i in convEvents)
{
var msg = convEvents[i];
if (msg.from === this.owner)
{
// we have converted an enemy, let's assign it as a defender
if (this.removeEnemy(gameState, msg.entity) && msg.to === PlayerID)
this.addDefender(gameState, msg.entity);
} else if (msg.from === PlayerID)
this.removeDefender(gameState, msg.entity); // TODO: add allies
}
}
m.Army.prototype.update = function (gameState)
{
var breakaways = [];
// TODO: assign unassigned defenders, cleanup of a few things.
// perhaps occasional strength recomputation
if (gameState.getTimeElapsed() - this.positionLastUpdate > 5000)
{
this.recalculatePosition(gameState);
this.positionLastUpdate = gameState.getTimeElapsed();
// Check for breakaways.
for (var i in this.entities)
{
var id = this.entities[i];
var ent = gameState.getEntityById(id);
if (API3.SquareVectorDistance(ent.position(), this.position) > this.breakawaySize)
{
breakaways.push(id);
this.removeEnemy(gameState, id);
}
}
for (var i in this.ownEntities)
{
var ent = gameState.getEntityById(this.ownEntities[i]);
var eee = gameState.getEntityById(this.assignedTo[this.ownEntities[i]]);
if (ent.isIdle())
ent.attack(this.assignedTo[this.ownEntities[i]]);
}
}
this.checkDangerosity(gameState);
var normalWatch = this.totalStrength * this.watchTSMultiplicator;
if (this.state === 2)
this.watchLevel = normalWatch;
else if (this.watchLevel > normalWatch)
this.watchLevel = normalWatch;
else
this.watchLevel -= this.watchDecrement;
// TODO: deal with watchLevel?
return breakaways;
}
m.Army.prototype.debug = function (gameState)
{
m.debug ("Army " + this.ID)
m.debug ("state " + this.state);
m.debug ("WatchLevel " + this.watchLevel);
m.debug ("Entities " + this.entities.length);
m.debug ("Strength " + this.totalStrength);
for (id in this.assignedAgainst)
m.debug ("Assigned " + uneval(this.assignedAgainst[id]) + " against " + id);
//for each (ent in this.entities)
// debug (gameState.getEntityById(ent)._templateName + ", ID " + ent);
//debug ("Defenders " + this.ownEntities.length);
//for each (ent in this.ownEntities)
//{
// if (gameState.getEntityById(ent) !== undefined)
// debug (gameState.getEntityById(ent)._templateName + ", ID " + ent);
// else
// debug("ent " + ent);
//}
//debug ("Strength " + this.ownTotalStrenght);
m.debug ("");
}
return m;
}(AEGIS);

File diff suppressed because it is too large Load Diff

View File

@ -15,8 +15,8 @@ var AEGIS = function(m)
*/
m.HQ = function(Config) {
this.Config = Config;
this.targetNumBuilders = this.Config.Economy.targetNumBuilders; // number of workers we want building stuff
this.dockStartTime = this.Config.Economy.dockStartTime * 1000;
@ -173,66 +173,63 @@ m.HQ.prototype.init = function(gameState, events, queues){
if (PlayerID != i && gameState.isPlayerEnemy(i)) {
this.enemyWatchers[i] = new m.enemyWatcher(gameState, i);
this.ennWatcherIndex.push(i);
this.defenceManager.enemyArmy[i] = [];
}
};
m.HQ.prototype.checkEvents = function (gameState, events, queues) {
for (var i in events)
// TODO: probably check stuffs like a base destruction.
var CreateEvents = events["Create"];
var ConstructionEvents = events["ConstructionFinished"];
for (i in CreateEvents)
{
if (events[i].type == "Destroy")
var evt = CreateEvents[i];
// Let's check if we have a building set to create a new base.
if (evt && evt.entity)
{
// TODO: probably check stuffs like a base destruction.
} else if (events[i].type == "Create")
{
var evt = events[i];
// Let's check if we have a building set to create a new base.
if (evt.msg && evt.msg.entity)
var ent = gameState.getEntityById(evt.entity);
if (ent === undefined)
continue; // happens when this message is right before a "Destroy" one for the same entity.
if (ent.isOwn(PlayerID) && ent.getMetadata(PlayerID, "base") === -1)
{
var ent = gameState.getEntityById(evt.msg.entity);
// Okay so let's try to create a new base around this.
var bID = m.playerGlobals[PlayerID].uniqueIDBases;
this.baseManagers[bID] = new m.BaseManager(this.Config);
this.baseManagers[bID].init(gameState, events, true);
this.baseManagers[bID].setAnchor(ent);
this.baseManagers[bID].initTerritory(this, gameState);
if (ent === undefined)
continue; // happens when this message is right before a "Destroy" one for the same entity.
if (ent.isOwn(PlayerID) && ent.getMetadata(PlayerID, "base") === -1)
{
// Okay so let's try to create a new base around this.
var bID = m.playerGlobals[PlayerID].uniqueIDBases;
this.baseManagers[bID] = new m.BaseManager(this.Config);
this.baseManagers[bID].init(gameState, events, true);
this.baseManagers[bID].setAnchor(ent);
this.baseManagers[bID].initTerritory(this, gameState);
// Let's get a few units out there to build this.
// TODO: select the best base, or use multiple bases.
var builders = this.bulkPickWorkers(gameState, bID, 10);
builders.forEach(function (worker) {
worker.setMetadata(PlayerID, "base", bID);
worker.setMetadata(PlayerID, "subrole", "builder");
worker.setMetadata(PlayerID, "target-foundation", ent.id());
});
}
// Let's get a few units out there to build this.
// TODO: select the best base, or use multiple bases.
var builders = this.bulkPickWorkers(gameState, bID, 10);
builders.forEach(function (worker) {
worker.setMetadata(PlayerID, "base", bID);
worker.setMetadata(PlayerID, "subrole", "builder");
worker.setMetadata(PlayerID, "target-foundation", ent.id());
});
}
} else if (events[i].type == "ConstructionFinished")
}
}
for (i in ConstructionEvents)
{
var evt = ConstructionEvents[i];
// Let's check if we have a building set to create a new base.
// TODO: move to the base manager.
if (evt.newentity)
{
var evt = events[i];
// Let's check if we have a building set to create a new base.
// TODO: move to the base manager.
if (evt.msg && evt.msg.newentity)
var ent = gameState.getEntityById(evt.newentity);
if (ent === undefined)
continue; // happens when this message is right before a "Destroy" one for the same entity.
if (ent.isOwn(PlayerID) && ent.getMetadata(PlayerID, "baseAnchor") == true)
{
var ent = gameState.getEntityById(evt.msg.newentity);
if (ent === undefined)
continue; // happens when this message is right before a "Destroy" one for the same entity.
if (ent.isOwn(PlayerID) && ent.getMetadata(PlayerID, "baseAnchor") == true)
var base = ent.getMetadata(PlayerID, "base");
if (this.baseManagers[base].constructing)
{
var base = ent.getMetadata(PlayerID, "base");
if (this.baseManagers[base].constructing)
{
this.baseManagers[base].constructing = false;
this.baseManagers[base].initGatheringFunctions(this, gameState);
}
this.baseManagers[base].constructing = false;
this.baseManagers[base].initGatheringFunctions(this, gameState);
}
}
}
@ -582,11 +579,13 @@ m.HQ.prototype.findBestEcoCCLocation = function(gameState, resource){
entPos = [entPos[0]/4.0,entPos[1]/4.0];
var dist = API3.SquareVectorDistance(entPos, pos);
if (dist < 3500 || dist > 7900)
friendlyTiles.map[j] /= 2.0;
if (dist < 2120)
{
canBuild = false;
continue;
} else if (dist < 8000 || this.waterMap)
} else if (dist < 9200 || this.waterMap)
canBuild2 = true;
}
// checking for bases.
@ -625,12 +624,13 @@ m.HQ.prototype.findBestEcoCCLocation = function(gameState, resource){
continue;
}
dpPos = [dpPos[0]/4.0,dpPos[1]/4.0];
if (API3.SquareVectorDistance(dpPos, pos) < 100)
var dist = API3.SquareVectorDistance(dpPos, pos);
if (dist < 600)
{
friendlyTiles.map[j] = 0;
continue;
} else if (API3.SquareVectorDistance(dpPos, pos) < 400)
friendlyTiles.map[j] /= 2;
} else if (dist < 1500)
friendlyTiles.map[j] /= 2.0;
}
friendlyTiles.map[j] *= 1.5;
@ -1070,6 +1070,8 @@ m.HQ.prototype.update = function(gameState, queues, events) {
this.buildMoreHouses(gameState,queues);
this.GetCurrentGatherRates(gameState);
if (gameState.getTimeElapsed() > this.techStartTime && gameState.currentPhase() > 2)
this.tryResearchTechs(gameState,queues);

View File

@ -112,29 +112,25 @@ m.NavalManager.prototype.canReach = function (gameState, regionA, regionB) {
m.NavalManager.prototype.checkEvents = function (gameState, queues, events) {
for (var i in events)
var evts = events["ConstructionFinished"];
// TODO: probably check stuffs like a base destruction.
for (i in evts)
{
if (events[i].type == "Destroy")
var evt = evts[i];
if (evt && evt.newentity)
{
// TODO: probably check stuffs like a base destruction.
} else if (events[i].type == "ConstructionFinished")
{
var evt = events[i];
if (evt.msg && evt.msg.newentity)
var entity = gameState.getEntityById(evt.newentity);
if (entity && entity.hasClass("Dock") && entity.isOwn(PlayerID))
{
var entity = gameState.getEntityById(evt.msg.newentity);
if (entity && entity.hasClass("Dock") && entity.isOwn(PlayerID))
{
// okay we have a dock whose construction is finished.
// let's assign it to us.
var pos = entity.position();
var li = gameState.ai.accessibility.getAccessValue(pos);
var ni = entity.getMetadata(PlayerID, "sea");
if (this.landZoneDocked[li].indexOf(ni) === -1)
this.landZoneDocked[li].push(ni);
if (this.accessibleSeas.indexOf(ni) === -1)
this.accessibleSeas.push(ni);
}
// okay we have a dock whose construction is finished.
// let's assign it to us.
var pos = entity.position();
var li = gameState.ai.accessibility.getAccessValue(pos);
var ni = entity.getMetadata(PlayerID, "sea");
if (this.landZoneDocked[li].indexOf(ni) === -1)
this.landZoneDocked[li].push(ni);
if (this.accessibleSeas.indexOf(ni) === -1)
this.accessibleSeas.push(ni);
}
}
}

View File

@ -147,9 +147,7 @@ m.QueueManager.prototype.wantedGatherRates = function(gameState) {
// estimate time based on priority + cost + nb
// TODO: work on this.
for (var type in qCosts)
{
qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name]));
}
qTime += 30000;
} else {
// TODO: work on this.
@ -528,9 +526,7 @@ m.QueueManager.prototype.addQueue = function(queueName, priority) {
var self = this;
this.queueArrays = [];
for (var p in this.queues)
{
this.queueArrays.push([p,this.queues[p]]);
}
this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) });
}
}
@ -547,9 +543,7 @@ m.QueueManager.prototype.removeQueue = function(queueName) {
var self = this;
this.queueArrays = [];
for (var p in this.queues)
{
this.queueArrays.push([p,this.queues[p]]);
}
this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) });
}
}
@ -559,9 +553,7 @@ m.QueueManager.prototype.changePriority = function(queueName, newPriority) {
this.priorities[queueName] = newPriority;
this.queueArrays = [];
for (var p in this.queues)
{
this.queueArrays.push([p,this.queues[p]]);
}
this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) });
}

View File

@ -202,7 +202,6 @@ m.Worker.prototype.checkUnsatisfactoryResource = function(gameState) {
};
m.Worker.prototype.startGathering = function(baseManager, gameState) {
var resource = this.ent.getMetadata(PlayerID, "gather-type");
var ent = this.ent;
var self = this;
@ -276,7 +275,7 @@ m.Worker.prototype.startGathering = function(baseManager, gameState) {
}
});
}
if (!nearestResources || nearestResources.length === 0){
if (resource === "food")
if (this.buildAnyField(gameState))
@ -338,9 +337,9 @@ m.Worker.prototype.startGathering = function(baseManager, gameState) {
return;
}
if (supply.isFull() === true
if (supply.isFull(PlayerID) === true
|| (gameState.turnCache["ressGathererNB"] && gameState.turnCache["ressGathererNB"][supply.id()]
&& gameState.turnCache["ressGathererNB"][supply.id()] + supply.resourceSupplyGatherers().length >= supply.maxGatherers))
&& gameState.turnCache["ressGathererNB"][supply.id()] + supply.resourceSupplyGatherers(PlayerID).length >= supply.maxGatherers()))
return;
@ -520,7 +519,7 @@ m.Worker.prototype.startHunting = function(gameState, baseManager){
if (supply.getMetadata(PlayerID, "inaccessible") === true)
return;
if (supply.isFull() === true)
if (supply.isFull(PlayerID) === true)
return;
if (!supply.hasClass("Animal"))

View File

@ -64,8 +64,10 @@ m.BaseAI.prototype.CustomInit = function()
m.BaseAI.prototype.HandleMessage = function(state, playerID, sharedAI)
{
this.events = sharedAI.events;
PlayerID = playerID;
this.events = sharedAI.events;
this.passabilityMap = sharedAI.passabilityMap;
this.territoryMap = sharedAI.territoryMap;
if (this.isDeserialized && this.turn !== 0)
{

View File

@ -340,7 +340,7 @@ m.EntityTemplate = m.Class({
return +this._template.ResourceSupply.MaxGatherers;
return 0;
},
resourceGatherRates: function() {
if (!this._template.ResourceGatherer)
return undefined;
@ -558,17 +558,17 @@ m.Entity = m.Class({
return this._entity.resourceSupplyAmount;
},
resourceSupplyGatherers: function()
resourceSupplyGatherers: function(player)
{
if (this._entity.resourceSupplyGatherers !== undefined)
return this._entity.resourceSupplyGatherers;
return this._entity.resourceSupplyGatherers[player-1];
return [];
},
isFull: function()
isFull: function(player)
{
if (this._entity.resourceSupplyGatherers !== undefined)
return (this.maxGatherers() === this._entity.resourceSupplyGatherers.length);
return (this.maxGatherers() === this._entity.resourceSupplyGatherers[player-1].length);
return undefined;
},
@ -578,6 +578,40 @@ m.Entity = m.Class({
return undefined;
return this._entity.resourceCarrying;
},
currentGatherRate: function() {
// returns the gather rate for the current target if applicable.
if (!this._template.ResourceGatherer)
return undefined;
if (this.unitAIOrderData().length &&
(this.unitAIState().split(".")[1] === "GATHER" || this.unitAIState().split(".")[1] === "RETURNRESOURCE"))
{
var ress = undefined;
// this is an abuse of "_ai" but it works.
if (this.unitAIState().split(".")[1] === "GATHER" && this.unitAIOrderData()[0]["target"] !== undefined)
ress = this._ai._entities[this.unitAIOrderData()[0]["target"]];
else if (this.unitAIOrderData()[1] !== undefined && this.unitAIOrderData()[1]["target"] !== undefined)
ress = this._ai._entities[this.unitAIOrderData()[1]["target"]];
if (ress == undefined)
return undefined;
var type = ress.resourceSupplyType();
var tstring = type.generic + "." + type.specific;
if (type.generic == "treasure")
return 1000;
var speed = GetTechModifiedProperty(this._techModifications, this._template, "ResourceGatherer/BaseSpeed", +this._template.ResourceGatherer.BaseSpeed);
speed *= GetTechModifiedProperty(this._techModifications, this._template, "ResourceGatherer/Rates/"+tstring, +this._template.ResourceGatherer.Rates[tstring]);
if (speed)
return speed;
return 0;
}
return undefined;
},
garrisoned: function() { return new m.EntityCollection(this._ai, this._entity.garrisoned); },
@ -702,6 +736,7 @@ m.Entity = m.Class({
construct: function(template, x, z, angle, metadata) {
// TODO: verify this unit can construct this, just for internal
// sanity-checking and error reporting
Engine.PostCommand(PlayerID,{
"type": "construct",
"entities": [this.id()],

View File

@ -61,32 +61,63 @@ m.Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) {
str = +strength;
break;
}
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
var quant = 0;
switch (type){
case 'linear':
var r = Math.sqrt(r2);
quant = str * (maxDist - r);
break;
case 'quadratic':
quant = str * (maxDist2 - r2);
break;
case 'constant':
quant = str;
break;
// code duplicating for speed
if (type === 'linear' || type === "linear")
{
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
var quant = 0;
var r = Math.sqrt(r2);
quant = str * (maxDist - r);
if (this.map[x + y * this.width] + quant < 0)
this.map[x + y * this.width] = 0;
else if (this.map[x + y * this.width] + quant > this.maxVal)
this.map[x + y * this.width] = this.maxVal; // avoids overflow.
else
this.map[x + y * this.width] += quant;
}
}
}
} else if (type === 'quadratic' || type === "quadratic")
{
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
var quant = str * (maxDist2 - r2);
if (this.map[x + y * this.width] + quant < 0)
this.map[x + y * this.width] = 0;
else if (this.map[x + y * this.width] + quant > this.maxVal)
this.map[x + y * this.width] = this.maxVal; // avoids overflow.
else
this.map[x + y * this.width] += quant;
}
}
}
} else if (type === 'constant' || type === "constant")
{
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
if (this.map[x + y * this.width] + str < 0)
this.map[x + y * this.width] = 0;
else if (this.map[x + y * this.width] + str > this.maxVal)
this.map[x + y * this.width] = this.maxVal; // avoids overflow.
else
this.map[x + y * this.width] += str;
}
if (this.map[x + y * this.width] + quant < 0)
this.map[x + y * this.width] = 0;
else if (this.map[x + y * this.width] + quant > this.maxVal)
this.map[x + y * this.width] = this.maxVal; // avoids overflow.
else
this.map[x + y * this.width] += quant;
}
}
}
@ -115,32 +146,67 @@ m.Map.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type) {
break;
}
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
var quant = 0;
switch (type){
case 'linear':
var r = Math.sqrt(r2);
quant = str * (maxDist - r);
break;
case 'quadratic':
quant = str * (maxDist2 - r2);
break;
case 'constant':
quant = str;
break;
if (type === 'linear' || type === "linear")
{
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
var quant = 0;
var r = Math.sqrt(r2);
quant = str * (maxDist - r);
var machin = this.map[x + y * this.width] * quant;
if (machin < 0)
this.map[x + y * this.width] = 0;
else if (machin > this.maxVal)
this.map[x + y * this.width] = this.maxVal;
else
this.map[x + y * this.width] = machin;
}
}
}
} else if (type === 'quadratic' || type === "quadratic")
{
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
var quant = str * (maxDist2 - r2);
var machin = this.map[x + y * this.width] * quant;
if (machin < 0)
this.map[x + y * this.width] = 0;
else if (machin > this.maxVal)
this.map[x + y * this.width] = this.maxVal;
else
this.map[x + y * this.width] = machin;
}
}
}
} else if (type === 'constant' || type === "constant")
{
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
var machin = this.map[x + y * this.width] * str;
if (machin < 0)
this.map[x + y * this.width] = 0;
else if (machin > this.maxVal)
this.map[x + y * this.width] = this.maxVal;
else
this.map[x + y * this.width] = machin;
}
var machin = this.map[x + y * this.width] * quant;
if (machin < 0)
this.map[x + y * this.width] = 0;
else if (machin > this.maxVal)
this.map[x + y * this.width] = this.maxVal;
else
this.map[x + y * this.width] = machin;
}
}
}

View File

@ -197,100 +197,109 @@ m.SharedScript.prototype.ApplyEntitiesDelta = function(state)
var foundationFinished = {};
for each (var evt in state.events)
// by order of updating:
// we "Destroy" last because we want to be able to switch Metadata first.
var CreateEvents = state.events["Create"];
var DestroyEvents = state.events["Destroy"];
var RenamingEvents = state.events["EntityRenamed"];
var TrainingEvents = state.events["TrainingFinished"];
var ConstructionEvents = state.events["ConstructionFinished"];
var MetadataEvents = state.events["AIMetadata"];
var ownershipChangeEvents = state.events["OwnershipChanged"];
for (var i = 0; i < CreateEvents.length; ++i)
{
if (evt.type == "Create")
{
if (!state.entities[evt.msg.entity])
{
continue; // Sometimes there are things like foundations which get destroyed too fast
}
this._entities[evt.msg.entity] = new m.Entity(this, state.entities[evt.msg.entity]);
this.entities.addEnt(this._entities[evt.msg.entity]);
var evt = CreateEvents[i];
if (!state.entities[evt.entity])
continue; // Sometimes there are things like foundations which get destroyed too fast
// Update all the entity collections since the create operation affects static properties as well as dynamic
for (var entCollection in this._entityCollections)
{
this._entityCollections[entCollection].updateEnt(this._entities[evt.msg.entity]);
}
}
else if (evt.type == "Destroy")
this._entities[evt.entity] = new m.Entity(this, state.entities[evt.entity]);
this.entities.addEnt(this._entities[evt.entity]);
// Update all the entity collections since the create operation affects static properties as well as dynamic
for (var entCollection in this._entityCollections)
this._entityCollections[entCollection].updateEnt(this._entities[evt.entity]);
}
for (var i in RenamingEvents)
{
var evt = RenamingEvents[i];
// Switch the metadata
for (var p in this._players)
{
// A small warning: javascript "delete" does not actually delete, it only removes the reference in this object.
// the "deleted" object remains in memory, and any older reference to it will still reference it as if it were not "deleted".
// Worse, they might prevent it from being garbage collected, thus making it stay alive and consuming ram needlessly.
// So take care, and if you encounter a weird bug with deletion not appearing to work correctly, this is probably why.
if (!this._entities[evt.msg.entity])
continue;
if (foundationFinished[evt.msg.entity])
evt.msg["SuccessfulFoundation"] = true;
// The entity was destroyed but its data may still be useful, so
// remember the entity and this AI's metadata concerning it
evt.msg.metadata = {};
evt.msg.entityObj = (evt.msg.entityObj || this._entities[evt.msg.entity]);
for (var i in this._players)
evt.msg.metadata[this._players[i]] = this._entityMetadata[this._players[i]][evt.msg.entity];
for each (var entCol in this._entityCollections)
{
entCol.removeEnt(this._entities[evt.msg.entity]);
}
this.entities.removeEnt(this._entities[evt.msg.entity]);
delete this._entities[evt.msg.entity];
for (var i in this._players)
delete this._entityMetadata[this._players[i]][evt.msg.entity];
this._entityMetadata[this._players[p]][evt.newentity] = this._entityMetadata[this._players[p]][evt.entity];
this._entityMetadata[this._players[p]][evt.entity] = {};
}
else if (evt.type == "EntityRenamed")
}
for (var i in TrainingEvents)
{
var evt = TrainingEvents[i];
// Apply metadata stored in training queues
for each (var ent in evt.entities)
{
// Switch the metadata
for (var i in this._players)
for (var key in evt.metadata)
{
this._entityMetadata[this._players[i]][evt.msg.newentity] = this._entityMetadata[this._players[i]][evt.msg.entity];
this._entityMetadata[this._players[i]][evt.msg.entity] = {};
}
}
else if (evt.type == "TrainingFinished")
{
// Apply metadata stored in training queues
for each (var ent in evt.msg.entities)
{
for (var key in evt.msg.metadata)
{
this.setMetadata(evt.msg.owner, this._entities[ent], key, evt.msg.metadata[key])
}
}
}
else if (evt.type == "ConstructionFinished")
{
// we can rely on this being before the "Destroy" command as this is the order defined by FOundation.js
// we'll move metadata.
if (!this._entities[evt.msg.entity])
continue;
var ent = this._entities[evt.msg.entity];
var newEnt = this._entities[evt.msg.newentity];
if (this._entityMetadata[ent.owner()] && this._entityMetadata[ent.owner()][evt.msg.entity] !== undefined)
for (var key in this._entityMetadata[ent.owner()][evt.msg.entity])
{
this.setMetadata(ent.owner(), newEnt, key, this._entityMetadata[ent.owner()][evt.msg.entity][key])
}
foundationFinished[evt.msg.entity] = true;
}
else if (evt.type == "AIMetadata")
{
if (!this._entities[evt.msg.id])
continue; // might happen in some rare cases of foundations getting destroyed, perhaps.
// Apply metadata (here for buildings for example)
for (var key in evt.msg.metadata)
{
this.setMetadata(evt.msg.owner, this._entities[evt.msg.id], key, evt.msg.metadata[key])
this.setMetadata(evt.owner, this._entities[ent], key, evt.metadata[key])
}
}
}
for (var i in ConstructionEvents)
{
var evt = ConstructionEvents[i];
// we'll move metadata.
if (!this._entities[evt.entity])
continue;
var ent = this._entities[evt.entity];
var newEnt = this._entities[evt.newentity];
if (this._entityMetadata[ent.owner()] && this._entityMetadata[ent.owner()][evt.entity] !== undefined)
for (var key in this._entityMetadata[ent.owner()][evt.entity])
{
this.setMetadata(ent.owner(), newEnt, key, this._entityMetadata[ent.owner()][evt.entity][key])
}
foundationFinished[evt.entity] = true;
}
for (var i in MetadataEvents)
{
var evt = MetadataEvents[i];
if (!this._entities[evt.id])
continue; // might happen in some rare cases of foundations getting destroyed, perhaps.
// Apply metadata (here for buildings for example)
for (var key in evt.metadata)
{
this.setMetadata(evt.owner, this._entities[evt.id], key, evt.metadata[key])
}
}
for (var i = 0; i < DestroyEvents.length; ++i)
{
var evt = DestroyEvents[i];
// A small warning: javascript "delete" does not actually delete, it only removes the reference in this object.
// the "deleted" object remains in memory, and any older reference to it will still reference it as if it were not "deleted".
// Worse, they might prevent it from being garbage collected, thus making it stay alive and consuming ram needlessly.
// So take care, and if you encounter a weird bug with deletion not appearing to work correctly, this is probably why.
if (!this._entities[evt.entity])
continue;
if (foundationFinished[evt.entity])
evt["SuccessfulFoundation"] = true;
// The entity was destroyed but its data may still be useful, so
// remember the entity and this AI's metadata concerning it
evt.metadata = {};
evt.entityObj = this._entities[evt.entity];
for (var j in this._players)
evt.metadata[this._players[j]] = this._entityMetadata[this._players[j]][evt.entity];
for each (var entCol in this._entityCollections)
{
entCol.removeEnt(this._entities[evt.entity]);
}
this.entities.removeEnt(this._entities[evt.entity]);
delete this._entities[evt.entity];
for (var j in this._players)
delete this._entityMetadata[this._players[j]][evt.entity];
}
for (var id in state.entities)
{
var changes = state.entities[id];

View File

@ -13,7 +13,7 @@ var API3 = function(m)
// this should only be called by an AI player after setting gamestate.ai
// The initializer creates an expanded influence map for checking.
// It's not extraordinarily slow, but it might be.
// It's not extraordinarily slow, but it's not exactly fast either.
m.aStarPath = function(gameState, onWater, disregardEntities, targetTerritory) {
var self = this;

View File

@ -187,51 +187,49 @@ m.TerrainAnalysis.prototype.countConnected = function(startIndex, byLand){
m.TerrainAnalysis.prototype.updateMapWithEvents = function(sharedAI) {
var self = this;
var events = sharedAI.events;
var events = sharedAI.events["Destroy"];
var passabilityMap = sharedAI.passabilityMap;
// looking for creation or destruction of entities, and updates the map accordingly.
for (var i in events) {
var e = events[i];
if (e.type === "Destroy") {
if (e.msg.entityObj){
var ent = e.msg.entityObj;
if (ent.hasClass("Geology")) {
var x = self.gamePosToMapPos(ent.position())[0];
var y = self.gamePosToMapPos(ent.position())[1];
// remove it. Don't really care about surrounding and possible overlappings.
var radius = Math.floor(ent.obstructionRadius() / self.cellSize);
for (var xx = -radius; xx <= radius;xx++)
for (var yy = -radius; yy <= radius;yy++)
if (e.entityObj){
var ent = e.entityObj;
if (ent.hasClass("Geology")) {
var x = self.gamePosToMapPos(ent.position())[0];
var y = self.gamePosToMapPos(ent.position())[1];
// remove it. Don't really care about surrounding and possible overlappings.
var radius = Math.floor(ent.obstructionRadius() / self.cellSize);
for (var xx = -radius; xx <= radius;xx++)
for (var yy = -radius; yy <= radius;yy++)
{
if (x+xx >= 0 && x+xx < self.width && y+yy >= 0 && y+yy < self.height && this.map[(x+xx) + (y+yy)*self.width] === 30)
{
if (x+xx >= 0 && x+xx < self.width && y+yy >= 0 && y+yy < self.height && this.map[(x+xx) + (y+yy)*self.width] === 30)
{
this.map[(x+xx) + (y+yy)*self.width] = 255;
}
this.map[(x+xx) + (y+yy)*self.width] = 255;
}
} else if (ent.hasClass("ForestPlant")){
var x = self.gamePosToMapPos(ent.position())[0];
var y = self.gamePosToMapPos(ent.position())[1];
var nbOfNeigh = 0;
for (var xx = -1; xx <= 1;xx++)
for (var yy = -1; yy <= 1;yy++)
}
} else if (ent.hasClass("ForestPlant")){
var x = self.gamePosToMapPos(ent.position())[0];
var y = self.gamePosToMapPos(ent.position())[1];
var nbOfNeigh = 0;
for (var xx = -1; xx <= 1;xx++)
for (var yy = -1; yy <= 1;yy++)
{
if (xx == 0 && yy == 0)
continue;
if (this.map[(x+xx) + (y+yy)*self.width] === 40)
nbOfNeigh++;
else if (this.map[(x+xx) + (y+yy)*self.width] === 41)
{
if (xx == 0 && yy == 0)
continue;
if (this.map[(x+xx) + (y+yy)*self.width] === 40)
nbOfNeigh++;
else if (this.map[(x+xx) + (y+yy)*self.width] === 41)
{
this.map[(x+xx) + (y+yy)*self.width] = 255;
}
else if (this.map[(x+xx) + (y+yy)*self.width] > 41 && this.map[(x+xx) + (y+yy)*self.width] < 50)
this.map[(x+xx) + (y+yy)*self.width] = this.map[(x+xx) + (y+yy)*self.width] - 1;
this.map[(x+xx) + (y+yy)*self.width] = 255;
}
if (nbOfNeigh > 0)
this.map[x + y*self.width] = this.map[x + y*self.width] = 40 + nbOfNeigh;
else
this.map[x + y*self.width] = this.map[x + y*self.width] = 255;
}
else if (this.map[(x+xx) + (y+yy)*self.width] > 41 && this.map[(x+xx) + (y+yy)*self.width] < 50)
this.map[(x+xx) + (y+yy)*self.width] = this.map[(x+xx) + (y+yy)*self.width] - 1;
}
if (nbOfNeigh > 0)
this.map[x + y*self.width] = this.map[x + y*self.width] = 40 + nbOfNeigh;
else
this.map[x + y*self.width] = this.map[x + y*self.width] = 255;
}
}
}
@ -730,54 +728,57 @@ m.SharedScript.prototype.updateResourceMaps = function(sharedScript, events) {
// Look for destroy events and subtract the entities original influence from the resourceMap
// TODO: perhaps do something when dropsites appear/disappear.
for (var key in events) {
var e = events[key];
if (e.type === "Destroy") {
if (e.msg.entityObj){
var ent = e.msg.entityObj;
if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic !== "treasure") {
var resource = ent.resourceSupplyType().generic;
var x = Math.floor(ent.position()[0] / 4);
var z = Math.floor(ent.position()[1] / 4);
var strength = Math.floor(ent.resourceSupplyMax()/this.decreaseFactor[resource]);
if (resource === "wood" || resource === "food")
{
this.resourceMaps[resource].addInfluence(x, z, 2, 5,'constant');
this.resourceMaps[resource].addInfluence(x, z, 9.0, -strength,'constant');
this.CCResourceMaps[resource].addInfluence(x, z, 15, -strength/2.0,'constant');
}
else if (resource === "stone" || resource === "metal")
{
this.resourceMaps[resource].addInfluence(x, z, 8, 50);
this.resourceMaps[resource].addInfluence(x, z, 12.0, -strength/1.5);
this.resourceMaps[resource].addInfluence(x, z, 12.0, -strength/2.0,'constant');
this.CCResourceMaps[resource].addInfluence(x, z, 30, -strength,'constant');
}
var destEvents = events["Destroy"];
var createEvents = events["Create"];
for (var key in destEvents) {
var e = destEvents[key];
if (e.entityObj){
var ent = e.entityObj;
if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic !== "treasure") {
var resource = ent.resourceSupplyType().generic;
var x = Math.floor(ent.position()[0] / 4);
var z = Math.floor(ent.position()[1] / 4);
var strength = Math.floor(ent.resourceSupplyMax()/this.decreaseFactor[resource]);
if (resource === "wood" || resource === "food")
{
this.resourceMaps[resource].addInfluence(x, z, 2, 5,'constant');
this.resourceMaps[resource].addInfluence(x, z, 9.0, -strength,'constant');
this.CCResourceMaps[resource].addInfluence(x, z, 15, -strength/2.0,'constant');
}
else if (resource === "stone" || resource === "metal")
{
this.resourceMaps[resource].addInfluence(x, z, 8, 50);
this.resourceMaps[resource].addInfluence(x, z, 12.0, -strength/1.5);
this.resourceMaps[resource].addInfluence(x, z, 12.0, -strength/2.0,'constant');
this.CCResourceMaps[resource].addInfluence(x, z, 30, -strength,'constant');
}
}
} else if (e.type === "Create") {
if (e.msg.entity){
var ent = sharedScript._entities[e.msg.entity];
if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic !== "treasure"){
var resource = ent.resourceSupplyType().generic;
var x = Math.floor(ent.position()[0] / 4);
var z = Math.floor(ent.position()[1] / 4);
var strength = Math.floor(ent.resourceSupplyMax()/this.decreaseFactor[resource]);
if (resource === "wood" || resource === "food")
{
this.CCResourceMaps[resource].addInfluence(x, z, 15, strength/2.0,'constant');
this.resourceMaps[resource].addInfluence(x, z, 9.0, strength,'constant');
this.resourceMaps[resource].addInfluence(x, z, 2, -5,'constant');
}
else if (resource === "stone" || resource === "metal")
{
this.CCResourceMaps[resource].addInfluence(x, z, 30, strength,'constant');
this.resourceMaps[resource].addInfluence(x, z, 12.0, strength/1.5);
this.resourceMaps[resource].addInfluence(x, z, 12.0, strength/2.0,'constant');
this.resourceMaps[resource].addInfluence(x, z, 8, -50);
}
}
}
for (var key in createEvents) {
var e = createEvents[key];
if (e.entity){
var ent = sharedScript._entities[e.entity];
if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic !== "treasure"){
var resource = ent.resourceSupplyType().generic;
var x = Math.floor(ent.position()[0] / 4);
var z = Math.floor(ent.position()[1] / 4);
var strength = Math.floor(ent.resourceSupplyMax()/this.decreaseFactor[resource]);
if (resource === "wood" || resource === "food")
{
this.CCResourceMaps[resource].addInfluence(x, z, 15, strength/2.0,'constant');
this.resourceMaps[resource].addInfluence(x, z, 9.0, strength,'constant');
this.resourceMaps[resource].addInfluence(x, z, 2, -5,'constant');
}
else if (resource === "stone" || resource === "metal")
{
this.CCResourceMaps[resource].addInfluence(x, z, 30, strength,'constant');
this.resourceMaps[resource].addInfluence(x, z, 12.0, strength/1.5);
this.resourceMaps[resource].addInfluence(x, z, 12.0, strength/2.0,'constant');
this.resourceMaps[resource].addInfluence(x, z, 8, -50);
}
}
}

View File

@ -80,13 +80,9 @@ m.ShallowClone = function(obj)
// Picks a random element from an array
m.PickRandom = function(list){
if (list.length === 0)
{
return undefined;
}
else
{
return list[Math.floor(Math.random()*list.length)];
}
}
return m;

View File

@ -5,7 +5,18 @@ AIInterface.prototype.Schema =
AIInterface.prototype.Init = function()
{
this.events = [];
this.events = {};
this.events["Create"] = [];
this.events["Destroy"] = [];
this.events["Attacked"] = [];
this.events["RangeUpdate"] = [];
this.events["ConstructionFinished"] = [];
this.events["TrainingFinished"] = [];
this.events["AIMetadata"] = [];
this.events["PlayerDefeated"] = [];
this.events["EntityRenamed"] = [];
this.events["OwnershipChanged"] = [];
this.changedEntities = {};
};
@ -17,11 +28,30 @@ AIInterface.prototype.GetRepresentation = function()
var state = cmpGuiInterface.GetExtendedSimulationState(-1);
// Add some extra AI-specific data
state.events = this.events;
state.events = {};
state.events["Create"] = this.events["Create"];
state.events["Destroy"] = this.events["Destroy"];
state.events["Attacked"] = this.events["Attacked"];
state.events["RangeUpdate"] = this.events["RangeUpdate"];
state.events["ConstructionFinished"] = this.events["ConstructionFinished"];
state.events["TrainingFinished"] = this.events["TrainingFinished"];
state.events["AIMetadata"] = this.events["AIMetadata"];
state.events["PlayerDefeated"] = this.events["PlayerDefeated"];
state.events["EntityRenamed"] = this.events["EntityRenamed"];
state.events["OwnershipChanged"] = this.events["OwnershipChanged"];
// Reset the event list for the next turn
this.events = [];
this.events["Create"] = [];
this.events["Destroy"] = [];
this.events["Attacked"] = [];
this.events["RangeUpdate"] = [];
this.events["ConstructionFinished"] = [];
this.events["TrainingFinished"] = [];
this.events["AIMetadata"] = [];
this.events["PlayerDefeated"] = [];
this.events["EntityRenamed"] = [];
this.events["OwnershipChanged"] = [];
// Add entity representations
Engine.ProfileStart("proxy representations");
state.entities = {};
@ -45,13 +75,46 @@ AIInterface.prototype.GetFullRepresentation = function(flushEvents)
var state = cmpGuiInterface.GetExtendedSimulationState(-1);
// Add some extra AI-specific data
state.events = this.events;
state.events = {};
state.events["Create"] = this.events["Create"];
state.events["Destroy"] = this.events["Destroy"];
state.events["Attacked"] = this.events["Attacked"];
state.events["RangeUpdate"] = this.events["RangeUpdate"];
state.events["ConstructionFinished"] = this.events["ConstructionFinished"];
state.events["TrainingFinished"] = this.events["TrainingFinished"];
state.events["AIMetadata"] = this.events["AIMetadata"];
state.events["PlayerDefeated"] = this.events["PlayerDefeated"];
state.events["EntityRenamed"] = this.events["EntityRenamed"];
state.events["OwnershipChanged"] = this.events["OwnershipChanged"];
if (flushEvents)
{
state.events = [];
this.events = [];
state.events["Create"] = [];
state.events["Destroy"] = [];
state.events["Attacked"] = [];
state.events["RangeUpdate"] = [];
state.events["ConstructionFinished"] = [];
state.events["TrainingFinished"] = [];
state.events["AIMetadata"] = [];
state.events["PlayerDefeated"] = [];
state.events["EntityRenamed"] = [];
state.events["OwnershipChanged"] = [];
}
// Reset the event list for the next turn
this.events["Create"] = [];
this.events["Destroy"] = [];
this.events["Attacked"] = [];
this.events["RangeUpdate"] = [];
this.events["ConstructionFinished"] = [];
this.events["TrainingFinished"] = [];
this.events["AIMetadata"] = [];
this.events["PlayerDefeated"] = [];
this.events["EntityRenamed"] = [];
this.events["OwnershipChanged"] = [];
// Add entity representations
Engine.ProfileStart("proxy representations");
state.entities = {};
@ -78,17 +141,19 @@ AIInterface.prototype.ChangedEntity = function(ent)
AIInterface.prototype.PushEvent = function(type, msg)
{
this.events.push({"type": type, "msg": msg});
if (this.events[type] === undefined)
warn("Tried to push unknown event type " + type +", please add it to AIInterface.js");
this.events[type].push(msg);
};
AIInterface.prototype.OnGlobalPlayerDefeated = function(msg)
{
this.events.push({"type": "PlayerDefeated", "msg": msg});
this.events["PlayerDefeated"].push(msg);
};
AIInterface.prototype.OnGlobalEntityRenamed = function(msg)
{
this.events.push({"type": "EntityRenamed", "msg": msg});
this.events["EntityRenamed"].push(msg);
};
Engine.RegisterComponentType(IID_AIInterface, "AIInterface", AIInterface);

View File

@ -34,7 +34,8 @@ AIProxy.prototype.Init = function()
{
this.changes = null;
this.needsFullGet = true;
this.owner = -1; // for convenience now and then.
// Let the AIInterface know that we exist and that it should query us
this.NotifyChange();
};
@ -91,13 +92,6 @@ AIProxy.prototype.OnHealthChanged = function(msg)
this.changes.hitpoints = msg.to;
};
AIProxy.prototype.OnOwnershipChanged = function(msg)
{
this.NotifyChange();
this.changes.owner = msg.to;
};
AIProxy.prototype.OnUnitIdleChanged = function(msg)
{
this.NotifyChange();
@ -199,6 +193,8 @@ AIProxy.prototype.GetFullRepresentation = function()
{
// Updated by OnOwnershipChanged
ret.owner = cmpOwnership.GetOwner();
if (!this.owner)
this.owner = ret.owner;
}
var cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
@ -258,16 +254,28 @@ AIProxy.prototype.GetFullRepresentation = function()
// because that would be very expensive and AI will rarely care about all those
// events.)
AIProxy.prototype.OnCreate = function(msg)
// special case: this changes the state and sends an event.
AIProxy.prototype.OnOwnershipChanged = function(msg)
{
this.NotifyChange();
if (msg.from === -1)
{
var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface);
cmpAIInterface.PushEvent("Create", {"entity" : msg.entity});
return;
} else if (msg.to === -1)
{
var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface);
cmpAIInterface.PushEvent("Destroy", {"entity" : msg.entity});
return;
}
this.owner = msg.to;
this.changes.owner = msg.to;
var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface);
cmpAIInterface.PushEvent("Create", msg);
};
AIProxy.prototype.OnDestroy = function(msg)
{
var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface);
cmpAIInterface.PushEvent("Destroy", msg);
cmpAIInterface.PushEvent("OwnershipChanged", msg);
};
AIProxy.prototype.OnAttacked = function(msg)
@ -276,6 +284,16 @@ AIProxy.prototype.OnAttacked = function(msg)
cmpAIInterface.PushEvent("Attacked", msg);
};
/*
Deactivated for actually not really being practical for most uses.
AIProxy.prototype.OnRangeUpdate = function(msg)
{
var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface);
msg.owner = this.owner;
cmpAIInterface.PushEvent("RangeUpdate", msg);
warn(uneval(msg));
};*/
AIProxy.prototype.OnConstructionFinished = function(msg)
{
var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface);

View File

@ -255,9 +255,9 @@ ResourceGatherer.prototype.GetTargetGatherRate = function(target)
else if (type.generic && rates[type.generic])
{
rate = rates[type.generic] / cmpPlayer.GetCheatTimeMultiplier();
}
// Apply diminishing returns with more gatherers, for e.g. infinite farms. For most resources this has no effect. (GetDiminishingReturns will return null.)
}
// Apply diminishing returns with more gatherers, for e.g. infinite farms. For most resources this has no effect. (GetDiminishingReturns will return null.)
// Note to people looking to change <DiminishingReturns> in a template: This is a bit complicated. Basically, the lower that number is
// the steeper diminishing returns will be. I suggest playing around with Wolfram Alpha or a graphing calculator a bit.
// In each of the following links, replace 0.65 with the gather rate of your worker for the resource with diminishing returns and
@ -274,11 +274,12 @@ ResourceGatherer.prototype.GetTargetGatherRate = function(target)
// between -0.5 and 0.5. Adding 0.5 to that changes the range to 0 to 1. The diminishingReturns constant
// adjusts the period of the curve.
// Alternatively, just find scythetwirler (who came up with the math here) or alpha123 (who wrote the code) on IRC.
var diminishingReturns = cmpResourceSupply.GetDiminishingReturns();
if (diminishingReturns)
rate = (0.5 * Math.cos((cmpResourceSupply.GetGatherers().length - 1) * Math.PI / diminishingReturns) + 0.5) * rate;
return rate || 0;
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var diminishingReturns = cmpResourceSupply.GetDiminishingReturns();
if (diminishingReturns)
rate = (0.5 * Math.cos((cmpResourceSupply.GetGatherers(cmpOwnership.GetOwner()).length - 1) * Math.PI / diminishingReturns) + 0.5) * rate;
return rate || 0;
};
/**

View File

@ -28,10 +28,10 @@ ResourceSupply.prototype.Schema =
"<value>treasure.stone</value>" +
"<value>treasure.metal</value>" +
"<value>treasure.food</value>" +
"</choice>" +
"</element>" +
"<element name='MaxGatherers' a:help='Amount of gatherers who can gather resources from this entity at the same time'>" +
"<data type='nonNegativeInteger'/>" +
"</choice>" +
"</element>" +
"<element name='MaxGatherers' a:help='Amount of gatherers who can gather resources from this entity at the same time'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"<optional>" +
"<element name='DiminishingReturns' a:help='The rate at which adding more gatherers decreases overall efficiency. Lower numbers = faster dropoff. Leave the element out for no diminishing returns.'>" +
@ -43,10 +43,10 @@ ResourceSupply.prototype.Init = function()
{
// Current resource amount (non-negative)
this.amount = this.GetMaxAmount();
this.gatherers = []; // list of IDs
this.infinite = !isFinite(+this.template.Amount);
};
this.gatherers = [[], [], [], [], [], [], [], []]; // list of IDs for each players
this.infinite = !isFinite(+this.template.Amount);
};
ResourceSupply.prototype.IsInfinite = function()
{
return this.infinite;
@ -72,9 +72,11 @@ ResourceSupply.prototype.GetMaxGatherers = function()
return +this.template.MaxGatherers;
};
ResourceSupply.prototype.GetGatherers = function()
ResourceSupply.prototype.GetGatherers = function(player)
{
return this.gatherers;
if (player === undefined)
return this.gatherers;
return this.gatherers[player-1];
};
ResourceSupply.prototype.GetDiminishingReturns = function()
@ -94,16 +96,16 @@ ResourceSupply.prototype.TakeResources = function(rate)
var old = this.amount;
this.amount = Math.max(0, old - rate);
var change = old - this.amount;
// Remove entities that have been exhausted
if (this.amount == 0)
Engine.DestroyEntity(this.entity);
Engine.PostMessage(this.entity, MT_ResourceSupplyChanged, { "from": old, "to": this.amount });
return { "amount": change, "exhausted": (this.amount == 0) };
};
// Remove entities that have been exhausted
if (this.amount == 0)
Engine.DestroyEntity(this.entity);
Engine.PostMessage(this.entity, MT_ResourceSupplyChanged, { "from": old, "to": this.amount });
return { "amount": change, "exhausted": (this.amount == 0) };
};
ResourceSupply.prototype.GetType = function()
{
// All resources must have both type and subtype
@ -112,37 +114,55 @@ ResourceSupply.prototype.GetType = function()
return { "generic": type, "specific": subtype };
};
ResourceSupply.prototype.IsAvailable = function(gathererID)
ResourceSupply.prototype.IsAvailable = function(player, gathererID)
{
if (this.gatherers.length < this.GetMaxGatherers() || this.gatherers.indexOf(gathererID) !== -1)
if (this.gatherers[player-1].length < this.GetMaxGatherers() || this.gatherers[player-1].indexOf(gathererID) !== -1)
return true;
return false;
};
ResourceSupply.prototype.AddGatherer = function(gathererID)
{
if (!this.IsAvailable(gathererID))
return false;
if (this.gatherers.indexOf(gathererID) === -1)
{
this.gatherers.push(gathererID);
// broadcast message, mainly useful for the AIs.
Engine.PostMessage(this.entity, MT_ResourceSupplyGatherersChanged, { "to": this.gatherers });
}
return true;
};
};
ResourceSupply.prototype.AddGatherer = function(player, gathererID)
{
if (!this.IsAvailable(player, gathererID))
return false;
if (this.gatherers[player-1].indexOf(gathererID) === -1)
{
this.gatherers[player-1].push(gathererID);
// broadcast message, mainly useful for the AIs.
Engine.PostMessage(this.entity, MT_ResourceSupplyGatherersChanged, { "to": this.gatherers });
}
return true;
};
// should this return false if the gatherer didn't gather from said resource?
ResourceSupply.prototype.RemoveGatherer = function(gathererID)
ResourceSupply.prototype.RemoveGatherer = function(gathererID, player)
{
if (this.gatherers.indexOf(gathererID) !== -1)
// this can happen if the unit is dead
if (player === undefined || player === -1)
{
this.gatherers.splice(this.gatherers.indexOf(gathererID),1);
// broadcast message, mainly useful for the AIs.
Engine.PostMessage(this.entity, MT_ResourceSupplyGatherersChanged, { "to": this.gatherers });
}
};
for (var i = 1; i <= 8; ++i)
{
var index = this.gatherers[i].indexOf(gathererID);
if (index !== -1)
{
this.gatherers[i].splice(index,1);
// broadcast message, mainly useful for the AIs.
Engine.PostMessage(this.entity, MT_ResourceSupplyGatherersChanged, { "to": this.gatherers });
return;
}
}
} else {
var index = this.gatherers[player-1].indexOf(gathererID);
if (index !== -1)
{
this.gatherers[player-1].splice(index,1);
// broadcast message, mainly useful for the AIs.
Engine.PostMessage(this.entity, MT_ResourceSupplyGatherersChanged, { "to": this.gatherers });
return;
}
}
};
Engine.RegisterComponentType(IID_ResourceSupply, "ResourceSupply", ResourceSupply);

View File

@ -1970,8 +1970,9 @@ var UnitFsmSpec = {
this.gatheringTarget = this.order.data.target; // temporary, deleted in "leave".
// check that we can gather from the resource we're supposed to gather from.
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
if (!cmpSupply || !cmpSupply.AddGatherer(this.entity))
if (!cmpSupply || !cmpSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity))
{
// Save the current order's data in case we need it later
var oldType = this.order.data.type;
@ -2029,8 +2030,11 @@ var UnitFsmSpec = {
// We failed to reach the target
// remove us from the list of entities gathering from Resource.
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
if (cmpSupply)
if (cmpSupply && cmpOwnership)
cmpSupply.RemoveGatherer(this.entity, cmpOwnership.GetOwner());
else if (cmpSupply)
cmpSupply.RemoveGatherer(this.entity);
// Save the current order's data in case we need it later
@ -2069,8 +2073,11 @@ var UnitFsmSpec = {
},
"leave": function() {
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
if (cmpSupply)
if (cmpSupply && cmpOwnership)
cmpSupply.RemoveGatherer(this.entity, cmpOwnership.GetOwner());
else if (cmpSupply)
cmpSupply.RemoveGatherer(this.entity);
delete this.gatheringTarget;
},
@ -2128,8 +2135,9 @@ var UnitFsmSpec = {
{
// Check that we can gather from the resource we're supposed to gather from.
// Will only be added if we're not already in.
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
if (!cmpSupply || !cmpSupply.AddGatherer(this.entity))
if (!cmpSupply || !cmpSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity))
{
this.gatheringTarget = INVALID_ENTITY;
this.StartTimer(0);
@ -2184,8 +2192,11 @@ var UnitFsmSpec = {
"leave": function() {
this.StopTimer();
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
if (cmpSupply)
if (cmpSupply && cmpOwnership)
cmpSupply.RemoveGatherer(this.entity, cmpOwnership.GetOwner());
else if (cmpSupply)
cmpSupply.RemoveGatherer(this.entity);
delete this.gatheringTarget;
@ -2197,9 +2208,12 @@ var UnitFsmSpec = {
var resourceTemplate = this.order.data.template;
var resourceType = this.order.data.type;
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership)
return;
if (cmpSupply && cmpSupply.IsAvailable(this.entity))
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
if (cmpSupply && cmpSupply.IsAvailable(cmpOwnership.GetOwner(), this.entity))
{
// Check we can still reach and gather from the target
if (this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer) && this.CanGather(this.gatheringTarget))
@ -3857,6 +3871,7 @@ UnitAI.prototype.FindNearbyResource = function(filter)
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var nearby = cmpRangeManager.ExecuteQuery(this.entity, 0, range, players, IID_ResourceSupply);
for each (var ent in nearby)
{
@ -3871,7 +3886,7 @@ UnitAI.prototype.FindNearbyResource = function(filter)
if (template.indexOf("resource|") != -1)
template = template.slice(9);
if (amount > 0 && cmpResourceSupply.IsAvailable(this.entity) && filter(ent, type, template))
if (amount > 0 && cmpResourceSupply.IsAvailable(cmpOwnership.GetOwner(), this.entity) && filter(ent, type, template))
return ent;
}