forked from 0ad/0ad
Load map descriptions from their XML file.
Add basic hold-fire stance, and use it for some test maps. Add JSON data container to map XML files, to simplify the interaction between scripts and maps. Fix fixed-point printing so it roundtrips safely through map files. Fix camera startup positions in old-format maps. This was SVN commit r7844.
This commit is contained in:
parent
a5171d9145
commit
65bcedb9fc
@ -21,6 +21,9 @@ var g_MaxPlayers = 8;
|
||||
|
||||
var g_ChatMessages = [];
|
||||
|
||||
// Cache of output from Engine.LoadMapData
|
||||
var g_MapData = {};
|
||||
|
||||
function init(attribs)
|
||||
{
|
||||
switch (attribs.type)
|
||||
@ -162,6 +165,14 @@ function initMapNameList(object)
|
||||
object.selected = selected;
|
||||
}
|
||||
|
||||
function loadMapData(name)
|
||||
{
|
||||
if (!(name in g_MapData))
|
||||
g_MapData[name] = Engine.LoadMapData(name);
|
||||
|
||||
return g_MapData[name];
|
||||
}
|
||||
|
||||
// Called when the user selects a map from the list
|
||||
function selectMap(name)
|
||||
{
|
||||
@ -193,16 +204,18 @@ function onGameAttributesChange()
|
||||
|
||||
getGUIObjectByName("mapInfoName").caption = mapName;
|
||||
|
||||
var description = "Sorry, no description available.";
|
||||
var mapData = loadMapData(mapName);
|
||||
var mapSettings = (mapData && mapData.settings ? mapData.settings : {});
|
||||
|
||||
// TODO: we ought to load map descriptions from the map itself, somehow.
|
||||
// Just hardcode it now for testing.
|
||||
if (mapName == "Latium")
|
||||
{
|
||||
description = "2 players. A fertile coastal region which was the birthplace of the Roman Empire. Plentiful natural resources let you build up a city and experiment with the game’s features in relative peace. Some more description could go here if you want as long as it’s not too long and still fits on the screen.";
|
||||
}
|
||||
// Load the description from the map file, if there is one
|
||||
var description = mapSettings.Description || "Sorry, no description available.";
|
||||
|
||||
getGUIObjectByName("mapInfoDescription").caption = description;
|
||||
// Describe the number of players
|
||||
var playerString = "";
|
||||
if (mapSettings.NumPlayers)
|
||||
playerString = mapSettings.NumPlayers + " " + (mapSettings.NumPlayers == 1 ? "player" : "players") + ". ";
|
||||
|
||||
getGUIObjectByName("mapInfoDescription").caption = playerString + description;
|
||||
|
||||
g_IsInGuiUpdate = false;
|
||||
}
|
||||
|
BIN
binaries/data/mods/public/maps/scenarios/Latium.xml
(Stored with Git LFS)
BIN
binaries/data/mods/public/maps/scenarios/Latium.xml
(Stored with Git LFS)
Binary file not shown.
BIN
binaries/data/mods/public/maps/scenarios/techdemo3.xml
(Stored with Git LFS)
BIN
binaries/data/mods/public/maps/scenarios/techdemo3.xml
(Stored with Git LFS)
Binary file not shown.
@ -5,6 +5,17 @@ UnitAI.prototype.Schema =
|
||||
"<a:example/>" +
|
||||
"<empty/>";
|
||||
|
||||
// Very basic stance support (currently just for test maps where we don't want
|
||||
// everyone killing each other immediately after loading)
|
||||
var g_Stances = {
|
||||
"aggressive": {
|
||||
attackOnSight: true,
|
||||
},
|
||||
"holdfire": {
|
||||
attackOnSight: false,
|
||||
},
|
||||
};
|
||||
|
||||
var UnitFsmSpec = {
|
||||
|
||||
"INDIVIDUAL": {
|
||||
@ -41,7 +52,7 @@ var UnitFsmSpec = {
|
||||
{
|
||||
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
var ents = rangeMan.ResetActiveQuery(this.losRangeQuery);
|
||||
if (this.AttackVisibleEntity(ents))
|
||||
if (this.GetStance().attackOnSight && this.AttackVisibleEntity(ents))
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -56,10 +67,11 @@ var UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"LosRangeUpdate": function(msg) {
|
||||
// TODO: implement stances (ignore this message if hold-fire stance)
|
||||
|
||||
if (this.GetStance().attackOnSight)
|
||||
{
|
||||
// Start attacking one of the newly-seen enemy (if any)
|
||||
this.AttackVisibleEntity(msg.data.added);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -373,6 +385,8 @@ UnitAI.prototype.Init = function()
|
||||
{
|
||||
this.orderQueue = []; // current order is at the front of the list
|
||||
this.order = undefined; // always == this.orderQueue[0]
|
||||
|
||||
this.SetStance("aggressive");
|
||||
};
|
||||
|
||||
UnitAI.prototype.OnCreate = function()
|
||||
@ -732,6 +746,19 @@ UnitAI.prototype.Repair = function(target, queued)
|
||||
this.AddOrder("Repair", { "target": target }, queued);
|
||||
};
|
||||
|
||||
UnitAI.prototype.SetStance = function(stance)
|
||||
{
|
||||
if (g_Stances[stance])
|
||||
this.stance = stance;
|
||||
else
|
||||
error("UnitAI: Setting to invalid stance '"+stance+"'");
|
||||
};
|
||||
|
||||
UnitAI.prototype.GetStance = function()
|
||||
{
|
||||
return g_Stances[this.stance];
|
||||
};
|
||||
|
||||
//// Helper functions ////
|
||||
|
||||
UnitAI.prototype.CanAttack = function(target)
|
||||
|
17
binaries/data/mods/public/simulation/helpers/Setup.js
Normal file
17
binaries/data/mods/public/simulation/helpers/Setup.js
Normal file
@ -0,0 +1,17 @@
|
||||
function LoadMapSettings(settings)
|
||||
{
|
||||
// Default settings for old maps
|
||||
if (!settings)
|
||||
settings = {};
|
||||
|
||||
if (settings.DefaultStance)
|
||||
{
|
||||
for each (var ent in Engine.GetEntitiesWithInterface(IID_UnitAI))
|
||||
{
|
||||
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
|
||||
cmpUnitAI.SetStance(settings.DefaultStance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Engine.RegisterGlobal("LoadMapSettings", LoadMapSettings);
|
@ -33,6 +33,7 @@
|
||||
#include "ps/XML/Xeromyces.h"
|
||||
#include "renderer/SkyManager.h"
|
||||
#include "renderer/WaterManager.h"
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
#include "simulation2/Simulation2.h"
|
||||
#include "simulation2/components/ICmpOwnership.h"
|
||||
#include "simulation2/components/ICmpPlayer.h"
|
||||
@ -247,6 +248,50 @@ int CMapReader::ApplyData()
|
||||
|
||||
|
||||
|
||||
PSRETURN CMapSummaryReader::LoadMap(const VfsPath& pathname)
|
||||
{
|
||||
VfsPath filename_xml = fs::change_extension(pathname, L".xml");
|
||||
|
||||
CXeromyces xmb_file;
|
||||
if (xmb_file.Load(g_VFS, filename_xml) != PSRETURN_OK)
|
||||
return PSRETURN_File_ReadFailed;
|
||||
|
||||
// Define all the relevant elements used in the XML file
|
||||
#define EL(x) int el_##x = xmb_file.GetElementID(#x)
|
||||
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
|
||||
EL(scenario);
|
||||
EL(scriptsettings);
|
||||
#undef AT
|
||||
#undef EL
|
||||
|
||||
XMBElement root = xmb_file.GetRoot();
|
||||
debug_assert(root.GetNodeName() == el_scenario);
|
||||
|
||||
XERO_ITER_EL(root, child)
|
||||
{
|
||||
int child_name = child.GetNodeName();
|
||||
if (child_name == el_scriptsettings)
|
||||
{
|
||||
m_ScriptSettings = child.GetText();
|
||||
}
|
||||
}
|
||||
|
||||
return PSRETURN_OK;
|
||||
}
|
||||
|
||||
CScriptValRooted CMapSummaryReader::GetScriptData(ScriptInterface& scriptInterface)
|
||||
{
|
||||
CScriptValRooted data;
|
||||
scriptInterface.Eval("({})", data);
|
||||
if (!m_ScriptSettings.empty())
|
||||
scriptInterface.SetProperty(data.get(), "settings", scriptInterface.ParseJSON(m_ScriptSettings), false);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Holds various state data while reading maps, so that loading can be
|
||||
// interrupted (e.g. to update the progress display) then later resumed.
|
||||
class CXMLReader
|
||||
@ -1115,16 +1160,25 @@ int CXMLReader::ReadOldEntities(XMBElement parent, double end_time)
|
||||
entity_id_t ent = m_MapReader.pSimulation2->AddEntity(TemplateName);
|
||||
if (ent != INVALID_ENTITY)
|
||||
{
|
||||
CmpPtr<ICmpPosition> cmpPos(*m_MapReader.pSimulation2, ent);
|
||||
if (!cmpPos.null())
|
||||
CmpPtr<ICmpPosition> cmpPosition(*m_MapReader.pSimulation2, ent);
|
||||
if (!cmpPosition.null())
|
||||
{
|
||||
cmpPos->JumpTo(Position.X, Position.Z);
|
||||
cmpPos->SetYRotation(Orientation);
|
||||
cmpPosition->JumpTo(Position.X, Position.Z);
|
||||
cmpPosition->SetYRotation(Orientation);
|
||||
}
|
||||
|
||||
CmpPtr<ICmpOwnership> cmpOwner(*m_MapReader.pSimulation2, ent);
|
||||
if (!cmpOwner.null())
|
||||
cmpOwner->SetOwner(PlayerID);
|
||||
|
||||
if (m_MapReader.m_CameraStartupTarget == INVALID_ENTITY && !cmpPosition.null())
|
||||
{
|
||||
// Special-case civil centre files to initialise the camera.
|
||||
if (PlayerID == m_MapReader.m_PlayerID && boost::algorithm::ends_with(TemplateName, L"civil_centre"))
|
||||
{
|
||||
m_MapReader.m_CameraStartupTarget = ent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
completed_jobs++;
|
||||
@ -1223,6 +1277,10 @@ int CXMLReader::ProgressiveRead()
|
||||
{
|
||||
ReadCamera(node);
|
||||
}
|
||||
else if (name == "ScriptSettings")
|
||||
{
|
||||
m_MapReader.pSimulation2->SetMapSettings(node.GetText());
|
||||
}
|
||||
else if (m_MapReader.file_format_version <= 4 && name == "Entities")
|
||||
{
|
||||
ret = ReadOldEntities(node, end_time);
|
||||
|
@ -35,6 +35,8 @@ class CCinemaManager;
|
||||
class CTriggerManager;
|
||||
class CSimulation2;
|
||||
class CTextureEntry;
|
||||
class CScriptValRooted;
|
||||
class ScriptInterface;
|
||||
|
||||
class CXMLReader;
|
||||
|
||||
@ -104,4 +106,31 @@ private:
|
||||
CXMLReader* xml_reader;
|
||||
};
|
||||
|
||||
/**
|
||||
* A restricted map reader that returns various summary information
|
||||
* for use by scripts (particularly the GUI).
|
||||
*/
|
||||
class CMapSummaryReader
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Try to load a map file.
|
||||
* @param pathname Path to .pmp or .xml file
|
||||
*/
|
||||
PSRETURN LoadMap(const VfsPath& pathname);
|
||||
|
||||
/**
|
||||
* Returns a value of the form:
|
||||
* @code
|
||||
* {
|
||||
* "settings": { ... contents of the map's <ScriptSettings> ... }
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
CScriptValRooted GetScriptData(ScriptInterface& scriptInterface);
|
||||
|
||||
private:
|
||||
utf16string m_ScriptSettings;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -167,6 +167,7 @@ void CMapWriter::PackTerrain(CFilePacker& packer, CTerrain* pTerrain)
|
||||
// pack tile data
|
||||
packer.PackRaw(&tiles[0],sizeof(STileDesc)*tiles.size());
|
||||
}
|
||||
|
||||
void CMapWriter::WriteXML(const VfsPath& filename,
|
||||
WaterManager* pWaterMan, SkyManager* pSkyMan,
|
||||
CLightEnv* pLightEnv, CCamera* pCamera, CCinemaManager* pCinema,
|
||||
@ -275,6 +276,16 @@ void CMapWriter::WriteXML(const VfsPath& filename,
|
||||
}
|
||||
}
|
||||
|
||||
if (pSimulation2)
|
||||
{
|
||||
std::string settings = pSimulation2->GetMapSettings();
|
||||
if (!settings.empty())
|
||||
{
|
||||
XML_Element("ScriptSettings");
|
||||
XML_CDATA(("\n" + settings + "\n").c_str());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
XML_Element("Entities");
|
||||
|
||||
@ -310,13 +321,13 @@ void CMapWriter::WriteXML(const VfsPath& filename,
|
||||
CFixedVector3D rot = cmpPosition->GetRotation();
|
||||
{
|
||||
XML_Element("Position");
|
||||
XML_Attribute("x", pos.X.ToDouble());
|
||||
XML_Attribute("z", pos.Z.ToDouble());
|
||||
XML_Attribute("x", pos.X);
|
||||
XML_Attribute("z", pos.Z);
|
||||
// TODO: height offset etc
|
||||
}
|
||||
{
|
||||
XML_Element("Orientation");
|
||||
XML_Attribute("y", rot.Y.ToDouble());
|
||||
XML_Attribute("y", rot.Y);
|
||||
// TODO: X, Z maybe
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include "graphics/Camera.h"
|
||||
#include "graphics/GameView.h"
|
||||
#include "graphics/MapReader.h"
|
||||
#include "gui/GUIManager.h"
|
||||
#include "lib/sysdep/sysdep.h"
|
||||
#include "maths/FixedVector3D.h"
|
||||
@ -291,6 +292,18 @@ bool AtlasIsAvailable(void* UNUSED(cbdata))
|
||||
return ATLAS_IsAvailable();
|
||||
}
|
||||
|
||||
CScriptVal LoadMapData(void* cbdata, std::wstring name)
|
||||
{
|
||||
CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
|
||||
|
||||
CMapSummaryReader reader;
|
||||
PSRETURN err = reader.LoadMap(VfsPath(L"maps/scenarios/") / (name + L".xml"));
|
||||
if (err)
|
||||
return CScriptVal();
|
||||
|
||||
return reader.GetScriptData(guiManager->GetScriptInterface()).get();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void GuiScriptingInit(ScriptInterface& scriptInterface)
|
||||
@ -329,4 +342,5 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
|
||||
scriptInterface.RegisterFunction<void, std::string, &OpenURL>("OpenURL");
|
||||
scriptInterface.RegisterFunction<void, &RestartInAtlas>("RestartInAtlas");
|
||||
scriptInterface.RegisterFunction<bool, &AtlasIsAvailable>("AtlasIsAvailable");
|
||||
scriptInterface.RegisterFunction<CScriptVal, std::wstring, &LoadMapData>("LoadMapData");
|
||||
}
|
||||
|
@ -90,6 +90,57 @@ CFixed_15_16 CFixed_15_16::FromString(const CStrW& s)
|
||||
return FromString(CStr8(s));
|
||||
}
|
||||
|
||||
template<>
|
||||
CStr8 CFixed_15_16::ToString() const
|
||||
{
|
||||
std::stringstream r;
|
||||
|
||||
u32 posvalue = abs(value);
|
||||
if (value < 0)
|
||||
r << "-";
|
||||
|
||||
r << (posvalue >> fract_bits);
|
||||
|
||||
u16 fraction = posvalue & ((1 << fract_bits) - 1);
|
||||
if (fraction)
|
||||
{
|
||||
r << ".";
|
||||
|
||||
u32 frac = 0;
|
||||
u32 div = 1;
|
||||
|
||||
// Do the inverse of FromString: Keep adding digits until (frac<<16)/div == expected fraction
|
||||
while (true)
|
||||
{
|
||||
frac *= 10;
|
||||
div *= 10;
|
||||
|
||||
// Low estimate of d such that ((frac+d)<<16)/div == fraction
|
||||
u32 digit = (((u64)fraction*div) >> 16) - frac;
|
||||
frac += digit;
|
||||
|
||||
// If this gives the exact target, then add the digit and stop
|
||||
if (((u64)frac << 16) / div == fraction)
|
||||
{
|
||||
r << digit;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the next higher digit gives the exact target, then add that digit and stop
|
||||
if (digit <= 8 && (((u64)frac+1) << 16) / div == fraction)
|
||||
{
|
||||
r << digit+1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise add the digit and continue
|
||||
r << digit;
|
||||
}
|
||||
}
|
||||
|
||||
return r.str();
|
||||
}
|
||||
|
||||
// Based on http://www.dspguru.com/dsp/tricks/fixed-point-atan2-with-self-normalization
|
||||
CFixed_15_16 atan2_approx(CFixed_15_16 y, CFixed_15_16 x)
|
||||
{
|
||||
|
@ -166,6 +166,9 @@ public:
|
||||
return (value + fract_pow2 - 1) >> fract_bits;
|
||||
}
|
||||
|
||||
/// Returns the shortest string such that FromString will parse to the correct value.
|
||||
CStr8 ToString() const;
|
||||
|
||||
/// Returns true if the number is precisely 0.
|
||||
bool IsZero() const { return value == 0; }
|
||||
|
||||
|
@ -116,6 +116,31 @@ public:
|
||||
// TODO: could test more large/small numbers, errors, etc
|
||||
}
|
||||
|
||||
void test_ToString()
|
||||
{
|
||||
#define T(n, str) { fixed f = fixed::FromDouble(n); TS_ASSERT_STR_EQUALS(f.ToString(), str); TS_ASSERT_EQUALS(fixed::FromString(f.ToString()), f); }
|
||||
|
||||
T(1.0, "1");
|
||||
T(-1.0, "-1");
|
||||
T(10000.0, "10000");
|
||||
T(1.25, "1.25");
|
||||
T(-1.25, "-1.25");
|
||||
T(0.5, "0.5");
|
||||
T(1.0/65536.0, "0.00002");
|
||||
T(2.0/65536.0, "0.00004");
|
||||
T(250367.0/65536.0, "3.8203");
|
||||
T(32768.0 - 1.0/65536.0, "32767.99999");
|
||||
T(-32768.0 + 1.0/65536.0, "-32767.99999");
|
||||
|
||||
#undef T
|
||||
|
||||
for (int i = 0; i < 65536; ++i)
|
||||
{
|
||||
fixed f = fixed::FromDouble(i / 65536.0);
|
||||
TS_ASSERT_EQUALS(fixed::FromString(f.ToString()), f);
|
||||
}
|
||||
}
|
||||
|
||||
void test_RoundToZero()
|
||||
{
|
||||
TS_ASSERT_EQUALS(fixed::FromFloat(10.f).ToInt_RoundToZero(), 10);
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "ps/Filesystem.h"
|
||||
#include "lib/utf8.h"
|
||||
#include "lib/sysdep/cpu.h"
|
||||
#include "maths/Fixed.h"
|
||||
|
||||
#define LOG_CATEGORY L"xml"
|
||||
|
||||
@ -273,6 +274,11 @@ template <> void XMLWriter_File::ElementAttribute<std::wstring>(const char* name
|
||||
ElementAttribute(name, utf8_from_wstring(value).c_str(), newelement);
|
||||
}
|
||||
|
||||
template <> void XMLWriter_File::ElementAttribute<fixed>(const char* name, const fixed& value, bool newelement)
|
||||
{
|
||||
ElementAttribute(name, value.ToString().c_str(), newelement);
|
||||
}
|
||||
|
||||
// Use CStr's conversion for most types:
|
||||
#define TYPE2(ID_T, ARG_T) \
|
||||
template <> void XMLWriter_File::ElementAttribute<ID_T>(const char* name, ARG_T value, bool newelement) \
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "AutoRooters.h"
|
||||
|
||||
#include "lib/debug.h"
|
||||
#include "lib/utf8.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/Profile.h"
|
||||
#include "ps/utf16string.h"
|
||||
@ -628,6 +629,58 @@ std::wstring ScriptInterface::ToString(jsval obj)
|
||||
return source;
|
||||
}
|
||||
|
||||
CScriptValRooted ScriptInterface::ParseJSON(const utf16string& string)
|
||||
{
|
||||
jsval vp;
|
||||
JSONParser* parser = JS_BeginJSONParse(m->m_cx, &vp);
|
||||
if (!parser)
|
||||
{
|
||||
LOGERROR(L"ParseJSON failed to begin");
|
||||
return CScriptValRooted();
|
||||
}
|
||||
|
||||
if (!JS_ConsumeJSONText(m->m_cx, parser, string.c_str(), string.size()))
|
||||
{
|
||||
LOGERROR(L"ParseJSON failed to consume");
|
||||
return CScriptValRooted();
|
||||
}
|
||||
|
||||
if (!JS_FinishJSONParse(m->m_cx, parser, JSVAL_NULL))
|
||||
{
|
||||
LOGERROR(L"ParseJSON failed to finish");
|
||||
return CScriptValRooted();
|
||||
}
|
||||
|
||||
return CScriptValRooted(m->m_cx, vp);
|
||||
}
|
||||
|
||||
struct Stringifier
|
||||
{
|
||||
static JSBool callback(const jschar *buf, uint32 len, void *data)
|
||||
{
|
||||
utf16string str(buf, buf+len);
|
||||
std::wstring strw(str.begin(), str.end());
|
||||
|
||||
LibError err; // ignore Unicode errors
|
||||
static_cast<Stringifier*>(data)->stream << utf8_from_wstring(strw, &err);
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
std::stringstream stream;
|
||||
};
|
||||
|
||||
std::string ScriptInterface::StringifyJSON(jsval obj)
|
||||
{
|
||||
Stringifier str;
|
||||
if (!JS_Stringify(m->m_cx, &obj, NULL, INT_TO_JSVAL(2), &Stringifier::callback, &str))
|
||||
{
|
||||
LOGERROR(L"StringifyJSON failed");
|
||||
return "";
|
||||
}
|
||||
|
||||
return str.stream.str();
|
||||
}
|
||||
|
||||
void ScriptInterface::ReportError(const char* msg)
|
||||
{
|
||||
// JS_ReportError by itself doesn't seem to set a JS-style exception, and so
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -29,6 +29,8 @@
|
||||
#include "ScriptTypes.h"
|
||||
#include "ScriptVal.h"
|
||||
|
||||
#include "ps/utf16string.h"
|
||||
|
||||
class AutoGCRooter;
|
||||
|
||||
namespace boost { class rand48; }
|
||||
@ -154,6 +156,16 @@ public:
|
||||
|
||||
std::wstring ToString(jsval obj);
|
||||
|
||||
/**
|
||||
* Parse a JSON string. Returns the undefined value on error.
|
||||
*/
|
||||
CScriptValRooted ParseJSON(const utf16string& string);
|
||||
|
||||
/**
|
||||
* Stringify to a JSON string, UTF-8 encoded. Returns an empty string on error.
|
||||
*/
|
||||
std::string StringifyJSON(jsval obj);
|
||||
|
||||
/**
|
||||
* Report the given error message through the JS error reporting mechanism,
|
||||
* and throw a JS exception. (Callers can check IsPendingException, and must
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
|
||||
#include "lib/utf8.h"
|
||||
#include "ps/CLogger.h"
|
||||
|
||||
#include <boost/random/linear_congruential.hpp>
|
||||
@ -125,4 +126,20 @@ public:
|
||||
TS_ASSERT(script.Eval("Math.random()", d2));
|
||||
TS_ASSERT_EQUALS(d1, d2);
|
||||
}
|
||||
|
||||
void test_json()
|
||||
{
|
||||
ScriptInterface script("Test");
|
||||
|
||||
std::string input = "({'x':1,'z':[2,'3\\u263A\\ud800'],\"y\":true})";
|
||||
CScriptValRooted val;
|
||||
TS_ASSERT(script.Eval(input.c_str(), val));
|
||||
|
||||
std::string stringified = script.StringifyJSON(val.get());
|
||||
TS_ASSERT_STR_EQUALS(stringified, "{\n \"x\":1,\n \"z\":[2,\n \"3\xE2\x98\xBA\xEF\xBF\xBD\"\n ],\n \"y\":true\n}");
|
||||
|
||||
std::wstring stringifiedw = wstring_from_utf8(stringified.c_str());
|
||||
val = script.ParseJSON(utf16string(stringifiedw.begin(), stringifiedw.end()));
|
||||
TS_ASSERT_WSTR_EQUALS(script.ToString(val.get()), L"({x:1, z:[2, \"3\\u263A\\uFFFD\"], y:true})");
|
||||
}
|
||||
};
|
||||
|
@ -135,6 +135,7 @@ public:
|
||||
double m_DeltaTime;
|
||||
|
||||
std::wstring m_StartupScript;
|
||||
CScriptValRooted m_MapSettings;
|
||||
|
||||
std::set<std::wstring> m_LoadedScripts;
|
||||
|
||||
@ -189,8 +190,15 @@ bool CSimulation2Impl::Update(int turnLength, const std::vector<SimulationComman
|
||||
CMessageTurnStart msgTurnStart;
|
||||
m_ComponentManager.BroadcastMessage(msgTurnStart);
|
||||
|
||||
if (m_TurnNumber == 0 && !m_StartupScript.empty())
|
||||
if (m_TurnNumber == 0)
|
||||
{
|
||||
ScriptInterface& scriptInterface = m_ComponentManager.GetScriptInterface();
|
||||
CScriptVal ret;
|
||||
scriptInterface.CallFunction(scriptInterface.GetGlobalObject(), "LoadMapSettings", m_MapSettings, ret);
|
||||
|
||||
if (!m_StartupScript.empty())
|
||||
m_ComponentManager.GetScriptInterface().LoadScript(L"map startup script", m_StartupScript);
|
||||
}
|
||||
|
||||
CmpPtr<ICmpCommandQueue> cmpCommandQueue(m_SimContext, SYSTEM_ENTITY);
|
||||
if (!cmpCommandQueue.null())
|
||||
@ -370,6 +378,16 @@ const std::wstring& CSimulation2::GetStartupScript()
|
||||
return m->m_StartupScript;
|
||||
}
|
||||
|
||||
void CSimulation2::SetMapSettings(const utf16string& settings)
|
||||
{
|
||||
m->m_MapSettings = m->m_ComponentManager.GetScriptInterface().ParseJSON(settings);
|
||||
}
|
||||
|
||||
std::string CSimulation2::GetMapSettings()
|
||||
{
|
||||
return m->m_ComponentManager.GetScriptInterface().StringifyJSON(m->m_MapSettings.get());
|
||||
}
|
||||
|
||||
LibError CSimulation2::ReloadChangedFile(const VfsPath& path)
|
||||
{
|
||||
return m->ReloadChangedFile(path);
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "scriptinterface/ScriptVal.h"
|
||||
|
||||
#include "lib/file/vfs/vfs_path.h"
|
||||
#include "ps/utf16string.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
@ -65,9 +66,27 @@ public:
|
||||
*/
|
||||
bool LoadDefaultScripts();
|
||||
|
||||
/**
|
||||
* Set a startup script, which will get executed before the first turn.
|
||||
*/
|
||||
void SetStartupScript(const std::wstring& script);
|
||||
|
||||
/**
|
||||
* Get the current startup script.
|
||||
*/
|
||||
const std::wstring& GetStartupScript();
|
||||
|
||||
/**
|
||||
* Set the initial map settings (as a JSON string), which will be used
|
||||
* to set up the simulation state.
|
||||
*/
|
||||
void SetMapSettings(const utf16string& settings);
|
||||
|
||||
/**
|
||||
* Get the current map settings as a UTF-8 JSON string.
|
||||
*/
|
||||
std::string GetMapSettings();
|
||||
|
||||
/**
|
||||
* Reload any scripts that were loaded from the given filename.
|
||||
* (This is used to implement hotloading.)
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
#include "lib/secure_crt.h"
|
||||
#include "lib/utf8.h"
|
||||
#include "ps/CStr.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
@ -106,7 +107,7 @@ void CDebugSerializer::PutNumber(const char* name, double value)
|
||||
|
||||
void CDebugSerializer::PutNumber(const char* name, fixed value)
|
||||
{
|
||||
m_Stream << INDENT << name << ": " << canonfloat(value.ToDouble(), 11) << "\n";
|
||||
m_Stream << INDENT << name << ": " << value.ToString() << "\n";
|
||||
}
|
||||
|
||||
void CDebugSerializer::PutBool(const char* name, bool value)
|
||||
|
@ -71,6 +71,7 @@ CComponentManager::CComponentManager(CSimContext& context, bool skipScriptFuncti
|
||||
m_ScriptInterface.RegisterFunction<void, std::string, CComponentManager::Script_RegisterMessageType> ("RegisterMessageType");
|
||||
m_ScriptInterface.RegisterFunction<void, std::string, CScriptVal, CComponentManager::Script_RegisterGlobal> ("RegisterGlobal");
|
||||
m_ScriptInterface.RegisterFunction<IComponent*, int, int, CComponentManager::Script_QueryInterface> ("QueryInterface");
|
||||
m_ScriptInterface.RegisterFunction<std::vector<int>, int, CComponentManager::Script_GetEntitiesWithInterface> ("GetEntitiesWithInterface");
|
||||
m_ScriptInterface.RegisterFunction<void, int, int, CScriptVal, CComponentManager::Script_PostMessage> ("PostMessage");
|
||||
m_ScriptInterface.RegisterFunction<void, int, CScriptVal, CComponentManager::Script_BroadcastMessage> ("BroadcastMessage");
|
||||
m_ScriptInterface.RegisterFunction<int, std::string, CComponentManager::Script_AddEntity> ("AddEntity");
|
||||
@ -344,6 +345,17 @@ IComponent* CComponentManager::Script_QueryInterface(void* cbdata, int ent, int
|
||||
return component;
|
||||
}
|
||||
|
||||
std::vector<int> CComponentManager::Script_GetEntitiesWithInterface(void* cbdata, int iid)
|
||||
{
|
||||
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
|
||||
|
||||
std::vector<int> ret;
|
||||
const std::map<entity_id_t, IComponent*>& ents = componentManager->GetEntitiesWithInterface(iid);
|
||||
for (std::map<entity_id_t, IComponent*>::const_iterator it = ents.begin(); it != ents.end(); ++it)
|
||||
ret.push_back(it->first);
|
||||
return ret;
|
||||
}
|
||||
|
||||
CMessage* CComponentManager::ConstructMessage(int mtid, CScriptVal data)
|
||||
{
|
||||
if (mtid == MT__Invalid || mtid > (int)m_MessageTypeIdsByName.size()) // (IDs start at 1 so use '>' here)
|
||||
|
@ -219,6 +219,7 @@ private:
|
||||
static void Script_RegisterMessageType(void* cbdata, std::string name);
|
||||
static void Script_RegisterGlobal(void* cbdata, std::string name, CScriptVal value);
|
||||
static IComponent* Script_QueryInterface(void* cbdata, int ent, int iid);
|
||||
static std::vector<int> Script_GetEntitiesWithInterface(void* cbdata, int iid);
|
||||
static void Script_PostMessage(void* cbdata, int ent, int mtid, CScriptVal data);
|
||||
static void Script_BroadcastMessage(void* cbdata, int mtid, CScriptVal data);
|
||||
static int Script_AddEntity(void* cbdata, std::string templateName);
|
||||
|
Loading…
Reference in New Issue
Block a user