diff --git a/binaries/data/mods/_test.sim/simulation/components/test-param.js b/binaries/data/mods/_test.sim/simulation/components/test-param.js
index de3bb1e2ca..ec3e0173ae 100644
--- a/binaries/data/mods/_test.sim/simulation/components/test-param.js
+++ b/binaries/data/mods/_test.sim/simulation/components/test-param.js
@@ -20,9 +20,9 @@ Engine.RegisterComponentType(IID_Test1, "TestScript1_Init", TestScript1_Init);
function TestScript1_readonly() {}
TestScript1_readonly.prototype.GetX = function() {
- this.template = null;
- delete this.template;
- try { this.template.x += 1000; } catch(e) { }
+ try { this.template = null; } catch(e) { }
+ try { delete this.template; } catch(e) { }
+ try { this.template.x += 1000; } catch(e) { }
try { delete this.template.x; } catch(e) { }
try { this.template.y = 2000; } catch(e) { }
return +(this.template.x || 1) + +(this.template.y || 2);
diff --git a/binaries/data/mods/_test.sim/simulation/templates/special/pathfinder.xml b/binaries/data/mods/_test.sim/simulation/data/pathfinder.xml
similarity index 100%
rename from binaries/data/mods/_test.sim/simulation/templates/special/pathfinder.xml
rename to binaries/data/mods/_test.sim/simulation/data/pathfinder.xml
diff --git a/binaries/data/mods/public/gui/aiconfig/aiconfig.js b/binaries/data/mods/public/gui/aiconfig/aiconfig.js
new file mode 100644
index 0000000000..a6a063e255
--- /dev/null
+++ b/binaries/data/mods/public/gui/aiconfig/aiconfig.js
@@ -0,0 +1,43 @@
+var g_AIs; // [ {"id": ..., "data": {"name": ..., "description": ..., ...} }, ... ]
+var g_Callback; // for the OK button
+
+function init(settings)
+{
+ g_Callback = settings.callback;
+
+ g_AIs = [
+ {id: "", data: {name: "None", description: "AI will be disabled for this player."}}
+ ].concat(settings.ais);
+
+ var aiSelection = getGUIObjectByName("aiSelection");
+ aiSelection.list = [ ai.data.name for each (ai in g_AIs) ];
+
+ var selected = 0;
+ for (var i = 0; i < g_AIs.length; ++i)
+ {
+ if (g_AIs[i].id == settings.id)
+ {
+ selected = i;
+ break;
+ }
+ }
+ aiSelection.selected = selected;
+}
+
+function selectAI(idx)
+{
+ var id = g_AIs[idx].id;
+ var name = g_AIs[idx].data.name;
+ var description = g_AIs[idx].data.description;
+
+ getGUIObjectByName("aiDescription").caption = description;
+}
+
+function returnAI()
+{
+ var aiSelection = getGUIObjectByName("aiSelection");
+ var idx = aiSelection.selected;
+ var id = g_AIs[idx].id;
+ var name = g_AIs[idx].data.name;
+ g_Callback({"id": id, "name": name});
+}
diff --git a/binaries/data/mods/public/gui/aiconfig/aiconfig.xml b/binaries/data/mods/public/gui/aiconfig/aiconfig.xml
new file mode 100644
index 0000000000..7c54e3fc62
--- /dev/null
+++ b/binaries/data/mods/public/gui/aiconfig/aiconfig.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.js b/binaries/data/mods/public/gui/gamesetup/gamesetup.js
index d27442d331..a432676a12 100644
--- a/binaries/data/mods/public/gui/gamesetup/gamesetup.js
+++ b/binaries/data/mods/public/gui/gamesetup/gamesetup.js
@@ -1,5 +1,5 @@
// Name displayed for unassigned player slots
-const NAME_UNASSIGNED = "[color=\"90 90 90 255\"][unassigned]";
+const NAME_UNASSIGNED = "[color=\"90 90 90 255\"][Computer AI]";
// Is this is a networked game, or offline
var g_IsNetworked;
@@ -24,6 +24,7 @@ var g_GameAttributes = {
BaseTerrain: "grass1_spring",
BaseHeight: 0,
PlayerData: [],
+ AIs: [], // indexed by player ID; values are AI id strings
RevealMap: false,
LockTeams: false,
GameType: "conquest"
@@ -36,6 +37,8 @@ var g_MaxPlayers = 8;
// Number of players for currently selected map
var g_NumPlayers = 0;
+var g_AIs = [];
+
var g_ChatMessages = [];
// Data caches
@@ -64,7 +67,10 @@ function init(attribs)
default:
error("Unexpected 'type' in gamesetup init: "+attribs.type);
}
-
+
+ // Load AI list
+ g_AIs = Engine.GetAIs();
+
// Get default player data - remove gaia
var pDefs = initPlayerDefaults();
pDefs.shift();
@@ -86,7 +92,7 @@ function init(attribs)
addFilter("Old Maps", function(settings) { return !settings; });
addFilter("All Maps", function(settings) { return true; });
- //Populate map filters dropdown
+ // Populate map filters dropdown
var mapFilters = getGUIObjectByName("mapFilterSelection");
mapFilters.list = getFilters();
g_GameAttributes.mapFilter = "Default";
@@ -697,7 +703,7 @@ function onGameAttributesChange()
var pTeam = getGUIObjectByName("playerTeam["+i+"]");
var pTeamText = getGUIObjectByName("playerTeamText["+i+"]");
var pColor = getGUIObjectByName("playerColour["+i+"]");
-
+
// Player data / defaults
var pData = mapSettings.PlayerData ? mapSettings.PlayerData[i] : {};
var pDefs = g_GameAttributes.settings.PlayerData ? g_GameAttributes.settings.PlayerData[i] : {};
@@ -705,7 +711,7 @@ function onGameAttributesChange()
// Common to all game types
var color = iColorToString(getSetting(pData, pDefs, "Colour"));
pColor.sprite = "colour:"+color+" 100";
- pName.caption = getSetting(pData, pDefs, "Name");
+ pName.caption = getSetting(pData, pDefs, "Name");
var team = getSetting(pData, pDefs, "Team");
var civ = getSetting(pData, pDefs, "Civ");
@@ -766,7 +772,7 @@ function launchGame()
}
// Remove extra player data
g_GameAttributes.settings.PlayerData = g_GameAttributes.settings.PlayerData.slice(0, g_NumPlayers);
-
+
Engine.StartGame(g_GameAttributes, playerID);
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": g_GameAttributes,
@@ -797,13 +803,39 @@ function updatePlayerList()
for (var i = 0; i < g_MaxPlayers; ++i)
{
+ let playerID = i+1;
+
+ var selection = (assignments[playerID] || 0);
+
+ // If no human is assigned, show the AI config button
+ var configButton = getGUIObjectByName("playerConfig["+i+"]");
+ if (selection == 0 && g_IsController)
+ {
+ configButton.hidden = false;
+ configButton.onpress = function()
+ {
+ Engine.PushGuiPage("page_aiconfig.xml", {
+ ais: g_AIs,
+ id: g_GameAttributes.settings.AIs[playerID],
+ callback: function(ai) {
+ if (ai.id == "")
+ delete g_GameAttributes.settings.AIs[playerID];
+ else
+ g_GameAttributes.settings.AIs[playerID] = ai.id;
+ }
+ });
+ };
+ }
+ else
+ {
+ configButton.hidden = true;
+ }
+
var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
assignBox.list = hostNameList;
- var selection = (assignments[i+1] || 0);
if (assignBox.selected != selection)
assignBox.selected = selection;
- let playerID = i+1;
if (g_IsNetworked && g_IsController)
{
assignBox.onselectionchange = function ()
diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.xml b/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
index 092fb6f213..5e0d60e269 100644
--- a/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
+++ b/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
@@ -103,8 +103,12 @@
+ c
-
+
@@ -188,4 +192,4 @@
-
\ No newline at end of file
+
diff --git a/binaries/data/mods/public/gui/page_aiconfig.xml b/binaries/data/mods/public/gui/page_aiconfig.xml
new file mode 100644
index 0000000000..ceb1c92dce
--- /dev/null
+++ b/binaries/data/mods/public/gui/page_aiconfig.xml
@@ -0,0 +1,7 @@
+
+
+ common/setup.xml
+ common/sprite1.xml
+ common/styles.xml
+ aiconfig/aiconfig.xml
+
diff --git a/binaries/data/mods/public/gui/session/utility_functions.js b/binaries/data/mods/public/gui/session/utility_functions.js
index 53df0f60f2..fefdab0fed 100644
--- a/binaries/data/mods/public/gui/session/utility_functions.js
+++ b/binaries/data/mods/public/gui/session/utility_functions.js
@@ -119,6 +119,17 @@ function getPlayerData(playerAssignments)
return players;
}
+function findGuidForPlayerID(playerAssignments, player)
+{
+ for (var playerGuid in playerAssignments)
+ {
+ var playerAssignment = playerAssignments[playerGuid];
+ if (playerAssignment.player == player)
+ return playerGuid;
+ }
+ return undefined;
+}
+
// Update player data when a host has connected
function updatePlayerDataAdd(players, hostGuid, playerAssignment)
{
diff --git a/binaries/data/mods/public/maps/scenarios/Combat_demo_(huge).xml b/binaries/data/mods/public/maps/scenarios/Combat_demo_(huge).xml
index 7b6bc068b4..1d0224896e 100755
--- a/binaries/data/mods/public/maps/scenarios/Combat_demo_(huge).xml
+++ b/binaries/data/mods/public/maps/scenarios/Combat_demo_(huge).xml
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f1af6b6dde1e44ef7126355bfe25a3e4ed4cdf678a12351d8d1bdf89b9e86314
-size 2860
+oid sha256:cc6a53ac292f004227ee7a7a1bfacdfe5d7f03fac42f750e4e3f4406b420751c
+size 2712
diff --git a/binaries/data/mods/public/maps/scenarios/Units_demo.xml b/binaries/data/mods/public/maps/scenarios/Units_demo.xml
index bbcbec078d..5abbcdf28a 100644
--- a/binaries/data/mods/public/maps/scenarios/Units_demo.xml
+++ b/binaries/data/mods/public/maps/scenarios/Units_demo.xml
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4b5bdfec10c985668216314a92a94a565cf1a96fa3df22657d57c4e64cc7ddc9
-size 2387
+oid sha256:057a66b494b3f695fb6a296cf15a499ee2d4343f8623457546e53ce627d962ca
+size 2391
diff --git a/binaries/data/mods/public/simulation/ai/scaredybot/data.json b/binaries/data/mods/public/simulation/ai/scaredybot/data.json
new file mode 100644
index 0000000000..cd89763b0f
--- /dev/null
+++ b/binaries/data/mods/public/simulation/ai/scaredybot/data.json
@@ -0,0 +1,5 @@
+{
+ "name": "Scaredy Bot",
+ "description": "An AI that is terrified by the mere possibility of having to fight.",
+ "constructor": "ScaredyBotAI"
+}
diff --git a/binaries/data/mods/public/simulation/ai/scaredybot/scaredybot.js b/binaries/data/mods/public/simulation/ai/scaredybot/scaredybot.js
new file mode 100644
index 0000000000..553b89ba2a
--- /dev/null
+++ b/binaries/data/mods/public/simulation/ai/scaredybot/scaredybot.js
@@ -0,0 +1,32 @@
+function ScaredyBotAI(settings)
+{
+ warn("Constructing ScaredyBotAI for player "+settings.player);
+
+ this.player = settings.player;
+ this.turn = 0;
+ this.suicideTurn = 20;
+}
+
+ScaredyBotAI.prototype.HandleMessage = function(state)
+{
+// print("### HandleMessage("+uneval(state)+")\n\n");
+// print(uneval(this)+"\n\n");
+
+ if (this.turn == 0)
+ {
+ Engine.PostCommand({"type": "chat", "message": "Good morning."});
+ }
+
+ if (this.turn == this.suicideTurn)
+ {
+ Engine.PostCommand({"type": "chat", "message": "I quake in my boots! My troops cannot hope to survive against a power such as yours."});
+
+ var myEntities = [];
+ for (var ent in state.entities)
+ if (state.entities[ent].player == this.player)
+ myEntities.push(+ent);
+ Engine.PostCommand({"type": "delete-entities", "entities": myEntities});
+ }
+
+ this.turn++;
+};
diff --git a/binaries/data/mods/public/simulation/components/AIInterface.js b/binaries/data/mods/public/simulation/components/AIInterface.js
new file mode 100644
index 0000000000..6e8ed3971f
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/AIInterface.js
@@ -0,0 +1,63 @@
+function AIInterface() {}
+
+AIInterface.prototype.Schema =
+ "";
+
+AIInterface.prototype.Init = function()
+{
+ this.events = [];
+};
+
+AIInterface.prototype.GetRepresentation = function()
+{
+ var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+
+ // Return the same game state as the GUI uses
+ var state = cmpGuiInterface.GetSimulationState(-1);
+
+ // Add some extra AI-specific data
+ state.events = this.events;
+
+ // Reset the event list for the next turn
+ this.events = [];
+
+ return state;
+};
+
+// Set up a load of event handlers to capture interesting things going on
+// in the world, which we will report to AI:
+// (This shouldn't include extremely high-frequency events, like PositionChanged,
+// because that would be very expensive and AI will rarely care about all those
+// events.)
+
+AIInterface.prototype.OnGlobalCreate = function(msg)
+{
+ this.events.push({"type": "Create", "msg": msg});
+};
+
+AIInterface.prototype.OnGlobalDestroy = function(msg)
+{
+ this.events.push({"type": "Destroy", "msg": msg});
+};
+
+AIInterface.prototype.OnGlobalOwnershipChanged = function(msg)
+{
+ this.events.push({"type": "OwnershipChanged", "msg": msg});
+};
+
+AIInterface.prototype.OnGlobalAttacked = function(msg)
+{
+ this.events.push({"type": "Attacked", "msg": msg});
+};
+
+AIInterface.prototype.OnGlobalConstructionFinished = function(msg)
+{
+ this.events.push({"type": "ConstructionFinished", "msg": msg});
+};
+
+AIInterface.prototype.OnGlobalPlayerDefeated = function(msg)
+{
+ this.events.push({"type": "PlayerDefeated", "msg": msg});
+};
+
+Engine.RegisterComponentType(IID_AIInterface, "AIInterface", AIInterface);
diff --git a/binaries/data/mods/public/simulation/components/AIProxy.js b/binaries/data/mods/public/simulation/components/AIProxy.js
new file mode 100644
index 0000000000..dedee134f2
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/AIProxy.js
@@ -0,0 +1,20 @@
+function AIProxy() {}
+
+AIProxy.prototype.Schema =
+ "";
+
+AIProxy.prototype.Init = function()
+{
+};
+
+AIProxy.prototype.GetRepresentation = function()
+{
+ // Currently we'll just return the same data that the GUI uses.
+ // Maybe we should add/remove things (or make it more efficient)
+ // later.
+
+ var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ return cmpGuiInterface.GetEntityState(-1, this.entity);
+};
+
+Engine.RegisterComponentType(IID_AIProxy, "AIProxy", AIProxy);
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
index 44bebeefd5..3fed57d5ca 100644
--- a/binaries/data/mods/public/simulation/components/GuiInterface.js
+++ b/binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -5,9 +5,16 @@ GuiInterface.prototype.Schema =
GuiInterface.prototype.Serialize = function()
{
+ // This component isn't network-synchronised so we mustn't serialise
+ // its non-deterministic data. Instead just return an empty object.
return {};
};
+GuiInterface.prototype.Deserialize = function(obj)
+{
+ this.Init();
+};
+
GuiInterface.prototype.Init = function()
{
this.placementEntity = undefined; // = undefined or [templateName, entityID]
@@ -15,10 +22,15 @@ GuiInterface.prototype.Init = function()
this.notifications = [];
};
-/**
- * All of the functions defined below are called via Engine.GuiInterfaceCall(name, arg) from GUI scripts, and executed here with arguments (player, arg)
-*/
+/*
+ * All of the functions defined below are called via Engine.GuiInterfaceCall(name, arg)
+ * from GUI scripts, and executed here with arguments (player, arg).
+ */
+/**
+ * Returns global information about the current game state.
+ * This is used by the GUI and also by AI scripts.
+ */
GuiInterface.prototype.GetSimulationState = function(player)
{
var ret = {
diff --git a/binaries/data/mods/public/simulation/templates/special/pathfinder.xml b/binaries/data/mods/public/simulation/data/pathfinder.xml
similarity index 100%
rename from binaries/data/mods/public/simulation/templates/special/pathfinder.xml
rename to binaries/data/mods/public/simulation/data/pathfinder.xml
diff --git a/binaries/data/mods/public/simulation/helpers/InitGame.js b/binaries/data/mods/public/simulation/helpers/InitGame.js
index c11061c7e2..db38df2e93 100644
--- a/binaries/data/mods/public/simulation/helpers/InitGame.js
+++ b/binaries/data/mods/public/simulation/helpers/InitGame.js
@@ -1,9 +1,14 @@
function InitGame(settings)
{
- // This will be called after the map (settings) have been loaded, before the simulation has started.
- // TODO: Is this even needed?
- if (!settings)
- settings = {};
+ // This will be called after the map settings have been loaded,
+ // before the simulation has started.
+
+ var cmpAIManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIManager);
+ for (var i = 0; i < settings.AIs.length; ++i)
+ {
+ if (settings.AIs[i])
+ cmpAIManager.AddPlayer(settings.AIs[i], i);
+ }
}
Engine.RegisterGlobal("InitGame", InitGame);
diff --git a/binaries/data/mods/public/simulation/helpers/Player.js b/binaries/data/mods/public/simulation/helpers/Player.js
index c94516ccf3..b7663f5d2f 100644
--- a/binaries/data/mods/public/simulation/helpers/Player.js
+++ b/binaries/data/mods/public/simulation/helpers/Player.js
@@ -9,12 +9,12 @@ function LoadPlayerSettings(settings)
// Default settings
if (!settings)
settings = {};
-
+
// Get default player data
var rawData = Engine.ReadJSONFile("player_defaults.json");
if (!(rawData && rawData.PlayerData))
error("Error reading player default data: player_defaults.json");
-
+
var playerDefaults = rawData.PlayerData;
// default number of players
diff --git a/binaries/data/mods/public/simulation/helpers/Setup.js b/binaries/data/mods/public/simulation/helpers/Setup.js
index 691d283c8b..1426571c2c 100644
--- a/binaries/data/mods/public/simulation/helpers/Setup.js
+++ b/binaries/data/mods/public/simulation/helpers/Setup.js
@@ -9,7 +9,6 @@ function LoadMapSettings(settings)
if (!settings)
settings = {};
-
if (settings.DefaultStance)
{
for each (var ent in Engine.GetEntitiesWithInterface(IID_UnitAI))
diff --git a/binaries/data/mods/public/simulation/templates/template_entity_full.xml b/binaries/data/mods/public/simulation/templates/template_entity_full.xml
index e115134078..52cc4797fe 100644
--- a/binaries/data/mods/public/simulation/templates/template_entity_full.xml
+++ b/binaries/data/mods/public/simulation/templates/template_entity_full.xml
@@ -1,10 +1,11 @@
-
- 0
- upright
- false
-
-
-
+
+
+
+ 0
+ upright
+ false
+
+
diff --git a/binaries/data/mods/public/simulation/templates/template_entity_quasi.xml b/binaries/data/mods/public/simulation/templates/template_entity_quasi.xml
index 5c5d1e7637..fcf0a7d16d 100644
--- a/binaries/data/mods/public/simulation/templates/template_entity_quasi.xml
+++ b/binaries/data/mods/public/simulation/templates/template_entity_quasi.xml
@@ -1,9 +1,10 @@
-
- 0
- upright
- false
-
-
+
+
+
+ 0
+ upright
+ false
+
diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
index e5f69c4acf..c3533d6464 100644
--- a/source/gui/scripting/ScriptFunctions.cpp
+++ b/source/gui/scripting/ScriptFunctions.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -40,6 +40,7 @@
#include "ps/GameSetup/Config.h"
#include "simulation2/Simulation2.h"
+#include "simulation2/components/ICmpAIManager.h"
#include "simulation2/components/ICmpCommandQueue.h"
#include "simulation2/components/ICmpGuiInterface.h"
#include "simulation2/components/ICmpRangeManager.h"
@@ -280,6 +281,13 @@ void SendNetworkChat(void* UNUSED(cbdata), std::wstring message)
g_NetClient->SendChatMessage(message);
}
+std::vector GetAIs(void* cbdata)
+{
+ CGUIManager* guiManager = static_cast (cbdata);
+
+ return ICmpAIManager::GetAIs(guiManager->GetScriptInterface());
+}
+
void OpenURL(void* UNUSED(cbdata), std::string url)
{
sys_open_url(url);
@@ -426,6 +434,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction("SetNetworkGameAttributes");
scriptInterface.RegisterFunction("AssignNetworkPlayer");
scriptInterface.RegisterFunction("SendNetworkChat");
+ scriptInterface.RegisterFunction, &GetAIs>("GetAIs");
// Misc functions
scriptInterface.RegisterFunction("SetCursor");
diff --git a/source/ps/Game.cpp b/source/ps/Game.cpp
index b7b3f2f354..86e9255300 100644
--- a/source/ps/Game.cpp
+++ b/source/ps/Game.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -15,12 +15,6 @@
* along with 0 A.D. If not, see .
*/
-/**
- * File : Game.cpp
- * Project : engine
- * Description : Contains the CGame Class implementation.
- *
- **/
#include "precompiled.h"
#include "Game.h"
@@ -78,11 +72,6 @@ CGame::CGame(bool disableGraphics):
m_TurnManager = new CNetLocalTurnManager(*m_Simulation2, GetReplayLogger()); // this will get replaced if we're a net server/client
m_Simulation2->LoadDefaultScripts();
- m_Simulation2->ResetState();
-
- // TODO: If this function is even needed now, get initData
- CScriptVal initData;
- m_Simulation2->InitGame(initData);
}
/**
@@ -91,6 +80,9 @@ CGame::CGame(bool disableGraphics):
**/
CGame::~CGame()
{
+ // Clear rooted value before destroying its context
+ m_RegisteredAttribs = CScriptValRooted();
+
// Again, the in-game call tree is going to be different to the main menu one.
if (CProfileManager::IsInitialised())
g_Profiler.StructuralReset();
@@ -121,6 +113,8 @@ void CGame::SetTurnManager(CNetTurnManager* turnManager)
**/
void CGame::RegisterInit(const CScriptValRooted& attribs)
{
+ m_RegisteredAttribs = attribs; // save the attributes for ReallyStartGame
+
std::string mapType;
m_Simulation2->GetScriptInterface().GetProperty(attribs.get(), "mapType", mapType);
@@ -147,7 +141,6 @@ void CGame::RegisterInit(const CScriptValRooted& attribs)
// TODO: Coming in another patch
}
-
LDR_EndRegistering();
}
@@ -158,6 +151,10 @@ void CGame::RegisterInit(const CScriptValRooted& attribs)
**/
PSRETURN CGame::ReallyStartGame()
{
+ CScriptVal settings;
+ m_Simulation2->GetScriptInterface().GetProperty(m_RegisteredAttribs.get(), "settings", settings);
+ m_Simulation2->InitGame(settings);
+
// Call the reallyStartGame GUI function, but only if it exists
if (g_GUI && g_GUI->HasPages())
{
diff --git a/source/ps/Game.h b/source/ps/Game.h
index 5d0f4cdd07..082787b0a4 100644
--- a/source/ps/Game.h
+++ b/source/ps/Game.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -15,23 +15,18 @@
* along with 0 A.D. If not, see .
*/
-/**
- * File : Game.h
- * Project : engine
- * Description : Contains the CGame Class which is a representation of the game itself.
- *
- **/
#ifndef INCLUDED_GAME
#define INCLUDED_GAME
#include "ps/Errors.h"
#include
+#include "scriptinterface/ScriptVal.h"
+
class CWorld;
class CSimulation2;
class CGameView;
class CNetTurnManager;
-class CScriptValRooted;
class IReplayLogger;
struct CColor;
@@ -159,6 +154,7 @@ public:
private:
void RegisterInit(const CScriptValRooted& attribs);
IReplayLogger* m_ReplayLogger;
+ CScriptValRooted m_RegisteredAttribs;
};
extern CGame *g_Game;
diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp
index d7ba6ceaf9..5709ac303b 100644
--- a/source/ps/GameSetup/GameSetup.cpp
+++ b/source/ps/GameSetup/GameSetup.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -915,6 +915,32 @@ static bool Autostart(const CmdLineArgs& args)
scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"), false);
scriptInterface.SetProperty(attrs.get(), "map", std::string(autostartMap), false);
+ // Set attrs.settings = { AIs: [...] }:
+
+ /*
+ * Handle command-line options for AI:
+ * -autostart-ai=1:dummybot -autostart-ai=2:dummybot -- adds the dummybot AI to players 1 and 2
+ */
+ CScriptValRooted ais;
+ scriptInterface.Eval("([])", ais);
+ if (args.Has("autostart-ai"))
+ {
+ std::vector aiArgs = args.GetMultiple("autostart-ai");
+ for (size_t i = 0; i < aiArgs.size(); ++i)
+ {
+ int player = aiArgs[i].BeforeFirst(":").ToInt();
+ CStr name = aiArgs[i].AfterFirst(":");
+ scriptInterface.SetPropertyInt(ais.get(), player, std::string(name), false);
+ }
+ }
+
+ CScriptValRooted settings;
+ scriptInterface.Eval("({})", settings);
+ scriptInterface.SetProperty(settings.get(), "AIs", ais, false);
+ scriptInterface.SetProperty(attrs.get(), "settings", settings, false);
+
+
+
CScriptValRooted mpInitData;
g_GUI->GetScriptInterface().Eval("({isNetworked:true, playerAssignments:{}})", mpInitData);
g_GUI->GetScriptInterface().SetProperty(mpInitData.get(), "attribs",
diff --git a/source/scriptinterface/ScriptConversions.cpp b/source/scriptinterface/ScriptConversions.cpp
index 66e86a5a8f..8e291b7796 100644
--- a/source/scriptinterface/ScriptConversions.cpp
+++ b/source/scriptinterface/ScriptConversions.cpp
@@ -244,27 +244,20 @@ template static bool FromJSVal_vector(JSContext* cx, jsval v, std::v
return true;
}
-template<> jsval ScriptInterface::ToJSVal >(JSContext* cx, const std::vector& val)
-{
- return ToJSVal_vector(cx, val);
-}
+// Instantiate various vector types:
-template<> jsval ScriptInterface::ToJSVal >(JSContext* cx, const std::vector& val)
-{
- return ToJSVal_vector(cx, val);
-}
+#define VECTOR(T) \
+ template<> jsval ScriptInterface::ToJSVal >(JSContext* cx, const std::vector& val) \
+ { \
+ return ToJSVal_vector(cx, val); \
+ } \
+ template<> bool ScriptInterface::FromJSVal >(JSContext* cx, jsval v, std::vector& out) \
+ { \
+ return FromJSVal_vector(cx, v, out); \
+ }
-template<> jsval ScriptInterface::ToJSVal >(JSContext* cx, const std::vector& val)
-{
- return ToJSVal_vector(cx, val);
-}
-
-template<> bool ScriptInterface::FromJSVal >(JSContext* cx, jsval v, std::vector& out)
-{
- return FromJSVal_vector(cx, v, out);
-}
-
-template<> bool ScriptInterface::FromJSVal >(JSContext* cx, jsval v, std::vector& out)
-{
- return FromJSVal_vector(cx, v, out);
-}
+VECTOR(int)
+VECTOR(u32)
+VECTOR(std::string)
+VECTOR(std::wstring)
+VECTOR(CScriptValRooted)
diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp
index dc9d8dac05..aa209cfa3f 100644
--- a/source/scriptinterface/ScriptInterface.cpp
+++ b/source/scriptinterface/ScriptInterface.cpp
@@ -24,6 +24,7 @@
#include "lib/debug.h"
#include "lib/utf8.h"
#include "ps/CLogger.h"
+#include "ps/Filesystem.h"
#include "ps/Profile.h"
#include "ps/utf16string.h"
@@ -402,7 +403,7 @@ AutoGCRooter* ScriptInterface::ReplaceAutoGCRooter(AutoGCRooter* rooter)
}
-jsval ScriptInterface::CallConstructor(jsval ctor)
+jsval ScriptInterface::CallConstructor(jsval ctor, jsval arg)
{
if (!JSVAL_IS_OBJECT(ctor))
{
@@ -410,7 +411,43 @@ jsval ScriptInterface::CallConstructor(jsval ctor)
return JSVAL_VOID;
}
- return OBJECT_TO_JSVAL(JS_New(m->m_cx, JSVAL_TO_OBJECT(ctor), 0, NULL));
+ return OBJECT_TO_JSVAL(JS_New(m->m_cx, JSVAL_TO_OBJECT(ctor), 1, &arg));
+}
+
+jsval ScriptInterface::NewObjectFromConstructor(jsval ctor)
+{
+ // Get the constructor's prototype
+ // (Can't use JS_GetPrototype, since we want .prototype not .__proto__)
+ jsval protoVal;
+ if (!JS_GetProperty(m->m_cx, JSVAL_TO_OBJECT(ctor), "prototype", &protoVal))
+ {
+ LOGERROR(L"NewObjectFromConstructor: can't get prototype");
+ return JSVAL_VOID;
+ }
+
+ if (!JSVAL_IS_OBJECT(protoVal))
+ {
+ LOGERROR(L"NewObjectFromConstructor: prototype is not an object");
+ return JSVAL_VOID;
+ }
+
+ JSObject* proto = JSVAL_TO_OBJECT(protoVal);
+ JSObject* parent = JS_GetParent(m->m_cx, JSVAL_TO_OBJECT(ctor));
+ // TODO: rooting?
+ if (!proto || !parent)
+ {
+ LOGERROR(L"NewObjectFromConstructor: null proto/parent");
+ return JSVAL_VOID;
+ }
+
+ JSObject* obj = JS_NewObject(m->m_cx, NULL, proto, parent);
+ if (!obj)
+ {
+ LOGERROR(L"NewObjectFromConstructor: object creation failed");
+ return JSVAL_VOID;
+ }
+
+ return OBJECT_TO_JSVAL(obj);
}
bool ScriptInterface::CallFunctionVoid(jsval val, const char* name)
@@ -465,13 +502,13 @@ bool ScriptInterface::SetGlobal_(const char* name, jsval value, bool replace)
return ok ? true : false;
}
-bool ScriptInterface::SetProperty_(jsval obj, const char* name, jsval value, bool constant)
+bool ScriptInterface::SetProperty_(jsval obj, const char* name, jsval value, bool constant, bool enumerate)
{
- uintN attrs;
+ uintN attrs = 0;
if (constant)
- attrs = JSPROP_READONLY | JSPROP_PERMANENT;
- else
- attrs = JSPROP_ENUMERATE;
+ attrs |= JSPROP_READONLY | JSPROP_PERMANENT;
+ if (enumerate)
+ attrs |= JSPROP_ENUMERATE;
if (! JSVAL_IS_OBJECT(obj))
return false;
@@ -482,6 +519,23 @@ bool ScriptInterface::SetProperty_(jsval obj, const char* name, jsval value, boo
return true;
}
+bool ScriptInterface::SetPropertyInt_(jsval obj, int name, jsval value, bool constant, bool enumerate)
+{
+ uintN attrs = 0;
+ if (constant)
+ attrs |= JSPROP_READONLY | JSPROP_PERMANENT;
+ if (enumerate)
+ attrs |= JSPROP_ENUMERATE;
+
+ if (! JSVAL_IS_OBJECT(obj))
+ return false;
+ JSObject* object = JSVAL_TO_OBJECT(obj);
+
+ if (! JS_DefinePropertyById(m->m_cx, object, INT_TO_JSID(name), value, NULL, NULL, attrs))
+ return false;
+ return true;
+}
+
bool ScriptInterface::GetProperty_(jsval obj, const char* name, jsval& out)
{
if (! JSVAL_IS_OBJECT(obj))
@@ -552,6 +606,17 @@ bool ScriptInterface::SetPrototype(jsval obj, jsval proto)
return JS_SetPrototype(m->m_cx, JSVAL_TO_OBJECT(obj), JSVAL_TO_OBJECT(proto)) ? true : false;
}
+bool ScriptInterface::FreezeObject(jsval obj, bool deep)
+{
+ if (!JSVAL_IS_OBJECT(obj))
+ return false;
+
+ if (deep)
+ return JS_DeepFreezeObject(m->m_cx, JSVAL_TO_OBJECT(obj));
+ else
+ return JS_FreezeObject(m->m_cx, JSVAL_TO_OBJECT(obj));
+}
+
bool ScriptInterface::LoadScript(const std::wstring& filename, const std::wstring& code)
{
std::string fnAscii(filename.begin(), filename.end());
@@ -572,6 +637,41 @@ bool ScriptInterface::LoadScript(const std::wstring& filename, const std::wstrin
return ok ? true : false;
}
+bool ScriptInterface::LoadGlobalScriptFile(const VfsPath& path)
+{
+ if (!FileExists(g_VFS, path))
+ {
+ LOGERROR(L"File '%ls' does not exist", path.string().c_str());
+ return false;
+ }
+
+ CVFSFile file;
+
+ PSRETURN ret = file.Load(g_VFS, path);
+
+ if (ret != PSRETURN_OK)
+ {
+ LOGERROR(L"Failed to load file '%ls': %hs", path.string().c_str(), GetErrorString(ret));
+ return false;
+ }
+
+ std::string content(file.GetBuffer(), file.GetBuffer() + file.GetBufferSize());
+ std::wstring code = wstring_from_utf8(content);
+
+ std::string fnAscii(path.string().begin(), path.string().end());
+
+ // Compile the code in strict mode, to encourage better coding practices and
+ // to possibly help SpiderMonkey with optimisations
+ std::wstring codeStrict = L"\"use strict\";\n" + code;
+ utf16string codeUtf16(codeStrict.begin(), codeStrict.end());
+ uintN lineNo = 0; // put the automatic 'use strict' on line 0, so the real code starts at line 1
+
+ jsval rval;
+ JSBool ok = JS_EvaluateUCScript(m->m_cx, m->m_glob, reinterpret_cast (codeUtf16.c_str()), (uintN)(codeUtf16.length()), fnAscii.c_str(), lineNo, &rval);
+ return ok ? true : false;
+}
+
+
bool ScriptInterface::Eval(const char* code)
{
jsval rval;
@@ -635,6 +735,29 @@ CScriptValRooted ScriptInterface::ParseJSON(const std::string& string_utf8)
return ParseJSON(utf16string(attrsW.begin(), attrsW.end()));
}
+CScriptValRooted ScriptInterface::ReadJSONFile(const VfsPath& path)
+{
+ if (!FileExists(g_VFS, path))
+ {
+ LOGERROR(L"File '%ls' does not exist", path.string().c_str());
+ return CScriptValRooted();
+ }
+
+ CVFSFile file;
+
+ PSRETURN ret = file.Load(g_VFS, path);
+
+ if (ret != PSRETURN_OK)
+ {
+ LOGERROR(L"Failed to load file '%ls': %hs", path.string().c_str(), GetErrorString(ret));
+ return CScriptValRooted();
+ }
+
+ std::string content(file.GetBuffer(), file.GetBuffer() + file.GetBufferSize()); // assume it's UTF-8
+
+ return ParseJSON(content);
+}
+
struct Stringifier
{
static JSBool callback(const jschar* buf, uint32 len, void* data)
diff --git a/source/scriptinterface/ScriptInterface.h b/source/scriptinterface/ScriptInterface.h
index fb99a61459..6fc6471f22 100644
--- a/source/scriptinterface/ScriptInterface.h
+++ b/source/scriptinterface/ScriptInterface.h
@@ -27,6 +27,7 @@
#include "js/jsapi.h"
+#include "lib/file/vfs/vfs_path.h"
#include "ps/Profile.h"
#include "ps/utf16string.h"
@@ -85,11 +86,17 @@ public:
void ReplaceNondeterministicFunctions(boost::rand48& rng);
/**
- * Call a constructor function, roughly equivalent to JS "new ctor".
- *
- * @return The new object; or 0 on failure, and logs an error message
+ * Call a constructor function, equivalent to JS "new ctor(arg)".
+ * @return The new object; or JSVAL_VOID on failure, and logs an error message
*/
- jsval CallConstructor(jsval ctor);
+ jsval CallConstructor(jsval ctor, jsval arg);
+
+ /**
+ * Create an object as with CallConstructor except don't actually execute the
+ * constructor function.
+ * @return The new object; or JSVAL_VOID on failure, and logs an error message
+ */
+ jsval NewObjectFromConstructor(jsval ctor);
/**
* Call the named property on the given object, with void return type and 0 arguments
@@ -138,6 +145,12 @@ public:
template
bool CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2, R& ret);
+ /**
+ * Call the named property on the given object, with return type R and 4 arguments
+ */
+ template
+ bool CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2, const T3& a3, R& ret);
+
jsval GetGlobalObject();
JSClass* GetGlobalClass();
@@ -155,7 +168,14 @@ public:
* Optionally makes it {ReadOnly, DontDelete, DontEnum}.
*/
template
- bool SetProperty(jsval obj, const char* name, const T& value, bool constant);
+ bool SetProperty(jsval obj, const char* name, const T& value, bool constant, bool enumerate = true);
+
+ /**
+ * Set the integer-named property on the given object.
+ * Optionally makes it {ReadOnly, DontDelete, DontEnum}.
+ */
+ template
+ bool SetPropertyInt(jsval obj, int name, const T& value, bool constant, bool enumerate = true);
template
bool GetProperty(jsval obj, const char* name, T& out);
@@ -166,6 +186,8 @@ public:
bool SetPrototype(jsval obj, jsval proto);
+ bool FreezeObject(jsval obj, bool deep);
+
bool Eval(const char* code);
template bool Eval(const CHAR* code, T& out);
@@ -182,6 +204,11 @@ public:
*/
CScriptValRooted ParseJSON(const std::string& string_utf8);
+ /**
+ * Read a JSON file. Returns the undefined value on error.
+ */
+ CScriptValRooted ReadJSONFile(const VfsPath& path);
+
/**
* Stringify to a JSON string, UTF-8 encoded. Returns an empty string on error.
*/
@@ -202,6 +229,12 @@ public:
*/
bool LoadScript(const std::wstring& filename, const std::wstring& code);
+ /**
+ * Load and execute the given script in the global scope.
+ * @return true on successful compilation and execution; false otherwise
+ */
+ bool LoadGlobalScriptFile(const VfsPath& path);
+
/**
* Construct a new value (usable in this ScriptInterface's context) by cloning
* a value from a different context.
@@ -235,7 +268,8 @@ private:
bool Eval_(const char* code, jsval& ret);
bool Eval_(const wchar_t* code, jsval& ret);
bool SetGlobal_(const char* name, jsval value, bool replace);
- bool SetProperty_(jsval obj, const char* name, jsval value, bool readonly);
+ bool SetProperty_(jsval obj, const char* name, jsval value, bool readonly, bool enumerate);
+ bool SetPropertyInt_(jsval obj, int name, jsval value, bool readonly, bool enumerate);
bool GetProperty_(jsval obj, const char* name, jsval& value);
static bool IsExceptionPending(JSContext* cx);
static JSClass* GetClass(JSContext* cx, JSObject* obj);
@@ -344,6 +378,21 @@ bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, co
return FromJSVal(GetContext(), jsRet, ret);
}
+template
+bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2, const T3& a3, R& ret)
+{
+ jsval jsRet;
+ jsval argv[4];
+ argv[0] = ToJSVal(GetContext(), a0);
+ argv[1] = ToJSVal(GetContext(), a1);
+ argv[2] = ToJSVal(GetContext(), a2);
+ argv[3] = ToJSVal(GetContext(), a3);
+ bool ok = CallFunction_(val, name, 4, argv, jsRet);
+ if (!ok)
+ return false;
+ return FromJSVal(GetContext(), jsRet, ret);
+}
+
template
bool ScriptInterface::SetGlobal(const char* name, const T& value, bool replace)
{
@@ -351,9 +400,15 @@ bool ScriptInterface::SetGlobal(const char* name, const T& value, bool replace)
}
template
-bool ScriptInterface::SetProperty(jsval obj, const char* name, const T& value, bool readonly)
+bool ScriptInterface::SetProperty(jsval obj, const char* name, const T& value, bool readonly, bool enumerate)
{
- return SetProperty_(obj, name, ToJSVal(GetContext(), value), readonly);
+ return SetProperty_(obj, name, ToJSVal(GetContext(), value), readonly, enumerate);
+}
+
+template
+bool ScriptInterface::SetPropertyInt(jsval obj, int name, const T& value, bool readonly, bool enumerate)
+{
+ return SetPropertyInt_(obj, name, ToJSVal(GetContext(), value), readonly, enumerate);
}
template
diff --git a/source/scriptinterface/ScriptVal.h b/source/scriptinterface/ScriptVal.h
index 5ac1e452c3..0efa57e322 100644
--- a/source/scriptinterface/ScriptVal.h
+++ b/source/scriptinterface/ScriptVal.h
@@ -32,8 +32,16 @@ public:
CScriptVal() : m_Val(JSVAL_VOID) { }
CScriptVal(jsval val) : m_Val(val) { }
+ /**
+ * Returns the current value.
+ */
const jsval& get() const { return m_Val; }
+ /**
+ * Returns whether the value is JSVAL_VOID.
+ */
+ bool undefined() const { return JSVAL_IS_VOID(m_Val); }
+
private:
jsval m_Val;
};
diff --git a/source/simulation2/Simulation2.cpp b/source/simulation2/Simulation2.cpp
index 7f0fb43737..a14c00707b 100644
--- a/source/simulation2/Simulation2.cpp
+++ b/source/simulation2/Simulation2.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -23,8 +23,9 @@
#include "simulation2/system/ComponentManager.h"
#include "simulation2/system/ParamNode.h"
#include "simulation2/system/SimContext.h"
-#include "simulation2/components/ICmpTemplateManager.h"
+#include "simulation2/components/ICmpAIManager.h"
#include "simulation2/components/ICmpCommandQueue.h"
+#include "simulation2/components/ICmpTemplateManager.h"
#include "lib/file/file_system_util.h"
#include "lib/utf8.h"
@@ -55,8 +56,6 @@ public:
RegisterFileReloadFunc(ReloadChangedFileCB, this);
// m_EnableOOSLog = true; // TODO: this should be a command-line flag or similar
-
- // (can't call ResetState here since the scripts haven't been loaded yet)
}
~CSimulation2Impl()
@@ -64,7 +63,7 @@ public:
UnregisterFileReloadFunc(ReloadChangedFileCB, this);
}
- void ResetState(bool skipScriptedComponents)
+ void ResetState(bool skipScriptedComponents, bool skipAI)
{
m_ComponentManager.ResetState();
@@ -87,6 +86,11 @@ public:
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Terrain, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_WaterManager, noParam);
+ if (!skipAI)
+ {
+ m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_AIManager, noParam);
+ }
+
// Add scripted system components:
if (!skipScriptedComponents)
{
@@ -96,10 +100,11 @@ public:
LOGERROR(L"Can't find component type " L##name); \
m_ComponentManager.AddComponent(SYSTEM_ENTITY, cid, noParam)
+ LOAD_SCRIPTED_COMPONENT("AIInterface");
+ LOAD_SCRIPTED_COMPONENT("EndGameManager");
LOAD_SCRIPTED_COMPONENT("GuiInterface");
LOAD_SCRIPTED_COMPONENT("PlayerManager");
LOAD_SCRIPTED_COMPONENT("Timer");
- LOAD_SCRIPTED_COMPONENT("EndGameManager");
#undef LOAD_SCRIPTED_COMPONENT
}
@@ -186,6 +191,11 @@ bool CSimulation2Impl::Update(int turnLength, const std::vectorFinishAsyncRequests();
+ // Push AI commands onto the queue before we use them
+ CmpPtr cmpAIManager(m_SimContext, SYSTEM_ENTITY);
+ if (!cmpAIManager.null())
+ cmpAIManager->PushCommands();
+
CmpPtr cmpCommandQueue(m_SimContext, SYSTEM_ENTITY);
if (!cmpCommandQueue.null())
cmpCommandQueue->FlushTurn(commands);
@@ -223,6 +233,10 @@ bool CSimulation2Impl::Update(int turnLength, const std::vectorStartComputation();
+
++m_TurnNumber;
return true; // TODO: don't bother with bool return
@@ -429,9 +443,9 @@ LibError CSimulation2::ReloadChangedFile(const VfsPath& path)
return m->ReloadChangedFile(path);
}
-void CSimulation2::ResetState(bool skipGui)
+void CSimulation2::ResetState(bool skipScriptedComponents, bool skipAI)
{
- m->ResetState(skipGui);
+ m->ResetState(skipScriptedComponents, skipAI);
}
bool CSimulation2::ComputeStateHash(std::string& outHash)
diff --git a/source/simulation2/Simulation2.h b/source/simulation2/Simulation2.h
index b3fd2098f7..c842976295 100644
--- a/source/simulation2/Simulation2.h
+++ b/source/simulation2/Simulation2.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -115,8 +115,11 @@ public:
* before any methods that depend on the simulation state.
* @param skipScriptedComponents don't load the scripted system components
* (this is intended for use by test cases that don't mount all of VFS)
+ * @param skipAI don't initialise the AI system
+ * (this is intended for use by test cases that don't want all entity
+ * templates loaded automatically)
*/
- void ResetState(bool skipScriptedComponents = false);
+ void ResetState(bool skipScriptedComponents = false, bool skipAI = false);
/**
* Initialise a new game, based on some script data. (Called on CGame instantiation)
diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h
index cc08c6eb83..00af7da37a 100644
--- a/source/simulation2/TypeList.h
+++ b/source/simulation2/TypeList.h
@@ -57,6 +57,15 @@ COMPONENT(UnknownScript)
// In alphabetical order:
+INTERFACE(AIInterface)
+COMPONENT(AIInterfaceScripted)
+
+INTERFACE(AIManager)
+COMPONENT(AIManager)
+
+INTERFACE(AIProxy)
+COMPONENT(AIProxyScripted)
+
INTERFACE(CommandQueue)
COMPONENT(CommandQueue)
diff --git a/source/simulation2/components/CCmpAIManager.cpp b/source/simulation2/components/CCmpAIManager.cpp
new file mode 100644
index 0000000000..6b67f1e36b
--- /dev/null
+++ b/source/simulation2/components/CCmpAIManager.cpp
@@ -0,0 +1,511 @@
+/* Copyright (C) 2011 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "simulation2/system/Component.h"
+#include "ICmpAIManager.h"
+
+#include "lib/timer.h"
+#include "ps/CLogger.h"
+#include "ps/Filesystem.h"
+#include "simulation2/components/ICmpAIInterface.h"
+#include "simulation2/components/ICmpAIProxy.h"
+#include "simulation2/components/ICmpCommandQueue.h"
+#include "simulation2/components/ICmpTemplateManager.h"
+#include "simulation2/serialization/DebugSerializer.h"
+#include "simulation2/serialization/StdDeserializer.h"
+#include "simulation2/serialization/StdSerializer.h"
+#include "simulation2/serialization/SerializeTemplates.h"
+
+/**
+ * @file Player AI interface.
+ *
+ * AI is primarily scripted, and the CCmpAIManager component defined here
+ * takes care of managing all the scripts.
+ *
+ * To avoid slow AI scripts causing jerky rendering, they are run in a background
+ * thread (maintained by CAIWorker) so that it's okay if they take a whole simulation
+ * turn before returning their results (though preferably they shouldn't use nearly
+ * that much CPU).
+ *
+ * CCmpAIManager grabs the world state after each turn (making use of AIInterface.js
+ * and AIProxy.js to decide what data to include) then passes it to CAIWorker.
+ * The AI scripts will then run asynchronously and return a list of commands to execute.
+ * Any attempts to read the command list (including indirectly via serialization)
+ * will block until it's actually completed, so the rest of the engine should avoid
+ * reading it for as long as possible.
+ *
+ * TODO: actually the thread isn't implemented yet, because performance hasn't been
+ * sufficiently problematic to justify the complexity yet, but the CAIWorker interface
+ * is designed to hopefully support threading when we want it.
+ */
+
+class CAIWorker
+{
+private:
+ struct SAIPlayer
+ {
+ std::wstring aiName;
+ player_id_t player;
+ CScriptValRooted obj;
+ };
+
+ struct SCommands
+ {
+ player_id_t player;
+ std::vector commands;
+ };
+
+public:
+ struct SReturnedCommands
+ {
+ player_id_t player;
+ std::vector commands;
+ };
+
+ CAIWorker() :
+ m_ScriptInterface("Engine", "AI"),
+ m_CommandsComputed(true),
+ m_CurrentlyComputingPlayer(-1)
+ {
+ m_ScriptInterface.SetCallbackData(static_cast (this));
+
+ // TODO: ought to seed the RNG (in a network-synchronised way) before we use it
+ m_ScriptInterface.ReplaceNondeterministicFunctions(m_RNG);
+
+ m_ScriptInterface.RegisterFunction("PostCommand");
+ }
+
+ ~CAIWorker()
+ {
+ // Clear rooted script values before destructing the script interface
+ m_EntityTemplates = CScriptValRooted();
+ m_PlayerMetadata.clear();
+ m_Players.clear();
+ m_Commands.clear();
+ }
+
+ bool AddPlayer(const std::wstring& aiName, player_id_t player, bool callConstructor)
+ {
+ std::wstring path = L"simulation/ai/" + aiName + L"/data.json";
+ CScriptValRooted metadata = LoadPlayerFiles(aiName, path);
+ if (metadata.uninitialised())
+ {
+ LOGERROR(L"Failed to create AI player: can't find %ls", path.c_str());
+ return false;
+ }
+
+ // Get the constructor name from the metadata
+ std::string constructor;
+ if (!m_ScriptInterface.GetProperty(metadata.get(), "constructor", constructor))
+ {
+ LOGERROR(L"Failed to create AI player: %ls: missing 'constructor'", path.c_str());
+ return false;
+ }
+
+ // Get the constructor function from the loaded scripts
+ CScriptVal ctor;
+ if (!m_ScriptInterface.GetProperty(m_ScriptInterface.GetGlobalObject(), constructor.c_str(), ctor)
+ || ctor.undefined())
+ {
+ LOGERROR(L"Failed to create AI player: %ls: can't find constructor '%hs'", path.c_str(), constructor.c_str());
+ return false;
+ }
+
+ CScriptVal obj;
+
+ if (callConstructor)
+ {
+ // Set up the data to pass as the constructor argument
+ CScriptVal settings;
+ m_ScriptInterface.Eval(L"({})", settings);
+ m_ScriptInterface.SetProperty(settings.get(), "player", player, false);
+ m_ScriptInterface.SetProperty(settings.get(), "templates", m_EntityTemplates, false);
+
+ obj = m_ScriptInterface.CallConstructor(ctor.get(), settings.get());
+ }
+ else
+ {
+ // For deserialization, we want to create the object with the correct prototype
+ // but don't want to actually run the constructor again
+ obj = m_ScriptInterface.NewObjectFromConstructor(ctor.get());
+ }
+
+ if (obj.undefined())
+ {
+ LOGERROR(L"Failed to create AI player: %ls: error calling constructor '%hs'", path.c_str(), constructor.c_str());
+ return false;
+ }
+
+ SAIPlayer ai;
+ ai.aiName = aiName;
+ ai.player = player;
+ ai.obj = CScriptValRooted(m_ScriptInterface.GetContext(), obj);
+ m_Players.push_back(ai);
+ return true;
+ }
+
+ void StartComputation(const std::string& gameState)
+ {
+ debug_assert(m_CommandsComputed);
+
+ m_GameState = gameState;
+
+ m_CommandsComputed = false;
+ }
+
+ void WaitToFinishComputation()
+ {
+ if (!m_CommandsComputed)
+ {
+ PerformComputation();
+ m_CommandsComputed = true;
+ }
+ }
+
+ void GetCommands(std::vector& commands)
+ {
+ WaitToFinishComputation();
+
+ commands.clear();
+ commands.resize(m_Commands.size());
+ for (size_t i = 0; i < m_Commands.size(); ++i)
+ {
+ commands[i].player = m_Commands[i].player;
+ commands[i].commands.resize(m_Commands[i].commands.size());
+ for (size_t j = 0; j < m_Commands[i].commands.size(); ++j)
+ {
+ // Serialize the returned command, so that it's safe to transfer
+ // across threads (in the future when we actually run AI in a
+ // background thread)
+ std::stringstream stream;
+ CStdSerializer serializer(m_ScriptInterface, stream);
+ serializer.ScriptVal("command", m_Commands[i].commands[j]);
+ commands[i].commands[j] = stream.str();
+ }
+ }
+ }
+
+ void LoadEntityTemplates(const std::vector >& templates)
+ {
+ m_ScriptInterface.Eval("({})", m_EntityTemplates);
+
+ for (size_t i = 0; i < templates.size(); ++i)
+ {
+ jsval val = templates[i].second->ToJSVal(m_ScriptInterface.GetContext(), false);
+ m_ScriptInterface.SetProperty(m_EntityTemplates.get(), templates[i].first.c_str(), CScriptVal(val), true);
+ }
+
+ // Since the template data is shared between AI players, freeze it
+ // to stop any of them changing it and confusing the other players
+ m_ScriptInterface.FreezeObject(m_EntityTemplates.get(), true);
+ }
+
+ void Serialize(std::ostream& stream, bool isDebug)
+ {
+ WaitToFinishComputation();
+
+ if (isDebug)
+ {
+ CDebugSerializer serializer(m_ScriptInterface, stream);
+ serializer.Indent(4);
+ SerializeState(serializer);
+ }
+ else
+ {
+ CStdSerializer serializer(m_ScriptInterface, stream);
+ SerializeState(serializer);
+ }
+ }
+
+ void SerializeState(ISerializer& serializer)
+ {
+ serializer.NumberU32_Unbounded("num ais", m_Players.size());
+
+ for (size_t i = 0; i < m_Players.size(); ++i)
+ {
+ serializer.String("name", m_Players[i].aiName, 0, 256);
+ serializer.NumberI32_Unbounded("player", m_Players[i].player);
+ serializer.ScriptVal("data", m_Players[i].obj);
+ }
+
+ serializer.NumberU32_Unbounded("num ai commands", m_Commands.size());
+
+ for (size_t i = 0; i < m_Commands.size(); ++i)
+ {
+ serializer.NumberI32_Unbounded("player", m_Commands[i].player);
+ SerializeVector()(serializer, "commands", m_Commands[i].commands);
+ }
+ }
+
+ void Deserialize(std::istream& stream)
+ {
+ debug_assert(m_CommandsComputed); // deserializing while we're still actively computing would be bad
+
+ CStdDeserializer deserializer(m_ScriptInterface, stream);
+
+ m_PlayerMetadata.clear();
+ m_Players.clear();
+ m_Commands.clear();
+
+ uint32_t numAis;
+ deserializer.NumberU32_Unbounded("num ais", numAis);
+
+ for (size_t i = 0; i < numAis; ++i)
+ {
+ std::wstring name;
+ player_id_t player;
+ deserializer.String("name", name, 0, 256);
+ deserializer.NumberI32_Unbounded("player", player);
+ if (!AddPlayer(name, player, false))
+ throw PSERROR_Deserialize_ScriptError();
+
+ // Use ScriptObjectAppend so we don't lose the carefully-constructed
+ // prototype/parent of this object
+ deserializer.ScriptObjectAppend("data", m_Players.back().obj.getRef());
+ }
+
+ uint32_t numCommands;
+ deserializer.NumberU32_Unbounded("num ai commands", numCommands);
+
+ m_Commands.resize(numCommands);
+ for (size_t i = 0; i < numCommands; ++i)
+ {
+ deserializer.NumberI32_Unbounded("player", m_Commands[i].player);
+ SerializeVector()(deserializer, "commands", m_Commands[i].commands);
+ }
+ }
+
+private:
+ CScriptValRooted LoadPlayerFiles(const std::wstring& aiName, const std::wstring& path)
+ {
+ if (m_PlayerMetadata.find(path) == m_PlayerMetadata.end())
+ {
+ // Load and cache the AI player metadata
+ m_PlayerMetadata[path] = m_ScriptInterface.ReadJSONFile(path);
+
+ // TODO: includes
+
+ // Load and execute *.js
+ VfsPaths pathnames;
+ fs_util::GetPathnames(g_VFS, L"simulation/ai/" + aiName + L"/", L"*.js", pathnames);
+ for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it)
+ {
+ m_ScriptInterface.LoadGlobalScriptFile(*it);
+ }
+ }
+
+ return m_PlayerMetadata[path];
+ }
+
+ void PerformComputation()
+ {
+ PROFILE("AI compute");
+
+ m_Commands.clear();
+
+ // Deserialize the game state, to pass to the AI's HandleMessage
+ CScriptVal state;
+
+ {
+// TIMER(L"deserialize AI game state");
+ std::stringstream stream(m_GameState);
+ CStdDeserializer deserializer(m_ScriptInterface, stream);
+ deserializer.ScriptVal("state", state);
+ }
+
+ m_ScriptInterface.FreezeObject(state.get(), true);
+
+ m_Commands.resize(m_Players.size());
+
+ for (size_t i = 0; i < m_Players.size(); ++i)
+ {
+ m_Commands[i].player = m_Players[i].player;
+
+ m_CurrentlyComputingPlayer = i;
+ m_ScriptInterface.CallFunctionVoid(m_Players[i].obj.get(), "HandleMessage", state);
+ // (This script will probably call PostCommand)
+ }
+
+ m_CurrentlyComputingPlayer = -1;
+ }
+
+ static void PostCommand(void* cbdata, CScriptValRooted cmd)
+ {
+ CAIWorker* self = static_cast (cbdata);
+
+ debug_assert(self->m_CurrentlyComputingPlayer >= 0); // called outside of PerformComputation somehow
+
+ self->m_Commands[self->m_CurrentlyComputingPlayer].commands.push_back(cmd);
+ }
+
+ ScriptInterface m_ScriptInterface;
+ boost::rand48 m_RNG;
+
+ CScriptValRooted m_EntityTemplates;
+ std::map m_PlayerMetadata;
+ std::vector m_Players;
+
+ std::string m_GameState;
+
+ bool m_CommandsComputed;
+ std::vector m_Commands;
+ int m_CurrentlyComputingPlayer; // used so PostCommand knows what player the command is for
+};
+
+
+
+class CCmpAIManager : public ICmpAIManager
+{
+public:
+ static void ClassInit(CComponentManager& UNUSED(componentManager))
+ {
+ }
+
+ DEFAULT_COMPONENT_ALLOCATOR(AIManager)
+
+ static std::string GetSchema()
+ {
+ return "";
+ }
+
+ virtual void Init(const CSimContext& UNUSED(context), const CParamNode& UNUSED(paramNode))
+ {
+ LoadEntityTemplates();
+ }
+
+ virtual void Deinit(const CSimContext& UNUSED(context))
+ {
+ }
+
+ virtual void Serialize(ISerializer& serialize)
+ {
+ // Because the AI worker uses its own ScriptInterface, we can't use the
+ // ISerializer (which was initialised with the simulation ScriptInterface)
+ // directly. So we'll just grab the ISerializer's stream and write to it
+ // with an independent serializer.
+
+ m_Worker.Serialize(serialize.GetStream(), serialize.IsDebug());
+ }
+
+ virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& deserialize)
+ {
+ Init(context, paramNode);
+
+ m_Worker.Deserialize(deserialize.GetStream());
+ }
+
+ virtual void AddPlayer(std::wstring id, player_id_t player)
+ {
+ m_Worker.AddPlayer(id, player, true);
+ }
+
+ virtual void StartComputation()
+ {
+ PROFILE("AI setup");
+
+ ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
+
+ CmpPtr cmpAIInterface(GetSimContext(), SYSTEM_ENTITY);
+ debug_assert(!cmpAIInterface.null());
+
+ // Get most of the game state from AIInterface
+ CScriptVal state = cmpAIInterface->GetRepresentation();
+
+ // Get entity state from each entity:
+
+ CScriptVal entities;
+ scriptInterface.Eval(L"({})", entities);
+
+ const std::map& ents = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_AIProxy);
+
+ for (std::map::const_iterator it = ents.begin(); it != ents.end(); ++it)
+ {
+ // Skip local entities
+ if (ENTITY_IS_LOCAL(it->first))
+ continue;
+
+ CScriptVal rep = static_cast(it->second)->GetRepresentation();
+
+ scriptInterface.SetPropertyInt(entities.get(), it->first, rep, true);
+ }
+
+ // Add the entities state into the object returned by AIInterface
+ scriptInterface.SetProperty(state.get(), "entities", entities, true);
+
+ // Serialize the state representation, so that it's safe to transfer
+ // across threads (in the future when we actually run AI in a
+ // background thread)
+ std::stringstream stream;
+ CStdSerializer serializer(scriptInterface, stream);
+ serializer.ScriptVal("state", state);
+
+ m_Worker.StartComputation(stream.str());
+ }
+
+ virtual void PushCommands()
+ {
+ ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
+
+ std::vector commands;
+ m_Worker.GetCommands(commands);
+
+ CmpPtr cmpCommandQueue(GetSimContext(), SYSTEM_ENTITY);
+ if (cmpCommandQueue.null())
+ return;
+
+ for (size_t i = 0; i < commands.size(); ++i)
+ {
+ for (size_t j = 0; j < commands[i].commands.size(); ++j)
+ {
+ std::stringstream stream(commands[i].commands[j]);
+ CStdDeserializer deserializer(scriptInterface, stream);
+ CScriptVal cmd;
+ deserializer.ScriptVal("command", cmd);
+ cmpCommandQueue->PushLocalCommand(commands[i].player, cmd);
+ }
+ }
+ }
+
+private:
+ void LoadEntityTemplates()
+ {
+ TIMER(L"LoadEntityTemplates");
+
+ CmpPtr cmpTemplateManager(GetSimContext(), SYSTEM_ENTITY);
+ debug_assert(!cmpTemplateManager.null());
+
+ std::vector templateNames = cmpTemplateManager->FindAllTemplates(false);
+
+ std::vector > templates;
+ templates.reserve(templateNames.size());
+
+ for (size_t i = 0; i < templateNames.size(); ++i)
+ {
+ const CParamNode* node = cmpTemplateManager->GetTemplate(templateNames[i]);
+ if (node)
+ templates.push_back(std::make_pair(templateNames[i], node));
+ }
+
+ m_Worker.LoadEntityTemplates(templates);
+ }
+
+ CAIWorker m_Worker;
+};
+
+REGISTER_COMPONENT_TYPE(AIManager)
diff --git a/source/simulation2/components/CCmpCommandQueue.cpp b/source/simulation2/components/CCmpCommandQueue.cpp
index ab23137d95..ea7a2852f8 100644
--- a/source/simulation2/components/CCmpCommandQueue.cpp
+++ b/source/simulation2/components/CCmpCommandQueue.cpp
@@ -75,7 +75,7 @@ public:
}
}
- virtual void PushLocalCommand(int player, CScriptVal cmd)
+ virtual void PushLocalCommand(player_id_t player, CScriptVal cmd)
{
JSContext* cx = GetSimContext().GetScriptInterface().GetContext();
diff --git a/source/simulation2/components/CCmpPathfinder.cpp b/source/simulation2/components/CCmpPathfinder.cpp
index 29befaec3d..bce1e850ea 100644
--- a/source/simulation2/components/CCmpPathfinder.cpp
+++ b/source/simulation2/components/CCmpPathfinder.cpp
@@ -56,7 +56,7 @@ void CCmpPathfinder::Init(const CSimContext& UNUSED(context), const CParamNode&
// we can't use the real paramNode (it won't get handled properly when deserializing),
// so load the data from a special XML file.
CParamNode externalParamNode;
- CParamNode::LoadXML(externalParamNode, L"simulation/templates/special/pathfinder.xml");
+ CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml");
const CParamNode::ChildrenMap& passClasses = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses").GetChildren();
diff --git a/source/simulation2/components/CCmpTemplateManager.cpp b/source/simulation2/components/CCmpTemplateManager.cpp
index 42d3a3ca6d..e1d41d257e 100644
--- a/source/simulation2/components/CCmpTemplateManager.cpp
+++ b/source/simulation2/components/CCmpTemplateManager.cpp
@@ -129,7 +129,7 @@ public:
virtual std::string GetCurrentTemplateName(entity_id_t ent);
- virtual std::vector FindAllTemplates();
+ virtual std::vector FindAllTemplates(bool includeActors);
virtual std::vector GetEntitiesUsingTemplate(std::string templateName);
@@ -360,7 +360,7 @@ void CCmpTemplateManager::ConstructTemplateActor(const std::string& actorName, C
static LibError AddToTemplates(const VfsPath& pathname, const FileInfo& UNUSED(fileInfo), const uintptr_t cbData)
{
- std::vector& templates = *(std::vector*)cbData;
+ std::vector& templates = *(std::vector*)cbData;
// Strip the .xml extension
VfsPath pathstem = change_extension(pathname, L"");
@@ -371,28 +371,28 @@ static LibError AddToTemplates(const VfsPath& pathname, const FileInfo& UNUSED(f
if (name.substr(0, 9) == L"template_")
return INFO::OK;
- templates.push_back(name);
+ templates.push_back(std::string(name.begin(), name.end()));
return INFO::OK;
}
static LibError AddActorToTemplates(const VfsPath& pathname, const FileInfo& UNUSED(fileInfo), const uintptr_t cbData)
{
- std::vector& templates = *(std::vector*)cbData;
+ std::vector& templates = *(std::vector*)cbData;
// Strip the root from the path
std::wstring name = pathname.string().substr(ARRAY_SIZE(ACTOR_ROOT)-1);
- templates.push_back(L"actor|" + name);
+ templates.push_back("actor|" + std::string(name.begin(), name.end()));
return INFO::OK;
}
-std::vector CCmpTemplateManager::FindAllTemplates()
+std::vector CCmpTemplateManager::FindAllTemplates(bool includeActors)
{
// TODO: eventually this should probably read all the template files and look for flags to
// determine which should be displayed in the editor (and in what categories etc); for now we'll
// just return all the files
- std::vector templates;
+ std::vector templates;
LibError ok;
@@ -400,9 +400,12 @@ std::vector CCmpTemplateManager::FindAllTemplates()
ok = fs_util::ForEachFile(g_VFS, TEMPLATE_ROOT, AddToTemplates, (uintptr_t)&templates, L"*.xml", fs_util::DIR_RECURSIVE);
WARN_ERR(ok);
- // Add all the actors too
- ok = fs_util::ForEachFile(g_VFS, ACTOR_ROOT, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", fs_util::DIR_RECURSIVE);
- WARN_ERR(ok);
+ if (includeActors)
+ {
+ // Add all the actors too
+ ok = fs_util::ForEachFile(g_VFS, ACTOR_ROOT, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", fs_util::DIR_RECURSIVE);
+ WARN_ERR(ok);
+ }
return templates;
}
diff --git a/source/simulation2/components/ICmpAIInterface.cpp b/source/simulation2/components/ICmpAIInterface.cpp
new file mode 100644
index 0000000000..2011644556
--- /dev/null
+++ b/source/simulation2/components/ICmpAIInterface.cpp
@@ -0,0 +1,39 @@
+/* Copyright (C) 2011 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "ICmpAIInterface.h"
+
+#include "simulation2/system/InterfaceScripted.h"
+#include "simulation2/scripting/ScriptComponent.h"
+
+BEGIN_INTERFACE_WRAPPER(AIInterface)
+END_INTERFACE_WRAPPER(AIInterface)
+
+class CCmpAIInterfaceScripted : public ICmpAIInterface
+{
+public:
+ DEFAULT_SCRIPT_WRAPPER(AIInterfaceScripted)
+
+ virtual CScriptVal GetRepresentation()
+ {
+ return m_Script.Call ("GetRepresentation");
+ }
+};
+
+REGISTER_COMPONENT_SCRIPT_WRAPPER(AIInterfaceScripted)
diff --git a/source/simulation2/components/ICmpAIInterface.h b/source/simulation2/components/ICmpAIInterface.h
new file mode 100644
index 0000000000..ad26bec09f
--- /dev/null
+++ b/source/simulation2/components/ICmpAIInterface.h
@@ -0,0 +1,35 @@
+/* Copyright (C) 2011 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_ICMPAIINTERFACE
+#define INCLUDED_ICMPAIINTERFACE
+
+#include "simulation2/system/Interface.h"
+
+class ICmpAIInterface : public IComponent
+{
+public:
+ /**
+ * Returns a script object that represents the current world state,
+ * to be passed to AI scripts.
+ */
+ virtual CScriptVal GetRepresentation() = 0;
+
+ DECLARE_INTERFACE_TYPE(AIInterface)
+};
+
+#endif // INCLUDED_ICMPAIINTERFACE
diff --git a/source/simulation2/components/ICmpAIManager.cpp b/source/simulation2/components/ICmpAIManager.cpp
new file mode 100644
index 0000000000..81610029b2
--- /dev/null
+++ b/source/simulation2/components/ICmpAIManager.cpp
@@ -0,0 +1,73 @@
+/* Copyright (C) 2011 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "ICmpAIManager.h"
+
+#include "simulation2/system/InterfaceScripted.h"
+
+#include "lib/file/file_system_util.h"
+#include "ps/Filesystem.h"
+
+BEGIN_INTERFACE_WRAPPER(AIManager)
+DEFINE_INTERFACE_METHOD_2("AddPlayer", void, ICmpAIManager, AddPlayer, std::wstring, player_id_t)
+END_INTERFACE_WRAPPER(AIManager)
+
+// Implement the static method that finds all AI scripts
+// that can be loaded via AddPlayer:
+
+struct GetAIsHelper
+{
+ GetAIsHelper(ScriptInterface& scriptInterface) :
+ m_ScriptInterface(scriptInterface)
+ {
+ }
+
+ void Run()
+ {
+ fs_util::ForEachFile(g_VFS, L"simulation/ai/", Callback, (uintptr_t)this, L"*.json", fs_util::DIR_RECURSIVE);
+ }
+
+ static LibError Callback(const VfsPath& pathname, const FileInfo& UNUSED(fileInfo), const uintptr_t cbData)
+ {
+ GetAIsHelper* self = (GetAIsHelper*)cbData;
+
+ // Extract the 3rd component of the path (i.e. the directory after simulation/ai/)
+ VfsPath::iterator it = pathname.begin();
+ std::advance(it, 2);
+ std::wstring dirname = *it;
+
+ CScriptValRooted ai;
+ self->m_ScriptInterface.Eval("({})", ai);
+ self->m_ScriptInterface.SetProperty(ai.get(), "id", dirname, true);
+ self->m_ScriptInterface.SetProperty(ai.get(), "data", self->m_ScriptInterface.ReadJSONFile(pathname), true);
+ self->m_AIs.push_back(ai);
+
+ return INFO::OK;
+ }
+
+ std::vector m_AIs;
+ ScriptInterface& m_ScriptInterface;
+};
+
+std::vector ICmpAIManager::GetAIs(ScriptInterface& scriptInterface)
+{
+ GetAIsHelper helper(scriptInterface);
+ helper.Run();
+ return helper.m_AIs;
+}
diff --git a/source/simulation2/components/ICmpAIManager.h b/source/simulation2/components/ICmpAIManager.h
new file mode 100644
index 0000000000..4131511545
--- /dev/null
+++ b/source/simulation2/components/ICmpAIManager.h
@@ -0,0 +1,56 @@
+/* Copyright (C) 2011 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_ICMPAIMANAGER
+#define INCLUDED_ICMPAIMANAGER
+
+#include "simulation2/system/Interface.h"
+
+#include "simulation2/helpers/Player.h"
+
+class ICmpAIManager : public IComponent
+{
+public:
+ /**
+ * Add a new AI player into the world, based on the AI script identified
+ * by @p id (corresponding to a subdirectory in simulation/ai/),
+ * to control player @p player.
+ */
+ virtual void AddPlayer(std::wstring id, player_id_t player) = 0;
+
+ /**
+ * Call this at the end of a turn, to trigger AI computation which will be
+ * ready for the next turn.
+ */
+ virtual void StartComputation() = 0;
+
+ /**
+ * Call this at the start of a turn, to push the computed AI commands into
+ * the command queue.
+ */
+ virtual void PushCommands() = 0;
+
+ /**
+ * Returns a vector of {"id":"value-for-AddPlayer", "name":"Human readable name"}
+ * objects, based on all the available AI scripts.
+ */
+ static std::vector GetAIs(ScriptInterface& scriptInterface);
+
+ DECLARE_INTERFACE_TYPE(AIManager)
+};
+
+#endif // INCLUDED_ICMPAIMANAGER
diff --git a/source/simulation2/components/ICmpAIProxy.cpp b/source/simulation2/components/ICmpAIProxy.cpp
new file mode 100644
index 0000000000..20707ea026
--- /dev/null
+++ b/source/simulation2/components/ICmpAIProxy.cpp
@@ -0,0 +1,39 @@
+/* Copyright (C) 2011 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "ICmpAIProxy.h"
+
+#include "simulation2/system/InterfaceScripted.h"
+#include "simulation2/scripting/ScriptComponent.h"
+
+BEGIN_INTERFACE_WRAPPER(AIProxy)
+END_INTERFACE_WRAPPER(AIProxy)
+
+class CCmpAIProxyScripted : public ICmpAIProxy
+{
+public:
+ DEFAULT_SCRIPT_WRAPPER(AIProxyScripted)
+
+ virtual CScriptVal GetRepresentation()
+ {
+ return m_Script.Call ("GetRepresentation");
+ }
+};
+
+REGISTER_COMPONENT_SCRIPT_WRAPPER(AIProxyScripted)
diff --git a/source/simulation2/components/ICmpAIProxy.h b/source/simulation2/components/ICmpAIProxy.h
new file mode 100644
index 0000000000..cc570c8c80
--- /dev/null
+++ b/source/simulation2/components/ICmpAIProxy.h
@@ -0,0 +1,35 @@
+/* Copyright (C) 2011 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_ICMPAIPROXY
+#define INCLUDED_ICMPAIPROXY
+
+#include "simulation2/system/Interface.h"
+
+class ICmpAIProxy : public IComponent
+{
+public:
+ /**
+ * Returns a script object that represents the current entity state,
+ * to be passed to AI scripts.
+ */
+ virtual CScriptVal GetRepresentation() = 0;
+
+ DECLARE_INTERFACE_TYPE(AIProxy)
+};
+
+#endif // INCLUDED_ICMPAIPROXY
diff --git a/source/simulation2/components/ICmpCommandQueue.cpp b/source/simulation2/components/ICmpCommandQueue.cpp
index 535e09db54..db173b71a1 100644
--- a/source/simulation2/components/ICmpCommandQueue.cpp
+++ b/source/simulation2/components/ICmpCommandQueue.cpp
@@ -22,7 +22,7 @@
#include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(CommandQueue)
-DEFINE_INTERFACE_METHOD_2("PushLocalCommand", void, ICmpCommandQueue, PushLocalCommand, int, CScriptVal)
+DEFINE_INTERFACE_METHOD_2("PushLocalCommand", void, ICmpCommandQueue, PushLocalCommand, player_id_t, CScriptVal)
DEFINE_INTERFACE_METHOD_1("PostNetworkCommand", void, ICmpCommandQueue, PostNetworkCommand, CScriptVal)
// Excluded: FlushTurn (doesn't make sense for scripts to call it)
END_INTERFACE_WRAPPER(CommandQueue)
diff --git a/source/simulation2/components/ICmpCommandQueue.h b/source/simulation2/components/ICmpCommandQueue.h
index 1a1776e87b..6442cfa2ea 100644
--- a/source/simulation2/components/ICmpCommandQueue.h
+++ b/source/simulation2/components/ICmpCommandQueue.h
@@ -40,7 +40,7 @@ public:
/**
* Pushes a new command onto the local queue. @p cmd does not need to be rooted.
*/
- virtual void PushLocalCommand(int player, CScriptVal cmd) = 0;
+ virtual void PushLocalCommand(player_id_t player, CScriptVal cmd) = 0;
/**
* Send a command associated with the current player to the networking system.
diff --git a/source/simulation2/components/ICmpTemplateManager.cpp b/source/simulation2/components/ICmpTemplateManager.cpp
index c7e23e08a9..6792180706 100644
--- a/source/simulation2/components/ICmpTemplateManager.cpp
+++ b/source/simulation2/components/ICmpTemplateManager.cpp
@@ -24,6 +24,6 @@
BEGIN_INTERFACE_WRAPPER(TemplateManager)
DEFINE_INTERFACE_METHOD_1("GetTemplate", const CParamNode*, ICmpTemplateManager, GetTemplate, std::string)
DEFINE_INTERFACE_METHOD_1("GetCurrentTemplateName", std::string, ICmpTemplateManager, GetCurrentTemplateName, entity_id_t)
-DEFINE_INTERFACE_METHOD_0("FindAllTemplates", std::vector, ICmpTemplateManager, FindAllTemplates)
+DEFINE_INTERFACE_METHOD_1("FindAllTemplates", std::vector, ICmpTemplateManager, FindAllTemplates, bool)
DEFINE_INTERFACE_METHOD_1("GetEntitiesUsingTemplate", std::vector, ICmpTemplateManager, GetEntitiesUsingTemplate, std::string)
END_INTERFACE_WRAPPER(TemplateManager)
diff --git a/source/simulation2/components/ICmpTemplateManager.h b/source/simulation2/components/ICmpTemplateManager.h
index e5a7d99c9a..96df61bacc 100644
--- a/source/simulation2/components/ICmpTemplateManager.h
+++ b/source/simulation2/components/ICmpTemplateManager.h
@@ -89,7 +89,7 @@ public:
* (This includes "actor|foo" etc names).
* Intended for use by the map editor. This is likely to be quite slow.
*/
- virtual std::vector FindAllTemplates() = 0;
+ virtual std::vector FindAllTemplates(bool includeActors) = 0;
/**
* Permanently disable XML validation (intended solely for test cases).
diff --git a/source/simulation2/helpers/SimulationCommand.h b/source/simulation2/helpers/SimulationCommand.h
index db9ea3cdf6..62b051ade8 100644
--- a/source/simulation2/helpers/SimulationCommand.h
+++ b/source/simulation2/helpers/SimulationCommand.h
@@ -19,13 +19,14 @@
#define INCLUDED_SIMULATIONCOMMAND
#include "scriptinterface/ScriptVal.h"
+#include "simulation2/helpers/Player.h"
/**
* Simulation command, typically received over the network in multiplayer games.
*/
struct SimulationCommand
{
- int player;
+ player_id_t player;
CScriptValRooted data;
};
diff --git a/source/simulation2/scripting/EngineScriptConversions.cpp b/source/simulation2/scripting/EngineScriptConversions.cpp
index 3d523e82b1..4ce007fcdf 100644
--- a/source/simulation2/scripting/EngineScriptConversions.cpp
+++ b/source/simulation2/scripting/EngineScriptConversions.cpp
@@ -61,67 +61,15 @@ template<> jsval ScriptInterface::ToJSVal(JSContext* cx, IComponent
return OBJECT_TO_JSVAL(obj);
}
-static jsval ConvertCParamNode(JSContext* cx, CParamNode const& val)
+template<> jsval ScriptInterface::ToJSVal(JSContext* cx, CParamNode const& val)
{
- const std::map& children = val.GetChildren();
- if (children.empty())
- {
- // Empty node - map to undefined
- if (val.ToString().empty())
- return JSVAL_VOID;
-
- // Just a string
- utf16string text(val.ToString().begin(), val.ToString().end());
- JSString* str = JS_InternUCStringN(cx, reinterpret_cast(text.data()), text.length());
- if (str)
- return STRING_TO_JSVAL(str);
- // TODO: report error
- return JSVAL_VOID;
- }
-
- // Got child nodes - convert this node into a hash-table-style object:
-
- JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL);
- if (!obj)
- return JSVAL_VOID; // TODO: report error
- CScriptValRooted objRoot(cx, OBJECT_TO_JSVAL(obj));
-
- for (std::map::const_iterator it = children.begin(); it != children.end(); ++it)
- {
- jsval childVal = ConvertCParamNode(cx, it->second);
- if (!JS_SetProperty(cx, obj, it->first.c_str(), &childVal))
- return JSVAL_VOID; // TODO: report error
- }
-
- // If the node has a string too, add that as an extra property
- if (!val.ToString().empty())
- {
- utf16string text(val.ToString().begin(), val.ToString().end());
- JSString* str = JS_InternUCStringN(cx, reinterpret_cast(text.data()), text.length());
- if (!str)
- return JSVAL_VOID; // TODO: report error
- jsval childVal = STRING_TO_JSVAL(str);
- if (!JS_SetProperty(cx, obj, "_string", &childVal))
- return JSVAL_VOID; // TODO: report error
- }
+ jsval rval = val.ToJSVal(cx, true);
// Prevent modifications to the object, so that it's safe to share between
// components and to reconstruct on deserialization
- //JS_SealObject(cx, obj, JS_TRUE);
- // TODO: need to re-enable this when it works safely (e.g. it doesn't seal the
- // global object too (via the parent chain))
+ if (JSVAL_IS_OBJECT(rval))
+ JS_DeepFreezeObject(cx, JSVAL_TO_OBJECT(rval));
- return OBJECT_TO_JSVAL(obj);
-}
-
-template<> jsval ScriptInterface::ToJSVal(JSContext* cx, CParamNode const& val)
-{
- // If there's a cached value, then return it
- if (!JSVAL_IS_VOID(val.GetScriptVal().get()))
- return val.GetScriptVal().get();
-
- jsval rval = ConvertCParamNode(cx, val);
- val.SetScriptVal(CScriptValRooted(cx, rval));
return rval;
}
diff --git a/source/simulation2/scripting/ScriptComponent.cpp b/source/simulation2/scripting/ScriptComponent.cpp
index 246a42acc5..b3ec4b7457 100644
--- a/source/simulation2/scripting/ScriptComponent.cpp
+++ b/source/simulation2/scripting/ScriptComponent.cpp
@@ -27,12 +27,13 @@ CComponentTypeScript::CComponentTypeScript(ScriptInterface& scriptInterface, jsv
{
// Cache the property detection for efficiency
m_HasCustomSerialize = m_ScriptInterface.HasProperty(m_Instance.get(), "Serialize");
+ m_HasCustomDeserialize = m_ScriptInterface.HasProperty(m_Instance.get(), "Deserialize");
}
void CComponentTypeScript::Init(const CSimContext& UNUSED(context), const CParamNode& paramNode, entity_id_t ent)
{
- m_ScriptInterface.SetProperty(m_Instance.get(), "entity", (int)ent, true);
- m_ScriptInterface.SetProperty(m_Instance.get(), "template", paramNode, true);
+ m_ScriptInterface.SetProperty(m_Instance.get(), "entity", (int)ent, true, false);
+ m_ScriptInterface.SetProperty(m_Instance.get(), "template", paramNode, true, false);
m_ScriptInterface.CallFunctionVoid(m_Instance.get(), "Init");
}
@@ -57,7 +58,7 @@ void CComponentTypeScript::Serialize(ISerializer& serialize)
// serialized instead of the component itself
if (m_HasCustomSerialize)
{
- CScriptValRooted val;
+ CScriptVal val;
if (!m_ScriptInterface.CallFunction(m_Instance.get(), "Serialize", val))
LOGERROR(L"Script Serialize call failed");
serialize.ScriptVal("object", val);
@@ -70,12 +71,22 @@ void CComponentTypeScript::Serialize(ISerializer& serialize)
void CComponentTypeScript::Deserialize(const CSimContext& UNUSED(context), const CParamNode& paramNode, IDeserializer& deserialize, entity_id_t ent)
{
- // TODO: maybe we want to allow a script Deserialize() function, to mirror the Serialize() above
+ // Support a custom "Deserialize" function, to which we pass the deserialized data
+ // instead of automatically adding the deserialized properties onto the object
+ if (m_HasCustomDeserialize)
+ {
+ CScriptVal val;
+ deserialize.ScriptVal("object", val);
+ if (!m_ScriptInterface.CallFunctionVoid(m_Instance.get(), "Deserialize", val))
+ LOGERROR(L"Script Deserialize call failed");
+ }
+ else
+ {
+ // Use ScriptObjectAppend so we don't lose the carefully-constructed
+ // prototype/parent of this object
+ deserialize.ScriptObjectAppend("object", m_Instance.getRef());
+ }
- // Use ScriptObjectAppend so we don't lose the carefully-constructed
- // prototype/parent of this object
- deserialize.ScriptObjectAppend("object", m_Instance.getRef());
-
- m_ScriptInterface.SetProperty(m_Instance.get(), "entity", (int)ent, true);
- m_ScriptInterface.SetProperty(m_Instance.get(), "template", paramNode, true);
+ m_ScriptInterface.SetProperty(m_Instance.get(), "entity", (int)ent, true, false);
+ m_ScriptInterface.SetProperty(m_Instance.get(), "template", paramNode, true, false);
}
diff --git a/source/simulation2/scripting/ScriptComponent.h b/source/simulation2/scripting/ScriptComponent.h
index e7e45f1ec8..ad5aba1ee8 100644
--- a/source/simulation2/scripting/ScriptComponent.h
+++ b/source/simulation2/scripting/ScriptComponent.h
@@ -76,6 +76,7 @@ private:
ScriptInterface& m_ScriptInterface;
CScriptValRooted m_Instance;
bool m_HasCustomSerialize;
+ bool m_HasCustomDeserialize;
NONCOPYABLE(CComponentTypeScript);
};
diff --git a/source/simulation2/serialization/BinarySerializer.h b/source/simulation2/serialization/BinarySerializer.h
index 6b1f96f6a8..000af6b78b 100644
--- a/source/simulation2/serialization/BinarySerializer.h
+++ b/source/simulation2/serialization/BinarySerializer.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -27,6 +27,26 @@
#include