mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 13:23:56 -07:00
Replace New dialog box with separate tools for resizing maps and replacing terrain textures, to provide more power and to simplify the problem of initialising map settings. Fix engine to cope with dynamic map resizing. Add JSON support to AtObj, to let C++ interact with JSON more easily. This was SVN commit r9566.
268 lines
6.6 KiB
C++
268 lines
6.6 KiB
C++
/* 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 "AtlasObject.h"
|
|
#include "AtlasObjectImpl.h"
|
|
|
|
#include "../AtlasScript/ScriptInterface.h"
|
|
|
|
#include "wx/log.h"
|
|
|
|
#include <sstream>
|
|
|
|
static AtSmartPtr<AtNode> ConvertNode(JSContext* cx, jsval node);
|
|
|
|
AtObj AtlasObject::LoadFromJSON(JSContext* cx, const std::string& json)
|
|
{
|
|
// Convert UTF8 to UTF16
|
|
wxString jsonW(json.c_str(), wxConvUTF8);
|
|
size_t json16len;
|
|
wxCharBuffer json16 = wxMBConvUTF16().cWC2MB(jsonW.c_str(), jsonW.Length(), &json16len);
|
|
|
|
jsval vp = JSVAL_NULL;
|
|
JSONParser* parser = JS_BeginJSONParse(cx, &vp);
|
|
if (!parser)
|
|
{
|
|
wxLogError(_T("ParseJSON failed to begin"));
|
|
return AtObj();
|
|
}
|
|
|
|
if (!JS_ConsumeJSONText(cx, parser, reinterpret_cast<const jschar*>(json16.data()), (uint32)(json16len/2)))
|
|
{
|
|
wxLogError(_T("ParseJSON failed to consume"));
|
|
return AtObj();
|
|
}
|
|
|
|
if (!JS_FinishJSONParse(cx, parser, JSVAL_NULL))
|
|
{
|
|
wxLogError(_T("ParseJSON failed to finish"));
|
|
return AtObj();
|
|
}
|
|
|
|
AtObj obj;
|
|
obj.p = ConvertNode(cx, vp);
|
|
|
|
return obj;
|
|
}
|
|
|
|
// Convert from a jsval to an AtNode
|
|
static AtSmartPtr<AtNode> ConvertNode(JSContext* cx, jsval node)
|
|
{
|
|
AtSmartPtr<AtNode> obj (new AtNode());
|
|
|
|
// Non-objects get converted into strings
|
|
if (!JSVAL_IS_OBJECT(node))
|
|
{
|
|
JSString* str = JS_ValueToString(cx, node);
|
|
if (!str)
|
|
return obj; // error
|
|
size_t valueLen;
|
|
const jschar* valueChars = JS_GetStringCharsAndLength(str, &valueLen);
|
|
if (!valueChars)
|
|
return obj; // error
|
|
wxString valueWx(reinterpret_cast<const char*>(valueChars), wxMBConvUTF16(), valueLen*2);
|
|
|
|
obj->value = valueWx.c_str();
|
|
|
|
// Annotate numbers/booleans specially, to allow round-tripping
|
|
if (JSVAL_IS_NUMBER(node))
|
|
{
|
|
obj->children.insert(AtNode::child_pairtype(
|
|
"@number", AtSmartPtr<AtNode>(new AtNode())
|
|
));
|
|
}
|
|
else if (JSVAL_IS_BOOLEAN(node))
|
|
{
|
|
obj->children.insert(AtNode::child_pairtype(
|
|
"@boolean", AtSmartPtr<AtNode>(new AtNode())
|
|
));
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
JSObject* it = JS_NewPropertyIterator(cx, JSVAL_TO_OBJECT(node));
|
|
if (!it)
|
|
return obj; // error
|
|
|
|
while (true)
|
|
{
|
|
jsid idp;
|
|
jsval val;
|
|
if (! JS_NextProperty(cx, it, &idp) || ! JS_IdToValue(cx, idp, &val))
|
|
return obj; // error
|
|
if (val == JSVAL_VOID)
|
|
break; // end of iteration
|
|
if (! JSVAL_IS_STRING(val))
|
|
continue; // ignore integer properties
|
|
|
|
JSString* name = JSVAL_TO_STRING(val);
|
|
size_t len = JS_GetStringLength(name);
|
|
jschar* chars = JS_GetStringChars(name);
|
|
wxString nameWx(reinterpret_cast<char*>(chars), wxMBConvUTF16(), len*2);
|
|
std::string nameStr(nameWx.ToUTF8().data());
|
|
|
|
jsval vp;
|
|
if (!JS_GetPropertyById(cx, JSVAL_TO_OBJECT(node), idp, &vp))
|
|
return obj; // error
|
|
|
|
// Unwrap arrays into a special format like <$name><item>$i0</item><item>...
|
|
// (This assumes arrays aren't nested)
|
|
if (JSVAL_IS_OBJECT(vp) && JS_IsArrayObject(cx, JSVAL_TO_OBJECT(vp)))
|
|
{
|
|
AtSmartPtr<AtNode> child(new AtNode());
|
|
child->children.insert(AtNode::child_pairtype(
|
|
"@array", AtSmartPtr<AtNode>(new AtNode())
|
|
));
|
|
|
|
jsuint arrayLength;
|
|
if (!JS_GetArrayLength(cx, JSVAL_TO_OBJECT(vp), &arrayLength))
|
|
return obj; // error
|
|
|
|
for (jsuint i = 0; i < arrayLength; ++i)
|
|
{
|
|
jsval val;
|
|
if (!JS_GetElement(cx, JSVAL_TO_OBJECT(vp), i, &val))
|
|
return obj; // error
|
|
|
|
child->children.insert(AtNode::child_pairtype(
|
|
"item", ConvertNode(cx, val)
|
|
));
|
|
}
|
|
|
|
obj->children.insert(AtNode::child_pairtype(
|
|
nameStr, child
|
|
));
|
|
}
|
|
else
|
|
{
|
|
obj->children.insert(AtNode::child_pairtype(
|
|
nameStr, ConvertNode(cx, vp)
|
|
));
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
|
|
jsval BuildJSVal(JSContext* cx, AtNode::Ptr p)
|
|
{
|
|
if (!p)
|
|
return JSVAL_VOID;
|
|
|
|
// Special case for numbers/booleans to allow round-tripping
|
|
if (p->children.count("@number"))
|
|
{
|
|
// Convert to double
|
|
std::wstringstream str;
|
|
str << p->value;
|
|
double val = 0;
|
|
str >> val;
|
|
|
|
jsval rval;
|
|
if (!JS_NewNumberValue(cx, val, &rval))
|
|
return JSVAL_VOID; // error
|
|
return rval;
|
|
}
|
|
else if (p->children.count("@boolean"))
|
|
{
|
|
bool val = false;
|
|
if (p->value == L"true")
|
|
val = true;
|
|
|
|
return BOOLEAN_TO_JSVAL(val);
|
|
}
|
|
|
|
// If no children, then use the value string instead
|
|
if (p->children.empty())
|
|
{
|
|
size_t val16len;
|
|
wxCharBuffer val16 = wxMBConvUTF16().cWC2MB(p->value.c_str(), p->value.length(), &val16len);
|
|
|
|
JSString* str = JS_NewUCStringCopyN(cx, reinterpret_cast<const jschar*>(val16.data()), (uint32)(val16len/2));
|
|
if (!str)
|
|
return JSVAL_VOID; // error
|
|
return STRING_TO_JSVAL(str);
|
|
}
|
|
|
|
if (p->children.find("@array") != p->children.end())
|
|
{
|
|
JSObject* obj = JS_NewArrayObject(cx, 0, NULL);
|
|
if (!obj)
|
|
return JSVAL_VOID; // error
|
|
|
|
// Find the <item> children
|
|
AtNode::child_maptype::const_iterator lower = p->children.lower_bound("item");
|
|
AtNode::child_maptype::const_iterator upper = p->children.upper_bound("item");
|
|
|
|
jsint idx = 0;
|
|
for (AtNode::child_maptype::const_iterator it = lower; it != upper; ++it)
|
|
{
|
|
jsval val = BuildJSVal(cx, it->second);
|
|
if (!JS_SetElement(cx, obj, idx, &val))
|
|
return JSVAL_VOID; // error
|
|
|
|
++idx;
|
|
}
|
|
|
|
return OBJECT_TO_JSVAL(obj);
|
|
}
|
|
else
|
|
{
|
|
JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL);
|
|
if (!obj)
|
|
return JSVAL_VOID; // error
|
|
|
|
for (AtNode::child_maptype::const_iterator it = p->children.begin(); it != p->children.end(); ++it)
|
|
{
|
|
jsval val = BuildJSVal(cx, it->second);
|
|
if (!JS_SetProperty(cx, obj, it->first.c_str(), &val))
|
|
return JSVAL_VOID; // error
|
|
}
|
|
|
|
return OBJECT_TO_JSVAL(obj);
|
|
}
|
|
}
|
|
|
|
struct Stringifier
|
|
{
|
|
static JSBool callback(const jschar* buf, uint32 len, void* data)
|
|
{
|
|
wxString textWx(reinterpret_cast<const char*>(buf), wxMBConvUTF16(), len*2);
|
|
std::string textStr(textWx.ToUTF8().data());
|
|
|
|
static_cast<Stringifier*>(data)->stream << textStr;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
std::stringstream stream;
|
|
};
|
|
|
|
std::string AtlasObject::SaveToJSON(JSContext* cx, AtObj& obj)
|
|
{
|
|
jsval root = BuildJSVal(cx, obj.p);
|
|
|
|
Stringifier str;
|
|
if (!JS_Stringify(cx, &root, NULL, JSVAL_VOID, &Stringifier::callback, &str))
|
|
{
|
|
wxLogError(_T("SaveToJSON failed"));
|
|
return "";
|
|
}
|
|
|
|
return str.stream.str();
|
|
}
|