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:
Ykkrosh 2010-08-04 21:15:41 +00:00
parent a5171d9145
commit 65bcedb9fc
21 changed files with 415 additions and 28 deletions

View File

@ -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.";
// Describe the number of players
var playerString = "";
if (mapSettings.NumPlayers)
playerString = mapSettings.NumPlayers + " " + (mapSettings.NumPlayers == 1 ? "player" : "players") + ". ";
getGUIObjectByName("mapInfoDescription").caption = description;
getGUIObjectByName("mapInfoDescription").caption = playerString + description;
g_IsInGuiUpdate = false;
}

Binary file not shown.

Binary file not shown.

View File

@ -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)
// Start attacking one of the newly-seen enemy (if any)
this.AttackVisibleEntity(msg.data.added);
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)

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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())
m_ComponentManager.GetScriptInterface().LoadScript(L"map startup script", m_StartupScript);
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);

View File

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

View File

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

View File

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

View File

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