diff --git a/binaries/data/tools/atlas/scripts/editorstate.js b/binaries/data/tools/atlas/scripts/editorstate.js index 2d9e3f8dd9..6ebccffe2b 100644 --- a/binaries/data/tools/atlas/scripts/editorstate.js +++ b/binaries/data/tools/atlas/scripts/editorstate.js @@ -1,10 +1,15 @@ +Atlas.RenderView = { + GAME: 1, + ACTOR: 2 +}; // TODO: this should probably be defined by the C++ code + // Define the default state settings. // (It's done this way so that this script can be dynamically reloaded, // and won't overwrite the previous runtime state but will still pick // up any new properties) var defaults = { objectSettings: { - view: undefined, + view: Atlas.RenderView.GAME, selectedObjects: [], playerID: 1, actorSelections: [], @@ -20,18 +25,21 @@ defaults.objectSettings.toSObjectSettings = function () { } defaults.objectSettings.onSelectionChange = function () { - if (! this.selectedObjects.length) - return; // TODO: do something sensible here + if (! this.selectedObjects.length) { + // TODO: do something sensible here + this.actorSelections = []; + this.variantGroups = []; + } else { + // TODO: Support multiple selections + var selection = this.selectedObjects[0]; - // TODO: Support multiple selections - var selection = this.selectedObjects[0]; - - var settings = Atlas.Message.GetObjectSettings(this.view, selection).settings; - - this.playerID = settings.player; - this.actorSelections = settings.selections; - this.variantGroups = settings.variantgroups; + var settings = Atlas.Message.GetObjectSettings(this.view, selection).settings; + if (settings.player != -1) + this.playerID = settings.player; + this.actorSelections = settings.selections; + this.variantGroups = settings.variantgroups; + } this.notifyObservers(); } @@ -95,6 +103,7 @@ function makeObservable(obj) function postObjectSettingsToGame(objectSettings) { + // TODO: if view != GAME, we don't really want this to be an undoable command if (objectSettings.selectedObjects.length) Atlas.Message.SetObjectSettings(objectSettings.view, objectSettings.selectedObjects[0], objectSettings.toSObjectSettings()); diff --git a/binaries/data/tools/atlas/scripts/section/object.js b/binaries/data/tools/atlas/scripts/section/object.js index 602aceb131..f237ffca82 100644 --- a/binaries/data/tools/atlas/scripts/section/object.js +++ b/binaries/data/tools/atlas/scripts/section/object.js @@ -13,6 +13,145 @@ function setObjectFilter(objectList, objects, type) objectList.thaw(); } +var actorViewer = { + active: false, + distance: 20, + angle: 0, + elevation: Math.PI / 6, + actor: "(n) structures/fndn_1x1.xml", + animation: "idle", + // Animation playback speed + speed: 0, + // List of controls which should be hidden, and only shown when the Actor Viewer is active + controls: [] +}; + +actorViewer.toggle = function () { + if (this.active) { + // TODO: maybe this should switch back to whatever was selected before, + // not necessarily PlaceObject + Atlas.SetCurrentToolWith('PlaceObject', this.actor); + } else { + Atlas.SetCurrentToolWithVal('ScriptedTool', this); + } +}; + +actorViewer.postToGame = function () { + Atlas.Message.SetActorViewer(this.actor, this.animation, this.speed, false); +}; + +actorViewer.postLookAt = function () { + var offset = 0.3; // slight fudge so we turn nicely when going over the top of the unit + var pos = { + x: this.distance*Math.cos(this.elevation)*Math.sin(this.angle) + offset*Math.cos(this.angle), + y: this.distance*Math.sin(this.elevation), + z: this.distance*Math.cos(this.elevation)*Math.cos(this.angle) - offset*Math.sin(this.angle) + }; + Atlas.Message.LookAt(Atlas.RenderView.ACTOR, pos, {x:0, y:0, z:0}); +}; + +actorViewer.setActor = function (actor) { + this.actor = actor; + if (this.active) { + this.postToGame(); + Atlas.State.objectSettings.onSelectionChange(); + } +} + +actorViewer.onEnable = function () { + this.active = true; + this.button.label = this.buttonTextActive; + for each (ctrl in actorViewer.controls) + ctrl.show(true); + this.postToGame(); + Atlas.State.objectSettings.view = Atlas.RenderView.ACTOR; + Atlas.State.objectSettings.selectedObjects = [0]; + Atlas.State.objectSettings.onSelectionChange(); // (must come after postToGame) + this.postLookAt(); + Atlas.Message.RenderEnable(Atlas.RenderView.ACTOR); +} + +actorViewer.onDisable = function () { + this.active = false; + this.button.label = this.buttonTextInactive; + for each (ctrl in actorViewer.controls) + ctrl.show(false); + Atlas.State.objectSettings.view = Atlas.RenderView.GAME; + Atlas.State.objectSettings.selectedObjects = []; + Atlas.State.objectSettings.onSelectionChange(); + Atlas.Message.RenderEnable(Atlas.RenderView.GAME); +} + +actorViewer.onKey = function (evt, type) { + var code0 = '0'.charCodeAt(0); + var code9 = '9'.charCodeAt(0); + if (type == 'down' && evt.keyCode >= code0 && evt.keyCode <= code9) { + // (TODO: this should probably be 'char' not 'down'; but we don't get + // 'char' unless we return false from this function, in which case the + // scenario editor intercepts some other keys for itself) + Atlas.State.objectSettings.playerID = evt.keyCode - code0; + Atlas.State.objectSettings.notifyObservers(); + } + + // Prevent keys from passing through to the scenario editor + return true; +} + +actorViewer.onMouse = function (evt) { + var cameraChanged = false; + + var speedModifier = this.getSpeedModifier(); + + if (evt.wheelRotation) { + var speed = -1 * speedModifier; + this.distance += evt.wheelRotation * speed / evt.wheelDelta; + cameraChanged = true; + } + + if (evt.leftDown || evt.rightDown) { + this.mouseLastX = evt.x; + this.mouseLastY = evt.y; + this.mouseLastValid = true; + } else if (evt.dragging && this.mouseLastValid && (evt.leftIsDown || evt.rightIsDown)) { + var dx = evt.x - this.mouseLastX; + var dy = evt.y - this.mouseLastY; + this.mouseLastX = evt.x; + this.mouseLastY = evt.y; + this.angle += dx * Math.PI/256 * speedModifier; + if (evt.leftIsDown) + this.distance += (dy / 8) * speedModifier; + else // evt.rightIsDown + this.elevation += (dy * Math.PI/256) * speedModifier; + cameraChanged = true; + } else if ((evt.leftUp || evt.rightUp) && ! (evt.leftDown || evt.rightDown)) { + // In some situations (e.g. double-clicking the title bar to + // maximise the window) we get a dragging event without the matching + // buttondown; so disallow dragging when all buttons were released since + // the last buttondown. + // (TODO: does this problem affect the scenario editor too?) + this.mouseLastValid = false; + } + + if (cameraChanged) { + this.distance = Math.max(this.distance, 1/64); // don't let people fly through the origin + this.postLookAt(); + } + + return true; +}; + +actorViewer.getSpeedModifier = function () { // TODO: this should be shared with the rest of the application + if (wxGetKeyState(wxKeyCode.SHIFT) && wxGetKeyState(wxKeyCode.CONTROL)) + return 1/64; + else if (wxGetKeyState(wxKeyCode.CONTROL)) + return 1/4; + else if (wxGetKeyState(wxKeyCode.SHIFT)) + return 4; + else + return 1; +}; + + var g_observer; function init(window, bottomWindow) @@ -23,31 +162,41 @@ function init(window, bottomWindow) var objectList = new wxListBox(window, -1, wxDefaultPosition, wxDefaultSize, [], wxListBox.SINGLE | wxListBox.HSCROLL); + var objectType = 0; objectList.onListBox = function (evt) { if (evt.selection == -1) return; var id = objectList.objectIDs[evt.selection]; - Atlas.SetCurrentToolWith('PlaceObject', id); + + actorViewer.setActor(id); + if (! actorViewer.active) + Atlas.SetCurrentToolWith('PlaceObject', id); }; - setObjectFilter(objectList, objects, 0); + setObjectFilter(objectList, objects, objectType); var groupSelector = new wxChoice(window, -1, wxDefaultPosition, wxDefaultSize, ["Entities", "Actors (all)"] ); groupSelector.onChoice = function (evt) { - setObjectFilter(objectList, objects, evt.selection); + objectType = evt.selection; + setObjectFilter(objectList, objects, objectType); }; window.sizer.add(groupSelector, 0, wxStretch.EXPAND); window.sizer.add(objectList, 1, wxStretch.EXPAND); - - - bottomWindow.sizer = new wxBoxSizer(wxOrientation.VERTICAL); + + + var viewerButton = new wxButton(window, -1, "Switch to Actor Viewer"); + actorViewer.button = viewerButton; + actorViewer.buttonTextInactive = "Switch to Actor Viewer"; + actorViewer.buttonTextActive = "Switch to game view"; + viewerButton.onClicked = function () { actorViewer.toggle(); } + window.sizer.add(viewerButton, 0, wxStretch.EXPAND); + + var playerSelector = new wxChoice(bottomWindow, -1, wxDefaultPosition, wxDefaultSize, ["Gaia", "Player 1", "Player 2", "Player 3", "Player 4", "Player 5", "Player 6", "Player 7", "Player 8"] ); - bottomWindow.sizer.add(playerSelector); - playerSelector.selection = Atlas.State.objectSettings.playerID; playerSelector.onChoice = function (evt) { Atlas.State.objectSettings.playerID = evt.selection; @@ -57,6 +206,46 @@ function init(window, bottomWindow) playerSelector.selection = Atlas.State.objectSettings.playerID; } + var animationBoxBox = new wxStaticBox(bottomWindow, -1, "Animation"); + actorViewer.controls.push(animationBoxBox); + var animationBox = new wxStaticBoxSizer(animationBoxBox, wxOrientation.VERTICAL); + var animationSelector = new wxChoice(bottomWindow, -1, wxDefaultPosition, wxDefaultSize, + [ "attack1", "attack2", "build", "corpse", "death", + "gather_fruit", "gather_grain", "gather_wood", "gather_stone", "gather_metal", + "idle", "melee", "run", "walk" ] // TODO: this list should come from the actor + ); + animationSelector.stringSelection = "idle"; + actorViewer.controls.push(animationSelector); + animationSelector.onChoice = function (evt) { + actorViewer.animation = evt.string; + actorViewer.postToGame(); + }; + var animationSpeedSizer = new wxBoxSizer(wxOrientation.HORIZONTAL); + var speeds = [ ['Play', 1], ['Pause', 0], ['Slow', 0.1] ]; + for each (var speed in speeds) { + var button = new wxButton(bottomWindow, -1, speed[0], wxDefaultPosition, new wxSize(50, -1)); + actorViewer.controls.push(button); + (function (s) { // local scope for closure + button.onClicked = function () { + actorViewer.speed = s; + actorViewer.postToGame(); + }; + })(speed[1]); + animationSpeedSizer.add(button); + } + animationBox.add(animationSelector, 0, wxStretch.EXPAND); + animationBox.add(animationSpeedSizer, 0, wxStretch.EXPAND); + + var animationSizer = new wxBoxSizer(wxOrientation.VERTICAL); + animationSizer.minSize = new wxSize(160, -1); + animationSizer.add(playerSelector, 0, wxStretch.EXPAND); + animationSizer.add(animationBox, 0, wxStretch.EXPAND); + + + + for each (ctrl in actorViewer.controls) + ctrl.show(false); + var variationControl = new wxScrolledWindow(bottomWindow, -1); variationControl.setScrollRate(0, 5); @@ -64,7 +253,6 @@ function init(window, bottomWindow) var variationControlBox = new wxStaticBoxSizer(new wxStaticBox(bottomWindow, -1, "Actor Variation"), wxOrientation.VERTICAL); variationControl.sizer.minSize = new wxSize(160, -1); variationControlBox.add(variationControl, 1); - bottomWindow.sizer.add(variationControlBox, 1); function onVariationSelect() { // It's possible for a variant name to appear in multiple groups. @@ -103,6 +291,12 @@ function init(window, bottomWindow) bottomWindow.sizer.layout(); } + + bottomWindow.sizer = new wxBoxSizer(wxOrientation.HORIZONTAL); + bottomWindow.sizer.add(animationSizer); + bottomWindow.sizer.add(variationControlBox, 0, wxStretch.EXPAND); + + g_observer = function() { updatePlayerSelector(); updateVariationControl(); diff --git a/source/tools/atlas/AtlasScript/ScriptInterface.cpp b/source/tools/atlas/AtlasScript/ScriptInterface.cpp index 7fafbe4016..a55452b24d 100644 --- a/source/tools/atlas/AtlasScript/ScriptInterface.cpp +++ b/source/tools/atlas/AtlasScript/ScriptInterface.cpp @@ -37,6 +37,9 @@ #include "wxJS/gui/init.h" #include "wxJS/gui/control/panel.h" #include "wxJS/gui/misc/bitmap.h" +#include "wxJS/gui/event/jsevent.h" +#include "wxJS/gui/event/key.h" +#include "wxJS/gui/event/mouse.h" #include "GameInterface/Shareable.h" #include "GameInterface/Messages.h" @@ -77,8 +80,10 @@ namespace #endif } - // Use templated structs instead of functions, so that we can use partial specialisation: - + // Report runtime errors for unhandled types, so we don't have to bother + // defining all the types in advance. (TODO: at some point we should + // define all the types and then remove this bit so the errors are found + // at link-time.) template struct FromJSVal { static bool Convert(JSContext* cx, jsval WXUNUSED(v), T& WXUNUSED(out)) @@ -132,6 +137,15 @@ namespace } }; + template<> struct FromJSVal + { + static bool Convert(JSContext* WXUNUSED(cx), jsval v, jsval& out) + { + out = v; + return true; + } + }; + template<> struct FromJSVal { static bool Convert(JSContext* cx, jsval v, std::wstring& out) @@ -197,8 +211,11 @@ namespace }; //////////////////////////////////////////////////////////////// - // Primitive types: + // Report runtime errors for unhandled types, so we don't have to bother + // defining all the types in advance. (TODO: at some point we should + // define all the types and then remove this bit so the errors are found + // at link-time.) template struct ToJSVal { static jsval Convert(JSContext* cx, const T& WXUNUSED(val)) @@ -208,7 +225,10 @@ namespace } }; - /*template<> struct ToJSVal + //////////////////////////////////////////////////////////////// + // Primitive types: + + template<> struct ToJSVal { static jsval Convert(JSContext* cx, const float& val) { @@ -216,7 +236,7 @@ namespace JS_NewDoubleValue(cx, val, &rval); // ignore return value return rval; } - };*/ + }; template<> struct ToJSVal { @@ -264,6 +284,31 @@ namespace } }; + //////////////////////////////////////////////////////////////// + // wxJS types: + + template<> struct ToJSVal + { + static jsval Convert(JSContext* cx, const wxKeyEvent& val) + { + wxKeyEvent& evt = const_cast(val); // ugly, but needed for wxJS + wxjs::gui::PrivKeyEvent *jsEvent = new wxjs::gui::PrivKeyEvent(evt); + jsEvent->SetScoop(false); // (wxJS will clone the event now, and not modify the const version) + return wxjs::gui::KeyEvent::CreateObject(cx, jsEvent); + } + }; + + template<> struct ToJSVal + { + static jsval Convert(JSContext* cx, const wxMouseEvent& val) + { + wxMouseEvent& evt = const_cast(val); // see comments above for KeyEvent + wxjs::gui::PrivMouseEvent *jsEvent = new wxjs::gui::PrivMouseEvent(evt); + jsEvent->SetScoop(false); + return wxjs::gui::MouseEvent::CreateObject(cx, jsEvent); + } + }; + //////////////////////////////////////////////////////////////// // Compound types: @@ -295,6 +340,34 @@ namespace //////////////////////////////////////////////////////////////// // AtlasMessage structures: + template<> struct FromJSVal + { + static bool Convert(JSContext* cx, jsval v, AtlasMessage::Position& out) + { + JSObject* obj; + if (! JS_ValueToObject(cx, v, &obj) || obj == NULL) + FAIL("Argument must be an object"); + jsval val; + + float x, y, z; + if (! JS_GetProperty(cx, obj, "x", &val)) + FAIL("Failed to get 'x'"); + if (! ScriptInterface::FromJSVal(cx, val, x)) + FAIL("Failed to convert 'x'"); + if (! JS_GetProperty(cx, obj, "y", &val)) + FAIL("Failed to get 'y'"); + if (! ScriptInterface::FromJSVal(cx, val, y)) + FAIL("Failed to convert 'y'"); + if (! JS_GetProperty(cx, obj, "z", &val)) + FAIL("Failed to get 'z'"); + if (! ScriptInterface::FromJSVal(cx, val, z)) + FAIL("Failed to convert 'z'"); + + out = AtlasMessage::Position(x, y, z); + return true; + } + }; + template<> struct ToJSVal { static jsval Convert(JSContext* cx, const AtlasMessage::sTerrainGroupPreview& val) @@ -352,7 +425,7 @@ namespace { JSObject* obj; if (! JS_ValueToObject(cx, v, &obj) || obj == NULL) - FAIL("Argument must be an array"); + FAIL("Argument must be an object"); jsval val; int player; @@ -375,6 +448,7 @@ namespace return true; } }; + } template bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, T& out) @@ -391,8 +465,12 @@ template jsval ScriptInterface::ToJSVal(JSContext* cx, const T& v) // but are required for linking with other files template bool ScriptInterface::FromJSVal(JSContext*, jsval, wxString&); template bool ScriptInterface::FromJSVal(JSContext*, jsval, float&); +template bool ScriptInterface::FromJSVal(JSContext*, jsval, jsval&); template jsval ScriptInterface::ToJSVal(JSContext*, wxString const&); +template jsval ScriptInterface::ToJSVal(JSContext*, wxKeyEvent const&); +template jsval ScriptInterface::ToJSVal(JSContext*, wxMouseEvent const&); template jsval ScriptInterface::ToJSVal(JSContext*, int const&); +template jsval ScriptInterface::ToJSVal(JSContext*, float const&); template jsval ScriptInterface::ToJSVal >(JSContext*, std::vector const&); //////////////////////////////////////////////////////////////// @@ -574,24 +652,43 @@ JSContext* ScriptInterface::GetContext() return m->m_cx; } -void ScriptInterface::AddRoot(void* ptr) +bool ScriptInterface::AddRoot(void* ptr) { - JS_AddRoot(m->m_cx, ptr); + return JS_AddRoot(m->m_cx, ptr); } -void ScriptInterface::RemoveRoot(void* ptr) +bool ScriptInterface::RemoveRoot(void* ptr) { - JS_RemoveRoot(m->m_cx, ptr); + return JS_RemoveRoot(m->m_cx, ptr); } -void ScriptInterface::SetValue_(const wxString& name, jsval val) +ScriptInterface::LocalRootScope::LocalRootScope(ScriptInterface& scriptInterface) + : m_ScriptInterface(scriptInterface) +{ + m_OK = JS_EnterLocalRootScope(m_ScriptInterface.m->m_cx); +} + +ScriptInterface::LocalRootScope::~LocalRootScope() +{ + if (m_OK) + JS_LeaveLocalRootScope(m_ScriptInterface.m->m_cx); +} + +bool ScriptInterface::LocalRootScope::OK() +{ + return m_OK; +} + + +bool ScriptInterface::SetValue_(const wxString& name, jsval val) { jsval jsName = ToJSVal(m->m_cx, name); const uintN argc = 2; jsval argv[argc] = { jsName, val }; jsval rval; - JSBool ok = JS_CallFunctionName(m->m_cx, m->m_glob, "setValue", argc, argv, &rval); // TODO: error checking + JSBool ok = JS_CallFunctionName(m->m_cx, m->m_glob, "setValue", argc, argv, &rval); + return ok; } bool ScriptInterface::GetValue_(const wxString& name, jsval& ret) @@ -603,12 +700,32 @@ bool ScriptInterface::GetValue_(const wxString& name, jsval& ret) return JS_CallFunctionName(m->m_cx, m->m_glob, "getValue", argc, argv, &ret); } -void ScriptInterface::Eval(const wxString& script) +bool ScriptInterface::CallFunction(jsval val, const char* name) +{ + jsval jsRet; + std::vector argv; + return CallFunction_(val, name, argv, jsRet); +} + +bool ScriptInterface::CallFunction_(jsval val, const char* name, std::vector& args, jsval& ret) +{ + const uintN argc = args.size(); + jsval* argv = NULL; + if (argc) + argv = &args[0]; + wxCHECK(JSVAL_IS_OBJECT(val), false); + JSBool found; + wxCHECK(JS_HasProperty(m->m_cx, JSVAL_TO_OBJECT(val), name, &found), false); + if (! found) + return false; + return JS_CallFunctionName(m->m_cx, JSVAL_TO_OBJECT(val), name, argc, argv, &ret); +} + +bool ScriptInterface::Eval(const wxString& script) { jsval rval; - JSBool ok = JS_EvaluateScript(m->m_cx, m->m_glob, - script.mb_str(), script.length(), NULL, 0, &rval); - // TODO: error checking + JSBool ok = JS_EvaluateScript(m->m_cx, m->m_glob, script.mb_str(), script.length(), NULL, 0, &rval); + return ok; } bool ScriptInterface::Eval_(const wxString& script, jsval& rval) diff --git a/source/tools/atlas/AtlasScript/ScriptInterface.h b/source/tools/atlas/AtlasScript/ScriptInterface.h index bfab1766d3..4ccdef4ad4 100644 --- a/source/tools/atlas/AtlasScript/ScriptInterface.h +++ b/source/tools/atlas/AtlasScript/ScriptInterface.h @@ -16,6 +16,7 @@ */ #include +#include #ifdef _WIN32 # define XP_WIN @@ -49,9 +50,16 @@ public: void SetCallbackData(void* cbdata); static void* GetCallbackData(JSContext* cx); - template void SetValue(const wxString& name, const T& val); + template bool SetValue(const wxString& name, const T& val); template bool GetValue(const wxString& name, T& ret); - void Eval(const wxString& script); + + bool CallFunction(jsval val, const char* name); + template + bool CallFunction(jsval val, const char* name, const T& a, R& ret); + template + bool CallFunction(jsval val, const char* name, const T& a, const S& b, R& ret); + + bool Eval(const wxString& script); template bool Eval(const wxString& script, T& ret); // Defined elsewhere: @@ -64,14 +72,36 @@ public: wxPanel* LoadScriptAsPanel(const wxString& name, wxWindow* parent); std::pair LoadScriptAsSidebar(const wxString& name, wxWindow* side, wxWindow* bottom); + // Convert a jsval to a C++ type. (This might trigger GC.) template static bool FromJSVal(JSContext* cx, jsval val, T& ret); + // Convert a C++ type to a jsval. (This might trigger GC. The return + // value must be rooted if you don't want it to be collected.) template static jsval ToJSVal(JSContext* cx, const T& val); + + bool AddRoot(void* ptr); + bool RemoveRoot(void* ptr); + + // Helper class for automatically rooting values + class LocalRootScope + { + ScriptInterface& m_ScriptInterface; + bool m_OK; + public: + // Tries to enter local root scope, so newly created + // values won't be GCed. This might fail, so check OK() + LocalRootScope(ScriptInterface& scriptInterface); + // Returns true if the local root scope was successfully entered + bool OK(); + // Leaves the local root scope + ~LocalRootScope(); + }; + #define LOCAL_ROOT_SCOPE LocalRootScope scope(*this); if (! scope.OK()) return false + private: JSContext* GetContext(); - void AddRoot(void* ptr); - void RemoveRoot(void* ptr); - void SetValue_(const wxString& name, jsval val); + bool SetValue_(const wxString& name, jsval val); bool GetValue_(const wxString& name, jsval& ret); + bool CallFunction_(jsval val, const char* name, std::vector& args, jsval& ret); bool Eval_(const wxString& name, jsval& ret); void Register(const char* name, JSNative fptr, size_t nargs); @@ -81,7 +111,7 @@ private: #include "NativeWrapper.inl" template -void ScriptInterface::SetValue(const wxString& name, const T& val) +bool ScriptInterface::SetValue(const wxString& name, const T& val) { return SetValue_(name, ToJSVal(GetContext(), val)); } @@ -89,21 +119,42 @@ void ScriptInterface::SetValue(const wxString& name, const T& val) template bool ScriptInterface::GetValue(const wxString& name, T& ret) { + LOCAL_ROOT_SCOPE; jsval jsRet; if (! GetValue_(name, jsRet)) return false; - AddRoot(&jsRet); // root it while we do some more work (TODO: is this really necessary?) - bool ok = FromJSVal(GetContext(), jsRet, ret); - RemoveRoot(&jsRet); - return ok; + return FromJSVal(GetContext(), jsRet, ret); +} + +template +bool ScriptInterface::CallFunction(jsval val, const char* name, const T& a, R& ret) +{ + LOCAL_ROOT_SCOPE; + jsval jsRet; + std::vector argv; + argv.push_back(ToJSVal(GetContext(), a)); + bool ok = CallFunction_(val, name, argv, jsRet); + if (! ok) return false; + return FromJSVal(GetContext(), jsRet, ret); +} + +template +bool ScriptInterface::CallFunction(jsval val, const char* name, const T& a, const S& b, R& ret) +{ + LOCAL_ROOT_SCOPE; + jsval jsRet; + std::vector argv; + argv.push_back(ToJSVal(GetContext(), a)); + argv.push_back(ToJSVal(GetContext(), b)); + bool ok = CallFunction_(val, name, argv, jsRet); + if (! ok) return false; + return FromJSVal(GetContext(), jsRet, ret); } template bool ScriptInterface::Eval(const wxString& script, T& ret) { + LOCAL_ROOT_SCOPE; jsval jsRet; if (! Eval_(script, jsRet)) return false; - AddRoot(&jsRet); // root it while we do some more work - bool ok = FromJSVal(GetContext(), jsRet, ret); - RemoveRoot(&jsRet); - return ok; + return FromJSVal(GetContext(), jsRet, ret); } diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp index c040922eae..ac215a89ae 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp @@ -307,6 +307,11 @@ namespace static_cast(cbdata)->GetToolManager().SetCurrentTool(name, &arg); } + void SetCurrentToolWithVal(void* cbdata, wxString name, jsval arg) + { + static_cast(cbdata)->GetToolManager().SetCurrentTool(name, &arg); + } + wxString GetDataDirectory(void*) { return Datafile::GetDataDirectory(); @@ -347,6 +352,7 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterfac GetScriptInterface().RegisterFunction("GetDataDirectory"); GetScriptInterface().RegisterFunction("SetCurrentTool"); GetScriptInterface().RegisterFunction("SetCurrentToolWith"); + GetScriptInterface().RegisterFunction("SetCurrentToolWithVal"); GetScriptInterface().RegisterFunction("SetBrushStrength"); GetScriptInterface().RegisterFunction("SetSelectedTexture"); diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.cpp index 67db07713a..3f67375690 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.cpp @@ -43,7 +43,7 @@ AtlasMessage::sObjectSettings ObjectSettings::GetSettings() const { AtlasMessage::sObjectSettings settings; bool ok = m_ScriptInterface.Eval(_T("Atlas.State.objectSettings.toSObjectSettings()"), settings); - wxASSERT(ok); + wxCHECK(ok, AtlasMessage::sObjectSettings()); return settings; } diff --git a/source/tools/atlas/GameInterface/ActorViewer.cpp b/source/tools/atlas/GameInterface/ActorViewer.cpp index 3c3575628e..894660ffb2 100644 --- a/source/tools/atlas/GameInterface/ActorViewer.cpp +++ b/source/tools/atlas/GameInterface/ActorViewer.cpp @@ -40,6 +40,8 @@ #include "renderer/Renderer.h" #include "renderer/Scene.h" #include "renderer/SkyManager.h" +#include "simulation/EntityTemplateCollection.h" +#include "simulation/EntityTemplate.h" struct ActorViewerImpl : public Scene { @@ -84,6 +86,8 @@ ActorViewer::ActorViewer() { m.Unit = NULL; m.WalkEnabled = false; + m.GroundEnabled = true; + m.ShadowsEnabled = g_Renderer.GetOptionBool(CRenderer::OPT_SHADOWS); m.Background = SColor4ub(255, 255, 255, 255); // Set up the renderer @@ -129,10 +133,43 @@ void ActorViewer::UnloadObjects() m.ObjectManager.UnloadObjects(); } -void ActorViewer::SetActor(const CStrW& id, const CStrW& animation) +// We want to support selection of both entities and actors in the +// Actor Viewer tool, so work out the actor corresponding to the given +// string +static bool ParseObjectName(const CStrW& obj, CStrW& name) +{ + if (obj.substr(0, 4) == L"(e) ") + { + CStrW entname = obj.substr(4); + CEntityTemplate* entity = g_EntityTemplateCollection.GetTemplate(entname); + if (! entity) + return false; + name = entity->m_actorName; + return true; + } + else if (obj.substr(0, 4) == L"(n) ") + { + name = obj.substr(4); + return true; + } + else + { + // By default, assume it's just an actor name. (TODO: This + // case is probably only used by the obsolete standalone + // Actor Viewer and should get removed eventually.) + name = obj; + return true; + } +} + +void ActorViewer::SetActor(const CStrW& name, const CStrW& animation) { bool needsAnimReload = false; + CStrW id; + if (! ParseObjectName(name, id)) + id = L""; + if (! m.Unit || id != m.CurrentUnitID) { delete m.Unit;