From a45a926aeb47484c5c61acdc99fda3850a1ba528 Mon Sep 17 00:00:00 2001 From: wraitii Date: Wed, 6 Mar 2013 11:52:41 +0000 Subject: [PATCH] Fix an issue that caused a crash on serialization. Prepare the AI manager a little more for serialization. Fix bugs with tests. Fix some other issues in the AI (attack plans deal with walls better, choose better paths, target selection is better. Dock building won't be tried hundreds of times if it fails.) Changes the Oasis random map to only put a path in the middle 50% of the time. This was SVN commit r13230. --- .../data/mods/public/maps/random/oasis.js | 2 +- .../simulation/ai/common-api-v3/entity.js | 15 +++ .../simulation/ai/common-api-v3/filters.js | 7 + .../terrain-analysis-pathfinder.js | 2 +- .../ai/common-api-v3/terrain-analysis.js | 8 +- .../simulation/ai/qbot-wc/attack_plan.js | 126 +++++++++++++----- .../public/simulation/ai/qbot-wc/config.js | 12 +- .../public/simulation/ai/qbot-wc/economy.js | 4 +- .../public/simulation/ai/qbot-wc/military.js | 24 +++- .../simulation/ai/qbot-wc/plan-building.js | 2 + .../mods/public/simulation/ai/qbot-wc/qbot.js | 7 +- .../components/tests/test_GuiInterface.js | 64 ++++++++- .../simulation2/components/CCmpAIManager.cpp | 50 ++++--- 13 files changed, 248 insertions(+), 75 deletions(-) diff --git a/binaries/data/mods/public/maps/random/oasis.js b/binaries/data/mods/public/maps/random/oasis.js index f0c2cdde3a..07ffa994ea 100644 --- a/binaries/data/mods/public/maps/random/oasis.js +++ b/binaries/data/mods/public/maps/random/oasis.js @@ -265,7 +265,7 @@ terrainPainter = new LayeredPainter( [pOasisForestLight,tShoreBlend, tWater, tWa var elevationPainter = new SmoothElevationPainter(ELEVATION_SET, -3, 15 ); createArea(placer, [terrainPainter, elevationPainter, paintClass(clWater)], null); RMS.SetProgress(50); -if(mapSize > 150) { +if(mapSize > 150 && randInt(0,1)) { log ("creating path through"); var pAngle = randFloat(0, TWO_PI); var px = round(fx) + round(fractionToTiles(0.13 * cos(pAngle))); diff --git a/binaries/data/mods/public/simulation/ai/common-api-v3/entity.js b/binaries/data/mods/public/simulation/ai/common-api-v3/entity.js index e2384e6373..6e88e87a67 100644 --- a/binaries/data/mods/public/simulation/ai/common-api-v3/entity.js +++ b/binaries/data/mods/public/simulation/ai/common-api-v3/entity.js @@ -266,6 +266,21 @@ var EntityTemplate = Class({ return 1; }, + // returns true if the entity can attack the given class + canAttackClass: function(saidClass) { + if (!this._template.Attack) + return false; + + for (i in this._template.Attack) { + if (!this._template.Attack[i].RestrictedClasses) + continue; + var cannotAttack = this._template.Attack[i].RestrictedClasses._string.split(" "); + if (cannotAttack.indexOf(saidClass) !== -1) + return false; + } + return true; + }, + buildableEntities: function() { if (!this._template.Builder || !this._template.Builder.Entities._string) return undefined; diff --git a/binaries/data/mods/public/simulation/ai/common-api-v3/filters.js b/binaries/data/mods/public/simulation/ai/common-api-v3/filters.js index c1d71e2756..71853e4bd7 100644 --- a/binaries/data/mods/public/simulation/ai/common-api-v3/filters.js +++ b/binaries/data/mods/public/simulation/ai/common-api-v3/filters.js @@ -113,6 +113,13 @@ var Filters = { }, "dynamicProperties": ['unitAIOrderData']}; }, + + byCanAttack: function(saidClass){ + return {"func" : function(ent){ + return ent.canAttackClass(saidClass); + }, + "dynamicProperties": []}; + }, isSoldier: function(){ return {"func" : function(ent){ diff --git a/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis-pathfinder.js b/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis-pathfinder.js index 2bee2028f8..1c6d30aa2f 100644 --- a/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis-pathfinder.js +++ b/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis-pathfinder.js @@ -216,7 +216,7 @@ aStarPath.prototype.continuePath = function(gamestate) this.openList.push(index); } this.isOpened[index] = true; - if (SquareVectorDistance( [this.currentSquare%w, Math.floor(this.currentSquare/w)] , target) <= this.Sampling*this.Sampling) { + if (SquareVectorDistance( [this.currentSquare%w, Math.floor(this.currentSquare/w)] , target) <= this.Sampling*this.Sampling+1) { if (this.e != this.currentSquare) this.parentSquare[this.e] = this.currentSquare; found = true; diff --git a/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis.js b/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis.js index f0badec787..b7de041ee0 100644 --- a/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis.js +++ b/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis.js @@ -236,6 +236,7 @@ TerrainAnalysis.prototype.updateMapWithEvents = function(sharedAI) { * it can also determine if any point is "probably" reachable, assuming the unit can get close enough * for optimizations it's called after the TerrainAnalyser has finished initializing his map * so this can use the land regions already. + */ function Accessibility(rawState, terrainAnalyser){ var self = this; @@ -245,8 +246,11 @@ function Accessibility(rawState, terrainAnalyser){ this.regionSize = []; this.regionSize.push(0); - // initialized to 0, so start to 1 for optimization - this.regionID = 1; + + // initialized to 0, it's more optimized to start at 1 (I'm checking that if it's not 0, then it's already aprt of a region, don't touch); + // However I actually store all unpassable as region 1 (because if I don't, on some maps the toal nb of region is over 256, and it crashes as the mapis 8bit.) + // So start at 2. + this.regionID = 2; for (var i = 0; i < this.passMap.length; ++i) { if (this.passMap[i] === 0 && this.map[i] !== 0) { // any non-painted, non-inacessible area. this.regionSize.push(0); // updated diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/attack_plan.js b/binaries/data/mods/public/simulation/ai/qbot-wc/attack_plan.js index 80094cd72b..9a7a40e178 100755 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/attack_plan.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/attack_plan.js @@ -20,6 +20,8 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta max = enemyCount[i]; } } + if (this.targetPlayer === undefined) + return false; debug ("Target = " +this.targetPlayer); this.targetFinder = targetFinder || this.defaultTargetFinder; this.type = type || "normal"; @@ -184,6 +186,7 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta //debug ("after"); //Engine.DumpHeap(); + return true; }; CityAttack.prototype.getName = function(){ @@ -266,7 +269,7 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve } } // when we have a target, we path to it. - this.path = this.pathFinder.getPath(this.rallyPoint,this.targetPos, 2, 2);//,300000,gameState); + this.path = this.pathFinder.getPath(this.rallyPoint,this.targetPos, 4, 4,300000,gameState); if (this.path === undefined) { delete this.pathFinder; @@ -560,6 +563,9 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ var bool_attacked = false; // raids don't care about attacks much + if (this.unitCollection.length === 0) + return 0; + this.position = this.unitCollection.getCentrePosition(); var IDs = this.unitCollection.toIdArray(); @@ -673,32 +679,60 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ this.position = this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(); + // probably not too good. + if (!this.position) + return undefined; // should spawn an error. + if (SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) { this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).moveIndiv(this.path[0][0][0], this.path[0][0][1]); + // We're stuck, presumably. Check if there are no walls just close to us. If so, we're arrived, and we're gonna tear down some serious stone. + var walls = gameState.getEnemyEntities().filter(Filters.and(Filters.byOwner(this.targetPlayer), Filters.byClass("StoneWall"))); + var nexttoWalls = false; + walls.forEach( function (ent) { + if (!nexttoWalls && SquareVectorDistance(self.position, ent.position()) < 800) + nexttoWalls = true; + }); + // there are walls but we can attack + if (nexttoWalls && this.unitCollection.filter(Filters.byCanAttack("StoneWall")).length !== 0) + { + debug ("Attack Plan " +this.type +" " +this.name +" has met walls and is not happy."); + this.state = "arrived"; + } else if (nexttoWalls) { + // abort plan. + debug ("Attack Plan " +this.type +" " +this.name +" has met walls and gives up."); + return 0; + } } + + // check if our land units are close enough from the next waypoint. if (SquareVectorDistance(this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(), this.path[0][0]) < 600) { - // 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) + if (this.unitCollection.filter(Filters.byClass("Siege")).length !== 0 + && SquareVectorDistance(this.unitCollection.filter(Filters.byClass("Siege")).getCentrePosition(), this.path[0][0]) > 600) { - this.path.shift(); - if (this.path.length > 0){ - this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).moveIndiv(this.path[0][0][0], this.path[0][0][1]); - } else { - debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination."); - // we must assume we've arrived at the end of the trail. - this.state = "arrived"; - } - } else if (this.path[0][1] === true) - { - // okay we must load our units. - // check if we have some kind of ships. - var ships = this.unitCollection.filter(Filters.byClass("Warship")); - if (ships.length === 0) - return 0; // abort + } else { + // 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) + { + this.path.shift(); + if (this.path.length > 0){ + this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).moveIndiv(this.path[0][0][0], this.path[0][0][1]); + } else { + debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination."); + // we must assume we've arrived at the end of the trail. + this.state = "arrived"; + } + } else if (this.path[0][1] === true) + { + // okay we must load our units. + // check if we have some kind of ships. + var ships = this.unitCollection.filter(Filters.byClass("Warship")); + if (ships.length === 0) + return 0; // abort - debug ("switch to boarding"); - this.state = "boarding"; + debug ("switch to boarding"); + this.state = "boarding"; + } } } } else if (this.state === "shipping") { @@ -854,7 +888,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ this.unitCollection.forEach( function (ent) { //}) { if (ent.isIdle()) { var mStruct = enemyStructures.filter(function (enemy) {// }){ - if (!enemy.position()) { + if (!enemy.position() || (enemy.hasClass("StoneWall") && ent.canAttackClass("StoneWall"))) { return false; } if (SquareVectorDistance(enemy.position(),ent.position()) > ent.visionRange()*ent.visionRange() + 300) { @@ -871,32 +905,54 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ } return true; }); + var isGate = false; mUnit = mUnit.toEntityArray(); mStruct = mStruct.toEntityArray(); - mStruct.sort(function (struct) { - if (struct.hasClass("ConquestCritical")) - return 100 + struct.costSum(); - else - return struct.costSum(); - }) + mStruct.sort(function (structa,structb) { + var vala = structa.costSum(); + if (structa.hasClass("Gates") && ent.canAttackClass("StoneWall")) // we hate gates + { + isGate = true; + vala += 10000; + } else if (structa.hasClass("ConquestCritical")) + vala = 100; + var valb = structb.costSum(); + if (structb.hasClass("Gates") && ent.canAttackClass("StoneWall")) // we hate gates + { + isGate = true; + valb += 10000; + } else if (structb.hasClass("ConquestCritical")) + valb = 100; + return (valb - vala); + }); if (ent.hasClass("Siege")) { if (mStruct.length !== 0) { - var rand = Math.floor(Math.random() * mStruct.length*0.2); - ent.attack(mStruct[+rand].id()); - //debug ("Siege units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName()); + if (isGate) + ent.attack(mStruct[0].id()); + else + { + var rand = Math.floor(Math.random() * mStruct.length*0.2); + ent.attack(mStruct[+rand].id()); + //debug ("Siege units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName()); + } } else if (SquareVectorDistance(self.targetPos, ent.position()) > 900 ){ //debug ("Siege units moving to " + uneval(self.targetPos)); ent.move((self.targetPos[0] + ent.position()[0])/2,(self.targetPos[1] + ent.position()[1])/2); } } else { - if (mUnit.length !== 0) { + if (mUnit.length !== 0 && !isGate) { var rand = Math.floor(Math.random() * mUnit.length*0.99); ent.attack(mUnit[(+rand)].id()); //debug ("Units attacking a unit from " +mUnit[+rand].owner() + " , " +mUnit[+rand].templateName()); } else if (mStruct.length !== 0) { - var rand = Math.floor(Math.random() * mStruct.length*0.99); - ent.attack(mStruct[+rand].id()); - //debug ("Units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName()); + if (isGate) + ent.attack(mStruct[0].id()); + else + { + var rand = Math.floor(Math.random() * mStruct.length*0.2); + ent.attack(mStruct[+rand].id()); + //debug ("Units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName()); + } } else if (SquareVectorDistance(self.targetPos, ent.position()) > 900 ){ //debug ("Units moving to " + uneval(self.targetPos)); ent.move((self.targetPos[0] + ent.position()[0])/2,(self.targetPos[1] + ent.position()[1])/2); diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/config.js b/binaries/data/mods/public/simulation/ai/qbot-wc/config.js index 8bf643b4ac..9b432cbaff 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/config.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/config.js @@ -1,9 +1,9 @@ // Baseconfig is the highest difficulty. var baseConfig = { "Military" : { - "fortressStartTime" : 840, // Time to wait before building one fortress. + "fortressStartTime" : 780, // Time to wait before building one fortress. "fortressLapseTime" : 300, // Time to wait between building 2 fortresses (minimal) - "defenceBuildingTime" : 300, // Time to wait before building towers or fortresses + "defenceBuildingTime" : 600, // Time to wait before building towers or fortresses "advancedMilitaryStartTime" : 720, // Time to wait before building advanced military buildings. Also limited by phase 2. "attackPlansStartTime" : 0 // time to wait before attacking. Start as soon as possible (first barracks) }, @@ -11,7 +11,7 @@ var baseConfig = { "townPhase" : 180, // time to start trying to reach town phase (might be a while after. Still need the requirements + ress ) "cityPhase" : 540, // time to start trying to reach city phase "farmsteadStartTime" : 240, // Time to wait before building a farmstead. - "marketStartTime" : 620, // Time to wait before building the market. + "marketStartTime" : 480, // Time to wait before building the market. "dockStartTime" : 240, // Time to wait before building the dock "techStartTime" : 600, // time to wait before teching. "targetNumBuilders" : 1.5, // Base number of builders per foundation. Later updated, but this remains a multiplier. @@ -58,7 +58,7 @@ var baseConfig = { "villager" : 60, "economicBuilding" : 80, "dropsites" : 180, - "field" : 500, + "field" : 1000, "militaryBuilding" : 120, "defenceBuilding" : 17, "majorTech" : 100, @@ -83,7 +83,7 @@ if (Config.difficulty === 1) Config["Military"] = { "fortressStartTime" : 1000, "fortressLapseTime" : 400, - "defenceBuildingTime" : 350, + "defenceBuildingTime" : 720, "advancedMilitaryStartTime" : 1000, "attackPlansStartTime" : 600 }; @@ -106,7 +106,7 @@ if (Config.difficulty === 1) Config["Military"] = { "fortressStartTime" : 1500, "fortressLapseTime" : 1000000, - "defenceBuildingTime" : 500, + "defenceBuildingTime" : 900, "advancedMilitaryStartTime" : 1300, "attackPlansStartTime" : 1200 // 20 minutes ought to give enough times for beginners }; diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/economy.js b/binaries/data/mods/public/simulation/ai/qbot-wc/economy.js index e325c23925..59b407658b 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/economy.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/economy.js @@ -23,6 +23,8 @@ var EconomyManager = function() { this.farmsteadStartTime = Config.Economy.farmsteadStartTime * 1000; this.techStartTime = Config.Economy.techStartTime * 1000; + this.dockFailed = false; // sanity check + this.resourceMaps = {}; // Contains maps showing the density of wood, stone and metal this.CCResourceMaps = {}; // Contains maps showing the density of wood, stone and metal, optimized for CC placement. @@ -844,7 +846,7 @@ EconomyManager.prototype.buildFarmstead = function(gameState, queues){ }; EconomyManager.prototype.buildDock = function(gameState, queues){ - if (!gameState.ai.waterMap) + if (!gameState.ai.waterMap || this.dockFailed) return; if (gameState.getTimeElapsed() > this.dockStartTime) { if (queues.economicBuilding.countTotalQueuedUnitsWithClass("NavalMarket") === 0 && diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/military.js b/binaries/data/mods/public/simulation/ai/qbot-wc/military.js index 96acaaa4ec..ce00401f49 100755 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/military.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/military.js @@ -379,7 +379,7 @@ MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){ + queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["DefenseTower"] && gameState.currentPhase() > 1) { gameState.getOwnEntities().forEach(function(dropsiteEnt) { - if (dropsiteEnt.resourceDropsiteTypes() && dropsiteEnt.getMetadata(PlayerID, "defenseTower") !== true){ + if (dropsiteEnt.resourceDropsiteTypes() && dropsiteEnt.getMetadata(PlayerID, "defenseTower") !== true && !dropsiteEnt.hasClass("CivCentre")){ var position = dropsiteEnt.position(); if (position){ queues.defenceBuilding.addItem(new BuildingConstructionPlan(gameState, 'structures/{civ}_defense_tower', position)); @@ -574,14 +574,24 @@ MilitaryAttackManager.prototype.update = function(gameState, queues, events) { && gameState.getTimeElapsed() > this.attackPlansStartTime) { if (this.upcomingAttacks["CityAttack"].length == 0 && (gameState.getTimeElapsed() < 25*60000 || Config.difficulty < 2)) { var Lalala = new CityAttack(gameState, this,this.TotalAttackNumber, -1); - debug ("Military Manager: Creating the plan " +this.TotalAttackNumber); - this.TotalAttackNumber++; - this.upcomingAttacks["CityAttack"].push(Lalala); + if (!Lalala) + { + this.attackPlansEncounteredWater = true; // hack + } else { + debug ("Military Manager: Creating the plan " +this.TotalAttackNumber); + this.TotalAttackNumber++; + this.upcomingAttacks["CityAttack"].push(Lalala); + } } else if (this.upcomingAttacks["CityAttack"].length == 0 && Config.difficulty !== 0) { var Lalala = new CityAttack(gameState, this,this.TotalAttackNumber, -1, "superSized"); - debug ("Military Manager: Creating the super sized plan " +this.TotalAttackNumber); - this.TotalAttackNumber++; - this.upcomingAttacks["CityAttack"].push(Lalala); + if (!Lalala) + { + this.attackPlansEncounteredWater = true; // hack + } else { + debug ("Military Manager: Creating the super sized plan " +this.TotalAttackNumber); + this.TotalAttackNumber++; + this.upcomingAttacks["CityAttack"].push(Lalala); + } } } /* diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/plan-building.js b/binaries/data/mods/public/simulation/ai/qbot-wc/plan-building.js index 600a0b97bf..c2ec1cde8c 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/plan-building.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/plan-building.js @@ -38,6 +38,8 @@ BuildingConstructionPlan.prototype.execute = function(gameState) { var pos = this.findGoodPosition(gameState); if (!pos){ + if (this.template.hasClass("Naval")) + gameState.ai.modules.economy.dockFailed = true; debug("No room to place " + this.type); return; } diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/qbot.js b/binaries/data/mods/public/simulation/ai/qbot-wc/qbot.js index 7d50b15e18..5ca0bf11b3 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/qbot.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/qbot.js @@ -65,9 +65,12 @@ QBotAI.prototype.InitShared = function(gameState, sharedScript) { this.pathsToMe = []; this.pathInfo = { "angle" : 0, "needboat" : true, "mkeyPos" : myKeyEntities.toEntityArray()[0].position(), "ekeyPos" : enemyKeyEntities.toEntityArray()[0].position() }; - var pos = [this.pathInfo.mkeyPos[0] + 140*Math.cos(this.pathInfo.angle),this.pathInfo.mkeyPos[1] + 140*Math.sin(this.pathInfo.angle)]; + // 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] + 180*Math.cos(this.pathInfo.angle),this.pathInfo.mkeyPos[1] + 180*Math.sin(this.pathInfo.angle)]; var path = this.pathFinder.getPath(this.pathInfo.ekeyPos, pos, 3, 3);// uncomment for debug:*/, 300000, gameState); + //Engine.DumpImage("initialPath" + PlayerID + ".png", this.pathFinder.TotorMap.map, this.pathFinder.TotorMap.width,this.pathFinder.TotorMap.height,255); + if (path !== undefined && path[1] !== undefined && path[1] == false) { // path is viable and doesn't require boating. // blackzone the last two waypoints. @@ -171,7 +174,7 @@ QBotAI.prototype.OnUpdate = function(sharedScript) { if (this.pathInfo !== undefined) { - var pos = [this.pathInfo.mkeyPos[0] + 140*Math.cos(this.pathInfo.angle),this.pathInfo.mkeyPos[1] + 140*Math.sin(this.pathInfo.angle)]; + var pos = [this.pathInfo.mkeyPos[0] + 180*Math.cos(this.pathInfo.angle),this.pathInfo.mkeyPos[1] + 180*Math.sin(this.pathInfo.angle)]; var path = this.pathFinder.getPath(this.pathInfo.ekeyPos, pos, 6, 6);// uncomment for debug:*/, 300000, gameState); if (path !== undefined && path[1] !== undefined && path[1] == false) { // path is viable and doesn't require boating. diff --git a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js index d8bc5b36cf..2fadb08f5b 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js @@ -128,10 +128,48 @@ AddMock(101, IID_EntityLimits, { GetCounts: function() { return {"Bar": 0}; }, }); -AddMock(101, IID_TechnologyManager, { +AddMock(100, IID_TechnologyManager, { IsTechnologyResearched: function(tech) { if (tech == "phase_village") return true; else return false; }, + GetQueuedResearch: function() { return {}; }, + GetStartedResearch: function() { return {}; }, + GetResearchedTechs: function() { return {}; }, + GetClassCounts: function() { return {}; }, + GetTypeCountsByClass: function() { return {}; }, GetTechModifications: function() { return {}; }, }); +AddMock(100, IID_StatisticsTracker, { + GetStatistics: function() { + return { + "unitsTrained": 10, + "unitsLost": 9, + "buildingsConstructed": 5, + "buildingsLost": 4, + "civCentresBuilt": 1, + "resourcesGathered": { + "food": 100, + "wood": 0, + "metal": 0, + "stone": 0, + "vegetarianFood": 0, + }, + "treasuresCollected": 0, + "percentMapExplored": 10, + }; + }, + IncreaseTrainedUnitsCounter: function() { return 1; }, + IncreaseConstructedBuildingsCounter: function() { return 1; }, + IncreaseBuiltCivCentresCounter: function() { return 1; }, + }); + +AddMock(101, IID_TechnologyManager, { + IsTechnologyResearched: function(tech) { if (tech == "phase_village") return true; else return false; }, + GetQueuedResearch: function() { return {}; }, + GetStartedResearch: function() { return {}; }, + GetResearchedTechs: function() { return {}; }, + GetClassCounts: function() { return {}; }, + GetTypeCountsByClass: function() { return {}; }, + GetTechModifications: function() { return {}; }, +}); AddMock(101, IID_StatisticsTracker, { GetStatistics: function() { @@ -174,13 +212,18 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), { state: "active", team: -1, teamsLocked: false, - phase: "", + phase: "village", isAlly: [false, false], isNeutral: [false, false], isEnemy: [true, true], entityLimits: {"Foo": 10}, entityCounts: {"Foo": 5}, techModifications: {}, + researchQueued: {}, + researchStarted: {}, + researchedTechs: {}, + classCounts: {}, + typeCountsByClass: {}, }, { name: "Player 2", @@ -201,6 +244,11 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), { entityLimits: {"Bar": 20}, entityCounts: {"Bar": 0}, techModifications: {}, + researchQueued: {}, + researchStarted: {}, + researchedTechs: {}, + classCounts: {}, + typeCountsByClass: {}, } ], circularMap: false, @@ -221,13 +269,18 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), { state: "active", team: -1, teamsLocked: false, - phase: "", + phase: "village", isAlly: [false, false], isNeutral: [false, false], isEnemy: [true, true], entityLimits: {"Foo": 10}, entityCounts: {"Foo": 5}, techModifications: {}, + researchQueued: {}, + researchStarted: {}, + researchedTechs: {}, + classCounts: {}, + typeCountsByClass: {}, statistics: { unitsTrained: 10, unitsLost: 9, @@ -264,6 +317,11 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), { entityLimits: {"Bar": 20}, entityCounts: {"Bar": 0}, techModifications: {}, + researchQueued: {}, + researchStarted: {}, + researchedTechs: {}, + classCounts: {}, + typeCountsByClass: {}, statistics: { unitsTrained: 10, unitsLost: 9, diff --git a/source/simulation2/components/CCmpAIManager.cpp b/source/simulation2/components/CCmpAIManager.cpp index aaba3657e2..f5154404d9 100644 --- a/source/simulation2/components/CCmpAIManager.cpp +++ b/source/simulation2/components/CCmpAIManager.cpp @@ -359,7 +359,7 @@ public: JS_GC(self->m_ScriptInterface.GetContext()); } - bool TryLoadSharedComponent(bool callConstructor) + bool TryLoadSharedComponent(bool hasTechs) { // only load if there are AI players. if (m_Players.size() == 0) @@ -370,7 +370,7 @@ public: // reset the value so it can be used to determine if we actually initialized it. m_HasSharedComponent = false; - + VfsPaths sharedPathnames; // Check for "shared" module. vfs::GetPathnames(g_VFS, L"simulation/ai/common-api-v3/", L"*.js", sharedPathnames); @@ -423,10 +423,27 @@ public: } else { - // For deserialization, we want to create the object with the correct prototype - // but don't want to actually run the constructor again - // XXX: actually we don't currently use this path for deserialization - maybe delete it? - m_SharedAIObj = CScriptValRooted(m_ScriptInterface.GetContext(),m_ScriptInterface.NewObjectFromConstructor(ctor.get())); + // won't get the tech templates directly. + // Set up the data to pass as the constructor argument + CScriptVal settings; + m_ScriptInterface.Eval(L"({})", settings); + CScriptVal playersID; + m_ScriptInterface.Eval(L"({})", playersID); + for (size_t i = 0; i < m_Players.size(); ++i) + { + jsval val = m_ScriptInterface.ToJSVal(m_ScriptInterface.GetContext(), m_Players[i]->m_Player); + m_ScriptInterface.SetPropertyInt(playersID.get(), i, CScriptVal(val), true); + } + + m_ScriptInterface.SetProperty(settings.get(), "players", playersID); + + CScriptVal m_fakeTech; + m_ScriptInterface.Eval("({})", m_fakeTech); + m_ScriptInterface.SetProperty(settings.get(), "techTemplates", m_fakeTech, false); + + ENSURE(m_HasLoadedEntityTemplates); + m_ScriptInterface.SetProperty(settings.get(), "templates", m_EntityTemplates, false); + m_SharedAIObj = CScriptValRooted(m_ScriptInterface.GetContext(),m_ScriptInterface.CallConstructor(ctor.get(), settings.get())); } if (m_SharedAIObj.undefined()) @@ -640,6 +657,7 @@ public: if (!m_ScriptInterface.CallFunctionVoid(m_Players.back()->m_Obj.get(), "Deserialize", scriptData)) LOGERROR(L"AI script Deserialize call failed"); } + TryLoadSharedComponent(false); } int getPlayerSize() @@ -774,17 +792,6 @@ public: m_TerritoriesDirtyID = 0; StartLoadEntityTemplates(); - - // loading the technology template - ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); - - CmpPtr cmpTechTemplateManager(GetSimContext(), SYSTEM_ENTITY); - ENSURE(cmpTechTemplateManager); - - // Get the game state from AIInterface - CScriptVal techTemplates = cmpTechTemplateManager->GetAllTechs(); - - m_Worker.RegisterTechTemplates(scriptInterface.WriteStructuredClone(techTemplates.get())); } virtual void Deinit() @@ -847,6 +854,15 @@ public: virtual void TryLoadSharedComponent() { + ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); + // load the technology templates + CmpPtr cmpTechTemplateManager(GetSimContext(), SYSTEM_ENTITY); + ENSURE(cmpTechTemplateManager); + + // Get the game state from AIInterface + CScriptVal techTemplates = cmpTechTemplateManager->GetAllTechs(); + + m_Worker.RegisterTechTemplates(scriptInterface.WriteStructuredClone(techTemplates.get())); m_Worker.TryLoadSharedComponent(true); }