1
0
forked from 0ad/0ad

Fix errors when loading a saved game with Aegis by making sure the AI re-inits properly. Still no real saved game support.

This was SVN commit r14329.
This commit is contained in:
wraitii 2013-12-11 17:10:14 +00:00
parent 1c890d64ee
commit 1efd47c1ad
8 changed files with 64 additions and 105 deletions

View File

@ -227,56 +227,15 @@ AegisBot.prototype.chooseRandomStrategy = function()
}
};
// TODO: Remove override when the whole AI state is serialised
// TODO: this currently is very much equivalent to "rungamestateinit" with a few hacks. Should deserialize/serialize properly someday.
AegisBot.prototype.Deserialize = function(data, sharedScript)
/*AegisBot.prototype.Deserialize = function(data, sharedScript)
{
BaseAI.prototype.Deserialize.call(this, data);
var ents = sharedScript.entities.filter(Filters.byOwner(PlayerID));
var myKeyEntities = ents.filter(function(ent) {
return ent.hasClass("CivCentre");
});
if (myKeyEntities.length == 0){
myKeyEntities = sharedScript.entities.filter(Filters.byOwner(PlayerID));
}
var filter = Filters.byClass("CivCentre");
var enemyKeyEntities = sharedScript.entities.filter(Filters.not(Filters.byOwner(PlayerID))).filter(filter);
if (enemyKeyEntities.length == 0){
enemyKeyEntities = sharedScript.entities.filter(Filters.not(Filters.byOwner(PlayerID)));
}
this.terrainAnalyzer = sharedScript.terrainAnalyzer;
this.passabilityMap = sharedScript.passabilityMap;
var fakeState = { "ai" : this, "sharedScript" : sharedScript };
this.pathFinder = new aStarPath(fakeState, false, true);
this.pathsToMe = [];
this.pathInfo = { "angle" : 0, "needboat" : true, "mkeyPos" : myKeyEntities.toEntityArray()[0].position(), "ekeyPos" : enemyKeyEntities.toEntityArray()[0].position() };
// First path has a sampling of 3, which ensures we'll get at least one path even on Acropolis. The others are 6 so might fail.
var pos = [this.pathInfo.mkeyPos[0] + 150*Math.cos(this.pathInfo.angle),this.pathInfo.mkeyPos[1] + 150*Math.sin(this.pathInfo.angle)];
var path = this.pathFinder.getPath(this.pathInfo.ekeyPos, pos, 2, 2);
if (path !== undefined && path[1] !== undefined && path[1] == false) {
// path is viable and doesn't require boating.
// blackzone the last two waypoints.
this.pathFinder.markImpassableArea(path[0][0][0],path[0][0][1],20);
this.pathsToMe.push(path[0][0][0]);
this.pathInfo.needboat = false;
}
this.pathInfo.angle += Math.PI/3.0;
};
// Override the default serializer
AegisBot.prototype.Serialize = function()
{
//var ret = BaseAI.prototype.Serialize.call(this);
return {};
};
};*/
function debug(output){
if (Config.debug){

View File

@ -361,7 +361,10 @@ Defence.prototype.defendFromEnemies = function(gameState, events, HQ) {
for (var o in this.attackerCache) {
if ((this.attackerCacheLoopIndicator + o) % 2 === 0) {
this.attackerCache[o].forEach(function (ent) {
ent.attack(+o);
var attackPos = gameState.getEntityById(+o).position()
if (attackPos)
ent.attackMove(attackPos[0],attackPos[1]);
ent.setStance("aggressive");
});
}
}
@ -555,7 +558,10 @@ Defence.prototype.defendFromEnemies = function(gameState, events, HQ) {
defender.setMetadata(PlayerID, "formerrole", defender.getMetadata(PlayerID, "role"));
defender.setMetadata(PlayerID, "role","defence");
defender.setMetadata(PlayerID, "subrole","defending");
defender.attack(+enemy.id());
var attackPos = enemy.position();
if (attackPos)
defender.attackMove(attackPos[0],attackPos[1]);
defender.setStance("aggressive");
defender._entity.idle = false; // hack to prevent a bug as informations aren't updated during a turn
nonDefenders.updateEnt(defender);
assigned++;
@ -785,7 +791,11 @@ Defence.prototype.DealWithWantedUnits = function(gameState, events, HQ) {
ent.setMetadata(PlayerID, "formerrole", ent.getMetadata(PlayerID, "role"));
ent.setMetadata(PlayerID, "role","defence");
ent.setMetadata(PlayerID, "subrole", "defending");
ent.attack(+o);
var attackPos = gameState.getEntityById(+o).position();
if (attackPos)
ent.attackMove(attackPos[0],attackPos[1]);
// TODO: should probably be an else here, unless it really can't happen
ent.setStance("aggressive");
if (addedto[o])
addedto[o]++;
else
@ -814,7 +824,10 @@ Defence.prototype.DealWithWantedUnits = function(gameState, events, HQ) {
ent.setMetadata(PlayerID, "formerrole", ent.getMetadata(PlayerID, "role"));
ent.setMetadata(PlayerID, "role","defence");
ent.setMetadata(PlayerID, "subrole", "defending");
ent.attack(+o);
var attackPos = gameState.getEntityById(+o).position();
if (attackPos)
ent.attackMove(attackPos[0],attackPos[1]);
ent.setStance("aggressive");
if (addedto[o])
addedto[o]++;
else

View File

@ -38,6 +38,9 @@ ConstructionPlan.prototype.canStart = function(gameState) {
if (gameState.buildingsBuilt > 0)
return false;
if (!this.isGo(gameState))
return false;
// TODO: verify numeric limits etc
if (this.template.requiredTech() && !gameState.isResearched(this.template.requiredTech()))
{

View File

@ -26,6 +26,7 @@ BaseAI.prototype.Deserialize = function(data, sharedScript)
{
// TODO: ought to get the AI script subclass to deserialize its own state
// TODO: actually this is part of a larger reflection on wether AIs should or not.
this.isDeserialized = true;
};
BaseAI.prototype.Init = function(state, sharedAI)
@ -59,6 +60,13 @@ BaseAI.prototype.HandleMessage = function(state, sharedAI)
{
this.events = sharedAI.events;
if (this.isDeserialized && this.turn !== 0)
{
this.isDeserialized = false;
this.Init(state, sharedAI);
warn("AIs don't work completely with saved games yet. You may run into idle units and unused buildings.");
} else if (this.isDeserialized)
return;
this.OnUpdate(sharedAI);
};

View File

@ -588,6 +588,12 @@ var Entity = Class({
return this;
},
attackMove: function(x, z, queued) {
queued = queued || false;
Engine.PostCommand({"type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "queued": queued });
return this;
},
// violent, aggressive, defensive, passive, standground
setStance: function(stance,queued){
Engine.PostCommand({"type": "stance", "entities": [this.id()], "name" : stance, "queued": queued });

View File

@ -191,6 +191,12 @@ EntityCollection.prototype.move = function(x, z, queued)
Engine.PostCommand({"type": "walk", "entities": this.toIdArray(), "x": x, "z": z, "queued": queued});
return this;
};
EntityCollection.prototype.attackMove = function(x, z, queued)
{
queued = queued || false;
Engine.PostCommand({"type": "attack-walk", "entities": this.toIdArray(), "x": x, "z": z, "queued": queued});
return this;
};
EntityCollection.prototype.moveIndiv = function(x, z, queued)
{
queued = queued || false;

View File

@ -37,72 +37,19 @@ function SharedScript(settings)
//Return a simple object (using no classes etc) that will be serialized
//into saved games
//TODO: that
// note: we'll need to serialize much more than that before this can work.
SharedScript.prototype.Serialize = function()
{
// serializing entities without using the class.
var entities = [];
for (var id in this._entities)
{
var ent = this._entities[id];
entities.push( [ent._template, ent._entity, ent._templateName]);
}
// serialiazing metadata will be done by each AI on a AI basis and they shall update the shared script with that info on deserialization (using DeserializeMetadata() ).
// TODO: this may not be the most clever method.
return { "entities" : entities, "techModifs" : this._techModifications, "passabClasses" : this.passabilityClasses, "passabMap" : this.passabilityMap,
"timeElapsed" : this.timeElapsed, "techTemplates" : this._techTemplates, "players": this._players};
return { "players" : this._players, "templates" : this._templates, "techTp" : this._techTemplates };
};
// Called after the constructor when loading a saved game, with 'data' being
// whatever Serialize() returned
// todo: very not-finished. Mostly hacky, and ugly too.
SharedScript.prototype.Deserialize = function(data)
{
this._entities = {};
this._players = data.players;
this._entityMetadata = {};
for (var i in this._players)
this._entityMetadata[this._players[i]] = {};
this._techModifications = data.techModifs;
this.techModifications = data.techModifs; // needed for entities
this.passabilityClasses = data.passabClasses;
this.passabilityMap = data.passabMap;
var dataArray = [];
for (var i in this.passabilityMap.data)
dataArray.push(this.passabilityMap.data[i]);
this.passabilityMap.data = dataArray;
// TODO: this is needlessly slow (not to mention a hack)
// Should probably call a "init" function rather to avoid this and serialize the terrainanalyzer state.
var fakeState = { "passabilityClasses" : this.passabilityClasses, "passabilityMap":this.passabilityMap };
this.terrainAnalyzer = new TerrainAnalysis (this, fakeState);
this.accessibility = new Accessibility(fakeState, this.terrainAnalyzer);
this._techTemplates = data.techTemplates;
this.timeElapsed = data.timeElapsed;
// deserializing entities;
for (var i in data.entities)
{
var entData = data.entities[i];
entData[1].template = entData[2];
this._entities[entData[1].id] = new Entity(this, entData[1]);
}
// entity collection updated on create/destroy event.
this.entities = new EntityCollection(this, this._entities);
//deserializing game states.
fakeState["timeElapsed"] = this.timeElapsed;
fakeState["players"] = this._players;
this.gameState = {};
for (var i in this._players)
this.gameState[this._players[i]] = new GameState(this, fakeState, this._players[i]);
this._templates = data.templates;
this._techTemplates = data.techTp;
this.isDeserialized = true;
};
// Components that will be disabled in foundation entity templates.
@ -207,6 +154,12 @@ SharedScript.prototype.init = function(state) {
// applies entity deltas, and each gamestate.
SharedScript.prototype.onUpdate = function(state)
{
if (this.isDeserialized && this.turn !== 0)
{
this.isDeserialized = false;
this.init(state);
} else if (this.isDeserialized)
return;
// deals with updating based on create and destroy messages.
this.ApplyEntitiesDelta(state);

View File

@ -892,6 +892,7 @@ public:
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_TerritoriesDirtyID = 0;
m_JustDeserialized = false;
StartLoadEntityTemplates();
}
@ -917,6 +918,8 @@ public:
ForceLoadEntityTemplates();
m_Worker.Deserialize(deserialize.GetStream());
m_JustDeserialized = true;
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
@ -1016,7 +1019,11 @@ public:
ENSURE(cmpAIInterface);
// Get the game state from AIInterface
CScriptVal state = cmpAIInterface->GetRepresentation();
CScriptVal state;
if (m_JustDeserialized)
state = cmpAIInterface->GetFullRepresentation(true);
else
state = cmpAIInterface->GetRepresentation();
// Get the passability data
Grid<u16> dummyGrid;
@ -1040,6 +1047,8 @@ public:
LoadPathfinderClasses(state);
m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state.get()), *passabilityMap, *territoryMap, territoryMapDirty);
m_JustDeserialized = false;
}
virtual void PushCommands()
@ -1069,6 +1078,8 @@ private:
std::vector<std::pair<std::string, const CParamNode*> > m_Templates;
size_t m_TerritoriesDirtyID;
bool m_JustDeserialized;
void StartLoadEntityTemplates()
{
CmpPtr<ICmpTemplateManager> cmpTemplateManager(GetSystemEntity());