Index: build/premake/premake5.lua =================================================================== --- build/premake/premake5.lua +++ build/premake/premake5.lua @@ -822,7 +822,9 @@ source_dirs = { "tools/atlas/GameInterface", - "tools/atlas/GameInterface/Handlers" + "tools/atlas/GameInterface/Handlers", + "tools/atlas/EditorInterface", + "tools/atlas/EditorInterface/scripting" } extern_libs = { "boost", Index: source/gui/CGUI.h =================================================================== --- source/gui/CGUI.h +++ source/gui/CGUI.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -257,7 +257,8 @@ GUIProxyProps* GetProxyData(const js::BaseProxyHandler* ptr) { return m_ProxyData.at(ptr).get(); } std::shared_ptr GetScriptInterface() { return m_ScriptInterface; }; - + std::shared_ptr GetEditorScriptInterface() { return m_EditorScriptInterface; }; + private: /** * The CGUI takes ownership of the child object and links the parent with the child. @@ -562,6 +563,7 @@ //@{ std::shared_ptr m_ScriptInterface; + std::shared_ptr m_EditorScriptInterface; /** * don't want to pass this around with the Index: source/gui/CGUI.cpp =================================================================== --- source/gui/CGUI.cpp +++ source/gui/CGUI.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -104,7 +104,9 @@ m_ScriptInterface = std::make_shared("Engine", "GUIPage", context); m_ScriptInterface->SetCallbackData(this); - GuiScriptingInit(*m_ScriptInterface); + m_EditorScriptInterface = std::make_shared("Engine", "Editor", *m_ScriptInterface); + + GuiScriptingInit(*m_ScriptInterface, *m_EditorScriptInterface); m_ScriptInterface->LoadGlobalScripts(); } Index: source/gui/Scripting/ScriptFunctions.h =================================================================== --- source/gui/Scripting/ScriptFunctions.h +++ source/gui/Scripting/ScriptFunctions.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -20,6 +20,6 @@ class ScriptInterface; -void GuiScriptingInit(ScriptInterface& scriptInterface); +void GuiScriptingInit(ScriptInterface& scriptInterface, ScriptInterface& editorScriptInterface); #endif // INCLUDED_GUI_SCRIPTFUNCTIONS Index: source/gui/Scripting/ScriptFunctions.cpp =================================================================== --- source/gui/Scripting/ScriptFunctions.cpp +++ source/gui/Scripting/ScriptFunctions.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -41,6 +41,7 @@ #include "scriptinterface/ScriptInterface.h" #include "simulation2/scripting/JSInterface_Simulation.h" #include "soundmanager/scripting/JSInterface_Sound.h" +#include "tools/atlas/EditorInterface/scripting/JSInterface_Editor.h" /* * This file defines a set of functions that are available to GUI scripts, to allow @@ -48,7 +49,7 @@ * Functions are exposed to scripts within the global object 'Engine', so * scripts should call "Engine.FunctionName(...)" etc. */ -void GuiScriptingInit(ScriptInterface& scriptInterface) +void GuiScriptingInit(ScriptInterface& scriptInterface, ScriptInterface& editorScriptInterface) { ScriptRequest rq(scriptInterface); @@ -73,4 +74,5 @@ JSI_UserReport::RegisterScriptFunctions(rq); JSI_VFS::RegisterScriptFunctions_ReadWriteAnywhere(rq); JSI_VisualReplay::RegisterScriptFunctions(rq); + JSI_Editor::RegisterScriptFunctions(rq, scriptInterface, editorScriptInterface); } Index: source/ps/Game.h =================================================================== --- source/ps/Game.h +++ source/ps/Game.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -151,6 +151,16 @@ { return m_IsVisualReplay; } /** + * Get m_IsEditorGame. + * + * @return bool the value of m_IsEditorGame. + **/ + inline bool IsEditorGame() const + { + return m_IsEditorGame; + } + + /** * Get the pointer to the game world object. * * @return CWorld * the value of m_World. @@ -220,6 +230,8 @@ bool m_IsVisualReplay; std::istream* m_ReplayStream; u32 m_FinalReplayTurn; + + bool m_IsEditorGame; // true if the game is for use the editor }; extern CGame *g_Game; Index: source/ps/Game.cpp =================================================================== --- source/ps/Game.cpp +++ source/ps/Game.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -78,6 +78,7 @@ m_ViewedPlayerID(-1), m_IsSavedGame(false), m_IsVisualReplay(false), + m_IsEditorGame(false), m_ReplayStream(NULL) { // TODO: should use CDummyReplayLogger unless activated by cmd-line arg, perhaps? @@ -229,6 +230,9 @@ LOGERROR("GameSpeed could not be parsed."); } + if (Script::HasProperty(rq, attribs, "editorType")) + m_IsEditorGame = true; + LDR_BeginRegistering(); RegMemFun(m_Simulation2, &CSimulation2::ProgressiveLoad, L"Simulation init", 1000); @@ -411,7 +415,9 @@ g_Renderer.GetTimeManager().Update(deltaSimTime); } - if (doInterpolate) + if (m_IsEditorGame) + m_TurnManager->Interpolate(deltaRealTime * 1.0, deltaRealTime); + else if (doInterpolate) m_TurnManager->Interpolate(deltaSimTime, deltaRealTime); } Index: source/tools/atlas/EditorInterface/scripting/JSInterface_Editor.h =================================================================== --- source/tools/atlas/EditorInterface/scripting/JSInterface_Editor.h +++ source/tools/atlas/EditorInterface/scripting/JSInterface_Editor.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 . + */ + +#ifndef INCLUDED_JSINTERFACE_EDITOR +#define INCLUDED_JSINTERFACE_EDITOR + +class ScriptRequest; + +namespace JSI_Editor +{ + void RegisterScriptFunctions(const ScriptRequest& rq, ScriptInterface& scriptInterface, ScriptInterface& scriptEditorInterface); +} + +#endif // INCLUDED_JSINTERFACE_EDITOR + Index: source/tools/atlas/EditorInterface/scripting/JSInterface_Editor.cpp =================================================================== --- source/tools/atlas/EditorInterface/scripting/JSInterface_Editor.cpp +++ source/tools/atlas/EditorInterface/scripting/JSInterface_Editor.cpp @@ -0,0 +1,236 @@ +/* Copyright (C) 2023 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 . + */ + +#include "precompiled.h" + +#include "JSInterface_Editor.h" +#include "graphics/GameView.h" +#include "graphics/MapWriter.h" +#include "ps/Game.h" +#include "ps/World.h" +#include "renderer/Renderer.h" +#include "renderer/SceneRenderer.h" +#include "renderer/WaterManager.h" +#include "scriptinterface/FunctionWrapper.h" +#include "scriptinterface/ScriptContext.h" +#include "scriptinterface/ScriptInterface.h" +#include "scriptinterface/StructuredClone.h" +#include "lib/allocators/shared_ptr.h" +#include "simulation2/Simulation2.h" +#include "simulation2/components/ICmpAIManager.h" +#include "simulation2/components/ICmpRangeManager.h" +#include "simulation2/components/ICmpTemplateManager.h" + +namespace JSI_Editor +{ + void SetMapSettings(const ScriptInterface& scriptInterface, JS::HandleValue data) + { + if (!g_Game || !g_Game->IsEditorGame()) + return; + + CSimulation2* sim = g_Game->GetSimulation2(); + ENSURE(sim); + + ScriptRequest rqSim(sim->GetScriptInterface()); + JS::RootedValue mapSettings(rqSim.cx, Script::CloneValueFromOtherCompartment(sim->GetScriptInterface(), scriptInterface, data)); + sim->SetMapSettings(mapSettings); + } + + JS::Value GetMapSettings(const ScriptInterface& scriptInterface) + { + if (!g_Game || !g_Game->IsEditorGame()) + return JS::UndefinedHandleValue; + + CSimulation2* sim = g_Game->GetSimulation2(); + ENSURE(sim); + + ScriptRequest rqSim(sim->GetScriptInterface()); + JS::RootedValue mapSettings(rqSim.cx); + sim->GetMapSettings(&mapSettings); + + return Script::CloneValueFromOtherCompartment(scriptInterface, sim->GetScriptInterface(), mapSettings); + } + + bool IsEditorRunning(const ScriptInterface& UNUSED(scriptInterface)) + { + if (!g_Game) + return false; + + return g_Game->IsEditorGame(); + } + + void RevealMap(const ScriptInterface& UNUSED(scriptInterface)) + { + if (!g_Game || !g_Game->IsEditorGame()) + return; + + CSimulation2* sim = g_Game->GetSimulation2(); + ENSURE(sim); + + CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY); + if (cmpRangeManager) + cmpRangeManager->SetLosRevealAll(-1, true); + } + + std::vector> GetCivData(const ScriptInterface& UNUSED(scriptInterface)) + { + if (!g_Game || !g_Game->IsEditorGame()) + { + std::vector> emptyValue; + return emptyValue; + } + + CSimulation2* sim = g_Game->GetSimulation2(); + ENSURE(sim); + + CmpPtr cmpTemplateManager(*sim, SYSTEM_ENTITY); + return cmpTemplateManager->GetCivData(); + } + + JS::Value GetAIData(const ScriptInterface& scriptInterface) + { + if (!g_Game || !g_Game->IsEditorGame()) + return JS::UndefinedHandleValue; + + CSimulation2* sim = g_Game->GetSimulation2(); + ENSURE(sim); + + ScriptRequest rqSim(sim->GetScriptInterface()); + JS::RootedValue aiData(rqSim.cx, ICmpAIManager::GetAIs(sim->GetScriptInterface())); + + return Script::CloneValueFromOtherCompartment(scriptInterface, sim->GetScriptInterface(), aiData); + } + + void SaveMap(const ScriptInterface& UNUSED(scriptInterface), std::wstring filename) + { + if (!g_Game || !g_Game->IsEditorGame()) + return; + + CMapWriter writer; + VfsPath pathname = VfsPath(filename).ChangeExtension(L".pmp"); + writer.SaveMap(pathname, + g_Game->GetWorld()->GetTerrain(), + &g_Renderer.GetSceneRenderer().GetWaterManager(), &g_Renderer.GetSceneRenderer().GetSkyManager(), + &g_LightEnv, g_Game->GetView()->GetCamera(), g_Game->GetView()->GetCinema(), + &g_Renderer.GetPostprocManager(), + g_Game->GetSimulation2()); + } + + JS::Value GetInitAttributes(const ScriptInterface& scriptInterface) + { + if (!g_Game || !g_Game->IsEditorGame()) + return JS::UndefinedHandleValue; + + CSimulation2* sim = g_Game->GetSimulation2(); + ENSURE(sim); + + ScriptRequest rqSim(sim->GetScriptInterface()); + JS::RootedValue initSettings(rqSim.cx); + sim->GetInitAttributes(&initSettings); + + return Script::CloneValueFromOtherCompartment(scriptInterface, sim->GetScriptInterface(), initSettings); + } + + bool PlaySimulation(const ScriptInterface& UNUSED(scriptInterface)) + { + if (!g_Game || !g_Game->IsEditorGame()) + return false; + + CSimulation2* sim = g_Game->GetSimulation2(); + ENSURE(sim); + + std::stringstream simStateStream; + if (!sim->SerializeState(simStateStream)) + { + LOGERROR("Can't serialize state"); + return false; + } + + const VfsPath filename(L"saves/simulation.dat"); + if (g_VFS->CreateFile(filename, DummySharedPtr((u8*)simStateStream.str().c_str()), simStateStream.str().length()) != INFO::OK) + { + LOGERROR("Can't create simulation data: %s", filename.string8().c_str()); + return false; + } + + g_Game->SetSimRate(1.0); + return true; + } + + bool ResumeOrPauseSimulation(const ScriptInterface& UNUSED(scriptInterface)) + { + if (!g_Game || !g_Game->IsEditorGame()) + return false; + + float simRate = g_Game->GetSimRate(); + g_Game->SetSimRate(simRate > 0.0 ? 0.0 : 1.0); + return true; + } + + bool StopSimulation(const ScriptInterface& UNUSED(scriptInterface)) + { + if (!g_Game || !g_Game->IsEditorGame()) + return false; + + g_Game->SetSimRate(0.0); + + CSimulation2* sim = g_Game->GetSimulation2(); + ENSURE(sim); + + const VfsPath filename(L"saves/simulation.dat"); + std::shared_ptr buffer; + size_t size; + if (g_VFS->LoadFile(filename, buffer, size) != INFO::OK) + { + LOGERROR("Can't load simulation data: %s", filename.string8().c_str()); + return false; + } + + std::istringstream s(std::string(reinterpret_cast(buffer.get()), size)); + if (!sim->DeserializeState(s)) + { + LOGERROR("Can't deserialize state"); + return false; + } + + return true; + } + + void RegisterScriptFunctions(const ScriptRequest& rq, ScriptInterface& scriptInterface, ScriptInterface& scriptEditorInterface) + { + // Create EditorEngine context + ScriptRequest rqEditor(scriptEditorInterface); + + ScriptFunction::Register<&IsEditorRunning>(rqEditor, "IsEditorRunning"); + ScriptFunction::Register<&SetMapSettings>(rqEditor, "SetMapSettings"); + ScriptFunction::Register<&GetMapSettings>(rqEditor, "GetMapSettings"); + ScriptFunction::Register<&GetInitAttributes>(rqEditor, "GetInitAttributes"); + ScriptFunction::Register<&RevealMap>(rqEditor, "RevealMap"); + ScriptFunction::Register<&GetCivData>(rqEditor, "GetCivData"); + ScriptFunction::Register<&GetAIData>(rqEditor, "GetAIData"); + ScriptFunction::Register<&SaveMap>(rqEditor, "SaveMap"); + ScriptFunction::Register<&PlaySimulation>(rqEditor, "PlaySimulation"); + ScriptFunction::Register<&ResumeOrPauseSimulation>(rqEditor, "ResumeOrPauseSimulation"); + ScriptFunction::Register<&StopSimulation>(rqEditor, "StopSimulation"); + + JS::RootedValue global(rq.cx, rqEditor.globalValue()); + scriptInterface.SetGlobal("Editor", global, true); + JS::RootedValue scope(rq.cx, JS::ObjectValue(*rqEditor.nativeScope.get())); + scriptInterface.SetGlobal("EditorEngine", scope, true); + } +} +