diff --git a/src/gui/game/GameController.cpp b/src/gui/game/GameController.cpp index f9613b00c..e095c6547 100644 --- a/src/gui/game/GameController.cpp +++ b/src/gui/game/GameController.cpp @@ -1729,3 +1729,11 @@ bool GameController::ThreadedRenderingAllowed() { return gameModel->GetThreadedRendering() && !commandInterface->HaveSimGraphicsEventHandlers(); } + +void GameController::SetToolIndex(ByteString identifier, std::optional index) +{ + if (commandInterface) + { + commandInterface->SetToolIndex(identifier, index); + } +} diff --git a/src/gui/game/GameController.h b/src/gui/game/GameController.h index b3f0ded37..b17b433f8 100644 --- a/src/gui/game/GameController.h +++ b/src/gui/game/GameController.h @@ -206,4 +206,6 @@ public: void BeforeSimDraw(); void AfterSimDraw(); bool ThreadedRenderingAllowed(); + + void SetToolIndex(ByteString identifier, std::optional index); }; diff --git a/src/gui/game/GameModel.cpp b/src/gui/game/GameModel.cpp index 79a63c152..43c7e3bfb 100644 --- a/src/gui/game/GameModel.cpp +++ b/src/gui/game/GameModel.cpp @@ -598,6 +598,14 @@ Brush *GameModel::GetBrushByID(int i) return nullptr; } +int GameModel::GetBrushIndex(const Brush &brush) +{ + auto it = std::find_if(brushList.begin(), brushList.end(), [&brush](auto &ptr) { + return ptr.get() == &brush; + }); + return int(it - brushList.begin()); +} + int GameModel::GetBrushID() { return currentBrush; @@ -1647,6 +1655,7 @@ void GameModel::AllocTool(std::unique_ptr tool) index = int(tools.size()); tools.emplace_back(); } + GameController::Ref().SetToolIndex(tool->Identifier, *index); tools[*index] = std::move(tool); } @@ -1659,6 +1668,7 @@ void GameModel::FreeTool(Tool *tool) } auto &ptr = tools[*index]; DeselectTool(ptr->Identifier); + GameController::Ref().SetToolIndex(ptr->Identifier, std::nullopt); ptr.reset(); } diff --git a/src/gui/game/GameModel.h b/src/gui/game/GameModel.h index 068ee5da7..23abf8b0b 100644 --- a/src/gui/game/GameModel.h +++ b/src/gui/game/GameModel.h @@ -213,6 +213,7 @@ public: Brush &GetBrush(); Brush *GetBrushByID(int i); int GetBrushID(); + int GetBrushIndex(const Brush &brush); int BrushListSize() const { return int(brushList.size()); diff --git a/src/lua/CommandInterface.h b/src/lua/CommandInterface.h index 0afa3ad50..1e098ee22 100644 --- a/src/lua/CommandInterface.h +++ b/src/lua/CommandInterface.h @@ -5,6 +5,7 @@ #include "gui/game/GameControllerEvents.h" #include "TPTSTypes.h" #include +#include class GameModel; class GameController; @@ -55,5 +56,7 @@ public: AnyType tptS_quit(std::deque * words); ValueType testType(String word); + void SetToolIndex(ByteString identifier, std::optional index); + static CommandInterfacePtr Create(GameController *newGameController, GameModel *newGameModel); }; diff --git a/src/lua/LuaScriptInterface.cpp b/src/lua/LuaScriptInterface.cpp index 37ae26776..22b6b4e0c 100644 --- a/src/lua/LuaScriptInterface.cpp +++ b/src/lua/LuaScriptInterface.cpp @@ -155,6 +155,7 @@ LuaScriptInterface::LuaScriptInterface(GameController *newGameController, GameMo LuaRenderer::Open(L); LuaSimulation::Open(L); LuaSocket::Open(L); + LuaTools::Open(L); { lua_getglobal(L, "os"); lua_pushcfunction(L, osExit); @@ -210,6 +211,12 @@ void CommandInterface::Init() } } +void CommandInterface::SetToolIndex(ByteString identifier, std::optional index) +{ + auto *lsi = static_cast(this); + LuaTools::SetToolIndex(lsi->L, identifier, index); +} + void LuaGetProperty(lua_State *L, StructProperty property, intptr_t propertyAddress) { switch (property.Type) diff --git a/src/lua/LuaScriptInterface.h b/src/lua/LuaScriptInterface.h index e6f58fdc0..2519a5c5a 100644 --- a/src/lua/LuaScriptInterface.h +++ b/src/lua/LuaScriptInterface.h @@ -58,6 +58,17 @@ struct CustomElement LuaSmartRef changeType; }; +struct CustomTool +{ + LuaSmartRef perform; + LuaSmartRef click; + LuaSmartRef drag; + LuaSmartRef draw; + LuaSmartRef drawLine; + LuaSmartRef drawRect; + LuaSmartRef drawFill; +}; + class LuaScriptInterface : public CommandInterface { LuaStatePtr luaState; @@ -86,6 +97,7 @@ public: } std::vector customElements; // must come after luaState + std::vector customTools; EventTraits eventTraits = eventTraitNone; @@ -204,6 +216,12 @@ namespace LuaSocket void OpenTCP(lua_State *L); } +namespace LuaTools +{ + void Open(lua_State *L); + void SetToolIndex(lua_State *L, ByteString identifier, std::optional index); +} + inline LuaScriptInterface *GetLSI() { return static_cast(&CommandInterface::Ref()); diff --git a/src/lua/LuaSimulation.cpp b/src/lua/LuaSimulation.cpp index d2371d94a..2f72524c1 100644 --- a/src/lua/LuaSimulation.cpp +++ b/src/lua/LuaSimulation.cpp @@ -2020,21 +2020,6 @@ void LuaSimulation::Open(lua_State *L) LCONSTAS("NUM_WALLS", UI_WALLCOUNT); } - { - lua_newtable(L); - auto &toolList = lsi->gameModel->GetTools(); - for (int i = 0; i < int(toolList.size()); ++i) - { - tpt_lua_pushByteString(L, toolList[i]->Identifier); - lua_pushinteger(L, i); - lua_settable(L, -3); - lua_pushinteger(L, i); - tpt_lua_pushByteString(L, toolList[i]->Identifier); - lua_settable(L, -3); - } - lua_setfield(L, -2, "tools"); - LCONSTAS("NUM_TOOLS", UI_WALLCOUNT); - } #undef LCONSTAS #undef LCONSTF #undef LCONST diff --git a/src/lua/LuaTools.cpp b/src/lua/LuaTools.cpp new file mode 100644 index 000000000..63bd97175 --- /dev/null +++ b/src/lua/LuaTools.cpp @@ -0,0 +1,394 @@ +#include "LuaScriptInterface.h" +#include "gui/game/GameModel.h" +#include "simulation/SimTool.h" +#include "simulation/Simulation.h" +#include + +static int allocate(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TSTRING); + luaL_checktype(L, 2, LUA_TSTRING); + auto group = tpt_lua_toByteString(L, 1).ToUpper(); + auto name = tpt_lua_toByteString(L, 2).ToUpper(); + if (name.Contains("_")) + { + return luaL_error(L, "The tool name may not contain '_'."); + } + if (group.Contains("_")) + { + return luaL_error(L, "The group name may not contain '_'."); + } + if (group == "DEFAULT") + { + return luaL_error(L, "You cannot create tools in the 'DEFAULT' group."); + } + auto *lsi = GetLSI(); + auto identifier = group + "_TOOL_" + name; + { + SimTool tool; + tool.Identifier = identifier; + lsi->gameModel->AllocTool(std::make_unique(tool)); + } + lsi->gameModel->BuildMenus(); + auto index = *lsi->gameModel->GetToolIndex(lsi->gameModel->GetToolFromIdentifier(identifier)); + lsi->customTools.resize(std::max(int(lsi->customTools.size()), index + 1)); + lua_pushinteger(L, index); + return 1; +} + +static bool IsDefault(Tool *tool) +{ + return tool->Identifier.BeginsWith("DEFAULT_"); +} + +static int ffree(lua_State *L) +{ + int index = luaL_checkinteger(L, 1); + auto *lsi = GetLSI(); + auto *tool = lsi->gameModel->GetToolByIndex(index); + if (!tool) + { + return luaL_error(L, "Invalid tool"); + } + if (IsDefault(tool)) + { + return luaL_error(L, "Cannot free default tools"); + } + lsi->customTools[index] = {}; + lsi->gameModel->FreeTool(tool); + lsi->gameModel->BuildMenus(); + return 0; +} + +static int luaPerformWrapper(SimTool *tool, Simulation *sim, Particle *cpart, int x, int y, int brushX, int brushY, float strength) +{ + int ok = 0; + auto *lsi = GetLSI(); + auto L = lsi->L; + auto index = *lsi->gameModel->GetToolIndex(tool); + auto &customTools = lsi->customTools; + if (customTools[index].perform) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, customTools[index].perform); + if (cpart) + { + lua_pushinteger(L, cpart - &lsi->sim->parts[0]); + } + else + { + lua_pushnil(L); + } + lua_pushinteger(L, x); + lua_pushinteger(L, y); + lua_pushnumber(L, tool->Strength); + lua_pushboolean(L, tool->shiftBehaviour); + lua_pushboolean(L, tool->ctrlBehaviour); + lua_pushboolean(L, tool->altBehaviour); + lua_pushinteger(L, brushX); + lua_pushinteger(L, brushY); + if (tpt_lua_pcall(L, 9, 1, 0, eventTraitNone)) + { + lsi->Log(CommandInterface::LogError, "In perform func: " + LuaGetError()); + lua_pop(L, 1); + } + if (lua_isboolean(L, -1)) + { + ok = lua_toboolean(L, -1); + } + lua_pop(L, 1); + } + return ok; +} + +static void luaClickWrapper(SimTool *tool, Simulation *sim, const Brush &brush, ui::Point position) +{ + auto *lsi = GetLSI(); + auto L = lsi->L; + auto index = *lsi->gameModel->GetToolIndex(tool); + auto &customTools = lsi->customTools; + if (customTools[index].click) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, customTools[index].click); + lua_pushinteger(L, lsi->gameModel->GetBrushIndex(brush)); + lua_pushinteger(L, position.X); + lua_pushinteger(L, position.Y); + lua_pushnumber(L, tool->Strength); + lua_pushboolean(L, tool->shiftBehaviour); + lua_pushboolean(L, tool->ctrlBehaviour); + lua_pushboolean(L, tool->altBehaviour); + if (tpt_lua_pcall(L, 7, 0, 0, eventTraitNone)) + { + lsi->Log(CommandInterface::LogError, "In click func: " + LuaGetError()); + lua_pop(L, 1); + } + } +} + +static void luaDragWrapper(SimTool *tool, Simulation *sim, const Brush &brush, ui::Point position1, ui::Point position2) +{ + auto *lsi = GetLSI(); + auto L = lsi->L; + auto index = *lsi->gameModel->GetToolIndex(tool); + auto &customTools = lsi->customTools; + if (customTools[index].drag) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, customTools[index].drag); + lua_pushinteger(L, lsi->gameModel->GetBrushIndex(brush)); + lua_pushinteger(L, position1.X); + lua_pushinteger(L, position1.Y); + lua_pushinteger(L, position2.X); + lua_pushinteger(L, position2.Y); + lua_pushnumber(L, tool->Strength); + lua_pushboolean(L, tool->shiftBehaviour); + lua_pushboolean(L, tool->ctrlBehaviour); + lua_pushboolean(L, tool->altBehaviour); + if (tpt_lua_pcall(L, 9, 0, 0, eventTraitNone)) + { + lsi->Log(CommandInterface::LogError, "In drag func: " + LuaGetError()); + lua_pop(L, 1); + } + } +} + +static void luaDrawWrapper(SimTool *tool, Simulation *sim, const Brush &brush, ui::Point position) +{ + auto *lsi = GetLSI(); + auto L = lsi->L; + auto index = *lsi->gameModel->GetToolIndex(tool); + auto &customTools = lsi->customTools; + if (customTools[index].draw) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, customTools[index].draw); + lua_pushinteger(L, lsi->gameModel->GetBrushIndex(brush)); + lua_pushinteger(L, position.X); + lua_pushinteger(L, position.Y); + lua_pushnumber(L, tool->Strength); + lua_pushboolean(L, tool->shiftBehaviour); + lua_pushboolean(L, tool->ctrlBehaviour); + lua_pushboolean(L, tool->altBehaviour); + if (tpt_lua_pcall(L, 7, 0, 0, eventTraitNone)) + { + lsi->Log(CommandInterface::LogError, "In draw func: " + LuaGetError()); + lua_pop(L, 1); + } + } +} + +static void luaDrawLineWrapper(SimTool *tool, Simulation *sim, const Brush &brush, ui::Point position1, ui::Point position2, bool dragging) +{ + auto *lsi = GetLSI(); + auto L = lsi->L; + auto index = *lsi->gameModel->GetToolIndex(tool); + auto &customTools = lsi->customTools; + if (customTools[index].drawLine) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, customTools[index].drawLine); + lua_pushinteger(L, lsi->gameModel->GetBrushIndex(brush)); + lua_pushinteger(L, position1.X); + lua_pushinteger(L, position1.Y); + lua_pushinteger(L, position2.X); + lua_pushinteger(L, position2.Y); + lua_pushnumber(L, tool->Strength); + lua_pushboolean(L, tool->shiftBehaviour); + lua_pushboolean(L, tool->ctrlBehaviour); + lua_pushboolean(L, tool->altBehaviour); + if (tpt_lua_pcall(L, 9, 0, 0, eventTraitNone)) + { + lsi->Log(CommandInterface::LogError, "In drawLine func: " + LuaGetError()); + lua_pop(L, 1); + } + } +} + +static void luaDrawRectWrapper(SimTool *tool, Simulation *sim, const Brush &brush, ui::Point position1, ui::Point position2) +{ + auto *lsi = GetLSI(); + auto L = lsi->L; + auto index = *lsi->gameModel->GetToolIndex(tool); + auto &customTools = lsi->customTools; + if (customTools[index].drawRect) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, customTools[index].drawRect); + lua_pushinteger(L, lsi->gameModel->GetBrushIndex(brush)); + lua_pushinteger(L, position1.X); + lua_pushinteger(L, position1.Y); + lua_pushinteger(L, position2.X); + lua_pushinteger(L, position2.Y); + lua_pushnumber(L, tool->Strength); + lua_pushboolean(L, tool->shiftBehaviour); + lua_pushboolean(L, tool->ctrlBehaviour); + lua_pushboolean(L, tool->altBehaviour); + if (tpt_lua_pcall(L, 9, 0, 0, eventTraitNone)) + { + lsi->Log(CommandInterface::LogError, "In drawRect func: " + LuaGetError()); + lua_pop(L, 1); + } + } +} + +static void luaDrawFillWrapper(SimTool *tool, Simulation *sim, const Brush &brush, ui::Point position) +{ + auto *lsi = GetLSI(); + auto L = lsi->L; + auto index = *lsi->gameModel->GetToolIndex(tool); + auto &customTools = lsi->customTools; + if (customTools[index].drawFill) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, customTools[index].drawFill); + lua_pushinteger(L, lsi->gameModel->GetBrushIndex(brush)); + lua_pushinteger(L, position.X); + lua_pushinteger(L, position.Y); + lua_pushnumber(L, tool->Strength); + lua_pushboolean(L, tool->shiftBehaviour); + lua_pushboolean(L, tool->ctrlBehaviour); + lua_pushboolean(L, tool->altBehaviour); + if (tpt_lua_pcall(L, 7, 0, 0, eventTraitNone)) + { + lsi->Log(CommandInterface::LogError, "In drawFill func: " + LuaGetError()); + lua_pop(L, 1); + } + } +} + +template +struct DependentFalse : std::false_type +{ +}; + +static int property(lua_State *L) +{ + auto *lsi = GetLSI(); + int index = luaL_checkinteger(L, 1); + auto *tool = lsi->gameModel->GetToolByIndex(index); + if (!tool) + { + return luaL_error(L, "Invalid tool"); + } + ByteString propertyName = tpt_lua_checkByteString(L, 2); + auto handleCallback = [lsi, L, index, tool, &propertyName]( + auto customToolMember, + auto simToolMember, + auto wrapper, + const char *luaPropertyName + ) { + if (propertyName == luaPropertyName) + { + if (lua_gettop(L) > 2) + { + if (IsDefault(tool)) + { + luaL_error(L, "Cannot change callbacks of default tools"); + } + if (lua_type(L, 3) == LUA_TFUNCTION) + { + (lsi->customTools[index].*customToolMember).Assign(L, 3); + (static_cast(tool)->*simToolMember) = wrapper; + } + else if (lua_type(L, 3) == LUA_TBOOLEAN && !lua_toboolean(L, 3)) + { + (lsi->customTools[index].*customToolMember).Clear(); + (static_cast(tool)->*simToolMember) = SimTool().*simToolMember; + } + return true; + } + luaL_error(L, "Invalid tool property"); + } + return false; + }; + if (handleCallback(&CustomTool::perform , &SimTool::Perform , luaPerformWrapper , "Perform" ) || + handleCallback(&CustomTool::click , &SimTool::PerformClick , luaClickWrapper , "Click" ) || + handleCallback(&CustomTool::drag , &SimTool::PerformDrag , luaDragWrapper , "Drag" ) || + handleCallback(&CustomTool::draw , &SimTool::PerformDraw , luaDrawWrapper , "Draw" ) || + handleCallback(&CustomTool::drawLine, &SimTool::PerformDrawLine, luaDrawLineWrapper, "DrawLine") || + handleCallback(&CustomTool::drawRect, &SimTool::PerformDrawRect, luaDrawRectWrapper, "DrawRect") || + handleCallback(&CustomTool::drawFill, &SimTool::PerformDrawFill, luaDrawFillWrapper, "DrawFill")) + { + return 0; + } + int returnValueCount = 0; + auto handleProperty = [L, tool, &propertyName, &returnValueCount](auto simToolMember, const char *luaPropertyName) { + if (propertyName == luaPropertyName) + { + auto &thing = tool->*simToolMember; + using PropertyType = std::remove_reference_t; + if (lua_gettop(L) > 2) + { + if constexpr (std::is_same_v) thing = tpt_lua_checkString(L, 3); + else if constexpr (std::is_same_v>) thing = RGB::Unpack(luaL_checkinteger(L, 3)); + else static_assert(DependentFalse::value); + } + else + { + if constexpr (std::is_same_v) tpt_lua_pushString(L, thing); + else if constexpr (std::is_same_v>) lua_pushinteger(L, thing.Pack()); + else static_assert(DependentFalse::value); + returnValueCount = 1; + } + return true; + } + return false; + }; + if (handleProperty(&SimTool::Name , "Name" ) || + handleProperty(&SimTool::Description, "Description") || + handleProperty(&SimTool::Colour , "Colour" ) || + handleProperty(&SimTool::Colour , "Color" )) + { + return returnValueCount; + } + if (propertyName == "Identifier") + { + tpt_lua_pushByteString(L, tool->Identifier); + return 1; + } + return luaL_error(L, "Invalid tool property"); +} + +static int exists(lua_State *L) +{ + int index = luaL_checkinteger(L, 1); + auto *lsi = GetLSI(); + lua_pushboolean(L, bool(lsi->gameModel->GetToolByIndex(index))); + return 1; +} + +void LuaTools::Open(lua_State *L) +{ + auto *lsi = GetLSI(); + static const luaL_Reg reg[] = { +#define LFUNC(v) { #v, v } + LFUNC(allocate), + LFUNC(property), + LFUNC(exists), +#undef LFUNC + { "free", ffree }, + { NULL, NULL } + }; + lua_newtable(L); + luaL_register(L, NULL, reg); + lua_setglobal(L, "tools"); + auto &toolList = lsi->gameModel->GetTools(); + for (int i = 0; i < int(toolList.size()); ++i) + { + if (!toolList[i]) + { + continue; + } + SetToolIndex(L, toolList[i]->Identifier, i); + } +} + +void LuaTools::SetToolIndex(lua_State *L, ByteString identifier, std::optional index) +{ + lua_getglobal(L, "tools"); + tpt_lua_pushByteString(L, identifier); + if (index) + { + lua_pushinteger(L, *index); + } + else + { + lua_pushnil(L); + } + lua_settable(L, -3); + lua_pop(L, 1); +} diff --git a/src/lua/PlainCommandInterface.cpp b/src/lua/PlainCommandInterface.cpp index 707af8765..3f29f7925 100644 --- a/src/lua/PlainCommandInterface.cpp +++ b/src/lua/PlainCommandInterface.cpp @@ -37,3 +37,7 @@ String CommandInterface::FormatCommand(String command) { return PlainFormatCommand(command); } + +void CommandInterface::SetToolIndex(ByteString identifier, std::optional index) +{ +} diff --git a/src/lua/luascripts/compat.lua b/src/lua/luascripts/compat.lua index 724bf8761..bf1c7a63f 100644 --- a/src/lua/luascripts/compat.lua +++ b/src/lua/luascripts/compat.lua @@ -55,15 +55,15 @@ tpt.setfpscap = tpt.fpsCap ui.MOUSE_UP_BLUR = ui.MOUSEUP_BLUR ui.MOUSE_UP_DRAW_END = ui.MOUSEUP_DRAWEND ui.MOUSE_UP_NORMAL = ui.MOUSEUP_NORMAL -sim.TOOL_HEAT = sim.tools.DEFAULT_TOOL_HEAT -sim.TOOL_COOL = sim.tools.DEFAULT_TOOL_COOL -sim.TOOL_VAC = sim.tools.DEFAULT_TOOL_VAC -sim.TOOL_PGRV = sim.tools.DEFAULT_TOOL_PGRV -sim.TOOL_AIR = sim.tools.DEFAULT_TOOL_AIR -sim.TOOL_NGRV = sim.tools.DEFAULT_TOOL_NGRV -sim.TOOL_MIX = sim.tools.DEFAULT_TOOL_MIX -sim.TOOL_CYCL = sim.tools.DEFAULT_TOOL_CYCL -sim.TOOL_WIND = sim.tools.DEFAULT_TOOL_WIND +sim.TOOL_HEAT = tools.DEFAULT_TOOL_HEAT +sim.TOOL_COOL = tools.DEFAULT_TOOL_COOL +sim.TOOL_VAC = tools.DEFAULT_TOOL_VAC +sim.TOOL_PGRV = tools.DEFAULT_TOOL_PGRV +sim.TOOL_AIR = tools.DEFAULT_TOOL_AIR +sim.TOOL_NGRV = tools.DEFAULT_TOOL_NGRV +sim.TOOL_MIX = tools.DEFAULT_TOOL_MIX +sim.TOOL_CYCL = tools.DEFAULT_TOOL_CYCL +sim.TOOL_WIND = tools.DEFAULT_TOOL_WIND if socket then socket.gettime = socket.getTime end diff --git a/src/lua/meson.build b/src/lua/meson.build index a713fb21c..7446e68bd 100644 --- a/src/lua/meson.build +++ b/src/lua/meson.build @@ -21,6 +21,7 @@ luaconsole_files = files( 'LuaSocket.cpp', 'LuaSmartRef.cpp', 'LuaTextbox.cpp', + 'LuaTools.cpp', 'LuaWindow.cpp', ) if lua_variant != 'luajit'