Index: binaries/data/mods/public/maps/scenarios/Introductory_Tutorial.js =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/scenarios/Introductory_Tutorial.js @@ -0,0 +1,419 @@ +let tutorialGoals = [ + { + "instructions": markForTranslation("Welcome to the 0 A.D. tutorial."), + }, + { + "instructions": markForTranslation("First left-click on a female citizen, then Right-click on a berry bush nearby to make the unit collect food. Female citizens gather food faster than other units."), + "OnPlayerCommand": function(msg) + { + if (msg.cmd.type == "gather" && msg.cmd.target && + TriggerHelper.GetResourceType(msg.cmd.target).specific == "fruit") + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Select the citizen-soldier, right-click on a tree near the Civil Center to begin collecting wood. Citizen-soldiers gather wood faster than female citizens."), + "OnPlayerCommand": function(msg) + { + if (msg.cmd.type == "gather" && msg.cmd.target && + TriggerHelper.GetResourceType(msg.cmd.target).specific == "tree") + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Select the Civil Center building, and shift-click on the Hoplite icon (2nd in the row) once to begin training 5 Hoplites."), + "OnTrainingQueued": function(msg) + { + if (msg.unitTemplate != "units/spart_infantry_spearman_b" || +msg.count == 1) + { + let entity = msg.trainerEntity; + let cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue); + cmpProductionQueue.ResetQueue(); + let txt = +msg.count == 1 ? + markForTranslation("Do not forget to shift-click to produce several units.") : + markForTranslation("Shift-click on the HOPLITE icon."); + this.WarningMessage(txt); + return; + } + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Select the two idle female citizens and build a house nearby by selecting the house icon. Place the house by left-clicking on a piece of land."), + "OnPlayerCommand": function(msg) + { + if (msg.cmd.type == "repair" && TriggerHelper.EntityHasClass(msg.cmd.target, "House")) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("When they are ready, select the newly trained Hoplites and assign them to build a storehouse beside some nearby trees. They will begin to gather wood when it's constructed."), + "OnPlayerCommand": function(msg) + { + if (msg.cmd.type == "repair" && TriggerHelper.EntityHasClass(msg.cmd.target, "Storehouse")) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Build a set of 5 skirmishers by shift-clicking on the skirmisher icon (3rd in the row) in the Civil Center."), + "Init": function() + { + this.trainingDone = false; + }, + "OnTrainingQueued": function(msg) + { + if (msg.unitTemplate != "units/spart_infantry_javelinist_b" || +msg.count == 1) + { + let entity = msg.trainerEntity; + let cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue); + cmpProductionQueue.ResetQueue(); + let txt = +msg.count == 1 ? + markForTranslation("Do not forget to shift-click to produce several units.") : + markForTranslation("Shift-click on the SKIRMISHER icon."); + this.WarningMessage(txt); + return; + } + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Build a farmstead in an open space beside the Civil Center using any idle builders."), + "OnPlayerCommand": function(msg) + { + if (msg.cmd.type == "repair" && TriggerHelper.EntityHasClass(msg.cmd.target, "Farmstead")) + this.NextGoal(); + }, + "OnTrainingFinished": function(msg) + { + this.trainingDone = true; + } + }, + { + "instructions": markForTranslation("Let's wait for the farmstead to be built."), + "OnTrainingFinished": function(msg) + { + this.trainingDone = true; + }, + "OnStructureBuilt": function(msg) + { + if (TriggerHelper.EntityHasClass(msg.building, "Farmstead")) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Once the farmstead is constructed, its builders will automatically begin gathering food if there is any nearby. Select the builders and instead make them construct a field beside the farmstead."), + "Init": function() + { + this.farmStarted = false; + }, + "IsDone": function() + { + return this.farmStarted && this.trainingDone; + }, + "OnPlayerCommand": function(msg) + { + if (msg.cmd.type == "repair" && TriggerHelper.EntityHasClass(msg.cmd.target, "Field")) + this.farmStarted = true; + if (this.IsDone()) + this.NextGoal(); + }, + "OnTrainingFinished": function(msg) + { + this.trainingDone = true; + if (this.IsDone()) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("The field's builders will now automatically begin collecting food from the field. Using the newly created group of skirmishers, get them to build another house nearby."), + "OnPlayerCommand": function(msg) + { + if (msg.cmd.type == "repair" && TriggerHelper.EntityHasClass(msg.cmd.target, "House")) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Train 5 Hoplites from the Civil Center. Select the Civil Center and with it selected right click on a tree nearby. Units from the Civil Center will now automatically gather wood."), + "Init": function() + { + this.rallyPointSet = false; + this.trainingStarted = false; + }, + "IsDone": function() + { + return this.rallyPointSet && this.trainingStarted; + }, + "OnTrainingQueued": function(msg) + { + if (msg.unitTemplate != "units/spart_infantry_spearman_b" || +msg.count == 1) + { + let entity = msg.trainerEntity; + let cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue); + cmpProductionQueue.ResetQueue(); + let txt = +msg.count == 1 ? + markForTranslation("Do not forget to shift-click to produce several units.") : + markForTranslation("Shift-click on the HOPLITE icon."); + this.WarningMessage(txt); + return; + } + this.trainingStarted = true; + if (this.IsDone()) + this.NextGoal(); + }, + "OnPlayerCommand": function(msg) + { + if (msg.cmd.type != "set-rallypoint" || !msg.cmd.data || + !msg.cmd.data.command || msg.cmd.data.command != "gather" || + !msg.cmd.data.resourceType || msg.cmd.data.resourceType.specific != "tree") + { + this.WarningMessage(markForTranslation("Select the Civic Center, then hover your mouse over the tree and right-click when you see your cursor change into a Wood icon.")); + return; + } + this.rallyPointSet = true; + if (this.IsDone()) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Order the idle Skirmishers to build an outpost to the north east at the edge of your territory. This will be the fifth Village Phase structure that you have built, allowing you to advance to the Town Phase."), + "OnPlayerCommand": function(msg) + { + if (msg.cmd.type == "repair" && TriggerHelper.EntityHasClass(msg.cmd.target, "Outpost")) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Select the Civil Center again and advance to Town Phase by clicking on the 'II' icon (you have to wait for the outpost to be built first). This will allow Town Phase buildings to be constructed."), + "IsDone": function() + { + return TriggerHelper.HasDealtWithTech(this.playerID, "phase_town"); + }, + "OnResearchQueued": function(msg) + { + if (msg.technologyTemplate && TriggerHelper.EntityHasClass(msg.researcherEntity, "CivilCentre")) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("While waiting for the phasing up, you may reaffect your idle workers to gathering the resources you are short of."), + "IsDone": function() + { + let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); + let playerEnt = cmpPlayerManager.GetPlayerByID(this.playerID); + let cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager); + return cmpTechnologyManager && cmpTechnologyManager.IsTechnologyResearched("phase_town"); + }, + "OnResearchFinished": function(msg) + { + if (msg.tech == "phase_town") + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Start building 5 female citizens in the Civil Center and set its rally point to the farm (right click on it)."), + "Init": function() + { + this.rallyPointSet = false; + this.trainingStarted = false; + }, + "IsDone": function() + { + return this.rallyPointSet && this.trainingStarted; + }, + "OnTrainingQueued": function(msg) + { + if (msg.unitTemplate != "units/spart_support_female_citizen" || +msg.count == 1) + { + let entity = msg.trainerEntity; + let cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue); + cmpProductionQueue.ResetQueue(); + let txt = +msg.count == 1 ? + markForTranslation("Do not forget to shift-click to produce several units.") : + markForTranslation("Shift-click on the FEMALE icon."); + this.WarningMessage(txt); + return; + } + this.trainingStarted = true; + if (this.IsDone()) + this.NextGoal(); + }, + "OnPlayerCommand": function(msg) + { + if (msg.cmd.type != "set-rallypoint" || !msg.cmd.data || + !msg.cmd.data.command || msg.cmd.data.command != "gather" || + !msg.cmd.data.resourceType || msg.cmd.data.resourceType.specific != "grain") + return; + this.rallyPointSet = true; + if (this.IsDone()) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Build a Barracks nearby. Whenever your population limit is reached, build an extra house using any available builder units."), + "OnPlayerCommand": function(msg) + { + if (msg.cmd.type == "repair" && TriggerHelper.EntityHasClass(msg.cmd.target, "Barracks")) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Prepare for an attack by an enemy player. Build more soldiers using the Barracks, and get idle soldiers to build a Defense Tower near your Outpost."), + "OnPlayerCommand": function(msg) + { + if (msg.cmd.type == "repair" && TriggerHelper.EntityHasClass(msg.cmd.target, "DefenseTower")) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Build a Blacksmith and research the Infantry Training technology (sword icon) to improve infantry hack attack."), + "OnResearchQueued": function(msg) + { + if (msg.technologyTemplate && TriggerHelper.EntityHasClass(msg.researcherEntity, "Blacksmith")) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("The enemy is coming. Build more soldiers to fight off the enemies."), + "OnResearchFinished": function(msg) + { + this.LaunchAttack(); + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Try to repel the attack."), + "OnOwnershipChanged": function(msg) + { + if (msg.to != -1) + return; + if (this.IsAttackRepelled()) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("The enemy's attack has been defeated. Now build a market and a temple while assigning new units to gather any required resources."), + "Init": function() + { + this.marketStarted = false; + this.templeStarted = false; + }, + "IsDone": function() + { + return this.marketStarted && this.templeStarted; + }, + "OnPlayerCommand": function(msg) + { + if (msg.cmd.type != "repair") + return; + this.marketStarted = this.marketStarted || TriggerHelper.EntityHasClass(msg.cmd.target, "Market"); + this.templeStarted = this.templeStarted || TriggerHelper.EntityHasClass(msg.cmd.target, "Temple"); + if (this.IsDone()) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("When that City Phase requirements have been reached, select your Civil Center and advance to City Phase."), + "OnResearchQueued": function(msg) + { + if (msg.technologyTemplate && TriggerHelper.EntityHasClass(msg.researcherEntity, "CivilCentre")) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("While waiting for the phase change, you may build more soldiers at the barracks."), + "OnResearchFinished": function(msg) + { + if (msg.tech == "phase_city") + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("Now that you are in City Phase, build a fortress nearby (gather some stone first if needed) and then use it to build 2 Battering Rams."), + "Init": function() + { + this.ramCount = 0; + }, + "IsDone": function() + { + return this.ramCount > 1; + }, + "OnTrainingQueued": function(msg) + { + if (msg.unitTemplate == "units/spart_mechanical_siege_ram") + ++this.ramCount; + if (this.IsDone()) + { + this.RemoveChampions(); + this.NextGoal(); + } + } + }, + { + "instructions": [ + markForTranslation("Stop all your soldiers gathering resources and instead task small groups to find the enemy Civil Center on the map. Once The enemy's base has been spotted, send your siege weapons and all remaining soldiers to destroy it.\n"), + markForTranslation("Female citizens should continue to gather resources.") + ], + "OnOwnershipChanged": function(msg) + { + if (msg.from != this.enemyID) + return; + if (TriggerHelper.EntityHasClass(msg.entity, "CivilCentre")) + this.NextGoal(); + } + }, + { + "instructions": markForTranslation("The enemy has been defeated. These tutorial tasks are now completed."), + } +]; + +Trigger.prototype.LaunchAttack = function() +{ + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + let target = cmpRangeManager.GetEntitiesByPlayer(this.playerID).filter(e => Engine.QueryInterface(e, IID_Identity).HasClass("DefenseTower")); + this.attackers = cmpRangeManager.GetEntitiesByPlayer(this.enemyID).filter(e => Engine.QueryInterface(e, IID_Identity).HasClass("CitizenSoldier")); + if (!target.length) + return; + let position = Engine.QueryInterface(target[0], IID_Position).GetPosition2D(); + for (let ent of this.attackers) + { + let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); + if (cmpUnitAI) + cmpUnitAI.WalkAndFight(position.x, position.y, { "attack": ["Unit"] }, false); + } +}; + +Trigger.prototype.IsAttackRepelled = function() +{ + for (let ent of this.attackers) + { + let cmpHealth = Engine.QueryInterface(ent, IID_Health); + if (cmpHealth && cmpHealth.GetHitpoints() > 0) + return false; + } + return true; +}; + +Trigger.prototype.RemoveChampions = function() +{ + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + let champions = cmpRangeManager.GetEntitiesByPlayer(this.enemyID).filter(e => Engine.QueryInterface(e, IID_Identity).HasClass("Champion")); + let keep = 6; + for (let ent of champions) + { + let cmpHealth = Engine.QueryInterface(ent, IID_Health); + if (cmpHealth) + { + if (keep-- > 0) + continue; + cmpHealth.Kill(); + } + else + Engine.DestroyEntity(ent); + } +}; + +Trigger.prototype.tutorialGoals = tutorialGoals; +var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); +cmpTrigger.playerID = 1; +cmpTrigger.enemyID = 2; +cmpTrigger.RegisterTrigger("OnInitGame", "InitTutorial", { "enabled": true }); Index: binaries/data/mods/public/maps/scenarios/Introductory_Tutorial.xml =================================================================== --- binaries/data/mods/public/maps/scenarios/Introductory_Tutorial.xml +++ binaries/data/mods/public/maps/scenarios/Introductory_Tutorial.xml @@ -40,10 +40,8 @@ { "CircularMap": true, "Description": "This is a basic tutorial to get you started playing 0 A.D.", - "GameType": "conquest", - "Keywords": [ - "demo" - ], + "GameType": "endless", + "Keywords": ["demo", "trigger"], "LockTeams": false, "Name": "Introductory Tutorial", "PlayerData": [ @@ -65,8 +63,8 @@ "Team": -1 }, { - "AI": "tutorial-ai", - "Civ": "cart", + "AI": "", + "Civ": "athen", "Color": { "b": 20, "g": 20, @@ -83,7 +81,12 @@ } ], "Preview": "Introductory_Tutorial.png", - "RevealMap": false + "RevealMap": false, + "TriggerScripts": [ + "scripts/TriggerHelper.js", + "scripts/Tutorial.js", + "scenarios/Introductory_Tutorial.js" + ] } ]]> @@ -4066,157 +4069,157 @@ - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 @@ -4282,25 +4285,25 @@ - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 Index: binaries/data/mods/public/maps/scripts/TriggerHelper.js =================================================================== --- binaries/data/mods/public/maps/scripts/TriggerHelper.js +++ binaries/data/mods/public/maps/scripts/TriggerHelper.js @@ -219,7 +219,7 @@ let cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager); return cmpTechnologyManager && (cmpTechnologyManager.IsTechnologyQueued(techName) || cmpTechnologyManager.IsTechnologyStarted(techName) || - cmpTechnologyManager.IsTechnologyResearched(techName)) + cmpTechnologyManager.IsTechnologyResearched(techName)); }; Engine.RegisterGlobal("TriggerHelper", TriggerHelper); Index: binaries/data/mods/public/maps/scripts/Tutorial.js =================================================================== --- binaries/data/mods/public/maps/scripts/Tutorial.js +++ binaries/data/mods/public/maps/scripts/Tutorial.js @@ -23,6 +23,9 @@ this.RegisterTrigger(key, action, { "enabled": false }); this.tutorialEvents.push(key); } + + if (goal.instructions && typeof goal.instructions === "string") + goal.instructions = [goal.instructions]; } this.NextGoal(); Index: binaries/data/mods/public/simulation/ai/tutorial-ai/_init.js =================================================================== --- binaries/data/mods/public/simulation/ai/tutorial-ai/_init.js +++ /dev/null @@ -1 +0,0 @@ -Engine.IncludeModule("common-api"); Index: binaries/data/mods/public/simulation/ai/tutorial-ai/config.js =================================================================== --- binaries/data/mods/public/simulation/ai/tutorial-ai/config.js +++ /dev/null @@ -1,57 +0,0 @@ -var baseConfig = { - "attack" : { - "minAttackSize" : 20, // attackMoveToLocation - "maxAttackSize" : 60, // attackMoveToLocation - "enemyRatio" : 1.5, // attackMoveToLocation - "groupSize" : 10 // military - }, - - // defence - "defence" : { - "acquireDistance" : 220, - "releaseDistance" : 250, - "groupRadius" : 20, - "groupBreakRadius" : 40, - "groupMergeRadius" : 10, - "defenderRatio" : 2 - }, - - // military - "buildings" : { - "moderate" : { - "default" : [ "structures/{civ}_barracks" ] - }, - "advanced" : { - "cart" : [ "structures/{civ}_fortress", "structures/{civ}_embassy_celtic", - "structures/{civ}_embassy_iberian", "structures/{civ}_embassy_italiote" ], - "iber" : [ "structures/{civ}_fortress" ], - "pers" : [ "structures/{civ}_fortress", "structures/{civ}_stables", "structures/{civ}_apadana" ], - "rome" : [ "structures/{civ}_army_camp", "structures/{civ}_fortress" ] - }, - "fort" : { - "default" : [ "structures/{civ}_fortress" ], - } - }, - - // qbot - "priorities" : { // Note these are dynamic, you are only setting the initial values - "house" : 500, - "citizenSoldier" : 100, - "villager" : 100, - "economicBuilding" : 30, - "field" : 20, - "advancedSoldier" : 30, - "siege" : 10, - "militaryBuilding" : 50, - "defenceBuilding" : 17, - "civilCentre" : 1000 - }, - - "debug" : false -}; - -var Config = { - "debug": true -}; - -Config.__proto__ = baseConfig; Index: binaries/data/mods/public/simulation/ai/tutorial-ai/data.json =================================================================== --- binaries/data/mods/public/simulation/ai/tutorial-ai/data.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "Tutorial AI", - "description": "The Tutorial AI should only be used on the \"Introductory Tutorial\" scenario.", - "constructor": "TutorialAI", - "moduleName" : "TutorialAI", - "hidden": true, - "useShared": true -} Index: binaries/data/mods/public/simulation/ai/tutorial-ai/economic_walkthrough.js =================================================================== --- binaries/data/mods/public/simulation/ai/tutorial-ai/economic_walkthrough.js +++ /dev/null @@ -1,275 +0,0 @@ -var economic_walkthrough = [ - { - "instructions": markForTranslation("Warning: This is an advanced tutorial and goes quite fast. If you don't keep up just restart and try again. Practise makes perfect!"), - }, - { - "trigger": "time", - "time": 0, - "instructions": markForTranslation("Lets get started, first train a batch (shift click) of 5 female citizens from the CC (Civil Center).") - }, - { - "trigger": "time", - "time": 5, - "instructions": markForTranslation("Now, assign your female citizens to gather berries, your cavalryman to hunt the chickens and the citizen soldiers to gather wood.") - }, - { - "trigger": "time", - "time": 15, - "instructions": markForTranslation("Now set the rally point of your civil center on the berries.") - }, - { - "trigger": "time", - "time": 23, - "instructions": markForTranslation("Queue some slingers to train after the female citizens, you won't have enough resources for a batch.") - }, - { - "trigger": "time", - "time": 30, - "instructions": markForTranslation("When your female citizens have finished training, use 4 to build a house nearby.") - }, - { - "trigger": "time", - "time": 38, - "instructions": markForTranslation("Set the CC's rally point on the nearby trees, queue more slingers, you want to train 6 right now.") - }, - { - "trigger": "time", - "time": 70, - "instructions": markForTranslation("Queue a batch of 5 female citizens.") - }, - { - "trigger": "time", - "time": 75, - "instructions": markForTranslation("Use shift-click to queue another house for the 4 builders.") - }, - { - "trigger": "time", - "time": 85, - "instructions": markForTranslation("Set the rally point back to the berries once all of the citizen soldiers have finished training.") - }, - { - "trigger": "time", - "time": 95, - "instructions": markForTranslation("Send one citizen soldier to gather stone, send 2 others to build a storehouse near the southern trees.") - }, - { - "trigger": "time", - "time": 100, - "instructions": markForTranslation("Queue another 5 female citizens.") - }, - { - "trigger": "time", - "time": 107, - "instructions": markForTranslation("Use a female citizen to construct a field next to your CC.") - }, - { - "trigger": "time", - "time": 110, - "instructions": markForTranslation("Your cavalryman has probably run out of chickens by now, there are some goats to the left of your base.") - }, - { - "trigger": "time", - "time": 115, - "instructions": markForTranslation("Use 2-3 more female citizens to construct the field.") - }, - { - "trigger": "time", - "time": 130, - "instructions": markForTranslation("Queue another 5 female citizens.") - }, - { - "trigger": "time", - "time": 135, - "instructions": markForTranslation("Queue another house.") - }, - { - "trigger": "time", - "time": 145, - "instructions": markForTranslation("The berry bushes are getting crowded, send some female citizens to work the field, set the rally point there.") - }, - { - "trigger": "time", - "time": 160, - "instructions": markForTranslation("Queue another 5 female citizens, these should gather stone.") - }, - { - "trigger": "time", - "time": 170, - "instructions": markForTranslation("Move your woodcutters to the trees near the storehouse so they don’t have to walk as far.") - }, - { - "trigger": "time", - "time": 175, - "instructions": markForTranslation("Make sure the workers from the berry bushes are used to gather from the field rather than standing idle when they run out.") - }, - { - "trigger": "time", - "time": 190, - "instructions": markForTranslation("Queue another house to be built.") - }, - { - "trigger": "time", - "time": 195, - "instructions": markForTranslation("Queue 5 more female citizens. These will gather wood.") - }, - { - "trigger": "time", - "time": 220, - "instructions": markForTranslation("Build another field, this decreases the amount of walking for your female citizens.") - }, - { - "trigger": "time", - "time": 220, - "instructions": markForTranslation("Train some more slingers, send them to gather stone. Train about 5, you may not have enough resources for a batch right now.") - }, - { - "trigger": "time", - "time": 231, - "instructions": markForTranslation("Queue another house.") - }, - { - "trigger": "time", - "time": 260, - "instructions": markForTranslation("Queue another batch of 5 female citizens. These will gather wood.") - }, - { - "trigger": "time", - "time": 285, - "instructions": markForTranslation("Queue another house.") - }, - { - "trigger": "time", - "time": 290, - "instructions": markForTranslation("Queue a batch of spearmen.") - }, - { - "trigger": "time", - "time": 295, - "instructions": markForTranslation("Research the stone mining technology from the storehouse.") - }, - { - "trigger": "time", - "time": 300, - "instructions": markForTranslation("Queue a batch of slingers.") - }, - { - "trigger": "time", - "time": 310, - "instructions": markForTranslation("Move 3 women from wood and 3 from stone to farming. Women are cheap to train initially but later in the game it is best to maximize efficiency as you are able to train more citizen soldiers.") - }, - { - "trigger": "time", - "time": 325, - "instructions": markForTranslation("Queue another house.") - }, - { - "trigger": "time", - "time": 330, - "instructions": markForTranslation("The slingers that you queued earlier should gather stone once they are trained.") - }, - { - "trigger": "time", - "time": 340, - "instructions": markForTranslation("Queue a batch of spearmen, they should gather wood.") - }, - { - "trigger": "time", - "time": 360, - "instructions": markForTranslation("Next queue a batch of slingers.") - }, - { - "trigger": "time", - "time": 390, - "instructions": markForTranslation("Send some of the stone miners to mine metal, getting ready for the next age.") - }, - { - "trigger": "time", - "time": 400, - "instructions": markForTranslation("Queue another house.") - }, - { - "trigger": "time", - "time": 410, - "instructions": markForTranslation("Start researching Phase 2.") - }, - { - "trigger": "time", - "time": 430, - "instructions": markForTranslation("Queue some slingers.") - }, - { - "trigger": "time", - "time": 440, - "instructions": markForTranslation("When you have advanced, use a single citizen soldier to place a barracks foundation. Then use the rest of the non food or house building female citizens to construct it.") - }, - { - "trigger": "time", - "time": 458, - "instructions": markForTranslation("Queue 5 more females for construction work.") - }, - { - "trigger": "time", - "time": 480, - "instructions": markForTranslation("Queue a batch of slingers to gather the trees on the north side of your base.") - }, - { - "trigger": "time", - "time": 490, - "instructions": markForTranslation("This is the end of the walkthrough. This should give you a good idea of how to build up a nice economy rapidly.") - }, - { - "trigger": "time", - "time": 500, - "instructions": markForTranslation("At this point you should be able to fully use both training facilities, keep putting units into economic work, to keep growing faster and faster.") - }, - { - "trigger": "time", - "time": 505, - "instructions": markForTranslation("From this point you have a good base to build an army and wipe out the opposition.") - }, - { - "trigger": "time", - "time": 510, - "instructions": markForTranslation("The key points to remember are:") - }, - { - "trigger": "time", - "time": 513, - "instructions": markForTranslation(" - Don't have any idle units. Shift click queuing and rally points are very useful.") - }, - { - "trigger": "time", - "time": 516, - "instructions": markForTranslation(" - Use all of your resources, stockpiled resources are waste, having too much of a resource is waste.") - }, - { - "trigger": "time", - "time": 519, - "instructions": markForTranslation(" - Use the right units for the right task, female citizens gather food, cavalry hunt, citizen soldiers do the rest.") - }, - { - "trigger": "time", - "time": 522, - "instructions": markForTranslation(" - Don't be afraid to swap units from one resource to another, see point 2. Make sure they deposit what they are carrying first though.") - }, - { - "trigger": "time", - "time": 525, - "instructions": markForTranslation(" - Build enough houses, your CC should be kept busy as much as possible.") - }, - { - "trigger": "time", - "time": 528, - "instructions": markForTranslation(" - Female citizens are cheap, so you can build more of them and gather more resources.") - }, - { - "trigger": "time", - "time": 531, - "instructions": markForTranslation(" - Don't get carried away on the last point or you will get raided and have no defense.") - }, - { - "trigger": "time", - "time": 538, - "instructions": markForTranslation("Each map is different, so you will need to play slightly differently on each. This should give an outline for a high resource map.") - }, - ] Index: binaries/data/mods/public/simulation/ai/tutorial-ai/introductory_tutorial.js =================================================================== --- binaries/data/mods/public/simulation/ai/tutorial-ai/introductory_tutorial.js +++ /dev/null @@ -1,169 +0,0 @@ -var introductoryTutorial = [ -//Tutorial starts with a Civil Centre, 3 female citizens, and 1 skirmisher. The civ is Sparta. 2 AI players - One for attacking, one for base defending(?). -{ - "instructions": markForTranslation("Welcome to the 0 A.D. tutorial. First left-click on a female citizen, then Right-click on a berry bush nearby to make the unit collect food. Female citizens gather food faster than other units.") -}, -{ - "instructions": markForTranslation("Select the citizen-soldier, right-click on a tree near the Civil Center to begin collecting wood. Citizen-soldiers gather wood faster than female citizens."), - "trigger": "food_gathered" -}, -{ - "instructions": markForTranslation("Select the Civil Center building, and shift-click on the Hoplite icon (2nd in the row) once to begin training 5 Hoplites."), - "trigger": "wood_gathered" -}, -{ - "instructions": markForTranslation("Select the two idle female citizens and build a house nearby by selecting the house icon. Place the house by left-clicking on a piece of land."), - "trigger": "training_start", - "template": "units/spart_infantry_spearman_b", - "count": 5 -}, -{ - "instructions": markForTranslation("Select the newly trained Hoplites and assign them to build a storehouse beside some nearby trees. They will begin to gather wood when it's constructed."), - "trigger": "entity_count", - "template": "structures/spart_house", - "count": 1 -}, -{ - "instructions": markForTranslation("Build a set of 5 skirmishers by shift-clicking on the skirmisher icon (3rd in the row) in the Civil Center."), - "trigger": "entity_count", - "template": "structures/spart_storehouse", - "count": 1 -}, -{ - "instructions": markForTranslation("Build a farmstead in an open space beside the Civil Center using any idle builders."), - "trigger": "training_start", - "template": "units/spart_infantry_javelinist_b", - "count": 5 -}, -{ - "instructions": markForTranslation("Once the farmstead is constructed, its builders will automatically begin gathering food if there is any nearby. Select the builders and instead make them construct a field beside the farmstead."), - "trigger": "entity_count", - "template": "structures/spart_farmstead", - "count": 1 -}, -{ - "instructions": markForTranslation("The field's builders will now automatically begin collecting food from the field. Using the newly created group of skirmishers, get them to build another house nearby."), - "trigger": "entity_count", - "template": "structures/spart_field", - "count": 1 -}, -{ - "instructions": markForTranslation("Train 5 Hoplites from the Civil Center. Select the Civil Center and with it selected right click on a tree nearby. Units from the Civil Center will now automatically gather wood."), - "trigger": "entity_count", - "template": "structures/spart_house", - "count": 2 -}, -{ - "instructions": markForTranslation("Order the idle Skirmishers to build an outpost to the north east at the edge of your territory. This will be the fifth Village Phase structure that you have built, allowing you to advance to the Town Phase."), - "trigger": "entity_count", - "template": "units/spart_infantry_spearman_b", - "count": 10 -}, -{ - "instructions": markForTranslation("Select the Civil Center again and advance to Town Phase by clicking on the 'II' icon. This will allow Town Phase buildings to be constructed."), - "trigger": "entity_count", - "template": "structures/spart_outpost", - "count": 1 -}, -{ - "instructions": markForTranslation("Start building 5 female citizens in the Civil Center and set its rally point to the farm (right click on it)."), - "trigger": "relative_time", - "time": 34 //TODO: This is a hack, should be when town phase is researched -}, -{ - "instructions": markForTranslation("Build a Barracks nearby. Whenever your population limit is reached, build an extra house using any available builder units."), - "trigger": "entity_count", - "template": "units/spart_support_female_citizen", - "count": 8 -}, -{ - "instructions": markForTranslation("Prepare for an attack by an enemy player. Build more soldiers using the Barracks, and get idle soldiers to build a Defense Tower near your Outpost."), - "trigger": "entity_count", - "template": "structures/spart_barracks", - "count": 1 -}, -{ - "instructions": markForTranslation("Build a Blacksmith and research the Infantry Training technology (sword icon) to improve infantry hack attack."), - "trigger": "entity_count", - "template": "structures/spart_defense_tower", - "count": 1 -}, -{ - "instructions": markForTranslation("The enemy is coming. Build more soldiers to fight off the enemies."), - "action": introductory_tutorial_attack, - "trigger": "entity_count", - "template": "structures/spart_blacksmith", - "count": 1 -}, -{ - "instructions": markForTranslation("The enemy's attack has been defeated. Now build a market and temple while assigning new units to gather any required resources."), - "trigger": "dead_enemy_units", - "collectionId": "intro_tutorial_attackers" -}, -{ - "instructions": markForTranslation("Now that City Phase requirements have been reached, select your Civil Center and advance to City Phase."), - "trigger": "entity_counts", - "templates": ["structures/spart_market", "structures/spart_temple"], - "counts": [1,1] -}, -{ - "instructions": markForTranslation("Now that you are in City Phase, build a fortress nearby and use it to build 2 Battering Rams."), - "trigger": "relative_time", - "time": 65 //TODO: This is a hack, should be when city phase is researched -}, -{ - "instructions": markForTranslation("Stop all your soldiers gathering resources and instead task small groups to find the enemy Civil Center on the map. Female citizens should continue to gather resources."), - "action": introductory_tutorial_remove_champions, - "trigger": "entity_count", - "template": "units/spart_mechanical_siege_ram", - "count": 2 -}, -{ - "instructions": markForTranslation("The enemy's base has been spotted, send your siege weapons and all remaining soldiers to destroy it."), - "trigger": "near_cc" -}, -{ - "instructions": markForTranslation("The enemy has been defeated. All tutorial tasks are now completed…"), - "trigger": "dead_enemy_units", - "collectionId": "intro_tutorial_cc" -} -]; - -function introductory_tutorial_attack (gameState) { - var units = gameState.updatingCollection( - "intro_tutorial_attackers", - API3.Filters.or( - API3.Filters.byType("units/athen_infantry_spearman_b"), - API3.Filters.byType("units/athen_infantry_javelinist_b") - ), - gameState.getOwnEntities() - ); - var towers = gameState.updatingCollection( - "players_towers", - API3.Filters.byType("structures/spart_defense_tower"), - gameState.getEnemyEntities() - ); - var towerPos = towers.toEntityArray()[0].position(); - units.move(towerPos[0]+5, towerPos[1]+15); -} - -function introductory_tutorial_remove_champions (gameState) { - var units = gameState.updatingCollection( - "intro_tutorial_champions", - API3.Filters.or( - API3.Filters.byType("units/athen_champion_infantry"), - API3.Filters.or( - API3.Filters.byType("units/athen_champion_marine"), - API3.Filters.byType("units/athen_champion_ranged") - ) - ), - gameState.getOwnEntities() - ); - var cc = gameState.updatingCollection( - "intro_tutorial_cc", - API3.Filters.byType("structures/athen_civil_centre"), - gameState.getOwnEntities() - ); - - units.destroy(); -} Index: binaries/data/mods/public/simulation/ai/tutorial-ai/timer.js =================================================================== --- binaries/data/mods/public/simulation/ai/tutorial-ai/timer.js +++ /dev/null @@ -1,104 +0,0 @@ -//The Timer class // The instance of this class is created in the qBot object under the name 'timer' -//The methods that are available to call from this instance are: -//timer.setTimer : Creates a new timer with the given interval (miliseconds). -// Optionally set dalay or a limited repeat value. -//timer.checkTimer : Gives true if called at the time of the interval. -//timer.clearTimer : Deletes the timer permanently. No way to get the same timer back. -//timer.activateTimer : Sets the status of a deactivated timer to active. -//timer.deactivateTimer : Deactivates a timer. Deactivated timers will never give true. - - -//-EmjeR-// Timer class // -var Timer = function() { - ///Private array. - var alarmList = []; - - ///Private methods - function num_alarms() { - return alarmList.length; - }; - - function get_alarm(id) { - return alarmList[id]; - }; - - function add_alarm(index, alarm) { - alarmList[index] = alarm; - }; - - function delete_alarm(id) { - // Set the array element to undefined - delete alarmList[id]; - }; - - ///Privileged methods - // Add an new alarm to the list - this.setTimer = function(gameState, interval, delay, repeat) { - delay = delay || 0; - repeat = repeat || -1; - - var index = num_alarms(); - - //Add a new alarm to the list - add_alarm(index, new alarm(gameState, index, interval, delay, repeat)); - return index; - }; - - - // Check if a alarm has reached its interval. - this.checkTimer = function(gameState,id) { - var alarm = get_alarm(id); - if (alarm === undefined) - return false; - if (!alarm.active) - return false; - var time = gameState.getTimeElapsed(); - var alarmState = false; - - // If repeat forever (repeat is -1). Or if the alarm has rung less times than repeat. - if (alarm.repeat < 0 || alarm.counter < alarm.repeat) { - var time_difference = time - alarm.start_time - alarm.delay - alarm.interval * alarm.counter; - if (time_difference > alarm.interval) { - alarmState = true; - alarm.counter++; - } - } - - // Check if the alarm has rung 'alarm.repeat' times if so, delete the alarm. - if (alarm.counter >= alarm.repeat && alarm.repeat != -1) { - this.clearTimer(id); - } - - return alarmState; - }; - - // Remove an alarm from the list. - this.clearTimer = function(id) { - delete_alarm(id); - }; - - // Activate a deactivated alarm. - this.activateTimer = function(id) { - var alarm = get_alarm(id); - alarm.active = true; - }; - - // Deactivate an active alarm but don't delete it. - this.deactivateTimer = function(id) { - var alarm = get_alarm(id); - alarm.active = false; - }; -}; - - -//-EmjeR-// Alarm class // -var alarm = function(gameState, id, interval, delay, repeat) { - this.id = id; - this.interval = interval; - this.delay = delay; - this.repeat = repeat; - - this.start_time = gameState.getTimeElapsed(); - this.active = true; - this.counter = 0; -}; Index: binaries/data/mods/public/simulation/ai/tutorial-ai/tutorial.js =================================================================== --- binaries/data/mods/public/simulation/ai/tutorial-ai/tutorial.js +++ /dev/null @@ -1,204 +0,0 @@ -var TutorialAI = (function() { -var m = {}; - -m.TutorialAI = function(settings) { - API3.BaseAI.call(this, settings); - - this.turn = 0; - - this.firstTime = true; - - this.savedEvents = []; - - this.toUpdate = []; -} - -m.TutorialAI.prototype = new API3.BaseAI(); - -//Some modules need the gameState to fully initialise -m.TutorialAI.prototype.runInit = function(gameState) { - if (this.firstTime){ - this.firstTime = false; - - this.chooseTutorial(gameState); - - if (this.tutorial === undefined) return; - - this.currentPos = 0; - this.currentState = this.tutorial[this.currentPos]; - - this.lastChat = -1000000; - } -}; - -m.TutorialAI.prototype.chooseTutorial = function(gameState) { - var trees = gameState.updatingCollection("trees", API3.Filters.byClass("ForestPlant"), gameState.getEntities()); - - var numTrees = trees.length; - switch (numTrees) { - case 945: - this.tutorial = economic_walkthrough; - return; - case 221: - this.tutorial = introductoryTutorial; - return; - } - this.chat("Tutorial AI does not recognise this map, are you sure you have selected a tutorial map? (" + numTrees + " trees found)"); -}; - -m.TutorialAI.prototype.OnUpdate = function() { - if (this.gameFinished){ - return; - } - - if (this.events.length) - this.savedEvents = this.savedEvents.concat(this.events); - - Engine.ProfileStart("tutorialBot"); - - var gameState = this.gameState; - this.runInit(gameState); - - if (this.tutorial === undefined) return; - - if (gameState.getTimeElapsed() - this.lastChat > 30000){ - this.chat(this.currentState.instructions); - this.lastChat = gameState.getTimeElapsed(); - } - - // check to see if we need to change state - var nextState = this.tutorial[this.currentPos + 1]; - var doNext = false; - switch (nextState.trigger) { - case "near_cc": - var ents = gameState.getEnemyEntities(); - var CC = gameState.updatingCollection("cc", API3.Filters.and(API3.Filters.byClass("CivCentre"), API3.Filters.byOwner(2)), gameState.getEntities()); - - ents.forEach(function(ent) { - if (!ent.position()){ - return; - } - if (API3.VectorDistance(CC.toEntityArray()[0].position(), ent.position()) < 50){ - doNext = true; - } - }); - case "food_gathered": - var food = gameState.getResourceSupplies("food"); - food.forEach(function (ent) { - if (ent.resourceSupplyAmount() !== ent.resourceSupplyMax()) { - doNext = true; - } - }); - break; - case "wood_gathered": - var food = gameState.getResourceSupplies("wood"); - food.forEach(function (ent) { - if (ent.resourceSupplyAmount() != ent.resourceSupplyMax()) { - doNext = true; - } - }); - break; - case "training_start": - var trainingStructures = gameState.getEnemyEntities(); - trainingStructures.forEach(function (ent) { - if (ent.trainingQueue()) { - var queue = ent.trainingQueue(); - for (var i in queue) { - if (queue[i].unitTemplate === nextState.template && - queue[i].count === nextState.count) { - doNext = true; - } - } - } - }); - break; - case "entity_count": - var ents = gameState.updatingCollection( - nextState.template, - API3.Filters.byType(nextState.template), - gameState.getEnemyEntities()); - if (ents.length >= nextState.count) { - doNext = true; - } - break; - case "entity_counts": - doNext = true; - for (var i = 0; i < nextState.templates.length; i++) { - var ents = gameState.updatingCollection( - nextState.templates[i], - API3.Filters.byType(nextState.templates[i]), - gameState.getEnemyEntities()); - if (ents.length < nextState.counts[i]) { - doNext = false; - } - } - break; - case "dead_enemy_units": - var ents = gameState.updatingCollection(nextState.collectionId); - if (ents.length === 0) { - doNext = true; - } - break; - case"relative_time": - if (this.relativeTimeStart === undefined) { - this.relativeTimeStart = gameState.getTimeElapsed(); - } - if (this.relativeTimeStart + nextState.time * 1000 < gameState.getTimeElapsed()) { - delete this.relativeTimeStart; - doNext = true; - } - case "time": - if (nextState.time*1000 < gameState.getTimeElapsed()) { - doNext = true; - } - break; - } - if (doNext){ - this.currentPos += 1; - this.currentState = this.tutorial[this.currentPos]; - this.chat(this.currentState.instructions); - this.lastChat = gameState.getTimeElapsed(); - if (this.currentState.action) { - this.currentState.action(gameState); - } - - if (this.currentPos >= this.tutorial.length - 1){ - gameState.getOwnEntities().destroy(); - this.gameFinished = true; - } - } - - delete this.savedEvents; - this.savedEvents = []; - - Engine.ProfileStop(); - - this.turn++; -}; - -// TODO: Remove override when the whole AI state is serialised -m.TutorialAI.prototype.Deserialize = function(data) -{ - BaseAI.prototype.Deserialize.call(this, data); - this._entityMetadata = {}; -}; - -// Override the default serializer -m.TutorialAI.prototype.Serialize = function() -{ - return { - _rawEntities: this._rawEntities, - _ownEntities: this._ownEntities, - _entityMetadata: {} // We store fancy data structures in entity metadata so - //don't try and serialize it - }; -}; - -m.debug = function(output) -{ - API3.debug(output); -} - - -return m; -}());