# Add initial basic player AI framework.

Support direct access to serializer streams, so serializers can be
nested.
Make component script "this.template" read-only.
Stop globally-subscribed component scripts receiving messages posted to
local components, to reduce out-of-sync risks.
Move pathfinder data out of entity template directory.
Fix GuiInterface deserialization.

This was SVN commit r8865.
This commit is contained in:
Ykkrosh 2011-01-12 12:29:00 +00:00
parent 1051d10f54
commit dd501b2a5a
70 changed files with 1681 additions and 267 deletions

View file

@ -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);

View file

@ -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});
}

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<objects>
<script file="gui/aiconfig/aiconfig.js"/>
<!-- Add a translucent black background to fade out the menu page -->
<object type="image" z="0" sprite="bkTranslucent"/>
<object type="image" style="wheatWindow" size="50%-250 50%-100 50%+250 50%+100">
<object style="wheatWindowTitleBar" type="text">AI configuration</object>
<object type="button" style="wheatExit" tooltip_style="snToolTip">
<action on="Press"><![CDATA[
Engine.PopGuiPage();
]]></action>
</object>
<object size="25 10 100%-25 40">
<object
type="text"
font="serif-bold-18"
text_align="right"
size="0 0 100 100%">
AI Player
</object>
<object name="aiSelection" type="dropdown" style="wheatDropDown" size="110 0 310 30">
<action on="SelectionChange">selectAI(this.selected);</action>
</object>
</object>
<object size="25 50 100%-25 65%-5">
<object name="aiDescription" type="text" size="0 0 100% 100%"/>
<!-- TODO: we might want to add things like difficulty controls into here -->
</object>
<object type="button" style="wheatButton" size="100%-203 100%-33 100%-103 100%-3">
OK
<action on="Press"><![CDATA[
returnAI();
Engine.PopGuiPage();
]]></action>
</object>
<object type="button" style="wheatButton" size="100%-103 100%-33 100%-3 100%-3">
Cancel
<action on="Press"><![CDATA[
Engine.PopGuiPage();
]]></action>
</object>
</object>
</objects>

View file

@ -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 ()

View file

@ -103,8 +103,12 @@
<object name="playerColour[n]" type="image" size="0 0 100% 100%"/>
<object name="playerName[n]" type="text" text_align="right" text_valign="center" size="0 0 100 100%"/>
<object name="playerAssignment[n]" type="dropdown" style="wheatDropDown" size="100 2 250 100%-2" tooltip_style="onscreenToolTip" tooltip="Select player"/>
<object name="playerConfig[n]" type="button" style="wheatButton" size="251 6 264 24"
tooltip_style="onscreenToolTip"
tooltip="Configure AI settings"
>c</object>
<object name="playerCiv[n]" type="dropdown" style="wheatDropDown" size="265 2 395 100%-2" tooltip_style="onscreenToolTip" tooltip="Select player's civilization"/>
<object name="playerCivText[n]" type="text" text_align="center" text_valign="center" size="250 0 400 100%"/>
<object name="playerCivText[n]" type="text" text_align="center" text_valign="center" size="265 0 395 100%"/>
<object name="playerTeam[n]" type="dropdown" style="wheatDropDown" size="415 2 485 100%-2" tooltip_style="onscreenToolTip" tooltip="Select player's team"/>
<object name="playerTeamText[n]" type="text" text_align="center" text_valign="center" size="415 0 485 100%"/>
</object>
@ -188,4 +192,4 @@
</object>
</objects>
</objects>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<page>
<include>common/setup.xml</include>
<include>common/sprite1.xml</include>
<include>common/styles.xml</include>
<include>aiconfig/aiconfig.xml</include>
</page>

View file

@ -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)
{

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f1af6b6dde1e44ef7126355bfe25a3e4ed4cdf678a12351d8d1bdf89b9e86314
size 2860
oid sha256:cc6a53ac292f004227ee7a7a1bfacdfe5d7f03fac42f750e4e3f4406b420751c
size 2712

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4b5bdfec10c985668216314a92a94a565cf1a96fa3df22657d57c4e64cc7ddc9
size 2387
oid sha256:057a66b494b3f695fb6a296cf15a499ee2d4343f8623457546e53ce627d962ca
size 2391

View file

@ -0,0 +1,5 @@
{
"name": "Scaredy Bot",
"description": "An AI that is terrified by the mere possibility of having to fight.",
"constructor": "ScaredyBotAI"
}

View file

@ -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++;
};

View file

@ -0,0 +1,63 @@
function AIInterface() {}
AIInterface.prototype.Schema =
"<a:component type='system'/><empty/>";
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);

View file

@ -0,0 +1,20 @@
function AIProxy() {}
AIProxy.prototype.Schema =
"<empty/>";
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);

View file

@ -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 = {

View file

@ -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);

View file

@ -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

View file

@ -9,7 +9,6 @@ function LoadMapSettings(settings)
if (!settings)
settings = {};
if (settings.DefaultStance)
{
for each (var ent in Engine.GetEntitiesWithInterface(IID_UnitAI))

View file

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity>
<Position>
<Altitude>0</Altitude>
<Anchor>upright</Anchor>
<Floating>false</Floating>
</Position>
<Ownership/>
<Selectable/>
<AIProxy/>
<Ownership/>
<Position>
<Altitude>0</Altitude>
<Anchor>upright</Anchor>
<Floating>false</Floating>
</Position>
<Selectable/>
</Entity>

View file

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity>
<Position>
<Altitude>0</Altitude>
<Anchor>upright</Anchor>
<Floating>false</Floating>
</Position>
<Ownership/>
<AIProxy/>
<Ownership/>
<Position>
<Altitude>0</Altitude>
<Anchor>upright</Anchor>
<Floating>false</Floating>
</Position>
</Entity>

View file

@ -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<CScriptValRooted> GetAIs(void* cbdata)
{
CGUIManager* guiManager = static_cast<CGUIManager*> (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<void, CScriptVal, &SetNetworkGameAttributes>("SetNetworkGameAttributes");
scriptInterface.RegisterFunction<void, int, std::string, &AssignNetworkPlayer>("AssignNetworkPlayer");
scriptInterface.RegisterFunction<void, std::wstring, &SendNetworkChat>("SendNetworkChat");
scriptInterface.RegisterFunction<std::vector<CScriptValRooted>, &GetAIs>("GetAIs");
// Misc functions
scriptInterface.RegisterFunction<std::wstring, std::wstring, &SetCursor>("SetCursor");

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* 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())
{

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* 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 <vector>
#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;

View file

@ -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<CStr> 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",

View file

@ -244,27 +244,20 @@ template<typename T> static bool FromJSVal_vector(JSContext* cx, jsval v, std::v
return true;
}
template<> jsval ScriptInterface::ToJSVal<std::vector<int> >(JSContext* cx, const std::vector<int>& val)
{
return ToJSVal_vector(cx, val);
}
// Instantiate various vector types:
template<> jsval ScriptInterface::ToJSVal<std::vector<u32> >(JSContext* cx, const std::vector<u32>& val)
{
return ToJSVal_vector(cx, val);
}
#define VECTOR(T) \
template<> jsval ScriptInterface::ToJSVal<std::vector<T> >(JSContext* cx, const std::vector<T>& val) \
{ \
return ToJSVal_vector(cx, val); \
} \
template<> bool ScriptInterface::FromJSVal<std::vector<T> >(JSContext* cx, jsval v, std::vector<T>& out) \
{ \
return FromJSVal_vector(cx, v, out); \
}
template<> jsval ScriptInterface::ToJSVal<std::vector<std::wstring> >(JSContext* cx, const std::vector<std::wstring>& val)
{
return ToJSVal_vector(cx, val);
}
template<> bool ScriptInterface::FromJSVal<std::vector<int> >(JSContext* cx, jsval v, std::vector<int>& out)
{
return FromJSVal_vector(cx, v, out);
}
template<> bool ScriptInterface::FromJSVal<std::vector<u32> >(JSContext* cx, jsval v, std::vector<u32>& out)
{
return FromJSVal_vector(cx, v, out);
}
VECTOR(int)
VECTOR(u32)
VECTOR(std::string)
VECTOR(std::wstring)
VECTOR(CScriptValRooted)

View file

@ -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<const jschar*> (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)

View file

@ -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<typename T0, typename T1, typename T2, typename R>
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<typename T0, typename T1, typename T2, typename T3, typename R>
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<typename T>
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<typename T>
bool SetPropertyInt(jsval obj, int name, const T& value, bool constant, bool enumerate = true);
template<typename T>
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<typename T, typename CHAR> 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<typename T0, typename T1, typename T2, typename T3, typename R>
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<typename T>
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<typename T>
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<typename T>
bool ScriptInterface::SetPropertyInt(jsval obj, int name, const T& value, bool readonly, bool enumerate)
{
return SetPropertyInt_(obj, name, ToJSVal(GetContext(), value), readonly, enumerate);
}
template<typename T>

View file

@ -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;
};

View file

@ -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::vector<SimulationComman
if (!cmpPathfinder.null())
cmpPathfinder->FinishAsyncRequests();
// Push AI commands onto the queue before we use them
CmpPtr<ICmpAIManager> cmpAIManager(m_SimContext, SYSTEM_ENTITY);
if (!cmpAIManager.null())
cmpAIManager->PushCommands();
CmpPtr<ICmpCommandQueue> cmpCommandQueue(m_SimContext, SYSTEM_ENTITY);
if (!cmpCommandQueue.null())
cmpCommandQueue->FlushTurn(commands);
@ -223,6 +233,10 @@ bool CSimulation2Impl::Update(int turnLength, const std::vector<SimulationComman
if (m_EnableOOSLog)
DumpState();
// Start computing AI for the next turn
if (!cmpAIManager.null())
cmpAIManager->StartComputation();
++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)

View file

@ -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)

View file

@ -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)

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<CScriptValRooted> commands;
};
public:
struct SReturnedCommands
{
player_id_t player;
std::vector<std::string> commands;
};
CAIWorker() :
m_ScriptInterface("Engine", "AI"),
m_CommandsComputed(true),
m_CurrentlyComputingPlayer(-1)
{
m_ScriptInterface.SetCallbackData(static_cast<void*> (this));
// TODO: ought to seed the RNG (in a network-synchronised way) before we use it
m_ScriptInterface.ReplaceNondeterministicFunctions(m_RNG);
m_ScriptInterface.RegisterFunction<void, CScriptValRooted, CAIWorker::PostCommand>("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<SReturnedCommands>& 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<std::pair<std::string, const CParamNode*> >& 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<SerializeScriptVal>()(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<SerializeScriptVal>()(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<CAIWorker*> (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<std::wstring, CScriptValRooted> m_PlayerMetadata;
std::vector<SAIPlayer> m_Players;
std::string m_GameState;
bool m_CommandsComputed;
std::vector<SCommands> 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 "<a:component type='system'/><empty/>";
}
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<ICmpAIInterface> 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<entity_id_t, IComponent*>& ents = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_AIProxy);
for (std::map<entity_id_t, IComponent*>::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
// Skip local entities
if (ENTITY_IS_LOCAL(it->first))
continue;
CScriptVal rep = static_cast<ICmpAIProxy*>(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<CAIWorker::SReturnedCommands> commands;
m_Worker.GetCommands(commands);
CmpPtr<ICmpCommandQueue> 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<ICmpTemplateManager> cmpTemplateManager(GetSimContext(), SYSTEM_ENTITY);
debug_assert(!cmpTemplateManager.null());
std::vector<std::string> templateNames = cmpTemplateManager->FindAllTemplates(false);
std::vector<std::pair<std::string, const CParamNode*> > 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)

View file

@ -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();

View file

@ -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();

View file

@ -129,7 +129,7 @@ public:
virtual std::string GetCurrentTemplateName(entity_id_t ent);
virtual std::vector<std::wstring> FindAllTemplates();
virtual std::vector<std::string> FindAllTemplates(bool includeActors);
virtual std::vector<entity_id_t> 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<std::wstring>& templates = *(std::vector<std::wstring>*)cbData;
std::vector<std::string>& templates = *(std::vector<std::string>*)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<std::wstring>& templates = *(std::vector<std::wstring>*)cbData;
std::vector<std::string>& templates = *(std::vector<std::string>*)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<std::wstring> CCmpTemplateManager::FindAllTemplates()
std::vector<std::string> 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<std::wstring> templates;
std::vector<std::string> templates;
LibError ok;
@ -400,9 +400,12 @@ std::vector<std::wstring> 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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<CScriptVal> ("GetRepresentation");
}
};
REGISTER_COMPONENT_SCRIPT_WRAPPER(AIInterfaceScripted)

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<CScriptValRooted> m_AIs;
ScriptInterface& m_ScriptInterface;
};
std::vector<CScriptValRooted> ICmpAIManager::GetAIs(ScriptInterface& scriptInterface)
{
GetAIsHelper helper(scriptInterface);
helper.Run();
return helper.m_AIs;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<CScriptValRooted> GetAIs(ScriptInterface& scriptInterface);
DECLARE_INTERFACE_TYPE(AIManager)
};
#endif // INCLUDED_ICMPAIMANAGER

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<CScriptVal> ("GetRepresentation");
}
};
REGISTER_COMPONENT_SCRIPT_WRAPPER(AIProxyScripted)

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View file

@ -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)

View file

@ -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.

View file

@ -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<std::wstring>, ICmpTemplateManager, FindAllTemplates)
DEFINE_INTERFACE_METHOD_1("FindAllTemplates", std::vector<std::string>, ICmpTemplateManager, FindAllTemplates, bool)
DEFINE_INTERFACE_METHOD_1("GetEntitiesUsingTemplate", std::vector<entity_id_t>, ICmpTemplateManager, GetEntitiesUsingTemplate, std::string)
END_INTERFACE_WRAPPER(TemplateManager)

View file

@ -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<std::wstring> FindAllTemplates() = 0;
virtual std::vector<std::string> FindAllTemplates(bool includeActors) = 0;
/**
* Permanently disable XML validation (intended solely for test cases).

View file

@ -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;
};

View file

@ -61,67 +61,15 @@ template<> jsval ScriptInterface::ToJSVal<IComponent*>(JSContext* cx, IComponent
return OBJECT_TO_JSVAL(obj);
}
static jsval ConvertCParamNode(JSContext* cx, CParamNode const& val)
template<> jsval ScriptInterface::ToJSVal<CParamNode>(JSContext* cx, CParamNode const& val)
{
const std::map<std::string, CParamNode>& 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<const jschar*>(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<std::string, CParamNode>::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<const jschar*>(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<CParamNode>(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;
}

View file

@ -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);
}

View file

@ -76,6 +76,7 @@ private:
ScriptInterface& m_ScriptInterface;
CScriptValRooted m_Instance;
bool m_HasCustomSerialize;
bool m_HasCustomDeserialize;
NONCOPYABLE(CComponentTypeScript);
};

View file

@ -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 <map>
/**
* Wrapper for redirecting ostream writes to CBinarySerializer's impl
*/
template<typename T>
class CSerializerStreamBuf : public std::streambuf
{
T& m_SerializerImpl;
public:
CSerializerStreamBuf(T& impl) :
m_SerializerImpl(impl)
{
}
std::streamsize xsputn(const char* s, std::streamsize n)
{
m_SerializerImpl.Put("stream", reinterpret_cast<const u8*> (s), n);
return n;
}
};
/**
* PutScriptVal implementation details.
* (Split out from the main class because it's too big to be inlined.)
@ -64,14 +84,18 @@ class CBinarySerializer : public ISerializer
NONCOPYABLE(CBinarySerializer);
public:
CBinarySerializer(ScriptInterface& scriptInterface) :
m_ScriptImpl(new CBinarySerializerScriptImpl(scriptInterface, *this))
m_ScriptImpl(new CBinarySerializerScriptImpl(scriptInterface, *this)),
m_RawStreamBuf(m_Impl),
m_RawStream(&m_RawStreamBuf)
{
}
template <typename A>
CBinarySerializer(ScriptInterface& scriptInterface, A& a) :
m_ScriptImpl(new CBinarySerializerScriptImpl(scriptInterface, *this)),
m_Impl(a)
m_Impl(a),
m_RawStreamBuf(m_Impl),
m_RawStream(&m_RawStreamBuf)
{
}
@ -147,11 +171,19 @@ protected:
m_Impl.Put(name, data, len);
}
virtual std::ostream& GetStream()
{
return m_RawStream;
}
protected:
T m_Impl;
private:
std::auto_ptr<CBinarySerializerScriptImpl> m_ScriptImpl;
CSerializerStreamBuf<T> m_RawStreamBuf;
std::ostream m_RawStream;
};
#endif // INCLUDED_BINARYSERIALIZER

View file

@ -165,3 +165,8 @@ bool CDebugSerializer::IsDebug() const
{
return true;
}
std::ostream& CDebugSerializer::GetStream()
{
return m_Stream;
}

View file

@ -39,6 +39,7 @@ public:
void Dedent(int spaces);
virtual bool IsDebug() const;
virtual std::ostream& GetStream();
protected:
virtual void PutNumber(const char* name, uint8_t value);

View file

@ -67,6 +67,13 @@ public:
// Features for simulation-state serialisation:
virtual int GetVersion() const;
/**
* Returns a stream which can be used to deserialize data directly.
* (This is particularly useful for chaining multiple deserializers
* together.)
*/
virtual std::istream& GetStream() = 0;
protected:
virtual void ReadString(std::string& out);

View file

@ -238,6 +238,13 @@ public:
*/
virtual bool IsDebug() const;
/**
* Returns a stream which can be used to serialize data directly.
* (This is particularly useful for chaining multiple serializers
* together.)
*/
virtual std::ostream& GetStream() = 0;
protected:
virtual void PutNumber(const char* name, uint8_t value) = 0;
virtual void PutNumber(const char* name, int8_t value) = 0;

View file

@ -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,6 +23,8 @@
* Helper templates for serializing/deserializing common objects.
*/
#include "simulation2/components/ICmpPathfinder.h"
template<typename ELEM>
struct SerializeVector
{
@ -112,6 +114,19 @@ struct SerializeU32_Unbounded
}
};
struct SerializeScriptVal
{
void operator()(ISerializer& serialize, const char* name, CScriptValRooted value)
{
serialize.ScriptVal(name, value);
}
void operator()(IDeserializer& deserialize, const char* name, CScriptValRooted& value)
{
deserialize.ScriptVal(name, value);
}
};
struct SerializeWaypoint
{
void operator()(ISerializer& serialize, const char* UNUSED(name), const ICmpPathfinder::Waypoint& value)

View file

@ -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
@ -45,6 +45,11 @@ void CStdDeserializer::Get(u8* data, size_t len)
throw PSERROR_Deserialize_ReadFailed();
}
std::istream& CStdDeserializer::GetStream()
{
return m_Stream;
}
void CStdDeserializer::AddScriptBackref(JSObject* obj)
{
std::pair<std::map<u32, JSObject*>::iterator, bool> it = m_ScriptBackrefs.insert(std::make_pair((u32)m_ScriptBackrefs.size()+1, obj));

View file

@ -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
@ -37,6 +37,8 @@ public:
virtual void ScriptObjectAppend(const char* name, jsval& obj);
virtual void ScriptString(const char* name, JSString*& out);
virtual std::istream& GetStream();
protected:
virtual void Get(u8* data, size_t len);

View file

@ -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
@ -28,3 +28,8 @@ CStdSerializer::CStdSerializer(ScriptInterface& scriptInterface, std::ostream& s
CBinarySerializer<CStdSerializerImpl>(scriptInterface, stream)
{
}
std::ostream& CStdSerializer::GetStream()
{
return m_Impl.GetStream();
}

View file

@ -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,11 @@ public:
m_Stream.write((const char*)data, (std::streamsize)len);
}
std::ostream& GetStream()
{
return m_Stream;
}
private:
std::ostream& m_Stream;
};
@ -48,6 +53,8 @@ class CStdSerializer : public CBinarySerializer<CStdSerializerImpl>
{
public:
CStdSerializer(ScriptInterface& scriptInterface, std::ostream& stream);
virtual std::ostream& GetStream();
};
#endif // INCLUDED_STDSERIALIZER

View file

@ -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,8 @@
#include "ParamNode.h"
#include "SimContext.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
@ -582,7 +582,7 @@ IComponent* CComponentManager::ConstructComponent(entity_id_t ent, ComponentType
jsval obj = JSVAL_NULL;
if (ct.type == CT_Script)
{
obj = m_ScriptInterface.CallConstructor(ct.ctor.get());
obj = m_ScriptInterface.CallConstructor(ct.ctor.get(), JSVAL_VOID);
if (JSVAL_IS_VOID(obj))
{
LOGERROR(L"Script component constructor failed");
@ -748,17 +748,19 @@ void CComponentManager::PostMessage(entity_id_t ent, const CMessage& msg) const
std::vector<ComponentTypeId>::const_iterator ctit = it->second.begin();
for (; ctit != it->second.end(); ++ctit)
{
// Find the component instances of this type (if any)
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::const_iterator emap = m_ComponentsByTypeId.find(*ctit);
if (emap == m_ComponentsByTypeId.end())
continue;
// Send the message to all of them
std::map<entity_id_t, IComponent*>::const_iterator eit = emap->second.find(ent);
if (eit != emap->second.end())
eit->second->HandleMessage(m_SimContext, msg, false);
}
}
SendGlobalMessage(msg);
SendGlobalMessage(ent, msg);
}
void CComponentManager::BroadcastMessage(const CMessage& msg) const
@ -771,20 +773,22 @@ void CComponentManager::BroadcastMessage(const CMessage& msg) const
std::vector<ComponentTypeId>::const_iterator ctit = it->second.begin();
for (; ctit != it->second.end(); ++ctit)
{
// Find the component instances of this type (if any)
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::const_iterator emap = m_ComponentsByTypeId.find(*ctit);
if (emap == m_ComponentsByTypeId.end())
continue;
// Send the message to all of them
std::map<entity_id_t, IComponent*>::const_iterator eit = emap->second.begin();
for (; eit != emap->second.end(); ++eit)
eit->second->HandleMessage(m_SimContext, msg, false);
}
}
SendGlobalMessage(msg);
SendGlobalMessage(INVALID_ENTITY, msg);
}
void CComponentManager::SendGlobalMessage(const CMessage& msg) const
void CComponentManager::SendGlobalMessage(entity_id_t ent, const CMessage& msg) const
{
// (Common functionality for PostMessage and BroadcastMessage)
@ -796,10 +800,22 @@ void CComponentManager::SendGlobalMessage(const CMessage& msg) const
std::vector<ComponentTypeId>::const_iterator ctit = it->second.begin();
for (; ctit != it->second.end(); ++ctit)
{
// Special case: Messages for non-local entities shouldn't be sent to script
// components that subscribed globally, so that we don't have to worry about
// them accidentally picking up non-network-synchronised data.
if (ENTITY_IS_LOCAL(ent))
{
std::map<ComponentTypeId, ComponentType>::const_iterator it = m_ComponentTypesById.find(*ctit);
if (it != m_ComponentTypesById.end() && it->second.type == CT_Script)
continue;
}
// Find the component instances of this type (if any)
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::const_iterator emap = m_ComponentsByTypeId.find(*ctit);
if (emap == m_ComponentsByTypeId.end())
continue;
// Send the message to all of them
std::map<entity_id_t, IComponent*>::const_iterator eit = emap->second.begin();
for (; eit != emap->second.end(); ++eit)
eit->second->HandleMessage(m_SimContext, msg, true);
@ -875,7 +891,6 @@ std::string CComponentManager::GenerateSchema()
schema += "</grammar>";
// TODO: pretty-print
return schema;
}
@ -883,26 +898,7 @@ CScriptVal CComponentManager::Script_ReadJSONFile(void* cbdata, std::string file
{
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
VfsPath path = VfsPath(L"simulation/data/" + CStrW(fileName));
if (!FileExists(g_VFS, path))
{
LOGERROR(L"File 'simulation/data/%hs' does not exist", fileName.c_str());
return CScriptVal();
}
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 CScriptVal();
}
std::string content(file.GetBuffer(), file.GetBuffer() + file.GetBufferSize()); // assume it's UTF-8
return (componentManager->m_ScriptInterface.ParseJSON(content)).get();
std::wstring path = L"simulation/data/" + CStrW(fileName);
return componentManager->GetScriptInterface().ReadJSONFile(path).get();
}

View file

@ -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
@ -21,6 +21,7 @@
#include "Entity.h"
#include "Components.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/helpers/Player.h"
#include <boost/random/linear_congruential.hpp>
@ -228,7 +229,7 @@ private:
static CScriptVal Script_ReadJSONFile(void* cbdata, std::string fileName);
CMessage* ConstructMessage(int mtid, CScriptVal data);
void SendGlobalMessage(const CMessage& msg) const;
void SendGlobalMessage(entity_id_t ent, const CMessage& msg) const;
ComponentTypeId GetScriptWrapper(InterfaceId iid);

View file

@ -283,15 +283,63 @@ void CParamNode::ToXML(std::wostream& strm) const
}
}
void CParamNode::SetScriptVal(CScriptValRooted val) const
jsval CParamNode::ToJSVal(JSContext* cx, bool cacheValue) const
{
debug_assert(JSVAL_IS_VOID(m_ScriptVal.get()));
m_ScriptVal = val;
if (cacheValue && !m_ScriptVal.uninitialised())
return m_ScriptVal.get();
jsval val = ConstructJSVal(cx);
if (cacheValue)
m_ScriptVal = CScriptValRooted(cx, val);
return val;
}
CScriptValRooted CParamNode::GetScriptVal() const
jsval CParamNode::ConstructJSVal(JSContext* cx) const
{
return m_ScriptVal;
if (m_Childs.empty())
{
// Empty node - map to undefined
if (m_Value.empty())
return JSVAL_VOID;
// Just a string
utf16string text(m_Value.begin(), m_Value.end());
JSString* str = JS_InternUCStringN(cx, reinterpret_cast<const jschar*>(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<std::string, CParamNode>::const_iterator it = m_Childs.begin(); it != m_Childs.end(); ++it)
{
jsval childVal = it->second.ConstructJSVal(cx);
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 (!m_Value.empty())
{
utf16string text(m_Value.begin(), m_Value.end());
JSString* str = JS_InternUCStringN(cx, reinterpret_cast<const jschar*>(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
}
return OBJECT_TO_JSVAL(obj);
}
void CParamNode::ResetScriptVal()

View file

@ -193,6 +193,17 @@ public:
*/
void ToXML(std::wostream& strm) const;
/**
* Returns a jsval representation of this node and its children.
* If @p cacheValue is true, then the same jsval will be returned each time
* this is called (regardless of whether you passed the same @p cx - be careful
* to only use the cache in one context).
* When caching, the lifetime of @p cx must be longer than the lifetime of this node.
* The cache will be reset if *this* node is modified (e.g. by LoadXML),
* but *not* if any child nodes are modified (so don't do that).
*/
jsval ToJSVal(JSContext* cx, bool cacheValue) const;
/**
* Returns the names/nodes of the children of this node, ordered by name
*/
@ -204,29 +215,20 @@ public:
*/
static std::wstring EscapeXMLString(const std::wstring& str);
/**
* Stores the given script representation of this node, for use in cached conversions.
* This must only be called once.
* This will be reset to JSVAL_VOID if *this* node is modified (e.g. by LoadXML),
* but *not* if any child nodes are modified (so don't do that).
* The lifetime of the script context associated with the value must be longer
* than the lifetime of this node.
*/
void SetScriptVal(CScriptValRooted val) const;
/**
* Returns the value saved by SetScriptVal, or the default (JSVAL_VOID) if none was set.
*/
CScriptValRooted GetScriptVal() const;
private:
void ApplyLayer(const XMBFile& xmb, const XMBElement& element);
void ResetScriptVal();
jsval ConstructJSVal(JSContext* cx) const;
std::wstring m_Value;
ChildrenMap m_Childs;
bool m_IsOk;
/**
* Caches the ToJSVal script representation of this node.
*/
mutable CScriptValRooted m_ScriptVal;
};

View file

@ -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

View file

@ -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

View file

@ -239,12 +239,12 @@ public:
CmpPtr<ICmpTemplateManager> cmpTempMan(sim, SYSTEM_ENTITY);
TS_ASSERT(!cmpTempMan.null());
std::vector<std::wstring> templates = cmpTempMan->FindAllTemplates();
std::vector<std::string> templates = cmpTempMan->FindAllTemplates(true);
for (size_t i = 0; i < templates.size(); ++i)
{
std::wstring name = templates[i];
printf("# %ls\n", name.c_str());
const CParamNode* p = cmpTempMan->GetTemplate(CStr8(name));
std::string name = templates[i];
printf("# %s\n", name.c_str());
const CParamNode* p = cmpTempMan->GetTemplate(name);
TS_ASSERT(p != NULL);
}
}

View file

@ -489,7 +489,7 @@ public:
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent2, IID_Test1))->GetX(), 1+10+100+1000);
}
void TODO_test_script_template_readonly()
void test_script_template_readonly()
{
CSimContext context;
CComponentManager man(context);

View file

@ -59,7 +59,7 @@ public:
CSimulation2 sim(NULL, &m_Terrain);
TS_ASSERT(sim.LoadScripts(L"simulation/components/addentity/"));
sim.ResetState(true);
sim.ResetState(true, true);
entity_id_t ent1 = sim.AddEntity(L"test1");
TS_ASSERT_EQUALS(ent1, (u32)2);
@ -79,7 +79,7 @@ public:
CSimulation2 sim(NULL, &m_Terrain);
TS_ASSERT(sim.LoadScripts(L"simulation/components/addentity/"));
sim.ResetState(true);
sim.ResetState(true, true);
entity_id_t ent1 = sim.AddEntity(L"test1");
entity_id_t ent2 = sim.AddEntity(L"test1");
@ -136,7 +136,7 @@ public:
TS_ASSERT_OK(g_VFS->Invalidate(L"simulation/components/hotload/hotload.js"));
TS_ASSERT(sim.LoadScripts(L"simulation/components/hotload/"));
sim.ResetState(true);
sim.ResetState(true, true);
entity_id_t ent = sim.AddEntity(L"hotload");

View file

@ -80,20 +80,22 @@ QUERYHANDLER(GetObjectsList)
CmpPtr<ICmpTemplateManager> cmp(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (!cmp.null())
{
std::vector<std::wstring> names = cmp->FindAllTemplates();
std::vector<std::string> names = cmp->FindAllTemplates(true);
for (std::vector<std::wstring>::iterator it = names.begin(); it != names.end(); ++it)
for (std::vector<std::string>::iterator it = names.begin(); it != names.end(); ++it)
{
std::wstring name(it->begin(), it->end());
sObjectsListItem e;
e.id = *it;
if (it->substr(0, 6) == L"actor|")
e.id = name;
if (name.substr(0, 6) == L"actor|")
{
e.name = it->substr(6);
e.name = name.substr(6);
e.type = 1;
}
else
{
e.name = *it;
e.name = name;
e.type = 0;
}
objects.push_back(e);