From 12b572c7e405c10bea0b7818ca0ee0beaf9f66db Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 18:57:32 +0100 Subject: [PATCH 01/41] refactor: required mondernization of the io_service --- source/live_client.cpp | 45 ++++++--------------------------------- source/live_client.h | 2 +- source/net_connection.cpp | 8 +++---- source/net_connection.h | 4 ++-- vcpkg.json | 37 +++++++++++++++++--------------- 5 files changed, 34 insertions(+), 62 deletions(-) diff --git a/source/live_client.cpp b/source/live_client.cpp index 5a737e0b7..5377919c1 100644 --- a/source/live_client.cpp +++ b/source/live_client.cpp @@ -51,59 +51,28 @@ bool LiveClient::connect(const std::string& address, uint16_t port) { socket = std::make_shared(service); } - boost::asio::ip::tcp::resolver::query query(address, std::to_string(port)); - resolver->async_resolve(query, [this](const boost::system::error_code& error, boost::asio::ip::tcp::resolver::iterator endpoint_iterator) -> void { + resolver->async_resolve(address, std::to_string(port), [this](const boost::system::error_code& error, boost::asio::ip::tcp::resolver::results_type results) -> void { if (error) { logMessage("Error: " + error.message()); } else { - tryConnect(endpoint_iterator); + tryConnect(results); } }); - /* - if(!client->WaitOnConnect(5, 0)) { - if(log) - log->Disconnect(); - last_err = "Connection timed out."; - client->Destroy(); - client = nullptr; - delete connection; - return false; - } - - if(!client->IsConnected()) { - if(log) - log->Disconnect(); - last_err = "Connection refused by peer."; - client->Destroy(); - client = nullptr; - delete connection; - return false; - } - - if(log) - log->Message("Connection established!"); - */ return true; } -void LiveClient::tryConnect(boost::asio::ip::tcp::resolver::iterator endpoint_iterator) { +void LiveClient::tryConnect(const boost::asio::ip::tcp::resolver::results_type& results) { if (stopped) { return; } - if (endpoint_iterator == boost::asio::ip::tcp::resolver::iterator()) { - return; - } + logMessage("Connecting to server..."); - logMessage("Joining server " + endpoint_iterator->host_name() + ":" + endpoint_iterator->service_name() + "..."); - - boost::asio::async_connect(*socket, endpoint_iterator, [this](boost::system::error_code error, boost::asio::ip::tcp::resolver::iterator endpoint_iterator) -> void { - if (!socket->is_open()) { - tryConnect(++endpoint_iterator); - } else if (error) { + boost::asio::async_connect(*socket, results, [this](boost::system::error_code error, const boost::asio::ip::tcp::endpoint& endpoint) -> void { + if (error) { if (handleError(error)) { - tryConnect(++endpoint_iterator); + // } else { wxTheApp->CallAfter([this]() { close(); diff --git a/source/live_client.h b/source/live_client.h index c580266f0..fc8328ba2 100644 --- a/source/live_client.h +++ b/source/live_client.h @@ -33,7 +33,7 @@ class LiveClient : public LiveSocket { // bool connect(const std::string& address, uint16_t port); - void tryConnect(boost::asio::ip::tcp::resolver::iterator endpoint); + void tryConnect(const boost::asio::ip::tcp::resolver::results_type& results); void close(); bool handleError(const boost::system::error_code& error); diff --git a/source/net_connection.cpp b/source/net_connection.cpp index 4440473d8..f7fbbe74f 100644 --- a/source/net_connection.cpp +++ b/source/net_connection.cpp @@ -94,15 +94,15 @@ bool NetworkConnection::start() { stopped = false; if (!service) { - service = new boost::asio::io_service; + service = new boost::asio::io_context; } thread = std::thread([this]() -> void { - boost::asio::io_service& serviceRef = *service; + boost::asio::io_context& serviceRef = *service; try { while (!stopped) { serviceRef.run_one(); - serviceRef.reset(); + serviceRef.restart(); } } catch (std::exception& e) { std::cout << e.what() << std::endl; @@ -124,6 +124,6 @@ void NetworkConnection::stop() { service = nullptr; } -boost::asio::io_service& NetworkConnection::get_service() { +boost::asio::io_context& NetworkConnection::get_service() { return *service; } diff --git a/source/net_connection.h b/source/net_connection.h index 34004c8bd..d4a5a4fae 100644 --- a/source/net_connection.h +++ b/source/net_connection.h @@ -75,10 +75,10 @@ class NetworkConnection { bool start(); void stop(); - boost::asio::io_service& get_service(); + boost::asio::io_context& get_service(); private: - boost::asio::io_service* service; + boost::asio::io_context* service; std::thread thread; bool stopped; }; diff --git a/vcpkg.json b/vcpkg.json index af6510a18..958180751 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,18 +1,21 @@ { - "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", - "dependencies": [ - "wxwidgets", - "freeglut", - "asio", - "nlohmann-json", - "fmt", - { - "name": "libiconv", - "platform": "osx" - }, - "libarchive", - "boost-spirit", - "boost-asio" - ], - "builtin-baseline": "215a2535590f1f63788ac9bd2ed58ad15e6afdff" -} \ No newline at end of file + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "dependencies": [ + "wxwidgets", + "freeglut", + "asio", + "nlohmann-json", + "fmt", + { + "name": "libiconv", + "platform": "osx" + }, + "libarchive", + "boost-spirit", + "boost-asio", + "lua", + "sol2", + "cpr" + ], + "builtin-baseline": "734f8130ffe2f02cf855a3a42a2958f01b3fb005" +} From b935c8bb2f03a2c4df0306b55e87053d84adc09a Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 19:00:12 +0100 Subject: [PATCH 02/41] feat: introduce lua_api engine --- CMakeLists.txt | 8 +- source/CMakeLists.txt | 44 + source/fast_noise_lite.h | 2586 +++++++++++++++++++++++++++ source/lua/lua_api.cpp | 70 + source/lua/lua_api.h | 58 + source/lua/lua_api_algo.cpp | 849 +++++++++ source/lua/lua_api_algo.h | 28 + source/lua/lua_api_app.cpp | 784 +++++++++ source/lua/lua_api_app.h | 28 + source/lua/lua_api_brush.cpp | 157 ++ source/lua/lua_api_brush.h | 30 + source/lua/lua_api_color.cpp | 91 + source/lua/lua_api_color.h | 59 + source/lua/lua_api_creature.cpp | 138 ++ source/lua/lua_api_creature.h | 30 + source/lua/lua_api_geo.cpp | 932 ++++++++++ source/lua/lua_api_geo.h | 28 + source/lua/lua_api_http.cpp | 455 +++++ source/lua/lua_api_http.h | 30 + source/lua/lua_api_image.cpp | 277 +++ source/lua/lua_api_image.h | 86 + source/lua/lua_api_item.cpp | 253 +++ source/lua/lua_api_item.h | 29 + source/lua/lua_api_json.cpp | 172 ++ source/lua/lua_api_json.h | 16 + source/lua/lua_api_map.cpp | 192 ++ source/lua/lua_api_map.h | 29 + source/lua/lua_api_noise.cpp | 469 +++++ source/lua/lua_api_noise.h | 28 + source/lua/lua_api_position.cpp | 71 + source/lua/lua_api_position.h | 29 + source/lua/lua_api_selection.cpp | 151 ++ source/lua/lua_api_selection.h | 29 + source/lua/lua_api_tile.cpp | 549 ++++++ source/lua/lua_api_tile.h | 29 + source/lua/lua_dialog.cpp | 2709 +++++++++++++++++++++++++++++ source/lua/lua_dialog.h | 197 +++ source/lua/lua_engine.cpp | 234 +++ source/lua/lua_engine.h | 102 ++ source/lua/lua_script.cpp | 287 +++ source/lua/lua_script.h | 72 + source/lua/lua_script_manager.cpp | 649 +++++++ source/lua/lua_script_manager.h | 167 ++ source/lua/lua_scripts_window.cpp | 300 ++++ source/lua/lua_scripts_window.h | 71 + vcproj/Project/Editor.vcxproj | 43 +- 46 files changed, 13642 insertions(+), 3 deletions(-) create mode 100644 source/fast_noise_lite.h create mode 100644 source/lua/lua_api.cpp create mode 100644 source/lua/lua_api.h create mode 100644 source/lua/lua_api_algo.cpp create mode 100644 source/lua/lua_api_algo.h create mode 100644 source/lua/lua_api_app.cpp create mode 100644 source/lua/lua_api_app.h create mode 100644 source/lua/lua_api_brush.cpp create mode 100644 source/lua/lua_api_brush.h create mode 100644 source/lua/lua_api_color.cpp create mode 100644 source/lua/lua_api_color.h create mode 100644 source/lua/lua_api_creature.cpp create mode 100644 source/lua/lua_api_creature.h create mode 100644 source/lua/lua_api_geo.cpp create mode 100644 source/lua/lua_api_geo.h create mode 100644 source/lua/lua_api_http.cpp create mode 100644 source/lua/lua_api_http.h create mode 100644 source/lua/lua_api_image.cpp create mode 100644 source/lua/lua_api_image.h create mode 100644 source/lua/lua_api_item.cpp create mode 100644 source/lua/lua_api_item.h create mode 100644 source/lua/lua_api_json.cpp create mode 100644 source/lua/lua_api_json.h create mode 100644 source/lua/lua_api_map.cpp create mode 100644 source/lua/lua_api_map.h create mode 100644 source/lua/lua_api_noise.cpp create mode 100644 source/lua/lua_api_noise.h create mode 100644 source/lua/lua_api_position.cpp create mode 100644 source/lua/lua_api_position.h create mode 100644 source/lua/lua_api_selection.cpp create mode 100644 source/lua/lua_api_selection.h create mode 100644 source/lua/lua_api_tile.cpp create mode 100644 source/lua/lua_api_tile.h create mode 100644 source/lua/lua_dialog.cpp create mode 100644 source/lua/lua_dialog.h create mode 100644 source/lua/lua_engine.cpp create mode 100644 source/lua/lua_engine.h create mode 100644 source/lua/lua_script.cpp create mode 100644 source/lua/lua_script.h create mode 100644 source/lua/lua_script_manager.cpp create mode 100644 source/lua/lua_script_manager.h create mode 100644 source/lua/lua_scripts_window.cpp create mode 100644 source/lua/lua_scripts_window.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d2f8d6b11..401b1cacb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,10 @@ find_package(wxWidgets COMPONENTS html aui gl adv core net base REQUIRED) find_package(GLUT REQUIRED) find_package(ZLIB REQUIRED) +# Lua scripting support +find_package(Lua REQUIRED) +find_path(SOL2_INCLUDE_DIRS "sol/sol.hpp") + include(${wxWidgets_USE_FILE}) include(source/CMakeLists.txt) add_executable(rme ${rme_H} ${rme_SRC}) @@ -36,5 +40,5 @@ add_executable(rme ${rme_H} ${rme_SRC}) set_target_properties(rme PROPERTIES CXX_STANDARD 17) set_target_properties(rme PROPERTIES CXX_STANDARD_REQUIRED ON) -include_directories(${Boost_INCLUDE_DIRS} ${LibArchive_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR}) -target_link_libraries(rme ${wxWidgets_LIBRARIES} ${Boost_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${ZLIB_LIBRARIES}) \ No newline at end of file +include_directories(${Boost_INCLUDE_DIRS} ${LibArchive_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR} ${LUA_INCLUDE_DIR} ${SOL2_INCLUDE_DIRS}) +target_link_libraries(rme ${wxWidgets_LIBRARIES} ${Boost_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${ZLIB_LIBRARIES} ${LUA_LIBRARIES}) \ No newline at end of file diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index bb682151e..c32dd553c 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -59,6 +59,7 @@ ${CMAKE_CURRENT_LIST_DIR}/map.h ${CMAKE_CURRENT_LIST_DIR}/map_allocator.h ${CMAKE_CURRENT_LIST_DIR}/map_display.h ${CMAKE_CURRENT_LIST_DIR}/map_drawer.h +${CMAKE_CURRENT_LIST_DIR}/map_overlay.h ${CMAKE_CURRENT_LIST_DIR}/map_region.h ${CMAKE_CURRENT_LIST_DIR}/map_tab.h ${CMAKE_CURRENT_LIST_DIR}/map_window.h @@ -104,6 +105,28 @@ ${CMAKE_CURRENT_LIST_DIR}/wall_brush.h ${CMAKE_CURRENT_LIST_DIR}/waypoint_brush.h ${CMAKE_CURRENT_LIST_DIR}/waypoints.h ${CMAKE_CURRENT_LIST_DIR}/welcome_dialog.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_engine.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_script.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_script_manager.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_app.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_position.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_item.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_tile.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_map.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_selection.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_dialog.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_color.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_creature.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_brush.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_image.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_scripts_window.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_json.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_http.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_noise.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_algo.h +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_geo.h +${CMAKE_CURRENT_LIST_DIR}/FastNoiseLite.h ) set(rme_SRC @@ -207,4 +230,25 @@ ${CMAKE_CURRENT_LIST_DIR}/welcome_dialog.cpp ${CMAKE_CURRENT_LIST_DIR}/json/json_spirit_reader.cpp ${CMAKE_CURRENT_LIST_DIR}/json/json_spirit_value.cpp ${CMAKE_CURRENT_LIST_DIR}/json/json_spirit_writer.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_engine.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_script.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_script_manager.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_app.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_position.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_item.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_tile.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_map.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_selection.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_dialog.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_color.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_creature.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_brush.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_image.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_scripts_window.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_json.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_http.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_noise.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_algo.cpp +${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_geo.cpp ) diff --git a/source/fast_noise_lite.h b/source/fast_noise_lite.h new file mode 100644 index 000000000..c67f2e5ce --- /dev/null +++ b/source/fast_noise_lite.h @@ -0,0 +1,2586 @@ +// MIT License +// +// Copyright(c) 2023 Jordan Peck (jordan.me2@gmail.com) +// Copyright(c) 2023 Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// .'',;:cldxkO00KKXXNNWWWNNXKOkxdollcc::::::;:::ccllloooolllllllllooollc:,'... ...........',;cldxkO000Okxdlc::;;;,,;;;::cclllllll +// ..',;:ldxO0KXXNNNNNNNNXXK0kxdolcc::::::;;;,,,,,,;;;;;;;;;;:::cclllllc:;'.... ...........',;:ldxO0KXXXK0Okxdolc::;;;;::cllodddddo +// ...',:loxO0KXNNNNNXXKK0Okxdolc::;::::::::;;;,,'''''.....''',;:clllllc:;,'............''''''''',;:loxO0KXNNNNNXK0Okxdollccccllodxxxxxxd +// ....';:ldkO0KXXXKK00Okxdolcc:;;;;;::cclllcc:;;,''..... ....',;clooddolcc:;;;;,,;;;;;::::;;;;;;:cloxk0KXNWWWWWWNXKK0Okxddoooddxxkkkkkxx +// .....';:ldxkOOOOOkxxdolcc:;;;,,,;;:cllooooolcc:;'... ..,:codxkkkxddooollloooooooollcc:::::clodkO0KXNWWWWWWNNXK00Okxxxxxxxxkkkkxxx +// . ....';:cloddddo___________,,,,;;:clooddddoolc:,... ..,:ldx__00OOOkkk___kkkkkkxxdollc::::cclodkO0KXXNNNNNNXXK0OOkxxxxxxxxxxxxddd +// .......',;:cccc:| |,,,;;:cclooddddoll:;'.. ..';cox| \KKK000| |KK00OOkxdocc___;::clldxxkO0KKKKK00Okkxdddddddddddddddoo +// .......'',,,,,''| ________|',,;;::cclloooooolc:;'......___:ldk| \KK000| |XKKK0Okxolc| |;;::cclodxxkkkkxxdoolllcclllooodddooooo +// ''......''''....| | ....'',,,,;;;::cclloooollc:;,''.'| |oxk| \OOO0| |KKK00Oxdoll|___|;;;;;::ccllllllcc::;;,,;;;:cclloooooooo +// ;;,''.......... | |_____',,;;;____:___cllo________.___| |___| \xkk| |KK_______ool___:::;________;;;_______...'',;;:ccclllloo +// c:;,''......... | |:::/ ' |lo/ | | \dx| |0/ \d| |cc/ |'/ \......',,;;:ccllo +// ol:;,'..........| _____|ll/ __ |o/ ______|____ ___| | \o| |/ ___ \| |o/ ______|/ ___ \ .......'',;:clo +// dlc;,...........| |::clooo| / | |x\___ \KXKKK0| |dol| |\ \| | | | | |d\___ \..| | / / ....',:cl +// xoc;'... .....'| |llodddd| \__| |_____\ \KKK0O| |lc:| |'\ | |___| | |_____\ \.| |_/___/... ...',;:c +// dlc;'... ....',;| |oddddddo\ | |Okkx| |::;| |..\ |\ /| | | \ |... ....',;:c +// ol:,'.......',:c|___|xxxddollc\_____,___|_________/ddoll|___|,,,|___|...\_____|:\ ______/l|___|_________/...\________|'........',;::cc +// c:;'.......';:codxxkkkkxxolc::;::clodxkOO0OOkkxdollc::;;,,''''',,,,''''''''''',,'''''',;:loxkkOOkxol:;,'''',,;:ccllcc:;,'''''',;::ccll +// ;,'.......',:codxkOO0OOkxdlc:;,,;;:cldxxkkxxdolc:;;,,''.....'',;;:::;;,,,'''''........,;cldkO0KK0Okdoc::;;::cloodddoolc:;;;;;::ccllooo +// .........',;:lodxOO0000Okdoc:,,',,;:clloddoolc:;,''.......'',;:clooollc:;;,,''.......',:ldkOKXNNXX0Oxdolllloddxxxxxxdolccccccllooodddd +// . .....';:cldxkO0000Okxol:;,''',,;::cccc:;,,'.......'',;:cldxxkkxxdolc:;;,'.......';coxOKXNWWWNXKOkxddddxxkkkkkkxdoollllooddxxxxkkk +// ....',;:codxkO000OOxdoc:;,''',,,;;;;,''.......',,;:clodkO00000Okxolc::;,,''..',;:ldxOKXNWWWNNK0OkkkkkkkkkkkxxddooooodxxkOOOOO000 +// ....',;;clodxkkOOOkkdolc:;,,,,,,,,'..........,;:clodxkO0KKXKK0Okxdolcc::;;,,,;;:codkO0XXNNNNXKK0OOOOOkkkkxxdoollloodxkO0KKKXXXXX +// +// VERSION: 1.1.1 +// https://github.com/Auburn/FastNoiseLite + +#ifndef FASTNOISELITE_H +#define FASTNOISELITE_H + +#include + +class FastNoiseLite +{ +public: + enum NoiseType + { + NoiseType_OpenSimplex2, + NoiseType_OpenSimplex2S, + NoiseType_Cellular, + NoiseType_Perlin, + NoiseType_ValueCubic, + NoiseType_Value + }; + + enum RotationType3D + { + RotationType3D_None, + RotationType3D_ImproveXYPlanes, + RotationType3D_ImproveXZPlanes + }; + + enum FractalType + { + FractalType_None, + FractalType_FBm, + FractalType_Ridged, + FractalType_PingPong, + FractalType_DomainWarpProgressive, + FractalType_DomainWarpIndependent + }; + + enum CellularDistanceFunction + { + CellularDistanceFunction_Euclidean, + CellularDistanceFunction_EuclideanSq, + CellularDistanceFunction_Manhattan, + CellularDistanceFunction_Hybrid + }; + + enum CellularReturnType + { + CellularReturnType_CellValue, + CellularReturnType_Distance, + CellularReturnType_Distance2, + CellularReturnType_Distance2Add, + CellularReturnType_Distance2Sub, + CellularReturnType_Distance2Mul, + CellularReturnType_Distance2Div + }; + + enum DomainWarpType + { + DomainWarpType_OpenSimplex2, + DomainWarpType_OpenSimplex2Reduced, + DomainWarpType_BasicGrid + }; + + /// + /// Create new FastNoise object with optional seed + /// + FastNoiseLite(int seed = 1337) + { + mSeed = seed; + mFrequency = 0.01f; + mNoiseType = NoiseType_OpenSimplex2; + mRotationType3D = RotationType3D_None; + mTransformType3D = TransformType3D_DefaultOpenSimplex2; + + mFractalType = FractalType_None; + mOctaves = 3; + mLacunarity = 2.0f; + mGain = 0.5f; + mWeightedStrength = 0.0f; + mPingPongStrength = 2.0f; + + mFractalBounding = 1 / 1.75f; + + mCellularDistanceFunction = CellularDistanceFunction_EuclideanSq; + mCellularReturnType = CellularReturnType_Distance; + mCellularJitterModifier = 1.0f; + + mDomainWarpType = DomainWarpType_OpenSimplex2; + mWarpTransformType3D = TransformType3D_DefaultOpenSimplex2; + mDomainWarpAmp = 1.0f; + } + + /// + /// Sets seed used for all noise types + /// + /// + /// Default: 1337 + /// + void SetSeed(int seed) { mSeed = seed; } + + /// + /// Sets frequency for all noise types + /// + /// + /// Default: 0.01 + /// + void SetFrequency(float frequency) { mFrequency = frequency; } + + /// + /// Sets noise algorithm used for GetNoise(...) + /// + /// + /// Default: OpenSimplex2 + /// + void SetNoiseType(NoiseType noiseType) + { + mNoiseType = noiseType; + UpdateTransformType3D(); + } + + /// + /// Sets domain rotation type for 3D Noise and 3D DomainWarp. + /// Can aid in reducing directional artifacts when sampling a 2D plane in 3D + /// + /// + /// Default: None + /// + void SetRotationType3D(RotationType3D rotationType3D) + { + mRotationType3D = rotationType3D; + UpdateTransformType3D(); + UpdateWarpTransformType3D(); + } + + /// + /// Sets method for combining octaves in all fractal noise types + /// + /// + /// Default: None + /// Note: FractalType_DomainWarp... only affects DomainWarp(...) + /// + void SetFractalType(FractalType fractalType) { mFractalType = fractalType; } + + /// + /// Sets octave count for all fractal noise types + /// + /// + /// Default: 3 + /// + void SetFractalOctaves(int octaves) + { + mOctaves = octaves; + CalculateFractalBounding(); + } + + /// + /// Sets octave lacunarity for all fractal noise types + /// + /// + /// Default: 2.0 + /// + void SetFractalLacunarity(float lacunarity) { mLacunarity = lacunarity; } + + /// + /// Sets octave gain for all fractal noise types + /// + /// + /// Default: 0.5 + /// + void SetFractalGain(float gain) + { + mGain = gain; + CalculateFractalBounding(); + } + + /// + /// Sets octave weighting for all none DomainWarp fratal types + /// + /// + /// Default: 0.0 + /// Note: Keep between 0...1 to maintain -1...1 output bounding + /// + void SetFractalWeightedStrength(float weightedStrength) { mWeightedStrength = weightedStrength; } + + /// + /// Sets strength of the fractal ping pong effect + /// + /// + /// Default: 2.0 + /// + void SetFractalPingPongStrength(float pingPongStrength) { mPingPongStrength = pingPongStrength; } + + + /// + /// Sets distance function used in cellular noise calculations + /// + /// + /// Default: Distance + /// + void SetCellularDistanceFunction(CellularDistanceFunction cellularDistanceFunction) { mCellularDistanceFunction = cellularDistanceFunction; } + + /// + /// Sets return type from cellular noise calculations + /// + /// + /// Default: EuclideanSq + /// + void SetCellularReturnType(CellularReturnType cellularReturnType) { mCellularReturnType = cellularReturnType; } + + /// + /// Sets the maximum distance a cellular point can move from it's grid position + /// + /// + /// Default: 1.0 + /// Note: Setting this higher than 1 will cause artifacts + /// + void SetCellularJitter(float cellularJitter) { mCellularJitterModifier = cellularJitter; } + + + /// + /// Sets the warp algorithm when using DomainWarp(...) + /// + /// + /// Default: OpenSimplex2 + /// + void SetDomainWarpType(DomainWarpType domainWarpType) + { + mDomainWarpType = domainWarpType; + UpdateWarpTransformType3D(); + } + + + /// + /// Sets the maximum warp distance from original position when using DomainWarp(...) + /// + /// + /// Default: 1.0 + /// + void SetDomainWarpAmp(float domainWarpAmp) { mDomainWarpAmp = domainWarpAmp; } + + + /// + /// 2D noise at given position using current settings + /// + /// + /// Noise output bounded between -1...1 + /// + template + float GetNoise(FNfloat x, FNfloat y) const + { + Arguments_must_be_floating_point_values(); + + TransformNoiseCoordinate(x, y); + + switch (mFractalType) + { + default: + return GenNoiseSingle(mSeed, x, y); + case FractalType_FBm: + return GenFractalFBm(x, y); + case FractalType_Ridged: + return GenFractalRidged(x, y); + case FractalType_PingPong: + return GenFractalPingPong(x, y); + } + } + + /// + /// 3D noise at given position using current settings + /// + /// + /// Noise output bounded between -1...1 + /// + template + float GetNoise(FNfloat x, FNfloat y, FNfloat z) const + { + Arguments_must_be_floating_point_values(); + + TransformNoiseCoordinate(x, y, z); + + switch (mFractalType) + { + default: + return GenNoiseSingle(mSeed, x, y, z); + case FractalType_FBm: + return GenFractalFBm(x, y, z); + case FractalType_Ridged: + return GenFractalRidged(x, y, z); + case FractalType_PingPong: + return GenFractalPingPong(x, y, z); + } + } + + + /// + /// 2D warps the input position using current domain warp settings + /// + /// + /// Example usage with GetNoise + /// DomainWarp(x, y) + /// noise = GetNoise(x, y) + /// + template + void DomainWarp(FNfloat& x, FNfloat& y) const + { + Arguments_must_be_floating_point_values(); + + switch (mFractalType) + { + default: + DomainWarpSingle(x, y); + break; + case FractalType_DomainWarpProgressive: + DomainWarpFractalProgressive(x, y); + break; + case FractalType_DomainWarpIndependent: + DomainWarpFractalIndependent(x, y); + break; + } + } + + /// + /// 3D warps the input position using current domain warp settings + /// + /// + /// Example usage with GetNoise + /// DomainWarp(x, y, z) + /// noise = GetNoise(x, y, z) + /// + template + void DomainWarp(FNfloat& x, FNfloat& y, FNfloat& z) const + { + Arguments_must_be_floating_point_values(); + + switch (mFractalType) + { + default: + DomainWarpSingle(x, y, z); + break; + case FractalType_DomainWarpProgressive: + DomainWarpFractalProgressive(x, y, z); + break; + case FractalType_DomainWarpIndependent: + DomainWarpFractalIndependent(x, y, z); + break; + } + } + +private: + template + struct Arguments_must_be_floating_point_values; + + enum TransformType3D + { + TransformType3D_None, + TransformType3D_ImproveXYPlanes, + TransformType3D_ImproveXZPlanes, + TransformType3D_DefaultOpenSimplex2 + }; + + int mSeed; + float mFrequency; + NoiseType mNoiseType; + RotationType3D mRotationType3D; + TransformType3D mTransformType3D; + + FractalType mFractalType; + int mOctaves; + float mLacunarity; + float mGain; + float mWeightedStrength; + float mPingPongStrength; + + float mFractalBounding; + + CellularDistanceFunction mCellularDistanceFunction; + CellularReturnType mCellularReturnType; + float mCellularJitterModifier; + + DomainWarpType mDomainWarpType; + TransformType3D mWarpTransformType3D; + float mDomainWarpAmp; + + + template + struct Lookup + { + static const T Gradients2D[]; + static const T Gradients3D[]; + static const T RandVecs2D[]; + static const T RandVecs3D[]; + }; + + static float FastMin(float a, float b) { return a < b ? a : b; } + + static float FastMax(float a, float b) { return a > b ? a : b; } + + static float FastAbs(float f) { return f < 0 ? -f : f; } + + static float FastSqrt(float f) { return sqrtf(f); } + + template + static int FastFloor(FNfloat f) { return f >= 0 ? (int)f : (int)f - 1; } + + template + static int FastRound(FNfloat f) { return f >= 0 ? (int)(f + 0.5f) : (int)(f - 0.5f); } + + static float Lerp(float a, float b, float t) { return a + t * (b - a); } + + static float InterpHermite(float t) { return t * t * (3 - 2 * t); } + + static float InterpQuintic(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } + + static float CubicLerp(float a, float b, float c, float d, float t) + { + float p = (d - c) - (a - b); + return t * t * t * p + t * t * ((a - b) - p) + t * (c - a) + b; + } + + static float PingPong(float t) + { + t -= (int)(t * 0.5f) * 2; + return t < 1 ? t : 2 - t; + } + + void CalculateFractalBounding() + { + float gain = FastAbs(mGain); + float amp = gain; + float ampFractal = 1.0f; + for (int i = 1; i < mOctaves; i++) + { + ampFractal += amp; + amp *= gain; + } + mFractalBounding = 1 / ampFractal; + } + + // Hashing + static const int PrimeX = 501125321; + static const int PrimeY = 1136930381; + static const int PrimeZ = 1720413743; + + static int Hash(int seed, int xPrimed, int yPrimed) + { + int hash = seed ^ xPrimed ^ yPrimed; + + hash *= 0x27d4eb2d; + return hash; + } + + + static int Hash(int seed, int xPrimed, int yPrimed, int zPrimed) + { + int hash = seed ^ xPrimed ^ yPrimed ^ zPrimed; + + hash *= 0x27d4eb2d; + return hash; + } + + + static float ValCoord(int seed, int xPrimed, int yPrimed) + { + int hash = Hash(seed, xPrimed, yPrimed); + + hash *= hash; + hash ^= hash << 19; + return hash * (1 / 2147483648.0f); + } + + + static float ValCoord(int seed, int xPrimed, int yPrimed, int zPrimed) + { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + + hash *= hash; + hash ^= hash << 19; + return hash * (1 / 2147483648.0f); + } + + + float GradCoord(int seed, int xPrimed, int yPrimed, float xd, float yd) const + { + int hash = Hash(seed, xPrimed, yPrimed); + hash ^= hash >> 15; + hash &= 127 << 1; + + float xg = Lookup::Gradients2D[hash]; + float yg = Lookup::Gradients2D[hash | 1]; + + return xd * xg + yd * yg; + } + + + float GradCoord(int seed, int xPrimed, int yPrimed, int zPrimed, float xd, float yd, float zd) const + { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + hash ^= hash >> 15; + hash &= 63 << 2; + + float xg = Lookup::Gradients3D[hash]; + float yg = Lookup::Gradients3D[hash | 1]; + float zg = Lookup::Gradients3D[hash | 2]; + + return xd * xg + yd * yg + zd * zg; + } + + + void GradCoordOut(int seed, int xPrimed, int yPrimed, float& xo, float& yo) const + { + int hash = Hash(seed, xPrimed, yPrimed) & (255 << 1); + + xo = Lookup::RandVecs2D[hash]; + yo = Lookup::RandVecs2D[hash | 1]; + } + + + void GradCoordOut(int seed, int xPrimed, int yPrimed, int zPrimed, float& xo, float& yo, float& zo) const + { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed) & (255 << 2); + + xo = Lookup::RandVecs3D[hash]; + yo = Lookup::RandVecs3D[hash | 1]; + zo = Lookup::RandVecs3D[hash | 2]; + } + + + void GradCoordDual(int seed, int xPrimed, int yPrimed, float xd, float yd, float& xo, float& yo) const + { + int hash = Hash(seed, xPrimed, yPrimed); + int index1 = hash & (127 << 1); + int index2 = (hash >> 7) & (255 << 1); + + float xg = Lookup::Gradients2D[index1]; + float yg = Lookup::Gradients2D[index1 | 1]; + float value = xd * xg + yd * yg; + + float xgo = Lookup::RandVecs2D[index2]; + float ygo = Lookup::RandVecs2D[index2 | 1]; + + xo = value * xgo; + yo = value * ygo; + } + + + void GradCoordDual(int seed, int xPrimed, int yPrimed, int zPrimed, float xd, float yd, float zd, float& xo, float& yo, float& zo) const + { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int index1 = hash & (63 << 2); + int index2 = (hash >> 6) & (255 << 2); + + float xg = Lookup::Gradients3D[index1]; + float yg = Lookup::Gradients3D[index1 | 1]; + float zg = Lookup::Gradients3D[index1 | 2]; + float value = xd * xg + yd * yg + zd * zg; + + float xgo = Lookup::RandVecs3D[index2]; + float ygo = Lookup::RandVecs3D[index2 | 1]; + float zgo = Lookup::RandVecs3D[index2 | 2]; + + xo = value * xgo; + yo = value * ygo; + zo = value * zgo; + } + + + // Generic noise gen + + template + float GenNoiseSingle(int seed, FNfloat x, FNfloat y) const + { + switch (mNoiseType) + { + case NoiseType_OpenSimplex2: + return SingleSimplex(seed, x, y); + case NoiseType_OpenSimplex2S: + return SingleOpenSimplex2S(seed, x, y); + case NoiseType_Cellular: + return SingleCellular(seed, x, y); + case NoiseType_Perlin: + return SinglePerlin(seed, x, y); + case NoiseType_ValueCubic: + return SingleValueCubic(seed, x, y); + case NoiseType_Value: + return SingleValue(seed, x, y); + default: + return 0; + } + } + + template + float GenNoiseSingle(int seed, FNfloat x, FNfloat y, FNfloat z) const + { + switch (mNoiseType) + { + case NoiseType_OpenSimplex2: + return SingleOpenSimplex2(seed, x, y, z); + case NoiseType_OpenSimplex2S: + return SingleOpenSimplex2S(seed, x, y, z); + case NoiseType_Cellular: + return SingleCellular(seed, x, y, z); + case NoiseType_Perlin: + return SinglePerlin(seed, x, y, z); + case NoiseType_ValueCubic: + return SingleValueCubic(seed, x, y, z); + case NoiseType_Value: + return SingleValue(seed, x, y, z); + default: + return 0; + } + } + + + // Noise Coordinate Transforms (frequency, and possible skew or rotation) + + template + void TransformNoiseCoordinate(FNfloat& x, FNfloat& y) const + { + x *= mFrequency; + y *= mFrequency; + + switch (mNoiseType) + { + case NoiseType_OpenSimplex2: + case NoiseType_OpenSimplex2S: + { + const FNfloat SQRT3 = (FNfloat)1.7320508075688772935274463415059; + const FNfloat F2 = 0.5f * (SQRT3 - 1); + FNfloat t = (x + y) * F2; + x += t; + y += t; + } + break; + default: + break; + } + } + + template + void TransformNoiseCoordinate(FNfloat& x, FNfloat& y, FNfloat& z) const + { + x *= mFrequency; + y *= mFrequency; + z *= mFrequency; + + switch (mTransformType3D) + { + case TransformType3D_ImproveXYPlanes: + { + FNfloat xy = x + y; + FNfloat s2 = xy * -(FNfloat)0.211324865405187; + z *= (FNfloat)0.577350269189626; + x += s2 - z; + y = y + s2 - z; + z += xy * (FNfloat)0.577350269189626; + } + break; + case TransformType3D_ImproveXZPlanes: + { + FNfloat xz = x + z; + FNfloat s2 = xz * -(FNfloat)0.211324865405187; + y *= (FNfloat)0.577350269189626; + x += s2 - y; + z += s2 - y; + y += xz * (FNfloat)0.577350269189626; + } + break; + case TransformType3D_DefaultOpenSimplex2: + { + const FNfloat R3 = (FNfloat)(2.0 / 3.0); + FNfloat r = (x + y + z) * R3; // Rotation, not skew + x = r - x; + y = r - y; + z = r - z; + } + break; + default: + break; + } + } + + void UpdateTransformType3D() + { + switch (mRotationType3D) + { + case RotationType3D_ImproveXYPlanes: + mTransformType3D = TransformType3D_ImproveXYPlanes; + break; + case RotationType3D_ImproveXZPlanes: + mTransformType3D = TransformType3D_ImproveXZPlanes; + break; + default: + switch (mNoiseType) + { + case NoiseType_OpenSimplex2: + case NoiseType_OpenSimplex2S: + mTransformType3D = TransformType3D_DefaultOpenSimplex2; + break; + default: + mTransformType3D = TransformType3D_None; + break; + } + break; + } + } + + + // Domain Warp Coordinate Transforms + + template + void TransformDomainWarpCoordinate(FNfloat& x, FNfloat& y) const + { + switch (mDomainWarpType) + { + case DomainWarpType_OpenSimplex2: + case DomainWarpType_OpenSimplex2Reduced: + { + const FNfloat SQRT3 = (FNfloat)1.7320508075688772935274463415059; + const FNfloat F2 = 0.5f * (SQRT3 - 1); + FNfloat t = (x + y) * F2; + x += t; + y += t; + } + break; + default: + break; + } + } + + template + void TransformDomainWarpCoordinate(FNfloat& x, FNfloat& y, FNfloat& z) const + { + switch (mWarpTransformType3D) + { + case TransformType3D_ImproveXYPlanes: + { + FNfloat xy = x + y; + FNfloat s2 = xy * -(FNfloat)0.211324865405187; + z *= (FNfloat)0.577350269189626; + x += s2 - z; + y = y + s2 - z; + z += xy * (FNfloat)0.577350269189626; + } + break; + case TransformType3D_ImproveXZPlanes: + { + FNfloat xz = x + z; + FNfloat s2 = xz * -(FNfloat)0.211324865405187; + y *= (FNfloat)0.577350269189626; + x += s2 - y; + z += s2 - y; + y += xz * (FNfloat)0.577350269189626; + } + break; + case TransformType3D_DefaultOpenSimplex2: + { + const FNfloat R3 = (FNfloat)(2.0 / 3.0); + FNfloat r = (x + y + z) * R3; // Rotation, not skew + x = r - x; + y = r - y; + z = r - z; + } + break; + default: + break; + } + } + + void UpdateWarpTransformType3D() + { + switch (mRotationType3D) + { + case RotationType3D_ImproveXYPlanes: + mWarpTransformType3D = TransformType3D_ImproveXYPlanes; + break; + case RotationType3D_ImproveXZPlanes: + mWarpTransformType3D = TransformType3D_ImproveXZPlanes; + break; + default: + switch (mDomainWarpType) + { + case DomainWarpType_OpenSimplex2: + case DomainWarpType_OpenSimplex2Reduced: + mWarpTransformType3D = TransformType3D_DefaultOpenSimplex2; + break; + default: + mWarpTransformType3D = TransformType3D_None; + break; + } + break; + } + } + + + // Fractal FBm + + template + float GenFractalFBm(FNfloat x, FNfloat y) const + { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) + { + float noise = GenNoiseSingle(seed++, x, y); + sum += noise * amp; + amp *= Lerp(1.0f, FastMin(noise + 1, 2) * 0.5f, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + template + float GenFractalFBm(FNfloat x, FNfloat y, FNfloat z) const + { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) + { + float noise = GenNoiseSingle(seed++, x, y, z); + sum += noise * amp; + amp *= Lerp(1.0f, (noise + 1) * 0.5f, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + z *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + + // Fractal Ridged + + template + float GenFractalRidged(FNfloat x, FNfloat y) const + { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) + { + float noise = FastAbs(GenNoiseSingle(seed++, x, y)); + sum += (noise * -2 + 1) * amp; + amp *= Lerp(1.0f, 1 - noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + template + float GenFractalRidged(FNfloat x, FNfloat y, FNfloat z) const + { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) + { + float noise = FastAbs(GenNoiseSingle(seed++, x, y, z)); + sum += (noise * -2 + 1) * amp; + amp *= Lerp(1.0f, 1 - noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + z *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + + // Fractal PingPong + + template + float GenFractalPingPong(FNfloat x, FNfloat y) const + { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) + { + float noise = PingPong((GenNoiseSingle(seed++, x, y) + 1) * mPingPongStrength); + sum += (noise - 0.5f) * 2 * amp; + amp *= Lerp(1.0f, noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + template + float GenFractalPingPong(FNfloat x, FNfloat y, FNfloat z) const + { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) + { + float noise = PingPong((GenNoiseSingle(seed++, x, y, z) + 1) * mPingPongStrength); + sum += (noise - 0.5f) * 2 * amp; + amp *= Lerp(1.0f, noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + z *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + + // Simplex/OpenSimplex2 Noise + + template + float SingleSimplex(int seed, FNfloat x, FNfloat y) const + { + // 2D OpenSimplex2 case uses the same algorithm as ordinary Simplex. + + const float SQRT3 = 1.7320508075688772935274463415059f; + const float G2 = (3 - SQRT3) / 6; + + /* + * --- Skew moved to TransformNoiseCoordinate method --- + * const FNfloat F2 = 0.5f * (SQRT3 - 1); + * FNfloat s = (x + y) * F2; + * x += s; y += s; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + float xi = (float)(x - i); + float yi = (float)(y - j); + + float t = (xi + yi) * G2; + float x0 = (float)(xi - t); + float y0 = (float)(yi - t); + + i *= PrimeX; + j *= PrimeY; + + float n0, n1, n2; + + float a = 0.5f - x0 * x0 - y0 * y0; + if (a <= 0) n0 = 0; + else + { + n0 = (a * a) * (a * a) * GradCoord(seed, i, j, x0, y0); + } + + float c = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a); + if (c <= 0) n2 = 0; + else + { + float x2 = x0 + (2 * (float)G2 - 1); + float y2 = y0 + (2 * (float)G2 - 1); + n2 = (c * c) * (c * c) * GradCoord(seed, i + PrimeX, j + PrimeY, x2, y2); + } + + if (y0 > x0) + { + float x1 = x0 + (float)G2; + float y1 = y0 + ((float)G2 - 1); + float b = 0.5f - x1 * x1 - y1 * y1; + if (b <= 0) n1 = 0; + else + { + n1 = (b * b) * (b * b) * GradCoord(seed, i, j + PrimeY, x1, y1); + } + } + else + { + float x1 = x0 + ((float)G2 - 1); + float y1 = y0 + (float)G2; + float b = 0.5f - x1 * x1 - y1 * y1; + if (b <= 0) n1 = 0; + else + { + n1 = (b * b) * (b * b) * GradCoord(seed, i + PrimeX, j, x1, y1); + } + } + + return (n0 + n1 + n2) * 99.83685446303647f; + } + + template + float SingleOpenSimplex2(int seed, FNfloat x, FNfloat y, FNfloat z) const + { + // 3D OpenSimplex2 case uses two offset rotated cube grids. + + /* + * --- Rotation moved to TransformNoiseCoordinate method --- + * const FNfloat R3 = (FNfloat)(2.0 / 3.0); + * FNfloat r = (x + y + z) * R3; // Rotation, not skew + * x = r - x; y = r - y; z = r - z; + */ + + int i = FastRound(x); + int j = FastRound(y); + int k = FastRound(z); + float x0 = (float)(x - i); + float y0 = (float)(y - j); + float z0 = (float)(z - k); + + int xNSign = (int)(-1.0f - x0) | 1; + int yNSign = (int)(-1.0f - y0) | 1; + int zNSign = (int)(-1.0f - z0) | 1; + + float ax0 = xNSign * -x0; + float ay0 = yNSign * -y0; + float az0 = zNSign * -z0; + + i *= PrimeX; + j *= PrimeY; + k *= PrimeZ; + + float value = 0; + float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0); + + for (int l = 0; ; l++) + { + if (a > 0) + { + value += (a * a) * (a * a) * GradCoord(seed, i, j, k, x0, y0, z0); + } + + float b = a + 1; + int i1 = i; + int j1 = j; + int k1 = k; + float x1 = x0; + float y1 = y0; + float z1 = z0; + + if (ax0 >= ay0 && ax0 >= az0) + { + x1 += xNSign; + b -= xNSign * 2 * x1; + i1 -= xNSign * PrimeX; + } + else if (ay0 > ax0 && ay0 >= az0) + { + y1 += yNSign; + b -= yNSign * 2 * y1; + j1 -= yNSign * PrimeY; + } + else + { + z1 += zNSign; + b -= zNSign * 2 * z1; + k1 -= zNSign * PrimeZ; + } + + if (b > 0) + { + value += (b * b) * (b * b) * GradCoord(seed, i1, j1, k1, x1, y1, z1); + } + + if (l == 1) break; + + ax0 = 0.5f - ax0; + ay0 = 0.5f - ay0; + az0 = 0.5f - az0; + + x0 = xNSign * ax0; + y0 = yNSign * ay0; + z0 = zNSign * az0; + + a += (0.75f - ax0) - (ay0 + az0); + + i += (xNSign >> 1) & PrimeX; + j += (yNSign >> 1) & PrimeY; + k += (zNSign >> 1) & PrimeZ; + + xNSign = -xNSign; + yNSign = -yNSign; + zNSign = -zNSign; + + seed = ~seed; + } + + return value * 32.69428253173828125f; + } + + + // OpenSimplex2S Noise + + template + float SingleOpenSimplex2S(int seed, FNfloat x, FNfloat y) const + { + // 2D OpenSimplex2S case is a modified 2D simplex noise. + + const FNfloat SQRT3 = (FNfloat)1.7320508075688772935274463415059; + const FNfloat G2 = (3 - SQRT3) / 6; + + /* + * --- Skew moved to TransformNoiseCoordinate method --- + * const FNfloat F2 = 0.5f * (SQRT3 - 1); + * FNfloat s = (x + y) * F2; + * x += s; y += s; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + float xi = (float)(x - i); + float yi = (float)(y - j); + + i *= PrimeX; + j *= PrimeY; + int i1 = i + PrimeX; + int j1 = j + PrimeY; + + float t = (xi + yi) * (float)G2; + float x0 = xi - t; + float y0 = yi - t; + + float a0 = (2.0f / 3.0f) - x0 * x0 - y0 * y0; + float value = (a0 * a0) * (a0 * a0) * GradCoord(seed, i, j, x0, y0); + + float a1 = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a0); + float x1 = x0 - (float)(1 - 2 * G2); + float y1 = y0 - (float)(1 - 2 * G2); + value += (a1 * a1) * (a1 * a1) * GradCoord(seed, i1, j1, x1, y1); + + // Nested conditionals were faster than compact bit logic/arithmetic. + float xmyi = xi - yi; + if (t > G2) + { + if (xi + xmyi > 1) + { + float x2 = x0 + (float)(3 * G2 - 2); + float y2 = y0 + (float)(3 * G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) + { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i + (PrimeX << 1), j + PrimeY, x2, y2); + } + } + else + { + float x2 = x0 + (float)G2; + float y2 = y0 + (float)(G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) + { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j + PrimeY, x2, y2); + } + } + + if (yi - xmyi > 1) + { + float x3 = x0 + (float)(3 * G2 - 1); + float y3 = y0 + (float)(3 * G2 - 2); + float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3; + if (a3 > 0) + { + value += (a3 * a3) * (a3 * a3) * GradCoord(seed, i + PrimeX, j + (PrimeY << 1), x3, y3); + } + } + else + { + float x3 = x0 + (float)(G2 - 1); + float y3 = y0 + (float)G2; + float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3; + if (a3 > 0) + { + value += (a3 * a3) * (a3 * a3) * GradCoord(seed, i + PrimeX, j, x3, y3); + } + } + } + else + { + if (xi + xmyi < 0) + { + float x2 = x0 + (float)(1 - G2); + float y2 = y0 - (float)G2; + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) + { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i - PrimeX, j, x2, y2); + } + } + else + { + float x2 = x0 + (float)(G2 - 1); + float y2 = y0 + (float)G2; + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) + { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i + PrimeX, j, x2, y2); + } + } + + if (yi < xmyi) + { + float x2 = x0 - (float)G2; + float y2 = y0 - (float)(G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) + { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j - PrimeY, x2, y2); + } + } + else + { + float x2 = x0 + (float)G2; + float y2 = y0 + (float)(G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) + { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j + PrimeY, x2, y2); + } + } + } + + return value * 18.24196194486065f; + } + + template + float SingleOpenSimplex2S(int seed, FNfloat x, FNfloat y, FNfloat z) const + { + // 3D OpenSimplex2S case uses two offset rotated cube grids. + + /* + * --- Rotation moved to TransformNoiseCoordinate method --- + * const FNfloat R3 = (FNfloat)(2.0 / 3.0); + * FNfloat r = (x + y + z) * R3; // Rotation, not skew + * x = r - x; y = r - y; z = r - z; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + int k = FastFloor(z); + float xi = (float)(x - i); + float yi = (float)(y - j); + float zi = (float)(z - k); + + i *= PrimeX; + j *= PrimeY; + k *= PrimeZ; + int seed2 = seed + 1293373; + + int xNMask = (int)(-0.5f - xi); + int yNMask = (int)(-0.5f - yi); + int zNMask = (int)(-0.5f - zi); + + float x0 = xi + xNMask; + float y0 = yi + yNMask; + float z0 = zi + zNMask; + float a0 = 0.75f - x0 * x0 - y0 * y0 - z0 * z0; + float value = (a0 * a0) * (a0 * a0) * GradCoord(seed, + i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x0, y0, z0); + + float x1 = xi - 0.5f; + float y1 = yi - 0.5f; + float z1 = zi - 0.5f; + float a1 = 0.75f - x1 * x1 - y1 * y1 - z1 * z1; + value += (a1 * a1) * (a1 * a1) * GradCoord(seed2, + i + PrimeX, j + PrimeY, k + PrimeZ, x1, y1, z1); + + float xAFlipMask0 = ((xNMask | 1) << 1) * x1; + float yAFlipMask0 = ((yNMask | 1) << 1) * y1; + float zAFlipMask0 = ((zNMask | 1) << 1) * z1; + float xAFlipMask1 = (-2 - (xNMask << 2)) * x1 - 1.0f; + float yAFlipMask1 = (-2 - (yNMask << 2)) * y1 - 1.0f; + float zAFlipMask1 = (-2 - (zNMask << 2)) * z1 - 1.0f; + + bool skip5 = false; + float a2 = xAFlipMask0 + a0; + if (a2 > 0) + { + float x2 = x0 - (xNMask | 1); + float y2 = y0; + float z2 = z0; + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, + i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x2, y2, z2); + } + else + { + float a3 = yAFlipMask0 + zAFlipMask0 + a0; + if (a3 > 0) + { + float x3 = x0; + float y3 = y0 - (yNMask | 1); + float z3 = z0 - (zNMask | 1); + value += (a3 * a3) * (a3 * a3) * GradCoord(seed, + i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (~zNMask & PrimeZ), x3, y3, z3); + } + + float a4 = xAFlipMask1 + a1; + if (a4 > 0) + { + float x4 = (xNMask | 1) + x1; + float y4 = y1; + float z4 = z1; + value += (a4 * a4) * (a4 * a4) * GradCoord(seed2, + i + (xNMask & (PrimeX * 2)), j + PrimeY, k + PrimeZ, x4, y4, z4); + skip5 = true; + } + } + + bool skip9 = false; + float a6 = yAFlipMask0 + a0; + if (a6 > 0) + { + float x6 = x0; + float y6 = y0 - (yNMask | 1); + float z6 = z0; + value += (a6 * a6) * (a6 * a6) * GradCoord(seed, + i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), x6, y6, z6); + } + else + { + float a7 = xAFlipMask0 + zAFlipMask0 + a0; + if (a7 > 0) + { + float x7 = x0 - (xNMask | 1); + float y7 = y0; + float z7 = z0 - (zNMask | 1); + value += (a7 * a7) * (a7 * a7) * GradCoord(seed, + i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), x7, y7, z7); + } + + float a8 = yAFlipMask1 + a1; + if (a8 > 0) + { + float x8 = x1; + float y8 = (yNMask | 1) + y1; + float z8 = z1; + value += (a8 * a8) * (a8 * a8) * GradCoord(seed2, + i + PrimeX, j + (yNMask & (PrimeY << 1)), k + PrimeZ, x8, y8, z8); + skip9 = true; + } + } + + bool skipD = false; + float aA = zAFlipMask0 + a0; + if (aA > 0) + { + float xA = x0; + float yA = y0; + float zA = z0 - (zNMask | 1); + value += (aA * aA) * (aA * aA) * GradCoord(seed, + i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), xA, yA, zA); + } + else + { + float aB = xAFlipMask0 + yAFlipMask0 + a0; + if (aB > 0) + { + float xB = x0 - (xNMask | 1); + float yB = y0 - (yNMask | 1); + float zB = z0; + value += (aB * aB) * (aB * aB) * GradCoord(seed, + i + (~xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), xB, yB, zB); + } + + float aC = zAFlipMask1 + a1; + if (aC > 0) + { + float xC = x1; + float yC = y1; + float zC = (zNMask | 1) + z1; + value += (aC * aC) * (aC * aC) * GradCoord(seed2, + i + PrimeX, j + PrimeY, k + (zNMask & (PrimeZ << 1)), xC, yC, zC); + skipD = true; + } + } + + if (!skip5) + { + float a5 = yAFlipMask1 + zAFlipMask1 + a1; + if (a5 > 0) + { + float x5 = x1; + float y5 = (yNMask | 1) + y1; + float z5 = (zNMask | 1) + z1; + value += (a5 * a5) * (a5 * a5) * GradCoord(seed2, + i + PrimeX, j + (yNMask & (PrimeY << 1)), k + (zNMask & (PrimeZ << 1)), x5, y5, z5); + } + } + + if (!skip9) + { + float a9 = xAFlipMask1 + zAFlipMask1 + a1; + if (a9 > 0) + { + float x9 = (xNMask | 1) + x1; + float y9 = y1; + float z9 = (zNMask | 1) + z1; + value += (a9 * a9) * (a9 * a9) * GradCoord(seed2, + i + (xNMask & (PrimeX * 2)), j + PrimeY, k + (zNMask & (PrimeZ << 1)), x9, y9, z9); + } + } + + if (!skipD) + { + float aD = xAFlipMask1 + yAFlipMask1 + a1; + if (aD > 0) + { + float xD = (xNMask | 1) + x1; + float yD = (yNMask | 1) + y1; + float zD = z1; + value += (aD * aD) * (aD * aD) * GradCoord(seed2, + i + (xNMask & (PrimeX << 1)), j + (yNMask & (PrimeY << 1)), k + PrimeZ, xD, yD, zD); + } + } + + return value * 9.046026385208288f; + } + + + // Cellular Noise + + template + float SingleCellular(int seed, FNfloat x, FNfloat y) const + { + int xr = FastRound(x); + int yr = FastRound(y); + + float distance0 = 1e10f; + float distance1 = 1e10f; + int closestHash = 0; + + float cellularJitter = 0.43701595f * mCellularJitterModifier; + + int xPrimed = (xr - 1) * PrimeX; + int yPrimedBase = (yr - 1) * PrimeY; + + switch (mCellularDistanceFunction) + { + default: + case CellularDistanceFunction_Euclidean: + case CellularDistanceFunction_EuclideanSq: + for (int xi = xr - 1; xi <= xr + 1; xi++) + { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) + { + int hash = Hash(seed, xPrimed, yPrimed); + int idx = hash & (255 << 1); + + float vecX = (float)(xi - x) + Lookup::RandVecs2D[idx] * cellularJitter; + float vecY = (float)(yi - y) + Lookup::RandVecs2D[idx | 1] * cellularJitter; + + float newDistance = vecX * vecX + vecY * vecY; + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) + { + distance0 = newDistance; + closestHash = hash; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case CellularDistanceFunction_Manhattan: + for (int xi = xr - 1; xi <= xr + 1; xi++) + { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) + { + int hash = Hash(seed, xPrimed, yPrimed); + int idx = hash & (255 << 1); + + float vecX = (float)(xi - x) + Lookup::RandVecs2D[idx] * cellularJitter; + float vecY = (float)(yi - y) + Lookup::RandVecs2D[idx | 1] * cellularJitter; + + float newDistance = FastAbs(vecX) + FastAbs(vecY); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) + { + distance0 = newDistance; + closestHash = hash; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case CellularDistanceFunction_Hybrid: + for (int xi = xr - 1; xi <= xr + 1; xi++) + { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) + { + int hash = Hash(seed, xPrimed, yPrimed); + int idx = hash & (255 << 1); + + float vecX = (float)(xi - x) + Lookup::RandVecs2D[idx] * cellularJitter; + float vecY = (float)(yi - y) + Lookup::RandVecs2D[idx | 1] * cellularJitter; + + float newDistance = (FastAbs(vecX) + FastAbs(vecY)) + (vecX * vecX + vecY * vecY); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) + { + distance0 = newDistance; + closestHash = hash; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + } + + if (mCellularDistanceFunction == CellularDistanceFunction_Euclidean && mCellularReturnType >= CellularReturnType_Distance) + { + distance0 = FastSqrt(distance0); + + if (mCellularReturnType >= CellularReturnType_Distance2) + { + distance1 = FastSqrt(distance1); + } + } + + switch (mCellularReturnType) + { + case CellularReturnType_CellValue: + return closestHash * (1 / 2147483648.0f); + case CellularReturnType_Distance: + return distance0 - 1; + case CellularReturnType_Distance2: + return distance1 - 1; + case CellularReturnType_Distance2Add: + return (distance1 + distance0) * 0.5f - 1; + case CellularReturnType_Distance2Sub: + return distance1 - distance0 - 1; + case CellularReturnType_Distance2Mul: + return distance1 * distance0 * 0.5f - 1; + case CellularReturnType_Distance2Div: + return distance0 / distance1 - 1; + default: + return 0; + } + } + + template + float SingleCellular(int seed, FNfloat x, FNfloat y, FNfloat z) const + { + int xr = FastRound(x); + int yr = FastRound(y); + int zr = FastRound(z); + + float distance0 = 1e10f; + float distance1 = 1e10f; + int closestHash = 0; + + float cellularJitter = 0.39614353f * mCellularJitterModifier; + + int xPrimed = (xr - 1) * PrimeX; + int yPrimedBase = (yr - 1) * PrimeY; + int zPrimedBase = (zr - 1) * PrimeZ; + + switch (mCellularDistanceFunction) + { + case CellularDistanceFunction_Euclidean: + case CellularDistanceFunction_EuclideanSq: + for (int xi = xr - 1; xi <= xr + 1; xi++) + { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) + { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) + { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & (255 << 2); + + float vecX = (float)(xi - x) + Lookup::RandVecs3D[idx] * cellularJitter; + float vecY = (float)(yi - y) + Lookup::RandVecs3D[idx | 1] * cellularJitter; + float vecZ = (float)(zi - z) + Lookup::RandVecs3D[idx | 2] * cellularJitter; + + float newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ; + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) + { + distance0 = newDistance; + closestHash = hash; + } + zPrimed += PrimeZ; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case CellularDistanceFunction_Manhattan: + for (int xi = xr - 1; xi <= xr + 1; xi++) + { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) + { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) + { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & (255 << 2); + + float vecX = (float)(xi - x) + Lookup::RandVecs3D[idx] * cellularJitter; + float vecY = (float)(yi - y) + Lookup::RandVecs3D[idx | 1] * cellularJitter; + float vecZ = (float)(zi - z) + Lookup::RandVecs3D[idx | 2] * cellularJitter; + + float newDistance = FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) + { + distance0 = newDistance; + closestHash = hash; + } + zPrimed += PrimeZ; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case CellularDistanceFunction_Hybrid: + for (int xi = xr - 1; xi <= xr + 1; xi++) + { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) + { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) + { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & (255 << 2); + + float vecX = (float)(xi - x) + Lookup::RandVecs3D[idx] * cellularJitter; + float vecY = (float)(yi - y) + Lookup::RandVecs3D[idx | 1] * cellularJitter; + float vecZ = (float)(zi - z) + Lookup::RandVecs3D[idx | 2] * cellularJitter; + + float newDistance = (FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ)) + (vecX * vecX + vecY * vecY + vecZ * vecZ); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) + { + distance0 = newDistance; + closestHash = hash; + } + zPrimed += PrimeZ; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + default: + break; + } + + if (mCellularDistanceFunction == CellularDistanceFunction_Euclidean && mCellularReturnType >= CellularReturnType_Distance) + { + distance0 = FastSqrt(distance0); + + if (mCellularReturnType >= CellularReturnType_Distance2) + { + distance1 = FastSqrt(distance1); + } + } + + switch (mCellularReturnType) + { + case CellularReturnType_CellValue: + return closestHash * (1 / 2147483648.0f); + case CellularReturnType_Distance: + return distance0 - 1; + case CellularReturnType_Distance2: + return distance1 - 1; + case CellularReturnType_Distance2Add: + return (distance1 + distance0) * 0.5f - 1; + case CellularReturnType_Distance2Sub: + return distance1 - distance0 - 1; + case CellularReturnType_Distance2Mul: + return distance1 * distance0 * 0.5f - 1; + case CellularReturnType_Distance2Div: + return distance0 / distance1 - 1; + default: + return 0; + } + } + + + // Perlin Noise + + template + float SinglePerlin(int seed, FNfloat x, FNfloat y) const + { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + + float xd0 = (float)(x - x0); + float yd0 = (float)(y - y0); + float xd1 = xd0 - 1; + float yd1 = yd0 - 1; + + float xs = InterpQuintic(xd0); + float ys = InterpQuintic(yd0); + + x0 *= PrimeX; + y0 *= PrimeY; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + + float xf0 = Lerp(GradCoord(seed, x0, y0, xd0, yd0), GradCoord(seed, x1, y0, xd1, yd0), xs); + float xf1 = Lerp(GradCoord(seed, x0, y1, xd0, yd1), GradCoord(seed, x1, y1, xd1, yd1), xs); + + return Lerp(xf0, xf1, ys) * 1.4247691104677813f; + } + + template + float SinglePerlin(int seed, FNfloat x, FNfloat y, FNfloat z) const + { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + int z0 = FastFloor(z); + + float xd0 = (float)(x - x0); + float yd0 = (float)(y - y0); + float zd0 = (float)(z - z0); + float xd1 = xd0 - 1; + float yd1 = yd0 - 1; + float zd1 = zd0 - 1; + + float xs = InterpQuintic(xd0); + float ys = InterpQuintic(yd0); + float zs = InterpQuintic(zd0); + + x0 *= PrimeX; + y0 *= PrimeY; + z0 *= PrimeZ; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + int z1 = z0 + PrimeZ; + + float xf00 = Lerp(GradCoord(seed, x0, y0, z0, xd0, yd0, zd0), GradCoord(seed, x1, y0, z0, xd1, yd0, zd0), xs); + float xf10 = Lerp(GradCoord(seed, x0, y1, z0, xd0, yd1, zd0), GradCoord(seed, x1, y1, z0, xd1, yd1, zd0), xs); + float xf01 = Lerp(GradCoord(seed, x0, y0, z1, xd0, yd0, zd1), GradCoord(seed, x1, y0, z1, xd1, yd0, zd1), xs); + float xf11 = Lerp(GradCoord(seed, x0, y1, z1, xd0, yd1, zd1), GradCoord(seed, x1, y1, z1, xd1, yd1, zd1), xs); + + float yf0 = Lerp(xf00, xf10, ys); + float yf1 = Lerp(xf01, xf11, ys); + + return Lerp(yf0, yf1, zs) * 0.964921414852142333984375f; + } + + + // Value Cubic Noise + + template + float SingleValueCubic(int seed, FNfloat x, FNfloat y) const + { + int x1 = FastFloor(x); + int y1 = FastFloor(y); + + float xs = (float)(x - x1); + float ys = (float)(y - y1); + + x1 *= PrimeX; + y1 *= PrimeY; + int x0 = x1 - PrimeX; + int y0 = y1 - PrimeY; + int x2 = x1 + PrimeX; + int y2 = y1 + PrimeY; + int x3 = x1 + (int)((long)PrimeX << 1); + int y3 = y1 + (int)((long)PrimeY << 1); + + return CubicLerp( + CubicLerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), ValCoord(seed, x2, y0), ValCoord(seed, x3, y0), + xs), + CubicLerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), ValCoord(seed, x2, y1), ValCoord(seed, x3, y1), + xs), + CubicLerp(ValCoord(seed, x0, y2), ValCoord(seed, x1, y2), ValCoord(seed, x2, y2), ValCoord(seed, x3, y2), + xs), + CubicLerp(ValCoord(seed, x0, y3), ValCoord(seed, x1, y3), ValCoord(seed, x2, y3), ValCoord(seed, x3, y3), + xs), + ys) * (1 / (1.5f * 1.5f)); + } + + template + float SingleValueCubic(int seed, FNfloat x, FNfloat y, FNfloat z) const + { + int x1 = FastFloor(x); + int y1 = FastFloor(y); + int z1 = FastFloor(z); + + float xs = (float)(x - x1); + float ys = (float)(y - y1); + float zs = (float)(z - z1); + + x1 *= PrimeX; + y1 *= PrimeY; + z1 *= PrimeZ; + + int x0 = x1 - PrimeX; + int y0 = y1 - PrimeY; + int z0 = z1 - PrimeZ; + int x2 = x1 + PrimeX; + int y2 = y1 + PrimeY; + int z2 = z1 + PrimeZ; + int x3 = x1 + (int)((long)PrimeX << 1); + int y3 = y1 + (int)((long)PrimeY << 1); + int z3 = z1 + (int)((long)PrimeZ << 1); + + + return CubicLerp( + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), ValCoord(seed, x2, y0, z0), ValCoord(seed, x3, y0, z0), xs), + CubicLerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), ValCoord(seed, x2, y1, z0), ValCoord(seed, x3, y1, z0), xs), + CubicLerp(ValCoord(seed, x0, y2, z0), ValCoord(seed, x1, y2, z0), ValCoord(seed, x2, y2, z0), ValCoord(seed, x3, y2, z0), xs), + CubicLerp(ValCoord(seed, x0, y3, z0), ValCoord(seed, x1, y3, z0), ValCoord(seed, x2, y3, z0), ValCoord(seed, x3, y3, z0), xs), + ys), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), ValCoord(seed, x2, y0, z1), ValCoord(seed, x3, y0, z1), xs), + CubicLerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), ValCoord(seed, x2, y1, z1), ValCoord(seed, x3, y1, z1), xs), + CubicLerp(ValCoord(seed, x0, y2, z1), ValCoord(seed, x1, y2, z1), ValCoord(seed, x2, y2, z1), ValCoord(seed, x3, y2, z1), xs), + CubicLerp(ValCoord(seed, x0, y3, z1), ValCoord(seed, x1, y3, z1), ValCoord(seed, x2, y3, z1), ValCoord(seed, x3, y3, z1), xs), + ys), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z2), ValCoord(seed, x1, y0, z2), ValCoord(seed, x2, y0, z2), ValCoord(seed, x3, y0, z2), xs), + CubicLerp(ValCoord(seed, x0, y1, z2), ValCoord(seed, x1, y1, z2), ValCoord(seed, x2, y1, z2), ValCoord(seed, x3, y1, z2), xs), + CubicLerp(ValCoord(seed, x0, y2, z2), ValCoord(seed, x1, y2, z2), ValCoord(seed, x2, y2, z2), ValCoord(seed, x3, y2, z2), xs), + CubicLerp(ValCoord(seed, x0, y3, z2), ValCoord(seed, x1, y3, z2), ValCoord(seed, x2, y3, z2), ValCoord(seed, x3, y3, z2), xs), + ys), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z3), ValCoord(seed, x1, y0, z3), ValCoord(seed, x2, y0, z3), ValCoord(seed, x3, y0, z3), xs), + CubicLerp(ValCoord(seed, x0, y1, z3), ValCoord(seed, x1, y1, z3), ValCoord(seed, x2, y1, z3), ValCoord(seed, x3, y1, z3), xs), + CubicLerp(ValCoord(seed, x0, y2, z3), ValCoord(seed, x1, y2, z3), ValCoord(seed, x2, y2, z3), ValCoord(seed, x3, y2, z3), xs), + CubicLerp(ValCoord(seed, x0, y3, z3), ValCoord(seed, x1, y3, z3), ValCoord(seed, x2, y3, z3), ValCoord(seed, x3, y3, z3), xs), + ys), + zs) * (1 / (1.5f * 1.5f * 1.5f)); + } + + + // Value Noise + + template + float SingleValue(int seed, FNfloat x, FNfloat y) const + { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + + float xs = InterpHermite((float)(x - x0)); + float ys = InterpHermite((float)(y - y0)); + + x0 *= PrimeX; + y0 *= PrimeY; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + + float xf0 = Lerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), xs); + float xf1 = Lerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), xs); + + return Lerp(xf0, xf1, ys); + } + + template + float SingleValue(int seed, FNfloat x, FNfloat y, FNfloat z) const + { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + int z0 = FastFloor(z); + + float xs = InterpHermite((float)(x - x0)); + float ys = InterpHermite((float)(y - y0)); + float zs = InterpHermite((float)(z - z0)); + + x0 *= PrimeX; + y0 *= PrimeY; + z0 *= PrimeZ; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + int z1 = z0 + PrimeZ; + + float xf00 = Lerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), xs); + float xf10 = Lerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), xs); + float xf01 = Lerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), xs); + float xf11 = Lerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), xs); + + float yf0 = Lerp(xf00, xf10, ys); + float yf1 = Lerp(xf01, xf11, ys); + + return Lerp(yf0, yf1, zs); + } + + + // Domain Warp + + template + void DoSingleDomainWarp(int seed, float amp, float freq, FNfloat x, FNfloat y, FNfloat& xr, FNfloat& yr) const + { + switch (mDomainWarpType) + { + case DomainWarpType_OpenSimplex2: + SingleDomainWarpSimplexGradient(seed, amp * 38.283687591552734375f, freq, x, y, xr, yr, false); + break; + case DomainWarpType_OpenSimplex2Reduced: + SingleDomainWarpSimplexGradient(seed, amp * 16.0f, freq, x, y, xr, yr, true); + break; + case DomainWarpType_BasicGrid: + SingleDomainWarpBasicGrid(seed, amp, freq, x, y, xr, yr); + break; + } + } + + template + void DoSingleDomainWarp(int seed, float amp, float freq, FNfloat x, FNfloat y, FNfloat z, FNfloat& xr, FNfloat& yr, FNfloat& zr) const + { + switch (mDomainWarpType) + { + case DomainWarpType_OpenSimplex2: + SingleDomainWarpOpenSimplex2Gradient(seed, amp * 32.69428253173828125f, freq, x, y, z, xr, yr, zr, false); + break; + case DomainWarpType_OpenSimplex2Reduced: + SingleDomainWarpOpenSimplex2Gradient(seed, amp * 7.71604938271605f, freq, x, y, z, xr, yr, zr, true); + break; + case DomainWarpType_BasicGrid: + SingleDomainWarpBasicGrid(seed, amp, freq, x, y, z, xr, yr, zr); + break; + } + } + + + // Domain Warp Single Wrapper + + template + void DomainWarpSingle(FNfloat& x, FNfloat& y) const + { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + FNfloat xs = x; + FNfloat ys = y; + TransformDomainWarpCoordinate(xs, ys); + + DoSingleDomainWarp(seed, amp, freq, xs, ys, x, y); + } + + template + void DomainWarpSingle(FNfloat& x, FNfloat& y, FNfloat& z) const + { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + FNfloat xs = x; + FNfloat ys = y; + FNfloat zs = z; + TransformDomainWarpCoordinate(xs, ys, zs); + + DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, x, y, z); + } + + + // Domain Warp Fractal Progressive + + template + void DomainWarpFractalProgressive(FNfloat& x, FNfloat& y) const + { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) + { + FNfloat xs = x; + FNfloat ys = y; + TransformDomainWarpCoordinate(xs, ys); + + DoSingleDomainWarp(seed, amp, freq, xs, ys, x, y); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + template + void DomainWarpFractalProgressive(FNfloat& x, FNfloat& y, FNfloat& z) const + { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) + { + FNfloat xs = x; + FNfloat ys = y; + FNfloat zs = z; + TransformDomainWarpCoordinate(xs, ys, zs); + + DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, x, y, z); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + + // Domain Warp Fractal Independant + + template + void DomainWarpFractalIndependent(FNfloat& x, FNfloat& y) const + { + FNfloat xs = x; + FNfloat ys = y; + TransformDomainWarpCoordinate(xs, ys); + + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) + { + DoSingleDomainWarp(seed, amp, freq, xs, ys, x, y); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + template + void DomainWarpFractalIndependent(FNfloat& x, FNfloat& y, FNfloat& z) const + { + FNfloat xs = x; + FNfloat ys = y; + FNfloat zs = z; + TransformDomainWarpCoordinate(xs, ys, zs); + + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) + { + DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, x, y, z); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + + // Domain Warp Basic Grid + + template + void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, FNfloat x, FNfloat y, FNfloat& xr, FNfloat& yr) const + { + FNfloat xf = x * frequency; + FNfloat yf = y * frequency; + + int x0 = FastFloor(xf); + int y0 = FastFloor(yf); + + float xs = InterpHermite((float)(xf - x0)); + float ys = InterpHermite((float)(yf - y0)); + + x0 *= PrimeX; + y0 *= PrimeY; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + + int hash0 = Hash(seed, x0, y0) & (255 << 1); + int hash1 = Hash(seed, x1, y0) & (255 << 1); + + float lx0x = Lerp(Lookup::RandVecs2D[hash0], Lookup::RandVecs2D[hash1], xs); + float ly0x = Lerp(Lookup::RandVecs2D[hash0 | 1], Lookup::RandVecs2D[hash1 | 1], xs); + + hash0 = Hash(seed, x0, y1) & (255 << 1); + hash1 = Hash(seed, x1, y1) & (255 << 1); + + float lx1x = Lerp(Lookup::RandVecs2D[hash0], Lookup::RandVecs2D[hash1], xs); + float ly1x = Lerp(Lookup::RandVecs2D[hash0 | 1], Lookup::RandVecs2D[hash1 | 1], xs); + + xr += Lerp(lx0x, lx1x, ys) * warpAmp; + yr += Lerp(ly0x, ly1x, ys) * warpAmp; + } + + template + void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, FNfloat x, FNfloat y, FNfloat z, FNfloat& xr, FNfloat& yr, FNfloat& zr) const + { + FNfloat xf = x * frequency; + FNfloat yf = y * frequency; + FNfloat zf = z * frequency; + + int x0 = FastFloor(xf); + int y0 = FastFloor(yf); + int z0 = FastFloor(zf); + + float xs = InterpHermite((float)(xf - x0)); + float ys = InterpHermite((float)(yf - y0)); + float zs = InterpHermite((float)(zf - z0)); + + x0 *= PrimeX; + y0 *= PrimeY; + z0 *= PrimeZ; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + int z1 = z0 + PrimeZ; + + int hash0 = Hash(seed, x0, y0, z0) & (255 << 2); + int hash1 = Hash(seed, x1, y0, z0) & (255 << 2); + + float lx0x = Lerp(Lookup::RandVecs3D[hash0], Lookup::RandVecs3D[hash1], xs); + float ly0x = Lerp(Lookup::RandVecs3D[hash0 | 1], Lookup::RandVecs3D[hash1 | 1], xs); + float lz0x = Lerp(Lookup::RandVecs3D[hash0 | 2], Lookup::RandVecs3D[hash1 | 2], xs); + + hash0 = Hash(seed, x0, y1, z0) & (255 << 2); + hash1 = Hash(seed, x1, y1, z0) & (255 << 2); + + float lx1x = Lerp(Lookup::RandVecs3D[hash0], Lookup::RandVecs3D[hash1], xs); + float ly1x = Lerp(Lookup::RandVecs3D[hash0 | 1], Lookup::RandVecs3D[hash1 | 1], xs); + float lz1x = Lerp(Lookup::RandVecs3D[hash0 | 2], Lookup::RandVecs3D[hash1 | 2], xs); + + float lx0y = Lerp(lx0x, lx1x, ys); + float ly0y = Lerp(ly0x, ly1x, ys); + float lz0y = Lerp(lz0x, lz1x, ys); + + hash0 = Hash(seed, x0, y0, z1) & (255 << 2); + hash1 = Hash(seed, x1, y0, z1) & (255 << 2); + + lx0x = Lerp(Lookup::RandVecs3D[hash0], Lookup::RandVecs3D[hash1], xs); + ly0x = Lerp(Lookup::RandVecs3D[hash0 | 1], Lookup::RandVecs3D[hash1 | 1], xs); + lz0x = Lerp(Lookup::RandVecs3D[hash0 | 2], Lookup::RandVecs3D[hash1 | 2], xs); + + hash0 = Hash(seed, x0, y1, z1) & (255 << 2); + hash1 = Hash(seed, x1, y1, z1) & (255 << 2); + + lx1x = Lerp(Lookup::RandVecs3D[hash0], Lookup::RandVecs3D[hash1], xs); + ly1x = Lerp(Lookup::RandVecs3D[hash0 | 1], Lookup::RandVecs3D[hash1 | 1], xs); + lz1x = Lerp(Lookup::RandVecs3D[hash0 | 2], Lookup::RandVecs3D[hash1 | 2], xs); + + xr += Lerp(lx0y, Lerp(lx0x, lx1x, ys), zs) * warpAmp; + yr += Lerp(ly0y, Lerp(ly0x, ly1x, ys), zs) * warpAmp; + zr += Lerp(lz0y, Lerp(lz0x, lz1x, ys), zs) * warpAmp; + } + + + // Domain Warp Simplex/OpenSimplex2 + + template + void SingleDomainWarpSimplexGradient(int seed, float warpAmp, float frequency, FNfloat x, FNfloat y, FNfloat& xr, FNfloat& yr, bool outGradOnly) const + { + const float SQRT3 = 1.7320508075688772935274463415059f; + const float G2 = (3 - SQRT3) / 6; + + x *= frequency; + y *= frequency; + + /* + * --- Skew moved to TransformNoiseCoordinate method --- + * const FNfloat F2 = 0.5f * (SQRT3 - 1); + * FNfloat s = (x + y) * F2; + * x += s; y += s; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + float xi = (float)(x - i); + float yi = (float)(y - j); + + float t = (xi + yi) * G2; + float x0 = (float)(xi - t); + float y0 = (float)(yi - t); + + i *= PrimeX; + j *= PrimeY; + + float vx, vy; + vx = vy = 0; + + float a = 0.5f - x0 * x0 - y0 * y0; + if (a > 0) + { + float aaaa = (a * a) * (a * a); + float xo, yo; + if (outGradOnly) + GradCoordOut(seed, i, j, xo, yo); + else + GradCoordDual(seed, i, j, x0, y0, xo, yo); + vx += aaaa * xo; + vy += aaaa * yo; + } + + float c = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a); + if (c > 0) + { + float x2 = x0 + (2 * (float)G2 - 1); + float y2 = y0 + (2 * (float)G2 - 1); + float cccc = (c * c) * (c * c); + float xo, yo; + if (outGradOnly) + GradCoordOut(seed, i + PrimeX, j + PrimeY, xo, yo); + else + GradCoordDual(seed, i + PrimeX, j + PrimeY, x2, y2, xo, yo); + vx += cccc * xo; + vy += cccc * yo; + } + + if (y0 > x0) + { + float x1 = x0 + (float)G2; + float y1 = y0 + ((float)G2 - 1); + float b = 0.5f - x1 * x1 - y1 * y1; + if (b > 0) + { + float bbbb = (b * b) * (b * b); + float xo, yo; + if (outGradOnly) + GradCoordOut(seed, i, j + PrimeY, xo, yo); + else + GradCoordDual(seed, i, j + PrimeY, x1, y1, xo, yo); + vx += bbbb * xo; + vy += bbbb * yo; + } + } + else + { + float x1 = x0 + ((float)G2 - 1); + float y1 = y0 + (float)G2; + float b = 0.5f - x1 * x1 - y1 * y1; + if (b > 0) + { + float bbbb = (b * b) * (b * b); + float xo, yo; + if (outGradOnly) + GradCoordOut(seed, i + PrimeX, j, xo, yo); + else + GradCoordDual(seed, i + PrimeX, j, x1, y1, xo, yo); + vx += bbbb * xo; + vy += bbbb * yo; + } + } + + xr += vx * warpAmp; + yr += vy * warpAmp; + } + + template + void SingleDomainWarpOpenSimplex2Gradient(int seed, float warpAmp, float frequency, FNfloat x, FNfloat y, FNfloat z, FNfloat& xr, FNfloat& yr, FNfloat& zr, bool outGradOnly) const + { + x *= frequency; + y *= frequency; + z *= frequency; + + /* + * --- Rotation moved to TransformDomainWarpCoordinate method --- + * const FNfloat R3 = (FNfloat)(2.0 / 3.0); + * FNfloat r = (x + y + z) * R3; // Rotation, not skew + * x = r - x; y = r - y; z = r - z; + */ + + int i = FastRound(x); + int j = FastRound(y); + int k = FastRound(z); + float x0 = (float)x - i; + float y0 = (float)y - j; + float z0 = (float)z - k; + + int xNSign = (int)(-x0 - 1.0f) | 1; + int yNSign = (int)(-y0 - 1.0f) | 1; + int zNSign = (int)(-z0 - 1.0f) | 1; + + float ax0 = xNSign * -x0; + float ay0 = yNSign * -y0; + float az0 = zNSign * -z0; + + i *= PrimeX; + j *= PrimeY; + k *= PrimeZ; + + float vx, vy, vz; + vx = vy = vz = 0; + + float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0); + for (int l = 0; l < 2; l++) + { + if (a > 0) + { + float aaaa = (a * a) * (a * a); + float xo, yo, zo; + if (outGradOnly) + GradCoordOut(seed, i, j, k, xo, yo, zo); + else + GradCoordDual(seed, i, j, k, x0, y0, z0, xo, yo, zo); + vx += aaaa * xo; + vy += aaaa * yo; + vz += aaaa * zo; + } + + float b = a + 1; + int i1 = i; + int j1 = j; + int k1 = k; + float x1 = x0; + float y1 = y0; + float z1 = z0; + + if (ax0 >= ay0 && ax0 >= az0) + { + x1 += xNSign; + b -= xNSign * 2 * x1; + i1 -= xNSign * PrimeX; + } + else if (ay0 > ax0 && ay0 >= az0) + { + y1 += yNSign; + b -= yNSign * 2 * y1; + j1 -= yNSign * PrimeY; + } + else + { + z1 += zNSign; + b -= zNSign * 2 * z1; + k1 -= zNSign * PrimeZ; + } + + if (b > 0) + { + float bbbb = (b * b) * (b * b); + float xo, yo, zo; + if (outGradOnly) + GradCoordOut(seed, i1, j1, k1, xo, yo, zo); + else + GradCoordDual(seed, i1, j1, k1, x1, y1, z1, xo, yo, zo); + vx += bbbb * xo; + vy += bbbb * yo; + vz += bbbb * zo; + } + + if (l == 1) break; + + ax0 = 0.5f - ax0; + ay0 = 0.5f - ay0; + az0 = 0.5f - az0; + + x0 = xNSign * ax0; + y0 = yNSign * ay0; + z0 = zNSign * az0; + + a += (0.75f - ax0) - (ay0 + az0); + + i += (xNSign >> 1) & PrimeX; + j += (yNSign >> 1) & PrimeY; + k += (zNSign >> 1) & PrimeZ; + + xNSign = -xNSign; + yNSign = -yNSign; + zNSign = -zNSign; + + seed += 1293373; + } + + xr += vx * warpAmp; + yr += vy * warpAmp; + zr += vz * warpAmp; + } +}; + +template <> +struct FastNoiseLite::Arguments_must_be_floating_point_values {}; +template <> +struct FastNoiseLite::Arguments_must_be_floating_point_values {}; +template <> +struct FastNoiseLite::Arguments_must_be_floating_point_values {}; + +template +const T FastNoiseLite::Lookup::Gradients2D[] = +{ + 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, + 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, + 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, + -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, + -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, + -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, + 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, + 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, + 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, + -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, + -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, + -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, + 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, + 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, + 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, + -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, + -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, + -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, + 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, + 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, + 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, + -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, + -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, + -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, + 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, + 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, + 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, + -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, + -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, + -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, + 0.38268343236509f, 0.923879532511287f, 0.923879532511287f, 0.38268343236509f, 0.923879532511287f, -0.38268343236509f, 0.38268343236509f, -0.923879532511287f, + -0.38268343236509f, -0.923879532511287f, -0.923879532511287f, -0.38268343236509f, -0.923879532511287f, 0.38268343236509f, -0.38268343236509f, 0.923879532511287f, +}; + +template +const T FastNoiseLite::Lookup::RandVecs2D[] = +{ + -0.2700222198f, -0.9628540911f, 0.3863092627f, -0.9223693152f, 0.04444859006f, -0.999011673f, -0.5992523158f, -0.8005602176f, -0.7819280288f, 0.6233687174f, 0.9464672271f, 0.3227999196f, -0.6514146797f, -0.7587218957f, 0.9378472289f, 0.347048376f, + -0.8497875957f, -0.5271252623f, -0.879042592f, 0.4767432447f, -0.892300288f, -0.4514423508f, -0.379844434f, -0.9250503802f, -0.9951650832f, 0.0982163789f, 0.7724397808f, -0.6350880136f, 0.7573283322f, -0.6530343002f, -0.9928004525f, -0.119780055f, + -0.0532665713f, 0.9985803285f, 0.9754253726f, -0.2203300762f, -0.7665018163f, 0.6422421394f, 0.991636706f, 0.1290606184f, -0.994696838f, 0.1028503788f, -0.5379205513f, -0.84299554f, 0.5022815471f, -0.8647041387f, 0.4559821461f, -0.8899889226f, + -0.8659131224f, -0.5001944266f, 0.0879458407f, -0.9961252577f, -0.5051684983f, 0.8630207346f, 0.7753185226f, -0.6315704146f, -0.6921944612f, 0.7217110418f, -0.5191659449f, -0.8546734591f, 0.8978622882f, -0.4402764035f, -0.1706774107f, 0.9853269617f, + -0.9353430106f, -0.3537420705f, -0.9992404798f, 0.03896746794f, -0.2882064021f, -0.9575683108f, -0.9663811329f, 0.2571137995f, -0.8759714238f, -0.4823630009f, -0.8303123018f, -0.5572983775f, 0.05110133755f, -0.9986934731f, -0.8558373281f, -0.5172450752f, + 0.09887025282f, 0.9951003332f, 0.9189016087f, 0.3944867976f, -0.2439375892f, -0.9697909324f, -0.8121409387f, -0.5834613061f, -0.9910431363f, 0.1335421355f, 0.8492423985f, -0.5280031709f, -0.9717838994f, -0.2358729591f, 0.9949457207f, 0.1004142068f, + 0.6241065508f, -0.7813392434f, 0.662910307f, 0.7486988212f, -0.7197418176f, 0.6942418282f, -0.8143370775f, -0.5803922158f, 0.104521054f, -0.9945226741f, -0.1065926113f, -0.9943027784f, 0.445799684f, -0.8951327509f, 0.105547406f, 0.9944142724f, + -0.992790267f, 0.1198644477f, -0.8334366408f, 0.552615025f, 0.9115561563f, -0.4111755999f, 0.8285544909f, -0.5599084351f, 0.7217097654f, -0.6921957921f, 0.4940492677f, -0.8694339084f, -0.3652321272f, -0.9309164803f, -0.9696606758f, 0.2444548501f, + 0.08925509731f, -0.996008799f, 0.5354071276f, -0.8445941083f, -0.1053576186f, 0.9944343981f, -0.9890284586f, 0.1477251101f, 0.004856104961f, 0.9999882091f, 0.9885598478f, 0.1508291331f, 0.9286129562f, -0.3710498316f, -0.5832393863f, -0.8123003252f, + 0.3015207509f, 0.9534596146f, -0.9575110528f, 0.2883965738f, 0.9715802154f, -0.2367105511f, 0.229981792f, 0.9731949318f, 0.955763816f, -0.2941352207f, 0.740956116f, 0.6715534485f, -0.9971513787f, -0.07542630764f, 0.6905710663f, -0.7232645452f, + -0.290713703f, -0.9568100872f, 0.5912777791f, -0.8064679708f, -0.9454592212f, -0.325740481f, 0.6664455681f, 0.74555369f, 0.6236134912f, 0.7817328275f, 0.9126993851f, -0.4086316587f, -0.8191762011f, 0.5735419353f, -0.8812745759f, -0.4726046147f, + 0.9953313627f, 0.09651672651f, 0.9855650846f, -0.1692969699f, -0.8495980887f, 0.5274306472f, 0.6174853946f, -0.7865823463f, 0.8508156371f, 0.52546432f, 0.9985032451f, -0.05469249926f, 0.1971371563f, -0.9803759185f, 0.6607855748f, -0.7505747292f, + -0.03097494063f, 0.9995201614f, -0.6731660801f, 0.739491331f, -0.7195018362f, -0.6944905383f, 0.9727511689f, 0.2318515979f, 0.9997059088f, -0.0242506907f, 0.4421787429f, -0.8969269532f, 0.9981350961f, -0.061043673f, -0.9173660799f, -0.3980445648f, + -0.8150056635f, -0.5794529907f, -0.8789331304f, 0.4769450202f, 0.0158605829f, 0.999874213f, -0.8095464474f, 0.5870558317f, -0.9165898907f, -0.3998286786f, -0.8023542565f, 0.5968480938f, -0.5176737917f, 0.8555780767f, -0.8154407307f, -0.5788405779f, + 0.4022010347f, -0.9155513791f, -0.9052556868f, -0.4248672045f, 0.7317445619f, 0.6815789728f, -0.5647632201f, -0.8252529947f, -0.8403276335f, -0.5420788397f, -0.9314281527f, 0.363925262f, 0.5238198472f, 0.8518290719f, 0.7432803869f, -0.6689800195f, + -0.985371561f, -0.1704197369f, 0.4601468731f, 0.88784281f, 0.825855404f, 0.5638819483f, 0.6182366099f, 0.7859920446f, 0.8331502863f, -0.553046653f, 0.1500307506f, 0.9886813308f, -0.662330369f, -0.7492119075f, -0.668598664f, 0.743623444f, + 0.7025606278f, 0.7116238924f, -0.5419389763f, -0.8404178401f, -0.3388616456f, 0.9408362159f, 0.8331530315f, 0.5530425174f, -0.2989720662f, -0.9542618632f, 0.2638522993f, 0.9645630949f, 0.124108739f, -0.9922686234f, -0.7282649308f, -0.6852956957f, + 0.6962500149f, 0.7177993569f, -0.9183535368f, 0.3957610156f, -0.6326102274f, -0.7744703352f, -0.9331891859f, -0.359385508f, -0.1153779357f, -0.9933216659f, 0.9514974788f, -0.3076565421f, -0.08987977445f, -0.9959526224f, 0.6678496916f, 0.7442961705f, + 0.7952400393f, -0.6062947138f, -0.6462007402f, -0.7631674805f, -0.2733598753f, 0.9619118351f, 0.9669590226f, -0.254931851f, -0.9792894595f, 0.2024651934f, -0.5369502995f, -0.8436138784f, -0.270036471f, -0.9628500944f, -0.6400277131f, 0.7683518247f, + -0.7854537493f, -0.6189203566f, 0.06005905383f, -0.9981948257f, -0.02455770378f, 0.9996984141f, -0.65983623f, 0.751409442f, -0.6253894466f, -0.7803127835f, -0.6210408851f, -0.7837781695f, 0.8348888491f, 0.5504185768f, -0.1592275245f, 0.9872419133f, + 0.8367622488f, 0.5475663786f, -0.8675753916f, -0.4973056806f, -0.2022662628f, -0.9793305667f, 0.9399189937f, 0.3413975472f, 0.9877404807f, -0.1561049093f, -0.9034455656f, 0.4287028224f, 0.1269804218f, -0.9919052235f, -0.3819600854f, 0.924178821f, + 0.9754625894f, 0.2201652486f, -0.3204015856f, -0.9472818081f, -0.9874760884f, 0.1577687387f, 0.02535348474f, -0.9996785487f, 0.4835130794f, -0.8753371362f, -0.2850799925f, -0.9585037287f, -0.06805516006f, -0.99768156f, -0.7885244045f, -0.6150034663f, + 0.3185392127f, -0.9479096845f, 0.8880043089f, 0.4598351306f, 0.6476921488f, -0.7619021462f, 0.9820241299f, 0.1887554194f, 0.9357275128f, -0.3527237187f, -0.8894895414f, 0.4569555293f, 0.7922791302f, 0.6101588153f, 0.7483818261f, 0.6632681526f, + -0.7288929755f, -0.6846276581f, 0.8729032783f, -0.4878932944f, 0.8288345784f, 0.5594937369f, 0.08074567077f, 0.9967347374f, 0.9799148216f, -0.1994165048f, -0.580730673f, -0.8140957471f, -0.4700049791f, -0.8826637636f, 0.2409492979f, 0.9705377045f, + 0.9437816757f, -0.3305694308f, -0.8927998638f, -0.4504535528f, -0.8069622304f, 0.5906030467f, 0.06258973166f, 0.9980393407f, -0.9312597469f, 0.3643559849f, 0.5777449785f, 0.8162173362f, -0.3360095855f, -0.941858566f, 0.697932075f, -0.7161639607f, + -0.002008157227f, -0.9999979837f, -0.1827294312f, -0.9831632392f, -0.6523911722f, 0.7578824173f, -0.4302626911f, -0.9027037258f, -0.9985126289f, -0.05452091251f, -0.01028102172f, -0.9999471489f, -0.4946071129f, 0.8691166802f, -0.2999350194f, 0.9539596344f, + 0.8165471961f, 0.5772786819f, 0.2697460475f, 0.962931498f, -0.7306287391f, -0.6827749597f, -0.7590952064f, -0.6509796216f, -0.907053853f, 0.4210146171f, -0.5104861064f, -0.8598860013f, 0.8613350597f, 0.5080373165f, 0.5007881595f, -0.8655698812f, + -0.654158152f, 0.7563577938f, -0.8382755311f, -0.545246856f, 0.6940070834f, 0.7199681717f, 0.06950936031f, 0.9975812994f, 0.1702942185f, -0.9853932612f, 0.2695973274f, 0.9629731466f, 0.5519612192f, -0.8338697815f, 0.225657487f, -0.9742067022f, + 0.4215262855f, -0.9068161835f, 0.4881873305f, -0.8727388672f, -0.3683854996f, -0.9296731273f, -0.9825390578f, 0.1860564427f, 0.81256471f, 0.5828709909f, 0.3196460933f, -0.9475370046f, 0.9570913859f, 0.2897862643f, -0.6876655497f, -0.7260276109f, + -0.9988770922f, -0.047376731f, -0.1250179027f, 0.992154486f, -0.8280133617f, 0.560708367f, 0.9324863769f, -0.3612051451f, 0.6394653183f, 0.7688199442f, -0.01623847064f, -0.9998681473f, -0.9955014666f, -0.09474613458f, -0.81453315f, 0.580117012f, + 0.4037327978f, -0.9148769469f, 0.9944263371f, 0.1054336766f, -0.1624711654f, 0.9867132919f, -0.9949487814f, -0.100383875f, -0.6995302564f, 0.7146029809f, 0.5263414922f, -0.85027327f, -0.5395221479f, 0.841971408f, 0.6579370318f, 0.7530729462f, + 0.01426758847f, -0.9998982128f, -0.6734383991f, 0.7392433447f, 0.639412098f, -0.7688642071f, 0.9211571421f, 0.3891908523f, -0.146637214f, -0.9891903394f, -0.782318098f, 0.6228791163f, -0.5039610839f, -0.8637263605f, -0.7743120191f, -0.6328039957f, +}; + +template +const T FastNoiseLite::Lookup::Gradients3D[] = +{ + 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, + 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, + 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, + 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, + 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, + 1, 1, 0, 0, 0,-1, 1, 0, -1, 1, 0, 0, 0,-1,-1, 0 +}; + +template +const T FastNoiseLite::Lookup::RandVecs3D[] = +{ + -0.7292736885f, -0.6618439697f, 0.1735581948f, 0, 0.790292081f, -0.5480887466f, -0.2739291014f, 0, 0.7217578935f, 0.6226212466f, -0.3023380997f, 0, 0.565683137f, -0.8208298145f, -0.0790000257f, 0, 0.760049034f, -0.5555979497f, -0.3370999617f, 0, 0.3713945616f, 0.5011264475f, 0.7816254623f, 0, -0.1277062463f, -0.4254438999f, -0.8959289049f, 0, -0.2881560924f, -0.5815838982f, 0.7607405838f, 0, + 0.5849561111f, -0.662820239f, -0.4674352136f, 0, 0.3307171178f, 0.0391653737f, 0.94291689f, 0, 0.8712121778f, -0.4113374369f, -0.2679381538f, 0, 0.580981015f, 0.7021915846f, 0.4115677815f, 0, 0.503756873f, 0.6330056931f, -0.5878203852f, 0, 0.4493712205f, 0.601390195f, 0.6606022552f, 0, -0.6878403724f, 0.09018890807f, -0.7202371714f, 0, -0.5958956522f, -0.6469350577f, 0.475797649f, 0, + -0.5127052122f, 0.1946921978f, -0.8361987284f, 0, -0.9911507142f, -0.05410276466f, -0.1212153153f, 0, -0.2149721042f, 0.9720882117f, -0.09397607749f, 0, -0.7518650936f, -0.5428057603f, 0.3742469607f, 0, 0.5237068895f, 0.8516377189f, -0.02107817834f, 0, 0.6333504779f, 0.1926167129f, -0.7495104896f, 0, -0.06788241606f, 0.3998305789f, 0.9140719259f, 0, -0.5538628599f, -0.4729896695f, -0.6852128902f, 0, + -0.7261455366f, -0.5911990757f, 0.3509933228f, 0, -0.9229274737f, -0.1782808786f, 0.3412049336f, 0, -0.6968815002f, 0.6511274338f, 0.3006480328f, 0, 0.9608044783f, -0.2098363234f, -0.1811724921f, 0, 0.06817146062f, -0.9743405129f, 0.2145069156f, 0, -0.3577285196f, -0.6697087264f, -0.6507845481f, 0, -0.1868621131f, 0.7648617052f, -0.6164974636f, 0, -0.6541697588f, 0.3967914832f, 0.6439087246f, 0, + 0.6993340405f, -0.6164538506f, 0.3618239211f, 0, -0.1546665739f, 0.6291283928f, 0.7617583057f, 0, -0.6841612949f, -0.2580482182f, -0.6821542638f, 0, 0.5383980957f, 0.4258654885f, 0.7271630328f, 0, -0.5026987823f, -0.7939832935f, -0.3418836993f, 0, 0.3202971715f, 0.2834415347f, 0.9039195862f, 0, 0.8683227101f, -0.0003762656404f, -0.4959995258f, 0, 0.791120031f, -0.08511045745f, 0.6057105799f, 0, + -0.04011016052f, -0.4397248749f, 0.8972364289f, 0, 0.9145119872f, 0.3579346169f, -0.1885487608f, 0, -0.9612039066f, -0.2756484276f, 0.01024666929f, 0, 0.6510361721f, -0.2877799159f, -0.7023778346f, 0, -0.2041786351f, 0.7365237271f, 0.644859585f, 0, -0.7718263711f, 0.3790626912f, 0.5104855816f, 0, -0.3060082741f, -0.7692987727f, 0.5608371729f, 0, 0.454007341f, -0.5024843065f, 0.7357899537f, 0, + 0.4816795475f, 0.6021208291f, -0.6367380315f, 0, 0.6961980369f, -0.3222197429f, 0.641469197f, 0, -0.6532160499f, -0.6781148932f, 0.3368515753f, 0, 0.5089301236f, -0.6154662304f, -0.6018234363f, 0, -0.1635919754f, -0.9133604627f, -0.372840892f, 0, 0.52408019f, -0.8437664109f, 0.1157505864f, 0, 0.5902587356f, 0.4983817807f, -0.6349883666f, 0, 0.5863227872f, 0.494764745f, 0.6414307729f, 0, + 0.6779335087f, 0.2341345225f, 0.6968408593f, 0, 0.7177054546f, -0.6858979348f, 0.120178631f, 0, -0.5328819713f, -0.5205125012f, 0.6671608058f, 0, -0.8654874251f, -0.0700727088f, -0.4960053754f, 0, -0.2861810166f, 0.7952089234f, 0.5345495242f, 0, -0.04849529634f, 0.9810836427f, -0.1874115585f, 0, -0.6358521667f, 0.6058348682f, 0.4781800233f, 0, 0.6254794696f, -0.2861619734f, 0.7258696564f, 0, + -0.2585259868f, 0.5061949264f, -0.8227581726f, 0, 0.02136306781f, 0.5064016808f, -0.8620330371f, 0, 0.200111773f, 0.8599263484f, 0.4695550591f, 0, 0.4743561372f, 0.6014985084f, -0.6427953014f, 0, 0.6622993731f, -0.5202474575f, -0.5391679918f, 0, 0.08084972818f, -0.6532720452f, 0.7527940996f, 0, -0.6893687501f, 0.0592860349f, 0.7219805347f, 0, -0.1121887082f, -0.9673185067f, 0.2273952515f, 0, + 0.7344116094f, 0.5979668656f, -0.3210532909f, 0, 0.5789393465f, -0.2488849713f, 0.7764570201f, 0, 0.6988182827f, 0.3557169806f, -0.6205791146f, 0, -0.8636845529f, -0.2748771249f, -0.4224826141f, 0, -0.4247027957f, -0.4640880967f, 0.777335046f, 0, 0.5257722489f, -0.8427017621f, 0.1158329937f, 0, 0.9343830603f, 0.316302472f, -0.1639543925f, 0, -0.1016836419f, -0.8057303073f, -0.5834887393f, 0, + -0.6529238969f, 0.50602126f, -0.5635892736f, 0, -0.2465286165f, -0.9668205684f, -0.06694497494f, 0, -0.9776897119f, -0.2099250524f, -0.007368825344f, 0, 0.7736893337f, 0.5734244712f, 0.2694238123f, 0, -0.6095087895f, 0.4995678998f, 0.6155736747f, 0, 0.5794535482f, 0.7434546771f, 0.3339292269f, 0, -0.8226211154f, 0.08142581855f, 0.5627293636f, 0, -0.510385483f, 0.4703667658f, 0.7199039967f, 0, + -0.5764971849f, -0.07231656274f, -0.8138926898f, 0, 0.7250628871f, 0.3949971505f, -0.5641463116f, 0, -0.1525424005f, 0.4860840828f, -0.8604958341f, 0, -0.5550976208f, -0.4957820792f, 0.667882296f, 0, -0.1883614327f, 0.9145869398f, 0.357841725f, 0, 0.7625556724f, -0.5414408243f, -0.3540489801f, 0, -0.5870231946f, -0.3226498013f, -0.7424963803f, 0, 0.3051124198f, 0.2262544068f, -0.9250488391f, 0, + 0.6379576059f, 0.577242424f, -0.5097070502f, 0, -0.5966775796f, 0.1454852398f, -0.7891830656f, 0, -0.658330573f, 0.6555487542f, -0.3699414651f, 0, 0.7434892426f, 0.2351084581f, 0.6260573129f, 0, 0.5562114096f, 0.8264360377f, -0.0873632843f, 0, -0.3028940016f, -0.8251527185f, 0.4768419182f, 0, 0.1129343818f, -0.985888439f, -0.1235710781f, 0, 0.5937652891f, -0.5896813806f, 0.5474656618f, 0, + 0.6757964092f, -0.5835758614f, -0.4502648413f, 0, 0.7242302609f, -0.1152719764f, 0.6798550586f, 0, -0.9511914166f, 0.0753623979f, -0.2992580792f, 0, 0.2539470961f, -0.1886339355f, 0.9486454084f, 0, 0.571433621f, -0.1679450851f, -0.8032795685f, 0, -0.06778234979f, 0.3978269256f, 0.9149531629f, 0, 0.6074972649f, 0.733060024f, -0.3058922593f, 0, -0.5435478392f, 0.1675822484f, 0.8224791405f, 0, + -0.5876678086f, -0.3380045064f, -0.7351186982f, 0, -0.7967562402f, 0.04097822706f, -0.6029098428f, 0, -0.1996350917f, 0.8706294745f, 0.4496111079f, 0, -0.02787660336f, -0.9106232682f, -0.4122962022f, 0, -0.7797625996f, -0.6257634692f, 0.01975775581f, 0, -0.5211232846f, 0.7401644346f, -0.4249554471f, 0, 0.8575424857f, 0.4053272873f, -0.3167501783f, 0, 0.1045223322f, 0.8390195772f, -0.5339674439f, 0, + 0.3501822831f, 0.9242524096f, -0.1520850155f, 0, 0.1987849858f, 0.07647613266f, 0.9770547224f, 0, 0.7845996363f, 0.6066256811f, -0.1280964233f, 0, 0.09006737436f, -0.9750989929f, -0.2026569073f, 0, -0.8274343547f, -0.542299559f, 0.1458203587f, 0, -0.3485797732f, -0.415802277f, 0.840000362f, 0, -0.2471778936f, -0.7304819962f, -0.6366310879f, 0, -0.3700154943f, 0.8577948156f, 0.3567584454f, 0, + 0.5913394901f, -0.548311967f, -0.5913303597f, 0, 0.1204873514f, -0.7626472379f, -0.6354935001f, 0, 0.616959265f, 0.03079647928f, 0.7863922953f, 0, 0.1258156836f, -0.6640829889f, -0.7369967419f, 0, -0.6477565124f, -0.1740147258f, -0.7417077429f, 0, 0.6217889313f, -0.7804430448f, -0.06547655076f, 0, 0.6589943422f, -0.6096987708f, 0.4404473475f, 0, -0.2689837504f, -0.6732403169f, -0.6887635427f, 0, + -0.3849775103f, 0.5676542638f, 0.7277093879f, 0, 0.5754444408f, 0.8110471154f, -0.1051963504f, 0, 0.9141593684f, 0.3832947817f, 0.131900567f, 0, -0.107925319f, 0.9245493968f, 0.3654593525f, 0, 0.377977089f, 0.3043148782f, 0.8743716458f, 0, -0.2142885215f, -0.8259286236f, 0.5214617324f, 0, 0.5802544474f, 0.4148098596f, -0.7008834116f, 0, -0.1982660881f, 0.8567161266f, -0.4761596756f, 0, + -0.03381553704f, 0.3773180787f, -0.9254661404f, 0, -0.6867922841f, -0.6656597827f, 0.2919133642f, 0, 0.7731742607f, -0.2875793547f, -0.5652430251f, 0, -0.09655941928f, 0.9193708367f, -0.3813575004f, 0, 0.2715702457f, -0.9577909544f, -0.09426605581f, 0, 0.2451015704f, -0.6917998565f, -0.6792188003f, 0, 0.977700782f, -0.1753855374f, 0.1155036542f, 0, -0.5224739938f, 0.8521606816f, 0.02903615945f, 0, + -0.7734880599f, -0.5261292347f, 0.3534179531f, 0, -0.7134492443f, -0.269547243f, 0.6467878011f, 0, 0.1644037271f, 0.5105846203f, -0.8439637196f, 0, 0.6494635788f, 0.05585611296f, 0.7583384168f, 0, -0.4711970882f, 0.5017280509f, -0.7254255765f, 0, -0.6335764307f, -0.2381686273f, -0.7361091029f, 0, -0.9021533097f, -0.270947803f, -0.3357181763f, 0, -0.3793711033f, 0.872258117f, 0.3086152025f, 0, + -0.6855598966f, -0.3250143309f, 0.6514394162f, 0, 0.2900942212f, -0.7799057743f, -0.5546100667f, 0, -0.2098319339f, 0.85037073f, 0.4825351604f, 0, -0.4592603758f, 0.6598504336f, -0.5947077538f, 0, 0.8715945488f, 0.09616365406f, -0.4807031248f, 0, -0.6776666319f, 0.7118504878f, -0.1844907016f, 0, 0.7044377633f, 0.312427597f, 0.637304036f, 0, -0.7052318886f, -0.2401093292f, -0.6670798253f, 0, + 0.081921007f, -0.7207336136f, -0.6883545647f, 0, -0.6993680906f, -0.5875763221f, -0.4069869034f, 0, -0.1281454481f, 0.6419895885f, 0.7559286424f, 0, -0.6337388239f, -0.6785471501f, -0.3714146849f, 0, 0.5565051903f, -0.2168887573f, -0.8020356851f, 0, -0.5791554484f, 0.7244372011f, -0.3738578718f, 0, 0.1175779076f, -0.7096451073f, 0.6946792478f, 0, -0.6134619607f, 0.1323631078f, 0.7785527795f, 0, + 0.6984635305f, -0.02980516237f, -0.715024719f, 0, 0.8318082963f, -0.3930171956f, 0.3919597455f, 0, 0.1469576422f, 0.05541651717f, -0.9875892167f, 0, 0.708868575f, -0.2690503865f, 0.6520101478f, 0, 0.2726053183f, 0.67369766f, -0.68688995f, 0, -0.6591295371f, 0.3035458599f, -0.6880466294f, 0, 0.4815131379f, -0.7528270071f, 0.4487723203f, 0, 0.9430009463f, 0.1675647412f, -0.2875261255f, 0, + 0.434802957f, 0.7695304522f, -0.4677277752f, 0, 0.3931996188f, 0.594473625f, 0.7014236729f, 0, 0.7254336655f, -0.603925654f, 0.3301814672f, 0, 0.7590235227f, -0.6506083235f, 0.02433313207f, 0, -0.8552768592f, -0.3430042733f, 0.3883935666f, 0, -0.6139746835f, 0.6981725247f, 0.3682257648f, 0, -0.7465905486f, -0.5752009504f, 0.3342849376f, 0, 0.5730065677f, 0.810555537f, -0.1210916791f, 0, + -0.9225877367f, -0.3475211012f, -0.167514036f, 0, -0.7105816789f, -0.4719692027f, -0.5218416899f, 0, -0.08564609717f, 0.3583001386f, 0.929669703f, 0, -0.8279697606f, -0.2043157126f, 0.5222271202f, 0, 0.427944023f, 0.278165994f, 0.8599346446f, 0, 0.5399079671f, -0.7857120652f, -0.3019204161f, 0, 0.5678404253f, -0.5495413974f, -0.6128307303f, 0, -0.9896071041f, 0.1365639107f, -0.04503418428f, 0, + -0.6154342638f, -0.6440875597f, 0.4543037336f, 0, 0.1074204368f, -0.7946340692f, 0.5975094525f, 0, -0.3595449969f, -0.8885529948f, 0.28495784f, 0, -0.2180405296f, 0.1529888965f, 0.9638738118f, 0, -0.7277432317f, -0.6164050508f, -0.3007234646f, 0, 0.7249729114f, -0.00669719484f, 0.6887448187f, 0, -0.5553659455f, -0.5336586252f, 0.6377908264f, 0, 0.5137558015f, 0.7976208196f, -0.3160000073f, 0, + -0.3794024848f, 0.9245608561f, -0.03522751494f, 0, 0.8229248658f, 0.2745365933f, -0.4974176556f, 0, -0.5404114394f, 0.6091141441f, 0.5804613989f, 0, 0.8036581901f, -0.2703029469f, 0.5301601931f, 0, 0.6044318879f, 0.6832968393f, 0.4095943388f, 0, 0.06389988817f, 0.9658208605f, -0.2512108074f, 0, 0.1087113286f, 0.7402471173f, -0.6634877936f, 0, -0.713427712f, -0.6926784018f, 0.1059128479f, 0, + 0.6458897819f, -0.5724548511f, -0.5050958653f, 0, -0.6553931414f, 0.7381471625f, 0.159995615f, 0, 0.3910961323f, 0.9188871375f, -0.05186755998f, 0, -0.4879022471f, -0.5904376907f, 0.6429111375f, 0, 0.6014790094f, 0.7707441366f, -0.2101820095f, 0, -0.5677173047f, 0.7511360995f, 0.3368851762f, 0, 0.7858573506f, 0.226674665f, 0.5753666838f, 0, -0.4520345543f, -0.604222686f, -0.6561857263f, 0, + 0.002272116345f, 0.4132844051f, -0.9105991643f, 0, -0.5815751419f, -0.5162925989f, 0.6286591339f, 0, -0.03703704785f, 0.8273785755f, 0.5604221175f, 0, -0.5119692504f, 0.7953543429f, -0.3244980058f, 0, -0.2682417366f, -0.9572290247f, -0.1084387619f, 0, -0.2322482736f, -0.9679131102f, -0.09594243324f, 0, 0.3554328906f, -0.8881505545f, 0.2913006227f, 0, 0.7346520519f, -0.4371373164f, 0.5188422971f, 0, + 0.9985120116f, 0.04659011161f, -0.02833944577f, 0, -0.3727687496f, -0.9082481361f, 0.1900757285f, 0, 0.91737377f, -0.3483642108f, 0.1925298489f, 0, 0.2714911074f, 0.4147529736f, -0.8684886582f, 0, 0.5131763485f, -0.7116334161f, 0.4798207128f, 0, -0.8737353606f, 0.18886992f, -0.4482350644f, 0, 0.8460043821f, -0.3725217914f, 0.3814499973f, 0, 0.8978727456f, -0.1780209141f, -0.4026575304f, 0, + 0.2178065647f, -0.9698322841f, -0.1094789531f, 0, -0.1518031304f, -0.7788918132f, -0.6085091231f, 0, -0.2600384876f, -0.4755398075f, -0.8403819825f, 0, 0.572313509f, -0.7474340931f, -0.3373418503f, 0, -0.7174141009f, 0.1699017182f, -0.6756111411f, 0, -0.684180784f, 0.02145707593f, -0.7289967412f, 0, -0.2007447902f, 0.06555605789f, -0.9774476623f, 0, -0.1148803697f, -0.8044887315f, 0.5827524187f, 0, + -0.7870349638f, 0.03447489231f, 0.6159443543f, 0, -0.2015596421f, 0.6859872284f, 0.6991389226f, 0, -0.08581082512f, -0.10920836f, -0.9903080513f, 0, 0.5532693395f, 0.7325250401f, -0.396610771f, 0, -0.1842489331f, -0.9777375055f, -0.1004076743f, 0, 0.0775473789f, -0.9111505856f, 0.4047110257f, 0, 0.1399838409f, 0.7601631212f, -0.6344734459f, 0, 0.4484419361f, -0.845289248f, 0.2904925424f, 0 +}; + +#endif diff --git a/source/lua/lua_api.cpp b/source/lua/lua_api.cpp new file mode 100644 index 000000000..e96dc81ff --- /dev/null +++ b/source/lua/lua_api.cpp @@ -0,0 +1,70 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api.h" + +namespace LuaAPI { + +void registerAll(sol::state& lua) { + // Register base types first (order matters for dependencies) + + // Position is used by Tile and Map + registerPosition(lua); + + // Item is used by Tile + registerItem(lua); + + // Color is a standalone type + registerColor(lua); + + // Creature/Spawn are used by Tile (must come before Tile) + registerCreature(lua); + + // Tile is used by Map and Selection + registerTile(lua); + + // Map uses Tile and Position + registerMap(lua); + + // Selection uses Tile + registerSelection(lua); + + // Must come after Map and Selection so app.map/app.selection work + registerApp(lua); + + // Register Dialog class + registerDialog(lua); + + registerBrush(lua); + + // Register Image class + registerImage(lua); + + // Register JSON helper + registerJson(lua); + + // Register HTTP client + registerHttp(lua); + + // Register procedural generation APIs + registerNoise(lua); + registerAlgo(lua); + registerGeo(lua); +} + +} diff --git a/source/lua/lua_api.h b/source/lua/lua_api.h new file mode 100644 index 000000000..1a0c6f21d --- /dev/null +++ b/source/lua/lua_api.h @@ -0,0 +1,58 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_H +#define RME_LUA_API_H + +#define SOL_ALL_SAFETIES_ON 1 +#include + +// Forward declarations +class Tile; + +// Forward declarations for API modules +namespace LuaAPI { + // Register all APIs with the Lua state + void registerAll(sol::state& lua); + + // Individual API registration functions + void registerApp(sol::state& lua); + void registerDialog(sol::state& lua); + + void registerPosition(sol::state& lua); + void registerItem(sol::state& lua); + void registerTile(sol::state& lua); + void registerMap(sol::state& lua); + void registerSelection(sol::state& lua); + + // Called by tile modification functions to track changes + void markTileForUndo(Tile* tile); + + void registerColor(sol::state& lua); + void registerCreature(sol::state& lua); + void registerBrush(sol::state& lua); + void registerImage(sol::state& lua); + void registerJson(sol::state& lua); + void registerHttp(sol::state& lua); + + // Procedural generation APIs + void registerNoise(sol::state& lua); + void registerAlgo(sol::state& lua); + void registerGeo(sol::state& lua); +} + +#endif // RME_LUA_API_H diff --git a/source/lua/lua_api_algo.cpp b/source/lua/lua_api_algo.cpp new file mode 100644 index 000000000..901b900fe --- /dev/null +++ b/source/lua/lua_api_algo.cpp @@ -0,0 +1,849 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_algo.h" + +#include +#include +#include +#include +#include +#include + +namespace LuaAPI { + +// Helper: convert Lua table to 2D grid +static std::vector> tableToGrid(const sol::table& tbl, int width, int height) { + std::vector> grid(height, std::vector(width, 0)); + + for (int y = 1; y <= height; ++y) { + if (tbl[y].valid() && tbl[y].get_type() == sol::type::table) { + sol::table row = tbl[y]; + for (int x = 1; x <= width; ++x) { + if (row[x].valid()) { + grid[y - 1][x - 1] = row[x].get(); + } + } + } + } + return grid; +} + +// Helper: convert 2D grid to Lua table +static sol::table gridToTable(const std::vector>& grid, sol::state_view& lua) { + sol::table result = lua.create_table(); + for (size_t y = 0; y < grid.size(); ++y) { + sol::table row = lua.create_table(); + for (size_t x = 0; x < grid[y].size(); ++x) { + row[x + 1] = grid[y][x]; + } + result[y + 1] = row; + } + return result; +} + +// Helper: convert Lua table to 2D float grid +static std::vector> tableToFloatGrid(const sol::table& tbl, int width, int height) { + std::vector> grid(height, std::vector(width, 0.0f)); + + for (int y = 1; y <= height; ++y) { + if (tbl[y].valid() && tbl[y].get_type() == sol::type::table) { + sol::table row = tbl[y]; + for (int x = 1; x <= width; ++x) { + if (row[x].valid()) { + grid[y - 1][x - 1] = row[x].get(); + } + } + } + } + return grid; +} + +// Helper: convert 2D float grid to Lua table +static sol::table floatGridToTable(const std::vector>& grid, sol::state_view& lua) { + sol::table result = lua.create_table(); + for (size_t y = 0; y < grid.size(); ++y) { + sol::table row = lua.create_table(); + for (size_t x = 0; x < grid[y].size(); ++x) { + row[x + 1] = grid[y][x]; + } + result[y + 1] = row; + } + return result; +} + +void registerAlgo(sol::state& lua) { + sol::table algoTable = lua.create_table(); + + // ======================================== + // CELLULAR AUTOMATA + // ======================================== + + // algo.cellularAutomata(grid, options) -> grid + // Run cellular automata simulation (useful for caves, organic shapes) + // grid: 2D table where 1 = wall, 0 = floor + // options: { iterations, birthLimit, deathLimit, width, height } + algoTable.set_function("cellularAutomata", [](sol::table inputGrid, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + int iterations = 4; + int birthLimit = 4; // Become wall if neighbors >= birthLimit + int deathLimit = 3; // Stay wall if neighbors >= deathLimit + int width = 0; + int height = 0; + + // Get dimensions from grid + if (inputGrid[1].valid()) { + height = static_cast(inputGrid.size()); + if (inputGrid[1].get_type() == sol::type::table) { + sol::table firstRow = inputGrid[1]; + width = static_cast(firstRow.size()); + } + } + + if (options) { + sol::table opts = *options; + iterations = opts.get_or("iterations", 4); + birthLimit = opts.get_or("birthLimit", 4); + deathLimit = opts.get_or("deathLimit", 3); + width = opts.get_or("width", width); + height = opts.get_or("height", height); + } + + if (width <= 0 || height <= 0) { + return inputGrid; // Return unchanged if invalid dimensions + } + + auto grid = tableToGrid(inputGrid, width, height); + + // Run iterations + for (int iter = 0; iter < iterations; ++iter) { + std::vector> newGrid = grid; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + // Count neighbors (8-directional) + int neighbors = 0; + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + if (dx == 0 && dy == 0) continue; + int nx = x + dx; + int ny = y + dy; + // Treat edges as walls + if (nx < 0 || nx >= width || ny < 0 || ny >= height) { + neighbors++; + } else if (grid[ny][nx] == 1) { + neighbors++; + } + } + } + + // Apply rules + if (grid[y][x] == 1) { + // Wall survives if enough neighbors + newGrid[y][x] = (neighbors >= deathLimit) ? 1 : 0; + } else { + // Floor becomes wall if too many neighbors + newGrid[y][x] = (neighbors >= birthLimit) ? 1 : 0; + } + } + } + + grid = newGrid; + } + + return gridToTable(grid, lua); + }); + + // algo.generateCave(width, height, options) -> grid + // Generate a cave map using cellular automata + // options: { fillProbability, iterations, birthLimit, deathLimit, seed } + algoTable.set_function("generateCave", [](int width, int height, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + float fillProbability = 0.45f; + int iterations = 4; + int birthLimit = 4; + int deathLimit = 3; + int seed = static_cast(time(nullptr)); + + if (options) { + sol::table opts = *options; + fillProbability = opts.get_or("fillProbability", 0.45f); + iterations = opts.get_or("iterations", 4); + birthLimit = opts.get_or("birthLimit", 4); + deathLimit = opts.get_or("deathLimit", 3); + seed = opts.get_or("seed", seed); + } + + std::mt19937 rng(seed); + std::uniform_real_distribution dist(0.0f, 1.0f); + + // Initialize random grid + std::vector> grid(height, std::vector(width, 0)); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + // Edges are always walls + if (x == 0 || x == width - 1 || y == 0 || y == height - 1) { + grid[y][x] = 1; + } else { + grid[y][x] = (dist(rng) < fillProbability) ? 1 : 0; + } + } + } + + // Run cellular automata + for (int iter = 0; iter < iterations; ++iter) { + std::vector> newGrid = grid; + + for (int y = 1; y < height - 1; ++y) { + for (int x = 1; x < width - 1; ++x) { + int neighbors = 0; + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + if (dx == 0 && dy == 0) continue; + if (grid[y + dy][x + dx] == 1) neighbors++; + } + } + + if (grid[y][x] == 1) { + newGrid[y][x] = (neighbors >= deathLimit) ? 1 : 0; + } else { + newGrid[y][x] = (neighbors >= birthLimit) ? 1 : 0; + } + } + } + + grid = newGrid; + } + + return gridToTable(grid, lua); + }); + + // ======================================== + // EROSION ALGORITHMS + // ======================================== + + // algo.erode(heightmap, options) -> heightmap + // Hydraulic erosion simulation for terrain + // heightmap: 2D table of float values [0, 1] + // options: { iterations, erosionRadius, inertia, sedimentCapacity, minSlope, erosionSpeed, depositSpeed, evaporateSpeed, gravity } + algoTable.set_function("erode", [](sol::table inputHeightmap, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + // Get dimensions + int height = 0; + int width = 0; + if (inputHeightmap[1].valid()) { + height = static_cast(inputHeightmap.size()); + if (inputHeightmap[1].get_type() == sol::type::table) { + sol::table firstRow = inputHeightmap[1]; + width = static_cast(firstRow.size()); + } + } + + if (width <= 2 || height <= 2) { + return inputHeightmap; + } + + // Erosion parameters + int iterations = 50000; + int erosionRadius = 3; + float inertia = 0.05f; + float sedimentCapacity = 4.0f; + float minSlope = 0.01f; + float erosionSpeed = 0.3f; + float depositSpeed = 0.3f; + float evaporateSpeed = 0.01f; + float gravity = 4.0f; + int seed = static_cast(time(nullptr)); + int maxDropletLifetime = 30; + + if (options) { + sol::table opts = *options; + iterations = opts.get_or("iterations", 50000); + erosionRadius = opts.get_or("erosionRadius", 3); + inertia = opts.get_or("inertia", 0.05f); + sedimentCapacity = opts.get_or("sedimentCapacity", 4.0f); + minSlope = opts.get_or("minSlope", 0.01f); + erosionSpeed = opts.get_or("erosionSpeed", 0.3f); + depositSpeed = opts.get_or("depositSpeed", 0.3f); + evaporateSpeed = opts.get_or("evaporateSpeed", 0.01f); + gravity = opts.get_or("gravity", 4.0f); + seed = opts.get_or("seed", seed); + maxDropletLifetime = opts.get_or("maxDropletLifetime", 30); + } + + auto heightmap = tableToFloatGrid(inputHeightmap, width, height); + + std::mt19937 rng(seed); + std::uniform_real_distribution dist(0.0f, 1.0f); + + // Precompute erosion brush weights + std::vector>> brushIndices(erosionRadius * 2 + 1); + std::vector> brushWeights(erosionRadius * 2 + 1); + + for (int radius = 0; radius <= erosionRadius; ++radius) { + for (int y = -radius; y <= radius; ++y) { + for (int x = -radius; x <= radius; ++x) { + float sqrDst = (float)(x * x + y * y); + if (sqrDst <= radius * radius) { + brushIndices[radius].push_back({x, y}); + float weight = 1.0f - std::sqrt(sqrDst) / (float)radius; + brushWeights[radius].push_back(weight); + } + } + } + } + + // Helper to get interpolated height + auto getHeight = [&](float x, float y) -> float { + int xi = (int)x; + int yi = (int)y; + float fx = x - xi; + float fy = y - yi; + + xi = std::max(0, std::min(xi, width - 2)); + yi = std::max(0, std::min(yi, height - 2)); + + float h00 = heightmap[yi][xi]; + float h10 = heightmap[yi][xi + 1]; + float h01 = heightmap[yi + 1][xi]; + float h11 = heightmap[yi + 1][xi + 1]; + + return h00 * (1 - fx) * (1 - fy) + + h10 * fx * (1 - fy) + + h01 * (1 - fx) * fy + + h11 * fx * fy; + }; + + // Helper to get gradient + auto getGradient = [&](float x, float y) -> std::pair { + int xi = (int)x; + int yi = (int)y; + + xi = std::max(1, std::min(xi, width - 2)); + yi = std::max(1, std::min(yi, height - 2)); + + float gx = (heightmap[yi][xi + 1] - heightmap[yi][xi - 1]) * 0.5f; + float gy = (heightmap[yi + 1][xi] - heightmap[yi - 1][xi]) * 0.5f; + + return {gx, gy}; + }; + + // Simulate droplets + for (int i = 0; i < iterations; ++i) { + // Random starting position + float posX = dist(rng) * (width - 2) + 1; + float posY = dist(rng) * (height - 2) + 1; + float dirX = 0, dirY = 0; + float speed = 1; + float water = 1; + float sediment = 0; + + for (int lifetime = 0; lifetime < maxDropletLifetime; ++lifetime) { + int nodeX = (int)posX; + int nodeY = (int)posY; + + // Get gradient + auto [gx, gy] = getGradient(posX, posY); + + // Update direction with inertia + dirX = dirX * inertia - gx * (1 - inertia); + dirY = dirY * inertia - gy * (1 - inertia); + + // Normalize direction + float len = std::sqrt(dirX * dirX + dirY * dirY); + if (len > 0) { + dirX /= len; + dirY /= len; + } + + // New position + float newPosX = posX + dirX; + float newPosY = posY + dirY; + + // Stop if out of bounds + if (newPosX < 1 || newPosX >= width - 1 || newPosY < 1 || newPosY >= height - 1) { + break; + } + + // Height difference + float newHeight = getHeight(newPosX, newPosY); + float oldHeight = getHeight(posX, posY); + float deltaHeight = newHeight - oldHeight; + + // Calculate sediment capacity + float capacity = std::max(-deltaHeight, minSlope) * speed * water * sedimentCapacity; + + // Deposit or erode + if (sediment > capacity || deltaHeight > 0) { + // Deposit sediment + float amountToDeposit = (deltaHeight > 0) ? std::min(deltaHeight, sediment) : (sediment - capacity) * depositSpeed; + sediment -= amountToDeposit; + + // Deposit at current position + int hx = std::min(std::max(nodeX, 0), width - 1); + int hy = std::min(std::max(nodeY, 0), height - 1); + heightmap[hy][hx] += amountToDeposit; + } else { + // Erode terrain + float amountToErode = std::min((capacity - sediment) * erosionSpeed, -deltaHeight); + + // Erode in radius + for (size_t j = 0; j < brushIndices[erosionRadius].size(); ++j) { + int ex = nodeX + brushIndices[erosionRadius][j].first; + int ey = nodeY + brushIndices[erosionRadius][j].second; + + if (ex >= 0 && ex < width && ey >= 0 && ey < height) { + float weightedErode = amountToErode * brushWeights[erosionRadius][j]; + heightmap[ey][ex] -= weightedErode; + sediment += weightedErode; + } + } + } + + // Update position and speed + posX = newPosX; + posY = newPosY; + speed = std::sqrt(speed * speed + deltaHeight * gravity); + water *= (1 - evaporateSpeed); + } + } + + return floatGridToTable(heightmap, lua); + }); + + // algo.thermalErode(heightmap, options) -> heightmap + // Thermal erosion (talus/slope erosion) + algoTable.set_function("thermalErode", [](sol::table inputHeightmap, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + int height = 0; + int width = 0; + if (inputHeightmap[1].valid()) { + height = static_cast(inputHeightmap.size()); + if (inputHeightmap[1].get_type() == sol::type::table) { + sol::table firstRow = inputHeightmap[1]; + width = static_cast(firstRow.size()); + } + } + + if (width <= 2 || height <= 2) return inputHeightmap; + + int iterations = 50; + float talusAngle = 0.5f; // Maximum slope before erosion + float erosionAmount = 0.5f; + + if (options) { + sol::table opts = *options; + iterations = opts.get_or("iterations", 50); + talusAngle = opts.get_or("talusAngle", 0.5f); + erosionAmount = opts.get_or("erosionAmount", 0.5f); + } + + auto heightmap = tableToFloatGrid(inputHeightmap, width, height); + + // 4-directional neighbors + const int dx[] = {0, 1, 0, -1}; + const int dy[] = {-1, 0, 1, 0}; + + for (int iter = 0; iter < iterations; ++iter) { + auto newHeightmap = heightmap; + + for (int y = 1; y < height - 1; ++y) { + for (int x = 1; x < width - 1; ++x) { + float currentHeight = heightmap[y][x]; + + // Find maximum difference + float maxDiff = 0; + int maxIdx = -1; + + for (int i = 0; i < 4; ++i) { + int nx = x + dx[i]; + int ny = y + dy[i]; + float diff = currentHeight - heightmap[ny][nx]; + if (diff > maxDiff) { + maxDiff = diff; + maxIdx = i; + } + } + + // Erode if slope exceeds talus angle + if (maxDiff > talusAngle && maxIdx >= 0) { + float transfer = (maxDiff - talusAngle) * erosionAmount * 0.5f; + newHeightmap[y][x] -= transfer; + newHeightmap[y + dy[maxIdx]][x + dx[maxIdx]] += transfer; + } + } + } + + heightmap = newHeightmap; + } + + return floatGridToTable(heightmap, lua); + }); + + // ======================================== + // SMOOTHING ALGORITHMS + // ======================================== + + // algo.smooth(grid, options) -> grid + // Gaussian-like smoothing for grids + algoTable.set_function("smooth", [](sol::table inputGrid, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + int height = 0; + int width = 0; + if (inputGrid[1].valid()) { + height = static_cast(inputGrid.size()); + if (inputGrid[1].get_type() == sol::type::table) { + sol::table firstRow = inputGrid[1]; + width = static_cast(firstRow.size()); + } + } + + if (width <= 2 || height <= 2) return inputGrid; + + int iterations = 1; + int kernelSize = 3; + + if (options) { + sol::table opts = *options; + iterations = opts.get_or("iterations", 1); + kernelSize = opts.get_or("kernelSize", 3); + } + + auto grid = tableToFloatGrid(inputGrid, width, height); + + int radius = kernelSize / 2; + + for (int iter = 0; iter < iterations; ++iter) { + auto newGrid = grid; + + for (int y = radius; y < height - radius; ++y) { + for (int x = radius; x < width - radius; ++x) { + float sum = 0; + int count = 0; + + for (int dy = -radius; dy <= radius; ++dy) { + for (int dx = -radius; dx <= radius; ++dx) { + sum += grid[y + dy][x + dx]; + count++; + } + } + + newGrid[y][x] = sum / count; + } + } + + grid = newGrid; + } + + return floatGridToTable(grid, lua); + }); + + // ======================================== + // VORONOI DIAGRAM + // ======================================== + + // algo.voronoi(width, height, points) -> grid of region indices + // Generate Voronoi diagram from seed points + algoTable.set_function("voronoi", [](int width, int height, sol::table points, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + // Parse points + std::vector> seedPoints; + for (auto& kv : points) { + if (kv.second.get_type() == sol::type::table) { + sol::table pt = kv.second; + int x = pt.get_or("x", pt.get_or(1, 0)); + int y = pt.get_or("y", pt.get_or(2, 0)); + seedPoints.push_back({x, y}); + } + } + + if (seedPoints.empty()) { + return lua.create_table(); + } + + std::vector> grid(height, std::vector(width, 0)); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + float minDist = std::numeric_limits::max(); + int closestRegion = 0; + + for (size_t i = 0; i < seedPoints.size(); ++i) { + float dx = (float)(x - seedPoints[i].first); + float dy = (float)(y - seedPoints[i].second); + float dist = dx * dx + dy * dy; // Squared distance for speed + + if (dist < minDist) { + minDist = dist; + closestRegion = static_cast(i + 1); // 1-indexed for Lua + } + } + + grid[y][x] = closestRegion; + } + } + + return gridToTable(grid, lua); + }); + + // algo.generateRandomPoints(width, height, count, seed) -> table of points + // Generate random points for Voronoi, etc. + algoTable.set_function("generateRandomPoints", [](int width, int height, int count, sol::optional seed, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + int sd = seed.value_or(static_cast(time(nullptr))); + std::mt19937 rng(sd); + std::uniform_int_distribution distX(0, width - 1); + std::uniform_int_distribution distY(0, height - 1); + + sol::table result = lua.create_table(); + + for (int i = 0; i < count; ++i) { + sol::table point = lua.create_table(); + point["x"] = distX(rng); + point["y"] = distY(rng); + result[i + 1] = point; + } + + return result; + }); + + // ======================================== + // MAZE GENERATION + // ======================================== + + // algo.generateMaze(width, height, options) -> grid + // Generate a maze using recursive backtracking + algoTable.set_function("generateMaze", [](int width, int height, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + int seed = static_cast(time(nullptr)); + + if (options) { + sol::table opts = *options; + seed = opts.get_or("seed", seed); + } + + std::mt19937 rng(seed); + + // Make dimensions odd for proper maze + if (width % 2 == 0) width++; + if (height % 2 == 0) height++; + + // Initialize grid with walls + std::vector> grid(height, std::vector(width, 1)); + + // Recursive backtracking + std::function carve = [&](int x, int y) { + grid[y][x] = 0; + + // Shuffle directions + std::vector dirs = {0, 1, 2, 3}; // N, E, S, W + std::shuffle(dirs.begin(), dirs.end(), rng); + + const int dx[] = {0, 1, 0, -1}; + const int dy[] = {-1, 0, 1, 0}; + + for (int dir : dirs) { + int nx = x + dx[dir] * 2; + int ny = y + dy[dir] * 2; + + if (nx > 0 && nx < width - 1 && ny > 0 && ny < height - 1 && grid[ny][nx] == 1) { + // Carve through wall + grid[y + dy[dir]][x + dx[dir]] = 0; + carve(nx, ny); + } + } + }; + + // Start from (1, 1) + carve(1, 1); + + return gridToTable(grid, lua); + }); + + // ======================================== + // BSP (Binary Space Partitioning) - Dungeon Generation + // ======================================== + + // algo.generateDungeon(width, height, options) -> { grid, rooms } + // Generate a dungeon using BSP + algoTable.set_function("generateDungeon", [](int width, int height, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + int minRoomSize = 5; + int maxRoomSize = 15; + int seed = static_cast(time(nullptr)); + int maxDepth = 4; + + if (options) { + sol::table opts = *options; + minRoomSize = opts.get_or("minRoomSize", 5); + maxRoomSize = opts.get_or("maxRoomSize", 15); + seed = opts.get_or("seed", seed); + maxDepth = opts.get_or("maxDepth", 4); + } + + std::mt19937 rng(seed); + + // Initialize grid with walls + std::vector> grid(height, std::vector(width, 1)); + + // Store rooms + std::vector> rooms; // x, y, w, h + + // BSP Node + struct BSPNode { + int x, y, w, h; + BSPNode* left = nullptr; + BSPNode* right = nullptr; + int roomX, roomY, roomW, roomH; + bool hasRoom = false; + }; + + std::function split; + split = [&](int x, int y, int w, int h, int depth) -> BSPNode* { + BSPNode* node = new BSPNode{x, y, w, h}; + + if (depth >= maxDepth || w < minRoomSize * 2 || h < minRoomSize * 2) { + // Check if space is sufficient for a room + if (w < minRoomSize + 2 || h < minRoomSize + 2) { + return node; + } + + // Create room + std::uniform_int_distribution roomW(minRoomSize, std::min(maxRoomSize, w - 2)); + std::uniform_int_distribution roomH(minRoomSize, std::min(maxRoomSize, h - 2)); + + int rw = roomW(rng); + int rh = roomH(rng); + + std::uniform_int_distribution roomX(x + 1, x + w - rw - 1); + std::uniform_int_distribution roomY(y + 1, y + h - rh - 1); + + node->roomX = roomX(rng); + node->roomY = roomY(rng); + node->roomW = rw; + node->roomH = rh; + node->hasRoom = true; + + rooms.push_back({node->roomX, node->roomY, rw, rh}); + + return node; + } + + // Split + std::uniform_real_distribution splitDist(0.3f, 0.7f); + float splitRatio = splitDist(rng); + + bool splitHorizontal = (w < h) || (w == h && rng() % 2 == 0); + + if (splitHorizontal) { + int splitY = y + static_cast(h * splitRatio); + node->left = split(x, y, w, splitY - y, depth + 1); + node->right = split(x, splitY, w, y + h - splitY, depth + 1); + } else { + int splitX = x + static_cast(w * splitRatio); + node->left = split(x, y, splitX - x, h, depth + 1); + node->right = split(splitX, y, x + w - splitX, h, depth + 1); + } + + return node; + }; + + BSPNode* root = split(0, 0, width, height, 0); + + // Carve rooms + for (const auto& room : rooms) { + int rx = std::get<0>(room); + int ry = std::get<1>(room); + int rw = std::get<2>(room); + int rh = std::get<3>(room); + + for (int py = ry; py < ry + rh && py < height; ++py) { + for (int px = rx; px < rx + rw && px < width; ++px) { + grid[py][px] = 0; + } + } + } + + // Connect rooms with corridors + for (size_t i = 1; i < rooms.size(); ++i) { + int x1 = std::get<0>(rooms[i - 1]) + std::get<2>(rooms[i - 1]) / 2; + int y1 = std::get<1>(rooms[i - 1]) + std::get<3>(rooms[i - 1]) / 2; + int x2 = std::get<0>(rooms[i]) + std::get<2>(rooms[i]) / 2; + int y2 = std::get<1>(rooms[i]) + std::get<3>(rooms[i]) / 2; + + // L-shaped corridor + if (rng() % 2 == 0) { + // Horizontal first + for (int x = std::min(x1, x2); x <= std::max(x1, x2); ++x) { + if (y1 >= 0 && y1 < height && x >= 0 && x < width) grid[y1][x] = 0; + } + for (int y = std::min(y1, y2); y <= std::max(y1, y2); ++y) { + if (y >= 0 && y < height && x2 >= 0 && x2 < width) grid[y][x2] = 0; + } + } else { + // Vertical first + for (int y = std::min(y1, y2); y <= std::max(y1, y2); ++y) { + if (y >= 0 && y < height && x1 >= 0 && x1 < width) grid[y][x1] = 0; + } + for (int x = std::min(x1, x2); x <= std::max(x1, x2); ++x) { + if (y2 >= 0 && y2 < height && x >= 0 && x < width) grid[y2][x] = 0; + } + } + } + + // Cleanup BSP + std::function deleteBSP = [&](BSPNode* node) { + if (node) { + deleteBSP(node->left); + deleteBSP(node->right); + delete node; + } + }; + deleteBSP(root); + + // Return result + sol::table result = lua.create_table(); + result["grid"] = gridToTable(grid, lua); + + sol::table roomsTable = lua.create_table(); + for (size_t i = 0; i < rooms.size(); ++i) { + sol::table room = lua.create_table(); + room["x"] = std::get<0>(rooms[i]); + room["y"] = std::get<1>(rooms[i]); + room["width"] = std::get<2>(rooms[i]); + room["height"] = std::get<3>(rooms[i]); + roomsTable[i + 1] = room; + } + result["rooms"] = roomsTable; + + return result; + }); + + lua["algo"] = algoTable; +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_algo.h b/source/lua/lua_api_algo.h new file mode 100644 index 000000000..464899097 --- /dev/null +++ b/source/lua/lua_api_algo.h @@ -0,0 +1,28 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_ALGO_H +#define RME_LUA_API_ALGO_H + +#include "lua_api.h" + +namespace LuaAPI { + // Register algorithm functions (cellular automata, erosion, etc.) + void registerAlgo(sol::state& lua); +} + +#endif // RME_LUA_API_ALGO_H diff --git a/source/lua/lua_api_app.cpp b/source/lua/lua_api_app.cpp new file mode 100644 index 000000000..ce62a9e93 --- /dev/null +++ b/source/lua/lua_api_app.cpp @@ -0,0 +1,784 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_app.h" +#include "lua_script_manager.h" +#include "../gui.h" +#include "../editor.h" +#include "../map.h" +#include "../brush.h" +#include "../action.h" +#include "../tile.h" +#include "../selection.h" +#include "../items.h" +#include "../raw_brush.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace LuaAPI { + +// ============================================================================ +// Transaction System for Undo/Redo Support +// ============================================================================ + +// Transaction implementation +#include "../selection.h" +#include "../house.h" + +// Helper to sync map metadata when swapping tiles +static void updateTileMetadata(Editor* editor, Tile* tile, bool adding) { + if (!editor || !tile) return; + Map* map = editor->getMap(); + if (!map) return; + + if (adding) { + if (tile->spawn) map->addSpawn(tile); + if (tile->getHouseID()) { + House* h = map->houses.getHouse(tile->getHouseID()); + if (h) h->addTile(tile); + } + } else { + if (tile->spawn) map->removeSpawn(tile); + if (tile->getHouseID()) { + House* h = map->houses.getHouse(tile->getHouseID()); + if (h) h->removeTile(tile); + } + // Also clean up selection if removing + if (tile->isSelected()) { + // We use internal session to avoid creating undo actions for this cleanup + editor->selection.start(Selection::INTERNAL); + editor->selection.removeInternal(tile); + editor->selection.finish(Selection::INTERNAL); + } + } +} + +class LuaTransaction { + bool active; + Editor* editor; + BatchAction* batch; + Action* action; + std::unordered_map originalTiles; + + uint64_t positionKey(const Position& pos) const { + return (static_cast(pos.x) << 32) | + (static_cast(pos.y) << 16) | + static_cast(pos.z); + } + +public: + static LuaTransaction& getInstance() { + static LuaTransaction instance; + return instance; + } + + LuaTransaction() : active(false), editor(nullptr), batch(nullptr), action(nullptr) {} + + void begin(Editor* ed) { + if (active) { + throw sol::error("Transaction already in progress"); + } + + editor = ed; + if (!editor || !editor->actionQueue) { + throw sol::error("No editor or action queue available"); + } + + active = true; + batch = editor->actionQueue->createBatch(ACTION_LUA_SCRIPT); + action = editor->actionQueue->createAction(ACTION_LUA_SCRIPT); + originalTiles.clear(); + } + + void commit() { + if (!active) return; + + // Process each modified tile + for (auto& pair : originalTiles) { + Tile* originalTile = pair.second; + Position pos = originalTile->getPosition(); + + // Get the current (modified) tile from the map + Tile* modifiedTile = editor->getMap()->getTile(pos); + if (modifiedTile) { + // Create a deep copy of the modified tile - this is what we want as the "new" state + Tile* modifiedCopy = modifiedTile->deepCopy(*editor->getMap()); + + // Swap the original back into the map + Tile* swappedOut = editor->getMap()->swapTile(pos, originalTile); + + // swappedOut should be the modifiedTile. We need to clean it up. + // Remove it from Map metadata (spawns, houses) and selection + updateTileMetadata(editor, swappedOut, false); + + // Add originalTile back to Map metadata + updateTileMetadata(editor, originalTile, true); + + delete swappedOut; + + // Create Change with the modified copy + // When actions commit, they will swap modifiedCopy in and originalTile out. + // The Action system handles metadata updates during its commit/undo. + Change* change = new Change(modifiedCopy); + action->addChange(change); + } else { + // No real change or tile was removed, cleanup + delete originalTile; + } + } + + // Clear - ownership has been transferred + originalTiles.clear(); + + if (action->size() > 0) { + batch->addAndCommitAction(action); + editor->addBatch(batch); + editor->getMap()->doChange(); + g_gui.RefreshView(); // Force redraw immediately + } else { + // No changes, clean up + delete action; + delete batch; + } + + cleanup(); + } + + void rollback() { + if (!active) return; + + // Restore original tiles (discard any changes made) + for (auto& pair : originalTiles) { + Tile* originalTile = pair.second; + if (originalTile) { + Position pos = originalTile->getPosition(); + Tile* modifiedTile = editor->getMap()->swapTile(pos, originalTile); + + // Clean up modified tile + updateTileMetadata(editor, modifiedTile, false); + + // Restore original tile metadata + updateTileMetadata(editor, originalTile, true); + + delete modifiedTile; // Discard the modified version + } + } + originalTiles.clear(); + + // Discard without committing + delete action; + delete batch; + + cleanup(); + } + + void markTileModified(Tile* tile) { + if (!active || !tile) return; + + Position pos = tile->getPosition(); + uint64_t key = positionKey(pos); + + // Only snapshot the tile once per transaction (first time it's modified) + if (originalTiles.find(key) == originalTiles.end()) { + // Create a deep copy of the ORIGINAL tile BEFORE modification + Tile* originalCopy = tile->deepCopy(*editor->getMap()); + originalTiles[key] = originalCopy; + } + } + + bool isActive() const { return active; } + Editor* getEditor() const { return editor; } + +private: + void cleanup() { + active = false; + editor = nullptr; + batch = nullptr; + action = nullptr; + // Don't clear originalTiles here - it should be empty or ownership transferred + } +}; + +// Global accessor for tile modification tracking (used by lua_api_tile.cpp) +void markTileForUndo(Tile* tile) { + if (LuaTransaction::getInstance().isActive()) { + LuaTransaction::getInstance().markTileModified(tile); + } +} + +// ============================================================================ +// Helper Functions +// ============================================================================ + +// Helper function to show alert dialog +static int showAlert(sol::this_state ts, sol::object arg) { + sol::state_view lua(ts); + + std::string title = "Script"; + std::string message; + std::vector buttons; + + // Handle different argument types + if (arg.is()) { + message = arg.as(); + buttons.push_back("OK"); + } else if (arg.is()) { + sol::table opts = arg.as(); + + if (opts["title"].valid()) { + title = opts["title"].get(); + } + if (opts["text"].valid()) { + message = opts["text"].get(); + } + if (opts["buttons"].valid()) { + sol::table btns = opts["buttons"]; + for (size_t i = 1; i <= btns.size(); ++i) { + if (btns[i].valid()) { + buttons.push_back(btns[i].get()); + } + } + } + + if (buttons.empty()) { + buttons.push_back("OK"); + } + } else { + sol::function tostring = lua["tostring"]; + message = tostring(arg); + buttons.push_back("OK"); + } + + // Determine dialog style based on buttons + long style = wxCENTRE; + if (buttons.size() == 1) { + style |= wxOK; + } else if (buttons.size() == 2) { + std::string btn1 = buttons[0]; + std::string btn2 = buttons[1]; + std::transform(btn1.begin(), btn1.end(), btn1.begin(), ::tolower); + std::transform(btn2.begin(), btn2.end(), btn2.begin(), ::tolower); + + if ((btn1 == "ok" && btn2 == "cancel") || (btn1 == "cancel" && btn2 == "ok")) { + style |= wxOK | wxCANCEL; + } else if ((btn1 == "yes" && btn2 == "no") || (btn1 == "no" && btn2 == "yes")) { + style |= wxYES_NO; + } else { + style |= wxOK | wxCANCEL; + } + } else if (buttons.size() >= 3) { + style |= wxYES_NO | wxCANCEL; + } + + wxWindow* parent = g_gui.root; + wxMessageDialog dlg(parent, wxString(message), wxString(title), style); + + int result = dlg.ShowModal(); + + switch (result) { + case wxID_OK: + case wxID_YES: + return 1; + case wxID_NO: + return 2; + case wxID_CANCEL: + return buttons.size() >= 3 ? 3 : 2; + default: + return 0; + } +} + +// Check if a map is currently open +static bool hasMap() { + Editor* editor = g_gui.GetCurrentEditor(); + return editor != nullptr && editor->getMap() != nullptr; +} + +// Refresh the map view +static void refresh() { + g_gui.RefreshView(); +} + +// Get the current Map object +static Map* getMap() { + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) return nullptr; + return editor->getMap(); +} + +// Get the current Selection object +static Selection* getSelection() { + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) return nullptr; + return &editor->selection; +} + +static void setClipboard(const std::string& text) { + if (wxTheClipboard->Open()) { + wxTheClipboard->SetData(new wxTextDataObject(text)); + wxTheClipboard->Close(); + } +} + +// Transaction function with undo/redo support +static void transaction(const std::string& name, sol::function func) { + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) { + throw sol::error("No map open"); + } + + LuaTransaction& trans = LuaTransaction::getInstance(); + + try { + trans.begin(editor); + func(); + trans.commit(); + g_gui.RefreshView(); + } catch (const sol::error&) { + trans.rollback(); + throw; // Re-throw to show error to user + } catch (const std::exception& e) { + trans.rollback(); + throw sol::error(std::string("Transaction failed: ") + e.what()); + } catch (...) { + trans.rollback(); + throw sol::error("Transaction failed with unknown error"); + } +} + +static sol::object getBorders(sol::this_state ts) { + sol::state_view lua(ts); + + sol::table bordersTable = lua.create_table(); + + for (auto& pair : g_brushes.getBorders()) { + AutoBorder* border = pair.second; + if (!border) continue; + + sol::table b = lua.create_table(); + b["id"] = border->id; + b["group"] = border->group; + b["ground"] = border->ground; + + sol::table tiles = lua.create_table(); + for(int i = 0; i < 13; ++i) { + tiles[i+1] = border->tiles[i]; + } + b["tiles"] = tiles; + + bordersTable[border->id] = b; + } + + return bordersTable; +} + +static std::string getDataDirectory() { + return GUI::GetDataDirectory().ToStdString(); +} + +static sol::table storageForScript(sol::this_state ts, const std::string& name) { + sol::state_view lua(ts); + sol::table storage = lua.create_table(); + + std::string scriptDir = "."; + if (lua["SCRIPT_DIR"].valid()) { + scriptDir = lua["SCRIPT_DIR"]; + } + + std::string filename = name; + if (filename.find('.') == std::string::npos) { + filename += ".json"; + } + std::string path = scriptDir + "/" + filename; + storage["path"] = path; + + storage["load"] = [path](sol::this_state ts2, sol::object) -> sol::object { + sol::state_view lua(ts2); + std::ifstream file(path); + if (!file.is_open()) { + return sol::make_object(lua, sol::nil); + } + + std::stringstream buffer; + buffer << file.rdbuf(); + file.close(); + + std::string content = buffer.str(); + if (content.empty()) { + return sol::make_object(lua, sol::nil); + } + + sol::table json = lua["json"]; + if (!json.valid() || !json["decode"].valid()) { + return sol::make_object(lua, sol::nil); + } + + try { + sol::function decode = json["decode"]; + sol::protected_function_result result = decode(content); + if (!result.valid()) { + return sol::make_object(lua, sol::nil); + } + sol::object decoded = result; + return sol::make_object(lua, decoded); + } catch (const sol::error&) { + return sol::make_object(lua, sol::nil); + } + }; + + storage["save"] = [path](sol::this_state ts2, sol::object first, sol::object second) -> bool { + sol::state_view lua(ts2); + std::string content; + + sol::object data = (second.valid() && !second.is()) ? second : first; + if (!data.valid() || data.is()) { + return false; + } + + if (data.is()) { + content = data.as(); + } else { + sol::table json = lua["json"]; + if (!json.valid() || !json["encode_pretty"].valid()) { + return false; + } + try { + sol::function encode = json["encode_pretty"]; + sol::protected_function_result result = encode(data); + if (!result.valid()) { + return false; + } + content = result.get(); + } catch (const sol::error&) { + return false; + } + } + + std::ofstream file(path, std::ios::trunc); + if (!file.is_open()) { + return false; + } + file << content; + file.close(); + return true; + }; + + storage["clear"] = [path](sol::object) -> bool { + return std::remove(path.c_str()) == 0; + }; + + return storage; +} + +// ============================================================================ +// Register App API +// ============================================================================ + +void registerApp(sol::state& lua) { + // Create the 'app' table + sol::table app = lua.create_named_table("app"); + + // Version info + app["version"] = __RME_VERSION__; + app["apiVersion"] = 2; // Bumped version for new APIs + + // Functions + app["alert"] = showAlert; + app["hasMap"] = hasMap; + app["refresh"] = refresh; + app["transaction"] = transaction; + app["setClipboard"] = setClipboard; + app["getDataDirectory"] = getDataDirectory; + app["addContextMenu"] = [](const std::string& label, sol::function callback) { + g_luaScripts.registerContextMenuItem(label, callback); + }; + app["selectRaw"] = [](int itemId) { + if (g_items.typeExists(itemId)) { + ItemType& it = g_items[itemId]; + if (it.raw_brush) { + g_gui.SelectBrush(it.raw_brush, TILESET_RAW); + } + } + }; + + app["setCameraPosition"] = [](int x, int y, int z) { + g_gui.SetScreenCenterPosition(Position(x, y, z)); + }; + app["storage"] = storageForScript; + + // Yield to process pending UI events (prevents UI freeze during long operations) + app["yield"] = []() { + if (wxTheApp) { + wxTheApp->Yield(true); + } + }; + + // Sleep for a given number of milliseconds (use sparingly, blocks the UI) + app["sleep"] = [](int milliseconds) { + if (milliseconds > 0 && milliseconds <= 10000) { // Max 10 seconds + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); + } + }; + + // Get elapsed time in milliseconds since application start (high precision timer) + app["getTime"] = []() -> long { + return g_gui.gfx.getElapsedTime(); + }; + + // Event system: app.events:on("eventName", callback) / app.events:off(id) + sol::table events = lua.create_table(); + events["on"] = [](sol::this_state ts, sol::table self, const std::string& eventName, sol::function callback) -> int { + return g_luaScripts.addEventListener(eventName, callback); + }; + events["off"] = [](sol::this_state ts, sol::table self, int listenerId) -> bool { + return g_luaScripts.removeEventListener(listenerId); + }; + app["events"] = events; + + // Properties via metatable (for dynamic properties like 'map' and 'selection') + sol::table mt = lua.create_table(); + mt[sol::meta_function::index] = [](sol::this_state ts, sol::table self, std::string key) -> sol::object { + sol::state_view lua(ts); + + if (key == "map") { + Map* map = getMap(); + if (map) { + return sol::make_object(lua, map); + } + return sol::nil; + } + else if (key == "selection") { + Selection* sel = getSelection(); + if (sel) { + return sol::make_object(lua, sel); + } + return sol::nil; + } + else if (key == "borders") { + return getBorders(ts); + } + else if (key == "editor") { + Editor* editor = g_gui.GetCurrentEditor(); + if (editor) { + return sol::make_object(lua, editor); + } + return sol::nil; + } + else if (key == "brush") { + Brush* b = g_gui.GetCurrentBrush(); + if (b) return sol::make_object(lua, b); + return sol::nil; + } + else if (key == "brushSize") { + return sol::make_object(lua, g_gui.GetBrushSize()); + } + else if (key == "brushShape") { + return sol::make_object(lua, g_gui.GetBrushShape() == BRUSHSHAPE_CIRCLE ? "circle" : "square"); + } + else if (key == "brushVariation") { + return sol::make_object(lua, g_gui.GetBrushVariation()); + } + else if (key == "spawnTime") { + return sol::make_object(lua, g_gui.GetSpawnTime()); + } + return sol::nil; + }; + + mt[sol::meta_function::new_index] = [](sol::this_state ts, sol::table self, std::string key, sol::object value) { + if (key == "brushSize") { + if (value.is()) g_gui.SetBrushSize(value.as()); + } + else if (key == "brushShape") { + if (value.is()) { + std::string s = value.as(); + if (s == "circle") g_gui.SetBrushShape(BRUSHSHAPE_CIRCLE); + else if (s == "square") g_gui.SetBrushShape(BRUSHSHAPE_SQUARE); + } + } + else if (key == "brushVariation") { + if (value.is()) g_gui.SetBrushVariation(value.as()); + } + else if (key == "spawnTime") { + if (value.is()) g_gui.SetSpawnTime(value.as()); + } + }; + + // Map overlay system + sol::table mapView = lua.create_table(); + mapView["addOverlay"] = [](sol::variadic_args va) -> bool { + if (va.size() == 2 && va[0].is() && va[1].is()) { + return g_luaScripts.addMapOverlay(va[0].as(), va[1].as()); + } + if (va.size() == 3 && va[1].is() && va[2].is()) { + return g_luaScripts.addMapOverlay(va[1].as(), va[2].as()); + } + return false; + }; + mapView["removeOverlay"] = [](sol::variadic_args va) -> bool { + if (va.size() == 1 && va[0].is()) { + return g_luaScripts.removeMapOverlay(va[0].as()); + } + if (va.size() == 2 && va[1].is()) { + return g_luaScripts.removeMapOverlay(va[1].as()); + } + return false; + }; + mapView["setEnabled"] = [](sol::variadic_args va) -> bool { + if (va.size() == 2 && va[0].is() && va[1].is()) { + return g_luaScripts.setMapOverlayEnabled(va[0].as(), va[1].as()); + } + if (va.size() == 3 && va[1].is() && va[2].is()) { + return g_luaScripts.setMapOverlayEnabled(va[1].as(), va[2].as()); + } + return false; + }; + mapView["registerShow"] = [](sol::variadic_args va) -> bool { + std::string label; + std::string overlayId; + bool enabled = true; + sol::function ontoggle; + + if (va.size() == 2 && va[0].is() && va[1].is()) { + label = va[0].as(); + overlayId = va[1].as(); + } else if (va.size() >= 3) { + if (va[0].is()) { + if (va[1].is()) label = va[1].as(); + if (va[2].is()) overlayId = va[2].as(); + } else if (va[0].is() && va[1].is()) { + label = va[0].as(); + overlayId = va[1].as(); + } + } + + if (va.size() >= 3 && va[va.size() - 1].is()) { + sol::table opts = va[va.size() - 1].as(); + enabled = opts.get_or("enabled", enabled); + if (opts["ontoggle"].valid()) { + ontoggle = opts["ontoggle"]; + } + } else if (va.size() >= 3 && va[va.size() - 1].is()) { + enabled = va[va.size() - 1].as(); + } + + if (label.empty() || overlayId.empty()) { + return false; + } + + return g_luaScripts.registerMapOverlayShow(label, overlayId, enabled, ontoggle); + }; + app["mapView"] = mapView; + + app[sol::metatable_key] = mt; + + // Register Editor usertype for undo/redo functionality + lua.new_usertype("Editor", + sol::no_constructor, + + // Undo/Redo functions + "undo", [](Editor* editor) { + if (editor && editor->actionQueue && editor->actionQueue->canUndo()) { + editor->actionQueue->undo(); + g_gui.RefreshView(); + } + }, + "redo", [](Editor* editor) { + if (editor && editor->actionQueue && editor->actionQueue->canRedo()) { + editor->actionQueue->redo(); + g_gui.RefreshView(); + } + }, + "canUndo", [](Editor* editor) -> bool { + return editor && editor->actionQueue && editor->actionQueue->canUndo(); + }, + "canRedo", [](Editor* editor) -> bool { + return editor && editor->actionQueue && editor->actionQueue->canRedo(); + }, + + // History info + "historyIndex", sol::property([](Editor* editor) -> int { + if (editor && editor->actionQueue) { + return (int)editor->actionQueue->getCurrentIndex(); + } + return 0; + }), + "historySize", sol::property([](Editor* editor) -> int { + if (editor && editor->actionQueue) { + return (int)editor->actionQueue->getSize(); + } + return 0; + }), + + // Get history as a table + "getHistory", [](Editor* editor, sol::this_state ts) -> sol::table { + sol::state_view lua(ts); + sol::table history = lua.create_table(); + + if (editor && editor->actionQueue) { + size_t size = editor->actionQueue->getSize(); + for (size_t i = 0; i < size; ++i) { + sol::table input = lua.create_table(); + input["index"] = (int)(i + 1); // 1-based for Lua + input["name"] = editor->actionQueue->getActionName(i); + history[i + 1] = input; + } + } + return history; + }, + + // Navigate to specific history index + "goToHistory", [](Editor* editor, int targetIndex) { + if (!editor || !editor->actionQueue) return; + + int current = (int)editor->actionQueue->getCurrentIndex(); + int target = targetIndex; // Already 1-based from Lua + + if (target < 0) target = 0; + if (target > (int)editor->actionQueue->getSize()) { + target = (int)editor->actionQueue->getSize(); + } + + int diff = target - current; + + if (diff > 0) { + for (int i = 0; i < diff; ++i) { + if (editor->actionQueue->canRedo()) { + editor->actionQueue->redo(); + } + } + } else if (diff < 0) { + for (int i = 0; i < -diff; ++i) { + if (editor->actionQueue->canUndo()) { + editor->actionQueue->undo(); + } + } + } + g_gui.RefreshView(); + } + ); +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_app.h b/source/lua/lua_api_app.h new file mode 100644 index 000000000..e6228ad13 --- /dev/null +++ b/source/lua/lua_api_app.h @@ -0,0 +1,28 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_APP_H +#define RME_LUA_API_APP_H + +#define SOL_ALL_SAFETIES_ON 1 +#include + +namespace LuaAPI { + void registerApp(sol::state& lua); +} + +#endif // RME_LUA_API_APP_H diff --git a/source/lua/lua_api_brush.cpp b/source/lua/lua_api_brush.cpp new file mode 100644 index 000000000..ed4787dd3 --- /dev/null +++ b/source/lua/lua_api_brush.cpp @@ -0,0 +1,157 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_brush.h" +#include "../brush.h" +#include "../gui.h" + +namespace LuaAPI { + +// Helper to get brush type as string +static std::string getBrushTypeName(Brush* brush) { + if (!brush) return "unknown"; + + if (brush->isRaw()) return "raw"; + if (brush->isDoodad()) return "doodad"; + if (brush->isGround()) return "ground"; + if (brush->isWall()) return "wall"; + if (brush->isWallDecoration()) return "wall_decoration"; + if (brush->isTable()) return "table"; + if (brush->isCarpet()) return "carpet"; + if (brush->isDoor()) return "door"; + if (brush->isOptionalBorder()) return "optional_border"; + if (brush->isCreature()) return "creature"; + if (brush->isSpawn()) return "spawn"; + if (brush->isHouse()) return "house"; + if (brush->isHouseExit()) return "house_exit"; + if (brush->isWaypoint()) return "waypoint"; + if (brush->isFlag()) return "flag"; + if (brush->isEraser()) return "eraser"; + if (brush->isTerrain()) return "terrain"; + + return "unknown"; +} + +void registerBrush(sol::state& lua) { + // Register Brush usertype (read-only) + lua.new_usertype("Brush", + sol::no_constructor, + + // Properties (read-only) + "id", sol::property([](Brush* b) -> uint32_t { + return b ? b->getID() : 0; + }), + "name", sol::property([](Brush* b) -> std::string { + return b ? b->getName() : ""; + }), + "lookId", sol::property([](Brush* b) -> int { + return b ? b->getLookID() : 0; + }), + "type", sol::property([](Brush* b) -> std::string { + return getBrushTypeName(b); + }), + + // Capabilities (read-only) + "canDrag", sol::property([](Brush* b) -> bool { + return b && b->canDrag(); + }), + "canSmear", sol::property([](Brush* b) -> bool { + return b && b->canSmear(); + }), + "needsBorders", sol::property([](Brush* b) -> bool { + return b && b->needBorders(); + }), + "oneSizeFitsAll", sol::property([](Brush* b) -> bool { + return b && b->oneSizeFitsAll(); + }), + "maxVariation", sol::property([](Brush* b) -> int { + return b ? b->getMaxVariation() : 0; + }), + "visibleInPalette", sol::property([](Brush* b) -> bool { + return b && b->visibleInPalette(); + }), + + // Type checks + "isRaw", sol::property([](Brush* b) { return b && b->isRaw(); }), + "isDoodad", sol::property([](Brush* b) { return b && b->isDoodad(); }), + "isTerrain", sol::property([](Brush* b) { return b && b->isTerrain(); }), + "isGround", sol::property([](Brush* b) { return b && b->isGround(); }), + "isWall", sol::property([](Brush* b) { return b && b->isWall(); }), + "isTable", sol::property([](Brush* b) { return b && b->isTable(); }), + "isCarpet", sol::property([](Brush* b) { return b && b->isCarpet(); }), + "isDoor", sol::property([](Brush* b) { return b && b->isDoor(); }), + "isCreature", sol::property([](Brush* b) { return b && b->isCreature(); }), + "isSpawn", sol::property([](Brush* b) { return b && b->isSpawn(); }), + "isHouse", sol::property([](Brush* b) { return b && b->isHouse(); }), + "isEraser", sol::property([](Brush* b) { return b && b->isEraser(); }), + + // String representation + sol::meta_function::to_string, [](Brush* b) -> std::string { + if (!b) return "Brush(invalid)"; + return "Brush(\"" + b->getName() + "\", type=" + getBrushTypeName(b) + ")"; + } + ); + + // Register Brushes namespace for brush lookup + sol::table brushes = lua.create_named_table("Brushes"); + + // Get brush by name + brushes["get"] = [](const std::string& name) -> Brush* { + return g_brushes.getBrush(name); + }; + + // Get all brush names (for iteration) + brushes["getNames"] = [](sol::this_state ts) -> sol::table { + sol::state_view lua(ts); + sol::table names = lua.create_table(); + + int idx = 1; + for (const auto& pair : g_brushes.getMap()) { + names[idx++] = pair.first; + } + return names; + }; + + // Extend the existing 'app' table with brush-related functions + sol::table app = lua["app"]; + + // Current brush + app.set_function("getBrush", []() -> Brush* { + return g_gui.GetCurrentBrush(); + }); + + // Set brush by name + app.set_function("setBrush", [](const std::string& name) -> bool { + Brush* brush = g_brushes.getBrush(name); + if (brush) { + g_gui.SelectBrush(brush); + return true; + } + return false; + }); + + + + // Register BrushShape enum + lua.new_enum("BrushShape", + "SQUARE", BRUSHSHAPE_SQUARE, + "CIRCLE", BRUSHSHAPE_CIRCLE + ); +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_brush.h b/source/lua/lua_api_brush.h new file mode 100644 index 000000000..1e7401647 --- /dev/null +++ b/source/lua/lua_api_brush.h @@ -0,0 +1,30 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_BRUSH_H +#define RME_LUA_API_BRUSH_H + +#define SOL_ALL_SAFETIES_ON 1 +#include + +namespace LuaAPI { + // Register Brush usertype and Brushes namespace with Lua + // Also extends the app namespace with brush-related functions + void registerBrush(sol::state& lua); +} + +#endif // RME_LUA_API_BRUSH_H diff --git a/source/lua/lua_api_color.cpp b/source/lua/lua_api_color.cpp new file mode 100644 index 000000000..3899a2bfd --- /dev/null +++ b/source/lua/lua_api_color.cpp @@ -0,0 +1,91 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_color.h" + +namespace LuaAPI { + +void registerColor(sol::state& lua) { + // Register LuaColor as "Color" usertype + lua.new_usertype("Color", + // Constructors + sol::constructors< + LuaColor(), // Color() + LuaColor(uint8_t, uint8_t, uint8_t), // Color(r, g, b) + LuaColor(uint8_t, uint8_t, uint8_t, uint8_t) // Color(r, g, b, a) + >(), + + // Alternative constructor from table: Color{red=255, green=0, blue=0} + sol::call_constructor, sol::factories( + // Default constructor + []() { return LuaColor(); }, + // RGB constructor + [](uint8_t r, uint8_t g, uint8_t b) { return LuaColor(r, g, b); }, + // RGBA constructor + [](uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return LuaColor(r, g, b, a); }, + // Table constructor + [](sol::table t) { + LuaColor c; + c.red = t.get_or("red", 0); + c.green = t.get_or("green", 0); + c.blue = t.get_or("blue", 0); + c.alpha = t.get_or("alpha", 255); + return c; + } + ), + + // Properties (read/write) + "red", &LuaColor::red, + "green", &LuaColor::green, + "blue", &LuaColor::blue, + "alpha", &LuaColor::alpha, + + // Shorthand aliases + "r", &LuaColor::red, + "g", &LuaColor::green, + "b", &LuaColor::blue, + "a", &LuaColor::alpha, + + // Equality comparison + sol::meta_function::equal_to, &LuaColor::operator==, + + // String representation + sol::meta_function::to_string, [](const LuaColor& c) { + return "Color(" + std::to_string(c.red) + ", " + + std::to_string(c.green) + ", " + + std::to_string(c.blue) + + (c.alpha != 255 ? ", " + std::to_string(c.alpha) : "") + ")"; + } + ); + + // Predefined colors as constants + sol::table colorConstants = lua.create_named_table("Colors"); + colorConstants["BLACK"] = LuaColor(0, 0, 0); + colorConstants["WHITE"] = LuaColor(255, 255, 255); + colorConstants["RED"] = LuaColor(255, 0, 0); + colorConstants["GREEN"] = LuaColor(0, 255, 0); + colorConstants["BLUE"] = LuaColor(0, 0, 255); + colorConstants["YELLOW"] = LuaColor(255, 255, 0); + colorConstants["CYAN"] = LuaColor(0, 255, 255); + colorConstants["MAGENTA"] = LuaColor(255, 0, 255); + colorConstants["ORANGE"] = LuaColor(255, 165, 0); + colorConstants["GRAY"] = LuaColor(128, 128, 128); + colorConstants["TRANSPARENT"] = LuaColor(0, 0, 0, 0); +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_color.h b/source/lua/lua_api_color.h new file mode 100644 index 000000000..36fe8e67a --- /dev/null +++ b/source/lua/lua_api_color.h @@ -0,0 +1,59 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_COLOR_H +#define RME_LUA_API_COLOR_H + +#define SOL_ALL_SAFETIES_ON 1 +#include + +namespace LuaAPI { + +// Simple RGBA color class for Lua scripting +struct LuaColor { + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t alpha; + + LuaColor() : red(0), green(0), blue(0), alpha(255) {} + LuaColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) + : red(r), green(g), blue(b), alpha(a) {} + + // Convert to wxColour for use with wxWidgets + wxColour toWxColour() const { + return wxColour(red, green, blue, alpha); + } + + // Create from wxColour + static LuaColor fromWxColour(const wxColour& c) { + return LuaColor(c.Red(), c.Green(), c.Blue(), c.Alpha()); + } + + // Equality comparison + bool operator==(const LuaColor& other) const { + return red == other.red && green == other.green && + blue == other.blue && alpha == other.alpha; + } +}; + +// Register the Color usertype with Lua +void registerColor(sol::state& lua); + +} // namespace LuaAPI + +#endif // RME_LUA_API_COLOR_H diff --git a/source/lua/lua_api_creature.cpp b/source/lua/lua_api_creature.cpp new file mode 100644 index 000000000..f48410abf --- /dev/null +++ b/source/lua/lua_api_creature.cpp @@ -0,0 +1,138 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_creature.h" +#include "lua_api.h" +#include "../creature.h" +#include "../creatures.h" +#include "../spawn.h" +#include "../tile.h" +#include "../map.h" +#include "../editor.h" +#include "../gui.h" + +namespace LuaAPI { + +void registerCreature(sol::state& lua) { + // Register Direction enum + lua.new_enum("Direction", + "NORTH", NORTH, + "EAST", EAST, + "SOUTH", SOUTH, + "WEST", WEST + ); + + // Register Creature usertype (expanded from basic in lua_api_tile.cpp) + lua.new_usertype("Creature", + sol::no_constructor, + + // Properties (read-only) + "name", sol::property([](Creature* c) -> std::string { + return c ? c->getName() : ""; + }), + "isNpc", sol::property([](Creature* c) -> bool { + return c && c->isNpc(); + }), + + // Properties (read/write) + "spawnTime", sol::property( + [](Creature* c) -> int { return c ? c->getSpawnTime() : 0; }, + [](Creature* c, int time) { + if (c) { + c->setSpawnTime(time); + } + } + ), + "direction", sol::property( + [](Creature* c) -> int { return c ? static_cast(c->getDirection()) : 0; }, + [](Creature* c, int dir) { + if (c && dir >= DIRECTION_FIRST && dir <= DIRECTION_LAST) { + c->setDirection(static_cast(dir)); + } + } + ), + + // Selection + "isSelected", sol::property([](Creature* c) { return c && c->isSelected(); }), + "select", [](Creature* c) { if (c) c->select(); }, + "deselect", [](Creature* c) { if (c) c->deselect(); }, + + // String representation + sol::meta_function::to_string, [](Creature* c) -> std::string { + if (!c) return "Creature(invalid)"; + std::string dir; + switch (c->getDirection()) { + case NORTH: dir = "N"; break; + case EAST: dir = "E"; break; + case SOUTH: dir = "S"; break; + case WEST: dir = "W"; break; + default: dir = "?"; break; + } + return "Creature(\"" + c->getName() + "\", dir=" + dir + + ", spawn=" + std::to_string(c->getSpawnTime()) + "s)"; + } + ); + + // Register Spawn usertype (expanded from basic in lua_api_tile.cpp) + lua.new_usertype("Spawn", + sol::no_constructor, + + // Properties (read/write) + "size", sol::property( + [](Spawn* s) -> int { return s ? s->getSize() : 0; }, + [](Spawn* s, int size) { + if (s && size > 0 && size < 100) { + s->setSize(size); + } + } + ), + // Alias for size + "radius", sol::property( + [](Spawn* s) -> int { return s ? s->getSize() : 0; }, + [](Spawn* s, int size) { + if (s && size > 0 && size < 100) { + s->setSize(size); + } + } + ), + + // Selection + "isSelected", sol::property([](Spawn* s) { return s && s->isSelected(); }), + "select", [](Spawn* s) { if (s) s->select(); }, + "deselect", [](Spawn* s) { if (s) s->deselect(); }, + + // String representation + sol::meta_function::to_string, [](Spawn* s) -> std::string { + if (!s) return "Spawn(invalid)"; + return "Spawn(radius=" + std::to_string(s->getSize()) + ")"; + } + ); + + // Helper function to check if a creature type exists + lua["creatureExists"] = [](const std::string& name) -> bool { + return g_creatures[name] != nullptr; + }; + + // Helper function to check if a creature is an NPC by name + lua["isNpcType"] = [](const std::string& name) -> bool { + CreatureType* type = g_creatures[name]; + return type && type->isNpc; + }; +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_creature.h b/source/lua/lua_api_creature.h new file mode 100644 index 000000000..30a0476f0 --- /dev/null +++ b/source/lua/lua_api_creature.h @@ -0,0 +1,30 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_CREATURE_H +#define RME_LUA_API_CREATURE_H + +#define SOL_ALL_SAFETIES_ON 1 +#include + +namespace LuaAPI { + // Register Creature and Spawn usertypes with Lua + // Also registers the Direction enum + void registerCreature(sol::state& lua); +} + +#endif // RME_LUA_API_CREATURE_H diff --git a/source/lua/lua_api_geo.cpp b/source/lua/lua_api_geo.cpp new file mode 100644 index 000000000..0cd30b19c --- /dev/null +++ b/source/lua/lua_api_geo.cpp @@ -0,0 +1,932 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_geo.h" +#include + +#include +#include +#include +#include +#include +#include + +namespace LuaAPI { + +void registerGeo(sol::state& lua) { + sol::table geoTable = lua.create_table(); + + // ======================================== + // BRESENHAM'S LINE ALGORITHM + // ======================================== + + // geo.bresenhamLine(x1, y1, x2, y2) -> table of points + // Returns all points on a line between two points + geoTable.set_function("bresenhamLine", [](int x1, int y1, int x2, int y2, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + int dx = std::abs(x2 - x1); + int dy = std::abs(y2 - y1); + int sx = (x1 < x2) ? 1 : -1; + int sy = (y1 < y2) ? 1 : -1; + int err = dx - dy; + + int index = 1; + int x = x1, y = y1; + + while (true) { + sol::table point = lua.create_table(); + point["x"] = x; + point["y"] = y; + result[index++] = point; + + if (x == x2 && y == y2) break; + + int e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x += sx; + } + if (e2 < dx) { + err += dx; + y += sy; + } + } + + return result; + }); + + // geo.bresenhamLine3d(x1, y1, z1, x2, y2, z2) -> table of 3D points + geoTable.set_function("bresenhamLine3d", [](int x1, int y1, int z1, int x2, int y2, int z2, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + int dx = std::abs(x2 - x1); + int dy = std::abs(y2 - y1); + int dz = std::abs(z2 - z1); + + int sx = (x1 < x2) ? 1 : -1; + int sy = (y1 < y2) ? 1 : -1; + int sz = (z1 < z2) ? 1 : -1; + + // Driving axis is the one with greatest delta + int dm = std::max({dx, dy, dz}); + + int i = dm; + int x = x1, y = y1, z = z1; + + int xErr = dm / 2; + int yErr = dm / 2; + int zErr = dm / 2; + + int index = 1; + + for (int step = 0; step <= dm; ++step) { + sol::table point = lua.create_table(); + point["x"] = x; + point["y"] = y; + point["z"] = z; + result[index++] = point; + + xErr -= dx; + yErr -= dy; + zErr -= dz; + + if (xErr < 0) { + xErr += dm; + x += sx; + } + if (yErr < 0) { + yErr += dm; + y += sy; + } + if (zErr < 0) { + zErr += dm; + z += sz; + } + } + + return result; + }); + + // ======================================== + // BEZIER CURVES + // ======================================== + + // geo.bezierCurve(points, steps) -> table of points + // Quadratic/Cubic Bezier curve through control points + geoTable.set_function("bezierCurve", [](sol::table controlPoints, sol::optional steps, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + int numSteps = steps.value_or(20); + + // Parse control points + std::vector> points; + for (auto& kv : controlPoints) { + if (kv.second.get_type() == sol::type::table) { + sol::table pt = kv.second; + float x = pt.get_or("x", pt.get_or(1, 0.0f)); + float y = pt.get_or("y", pt.get_or(2, 0.0f)); + points.push_back({x, y}); + } + } + + if (points.size() < 2) { + return result; + } + + int index = 1; + + // De Casteljau's algorithm for any number of control points + auto deCasteljau = [&](float t) -> std::pair { + std::vector> temp = points; + + while (temp.size() > 1) { + std::vector> newTemp; + for (size_t i = 0; i < temp.size() - 1; ++i) { + float x = temp[i].first + t * (temp[i + 1].first - temp[i].first); + float y = temp[i].second + t * (temp[i + 1].second - temp[i].second); + newTemp.push_back({x, y}); + } + temp = newTemp; + } + + return temp[0]; + }; + + for (int i = 0; i <= numSteps; ++i) { + float t = (float)i / (float)numSteps; + auto [x, y] = deCasteljau(t); + + sol::table point = lua.create_table(); + point["x"] = std::round(x); + point["y"] = std::round(y); + result[index++] = point; + } + + return result; + }); + + // geo.bezierCurve3d(points, steps) -> table of 3D points + geoTable.set_function("bezierCurve3d", [](sol::table controlPoints, sol::optional steps, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + int numSteps = steps.value_or(20); + + // Parse control points + std::vector> points; + for (auto& kv : controlPoints) { + if (kv.second.get_type() == sol::type::table) { + sol::table pt = kv.second; + float x = pt.get_or("x", 0.0f); + float y = pt.get_or("y", 0.0f); + float z = pt.get_or("z", 0.0f); + points.push_back({x, y, z}); + } + } + + if (points.size() < 2) { + return result; + } + + int index = 1; + + auto deCasteljau3d = [&](float t) -> std::tuple { + std::vector> temp = points; + + while (temp.size() > 1) { + std::vector> newTemp; + for (size_t i = 0; i < temp.size() - 1; ++i) { + float x = std::get<0>(temp[i]) + t * (std::get<0>(temp[i + 1]) - std::get<0>(temp[i])); + float y = std::get<1>(temp[i]) + t * (std::get<1>(temp[i + 1]) - std::get<1>(temp[i])); + float z = std::get<2>(temp[i]) + t * (std::get<2>(temp[i + 1]) - std::get<2>(temp[i])); + newTemp.push_back({x, y, z}); + } + temp = newTemp; + } + + return temp[0]; + }; + + for (int i = 0; i <= numSteps; ++i) { + float t = (float)i / (float)numSteps; + auto [x, y, z] = deCasteljau3d(t); + + sol::table point = lua.create_table(); + point["x"] = std::round(x); + point["y"] = std::round(y); + point["z"] = std::round(z); + result[index++] = point; + } + + return result; + }); + + // ======================================== + // FLOOD FILL + // ======================================== + + // geo.floodFill(grid, startX, startY, newValue, options) -> grid + // Flood fill algorithm (4-connected or 8-connected) + geoTable.set_function("floodFill", [](sol::table inputGrid, int startX, int startY, int newValue, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + // Get dimensions + int height = static_cast(inputGrid.size()); + int width = 0; + if (inputGrid[1].valid() && inputGrid[1].get_type() == sol::type::table) { + sol::table firstRow = inputGrid[1]; + width = static_cast(firstRow.size()); + } + + if (width <= 0 || height <= 0) return inputGrid; + + bool eightConnected = false; + if (options) { + sol::table opts = *options; + eightConnected = opts.get_or("eightConnected", false); + } + + // Convert to grid + std::vector> grid(height, std::vector(width, 0)); + for (int y = 1; y <= height; ++y) { + if (inputGrid[y].valid() && inputGrid[y].get_type() == sol::type::table) { + sol::table row = inputGrid[y]; + for (int x = 1; x <= width; ++x) { + if (row[x].valid()) { + grid[y - 1][x - 1] = row[x].get(); + } + } + } + } + + // Adjust for 1-indexed Lua + int sx = startX - 1; + int sy = startY - 1; + + if (sx < 0 || sx >= width || sy < 0 || sy >= height) { + return inputGrid; + } + + int oldValue = grid[sy][sx]; + if (oldValue == newValue) { + return inputGrid; + } + + // BFS flood fill + std::queue> queue; + queue.push({sx, sy}); + + // Direction arrays + const int dx4[] = {0, 1, 0, -1}; + const int dy4[] = {-1, 0, 1, 0}; + const int dx8[] = {0, 1, 1, 1, 0, -1, -1, -1}; + const int dy8[] = {-1, -1, 0, 1, 1, 1, 0, -1}; + + const int* dx = eightConnected ? dx8 : dx4; + const int* dy = eightConnected ? dy8 : dy4; + int numDirs = eightConnected ? 8 : 4; + + while (!queue.empty()) { + auto [cx, cy] = queue.front(); + queue.pop(); + + if (cx < 0 || cx >= width || cy < 0 || cy >= height) continue; + if (grid[cy][cx] != oldValue) continue; + + grid[cy][cx] = newValue; + + for (int i = 0; i < numDirs; ++i) { + int nx = cx + dx[i]; + int ny = cy + dy[i]; + if (nx >= 0 && nx < width && ny >= 0 && ny < height && grid[ny][nx] == oldValue) { + queue.push({nx, ny}); + } + } + } + + // Convert back to Lua table + sol::table result = lua.create_table(); + for (int y = 0; y < height; ++y) { + sol::table row = lua.create_table(); + for (int x = 0; x < width; ++x) { + row[x + 1] = grid[y][x]; + } + result[y + 1] = row; + } + + return result; + }); + + // geo.getFloodFillPositions(grid, startX, startY, options) -> table of positions + // Returns all positions that would be filled without modifying the grid + geoTable.set_function("getFloodFillPositions", [](sol::table inputGrid, int startX, int startY, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + int height = static_cast(inputGrid.size()); + int width = 0; + if (inputGrid[1].valid() && inputGrid[1].get_type() == sol::type::table) { + sol::table firstRow = inputGrid[1]; + width = static_cast(firstRow.size()); + } + + if (width <= 0 || height <= 0) return result; + + bool eightConnected = false; + if (options) { + sol::table opts = *options; + eightConnected = opts.get_or("eightConnected", false); + } + + // Convert to grid + std::vector> grid(height, std::vector(width, 0)); + for (int y = 1; y <= height; ++y) { + if (inputGrid[y].valid() && inputGrid[y].get_type() == sol::type::table) { + sol::table row = inputGrid[y]; + for (int x = 1; x <= width; ++x) { + if (row[x].valid()) { + grid[y - 1][x - 1] = row[x].get(); + } + } + } + } + + int sx = startX - 1; + int sy = startY - 1; + + if (sx < 0 || sx >= width || sy < 0 || sy >= height) { + return result; + } + + int targetValue = grid[sy][sx]; + + std::vector> visited(height, std::vector(width, false)); + std::queue> queue; + queue.push({sx, sy}); + visited[sy][sx] = true; + + const int dx4[] = {0, 1, 0, -1}; + const int dy4[] = {-1, 0, 1, 0}; + const int dx8[] = {0, 1, 1, 1, 0, -1, -1, -1}; + const int dy8[] = {-1, -1, 0, 1, 1, 1, 0, -1}; + + const int* dx = eightConnected ? dx8 : dx4; + const int* dy = eightConnected ? dy8 : dy4; + int numDirs = eightConnected ? 8 : 4; + + int index = 1; + + while (!queue.empty()) { + auto [cx, cy] = queue.front(); + queue.pop(); + + sol::table point = lua.create_table(); + point["x"] = cx + 1; // Back to 1-indexed + point["y"] = cy + 1; + result[index++] = point; + + for (int i = 0; i < numDirs; ++i) { + int nx = cx + dx[i]; + int ny = cy + dy[i]; + if (nx >= 0 && nx < width && ny >= 0 && ny < height && + !visited[ny][nx] && grid[ny][nx] == targetValue) { + visited[ny][nx] = true; + queue.push({nx, ny}); + } + } + } + + return result; + }); + + // ======================================== + // CIRCLE / ELLIPSE + // ======================================== + + // geo.circle(centerX, centerY, radius, options) -> table of points + // Generate points on a circle (outline or filled) + geoTable.set_function("circle", [](int centerX, int centerY, int radius, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + bool filled = false; + if (options) { + sol::table opts = *options; + filled = opts.get_or("filled", false); + } + + int index = 1; + + if (filled) { + // Filled circle + for (int y = -radius; y <= radius; ++y) { + for (int x = -radius; x <= radius; ++x) { + if (x * x + y * y <= radius * radius) { + sol::table point = lua.create_table(); + point["x"] = centerX + x; + point["y"] = centerY + y; + result[index++] = point; + } + } + } + } else { + // Midpoint circle algorithm (outline) + int x = radius; + int y = 0; + int err = 0; + + auto addPoints = [&](int cx, int cy, int px, int py) { + sol::table p1 = lua.create_table(); p1["x"] = cx + px; p1["y"] = cy + py; result[index++] = p1; + sol::table p2 = lua.create_table(); p2["x"] = cx + py; p2["y"] = cy + px; result[index++] = p2; + sol::table p3 = lua.create_table(); p3["x"] = cx - py; p3["y"] = cy + px; result[index++] = p3; + sol::table p4 = lua.create_table(); p4["x"] = cx - px; p4["y"] = cy + py; result[index++] = p4; + sol::table p5 = lua.create_table(); p5["x"] = cx - px; p5["y"] = cy - py; result[index++] = p5; + sol::table p6 = lua.create_table(); p6["x"] = cx - py; p6["y"] = cy - px; result[index++] = p6; + sol::table p7 = lua.create_table(); p7["x"] = cx + py; p7["y"] = cy - px; result[index++] = p7; + sol::table p8 = lua.create_table(); p8["x"] = cx + px; p8["y"] = cy - py; result[index++] = p8; + }; + + while (x >= y) { + addPoints(centerX, centerY, x, y); + y++; + + if (err <= 0) { + err += 2 * y + 1; + } else { + x--; + err += 2 * (y - x) + 1; + } + } + } + + return result; + }); + + // geo.ellipse(centerX, centerY, radiusX, radiusY, options) -> table of points + geoTable.set_function("ellipse", [](int centerX, int centerY, int radiusX, int radiusY, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + bool filled = false; + if (options) { + sol::table opts = *options; + filled = opts.get_or("filled", false); + } + + int index = 1; + + if (filled) { + for (int y = -radiusY; y <= radiusY; ++y) { + for (int x = -radiusX; x <= radiusX; ++x) { + float dx = (float)x / radiusX; + float dy = (float)y / radiusY; + if (dx * dx + dy * dy <= 1.0f) { + sol::table point = lua.create_table(); + point["x"] = centerX + x; + point["y"] = centerY + y; + result[index++] = point; + } + } + } + } else { + // Midpoint ellipse algorithm + int rx2 = radiusX * radiusX; + int ry2 = radiusY * radiusY; + int twoRx2 = 2 * rx2; + int twoRy2 = 2 * ry2; + + int x = 0; + int y = radiusY; + int px = 0; + int py = twoRx2 * y; + + auto addEllipsePoints = [&](int cx, int cy, int ex, int ey) { + sol::table p1 = lua.create_table(); p1["x"] = cx + ex; p1["y"] = cy + ey; result[index++] = p1; + sol::table p2 = lua.create_table(); p2["x"] = cx - ex; p2["y"] = cy + ey; result[index++] = p2; + sol::table p3 = lua.create_table(); p3["x"] = cx + ex; p3["y"] = cy - ey; result[index++] = p3; + sol::table p4 = lua.create_table(); p4["x"] = cx - ex; p4["y"] = cy - ey; result[index++] = p4; + }; + + // Region 1 + int p = (int)(ry2 - rx2 * radiusY + 0.25 * rx2); + while (px < py) { + addEllipsePoints(centerX, centerY, x, y); + x++; + px += twoRy2; + if (p < 0) { + p += ry2 + px; + } else { + y--; + py -= twoRx2; + p += ry2 + px - py; + } + } + + // Region 2 + p = (int)(ry2 * (x + 0.5) * (x + 0.5) + rx2 * (y - 1) * (y - 1) - rx2 * ry2); + while (y >= 0) { + addEllipsePoints(centerX, centerY, x, y); + y--; + py -= twoRx2; + if (p > 0) { + p += rx2 - py; + } else { + x++; + px += twoRy2; + p += rx2 - py + px; + } + } + } + + return result; + }); + + // ======================================== + // RECTANGLE + // ======================================== + + // geo.rectangle(x1, y1, x2, y2, options) -> table of points + geoTable.set_function("rectangle", [](int x1, int y1, int x2, int y2, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + bool filled = false; + if (options) { + sol::table opts = *options; + filled = opts.get_or("filled", false); + } + + int minX = std::min(x1, x2); + int maxX = std::max(x1, x2); + int minY = std::min(y1, y2); + int maxY = std::max(y1, y2); + + int index = 1; + + if (filled) { + for (int y = minY; y <= maxY; ++y) { + for (int x = minX; x <= maxX; ++x) { + sol::table point = lua.create_table(); + point["x"] = x; + point["y"] = y; + result[index++] = point; + } + } + } else { + // Outline only + for (int x = minX; x <= maxX; ++x) { + sol::table p1 = lua.create_table(); p1["x"] = x; p1["y"] = minY; result[index++] = p1; + sol::table p2 = lua.create_table(); p2["x"] = x; p2["y"] = maxY; result[index++] = p2; + } + for (int y = minY + 1; y < maxY; ++y) { + sol::table p1 = lua.create_table(); p1["x"] = minX; p1["y"] = y; result[index++] = p1; + sol::table p2 = lua.create_table(); p2["x"] = maxX; p2["y"] = y; result[index++] = p2; + } + } + + return result; + }); + + // ======================================== + // POLYGON + // ======================================== + + // geo.polygon(vertices, options) -> table of points (outline using Bresenham) + geoTable.set_function("polygon", [](sol::table vertices, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + // Parse vertices + std::vector> verts; + for (auto& kv : vertices) { + if (kv.second.get_type() == sol::type::table) { + sol::table pt = kv.second; + int x = pt.get_or("x", pt.get_or(1, 0)); + int y = pt.get_or("y", pt.get_or(2, 0)); + verts.push_back({x, y}); + } + } + + if (verts.size() < 3) { + return result; + } + + int index = 1; + + // Draw lines between consecutive vertices + for (size_t i = 0; i < verts.size(); ++i) { + int x1 = verts[i].first; + int y1 = verts[i].second; + int x2 = verts[(i + 1) % verts.size()].first; + int y2 = verts[(i + 1) % verts.size()].second; + + // Bresenham + int dx = std::abs(x2 - x1); + int dy = std::abs(y2 - y1); + int sx = (x1 < x2) ? 1 : -1; + int sy = (y1 < y2) ? 1 : -1; + int err = dx - dy; + + int x = x1, y = y1; + + while (true) { + sol::table point = lua.create_table(); + point["x"] = x; + point["y"] = y; + result[index++] = point; + + if (x == x2 && y == y2) break; + + int e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x += sx; + } + if (e2 < dx) { + err += dx; + y += sy; + } + } + } + + return result; + }); + + // ======================================== + // DISTANCE FUNCTIONS + // ======================================== + + // geo.distance(x1, y1, x2, y2) -> number (Euclidean distance) + geoTable.set_function("distance", [](float x1, float y1, float x2, float y2) -> float { + float dx = x2 - x1; + float dy = y2 - y1; + return std::sqrt(dx * dx + dy * dy); + }); + + // geo.distanceSq(x1, y1, x2, y2) -> number (Squared distance, faster) + geoTable.set_function("distanceSq", [](float x1, float y1, float x2, float y2) -> float { + float dx = x2 - x1; + float dy = y2 - y1; + return dx * dx + dy * dy; + }); + + // geo.distanceManhattan(x1, y1, x2, y2) -> number + geoTable.set_function("distanceManhattan", [](int x1, int y1, int x2, int y2) -> int { + return std::abs(x2 - x1) + std::abs(y2 - y1); + }); + + // geo.distanceChebyshev(x1, y1, x2, y2) -> number (King's move distance) + geoTable.set_function("distanceChebyshev", [](int x1, int y1, int x2, int y2) -> int { + return std::max(std::abs(x2 - x1), std::abs(y2 - y1)); + }); + + // ======================================== + // POINT IN SHAPE TESTS + // ======================================== + + // geo.pointInCircle(px, py, cx, cy, radius) -> boolean + geoTable.set_function("pointInCircle", [](float px, float py, float cx, float cy, float radius) -> bool { + float dx = px - cx; + float dy = py - cy; + return dx * dx + dy * dy <= radius * radius; + }); + + // geo.pointInRectangle(px, py, x1, y1, x2, y2) -> boolean + geoTable.set_function("pointInRectangle", [](float px, float py, float x1, float y1, float x2, float y2) -> bool { + float minX = std::min(x1, x2); + float maxX = std::max(x1, x2); + float minY = std::min(y1, y2); + float maxY = std::max(y1, y2); + return px >= minX && px <= maxX && py >= minY && py <= maxY; + }); + + // geo.pointInPolygon(px, py, vertices) -> boolean (Ray casting algorithm) + geoTable.set_function("pointInPolygon", [](float px, float py, sol::table vertices) -> bool { + std::vector> verts; + for (auto& kv : vertices) { + if (kv.second.get_type() == sol::type::table) { + sol::table pt = kv.second; + float x = pt.get_or("x", pt.get_or(1, 0.0f)); + float y = pt.get_or("y", pt.get_or(2, 0.0f)); + verts.push_back({x, y}); + } + } + + if (verts.size() < 3) return false; + + bool inside = false; + size_t n = verts.size(); + for (size_t i = 0, j = n - 1; i < n; j = i++) { + float xi = verts[i].first, yi = verts[i].second; + float xj = verts[j].first, yj = verts[j].second; + + if (((yi > py) != (yj > py)) && + (px < (xj - xi) * (py - yi) / (yj - yi) + xi)) { + inside = !inside; + } + } + + return inside; + }); + + // ======================================== + // RANDOM SCATTER + // ======================================== + + // geo.randomScatter(x1, y1, x2, y2, count, options) -> table of points + // Scatter random points in a region (useful for doodad placement) + geoTable.set_function("randomScatter", [](int x1, int y1, int x2, int y2, int count, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + int seed = static_cast(time(nullptr)); + int minDistance = 0; + + if (options) { + sol::table opts = *options; + seed = opts.get_or("seed", seed); + minDistance = opts.get_or("minDistance", 0); + } + + std::mt19937 rng(seed); + std::uniform_int_distribution distX(std::min(x1, x2), std::max(x1, x2)); + std::uniform_int_distribution distY(std::min(y1, y2), std::max(y1, y2)); + + std::vector> points; + + int attempts = 0; + int maxAttempts = count * 100; + + while (points.size() < static_cast(count) && attempts < maxAttempts) { + attempts++; + int x = distX(rng); + int y = distY(rng); + + // Check minimum distance + bool valid = true; + if (minDistance > 0) { + for (const auto& p : points) { + int dx = x - p.first; + int dy = y - p.second; + if (dx * dx + dy * dy < minDistance * minDistance) { + valid = false; + break; + } + } + } + + if (valid) { + points.push_back({x, y}); + } + } + + for (size_t i = 0; i < points.size(); ++i) { + sol::table point = lua.create_table(); + point["x"] = points[i].first; + point["y"] = points[i].second; + result[i + 1] = point; + } + + return result; + }); + + // geo.poissonDiskSampling(x1, y1, x2, y2, minDistance, options) -> table of points + // Blue noise distribution (evenly spaced random points) + geoTable.set_function("poissonDiskSampling", [](int x1, int y1, int x2, int y2, float minDistance, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + int seed = static_cast(time(nullptr)); + int maxAttempts = 30; + + if (options) { + sol::table opts = *options; + seed = opts.get_or("seed", seed); + maxAttempts = opts.get_or("maxAttempts", 30); + } + + std::mt19937 rng(seed); + + int minX = std::min(x1, x2); + int maxX = std::max(x1, x2); + int minY = std::min(y1, y2); + int maxY = std::max(y1, y2); + + float width = static_cast(maxX - minX); + float height = static_cast(maxY - minY); + + float cellSize = minDistance / std::sqrt(2.0f); + int gridWidth = static_cast(std::ceil(width / cellSize)); + int gridHeight = static_cast(std::ceil(height / cellSize)); + + std::vector> grid(gridHeight, std::vector(gridWidth, -1)); + std::vector> points; + std::vector activeList; + + // Start with first point + std::uniform_real_distribution distW(0, width); + std::uniform_real_distribution distH(0, height); + + float firstX = distW(rng); + float firstY = distH(rng); + points.push_back({firstX, firstY}); + activeList.push_back(0); + + int gx = static_cast(firstX / cellSize); + int gy = static_cast(firstY / cellSize); + if (gx >= 0 && gx < gridWidth && gy >= 0 && gy < gridHeight) { + grid[gy][gx] = 0; + } + + std::uniform_real_distribution dist01(0, 1); + + while (!activeList.empty()) { + size_t randIndex = rng() % activeList.size(); + size_t pointIndex = activeList[randIndex]; + auto& point = points[pointIndex]; + + bool found = false; + + for (int k = 0; k < maxAttempts; ++k) { + float angle = dist01(rng) * 2 * 3.14159265f; + float r = minDistance + dist01(rng) * minDistance; + + float newX = point.first + r * std::cos(angle); + float newY = point.second + r * std::sin(angle); + + if (newX < 0 || newX >= width || newY < 0 || newY >= height) continue; + + int ngx = static_cast(newX / cellSize); + int ngy = static_cast(newY / cellSize); + + bool valid = true; + + // Check neighbors + for (int dy = -2; dy <= 2 && valid; ++dy) { + for (int dx = -2; dx <= 2 && valid; ++dx) { + int cx = ngx + dx; + int cy = ngy + dy; + if (cx >= 0 && cx < gridWidth && cy >= 0 && cy < gridHeight) { + int neighborIdx = grid[cy][cx]; + if (neighborIdx >= 0) { + auto& neighbor = points[neighborIdx]; + float ddx = newX - neighbor.first; + float ddy = newY - neighbor.second; + if (ddx * ddx + ddy * ddy < minDistance * minDistance) { + valid = false; + } + } + } + } + } + + if (valid) { + size_t newIdx = points.size(); + points.push_back({newX, newY}); + activeList.push_back(newIdx); + if (ngx >= 0 && ngx < gridWidth && ngy >= 0 && ngy < gridHeight) { + grid[ngy][ngx] = static_cast(newIdx); + } + found = true; + break; + } + } + + if (!found) { + activeList.erase(activeList.begin() + randIndex); + } + } + + for (size_t i = 0; i < points.size(); ++i) { + sol::table point = lua.create_table(); + point["x"] = static_cast(points[i].first) + minX; + point["y"] = static_cast(points[i].second) + minY; + result[i + 1] = point; + } + + return result; + }); + + lua["geo"] = geoTable; +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_geo.h b/source/lua/lua_api_geo.h new file mode 100644 index 000000000..ff807dad4 --- /dev/null +++ b/source/lua/lua_api_geo.h @@ -0,0 +1,28 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_GEO_H +#define RME_LUA_API_GEO_H + +#include "lua_api.h" + +namespace LuaAPI { + // Register geometry functions (bresenham line, bezier, flood fill, etc.) + void registerGeo(sol::state& lua); +} + +#endif // RME_LUA_API_GEO_H diff --git a/source/lua/lua_api_http.cpp b/source/lua/lua_api_http.cpp new file mode 100644 index 000000000..2ee0d656f --- /dev/null +++ b/source/lua/lua_api_http.cpp @@ -0,0 +1,455 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_http.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace LuaAPI { + +// StreamSession class for managing streaming HTTP requests +class StreamSession { +public: + StreamSession() : finished_(false), hasError_(false), statusCode_(0) {} + + void appendChunk(const std::string& chunk) { + std::lock_guard lock(mutex_); + chunks_.push(chunk); + cv_.notify_one(); + } + + std::string getNextChunk() { + std::lock_guard lock(mutex_); + if (chunks_.empty()) { + return ""; + } + std::string chunk = chunks_.front(); + chunks_.pop(); + return chunk; + } + + bool hasChunks() { + std::lock_guard lock(mutex_); + return !chunks_.empty(); + } + + void setFinished() { + std::lock_guard lock(mutex_); + finished_ = true; + cv_.notify_all(); + } + + bool isFinished() { + std::lock_guard lock(mutex_); + return finished_; + } + + void setError(const std::string& error) { + std::lock_guard lock(mutex_); + hasError_ = true; + errorMessage_ = error; + finished_ = true; + cv_.notify_all(); + } + + bool hasError() { + std::lock_guard lock(mutex_); + return hasError_; + } + + std::string getError() { + std::lock_guard lock(mutex_); + return errorMessage_; + } + + void setStatusCode(int code) { + statusCode_ = code; + } + + int getStatusCode() { + return statusCode_; + } + + void setHeaders(const cpr::Header& headers) { + std::lock_guard lock(mutex_); + responseHeaders_ = headers; + } + + cpr::Header getHeaders() { + std::lock_guard lock(mutex_); + return responseHeaders_; + } + +private: + std::queue chunks_; + std::mutex mutex_; + std::condition_variable cv_; + std::atomic finished_; + std::atomic hasError_; + std::string errorMessage_; + std::atomic statusCode_; + cpr::Header responseHeaders_; +}; + +// Global map to store active stream sessions +static std::map> g_streamSessions; +static std::mutex g_sessionsMutex; +static int g_nextSessionId = 1; + +// HTTP GET request +static sol::table httpGet(sol::this_state ts, const std::string& url, sol::optional optHeaders) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); + + cpr::Header headers; + if (optHeaders) { + sol::table headersTable = *optHeaders; + for (auto& pair : headersTable) { + if (pair.first.is() && pair.second.is()) { + headers[pair.first.as()] = pair.second.as(); + } + } + } + + cpr::Response response = cpr::Get(cpr::Url{url}, headers); + + result["status"] = static_cast(response.status_code); + result["body"] = response.text; + result["error"] = response.error.message; + result["ok"] = response.status_code >= 200 && response.status_code < 300; + + // Parse response headers + sol::table respHeaders = lua.create_table(); + for (const auto& h : response.header) { + respHeaders[h.first] = h.second; + } + result["headers"] = respHeaders; + + return result; +} + +// HTTP POST request +static sol::table httpPost(sol::this_state ts, const std::string& url, const std::string& body, sol::optional optHeaders) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); + + cpr::Header headers; + if (optHeaders) { + sol::table headersTable = *optHeaders; + for (auto& pair : headersTable) { + if (pair.first.is() && pair.second.is()) { + headers[pair.first.as()] = pair.second.as(); + } + } + } + + cpr::Response response = cpr::Post( + cpr::Url{url}, + cpr::Body{body}, + headers + ); + + result["status"] = static_cast(response.status_code); + result["body"] = response.text; + result["error"] = response.error.message; + result["ok"] = response.status_code >= 200 && response.status_code < 300; + + // Parse response headers + sol::table respHeaders = lua.create_table(); + for (const auto& h : response.header) { + respHeaders[h.first] = h.second; + } + result["headers"] = respHeaders; + + return result; +} + +// Helper function to convert Lua table to JSON +static std::function getLuaToJsonConverter() { + std::function luaToJson; + luaToJson = [&luaToJson](sol::object obj) -> nlohmann::json { + if (obj.is()) { + return obj.as(); + } else if (obj.is()) { + return obj.as(); + } else if (obj.is()) { + return obj.as(); + } else if (obj.is()) { + return obj.as(); + } else if (obj.is()) { + sol::table tbl = obj.as(); + + // Check if it's an array (sequential integer keys starting at 1) + bool isArray = true; + size_t expectedKey = 1; + for (auto& pair : tbl) { + if (!pair.first.is() || pair.first.as() != expectedKey) { + isArray = false; + break; + } + expectedKey++; + } + + if (isArray && expectedKey > 1) { + nlohmann::json arr = nlohmann::json::array(); + for (auto& pair : tbl) { + arr.push_back(luaToJson(pair.second)); + } + return arr; + } else { + nlohmann::json jsonObj = nlohmann::json::object(); + for (auto& pair : tbl) { + std::string key; + if (pair.first.is()) { + key = pair.first.as(); + } else if (pair.first.is()) { + key = std::to_string(pair.first.as()); + } else { + continue; + } + jsonObj[key] = luaToJson(pair.second); + } + return jsonObj; + } + } else if (obj.is()) { + return nullptr; + } + return nullptr; + }; + return luaToJson; +} + +// HTTP POST with JSON body +static sol::table httpPostJson(sol::this_state ts, const std::string& url, sol::table jsonBody, sol::optional optHeaders) { + sol::state_view lua(ts); + + auto luaToJson = getLuaToJsonConverter(); + std::string jsonStr = luaToJson(jsonBody).dump(); + + // Add Content-Type header if not present + sol::table headers = lua.create_table(); + headers["Content-Type"] = "application/json"; + + if (optHeaders) { + for (auto& pair : *optHeaders) { + if (pair.first.is() && pair.second.is()) { + headers[pair.first.as()] = pair.second.as(); + } + } + } + + return httpPost(ts, url, jsonStr, headers); +} + +// Start a streaming POST request - returns session ID +static sol::table httpPostStream(sol::this_state ts, const std::string& url, const std::string& body, sol::optional optHeaders) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); + + auto session = std::make_shared(); + int sessionId; + + { + std::lock_guard lock(g_sessionsMutex); + sessionId = g_nextSessionId++; + g_streamSessions[sessionId] = session; + } + + cpr::Header headers; + if (optHeaders) { + sol::table headersTable = *optHeaders; + for (auto& pair : headersTable) { + if (pair.first.is() && pair.second.is()) { + headers[pair.first.as()] = pair.second.as(); + } + } + } + + // Start the streaming request in a separate thread + std::thread([session, url, body, headers]() { + std::function writeCallback = [session](std::string_view data, intptr_t /*userdata*/) -> bool { + session->appendChunk(std::string(data)); + return true; + }; + + cpr::Response response = cpr::Post( + cpr::Url{url}, + cpr::Body{body}, + headers, + cpr::WriteCallback{writeCallback, 0} + ); + + session->setStatusCode(static_cast(response.status_code)); + session->setHeaders(response.header); + + if (response.error) { + session->setError(response.error.message); + } else { + session->setFinished(); + } + }).detach(); + + result["sessionId"] = sessionId; + result["ok"] = true; + + return result; +} + +// Start a streaming POST request with JSON body - returns session ID +static sol::table httpPostJsonStream(sol::this_state ts, const std::string& url, sol::table jsonBody, sol::optional optHeaders) { + sol::state_view lua(ts); + + auto luaToJson = getLuaToJsonConverter(); + std::string jsonStr = luaToJson(jsonBody).dump(); + + // Add Content-Type header if not present + sol::table headers = lua.create_table(); + headers["Content-Type"] = "application/json"; + + if (optHeaders) { + for (auto& pair : *optHeaders) { + if (pair.first.is() && pair.second.is()) { + headers[pair.first.as()] = pair.second.as(); + } + } + } + + return httpPostStream(ts, url, jsonStr, headers); +} + +// Read available chunks from a stream session +static sol::table httpStreamRead(sol::this_state ts, int sessionId) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); + + std::shared_ptr session; + { + std::lock_guard lock(g_sessionsMutex); + auto it = g_streamSessions.find(sessionId); + if (it == g_streamSessions.end()) { + result["ok"] = false; + result["error"] = "Invalid session ID"; + result["finished"] = true; + return result; + } + session = it->second; + } + + // Collect all available chunks + std::string data; + while (session->hasChunks()) { + data += session->getNextChunk(); + } + + result["data"] = data; + result["finished"] = session->isFinished(); + result["hasError"] = session->hasError(); + + if (session->hasError()) { + result["error"] = session->getError(); + result["ok"] = false; + } else { + result["ok"] = true; + } + + if (session->isFinished()) { + result["status"] = session->getStatusCode(); + + // Return headers when finished + sol::table respHeaders = lua.create_table(); + for (const auto& h : session->getHeaders()) { + respHeaders[h.first] = h.second; + } + result["headers"] = respHeaders; + } + + return result; +} + +// Close and cleanup a stream session +static bool httpStreamClose(int sessionId) { + std::lock_guard lock(g_sessionsMutex); + auto it = g_streamSessions.find(sessionId); + if (it != g_streamSessions.end()) { + g_streamSessions.erase(it); + return true; + } + return false; +} + +// Check if a stream session is finished +static sol::table httpStreamStatus(sol::this_state ts, int sessionId) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); + + std::shared_ptr session; + { + std::lock_guard lock(g_sessionsMutex); + auto it = g_streamSessions.find(sessionId); + if (it == g_streamSessions.end()) { + result["valid"] = false; + result["finished"] = true; + return result; + } + session = it->second; + } + + result["valid"] = true; + result["finished"] = session->isFinished(); + result["hasError"] = session->hasError(); + result["hasData"] = session->hasChunks(); + + if (session->hasError()) { + result["error"] = session->getError(); + } + + if (session->isFinished()) { + result["status"] = session->getStatusCode(); + } + + return result; +} + + + +void registerHttp(sol::state& lua) { + sol::table http = lua.create_named_table("http"); + + // Basic HTTP methods + http["get"] = httpGet; + http["post"] = httpPost; + http["postJson"] = httpPostJson; + + // Streaming HTTP methods + http["postStream"] = httpPostStream; + http["postJsonStream"] = httpPostJsonStream; + http["streamRead"] = httpStreamRead; + http["streamClose"] = httpStreamClose; + http["streamStatus"] = httpStreamStatus; +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_http.h b/source/lua/lua_api_http.h new file mode 100644 index 000000000..7671c3e98 --- /dev/null +++ b/source/lua/lua_api_http.h @@ -0,0 +1,30 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_HTTP_H +#define RME_LUA_API_HTTP_H + +#define SOL_ALL_SAFETIES_ON 1 +#include + +namespace LuaAPI { + +void registerHttp(sol::state& lua); + +} // namespace LuaAPI + +#endif // RME_LUA_API_HTTP_H diff --git a/source/lua/lua_api_image.cpp b/source/lua/lua_api_image.cpp new file mode 100644 index 000000000..e2ba9c3ef --- /dev/null +++ b/source/lua/lua_api_image.cpp @@ -0,0 +1,277 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_image.h" +#include "../gui.h" +#include "../graphics.h" +#include "../items.h" + +namespace LuaAPI { + +LuaImage::LuaImage() : spriteId(0), spriteSource(false) { + // Empty image +} + +LuaImage::LuaImage(const std::string& path) : filePath(path), spriteId(0), spriteSource(false) { + if (!path.empty()) { + image.LoadFile(wxString(path)); + } +} + +LuaImage::LuaImage(int id, bool isItemSprite) : spriteId(id), spriteSource(true) { + if (isItemSprite) { + // Get sprite ID from item type + if (g_items.typeExists(id)) { + ItemType& itemType = g_items.getItemType(id); + if (itemType.id != 0) { + loadFromSpriteId(itemType.clientID); + } + } + } else { + loadFromSpriteId(id); + } +} + +LuaImage::LuaImage(const LuaImage& other) : + image(other.image.IsOk() ? other.image.Copy() : wxImage()), + filePath(other.filePath), + spriteId(other.spriteId), + spriteSource(other.spriteSource) { +} + +LuaImage::~LuaImage() { + // wxImage handles its own cleanup +} + +LuaImage LuaImage::loadFromFile(const std::string& path) { + return LuaImage(path); +} + +LuaImage LuaImage::loadFromItemSprite(int itemId) { + return LuaImage(itemId, true); +} + +LuaImage LuaImage::loadFromSprite(int spriteId) { + return LuaImage(spriteId, false); +} + +// Helper to reliably get sprite ID from item ID, handling client ID mapping +int getItemSpriteId(int itemId) { + if (!g_items.typeExists(itemId)) return 0; + ItemType& itemType = g_items.getItemType(itemId); + return itemType.clientID; +} + +void LuaImage::loadFromSpriteId(int id) { + Sprite* sprite = g_gui.gfx.getSprite(id); + if (!sprite) { + return; + } + + GameSprite* gameSprite = dynamic_cast(sprite); + if (gameSprite && gameSprite->width > 0 && gameSprite->height > 0) { + // Calculate full sprite size (can be larger than 32x32 for multi-tile sprites) + int spriteWidth = gameSprite->width * 32; + int spriteHeight = gameSprite->height * 32; + + // Create image with alpha channel + image.Create(spriteWidth, spriteHeight); + image.InitAlpha(); + + // Fill with transparent background + unsigned char* imgData = image.GetData(); + unsigned char* alphaData = image.GetAlpha(); + memset(imgData, 0, spriteWidth * spriteHeight * 3); + memset(alphaData, 0, spriteWidth * spriteHeight); // Fully transparent + + // Get sprite data for each part + for (int y = 0; y < gameSprite->height; ++y) { + for (int x = 0; x < gameSprite->width; ++x) { + int spriteIndex = gameSprite->getIndex(x, y, 0, 0, 0, 0, 0); + if (spriteIndex >= 0 && spriteIndex < (int)gameSprite->spriteList.size()) { + auto* normalImage = gameSprite->spriteList[spriteIndex]; + if (normalImage) { + uint8_t* rgbaData = normalImage->getRGBAData(); + if (rgbaData) { + // Copy pixel data to the correct position + int destX = (gameSprite->width - 1 - x) * 32; + int destY = (gameSprite->height - 1 - y) * 32; + + for (int py = 0; py < 32; ++py) { + for (int px = 0; px < 32; ++px) { + int srcIdx = (py * 32 + px) * 4; + int destIdx = (destY + py) * spriteWidth + (destX + px); + + // Only copy non-transparent pixels + uint8_t alpha = rgbaData[srcIdx + 3]; + if (alpha > 0) { + imgData[destIdx * 3 + 0] = rgbaData[srcIdx + 0]; // R + imgData[destIdx * 3 + 1] = rgbaData[srcIdx + 1]; // G + imgData[destIdx * 3 + 2] = rgbaData[srcIdx + 2]; // B + alphaData[destIdx] = alpha; + } + } + } + } + } + } + } + } + return; + } + + // Fallback: use DC-based rendering + wxBitmap bmp(32, 32, 32); + wxMemoryDC dc(bmp); + dc.SetBackground(*wxWHITE_BRUSH); + dc.Clear(); + sprite->DrawTo(&dc, SPRITE_SIZE_32x32, 0, 0, 32, 32); + dc.SelectObject(wxNullBitmap); + image = bmp.ConvertToImage(); +} + +int LuaImage::getWidth() const { + return image.IsOk() ? image.GetWidth() : 0; +} + +int LuaImage::getHeight() const { + return image.IsOk() ? image.GetHeight() : 0; +} + +bool LuaImage::isValid() const { + return image.IsOk(); +} + +LuaImage LuaImage::resize(int width, int height, bool smooth) const { + LuaImage result; + if (image.IsOk() && width > 0 && height > 0) { + wxImageResizeQuality quality = smooth ? wxIMAGE_QUALITY_HIGH : wxIMAGE_QUALITY_NEAREST; + result.image = image.Scale(width, height, quality); + result.filePath = filePath; + result.spriteId = spriteId; + result.spriteSource = spriteSource; + } + return result; +} + +LuaImage LuaImage::scale(double factor, bool smooth) const { + if (factor <= 0 || !image.IsOk()) { + return LuaImage(); + } + int newWidth = static_cast(image.GetWidth() * factor); + int newHeight = static_cast(image.GetHeight() * factor); + return resize(newWidth, newHeight, smooth); +} + +wxBitmap LuaImage::getBitmap() const { + if (!image.IsOk()) { + return wxNullBitmap; + } + return wxBitmap(image); +} + +wxBitmap LuaImage::getBitmap(int width, int height, bool smooth) const { + if (!image.IsOk() || width <= 0 || height <= 0) { + return wxNullBitmap; + } + wxImageResizeQuality quality = smooth ? wxIMAGE_QUALITY_HIGH : wxIMAGE_QUALITY_NEAREST; + wxImage scaled = image.Scale(width, height, quality); + return wxBitmap(scaled); +} + +bool LuaImage::operator==(const LuaImage& other) const { + // Compare by path if file-based, or sprite ID if sprite-based + if (spriteSource && other.spriteSource) { + return spriteId == other.spriteId; + } + if (!spriteSource && !other.spriteSource) { + return filePath == other.filePath; + } + return false; +} + +void registerImage(sol::state& lua) { + // Register LuaImage as "Image" usertype + lua.new_usertype("Image", + // Constructors + sol::constructors< + LuaImage(), + LuaImage(const std::string&) + >(), + + // Alternative constructor from table: Image{path = "..."} or Image{itemid = 100} + sol::call_constructor, sol::factories( + // Default constructor + []() { return LuaImage(); }, + // Path constructor + [](const std::string& path) { return LuaImage(path); }, + // Table constructor + [](sol::table t) { + if (t["path"].valid()) { + return LuaImage::loadFromFile(t.get("path")); + } + if (t["itemid"].valid()) { + return LuaImage::loadFromItemSprite(t.get("itemid")); + } + if (t["spriteid"].valid()) { + return LuaImage::loadFromSprite(t.get("spriteid")); + } + return LuaImage(); + } + ), + + // Static factory methods + "fromFile", &LuaImage::loadFromFile, + "fromItemSprite", &LuaImage::loadFromItemSprite, + "fromSprite", &LuaImage::loadFromSprite, + + // Properties (read-only) + "width", sol::property(&LuaImage::getWidth), + "height", sol::property(&LuaImage::getHeight), + "valid", sol::property(&LuaImage::isValid), + "path", sol::property(&LuaImage::getPath), + "spriteId", sol::property(&LuaImage::getSpriteId), + "isFromSprite", sol::property(&LuaImage::isSpriteSource), + + // Methods + "resize", [](const LuaImage& img, int w, int h, sol::optional smooth) { + return img.resize(w, h, smooth.value_or(true)); + }, + "scale", [](const LuaImage& img, double factor, sol::optional smooth) { + return img.scale(factor, smooth.value_or(true)); + }, + + // Equality comparison + sol::meta_function::equal_to, &LuaImage::operator==, + + // String representation + sol::meta_function::to_string, [](const LuaImage& img) { + if (!img.isValid()) { + return std::string("Image(invalid)"); + } + if (img.isSpriteSource()) { + return "Image(sprite=" + std::to_string(img.getSpriteId()) + + ", " + std::to_string(img.getWidth()) + "x" + std::to_string(img.getHeight()) + ")"; + } + return "Image(\"" + img.getPath() + "\", " + + std::to_string(img.getWidth()) + "x" + std::to_string(img.getHeight()) + ")"; + } + ); +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_image.h b/source/lua/lua_api_image.h new file mode 100644 index 000000000..f18400a8d --- /dev/null +++ b/source/lua/lua_api_image.h @@ -0,0 +1,86 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_IMAGE_H +#define RME_LUA_API_IMAGE_H + +#define SOL_ALL_SAFETIES_ON 1 +#include +#include +#include + +namespace LuaAPI { + +// LuaImage class for Lua scripting +// Supports loading external images and game item sprites +class LuaImage { +public: + // Default constructor (empty image) + LuaImage(); + + // Constructor from file path + explicit LuaImage(const std::string& path); + + // Constructor from item sprite ID + LuaImage(int spriteId, bool isItemSprite); + + // Copy constructor + LuaImage(const LuaImage& other); + + ~LuaImage(); + + // Static factory methods for Lua + static LuaImage loadFromFile(const std::string& path); + static LuaImage loadFromItemSprite(int itemId); + static LuaImage loadFromSprite(int spriteId); + + // Properties + int getWidth() const; + int getHeight() const; + bool isValid() const; + std::string getPath() const { return filePath; } + int getSpriteId() const { return spriteId; } + bool isSpriteSource() const { return spriteSource; } + + // Operations + LuaImage resize(int width, int height, bool smooth = true) const; + LuaImage scale(double factor, bool smooth = true) const; + + // Get the underlying wxImage (for internal use) + const wxImage& getWxImage() const { return image; } + wxBitmap getBitmap() const; + wxBitmap getBitmap(int width, int height, bool smooth = true) const; + + // Equality comparison + bool operator==(const LuaImage& other) const; + +private: + wxImage image; + std::string filePath; + int spriteId = 0; + bool spriteSource = false; + + // Load image from game sprite + void loadFromSpriteId(int id); +}; + +// Register the Image usertype with Lua +void registerImage(sol::state& lua); + +} // namespace LuaAPI + +#endif // RME_LUA_API_IMAGE_H diff --git a/source/lua/lua_api_item.cpp b/source/lua/lua_api_item.cpp new file mode 100644 index 000000000..3c2c20d66 --- /dev/null +++ b/source/lua/lua_api_item.cpp @@ -0,0 +1,253 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_item.h" +#include "../item.h" +#include "../items.h" + +#include +#include + +namespace LuaAPI { + +// Helper: convert string to lowercase +static std::string toLower(const std::string& str) { + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { return std::tolower(c); }); + return result; +} + +// Helper: check if string contains another (case-insensitive) +static bool containsIgnoreCase(const std::string& haystack, const std::string& needle) { + std::string lowerHaystack = toLower(haystack); + std::string lowerNeedle = toLower(needle); + return lowerHaystack.find(lowerNeedle) != std::string::npos; +} + +void registerItem(sol::state& lua) { + // Register Item usertype + lua.new_usertype("Item", + // No public constructor - items are created via Tile:addItem() or obtained from tiles + sol::no_constructor, + + // Read-only properties + "id", sol::property(&Item::getID), + "clientId", sol::property(&Item::getClientID), + "name", sol::property(&Item::getName), + "fullName", sol::property(&Item::getFullName), + + // Read/write properties + "count", sol::property( + &Item::getCount, + [](Item& item, int count) { + item.setSubtype(static_cast(count)); + } + ), + "subtype", sol::property( + [](const Item& item) -> int { return item.getSubtype(); }, + [](Item& item, int subtype) { item.setSubtype(static_cast(subtype)); } + ), + "actionId", sol::property( + [](const Item& item) -> int { return item.getActionID(); }, + [](Item& item, int aid) { item.setActionID(static_cast(aid)); } + ), + "uniqueId", sol::property( + [](const Item& item) -> int { return item.getUniqueID(); }, + [](Item& item, int uid) { item.setUniqueID(static_cast(uid)); } + ), + "tier", sol::property( + [](const Item& item) -> int { return item.getTier(); }, + [](Item& item, int tier) { item.setTier(static_cast(tier)); } + ), + "text", sol::property(&Item::getText, &Item::setText), + "description", sol::property(&Item::getDescription, &Item::setDescription), + + // Selection + "isSelected", sol::property(&Item::isSelected), + "select", &Item::select, + "deselect", &Item::deselect, + + // Type checks (read-only) + "isStackable", sol::property(&Item::isStackable), + "isMoveable", sol::property(&Item::isMoveable), + "isPickupable", sol::property(&Item::isPickupable), + "isBlocking", sol::property(&Item::isBlocking), + "isGroundTile", sol::property(&Item::isGroundTile), + "isBorder", sol::property(&Item::isBorder), + "isWall", sol::property(&Item::isWall), + "isDoor", sol::property(&Item::isDoor), + "isTable", sol::property(&Item::isTable), + "isCarpet", sol::property(&Item::isCarpet), + "isHangable", sol::property(&Item::isHangable), + "isRoteable", sol::property(&Item::isRoteable), + "isFluidContainer", sol::property(&Item::isFluidContainer), + "isSplash", sol::property(&Item::isSplash), + "hasCharges", sol::property(&Item::hasCharges), + "hasElevation", sol::property([](const Item& item) { + return g_items[item.getID()].hasElevation; + }), + "zOrder", sol::property(&Item::getTopOrder), + + // Methods + "clone", [](const Item& item) -> Item* { + return item.deepCopy(); + }, + "rotate", &Item::doRotate, + + + "getName", [](int id) -> std::string { + if (g_items.typeExists(id)) { + return g_items.getItemType(id).name; + } + return ""; + }, + "getDescription", [](int id) -> std::string { + if (g_items.typeExists(id)) { + return g_items.getItemType(id).description; + } + return ""; + }, + + // String representation + sol::meta_function::to_string, [](const Item& item) { + return "Item(id=" + std::to_string(item.getID()) + + ", name=\"" + item.getName() + "\")"; + } + ); + + // Register Items namespace for item lookup + sol::table items = lua.create_named_table("Items"); + + // Get item type info by ID + items["get"] = [](int id) -> sol::optional { + if (!g_items.typeExists(id)) { + return sol::nullopt; + } + // Return item info as table (not the actual ItemType pointer) + return sol::nullopt; // We'll use getInfo instead + }; + + // Get item info by ID - returns a table with item properties + items["getInfo"] = [](sol::this_state ts, int id) -> sol::object { + sol::state_view lua(ts); + if (!g_items.typeExists(id)) { + return sol::nil; + } + ItemType& it = g_items.getItemType(id); + sol::table info = lua.create_table(); + info["id"] = it.id; + info["clientId"] = it.clientID; + info["name"] = it.name; + info["description"] = it.description; + info["isStackable"] = it.stackable; + info["isMoveable"] = it.moveable; + info["isPickupable"] = it.pickupable; + info["isGroundTile"] = it.isGroundTile(); + info["isBorder"] = it.isBorder; + info["isWall"] = it.isWall; + info["isDoor"] = it.isDoor(); + info["isTable"] = it.isTable; + info["isCarpet"] = it.isCarpet; + info["hasElevation"] = it.hasElevation; + return info; + }; + + // Check if item ID exists + items["exists"] = [](int id) -> bool { + return g_items.typeExists(id); + }; + + // Get max item ID + items["getMaxId"] = []() -> int { + return g_items.getMaxID(); + }; + + // Find items by name (returns array of {id, name} tables) + // Searches for items whose name contains the search string (case-insensitive) + items["findByName"] = [](sol::this_state ts, const std::string& searchName, sol::optional maxResults) -> sol::table { + sol::state_view lua(ts); + sol::table results = lua.create_table(); + + int limit = maxResults.value_or(50); // Default max 50 results + int count = 0; + int maxId = g_items.getMaxID(); + + std::string searchLower = toLower(searchName); + + for (int id = 1; id <= maxId && count < limit; ++id) { + if (!g_items.typeExists(id)) continue; + + ItemType& it = g_items.getItemType(id); + if (it.name.empty()) continue; + + // Check if name contains search string + if (containsIgnoreCase(it.name, searchName)) { + sol::table item = lua.create_table(); + item["id"] = it.id; + item["name"] = it.name; + results[++count] = item; + } + } + + return results; + }; + + // Find first item matching name exactly (case-insensitive) + // Returns item ID or nil + items["findIdByName"] = [](const std::string& searchName) -> sol::optional { + std::string searchLower = toLower(searchName); + int maxId = g_items.getMaxID(); + + // First pass: exact match + for (int id = 1; id <= maxId; ++id) { + if (!g_items.typeExists(id)) continue; + + ItemType& it = g_items.getItemType(id); + if (it.name.empty()) continue; + + if (toLower(it.name) == searchLower) { + return id; + } + } + + // Second pass: contains match (return first) + for (int id = 1; id <= maxId; ++id) { + if (!g_items.typeExists(id)) continue; + + ItemType& it = g_items.getItemType(id); + if (it.name.empty()) continue; + + if (containsIgnoreCase(it.name, searchName)) { + return id; + } + } + + return sol::nullopt; + }; + + // Get item name by ID + items["getName"] = [](int id) -> std::string { + if (g_items.typeExists(id)) { + return g_items.getItemType(id).name; + } + return ""; + }; +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_item.h b/source/lua/lua_api_item.h new file mode 100644 index 000000000..7eedb317c --- /dev/null +++ b/source/lua/lua_api_item.h @@ -0,0 +1,29 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_ITEM_H +#define RME_LUA_API_ITEM_H + +#define SOL_ALL_SAFETIES_ON 1 +#include + +namespace LuaAPI { + // Register the Item usertype with Lua + void registerItem(sol::state& lua); +} + +#endif // RME_LUA_API_ITEM_H diff --git a/source/lua/lua_api_json.cpp b/source/lua/lua_api_json.cpp new file mode 100644 index 000000000..81e0a1076 --- /dev/null +++ b/source/lua/lua_api_json.cpp @@ -0,0 +1,172 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_json.h" +#include "../json/json_spirit.h" + +namespace LuaAPI { + +// Forward declarations +sol::object valueToLua(const json_spirit::mValue& val, sol::state_view& lua); +json_spirit::mValue luaToValue(const sol::object& obj); + +// Convert JSON Spirit Value to Lua Object +sol::object valueToLua(const json_spirit::mValue& val, sol::state_view& lua) { + switch (val.type()) { + case json_spirit::null_type: + return sol::nil; + case json_spirit::bool_type: + return sol::make_object(lua, val.get_bool()); + case json_spirit::int_type: + return sol::make_object(lua, val.get_int()); + case json_spirit::real_type: + return sol::make_object(lua, val.get_real()); + case json_spirit::str_type: + return sol::make_object(lua, val.get_str()); + case json_spirit::array_type: { + sol::table t = lua.create_table(); + const json_spirit::mArray& arr = val.get_array(); + for (size_t i = 0; i < arr.size(); ++i) { + t[i + 1] = valueToLua(arr[i], lua); // Lua 1-based indexing + } + return t; + } + case json_spirit::obj_type: { + sol::table t = lua.create_table(); + const json_spirit::mObject& obj = val.get_obj(); + for (const auto& pair : obj) { + t[pair.first] = valueToLua(pair.second, lua); + } + return t; + } + } + return sol::nil; +} + +// Convert Lua Object to JSON Spirit Value +json_spirit::mValue luaToValue(const sol::object& obj) { + switch (obj.get_type()) { + case sol::type::nil: + return json_spirit::mValue(); // null + case sol::type::boolean: + return json_spirit::mValue(obj.as()); + case sol::type::number: + // check if int or double, but Lua 5.1/JIT uses double mostly. + // json_spirit distinguishes. + // Let's just use double if floating point, int otherwise? + // Simplification: use double for safety or check floor + { + double d = obj.as(); + if (d == (int64_t)d) { + return json_spirit::mValue((int64_t)d); + } + return json_spirit::mValue(d); + } + case sol::type::string: + return json_spirit::mValue(obj.as()); + case sol::type::table: { + sol::table t = obj.as(); + // Determine if it's an array or object + // Algorithm: allow explicit "is_array"? or check keys. + // For now, check if it has key 1. If so, treat as array? + // Or check length #t > 0. + // Robust check: iterate keys. If all are integers 1..N, it's an array. + + bool isArray = true; + size_t maxKey = 0; + size_t count = 0; + + for (auto& pair : t) { + count++; + if (pair.first.get_type() == sol::type::number) { + double k = pair.first.as(); + if (k >= 1 && k == (size_t)k) { + size_t idx = (size_t)k; + if (idx > maxKey) maxKey = idx; + } else { + isArray = false; + break; + } + } else { + isArray = false; + break; + } + } + + if (isArray && count > 0) { + // We also check sparse arrays? + // If maxKey == count, it's a dense array. + if (maxKey != count) { + // Sparse array or holes... treat as object with string keys? + // Or just treat as sparse array? json_spirit doesn't support sparse arrays directly + // standard JSON doesn't either (uses nulls). + // Let's assume non-sparse for array. + // If holes, better fail to object string keys. + isArray = false; + } + } + + // Empty table {} -> Object by default in many JSON libs, or Array? + // Lua {} is ambiguous. Let's default to Object (empty dict) as it's safer. + if (count == 0) isArray = false; + + if (isArray) { + json_spirit::mArray arr; + arr.reserve(count); + for (size_t i = 1; i <= count; ++i) { + if (t[i].valid()) + arr.push_back(luaToValue(t[i])); + else + arr.push_back(json_spirit::mValue()); // null + } + return json_spirit::mValue(arr); + } else { + json_spirit::mObject objVal; + for (auto& pair : t) { + std::string key; + if (pair.first.get_type() == sol::type::string) { + key = pair.first.as(); + } else if (pair.first.get_type() == sol::type::number) { + key = std::to_string(pair.first.as()); // Convert number keys to string for JSON object + } else { + continue; // Skip function keys etc + } + objVal[key] = luaToValue(pair.second); + } + return json_spirit::mValue(objVal); + } + } + default: + return json_spirit::mValue(); // null/ignore userdata/functions + } +} + +void registerJson(sol::state& lua) { + // Create "json" table + sol::table jsonTable = lua.create_table(); + + jsonTable.set_function("encode", [](sol::object obj, sol::this_state s) -> std::string { + json_spirit::mValue val = luaToValue(obj); + return json_spirit::write(val); // minified output + }); + + jsonTable.set_function("encode_pretty", [](sol::object obj, sol::this_state s) -> std::string { + json_spirit::mValue val = luaToValue(obj); + return json_spirit::write_formatted(val); // pretty-printed output + }); + + jsonTable.set_function("decode", [](std::string jsonStr, sol::this_state s) -> sol::object { + json_spirit::mValue val; + if (json_spirit::read(jsonStr, val)) { + sol::state_view lua(s); + return valueToLua(val, lua); + } + return sol::nil; + }); + + lua["json"] = jsonTable; +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_json.h b/source/lua/lua_api_json.h new file mode 100644 index 000000000..a8ab4b6d2 --- /dev/null +++ b/source/lua/lua_api_json.h @@ -0,0 +1,16 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// + +#ifndef LUA_API_JSON_H +#define LUA_API_JSON_H + +#include + +namespace LuaAPI { + +void registerJson(sol::state& lua); + +} + +#endif diff --git a/source/lua/lua_api_map.cpp b/source/lua/lua_api_map.cpp new file mode 100644 index 000000000..856fa4032 --- /dev/null +++ b/source/lua/lua_api_map.cpp @@ -0,0 +1,192 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_map.h" +#include "../map.h" +#include "../basemap.h" +#include "../tile.h" +#include "../position.h" +#include "../gui.h" +#include "../editor.h" + +namespace LuaAPI { + +// Custom iterator for Map tiles to use in Lua for-loops +class LuaMapTileIterator { +public: + LuaMapTileIterator(Map* map) : map(map), started(false) { + if (map) { + iter = map->begin(); + endIter = map->end(); + } + } + + std::tuple next(sol::this_state ts) { + sol::state_view lua(ts); + + if (!map) { + return std::make_tuple(sol::nil, sol::nil); + } + + // Find next valid tile + while (iter != endIter) { + TileLocation* loc = *iter; + ++iter; + + if (loc && loc->get()) { + Tile* tile = loc->get(); + return std::make_tuple( + sol::make_object(lua, tile), + sol::make_object(lua, tile) + ); + } + } + + return std::make_tuple(sol::nil, sol::nil); + } + +private: + Map* map; + MapIterator iter; + MapIterator endIter; + bool started; +}; + +// Iterator for Spawns +class LuaMapSpawnIterator { +public: + LuaMapSpawnIterator(Map* map) : map(map) { + if (map) { + iter = map->spawns.begin(); + endIter = map->spawns.end(); + } + } + + Tile* next() { + if (!map) return nullptr; + + if (iter != endIter) { + Position pos = *iter; + ++iter; + return map->getTile(pos); + } + return nullptr; + } + +private: + Map* map; + SpawnPositionList::const_iterator iter; + SpawnPositionList::const_iterator endIter; +}; + +void registerMap(sol::state& lua) { + // Register the iterator type + lua.new_usertype("MapTileIterator", + sol::no_constructor, + "next", &LuaMapTileIterator::next + ); + + // Register Spawn iterator + lua.new_usertype("MapSpawnIterator", + sol::no_constructor, + "next", &LuaMapSpawnIterator::next + ); + + // Register Map usertype + lua.new_usertype("Map", + // No public constructor - maps are obtained from app.map + sol::no_constructor, + + // Properties (read-only) + "name", sol::property(&Map::getName), + "filename", sol::property(&Map::getFilename), + "description", sol::property(&Map::getMapDescription), + "width", sol::property(&Map::getWidth), + "height", sol::property(&Map::getHeight), + "houseFilename", sol::property(&Map::getHouseFilename), + "spawnFilename", sol::property(&Map::getSpawnFilename), + "hasFile", sol::property(&Map::hasFile), + "hasChanged", sol::property(&Map::hasChanged), + "tileCount", sol::property([](Map* map) -> uint64_t { + return map ? map->getTileCount() : 0; + }), + + + // Get tile methods + "getTile", sol::overload( + [](Map* map, int x, int y, int z) -> Tile* { + return map ? map->getTile(x, y, z) : nullptr; + }, + [](Map* map, const Position& pos) -> Tile* { + return map ? map->getTile(pos) : nullptr; + } + ), + + // Get or create tile (for adding content to empty positions) + "getOrCreateTile", [](Map* map, sol::variadic_args va) -> Tile* { + if (!map) return nullptr; + + Position pos; + if (va.size() == 1 && va[0].is()) { + pos = va[0].as(); + } else if (va.size() == 3) { + pos.x = va[0].as(); + pos.y = va[1].as(); + pos.z = va[2].as(); + } else { + throw sol::error("getOrCreateTile expects (x, y, z) or (Position)"); + } + + return map->getOrCreateTile(pos); + }, + + // Tiles iterator - allows: for tile in map.tiles do ... end + "tiles", sol::property([](Map* map, sol::this_state ts) { + sol::state_view lua(ts); + + // Return an iterator function + auto iterator = std::make_shared(map); + + // Return the iterator function that Lua will call repeatedly + return sol::make_object(lua, [iterator](sol::this_state ts) { + return iterator->next(ts); + }); + }), + + // Spawns iterator - allows: for tile in map.spawns do ... end + "spawns", sol::property([](Map* map, sol::this_state ts) { + sol::state_view lua(ts); + + auto iterator = std::make_shared(map); + + return sol::make_object(lua, [iterator]() -> Tile* { + return iterator->next(); + }); + }), + + // String representation + sol::meta_function::to_string, [](Map* map) { + if (!map) return std::string("Map(invalid)"); + return "Map(\"" + map->getName() + "\", " + + std::to_string(map->getWidth()) + "x" + + std::to_string(map->getHeight()) + ")"; + } + ); +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_map.h b/source/lua/lua_api_map.h new file mode 100644 index 000000000..c6d8f4928 --- /dev/null +++ b/source/lua/lua_api_map.h @@ -0,0 +1,29 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_MAP_H +#define RME_LUA_API_MAP_H + +#define SOL_ALL_SAFETIES_ON 1 +#include + +namespace LuaAPI { + // Register the Map usertype with Lua + void registerMap(sol::state& lua); +} + +#endif // RME_LUA_API_MAP_H diff --git a/source/lua/lua_api_noise.cpp b/source/lua/lua_api_noise.cpp new file mode 100644 index 000000000..72dfeef1f --- /dev/null +++ b/source/lua/lua_api_noise.cpp @@ -0,0 +1,469 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_noise.h" +#include "../fast_noise_lite.h" + +#include +#include + +namespace LuaAPI { + +// Thread-local noise generator cache for better performance +// Each seed gets its own generator instance +class NoiseGeneratorCache { +public: + FastNoiseLite& getGenerator(int seed) { + std::lock_guard lock(mutex_); + auto it = generators_.find(seed); + if (it == generators_.end()) { + auto& gen = generators_[seed]; + gen.SetSeed(seed); + return gen; + } + return it->second; + } + + void clear() { + std::lock_guard lock(mutex_); + generators_.clear(); + } + +private: + std::unordered_map generators_; + std::mutex mutex_; +}; + +static NoiseGeneratorCache g_noiseCache; + +// Helper to create configured noise generator +static FastNoiseLite createNoiseGenerator(int seed, FastNoiseLite::NoiseType type, float frequency = 0.01f) { + FastNoiseLite noise; + noise.SetSeed(seed); + noise.SetNoiseType(type); + noise.SetFrequency(frequency); + return noise; +} + +void registerNoise(sol::state& lua) { + sol::table noiseTable = lua.create_table(); + + // ======================================== + // PERLIN NOISE + // ======================================== + + // noise.perlin(x, y, seed, frequency) -> number [-1, 1] + // Simple 2D perlin noise + noiseTable.set_function("perlin", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Perlin, freq); + return noise.GetNoise(x, y); + }); + + // noise.perlin3d(x, y, z, seed, frequency) -> number [-1, 1] + // 3D perlin noise + noiseTable.set_function("perlin3d", [](float x, float y, float z, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Perlin, freq); + return noise.GetNoise(x, y, z); + }); + + // ======================================== + // SIMPLEX / OPENSIMPLEX2 NOISE + // ======================================== + + // noise.simplex(x, y, seed, frequency) -> number [-1, 1] + // OpenSimplex2 noise - better quality than perlin + noiseTable.set_function("simplex", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_OpenSimplex2, freq); + return noise.GetNoise(x, y); + }); + + // noise.simplex3d(x, y, z, seed, frequency) -> number [-1, 1] + noiseTable.set_function("simplex3d", [](float x, float y, float z, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_OpenSimplex2, freq); + return noise.GetNoise(x, y, z); + }); + + // noise.simplexSmooth(x, y, seed, frequency) -> number [-1, 1] + // OpenSimplex2S - smoother variant + noiseTable.set_function("simplexSmooth", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_OpenSimplex2S, freq); + return noise.GetNoise(x, y); + }); + + // ======================================== + // CELLULAR / VORONOI NOISE + // ======================================== + + // noise.cellular(x, y, seed, frequency, distanceFunc, returnType) -> number + // Cellular/Voronoi noise for cave-like structures, organic patterns + noiseTable.set_function("cellular", [](float x, float y, sol::optional seed, sol::optional frequency, + sol::optional distanceFunc, sol::optional returnType) -> float { + + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + std::string distFn = distanceFunc.value_or("euclidean"); + std::string retType = returnType.value_or("distance"); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Cellular, freq); + + // Set distance function + if (distFn == "euclidean" || distFn == "euclideanSq") { + noise.SetCellularDistanceFunction(FastNoiseLite::CellularDistanceFunction_EuclideanSq); + } else if (distFn == "manhattan") { + noise.SetCellularDistanceFunction(FastNoiseLite::CellularDistanceFunction_Manhattan); + } else if (distFn == "hybrid") { + noise.SetCellularDistanceFunction(FastNoiseLite::CellularDistanceFunction_Hybrid); + } + + // Set return type + if (retType == "cellValue") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_CellValue); + } else if (retType == "distance") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance); + } else if (retType == "distance2") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2); + } else if (retType == "distance2Add") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2Add); + } else if (retType == "distance2Sub") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2Sub); + } else if (retType == "distance2Mul") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2Mul); + } else if (retType == "distance2Div") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2Div); + } + + return noise.GetNoise(x, y); + }); + + // noise.cellular3d(x, y, z, seed, frequency) -> number + noiseTable.set_function("cellular3d", [](float x, float y, float z, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Cellular, freq); + return noise.GetNoise(x, y, z); + }); + + // ======================================== + // VALUE NOISE + // ======================================== + + // noise.value(x, y, seed, frequency) -> number [-1, 1] + // Value noise - blocky, good for heightmaps + noiseTable.set_function("value", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Value, freq); + return noise.GetNoise(x, y); + }); + + // noise.valueCubic(x, y, seed, frequency) -> number [-1, 1] + // Cubic interpolated value noise - smoother than value + noiseTable.set_function("valueCubic", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_ValueCubic, freq); + return noise.GetNoise(x, y); + }); + + // ======================================== + // FBM (Fractional Brownian Motion) + // ======================================== + + // noise.fbm(x, y, seed, options) -> number + // Fractal Brownian Motion - layered noise for natural terrain + // options: { frequency, octaves, lacunarity, gain, noiseType } + noiseTable.set_function("fbm", [](float x, float y, sol::optional seed, sol::optional options) -> float { + int s = seed.value_or(1337); + + FastNoiseLite noise; + noise.SetSeed(s); + noise.SetFractalType(FastNoiseLite::FractalType_FBm); + + if (options) { + sol::table opts = *options; + + // Frequency + float freq = opts.get_or("frequency", 0.01f); + noise.SetFrequency(freq); + + // Octaves (layers of noise) + int octaves = opts.get_or("octaves", 4); + noise.SetFractalOctaves(octaves); + + // Lacunarity (frequency multiplier per octave) + float lacunarity = opts.get_or("lacunarity", 2.0f); + noise.SetFractalLacunarity(lacunarity); + + // Gain (amplitude multiplier per octave) + float gain = opts.get_or("gain", 0.5f); + noise.SetFractalGain(gain); + + // Noise type for fractal + std::string noiseType = opts.get_or("noiseType", "simplex"); + if (noiseType == "perlin") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); + } else if (noiseType == "simplex" || noiseType == "opensimplex") { + noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); + } else if (noiseType == "value") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Value); + } else if (noiseType == "cellular") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Cellular); + } + } else { + noise.SetFrequency(0.01f); + noise.SetFractalOctaves(4); + noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); + } + + return noise.GetNoise(x, y); + }); + + // noise.fbm3d(x, y, z, seed, options) -> number + noiseTable.set_function("fbm3d", [](float x, float y, float z, sol::optional seed, sol::optional options) -> float { + int s = seed.value_or(1337); + + FastNoiseLite noise; + noise.SetSeed(s); + noise.SetFractalType(FastNoiseLite::FractalType_FBm); + + if (options) { + sol::table opts = *options; + noise.SetFrequency(opts.get_or("frequency", 0.01f)); + noise.SetFractalOctaves(opts.get_or("octaves", 4)); + noise.SetFractalLacunarity(opts.get_or("lacunarity", 2.0f)); + noise.SetFractalGain(opts.get_or("gain", 0.5f)); + + std::string noiseType = opts.get_or("noiseType", "simplex"); + if (noiseType == "perlin") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); + } else { + noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); + } + } else { + noise.SetFrequency(0.01f); + noise.SetFractalOctaves(4); + noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); + } + + return noise.GetNoise(x, y, z); + }); + + // ======================================== + // RIDGED NOISE + // ======================================== + + // noise.ridged(x, y, seed, options) -> number + // Ridged fractal noise - good for mountains, veins + noiseTable.set_function("ridged", [](float x, float y, sol::optional seed, sol::optional options) -> float { + int s = seed.value_or(1337); + + FastNoiseLite noise; + noise.SetSeed(s); + noise.SetFractalType(FastNoiseLite::FractalType_Ridged); + noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); + + if (options) { + sol::table opts = *options; + noise.SetFrequency(opts.get_or("frequency", 0.01f)); + noise.SetFractalOctaves(opts.get_or("octaves", 4)); + noise.SetFractalLacunarity(opts.get_or("lacunarity", 2.0f)); + noise.SetFractalGain(opts.get_or("gain", 0.5f)); + } else { + noise.SetFrequency(0.01f); + noise.SetFractalOctaves(4); + } + + return noise.GetNoise(x, y); + }); + + // ======================================== + // DOMAIN WARP + // ======================================== + + // noise.warp(x, y, seed, options) -> x, y (warped coordinates) + // Domain warping - distorts input coordinates for organic effects + noiseTable.set_function("warp", [](float x, float y, sol::optional seed, sol::optional options, sol::this_state s) -> sol::object { + int sd = seed.value_or(1337); + + FastNoiseLite noise; + noise.SetSeed(sd); + noise.SetDomainWarpType(FastNoiseLite::DomainWarpType_OpenSimplex2); + + float amplitude = 30.0f; + float frequency = 0.01f; + + if (options) { + sol::table opts = *options; + amplitude = opts.get_or("amplitude", 30.0f); + frequency = opts.get_or("frequency", 0.01f); + + std::string warpType = opts.get_or("type", "simplex"); + if (warpType == "simplex" || warpType == "opensimplex") { + noise.SetDomainWarpType(FastNoiseLite::DomainWarpType_OpenSimplex2); + } else if (warpType == "simplexReduced") { + noise.SetDomainWarpType(FastNoiseLite::DomainWarpType_OpenSimplex2Reduced); + } else if (warpType == "basic") { + noise.SetDomainWarpType(FastNoiseLite::DomainWarpType_BasicGrid); + } + } + + noise.SetDomainWarpAmp(amplitude); + noise.SetFrequency(frequency); + + noise.DomainWarp(x, y); + + sol::state_view lua(s); + sol::table result = lua.create_table(); + result["x"] = x; + result["y"] = y; + return result; + }); + + // ======================================== + // UTILITY FUNCTIONS + // ======================================== + + // noise.normalize(value, min, max) -> number [0, 1] + // Normalize noise value from [-1,1] to [min, max] + noiseTable.set_function("normalize", [](float value, sol::optional minVal, sol::optional maxVal) -> float { + float min = minVal.value_or(0.0f); + float max = maxVal.value_or(1.0f); + // value is in [-1, 1], normalize to [0, 1] first, then scale + float normalized = (value + 1.0f) * 0.5f; + return min + normalized * (max - min); + }); + + // noise.threshold(value, threshold) -> boolean + // Returns true if value is above threshold + noiseTable.set_function("threshold", [](float value, float threshold) -> bool { + return value >= threshold; + }); + + // noise.map(value, inMin, inMax, outMin, outMax) -> number + // Map value from one range to another + noiseTable.set_function("map", [](float value, float inMin, float inMax, float outMin, float outMax) -> float { + float t = (value - inMin) / (inMax - inMin); + return outMin + t * (outMax - outMin); + }); + + // noise.clamp(value, min, max) -> number + noiseTable.set_function("clamp", [](float value, float min, float max) -> float { + if (value < min) return min; + if (value > max) return max; + return value; + }); + + // noise.lerp(a, b, t) -> number + // Linear interpolation + noiseTable.set_function("lerp", [](float a, float b, float t) -> float { + return a + t * (b - a); + }); + + // noise.smoothstep(edge0, edge1, x) -> number + // Smooth interpolation + noiseTable.set_function("smoothstep", [](float edge0, float edge1, float x) -> float { + float t = (x - edge0) / (edge1 - edge0); + if (t < 0.0f) t = 0.0f; + if (t > 1.0f) t = 1.0f; + return t * t * (3.0f - 2.0f * t); + }); + + // noise.clearCache() - clear noise generator cache + noiseTable.set_function("clearCache", []() { + g_noiseCache.clear(); + }); + + // ======================================== + // BATCH GENERATION (for performance) + // ======================================== + + // noise.generateGrid(x1, y1, x2, y2, options) -> table of values + // Generate noise values for a grid area (faster than individual calls) + noiseTable.set_function("generateGrid", [](int x1, int y1, int x2, int y2, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + FastNoiseLite noise; + + if (options) { + sol::table opts = *options; + noise.SetSeed(opts.get_or("seed", 1337)); + noise.SetFrequency(opts.get_or("frequency", 0.01f)); + + std::string noiseType = opts.get_or("noiseType", "simplex"); + if (noiseType == "perlin") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); + } else if (noiseType == "simplex") { + noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); + } else if (noiseType == "cellular") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Cellular); + } else if (noiseType == "value") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Value); + } + + // Fractal settings + std::string fractal = opts.get_or("fractal", "none"); + if (fractal == "fbm") { + noise.SetFractalType(FastNoiseLite::FractalType_FBm); + noise.SetFractalOctaves(opts.get_or("octaves", 4)); + noise.SetFractalLacunarity(opts.get_or("lacunarity", 2.0f)); + noise.SetFractalGain(opts.get_or("gain", 0.5f)); + } else if (fractal == "ridged") { + noise.SetFractalType(FastNoiseLite::FractalType_Ridged); + noise.SetFractalOctaves(opts.get_or("octaves", 4)); + } + } else { + noise.SetSeed(1337); + noise.SetFrequency(0.01f); + noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); + } + + // Generate values + for (int y = y1; y <= y2; ++y) { + sol::table row = lua.create_table(); + for (int x = x1; x <= x2; ++x) { + row[x - x1 + 1] = noise.GetNoise((float)x, (float)y); + } + result[y - y1 + 1] = row; + } + + return result; + }); + + lua["noise"] = noiseTable; +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_noise.h b/source/lua/lua_api_noise.h new file mode 100644 index 000000000..29041a0bb --- /dev/null +++ b/source/lua/lua_api_noise.h @@ -0,0 +1,28 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_NOISE_H +#define RME_LUA_API_NOISE_H + +#include "lua_api.h" + +namespace LuaAPI { + // Register noise generation functions (perlin, simplex, cellular, fbm, etc.) + void registerNoise(sol::state& lua); +} + +#endif // RME_LUA_API_NOISE_H diff --git a/source/lua/lua_api_position.cpp b/source/lua/lua_api_position.cpp new file mode 100644 index 000000000..5ef0866dd --- /dev/null +++ b/source/lua/lua_api_position.cpp @@ -0,0 +1,71 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_position.h" +#include "../position.h" + +namespace LuaAPI { + +void registerPosition(sol::state& lua) { + // Register Position usertype + lua.new_usertype("Position", + // Constructors + sol::constructors< + Position(), + Position(int, int, int) + >(), + + // Also allow construction from table: Position{x=100, y=200, z=7} + sol::call_constructor, sol::factories( + [](int x, int y, int z) { + return Position(x, y, z); + }, + [](sol::table t) { + int x = t.get_or("x", 0); + int y = t.get_or("y", 0); + int z = t.get_or("z", 0); + return Position(x, y, z); + } + ), + + // Properties + "x", &Position::x, + "y", &Position::y, + "z", &Position::z, + + // Methods + "isValid", &Position::isValid, + + // Operators + sol::meta_function::equal_to, [](const Position& a, const Position& b) { + return a == b; + }, + sol::meta_function::addition, [](const Position& a, const Position& b) { + return a + b; + }, + sol::meta_function::subtraction, [](const Position& a, const Position& b) { + return a - b; + }, + sol::meta_function::to_string, [](const Position& pos) { + return "Position(" + std::to_string(pos.x) + ", " + + std::to_string(pos.y) + ", " + std::to_string(pos.z) + ")"; + } + ); +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_position.h b/source/lua/lua_api_position.h new file mode 100644 index 000000000..01fde8994 --- /dev/null +++ b/source/lua/lua_api_position.h @@ -0,0 +1,29 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_POSITION_H +#define RME_LUA_API_POSITION_H + +#define SOL_ALL_SAFETIES_ON 1 +#include + +namespace LuaAPI { + // Register the Position usertype with Lua + void registerPosition(sol::state& lua); +} + +#endif // RME_LUA_API_POSITION_H diff --git a/source/lua/lua_api_selection.cpp b/source/lua/lua_api_selection.cpp new file mode 100644 index 000000000..4edb47e05 --- /dev/null +++ b/source/lua/lua_api_selection.cpp @@ -0,0 +1,151 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_selection.h" +#include "../selection.h" +#include "../tile.h" +#include "../position.h" +#include "../gui.h" +#include "../editor.h" + +namespace LuaAPI { + +// Helper to get the current selection +static Selection* getCurrentSelection() { + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) return nullptr; + return &editor->selection; +} + +// Get tiles as a Lua table +static sol::table getSelectionTiles(sol::this_state ts) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); + + Selection* sel = getCurrentSelection(); + if (!sel) return result; + + int idx = 1; + for (Tile* tile : sel->getTiles()) { + if (tile) { + result[idx++] = tile; + } + } + return result; +} + +// Get bounds as a table with min/max positions +static sol::table getSelectionBounds(sol::this_state ts) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); + + Selection* sel = getCurrentSelection(); + if (!sel || sel->size() == 0) { + return result; + } + + result["min"] = sel->minPosition(); + result["max"] = sel->maxPosition(); + return result; +} + +void registerSelection(sol::state& lua) { + // Register Selection usertype + lua.new_usertype("Selection", + // No public constructor - selection is obtained from app.selection + sol::no_constructor, + + // Properties + "isEmpty", sol::property([](Selection* sel) { + return sel == nullptr || sel->size() == 0; + }), + "size", sol::property([](Selection* sel) -> size_t { + return sel ? sel->size() : 0; + }), + "isBusy", sol::property([](Selection* sel) { + return sel && sel->isBusy(); + }), + + // Tiles collection (as a table) + "tiles", sol::property([](Selection* sel, sol::this_state ts) { + return getSelectionTiles(ts); + }), + + // Bounds + "bounds", sol::property([](Selection* sel, sol::this_state ts) { + return getSelectionBounds(ts); + }), + "minPosition", sol::property([](Selection* sel) -> Position { + return sel ? sel->minPosition() : Position(); + }), + "maxPosition", sol::property([](Selection* sel) -> Position { + return sel ? sel->maxPosition() : Position(); + }), + + // Methods + "clear", [](Selection* sel) { + if (sel) { + sel->start(Selection::INTERNAL); + sel->clear(); + sel->finish(Selection::INTERNAL); + } + }, + + "add", sol::overload( + [](Selection* sel, Tile* tile) { + if (sel && tile) { + sel->start(Selection::INTERNAL); + sel->add(tile); + sel->finish(Selection::INTERNAL); + } + }, + [](Selection* sel, Tile* tile, Item* item) { + if (sel && tile && item) { + sel->start(Selection::INTERNAL); + sel->add(tile, item); + sel->finish(Selection::INTERNAL); + } + } + ), + + "remove", sol::overload( + [](Selection* sel, Tile* tile) { + if (sel && tile) { + sel->start(Selection::INTERNAL); + sel->remove(tile); + sel->finish(Selection::INTERNAL); + } + }, + [](Selection* sel, Tile* tile, Item* item) { + if (sel && tile && item) { + sel->start(Selection::INTERNAL); + sel->remove(tile, item); + sel->finish(Selection::INTERNAL); + } + } + ), + + // String representation + sol::meta_function::to_string, [](Selection* sel) { + if (!sel) return std::string("Selection(invalid)"); + return "Selection(size=" + std::to_string(sel->size()) + ")"; + } + ); +} + +} // namespace LuaAPI diff --git a/source/lua/lua_api_selection.h b/source/lua/lua_api_selection.h new file mode 100644 index 000000000..6b5595fb3 --- /dev/null +++ b/source/lua/lua_api_selection.h @@ -0,0 +1,29 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_SELECTION_H +#define RME_LUA_API_SELECTION_H + +#define SOL_ALL_SAFETIES_ON 1 +#include + +namespace LuaAPI { + // Register the Selection usertype with Lua + void registerSelection(sol::state& lua); +} + +#endif // RME_LUA_API_SELECTION_H diff --git a/source/lua/lua_api_tile.cpp b/source/lua/lua_api_tile.cpp new file mode 100644 index 000000000..582846d16 --- /dev/null +++ b/source/lua/lua_api_tile.cpp @@ -0,0 +1,549 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_api_tile.h" +#include "lua_api.h" +#include +#include +#include "../tile.h" +#include "../item.h" +#include "../creature.h" +#include "../spawn.h" +#include "../brush.h" +#include "../ground_brush.h" +#include "../doodad_brush.h" +#include "../wall_brush.h" +#include "../gui.h" +#include "../editor.h" +#include "../map.h" + +namespace LuaAPI { + + // Helper to get items as a Lua table + static sol::table getTileItems(Tile* tile, sol::this_state ts) { + sol::state_view lua(ts); + sol::table items = lua.create_table(); + + if (!tile) return items; + + int idx = 1; + for (Item* item : tile->items) { + if (item) { + items[idx++] = item; + } + } + return items; + } + + // Add item to tile + static Item* addItemToTile(Tile* tile, int itemId, sol::optional countOpt) { + if (!tile) { + throw sol::error("Invalid tile"); + } + + // Mark tile for undo before modification + markTileForUndo(tile); + + Item* item = Item::Create(static_cast(itemId)); + if (!item) { + throw sol::error("Failed to create item with id " + std::to_string(itemId)); + } + + if (countOpt) { + item->setSubtype(static_cast(*countOpt)); + } + + tile->addItem(item); + tile->modify(); + + return item; + } + + // Remove item from tile + static bool removeItemFromTile(Tile* tile, Item* itemToRemove) { + if (!tile || !itemToRemove) { + return false; + } + + // Mark tile for undo before modification + markTileForUndo(tile); + + // Find and remove the item + for (auto it = tile->items.begin(); it != tile->items.end(); ++it) { + if (*it == itemToRemove) { + delete *it; + tile->items.erase(it); + tile->modify(); + return true; + } + } + + // Check if it's the ground + if (tile->ground == itemToRemove) { + delete tile->ground; + tile->ground = nullptr; + tile->modify(); + return true; + } + + return false; + } + + // Set creature on tile + static Creature* setTileCreature(Tile* tile, const std::string& creatureName, + sol::optional spawnTimeOpt, + sol::optional directionOpt) { + if (!tile) { + throw sol::error("Invalid tile"); + } + + // Check if creature type exists + CreatureType* type = g_creatures[creatureName]; + if (!type) { + throw sol::error("Unknown creature type: " + creatureName); + } + + // Mark tile for undo before modification + markTileForUndo(tile); + + // Remove existing creature + if (tile->creature) { + delete tile->creature; + tile->creature = nullptr; + } + + // Create new creature + tile->creature = newd Creature(creatureName); + + // Set spawn time (default to global setting or 60s) + int spawnTime = spawnTimeOpt.value_or(g_gui.GetSpawnTime()); + tile->creature->setSpawnTime(spawnTime); + + // Set direction (default SOUTH) + Direction dir = static_cast(directionOpt.value_or(SOUTH)); + if (dir >= DIRECTION_FIRST && dir <= DIRECTION_LAST) { + tile->creature->setDirection(dir); + } + + tile->modify(); + return tile->creature; + } + + // Remove creature from tile + static bool removeTileCreature(Tile* tile) { + if (!tile || !tile->creature) { + return false; + } + + // Mark tile for undo before modification + markTileForUndo(tile); + + delete tile->creature; + tile->creature = nullptr; + tile->modify(); + return true; + } + + // Set spawn on tile + static Spawn* setTileSpawn(Tile* tile, sol::optional sizeOpt) { + if (!tile) { + throw sol::error("Invalid tile"); + } + + // Mark tile for undo before modification + markTileForUndo(tile); + + // Get map instance + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) { + throw sol::error("No active editor"); + } + Map& map = editor->map; + + // Remove existing spawn from map metadata + if (tile->spawn) { + map.removeSpawn(tile); + delete tile->spawn; + tile->spawn = nullptr; + } + + // Create new spawn with given size (default 3) + int size = sizeOpt.value_or(3); + if (size < 1) size = 1; + if (size > 50) size = 50; + + tile->spawn = newd Spawn(size); + + // Register new spawn with map + map.addSpawn(tile); + + tile->modify(); + return tile->spawn; + } + + // Remove spawn from tile + static bool removeTileSpawn(Tile* tile) { + if (!tile || !tile->spawn) { + return false; + } + + // Mark tile for undo before modification + markTileForUndo(tile); + + // Get map instance + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) { + throw sol::error("No active editor"); + } + Map& map = editor->map; + + // Remove spawn from map metadata + map.removeSpawn(tile); + + delete tile->spawn; + tile->spawn = nullptr; + tile->modify(); + return true; + } + + // Set ground item + static void setTileGround(Tile* tile, sol::object groundObj) { + if (!tile) { + throw sol::error("Invalid tile"); + } + + // Mark tile for undo before modification + markTileForUndo(tile); + + // Remove existing ground + if (tile->ground) { + delete tile->ground; + tile->ground = nullptr; + } + + // Set new ground if provided + if (groundObj.is()) { + int groundId = groundObj.as(); + if (groundId > 0) { + tile->ground = Item::Create(static_cast(groundId)); + } + } else if (groundObj.is()) { + Item* item = groundObj.as(); + if (item) { + tile->ground = item->deepCopy(); + } + } + // If nil/none, ground stays null + + tile->modify(); + } + + // Set house ID + static void setTileHouseId(Tile* tile, uint32_t houseId) { + if (!tile) return; + + // Mark tile for undo before modification + markTileForUndo(tile); + + tile->setHouseID(houseId); + tile->modify(); + } + + // Apply a brush to a tile (with optional auto-bordering) + static bool applyBrushToTile(Tile* tile, const std::string& brushName, sol::optional autoBorder) { + if (!tile) return false; + + Brush* brush = g_brushes.getBrush(brushName); + if (!brush) return false; + + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) return false; + + markTileForUndo(tile); + + bool success = false; + + // Apply the brush based on its type + if (brush->isGround()) { + GroundBrush* groundBrush = brush->asGround(); + if (groundBrush) { + groundBrush->draw(&editor->map, tile, nullptr); + success = true; + } + } else if (brush->isDoodad()) { + DoodadBrush* doodadBrush = brush->asDoodad(); + if (doodadBrush) { + doodadBrush->draw(&editor->map, tile, nullptr); + success = true; + } + } else if (brush->isWall()) { + WallBrush* wallBrush = brush->asWall(); + if (wallBrush) { + wallBrush->draw(&editor->map, tile, nullptr); + success = true; + } + } else if (brush->isDoor()) { + DoorBrush* doorBrush = brush->asDoor(); + if (doorBrush) { + doorBrush->draw(&editor->map, tile, nullptr); + success = true; + } + } + + if (success) { + // Apply auto-bordering if requested (default: true for ground brushes) + bool doBorder = autoBorder.value_or(brush->isGround()); + if (doBorder) { + tile->borderize(&editor->map); + } + tile->modify(); + } + + return success; + } + + void registerTile(sol::state& lua) { + // Register Tile usertype + lua.new_usertype("Tile", + // No public constructor - tiles are obtained from the map + sol::no_constructor, + + // Position properties (read-only) + "position", sol::property([](Tile* tile) { return tile ? tile->getPosition() : Position(); }), + "x", sol::property([](Tile* tile) { return tile ? tile->getX() : 0; }), + "y", sol::property([](Tile* tile) { return tile ? tile->getY() : 0; }), + "z", sol::property([](Tile* tile) { return tile ? tile->getZ() : 0; }), + + // Ground (read/write) + "ground", sol::property( + [](Tile* tile) -> Item* { return tile ? tile->ground : nullptr; }, + setTileGround + ), + "hasGround", sol::property([](Tile* tile) { return tile && tile->hasGround(); }), + + // Items collection (read-only - use addItem/removeItem to modify) + "items", sol::property(getTileItems), + "itemCount", sol::property([](Tile* tile) { return tile ? tile->size() : 0; }), + + // House + "houseId", sol::property( + [](Tile* tile) -> uint32_t { return tile ? tile->getHouseID() : 0; }, + setTileHouseId + ), + "isHouseTile", sol::property([](Tile* tile) { return tile && tile->isHouseTile(); }), + "isHouseExit", sol::property([](Tile* tile) { return tile && tile->isHouseExit(); }), + + // Flags (read-only for now, some could be made writable) + "isPZ", sol::property([](Tile* tile) { return tile && tile->isPZ(); }), + "isBlocking", sol::property([](Tile* tile) { return tile && tile->isBlocking(); }), + "hasBorders", sol::property([](Tile* tile) { return tile && tile->hasBorders(); }), + "hasWall", sol::property([](Tile* tile) { return tile && tile->hasWall(); }), + "hasTable", sol::property([](Tile* tile) { return tile && tile->hasTable(); }), + "hasCarpet", sol::property([](Tile* tile) { return tile && tile->hasCarpet(); }), + "groundZOrder", sol::property([](Tile* tile) -> int { + if (!tile) return 0; + GroundBrush* brush = tile->getGroundBrush(); + return brush ? brush->getZ() : 0; + }), + "isNoPvp", sol::property([](Tile* tile) { return tile && (tile->getMapFlags() & TILESTATE_NOPVP); }), + "isNoLogout", sol::property([](Tile* tile) { return tile && (tile->getMapFlags() & TILESTATE_NOLOGOUT); }), + "isPvpZone", sol::property([](Tile* tile) { return tile && (tile->getMapFlags() & TILESTATE_PVPZONE); }), + + // Map flags + "mapFlags", sol::property( + [](Tile* tile) -> uint16_t { return tile ? tile->getMapFlags() : 0; }, + [](Tile* tile, uint16_t flags) { + if (tile) { + markTileForUndo(tile); + tile->setMapFlags(flags); + tile->modify(); + } + } + ), + + // Selection + "isSelected", sol::property([](Tile* tile) { return tile && tile->isSelected(); }), + "select", [](Tile* tile) { if (tile) tile->select(); }, + "deselect", [](Tile* tile) { if (tile) tile->deselect(); }, + + // Creature and Spawn (read-only access, use methods to modify) + "creature", sol::property([](Tile* tile) -> Creature* { return tile ? tile->creature : nullptr; }), + "spawn", sol::property([](Tile* tile) -> Spawn* { return tile ? tile->spawn : nullptr; }), + "hasCreature", sol::property([](Tile* tile) { return tile && tile->creature != nullptr; }), + "hasSpawn", sol::property([](Tile* tile) { return tile && tile->spawn != nullptr; }), + + // Creature methods + "setCreature", sol::overload( + [](Tile* tile, const std::string& name) -> Creature* { + return setTileCreature(tile, name, sol::nullopt, sol::nullopt); + }, + [](Tile* tile, const std::string& name, int spawnTime) -> Creature* { + return setTileCreature(tile, name, spawnTime, sol::nullopt); + }, + [](Tile* tile, const std::string& name, int spawnTime, int direction) -> Creature* { + return setTileCreature(tile, name, spawnTime, direction); + } + ), + "removeCreature", removeTileCreature, + + // Spawn methods + "setSpawn", sol::overload( + [](Tile* tile) -> Spawn* { + return setTileSpawn(tile, sol::nullopt); + }, + [](Tile* tile, int size) -> Spawn* { + return setTileSpawn(tile, size); + } + ), + "removeSpawn", removeTileSpawn, + + // Methods + "addItem", sol::overload( + [](Tile* tile, int itemId) -> Item* { + return addItemToTile(tile, itemId, sol::nullopt); + }, + [](Tile* tile, int itemId, int count) -> Item* { + return addItemToTile(tile, itemId, count); + } + ), + "removeItem", removeItemFromTile, + "applyBrush", applyBrushToTile, + "borderize", [](Tile* tile) { + if (!tile) return; + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) return; + markTileForUndo(tile); + tile->borderize(&editor->map); + tile->modify(); + }, + "wallize", [](Tile* tile) { + if (!tile) return; + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) return; + markTileForUndo(tile); + tile->wallize(&editor->map); + tile->modify(); + }, + "moveItem", sol::overload( + // Index-based move within same tile + // Semantics: Move item at fromIdx so it ends up at toIdx position + [](Tile* tile, int fromIdx, int toIdx) { + if (!tile) return; + + int from = fromIdx - 1; + int to = toIdx - 1; + int size = (int)tile->items.size(); + + if (from < 0 || from >= size || to < 0 || to >= size || from == to) { + return; + } + + markTileForUndo(tile); + + Item* item = tile->items[from]; + tile->items.erase(tile->items.begin() + from); + + // After erasing from position 'from', indices shift: + // - If to > from: the target position shifted down by 1, so use 'to' directly + // (because we want the item to end up at that visual slot) + // - If to < from: no shift needed, use 'to' directly + // In both cases, clamp to valid range after erase + int insertPos = std::clamp(to, 0, (int)tile->items.size()); + + tile->items.insert(tile->items.begin() + insertPos, item); + tile->modify(); + }, + // Move specific item object to new index in same tile + [](Tile* tile, Item* item, int toIdx) { + if (!tile || !item) return; + auto it = std::find(tile->items.begin(), tile->items.end(), item); + if (it == tile->items.end()) return; + + int from = (int)std::distance(tile->items.begin(), it); + int to = std::clamp(toIdx - 1, 0, (int)tile->items.size() - 1); + if (from == to) return; + + markTileForUndo(tile); + tile->items.erase(it); + + int insertPos = std::clamp(to, 0, (int)tile->items.size()); + tile->items.insert(tile->items.begin() + insertPos, item); + tile->modify(); + }, + // Move item to DIFFERENT tile + [](Tile* sourceTile, Item* item, Tile* destTile, sol::optional toIdx) { + if (!sourceTile || !item || !destTile) return; + auto it = std::find(sourceTile->items.begin(), sourceTile->items.end(), item); + if (it == sourceTile->items.end()) return; + + markTileForUndo(sourceTile); + markTileForUndo(destTile); + + sourceTile->items.erase(it); + int to = toIdx ? std::clamp(*toIdx - 1, 0, (int)destTile->items.size()) : (int)destTile->items.size(); + destTile->items.insert(destTile->items.begin() + to, item); + + sourceTile->modify(); + destTile->modify(); + } + ), + + "getPosition", [](Tile* tile, sol::this_state ts) { + sol::state_view lua(ts); + sol::table t = lua.create_table(); + if (tile) { + Position p = tile->getPosition(); + t["x"] = p.x; + t["y"] = p.y; + t["z"] = p.z; + } + return t; + }, + + "getItemAt", [](Tile* tile, int index) -> Item* { + if (!tile) return nullptr; + // Lua uses 1-based indexing + return tile->getItemAt(index - 1); + }, + + "getTopItem", [](Tile* tile) -> Item* { + return tile ? tile->getTopItem() : nullptr; + }, + + "getWall", [](Tile* tile) -> Item* { + return tile ? tile->getWall() : nullptr; + }, + + "getTable", [](Tile* tile) -> Item* { + return tile ? tile->getTable() : nullptr; + }, + + "getCarpet", [](Tile* tile) -> Item* { + return tile ? tile->getCarpet() : nullptr; + }, + + // String representation + sol::meta_function::to_string, [](Tile* tile) { + if (!tile) return std::string("Tile(invalid)"); + Position pos = tile->getPosition(); + return "Tile(" + std::to_string(pos.x) + ", " + + std::to_string(pos.y) + ", " + std::to_string(pos.z) + ")"; + } + ); + } +} // namespace LuaAPI diff --git a/source/lua/lua_api_tile.h b/source/lua/lua_api_tile.h new file mode 100644 index 000000000..7a56b0da2 --- /dev/null +++ b/source/lua/lua_api_tile.h @@ -0,0 +1,29 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_API_TILE_H +#define RME_LUA_API_TILE_H + +#define SOL_ALL_SAFETIES_ON 1 +#include + +namespace LuaAPI { + // Register the Tile usertype with Lua + void registerTile(sol::state& lua); +} + +#endif // RME_LUA_API_TILE_H diff --git a/source/lua/lua_dialog.cpp b/source/lua/lua_dialog.cpp new file mode 100644 index 000000000..c2275bc27 --- /dev/null +++ b/source/lua/lua_dialog.cpp @@ -0,0 +1,2709 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_dialog.h" +#include "lua_api_image.h" +#include "../gui.h" +#include "../items.h" +#include "../map_display.h" +#include "../map_drawer.h" +#include "../editor.h" +#include +#include "../common_windows.h" +#include "../find_item_window.h" +#include "../dcbutton.h" +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __WXMSW__ +#include +#endif + +using namespace std::string_literals; + +// Specialized Canvas for Lua Dialogs +// Specialized Canvas for Lua Dialogs +class MapPreviewCanvas : public MapCanvas { +public: + MapPreviewCanvas(wxWindow* parent, Editor& editor) : + MapCanvas(parent, editor, nullptr), + view_x(0), + view_y(0) { + + floor = 7; + zoom = 1.0; + + // Force Ingame mode for "Client Box" behavior + drawer->getOptions().SetIngame(); + drawer->getOptions().show_ingame_box = true; + } + + virtual ~MapPreviewCanvas() { + } + + // Overrides to decouple from MapWindow + void SetZoom(double value) override { + if (value < 0.125) value = 0.125; + if (value > 25.00) value = 25.0; + zoom = value; + wxGLCanvas::Refresh(); + } + + void GetViewBox(int* view_scroll_x, int* view_scroll_y, int* screensize_x, int* screensize_y) const override { + wxSize size = GetClientSize(); + *screensize_x = size.GetWidth(); + *screensize_y = size.GetHeight(); + *view_scroll_x = view_x; + *view_scroll_y = view_y; + } + + void ScreenToMap(int screen_x, int screen_y, int* map_x, int* map_y) override { + screen_x *= GetContentScaleFactor(); + screen_y *= GetContentScaleFactor(); + + if (screen_x < 0) { + *map_x = (view_x + screen_x) / 32; + } else { + *map_x = int(view_x + (screen_x * zoom)) / 32; + } + + if (screen_y < 0) { + *map_y = (view_y + screen_y) / 32; + } else { + *map_y = int(view_y + (screen_y * zoom)) / 32; + } + + if (floor <= 7) { + *map_x += 7 - floor; + *map_y += 7 - floor; + } + } + + void GetScreenCenter(int* map_x, int* map_y) override { + wxSize size = GetClientSize(); + ScreenToMap(size.GetWidth() / 2, size.GetHeight() / 2, map_x, map_y); + } + + void SetPosition(int x, int y, int z) { + wxSize size = GetClientSize(); + int width = size.GetWidth(); + int height = size.GetHeight(); + + // If not yet laid out, use a default size to avoid bad coordinates + if (width <= 0) width = 400; + if (height <= 0) height = 300; + + view_x = (x * 32) - (width * zoom) / 2; + view_y = (y * 32) - (height * zoom) / 2; + floor = z; + Refresh(); + } + + // Disable status bar updates for preview canvases + void UpdatePositionStatus(int x = -1, int y = -1) {} + void UpdateZoomStatus() {} + void Refresh() { + wxGLCanvas::Refresh(); + } + + void OnMouseMove(wxMouseEvent& event) { + cursor_x = event.GetX(); + cursor_y = event.GetY(); + } + + void OnWheel(wxMouseEvent& event) { + // Do nothing - disabled to prevent confusion and potential issues + } + + void OnGainMouse(wxMouseEvent& event) { Refresh(); } + void OnLoseMouse(wxMouseEvent& event) { Refresh(); } + void OnMouseLeftClick(wxMouseEvent& event) { SetFocus(); } + void OnMouseLeftRelease(wxMouseEvent& event) {} + void OnMouseRightClick(wxMouseEvent& event) {} + void OnMouseRightRelease(wxMouseEvent& event) {} + void OnMouseCenterClick(wxMouseEvent& event) {} + void OnMouseCenterRelease(wxMouseEvent& event) {} + + void SetViewSize(int w, int h) { + client_w = w; + client_h = h; + + // Auto-resize the window to fit the new client dimensions + int tile_pixel_size = static_cast(32 / zoom); + int req_w = w * tile_pixel_size; + int req_h = h * tile_pixel_size; + + // Add some padding for the frame borders if needed, but SetClientSize should handle content area + // We set the MinSize of the canvas to enforce this size + SetMinSize(wxSize(req_w, req_h)); + + // Attempt to resize the parent dialog to fit the new canvas size + wxWindow* parent = GetParent(); + if (parent) { + parent->Fit(); + } + + Refresh(); + } + + void SetLight(bool on) { + drawer->getOptions().show_lights = on; + Refresh(); + } + + void SyncView() { + Editor* current_editor = g_gui.GetCurrentEditor(); + if (!current_editor) return; + + MapTab* tab = g_gui.GetCurrentMapTab(); + if (tab) { + int cx, cy; + tab->GetCanvas()->GetScreenCenter(&cx, &cy); + SetPosition(cx, cy, (int)tab->GetCanvas()->GetFloor()); + SetZoom(tab->GetCanvas()->GetZoom()); + } + } + + int GetMapX() { + wxSize size = GetClientSize(); + int width = size.GetWidth() > 0 ? size.GetWidth() : 400; + return (view_x + (width * (double)zoom) / 2.0) / 32; + } + + int GetMapY() { + wxSize size = GetClientSize(); + int height = size.GetHeight() > 0 ? size.GetHeight() : 300; + return (view_y + (height * (double)zoom) / 2.0) / 32; + } + + int GetClientWidth() const override { return client_w; } + int GetClientHeight() const override { return client_h; } + +private: + int view_x; + int view_y; + + int client_w = ClientMapWidth; + int client_h = ClientMapHeight; + + DECLARE_EVENT_TABLE() +}; + +BEGIN_EVENT_TABLE(MapPreviewCanvas, MapCanvas) + EVT_MOTION(MapPreviewCanvas::OnMouseMove) + EVT_MOUSEWHEEL(MapPreviewCanvas::OnWheel) + EVT_ENTER_WINDOW(MapPreviewCanvas::OnGainMouse) + EVT_LEAVE_WINDOW(MapPreviewCanvas::OnLoseMouse) + EVT_LEFT_DOWN(MapPreviewCanvas::OnMouseLeftClick) + EVT_LEFT_UP(MapPreviewCanvas::OnMouseLeftRelease) + EVT_RIGHT_DOWN(MapPreviewCanvas::OnMouseRightClick) + EVT_RIGHT_UP(MapPreviewCanvas::OnMouseRightRelease) + EVT_MIDDLE_DOWN(MapPreviewCanvas::OnMouseCenterClick) + EVT_MIDDLE_UP(MapPreviewCanvas::OnMouseCenterRelease) + EVT_PAINT(MapCanvas::OnPaint) +END_EVENT_TABLE() + +BEGIN_EVENT_TABLE(LuaDialog, wxDialog) + EVT_CLOSE(LuaDialog::OnClose) +END_EVENT_TABLE() + +LuaDialog::LuaDialog(const std::string& title, sol::this_state ts) : + wxDialog(g_gui.root, wxID_ANY, wxString(title), wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + lua(ts) { + createLayout(); +} + +// Overload constructor to handle options +LuaDialog::LuaDialog(sol::table options, sol::this_state ts) : + wxDialog(g_gui.root, wxID_ANY, wxString(options.get_or("title", "Script Dialog"s)), + wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE | + (options.get_or("resizable", true) ? wxRESIZE_BORDER : 0) | + (options.get_or("topmost", false) ? wxSTAY_ON_TOP : 0)), + lua(ts) { + + reqWidth = options.get_or("width", -1); + reqHeight = options.get_or("height", -1); + reqX = options.get_or("x", -1); + reqY = options.get_or("y", -1); + + if (reqWidth != -1 || reqHeight != -1) { + SetMinSize(wxSize(reqWidth != -1 ? reqWidth : 150, reqHeight != -1 ? reqHeight : 100)); + SetSize(reqX != -1 ? reqX : -1, reqY != -1 ? reqY : -1, reqWidth, reqHeight); + } else if (reqX != -1 || reqY != -1) { + SetPosition(wxPoint(reqX != -1 ? reqX : GetPosition().x, reqY != -1 ? reqY : GetPosition().y)); + } + + if (options.get_or("dockable", false)) { + dockPanel = new wxPanel(g_gui.root, wxID_ANY); + + wxAuiPaneInfo info; + std::string title = options.get_or("title", "Script Dialog"s); + std::string id = options.get_or("id", title); + + info.Name(id); + info.Caption(title); + info.Right().Layer(1).Position(1).CloseButton(true).MaximizeButton(true); + + int minW = options.get_or("min_width", reqWidth != -1 ? reqWidth : 200); + int minH = options.get_or("min_height", reqHeight != -1 ? reqHeight : 150); + info.MinSize(wxSize(minW, minH)); + + if (reqWidth != -1 || reqHeight != -1) { + info.BestSize(wxSize(reqWidth != -1 ? reqWidth : 300, reqHeight != -1 ? reqHeight : 200)); + } else { + info.BestSize(wxSize(300, 200)); + } + + info.Dockable(true).Floatable(true); + + g_gui.aui_manager->AddPane(dockPanel, info); + g_gui.aui_manager->Update(); + } + + if (options["onclose"].valid()) { + oncloseCallback = options["onclose"]; + } + + createLayout(); +} + +LuaDialog::~LuaDialog() { + while (hotkeySuspendCount > 0) { + resumeHotkeys(); + } + if (dockPanel) { + g_gui.aui_manager->DetachPane(dockPanel); + g_gui.aui_manager->Update(); + dockPanel->Destroy(); + dockPanel = nullptr; + } +} + +void LuaDialog::createLayout() { + mainSizer = new wxBoxSizer(wxVERTICAL); + if (dockPanel) { + dockPanel->SetSizer(mainSizer); + } else { + SetSizer(mainSizer); + } + sizerStack.push(mainSizer); +} + +void LuaDialog::ensureRowSizer() { + if (!currentRowSizer) { + currentRowSizer = new wxBoxSizer(wxHORIZONTAL); + getSizerForWidget()->Add(currentRowSizer, 0, wxEXPAND | wxALL, 5); + } +} + +void LuaDialog::finishCurrentRow() { + currentRowSizer = nullptr; +} + +wxWindow* LuaDialog::getParentForWidget() { + if (currentTabPanel) { + return currentTabPanel; + } + if (dockPanel) { + return dockPanel; + } + return this; +} + +wxSizer* LuaDialog::getSizerForWidget() { + if (!sizerStack.empty()) { + return sizerStack.top(); + } + // Fallback mechanism (should shouldn't happen if managed correctly) + if (currentTabSizer) { + return currentTabSizer; + } + return mainSizer; +} + +LuaDialog* LuaDialog::wrap(sol::table options) { + finishCurrentRow(); + + // 'wrap' is just a horizontal box sizer that wraps? + // Wx doesn't have a simple WrapSizer like that, it has wxWrapSizer. + // But usually "visual grouping side-by-side" is just a Horizontal Box. + + wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); + getSizerForWidget()->Add(sizer, 1, wxEXPAND | wxALL, 0); // Add to current + sizerStack.push(sizer); // Push to stack + + return this; +} + +LuaDialog* LuaDialog::endwrap() { + finishCurrentRow(); + if (!sizerStack.empty() && sizerStack.top() != mainSizer && (!currentTabSizer || sizerStack.top() != currentTabSizer)) { + sizerStack.pop(); + } + return this; +} + +LuaDialog* LuaDialog::box(sol::table options) { + finishCurrentRow(); + + std::string orient = options.get_or("orient", "vertical"s); + std::string label = options.get_or("label", ""s); + + wxSizer* sizer; + if (!label.empty()) { + // Static box sizer + wxStaticBoxSizer* staticBox = new wxStaticBoxSizer(wxVERTICAL, getParentForWidget(), wxString(label)); + if (orient == "horizontal") { + // wxStaticBoxSizer constructor takes orient, but we passed vertical + // Actually can check if we can change it or construct differently + // Reconstruct correct orient + delete staticBox; + staticBox = new wxStaticBoxSizer(wxHORIZONTAL, getParentForWidget(), wxString(label)); + } + sizer = staticBox; + } else { + // Normal box sizer + if (orient == "horizontal") { + sizer = new wxBoxSizer(wxHORIZONTAL); + } else { + sizer = new wxBoxSizer(wxVERTICAL); + } + } + + getSizerForWidget()->Add(sizer, 1, wxEXPAND | wxALL, 5); + sizerStack.push(sizer); + + return this; +} + +LuaDialog* LuaDialog::endbox() { + finishCurrentRow(); + if (!sizerStack.empty() && sizerStack.top() != mainSizer && (!currentTabSizer || sizerStack.top() != currentTabSizer)) { + sizerStack.pop(); + } + return this; +} + +LuaDialog* LuaDialog::label(sol::table options) { + ensureRowSizer(); + + std::string text = options.get_or("text", "label"s); + std::string id = options.get_or("id", ""s); + + wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(text)); + currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + + if (!id.empty()) { + LuaDialogWidget widget; + widget.id = id; + widget.type = "label"; + widget.widget = label; + widgets.push_back(widget); + } + + applyCommonOptions(label, options); + + return this; +} + +LuaDialog* LuaDialog::mapCanvas(sol::table options) { + ensureRowSizer(); + + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) { + wxMessageBox("You must have a map open to use the Map Canvas widget.", "Error", wxOK | wxICON_ERROR); + return this; + } + + std::string id = options.get_or("id", "map_c_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or("label", ""s); + + if (!labelText.empty()) { + wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); + currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + } + + // Create the canvas + MapPreviewCanvas* canvas = new MapPreviewCanvas(getParentForWidget(), *editor); + + // Smaller default size to keep the window compact + canvas->SetMinSize(wxSize(200, 150)); + + // Default to center of map start + canvas->SetPosition(1000, 1000, 7); + + currentRowSizer->Add(canvas, 1, wxEXPAND | wxALL, 0); + + if (!id.empty()) { + LuaDialogWidget widget; + widget.id = id; + widget.type = "mapCanvas"; + widget.widget = canvas; + widgets.push_back(widget); + } + + applyCommonOptions(canvas, options); + return this; +} + +LuaDialog* LuaDialog::input(sol::table options) { + ensureRowSizer(); + + std::string id = options.get_or("id", "input_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or("label", ""s); + std::string text = options.get_or("text", ""s); + bool focus = options.get_or("focus", false); + + if (!labelText.empty()) { + wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); + currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + } + + wxTextCtrl* input = new wxTextCtrl(getParentForWidget(), wxID_ANY, wxString(text), + wxDefaultPosition, wxSize(150, -1)); + currentRowSizer->Add(input, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + + input->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent& event) { + suspendHotkeys(); + event.Skip(); + }); + input->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& event) { + resumeHotkeys(); + event.Skip(); + }); + + if (focus) { + input->SetFocus(); + } + + LuaDialogWidget widget; + widget.id = id; + widget.type = "input"; + widget.widget = input; + if (options["onchange"].valid()) { + widget.onchange = options["onchange"]; + } + widgets.push_back(widget); + + // Store initial value + values[id] = sol::make_object(lua, text); + + // Bind change event + input->Bind(wxEVT_TEXT, [this, id](wxCommandEvent&) { + onWidgetChange(id); + }); + + applyCommonOptions(input, options); + return this; +} + +LuaDialog* LuaDialog::number(sol::table options) { + ensureRowSizer(); + + std::string id = options.get_or("id", "number_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or("label", ""s); + double value = options.get_or("text", options.get_or("value", 0.0)); + int decimals = options.get_or("decimals", 0); + double minVal = options.get_or("min", -999999.0); + double maxVal = options.get_or("max", 999999.0); + + if (!labelText.empty()) { + wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); + currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + } + + wxSpinCtrlDouble* spin = new wxSpinCtrlDouble(getParentForWidget(), wxID_ANY, "", + wxDefaultPosition, wxSize(100, -1), wxSP_ARROW_KEYS, minVal, maxVal, value, decimals == 0 ? 1 : std::pow(0.1, decimals)); + spin->SetDigits(decimals); + currentRowSizer->Add(spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + + LuaDialogWidget widget; + widget.id = id; + widget.type = "number"; + widget.widget = spin; + if (options["onchange"].valid()) { + widget.onchange = options["onchange"]; + } + widgets.push_back(widget); + + values[id] = sol::make_object(lua, value); + + spin->Bind(wxEVT_SPINCTRLDOUBLE, [this, id](wxSpinDoubleEvent&) { + onWidgetChange(id); + }); + + applyCommonOptions(spin, options); + return this; +} + +LuaDialog* LuaDialog::slider(sol::table options) { + ensureRowSizer(); + + std::string id = options.get_or("id", "slider_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or("label", ""s); + int value = options.get_or("value", 0); + int minVal = options.get_or("min", 0); + int maxVal = options.get_or("max", 100); + + if (!labelText.empty()) { + wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); + currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + } + + wxSlider* slider = new wxSlider(getParentForWidget(), wxID_ANY, value, minVal, maxVal, + wxDefaultPosition, wxSize(150, -1)); + currentRowSizer->Add(slider, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + + LuaDialogWidget widget; + widget.id = id; + widget.type = "slider"; + widget.widget = slider; + if (options["onchange"].valid()) { + widget.onchange = options["onchange"]; + } + widgets.push_back(widget); + + values[id] = sol::make_object(lua, value); + + slider->Bind(wxEVT_SLIDER, [this, id](wxCommandEvent&) { + onWidgetChange(id); + }); + + applyCommonOptions(slider, options); + return this; +} + +LuaDialog* LuaDialog::check(sol::table options) { + ensureRowSizer(); + + std::string id = options.get_or("id", "check_"s + std::to_string(widgets.size())); + std::string text = options.get_or("text", ""s); + bool selected = options.get_or("selected", false); + + wxCheckBox* checkbox = new wxCheckBox(getParentForWidget(), wxID_ANY, wxString(text)); + checkbox->SetValue(selected); + currentRowSizer->Add(checkbox, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + + LuaDialogWidget widget; + widget.id = id; + widget.type = "check"; + widget.widget = checkbox; + if (options["onclick"].valid()) { + widget.onclick = options["onclick"]; + } + if (options["onchange"].valid()) { + widget.onchange = options["onchange"]; + } + widgets.push_back(widget); + + values[id] = sol::make_object(lua, selected); + + checkbox->Bind(wxEVT_CHECKBOX, [this, id](wxCommandEvent&) { + onWidgetChange(id); + }); + + applyCommonOptions(checkbox, options); + return this; +} + +LuaDialog* LuaDialog::radio(sol::table options) { + ensureRowSizer(); + + std::string id = options.get_or("id", "radio_"s + std::to_string(widgets.size())); + std::string text = options.get_or("text", ""s); + bool selected = options.get_or("selected", false); + + wxRadioButton* radio = new wxRadioButton(getParentForWidget(), wxID_ANY, wxString(text)); + radio->SetValue(selected); + currentRowSizer->Add(radio, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + + LuaDialogWidget widget; + widget.id = id; + widget.type = "radio"; + widget.widget = radio; + if (options["onclick"].valid()) { + widget.onclick = options["onclick"]; + } + widgets.push_back(widget); + + values[id] = sol::make_object(lua, selected); + + radio->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) { + onWidgetChange(id); + }); + + applyCommonOptions(radio, options); + return this; +} + +LuaDialog* LuaDialog::combobox(sol::table options) { + ensureRowSizer(); + + std::string id = options.get_or("id", "combobox_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or("label", ""s); + std::string selected = options.get_or("option", ""s); + + wxArrayString choices; + if (options["options"].valid()) { + sol::table opts = options["options"]; + for (size_t i = 1; i <= opts.size(); ++i) { + if (opts[i].valid()) { + choices.Add(wxString(opts[i].get())); + } + } + } + + if (!labelText.empty()) { + wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); + currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + } + + wxChoice* choice = new wxChoice(getParentForWidget(), wxID_ANY, wxDefaultPosition, + wxSize(150, -1), choices); + + if (!selected.empty()) { + int idx = choice->FindString(wxString(selected)); + if (idx != wxNOT_FOUND) { + choice->SetSelection(idx); + } + } else if (choices.GetCount() > 0) { + choice->SetSelection(0); + selected = choices[0].ToStdString(); + } + + currentRowSizer->Add(choice, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + + LuaDialogWidget widget; + widget.id = id; + widget.type = "combobox"; + widget.widget = choice; + if (options["onchange"].valid()) { + widget.onchange = options["onchange"]; + } + widgets.push_back(widget); + + values[id] = sol::make_object(lua, selected); + + choice->Bind(wxEVT_CHOICE, [this, id](wxCommandEvent&) { + onWidgetChange(id); + }); + + applyCommonOptions(choice, options); + return this; +} + +LuaDialog* LuaDialog::button(sol::table options) { + ensureRowSizer(); + + std::string id = options.get_or("id", "button_"s + std::to_string(widgets.size())); + std::string text = options.get_or("text", "Button"s); + bool focus = options.get_or("focus", false); + + wxButton* btn = new wxButton(getParentForWidget(), wxID_ANY, wxString(text)); + currentRowSizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + + if (focus) { + btn->SetDefault(); + } + + LuaDialogWidget widget; + widget.id = id; + widget.type = "button"; + widget.widget = btn; + if (options["onclick"].valid()) { + widget.onclick = options["onclick"]; + } + widgets.push_back(widget); + + values[id] = sol::make_object(lua, false); + + btn->Bind(wxEVT_BUTTON, [this, id](wxCommandEvent&) { + onButtonClick(id); + }); + + applyCommonOptions(btn, options); + return this; +} + +LuaDialog* LuaDialog::color(sol::table options) { + ensureRowSizer(); + + std::string id = options.get_or("id", "color_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or("label", ""s); + + wxColour defaultColor = *wxBLACK; + if (options["color"].valid()) { + sol::table c = options["color"]; + int r = c.get_or("red", c.get_or(1, 0)); + int g = c.get_or("green", c.get_or(2, 0)); + int b = c.get_or("blue", c.get_or(3, 0)); + defaultColor = wxColour(r, g, b); + } + + if (!labelText.empty()) { + wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); + currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + } + + wxColourPickerCtrl* picker = new wxColourPickerCtrl(getParentForWidget(), wxID_ANY, defaultColor); + currentRowSizer->Add(picker, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + + LuaDialogWidget widget; + widget.id = id; + widget.type = "color"; + widget.widget = picker; + if (options["onchange"].valid()) { + widget.onchange = options["onchange"]; + } + widgets.push_back(widget); + + // Store color as table + sol::table colorTable = lua.create_table(); + colorTable["red"] = defaultColor.Red(); + colorTable["green"] = defaultColor.Green(); + colorTable["blue"] = defaultColor.Blue(); + values[id] = colorTable; + + picker->Bind(wxEVT_COLOURPICKER_CHANGED, [this, id](wxColourPickerEvent&) { + onWidgetChange(id); + }); + + applyCommonOptions(picker, options); + return this; +} + +/////////////////////////////////////////////////////////////////////////////// +// LuaDialogListBox - Custom list widget for LuaDialog +/////////////////////////////////////////////////////////////////////////////// + +struct LuaListBoxItem { + std::string text; + int iconId = 0; + LuaAPI::LuaImage customImage; + std::string tooltip; +}; + +class LuaDialogListBox : public wxVListBox { +public: + LuaDialogListBox(wxWindow* parent, wxWindowID id, const wxSize& size) : + wxVListBox(parent, id, wxDefaultPosition, size, wxLB_SINGLE) { + Bind(wxEVT_LEFT_DOWN, &LuaDialogListBox::OnLeftDown, this); + Bind(wxEVT_LEFT_DCLICK, &LuaDialogListBox::OnLeftDouble, this); + Bind(wxEVT_MOTION, &LuaDialogListBox::OnMotion, this); + Bind(wxEVT_LEAVE_WINDOW, &LuaDialogListBox::OnLeaveWindow, this); + } + + void AddItem(const std::string& text, int iconId, const LuaAPI::LuaImage& img, const std::string& tooltip) { + LuaListBoxItem item; + item.text = text; + item.iconId = iconId; + item.customImage = img; + item.tooltip = tooltip; + if (!item.customImage.isValid() && iconId > 0) { + item.customImage = LuaAPI::LuaImage::loadFromSprite(iconId); + } + items.push_back(item); + SetItemCount(items.size()); + } + + void Clear() { + items.clear(); + SetItemCount(0); + lastTooltipItem = -1; + } + + void SetIconSize(int width, int height) { + iconWidth = width; + iconHeight = height; + if (itemHeight < iconHeight + 6) { + itemHeight = iconHeight + 6; + } + } + + void SetItemHeight(int height) { + itemHeight = height; + } + + void SetShowText(bool value) { + showText = value; + } + + void SetSmooth(bool value) { + smooth = value; + } + + const LuaListBoxItem* GetItem(int index) const { + if (index < 0 || index >= static_cast(items.size())) { + return nullptr; + } + return &items[index]; + } + + void OnLeftDown(wxMouseEvent& event) { + int n = HitTest(event.GetPosition()); + if (n != wxNOT_FOUND) { + SetSelection(n); + CallAfter([this, n]() { + wxCommandEvent ce(wxEVT_LISTBOX, GetId()); + ce.SetInt(n); + ce.SetEventObject(this); + ProcessWindowEvent(ce); + }); + } + event.Skip(); + } + + + void OnLeftDouble(wxMouseEvent& event) { + int n = HitTest(event.GetPosition()); + if (n != wxNOT_FOUND) { + SetSelection(n); + SetFocus(); + CallAfter([this, n]() { + wxCommandEvent ce(wxEVT_LISTBOX_DCLICK, GetId()); + ce.SetInt(n); + ce.SetEventObject(this); + ProcessWindowEvent(ce); + }); + } + event.Skip(); + } + + void OnMotion(wxMouseEvent& event) { + int n = HitTest(event.GetPosition()); + if (n != wxNOT_FOUND && n < static_cast(items.size())) { + if (n != lastTooltipItem) { + const std::string& tip = items[n].tooltip; + if (!tip.empty()) { + SetToolTip(wxString(tip)); + } else { + UnsetToolTip(); + } + lastTooltipItem = n; + } + } else if (lastTooltipItem != -1) { + UnsetToolTip(); + lastTooltipItem = -1; + } + event.Skip(); + } + + void OnLeaveWindow(wxMouseEvent& event) { + UnsetToolTip(); + lastTooltipItem = -1; + event.Skip(); + } + + virtual void OnDrawItem(wxDC& dc, const wxRect& rect, size_t n) const override { + if (n >= items.size()) return; + const LuaListBoxItem& item = items[n]; + + bool isSelected = IsSelected(n); + if (isSelected) { + dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT))); + dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT))); + } else { + dc.SetBrush(*wxTRANSPARENT_BRUSH); + dc.SetPen(*wxTRANSPARENT_PEN); + } + dc.DrawRectangle(rect); + + // Icon + int textX = rect.x + 5; + if (item.customImage.isValid()) { + wxBitmap bmp = item.customImage.getBitmap(iconWidth, iconHeight, smooth); + if (bmp.IsOk()) { + dc.DrawBitmap(bmp, rect.x + 2, rect.y + (rect.height - iconHeight) / 2, true); + textX += iconWidth + 4; + } + } + + if (showText) { + if (isSelected) { + dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT)); + } else { + dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT)); + } + + wxString text = wxString(item.text); + wxSize extent = dc.GetTextExtent(text); + dc.DrawText(text, textX, rect.y + (rect.height - extent.y) / 2); + } + } + + virtual wxCoord OnMeasureItem(size_t n) const override { + return itemHeight; + } + + int iconWidth = 16; + int iconHeight = 16; + int itemHeight = 24; + bool showText = true; + bool smooth = true; + int lastTooltipItem = -1; + std::vector items; +}; + +// Specialized ListCtrl for Grid with Tooltips +class LuaGridCtrl : public wxListCtrl { +public: + LuaGridCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style) : + wxListCtrl(parent, id, pos, size, style) { + Bind(wxEVT_MOTION, &LuaGridCtrl::OnMotion, this); + Bind(wxEVT_LEAVE_WINDOW, &LuaGridCtrl::OnLeaveWindow, this); + } + + std::map tooltips; + long lastTooltipItem = -1; + int iconWidth = 32; + int iconHeight = 32; + int cellWidth = -1; + int cellHeight = -1; + + void AddTooltip(long item, const std::string& tip) { + tooltips[item] = tip; + } + + void ClearTooltips() { + tooltips.clear(); + lastTooltipItem = -1; + } + + std::string GetTooltip(long item) const { + auto it = tooltips.find(item); + if (it == tooltips.end()) { + return ""s; + } + return it->second; + } + + void OnMotion(wxMouseEvent& event) { + int flags = 0; + long index = HitTest(event.GetPosition(), flags); + if (index != wxNOT_FOUND) { + if (index != lastTooltipItem) { + if (tooltips.count(index)) { + SetToolTip(wxString(tooltips[index])); + } else { + UnsetToolTip(); + } + lastTooltipItem = index; + } + } else { + if (lastTooltipItem != -1) { + UnsetToolTip(); + lastTooltipItem = -1; + } + } + event.Skip(); + } + + void OnLeaveWindow(wxMouseEvent& event) { + UnsetToolTip(); + lastTooltipItem = -1; + event.Skip(); + } +}; + + + +LuaDialog* LuaDialog::list(sol::table options) { + finishCurrentRow(); + + std::string id = options.get_or("id", "list_"s + std::to_string(widgets.size())); + int width = options.get_or("width", 200); + int height = options.get_or("height", 150); + int iconWidth = options.get_or("icon_width", 16); + int iconHeight = options.get_or("icon_height", 16); + int iconSize = options.get_or("icon_size", -1); + int itemHeight = options.get_or("item_height", 24); + bool showText = options.get_or("show_text", true); + bool smooth = options.get_or("smooth", true); + + if (iconSize > 0) { + iconWidth = iconSize; + iconHeight = iconSize; + } + + LuaDialogListBox* listbox = new LuaDialogListBox(getParentForWidget(), wxID_ANY, wxSize(width, height)); + listbox->SetIconSize(iconWidth, iconHeight); + if (itemHeight > 0) { + listbox->SetItemHeight(itemHeight); + } + listbox->SetShowText(showText); + listbox->SetSmooth(smooth); + getSizerForWidget()->Add(listbox, 1, wxEXPAND | wxALL, 5); + + // Populate items + if (options["items"].valid()) { + sol::table itemsTable = options["items"]; + for (auto& pair : itemsTable) { + if (pair.second.is()) { + sol::table itemTable = pair.second; + std::string text = itemTable.get_or("text", ""s); + int icon = itemTable.get_or("icon", 0); + std::string tooltip = itemTable.get_or("tooltip", ""s); + LuaAPI::LuaImage img; + if (itemTable["image"].valid()) { + if (itemTable["image"].is()) { + img = itemTable["image"].get(); + } else if (itemTable["image"].is()) { + img = LuaAPI::LuaImage::loadFromFile(itemTable["image"].get()); + } + } + listbox->AddItem(text, icon, img, tooltip); + } + } + } + + // Selection + int selection = options.get_or("selection", 0); + if (selection > 0 && selection <= (int)listbox->items.size()) { + listbox->SetSelection(selection - 1); + } + + LuaDialogWidget widget; + widget.id = id; + widget.type = "list"; + widget.widget = listbox; + + if (options["onchange"].valid()) { + widget.onchange = options["onchange"]; + } + if (options["ondoubleclick"].valid()) { + widget.ondoubleclick = options["ondoubleclick"]; + } + if (options["oncontextmenu"].valid()) { + widget.oncontextmenu = options["oncontextmenu"]; + } + if (options["onleftclick"].valid()) { + widget.onleftclick = options["onleftclick"]; + } + if (options["onrightclick"].valid()) { + widget.onrightclick = options["onrightclick"]; + } + widgets.push_back(widget); + + // Bind selection event + listbox->Bind(wxEVT_LISTBOX, [this, id](wxCommandEvent&) { + onWidgetChange(id); + }); + + // Bind double click event + listbox->Bind(wxEVT_LISTBOX_DCLICK, [this, id](wxCommandEvent&) { + onWidgetDoubleClick(id); + }); + + sol::function listLeftClick = widget.onleftclick; + listbox->Bind(wxEVT_LEFT_DOWN, [this, id, listbox, listLeftClick](wxMouseEvent& event) { + if (!listLeftClick.valid() || !lua.lua_state() || !listLeftClick.lua_state()) { + event.Skip(); + return; + } + int index = listbox->HitTest(event.GetPosition()); + if (index == wxNOT_FOUND) { + event.Skip(); + return; + } + const LuaListBoxItem* item = listbox->GetItem(index); + if (!item) { + event.Skip(); + return; + } + listbox->SetSelection(index); + listbox->SetFocus(); + values[id] = sol::make_object(lua, index + 1); + sol::table info = lua.create_table(); + info["type"] = "list"; + info["index"] = index + 1; + info["text"] = item->text; + if (!item->tooltip.empty()) { + info["tooltip"] = item->tooltip; + } + info["widget_id"] = id; + try { + listLeftClick(this, info); + } catch (const sol::error& e) { + wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); + } + event.Skip(); + }); + + sol::function listRightClick = widget.onrightclick; + listbox->Bind(wxEVT_RIGHT_DOWN, [this, id, listbox, listRightClick](wxMouseEvent& event) { + if (!listRightClick.valid() || !lua.lua_state() || !listRightClick.lua_state()) { + event.Skip(); + return; + } + int index = listbox->HitTest(event.GetPosition()); + if (index == wxNOT_FOUND) { + event.Skip(); + return; + } + const LuaListBoxItem* item = listbox->GetItem(index); + if (!item) { + event.Skip(); + return; + } + listbox->SetSelection(index); + listbox->SetFocus(); + values[id] = sol::make_object(lua, index + 1); + sol::table info = lua.create_table(); + info["type"] = "list"; + info["index"] = index + 1; + info["text"] = item->text; + if (!item->tooltip.empty()) { + info["tooltip"] = item->tooltip; + } + info["widget_id"] = id; + try { + listRightClick(this, info); + } catch (const sol::error& e) { + wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); + } + event.Skip(); + }); + + // Bind context menu event + sol::function listContextMenu = widget.oncontextmenu; + listbox->Bind(wxEVT_CONTEXT_MENU, [this, id, listbox, listContextMenu](wxContextMenuEvent& event) { + if (!listContextMenu.valid() || !lua.lua_state() || !listContextMenu.lua_state()) { + return; + } + wxPoint screenPos = event.GetPosition(); + if (screenPos == wxDefaultPosition) { + screenPos = wxGetMousePosition(); + } + wxPoint clientPos = listbox->ScreenToClient(screenPos); + int index = listbox->HitTest(clientPos); + if (index == wxNOT_FOUND) { + return; + } + + const LuaListBoxItem* item = listbox->GetItem(index); + if (!item) { + return; + } + + sol::table info = lua.create_table(); + info["type"] = "list"; + info["index"] = index + 1; + info["text"] = item->text; + if (!item->tooltip.empty()) { + info["tooltip"] = item->tooltip; + } + info["widget_id"] = id; + popupContextMenu(listContextMenu, info, listbox, screenPos); + }); + + applyCommonOptions(listbox, options); + return this; +} + +LuaDialog* LuaDialog::file(sol::table options) { + ensureRowSizer(); + + std::string id = options.get_or("id", "file_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or("label", ""s); + std::string filename = options.get_or("filename", ""s); + std::string filetypes = options.get_or("filetypes", "*.*"s); + bool save = options.get_or("save", false); + + if (!labelText.empty()) { + wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); + currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + } + + wxFilePickerCtrl* picker; + if (save) { + picker = new wxFilePickerCtrl(getParentForWidget(), wxID_ANY, wxString(filename), + "Select a file", wxString(filetypes), wxDefaultPosition, wxSize(200, -1), + wxFLP_SAVE | wxFLP_USE_TEXTCTRL); + } else { + picker = new wxFilePickerCtrl(getParentForWidget(), wxID_ANY, wxString(filename), + "Select a file", wxString(filetypes), wxDefaultPosition, wxSize(200, -1), + wxFLP_OPEN | wxFLP_FILE_MUST_EXIST | wxFLP_USE_TEXTCTRL); + } + currentRowSizer->Add(picker, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + + LuaDialogWidget widget; + widget.id = id; + widget.type = "file"; + widget.widget = picker; + if (options["onchange"].valid()) { + widget.onchange = options["onchange"]; + } + widgets.push_back(widget); + + values[id] = sol::make_object(lua, filename); + + picker->Bind(wxEVT_FILEPICKER_CHANGED, [this, id](wxFileDirPickerEvent&) { + onWidgetChange(id); + }); + + applyCommonOptions(picker, options); + return this; +} + +LuaDialog* LuaDialog::image(sol::table options) { + ensureRowSizer(); + + std::string id = options.get_or("id", "image_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or("label", ""s); + int width = options.get_or("width", -1); + int height = options.get_or("height", -1); + bool smooth = options.get_or("smooth", true); // Default to smooth scaling + + if (!labelText.empty()) { + wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); + currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + } + + wxBitmap bmp; + LuaAPI::LuaImage luaImage; + + // Check for different image sources + if (options["image"].valid()) { + // Direct LuaImage object + luaImage = options["image"].get(); + } else if (options["path"].valid()) { + // Load from file path + std::string path = options.get("path"); + luaImage = LuaAPI::LuaImage::loadFromFile(path); + } else if (options["itemid"].valid()) { + // Load from item sprite ID + int itemId = options.get("itemid"); + luaImage = LuaAPI::LuaImage::loadFromItemSprite(itemId); + } else if (options["spriteid"].valid()) { + // Load from raw sprite ID + int spriteId = options.get("spriteid"); + luaImage = LuaAPI::LuaImage::loadFromSprite(spriteId); + } + + // Get bitmap with optional resizing + if (luaImage.isValid()) { + if (width > 0 && height > 0) { + bmp = luaImage.getBitmap(width, height, smooth); + } else if (width > 0) { + // Scale proportionally based on width + double factor = static_cast(width) / luaImage.getWidth(); + int newHeight = static_cast(luaImage.getHeight() * factor); + bmp = luaImage.getBitmap(width, newHeight, smooth); + } else if (height > 0) { + // Scale proportionally based on height + double factor = static_cast(height) / luaImage.getHeight(); + int newWidth = static_cast(luaImage.getWidth() * factor); + bmp = luaImage.getBitmap(newWidth, height, smooth); + } else { + bmp = luaImage.getBitmap(); + } + } + + // Create placeholder if no valid image + if (!bmp.IsOk()) { + int placeholderW = width > 0 ? width : 32; + int placeholderH = height > 0 ? height : 32; + bmp.Create(placeholderW, placeholderH); + wxMemoryDC dc(bmp); + dc.SetBrush(*wxLIGHT_GREY_BRUSH); + dc.SetPen(*wxGREY_PEN); + dc.DrawRectangle(0, 0, placeholderW, placeholderH); + dc.SelectObject(wxNullBitmap); + } + + wxStaticBitmap* staticBmp = new wxStaticBitmap(getParentForWidget(), wxID_ANY, bmp); + currentRowSizer->Add(staticBmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + + LuaDialogWidget widget; + widget.id = id; + widget.type = "image"; + widget.widget = staticBmp; + widgets.push_back(widget); + + // Store the LuaImage in values for later retrieval/modification + values[id] = sol::make_object(lua, luaImage); + + applyCommonOptions(staticBmp, options); + return this; +} + +LuaDialog* LuaDialog::item(sol::table options) { + ensureRowSizer(); + + std::string id = options.get_or("id", "item_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or("label", ""s); + int itemId = options.get_or("itemid", 0); + + if (!labelText.empty()) { + wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); + currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + } + + int clientId = (itemId > 100 && itemId <= g_items.getMaxID()) ? g_items[itemId].clientID : 0; + ItemButton* btn = new ItemButton(getParentForWidget(), RENDER_SIZE_32x32, clientId); + currentRowSizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + + LuaDialogWidget widget; + widget.id = id; + widget.type = "item"; + widget.widget = btn; + if (options["onchange"].valid()) { + widget.onchange = options["onchange"]; + } + if (options["onclick"].valid()) { + widget.onclick = options["onclick"]; + } + widgets.push_back(widget); + + values[id] = sol::make_object(lua, itemId); + + bool readonly = options.get_or("readonly", false); + + // Bind click event + btn->Bind(wxEVT_BUTTON, [this, id, btn, readonly](wxCommandEvent&) { + // Check if we have a custom click handler + bool handled = false; + for (auto& w : widgets) { + if (w.id == id && w.onclick.valid()) { + onButtonClick(id); + handled = true; + break; + } + } + + if (!handled && !readonly) { + FindItemDialog dlg(this, "Select Item", false); + if (dlg.ShowModal() == wxID_OK) { + int newItemId = dlg.getResultID(); + int newClientId = (newItemId > 100 && newItemId <= g_items.getMaxID()) ? g_items[newItemId].clientID : 0; + btn->SetSprite(newClientId); + btn->Refresh(); // Ensure visual update holding the new sprite + values[id] = sol::make_object(lua, newItemId); + onWidgetChange(id); + } + } + }); + + applyCommonOptions(btn, options); + return this; +} + +LuaDialog* LuaDialog::separator() { + finishCurrentRow(); + + wxStaticLine* line = new wxStaticLine(getParentForWidget(), wxID_ANY, + wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); + getSizerForWidget()->Add(line, 0, wxEXPAND | wxALL, 5); + + // Separator doesn't support many options but we can try + // applyCommonOptions(line, options); // options not passed to separator() usually + // But signature is LuaDialog* separator(); so no options table. + + return this; +} + +LuaDialog* LuaDialog::newrow() { + finishCurrentRow(); + return this; +} + +LuaDialog* LuaDialog::tab(sol::table options) { + finishCurrentRow(); + + std::string id = options.get_or("id", "tab_"s + std::to_string(widgets.size())); + std::string text = options.get_or("text", "Tab"s); + bool isButton = options.get_or("button", false) || options.get_or("is_button", false); + int insertIndex = options.get_or("index", -1); + sol::function onclick; + sol::function oncontextmenu; + if (options["onclick"].valid()) { + onclick = options["onclick"]; + } + if (options["oncontextmenu"].valid()) { + oncontextmenu = options["oncontextmenu"]; + } + + // Create notebook if needed + if (!currentNotebook) { + wxWindow* parent = dockPanel ? static_cast(dockPanel) : static_cast(this); + currentNotebook = new wxNotebook(parent, wxID_ANY); + mainSizer->Add(currentNotebook, 1, wxEXPAND | wxALL, 5); + activeNotebook = currentNotebook; + tabInfos.clear(); + notebookEventsBound = false; + } + + if (!notebookEventsBound) { + notebookEventsBound = true; + currentNotebook->Bind(wxEVT_NOTEBOOK_PAGE_CHANGING, [this](wxNotebookEvent& event) { + int newSelection = event.GetSelection(); + if (newSelection != wxNOT_FOUND && newSelection < static_cast(tabInfos.size())) { + if (tabInfos[newSelection].isButton) { + if (suppressTabButtonClick && suppressTabButtonIndex == newSelection) { + suppressTabButtonClick = false; + suppressTabButtonIndex = -1; + event.Veto(); + return; + } + CallAfter([this, newSelection]() { + handleTabButtonClick(newSelection); + }); + event.Veto(); + return; + } + } + event.Skip(); + }); + + currentNotebook->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) { + if (!activeNotebook || tabInfos.empty()) { + event.Skip(); + return; + } + wxPoint pos = event.GetPosition(); + long flags = 0; + int index = activeNotebook->HitTest(pos, &flags); + if (index != wxNOT_FOUND && index < static_cast(tabInfos.size())) { + if (tabInfos[index].isButton) { + suppressTabButtonClick = true; + suppressTabButtonIndex = index; + CallAfter([this, index]() { + handleTabButtonClick(index); + }); + CallAfter([this, index]() { + if (suppressTabButtonClick && suppressTabButtonIndex == index) { + suppressTabButtonClick = false; + suppressTabButtonIndex = -1; + } + }); + event.StopPropagation(); + return; + } + } + event.Skip(); + }); + + currentNotebook->Bind(wxEVT_CONTEXT_MENU, [this](wxContextMenuEvent& event) { + if (!activeNotebook || tabInfos.empty()) { + return; + } + wxPoint screenPos = event.GetPosition(); + if (screenPos == wxDefaultPosition) { + screenPos = wxGetMousePosition(); + } + wxPoint clientPos = activeNotebook->ScreenToClient(screenPos); + long flags = 0; + int index = activeNotebook->HitTest(clientPos, &flags); + if (index == wxNOT_FOUND || index >= static_cast(tabInfos.size())) { + return; + } + handleTabContextMenu(index, screenPos); + }); + } + + // Create new tab panel + currentTabPanel = new wxPanel(currentNotebook); + currentTabSizer = new wxBoxSizer(wxVERTICAL); + currentTabPanel->SetSizer(currentTabSizer); + int pageIndex = -1; + if (insertIndex > 0) { + int zeroBased = insertIndex - 1; + int pageCount = currentNotebook->GetPageCount(); + if (zeroBased < 0) { + zeroBased = 0; + } else if (zeroBased > pageCount) { + zeroBased = pageCount; + } + currentNotebook->InsertPage(zeroBased, currentTabPanel, wxString(text)); + pageIndex = zeroBased; + } else { + currentNotebook->AddPage(currentTabPanel, wxString(text)); + pageIndex = currentNotebook->GetPageCount() - 1; + } + + LuaDialogTab tabInfo; + tabInfo.id = id; + tabInfo.text = text; + tabInfo.isButton = isButton; + tabInfo.onclick = onclick; + tabInfo.oncontextmenu = oncontextmenu; + if (pageIndex >= 0 && pageIndex <= static_cast(tabInfos.size())) { + tabInfos.insert(tabInfos.begin() + pageIndex, tabInfo); + } else { + tabInfos.push_back(tabInfo); + } + + sizerStack.push(currentTabSizer); + tabSizers.push_back(currentTabSizer); + + return this; +} + +LuaDialog* LuaDialog::endtabs() { + finishCurrentRow(); + // Pop all tab sizers that were added + while (!tabSizers.empty()) { + wxSizer* topSizer = tabSizers.back(); + if (!sizerStack.empty() && sizerStack.top() == topSizer) { + sizerStack.pop(); + } + tabSizers.pop_back(); + } + + currentNotebook = nullptr; + currentTabPanel = nullptr; + currentTabSizer = nullptr; + return this; +} + +sol::table LuaDialog::makeTabInfoTable(int index) { + sol::table info = lua.create_table(); + if (index < 0 || index >= static_cast(tabInfos.size())) { + return info; + } + const LuaDialogTab& tab = tabInfos[index]; + info["index"] = index + 1; + info["text"] = tab.text; + info["id"] = tab.id; + info["is_button"] = tab.isButton; + return info; +} + +void LuaDialog::handleTabButtonClick(int index) { + if (index < 0 || index >= static_cast(tabInfos.size())) { + return; + } + sol::function onclick = tabInfos[index].onclick; + if (!onclick.valid()) { + return; + } + if (!lua.lua_state()) { + return; + } + if (!onclick.lua_state()) { + return; + } + sol::table info = makeTabInfoTable(index); + try { + onclick(this, info); + } catch (const sol::error& e) { + wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); + } +} + +void LuaDialog::handleTabContextMenu(int index, const wxPoint& screenPos) { + if (index < 0 || index >= static_cast(tabInfos.size())) { + return; + } + sol::function oncontextmenu = tabInfos[index].oncontextmenu; + if (!oncontextmenu.valid()) { + return; + } + if (!lua.lua_state()) { + return; + } + if (!oncontextmenu.lua_state()) { + return; + } + + sol::table info = makeTabInfoTable(index); + info["type"] = "tab"; + popupContextMenu(oncontextmenu, info, activeNotebook, screenPos); +} + +void LuaDialog::popupContextMenu(const sol::function& callback, sol::table info, wxWindow* window, const wxPoint& screenPos) { + if (!callback.valid()) { + return; + } + if (!lua.lua_state() || !callback.lua_state()) { + return; + } + + sol::object result; + try { + result = callback(this, info); + } catch (const sol::error& e) { + wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); + return; + } + + if (!result.valid() || !result.is()) { + return; + } + + sol::table items = result.as(); + wxMenu menu; + std::map callbacks; + int nextId = wxID_HIGHEST + 1; + + for (auto& pair : items) { + if (!pair.second.is()) { + continue; + } + sol::table item = pair.second.as(); + bool separator = item.get_or("separator", false); + std::string text = item.get_or("text", ""s); + + if (separator || text.empty()) { + menu.AppendSeparator(); + continue; + } + + int id = nextId++; + wxMenuItem* menuItem = menu.Append(id, wxString(text)); + bool enabled = item.get_or("enabled", true); + menuItem->Enable(enabled); + + if (item["onclick"].valid()) { + sol::function fn = item["onclick"]; + if (fn.lua_state()) { + callbacks[id] = fn; + } + } + } + + if (menu.GetMenuItemCount() == 0) { + return; + } + + menu.Bind(wxEVT_MENU, [this, callbacks, info](wxCommandEvent& event) mutable { + if (!lua.lua_state()) { + return; + } + auto it = callbacks.find(event.GetId()); + if (it == callbacks.end()) { + return; + } + if (!it->second.lua_state()) { + return; + } + try { + it->second(this, info); + } catch (const sol::error& e) { + wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); + } + }); + + if (!window) { + return; + } + wxPoint clientPos = window->ScreenToClient(screenPos); + window->PopupMenu(&menu, clientPos); +} + +LuaDialog* LuaDialog::show(sol::optional options) { + if (dockPanel) { + wxAuiPaneInfo& info = g_gui.aui_manager->GetPane(dockPanel); + if (info.IsOk()) { + info.Show(); + g_gui.aui_manager->Update(); + } + isShowing = true; + return this; + } + + // Fit the dialog first to establish baseline size from sizers + Fit(); + + // Re-apply requested size from constructor if it's larger than fitted size + if (reqWidth != -1 || reqHeight != -1) { + wxSize sz = GetSize(); + if (reqWidth != -1) sz.x = std::max(sz.x, reqWidth); + if (reqHeight != -1) sz.y = std::max(sz.y, reqHeight); + SetSize(sz); + } + + // Process options + if (options) { + sol::table opts = *options; + waitMode = opts.get_or("wait", true); + + if (opts["bounds"].valid()) { + setBounds(opts["bounds"]); + } else if (reqX == -1 && reqY == -1) { + bool wantsCenter = true; + std::string centerOn = "parent"; + if (opts["center"].valid()) { + if (opts["center"].is()) { + wantsCenter = opts.get("center"); + } else if (opts["center"].is()) { + centerOn = opts.get("center"); + } + } + if (opts["center_on"].valid() && opts["center_on"].is()) { + centerOn = opts.get("center_on"); + } + if (wantsCenter) { + if (centerOn == "screen") { + Centre(); + } else { + CentreOnParent(); + } + } + } + } else { + if (reqX == -1 && reqY == -1) { + CentreOnParent(); + } + } + + isShowing = true; + + if (waitMode) { + ShowModal(); + isShowing = false; + } else { + Show(); + } + + return this; +} + +void LuaDialog::close() { + while (hotkeySuspendCount > 0) { + resumeHotkeys(); + } + if (dockPanel) { + wxAuiPaneInfo& info = g_gui.aui_manager->GetPane(dockPanel); + if (info.IsOk()) { + info.Hide(); + g_gui.aui_manager->Update(); + } + isShowing = false; + if (oncloseCallback.valid()) { + try { + oncloseCallback(); + } catch (const sol::error& e) { + wxMessageBox(wxString("Script error in onclose: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); + } + } + return; + } + + if (IsModal()) { + EndModal(wxID_OK); + } else { + Hide(); + } + isShowing = false; + if (oncloseCallback.valid()) { + try { + oncloseCallback(); + } catch (const sol::error& e) { + wxMessageBox(wxString("Script error in onclose: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); + } + } +} + +void LuaDialog::OnClose(wxCloseEvent& event) { + if (IsModal()) { + EndModal(wxID_CANCEL); + } else { + Hide(); + } + isShowing = false; + + if (oncloseCallback.valid()) { + try { + oncloseCallback(); + } catch (const sol::error& e) { + wxMessageBox(wxString("Script error in onclose: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); + } + } + + if (!IsModal() && event.CanVeto()) { + // Prevent destruction of modeless dialog so it can be reused + // event.Veto() is not needed if we don't Skip(), but it's safer + event.Veto(); + return; + } + event.Skip(); +} + +LuaDialog* LuaDialog::modify(sol::table options) { + // Modify widget properties + for (auto& pair : options) { + std::string id = pair.first.as(); + sol::table props = pair.second.as(); + + for (auto& widget : widgets) { + if (widget.id == id) { + // Apply property changes based on widget type + if (widget.type == "mapCanvas") { + MapPreviewCanvas* canvas = static_cast(widget.widget); + if (props["x"].valid() && props["y"].valid()) { + canvas->SetPosition(props.get_or("x", 1000), props.get_or("y", 1000), props.get_or("z", (int)canvas->GetFloor())); + } + if (props["zoom"].valid()) { + canvas->SetZoom(props.get_or("zoom", 1.0)); + } + if (props["floor"].valid()) { + canvas->SetFloor(props.get_or("floor", 7)); + canvas->Refresh(); + } + if (props["sync"].valid() && props.get_or("sync", false)) { + canvas->SyncView(); + } + if (props["client_w"].valid() && props["client_h"].valid()) { + canvas->SetViewSize(props.get_or("client_w", ClientMapWidth), props.get_or("client_h", ClientMapHeight)); + } + if (props["light"].valid()) { + canvas->SetLight(props.get_or("light", false)); + } + } else if (widget.type == "input") { + wxTextCtrl* ctrl = static_cast(widget.widget); + if (props["text"].valid()) { + ctrl->SetValue(wxString(props.get_or("text", ""s))); + } + } else if (widget.type == "number") { + wxSpinCtrlDouble* ctrl = static_cast(widget.widget); + if (props["value"].valid()) { + ctrl->SetValue(props.get_or("value", 0.0)); + } + } else if (widget.type == "check") { + wxCheckBox* ctrl = static_cast(widget.widget); + if (props["selected"].valid()) { + ctrl->SetValue(props.get_or("selected", false)); + } + } else if (widget.type == "item") { + ItemButton* ctrl = static_cast(widget.widget); + if (props["itemid"].valid()) { + int newItemId = props.get_or("itemid", 0); + ctrl->SetSprite(newItemId); + ctrl->Refresh(); + } + } else if (widget.type == "label") { + wxStaticText* ctrl = static_cast(widget.widget); + if (props["text"].valid()) { + ctrl->SetLabel(wxString(props.get_or("text", ""s))); + } + } else if (widget.type == "list") { + LuaDialogListBox* ctrl = static_cast(widget.widget); + if (props["icon_width"].valid() || props["icon_height"].valid() || props["icon_size"].valid()) { + int iconWidth = props.get_or("icon_width", ctrl->iconWidth); + int iconHeight = props.get_or("icon_height", ctrl->iconHeight); + int iconSize = props.get_or("icon_size", -1); + if (iconSize > 0) { + iconWidth = iconSize; + iconHeight = iconSize; + } + ctrl->SetIconSize(iconWidth, iconHeight); + } + if (props["item_height"].valid()) { + ctrl->SetItemHeight(props.get_or("item_height", ctrl->itemHeight)); + } + if (props["show_text"].valid()) { + ctrl->SetShowText(props.get_or("show_text", true)); + } + if (props["smooth"].valid()) { + ctrl->SetSmooth(props.get_or("smooth", true)); + } + if (props["items"].valid()) { + ctrl->Freeze(); + ctrl->Clear(); + sol::table itemsTable = props["items"]; + for (auto& pair : itemsTable) { + if (pair.second.is()) { + sol::table itemTable = pair.second; + std::string text = itemTable.get_or("text", ""s); + int icon = itemTable.get_or("icon", 0); + std::string tooltip = itemTable.get_or("tooltip", ""s); + LuaAPI::LuaImage img; + if (itemTable["image"].valid()) { + if (itemTable["image"].is()) { + img = itemTable["image"].get(); + } else if (itemTable["image"].is()) { + img = LuaAPI::LuaImage::loadFromFile(itemTable["image"].get()); + } + } + ctrl->AddItem(text, icon, img, tooltip); + } + } + ctrl->Refresh(); + ctrl->Thaw(); + } + if (props["selection"].valid()) { + int selection = props.get_or("selection", 0); + if (selection > 0 && selection <= (int)ctrl->items.size()) { + ctrl->SetSelection(selection - 1); + } else { + ctrl->SetSelection(wxNOT_FOUND); + } + } + + } else if (widget.type == "grid") { + LuaGridCtrl* ctrl = static_cast(widget.widget); + int iconWidth = ctrl->iconWidth; + int iconHeight = ctrl->iconHeight; + int cellWidth = ctrl->cellWidth; + int cellHeight = ctrl->cellHeight; + bool updateIconSize = false; + bool updateCellSize = false; + + int iconSize = props.get_or("icon_size", -1); + int itemSize = props.get_or("item_size", -1); + int itemWidth = props.get_or("item_width", -1); + int itemHeight = props.get_or("item_height", -1); + int iconWidthOpt = props.get_or("icon_width", -1); + int iconHeightOpt = props.get_or("icon_height", -1); + int cellSize = props.get_or("cell_size", -1); + int cellWidthOpt = props.get_or("cell_width", -1); + int cellHeightOpt = props.get_or("cell_height", -1); + + if (iconSize > 0) { + iconWidth = iconSize; + iconHeight = iconSize; + updateIconSize = true; + } + if (itemSize > 0) { + iconWidth = itemSize; + iconHeight = itemSize; + updateIconSize = true; + } + if (itemWidth > 0) { + iconWidth = itemWidth; + updateIconSize = true; + } + if (itemHeight > 0) { + iconHeight = itemHeight; + updateIconSize = true; + } + if (iconWidthOpt > 0) { + iconWidth = iconWidthOpt; + updateIconSize = true; + } + if (iconHeightOpt > 0) { + iconHeight = iconHeightOpt; + updateIconSize = true; + } + + if (cellSize > 0) { + cellWidth = cellSize; + cellHeight = cellSize; + updateCellSize = true; + } + if (cellWidthOpt > 0) { + cellWidth = cellWidthOpt; + updateCellSize = true; + } + if (cellHeightOpt > 0) { + cellHeight = cellHeightOpt; + updateCellSize = true; + } + + if (updateCellSize) { + if (cellWidth <= 0) { + cellWidth = iconWidth + 8; + } + if (cellHeight <= 0) { + cellHeight = iconHeight + 8; + } +#ifdef __WXMSW__ + ListView_SetIconSpacing(static_cast(ctrl->GetHandle()), cellWidth, cellHeight); +#endif + ctrl->cellWidth = cellWidth; + ctrl->cellHeight = cellHeight; + } + if (props["selection"].valid()) { + int selection = props.get_or("selection", 0); + long item = -1; + while ((item = ctrl->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) { + ctrl->SetItemState(item, 0, wxLIST_STATE_SELECTED); + } + if (selection > 0 && selection <= ctrl->GetItemCount()) { + ctrl->SetItemState(selection - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } + } + + + if (props["items"].valid()) { + ctrl->Freeze(); + ctrl->DeleteAllItems(); + ctrl->ClearTooltips(); + wxImageList* imageList = ctrl->GetImageList(wxIMAGE_LIST_NORMAL); + if (!imageList || updateIconSize) { + imageList = new wxImageList(iconWidth, iconHeight, true); + ctrl->AssignImageList(imageList, wxIMAGE_LIST_NORMAL); + ctrl->iconWidth = iconWidth; + ctrl->iconHeight = iconHeight; + } else { + imageList->RemoveAll(); + } + + sol::table itemsTable = props["items"]; + for (auto& pair : itemsTable) { + if (pair.second.is()) { + sol::table itemTable = pair.second; + std::string text = itemTable.get_or("text", ""s); + + LuaAPI::LuaImage img; + int imageIndex = -1; + + if (itemTable["image"].valid()) { + if (itemTable["image"].is()) { + img = itemTable["image"].get(); + } else if (itemTable["image"].is()) { + img = LuaAPI::LuaImage::loadFromFile(itemTable["image"].get()); + } + } + + if (img.isValid()) { + imageIndex = imageList->Add(img.getBitmap(iconWidth, iconHeight)); + } + + long index = ctrl->InsertItem(ctrl->GetItemCount(), text, imageIndex); + ctrl->SetItemData(index, index + 1); + + std::string tooltip = itemTable.get_or("tooltip", ""s); + if (!tooltip.empty()) { + // Must match the type cast + LuaGridCtrl* gridCtrl = static_cast(ctrl); + gridCtrl->AddTooltip(index, tooltip); + } + } + } + ctrl->Thaw(); + } + } else if (widget.type == "image") { + wxStaticBitmap* ctrl = static_cast(widget.widget); + + LuaAPI::LuaImage luaImage; + int width = props.get_or("width", -1); + int height = props.get_or("height", -1); + bool smooth = props.get_or("smooth", true); + + if (props["image"].valid()) { + luaImage = props["image"].get(); + } else if (props["path"].valid()) { + std::string path = props.get("path"); + luaImage = LuaAPI::LuaImage::loadFromFile(path); + } else if (props["itemid"].valid()) { + int itemId = props.get("itemid"); + luaImage = LuaAPI::LuaImage::loadFromItemSprite(itemId); + } else if (props["spriteid"].valid()) { + int spriteId = props.get("spriteid"); + luaImage = LuaAPI::LuaImage::loadFromSprite(spriteId); + } + + if (luaImage.isValid()) { + wxBitmap bmp; + if (width > 0 && height > 0) { + bmp = luaImage.getBitmap(width, height, smooth); + } else if (width > 0) { + double factor = static_cast(width) / luaImage.getWidth(); + int newHeight = static_cast(luaImage.getHeight() * factor); + bmp = luaImage.getBitmap(width, newHeight, smooth); + } else if (height > 0) { + double factor = static_cast(height) / luaImage.getHeight(); + int newWidth = static_cast(luaImage.getWidth() * factor); + bmp = luaImage.getBitmap(newWidth, height, smooth); + } else { + bmp = luaImage.getBitmap(); + } + if (bmp.IsOk()) { + ctrl->SetBitmap(bmp); + values[id] = sol::make_object(lua, luaImage); + } + } + } + + applyCommonOptions(widget.widget, props); + break; + } + } + } + return this; +} + +LuaDialog* LuaDialog::grid(sol::table options) { + ensureRowSizer(); + + std::string id = options.get_or("id", "grid_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or("label", ""s); + int iconWidth = options.get_or("icon_width", 32); + int iconHeight = options.get_or("icon_height", 32); + int iconSize = options.get_or("icon_size", -1); + int itemSize = options.get_or("item_size", -1); + int itemWidth = options.get_or("item_width", -1); + int itemHeight = options.get_or("item_height", -1); + int cellSize = options.get_or("cell_size", -1); + int cellWidth = options.get_or("cell_width", -1); + int cellHeight = options.get_or("cell_height", -1); + bool labelWrap = options.get_or("label_wrap", true); + bool showText = options.get_or("show_text", true); + + if (iconSize > 0) { + iconWidth = iconSize; + iconHeight = iconSize; + } + if (itemSize > 0) { + iconWidth = itemSize; + iconHeight = itemSize; + } + if (itemWidth > 0) { + iconWidth = itemWidth; + } + if (itemHeight > 0) { + iconHeight = itemHeight; + } + if (iconWidth <= 0) { + iconWidth = 32; + } + if (iconHeight <= 0) { + iconHeight = 32; + } + if (cellSize > 0) { + cellWidth = cellSize; + cellHeight = cellSize; + } + + if (!labelText.empty()) { + wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); + currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + } + + // Use LuaGridCtrl in Icon mode for grid view + LuaGridCtrl* grid = new LuaGridCtrl(getParentForWidget(), wxID_ANY, wxDefaultPosition, wxSize(300, 200), wxLC_ICON | wxLC_AUTOARRANGE | wxLC_SINGLE_SEL); + + // Create an image list + wxImageList* imageList = new wxImageList(iconWidth, iconHeight, true); + grid->AssignImageList(imageList, wxIMAGE_LIST_NORMAL); + + if (!showText) { + wxFont font = grid->GetFont(); + font.SetPointSize(1); + grid->SetFont(font); + } + +#ifdef __WXMSW__ + if (!labelWrap) { + HWND hwnd = static_cast(grid->GetHandle()); + LONG_PTR style = ::GetWindowLongPtr(hwnd, GWL_STYLE); + style |= LVS_NOLABELWRAP; + ::SetWindowLongPtr(hwnd, GWL_STYLE, style); + ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); + } + + if (cellWidth > 0 || cellHeight > 0) { + if (cellWidth <= 0) { + cellWidth = iconWidth + 8; + } + if (cellHeight <= 0) { + cellHeight = iconHeight + 8; + } + ListView_SetIconSpacing(static_cast(grid->GetHandle()), cellWidth, cellHeight); + } +#endif + grid->iconWidth = iconWidth; + grid->iconHeight = iconHeight; + grid->cellWidth = cellWidth; + grid->cellHeight = cellHeight; + + if (options["items"].valid()) { + sol::table itemsTable = options["items"]; + for (auto& pair : itemsTable) { + if (pair.second.is()) { + sol::table itemTable = pair.second; + std::string text = itemTable.get_or("text", ""s); + if (!showText) { + text.clear(); + } + + LuaAPI::LuaImage img; + int imageIndex = -1; + + if (itemTable["image"].valid()) { + if (itemTable["image"].is()) { + img = itemTable["image"].get(); + } else if (itemTable["image"].is()) { + img = LuaAPI::LuaImage::loadFromFile(itemTable["image"].get()); + } + } + + if (img.isValid()) { + imageIndex = imageList->Add(img.getBitmap(iconWidth, iconHeight)); + } + + long index = grid->InsertItem(grid->GetItemCount(), text, imageIndex); + // Store original item index/id in data if needed, or rely on position + grid->SetItemData(index, index + 1); // 1-based index + + std::string tooltip = itemTable.get_or("tooltip", ""s); + if (!tooltip.empty()) { + grid->AddTooltip(index, tooltip); + } + } + } + } + + currentRowSizer->Add(grid, 1, wxEXPAND | wxALL, 0); + + LuaDialogWidget widget; + widget.id = id; + widget.type = "grid"; + widget.widget = grid; + if (options["ondoubleclick"].valid()) { + widget.ondoubleclick = options["ondoubleclick"]; + } + if (options["onchange"].valid()) { + widget.onchange = options["onchange"]; + } + if (options["onleftclick"].valid()) { + widget.onleftclick = options["onleftclick"]; + } + if (options["onrightclick"].valid()) { + widget.onrightclick = options["onrightclick"]; + } + if (options["oncontextmenu"].valid()) { + widget.oncontextmenu = options["oncontextmenu"]; + } + widgets.push_back(widget); + + values[id] = sol::make_object(lua, 0); + + // Bind events + grid->Bind(wxEVT_LIST_ITEM_SELECTED, [this, id, grid](wxListEvent& event) { + values[id] = sol::make_object(lua, event.GetIndex() + 1); + onWidgetChange(id); + }); + + grid->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this, id, grid](wxListEvent& event) { + values[id] = sol::make_object(lua, event.GetIndex() + 1); + onWidgetDoubleClick(id); + }); + + sol::function gridLeftClick = widget.onleftclick; + grid->Bind(wxEVT_LEFT_DOWN, [this, id, grid, gridLeftClick](wxMouseEvent& event) { + if (!gridLeftClick.valid() || !lua.lua_state() || !gridLeftClick.lua_state()) { + event.Skip(); + return; + } + int flags = 0; + long index = grid->HitTest(event.GetPosition(), flags); + if (index == wxNOT_FOUND) { + event.Skip(); + return; + } + grid->SetItemState(index, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + grid->SetFocus(); + values[id] = sol::make_object(lua, static_cast(index + 1)); + sol::table info = lua.create_table(); + info["type"] = "grid"; + info["index"] = static_cast(index + 1); + info["text"] = grid->GetItemText(index).ToStdString(); + std::string tooltip = static_cast(grid)->GetTooltip(index); + if (!tooltip.empty()) { + info["tooltip"] = tooltip; + } + info["widget_id"] = id; + try { + gridLeftClick(this, info); + } catch (const sol::error& e) { + wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); + } + event.Skip(); + }); + + sol::function gridRightClick = widget.onrightclick; + grid->Bind(wxEVT_RIGHT_DOWN, [this, id, grid, gridRightClick](wxMouseEvent& event) { + if (!gridRightClick.valid() || !lua.lua_state() || !gridRightClick.lua_state()) { + event.Skip(); + return; + } + int flags = 0; + long index = grid->HitTest(event.GetPosition(), flags); + if (index == wxNOT_FOUND) { + event.Skip(); + return; + } + grid->SetItemState(index, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + grid->SetFocus(); + values[id] = sol::make_object(lua, static_cast(index + 1)); + sol::table info = lua.create_table(); + info["type"] = "grid"; + info["index"] = static_cast(index + 1); + info["text"] = grid->GetItemText(index).ToStdString(); + std::string tooltip = static_cast(grid)->GetTooltip(index); + if (!tooltip.empty()) { + info["tooltip"] = tooltip; + } + info["widget_id"] = id; + try { + gridRightClick(this, info); + } catch (const sol::error& e) { + wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); + } + event.Skip(); + }); + + sol::function gridContextMenu = widget.oncontextmenu; + grid->Bind(wxEVT_CONTEXT_MENU, [this, id, grid, gridContextMenu](wxContextMenuEvent& event) { + if (!gridContextMenu.valid() || !lua.lua_state() || !gridContextMenu.lua_state()) { + return; + } + wxPoint screenPos = event.GetPosition(); + if (screenPos == wxDefaultPosition) { + screenPos = wxGetMousePosition(); + } + wxPoint clientPos = grid->ScreenToClient(screenPos); + int flags = 0; + long index = grid->HitTest(clientPos, flags); + if (index == wxNOT_FOUND) { + return; + } + + sol::table info = lua.create_table(); + info["type"] = "grid"; + info["index"] = static_cast(index + 1); + info["text"] = grid->GetItemText(index).ToStdString(); + std::string tooltip = static_cast(grid)->GetTooltip(index); + if (!tooltip.empty()) { + info["tooltip"] = tooltip; + } + info["widget_id"] = id; + popupContextMenu(gridContextMenu, info, grid, screenPos); + }); + + applyCommonOptions(grid, options); + return this; +} + + + +void LuaDialog::repaint() { + Refresh(); + Update(); +} + +void LuaDialog::clear() { + // Clear all widgets + widgets.clear(); + values.clear(); + + // Destroy all children of mainSizer + mainSizer->Clear(true); // true = delete windows + + // Reset layout state + while (!sizerStack.empty()) { + sizerStack.pop(); + } + sizerStack.push(mainSizer); + tabSizers.clear(); + + currentRowSizer = nullptr; + currentNotebook = nullptr; + activeNotebook = nullptr; + currentTabPanel = nullptr; + currentTabSizer = nullptr; + tabInfos.clear(); + notebookEventsBound = false; + suppressTabButtonClick = false; + suppressTabButtonIndex = -1; + dockPanel ? dockPanel->Layout() : Layout(); +} + +void LuaDialog::layout() { + dockPanel ? dockPanel->Layout() : Layout(); +} + +sol::table LuaDialog::getData() { + collectAllValues(); + + sol::table data = lua.create_table(); + for (auto& pair : values) { + data[pair.first] = pair.second; + } + return data; +} + +void LuaDialog::setData(sol::table data) { + for (auto& pair : data) { + std::string id = pair.first.as(); + values[id] = pair.second; + } +} + +sol::table LuaDialog::getBounds() { + sol::table bounds = lua.create_table(); + wxRect rect = GetRect(); + bounds["x"] = rect.x; + bounds["y"] = rect.y; + bounds["width"] = rect.width; + bounds["height"] = rect.height; + return bounds; +} + +sol::object LuaDialog::getActiveTab() { + wxNotebook* notebook = activeNotebook ? activeNotebook : currentNotebook; + if (!notebook) { + return sol::make_object(lua, sol::nil); + } + + int selection = notebook->GetSelection(); + if (selection == wxNOT_FOUND) { + return sol::make_object(lua, sol::nil); + } + + wxString text = notebook->GetPageText(selection); + return sol::make_object(lua, text.ToStdString()); +} + +void LuaDialog::setBounds(sol::table bounds) { + int x = bounds.get_or("x", GetPosition().x); + int y = bounds.get_or("y", GetPosition().y); + int w = bounds.get_or("width", GetSize().GetWidth()); + int h = bounds.get_or("height", GetSize().GetHeight()); + SetSize(x, y, w, h); +} + +void LuaDialog::updateValue(const std::string& id) { + for (auto& widget : widgets) { + if (widget.id == id) { + if (widget.type == "input") { + wxTextCtrl* ctrl = static_cast(widget.widget); + values[id] = sol::make_object(lua, ctrl->GetValue().ToStdString()); + } else if (widget.type == "number") { + wxSpinCtrlDouble* ctrl = static_cast(widget.widget); + values[id] = sol::make_object(lua, ctrl->GetValue()); + } else if (widget.type == "slider") { + wxSlider* ctrl = static_cast(widget.widget); + values[id] = sol::make_object(lua, ctrl->GetValue()); + } else if (widget.type == "check") { + wxCheckBox* ctrl = static_cast(widget.widget); + values[id] = sol::make_object(lua, ctrl->GetValue()); + } else if (widget.type == "radio") { + wxRadioButton* ctrl = static_cast(widget.widget); + values[id] = sol::make_object(lua, ctrl->GetValue()); + } else if (widget.type == "combobox") { + wxChoice* ctrl = static_cast(widget.widget); + values[id] = sol::make_object(lua, ctrl->GetStringSelection().ToStdString()); + } else if (widget.type == "color") { + wxColourPickerCtrl* ctrl = static_cast(widget.widget); + wxColour c = ctrl->GetColour(); + sol::table colorTable = lua.create_table(); + colorTable["red"] = c.Red(); + colorTable["green"] = c.Green(); + colorTable["blue"] = c.Blue(); + values[id] = colorTable; + } else if (widget.type == "file") { + wxFilePickerCtrl* ctrl = static_cast(widget.widget); + values[id] = sol::make_object(lua, ctrl->GetPath().ToStdString()); + } else if (widget.type == "item") { + // Value is updated in the event handler + } else if (widget.type == "mapCanvas") { + MapPreviewCanvas* ctrl = static_cast(widget.widget); + sol::table mapData = lua.create_table(); + mapData["x"] = ctrl->GetMapX(); + mapData["y"] = ctrl->GetMapY(); + mapData["z"] = ctrl->GetFloor(); + mapData["zoom"] = ctrl->GetZoom(); + values[id] = mapData; + } else if (widget.type == "list") { + LuaDialogListBox* ctrl = static_cast(widget.widget); + values[id] = sol::make_object(lua, ctrl->GetSelection() + 1); // 1-based for Lua + } else if (widget.type == "grid") { + wxListCtrl* ctrl = static_cast(widget.widget); + long item = -1; + item = ctrl->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (item != -1) { + values[id] = sol::make_object(lua, item + 1); + } else { + values[id] = sol::make_object(lua, 0); + } + } + break; + } + } +} + +void LuaDialog::collectAllValues() { + for (auto& widget : widgets) { + updateValue(widget.id); + } +} + +void LuaDialog::applyCommonOptions(wxWindow* widget, sol::table options) { + if (options["tooltip"].valid()) { + widget->SetToolTip(wxString(options.get("tooltip"))); + } + if (options["enabled"].valid()) { + widget->Enable(options.get("enabled")); + } + if (options["visible"].valid()) { + widget->Show(options.get("visible")); + } +} + +void LuaDialog::suspendHotkeys() { + if (hotkeySuspendCount == 0) { + if (g_gui.AreHotkeysEnabled()) { + g_gui.DisableHotkeys(); + hotkeysDisabledByDialog = true; + } + } + ++hotkeySuspendCount; +} + +void LuaDialog::resumeHotkeys() { + if (hotkeySuspendCount <= 0) { + hotkeySuspendCount = 0; + return; + } + --hotkeySuspendCount; + if (hotkeySuspendCount == 0 && hotkeysDisabledByDialog) { + g_gui.EnableHotkeys(); + hotkeysDisabledByDialog = false; + } +} + +void LuaDialog::onWidgetChange(const std::string& id) { + updateValue(id); + + // Call onchange callback if set + sol::function onchange; + for (auto& widget : widgets) { + if (widget.id == id && widget.onchange.valid()) { + onchange = widget.onchange; + break; + } + } + + if (onchange.valid()) { + try { + onchange(this); + } catch (const sol::error& e) { + wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); + } + } +} + +void LuaDialog::onButtonClick(const std::string& id) { + // Set button value to true (for detecting which button was clicked) + values[id] = sol::make_object(lua, true); + + // Call onclick callback if set + sol::function onclick; + for (auto& widget : widgets) { + if (widget.id == id && widget.onclick.valid()) { + onclick = widget.onclick; + break; + } + } + + if (onclick.valid()) { + try { + onclick(this); + } catch (const sol::error& e) { + wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); + } + return; // onclick handles closing if needed + } + + // Default behavior: close dialog on button click (for OK/Cancel buttons) + std::string lowerId = id; + std::transform(lowerId.begin(), lowerId.end(), lowerId.begin(), ::tolower); + if (lowerId == "ok" || lowerId == "cancel" || lowerId == "close") { + close(); + } +} + +void LuaDialog::onWidgetDoubleClick(const std::string& id) { + updateValue(id); + + sol::function ondoubleclick; + for (auto& widget : widgets) { + if (widget.id == id && widget.ondoubleclick.valid()) { + ondoubleclick = widget.ondoubleclick; + break; + } + } + + if (ondoubleclick.valid()) { + try { + ondoubleclick(this); + } catch (const sol::error& e) { + wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); + } + } +} + +namespace LuaAPI { + +void registerDialog(sol::state& lua) { + // Register Dialog class with constructor and method chaining + lua.new_usertype("Dialog", + // Constructor: Dialog{title = "...", topmost = true} + sol::call_constructor, sol::factories( + [](sol::table options, sol::this_state ts) { + return new LuaDialog(options, ts); + }, + [](const std::string& title, sol::this_state ts) { + return new LuaDialog(title, ts); + } + ), + + // Widget methods (return self for chaining) + "label", &LuaDialog::label, + "input", &LuaDialog::input, + "number", &LuaDialog::number, + "slider", &LuaDialog::slider, + "check", &LuaDialog::check, + "radio", &LuaDialog::radio, + "combobox", &LuaDialog::combobox, + "button", &LuaDialog::button, + "color", &LuaDialog::color, + "item", &LuaDialog::item, + "file", &LuaDialog::file, + "image", &LuaDialog::image, + "separator", &LuaDialog::separator, + "newrow", &LuaDialog::newrow, + "tab", &LuaDialog::tab, + "endtabs", &LuaDialog::endtabs, + "wrap", &LuaDialog::wrap, + "endwrap", &LuaDialog::endwrap, + "box", &LuaDialog::box, + "endbox", &LuaDialog::endbox, + "mapCanvas", &LuaDialog::mapCanvas, + "list", &LuaDialog::list, + "grid", &LuaDialog::grid, + + // Dialog control + "show", &LuaDialog::show, + "close", &LuaDialog::close, + "modify", &LuaDialog::modify, + "repaint", &LuaDialog::repaint, + "clear", &LuaDialog::clear, + "layout", &LuaDialog::layout, + + // Properties + "data", sol::property(&LuaDialog::getData, &LuaDialog::setData), + "bounds", sol::property(&LuaDialog::getBounds, &LuaDialog::setBounds), + "dockable", sol::property(&LuaDialog::isDockable), + "activeTab", sol::property(&LuaDialog::getActiveTab) + ); +} + +} // namespace LuaAPI diff --git a/source/lua/lua_dialog.h b/source/lua/lua_dialog.h new file mode 100644 index 000000000..54b614626 --- /dev/null +++ b/source/lua/lua_dialog.h @@ -0,0 +1,197 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_DIALOG_H +#define RME_LUA_DIALOG_H + +#define SOL_ALL_SAFETIES_ON 1 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lua_api_image.h" + +#include +#include +#include +#include +#include +#include + +// Forward declaration +class LuaDialog; + +// Widget info stored for each widget added to the dialog +struct LuaDialogWidget { + std::string id; + std::string type; + wxWindow* widget = nullptr; + sol::function onchange; + sol::function onclick; + sol::function ondoubleclick; + sol::function oncontextmenu; + sol::function onleftclick; + sol::function onrightclick; +}; + +struct LuaDialogTab { + std::string id; + std::string text; + bool isButton = false; + sol::function onclick; + sol::function oncontextmenu; +}; + +// Lua Dialog wrapper class +// Provides Aseprite-style method chaining API for creating dialogs +class LuaDialog : public wxDialog { +public: + LuaDialog(const std::string& title, sol::this_state ts); + LuaDialog(sol::table options, sol::this_state ts); + virtual ~LuaDialog(); + + // Method chaining widget methods - all return pointer to self for chaining + LuaDialog* label(sol::table options); + LuaDialog* input(sol::table options); + LuaDialog* number(sol::table options); + LuaDialog* slider(sol::table options); + LuaDialog* check(sol::table options); + LuaDialog* radio(sol::table options); + LuaDialog* combobox(sol::table options); + LuaDialog* button(sol::table options); + LuaDialog* color(sol::table options); + LuaDialog* item(sol::table options); + LuaDialog* mapCanvas(sol::table options); + LuaDialog* list(sol::table options); + LuaDialog* grid(sol::table options); + LuaDialog* file(sol::table options); + LuaDialog* image(sol::table options); + LuaDialog* separator(); + LuaDialog* newrow(); + LuaDialog* tab(sol::table options); + + LuaDialog* endtabs(); + + // Nesting + LuaDialog* wrap(sol::table options); // Define explicit horizontal wrapper + LuaDialog* endwrap(); + + // Layout containers + LuaDialog* box(sol::table options); // Generic box sizer + LuaDialog* endbox(); + + // Dialog control + LuaDialog* show(sol::optional options); + void close(); + LuaDialog* modify(sol::table options); + void repaint(); + void clear(); + void layout(); + + // Data access + sol::table getData(); + void setData(sol::table data); + + // Bounds access + sol::table getBounds(); + void setBounds(sol::table bounds); + sol::object getActiveTab(); + + bool isDockable() const { return dockPanel != nullptr; } + +private: + sol::state_view lua; + std::vector widgets; + std::map values; + sol::function oncloseCallback; + + std::stack sizerStack; + std::vector tabSizers; + // Parent window for widget creation (usually dialog, but can be other panels) + // Actually, keeping text/buttons parenting to dialog is easier for events. + // As long as sizer hierarchy is correct, parent hierarchy is less strict in wx, but static box sizer needs static box sibling. + // Let's rely on 'getParentForWidget()' which currently uses 'currentTabPanel'. + + wxBoxSizer* mainSizer = nullptr; + wxBoxSizer* currentRowSizer = nullptr; + wxNotebook* currentNotebook = nullptr; + wxNotebook* activeNotebook = nullptr; + wxPanel* currentTabPanel = nullptr; + wxBoxSizer* currentTabSizer = nullptr; + int hotkeySuspendCount = 0; + bool hotkeysDisabledByDialog = false; + bool notebookEventsBound = false; + bool suppressTabButtonClick = false; + int suppressTabButtonIndex = -1; + + bool isShowing = false; + bool waitMode = true; + std::vector tabInfos; + + void createLayout(); + void ensureRowSizer(); + void finishCurrentRow(); + wxWindow* getParentForWidget(); + wxSizer* getSizerForWidget(); + + void updateValue(const std::string& id); + void collectAllValues(); + void applyCommonOptions(wxWindow* widget, sol::table options); + void suspendHotkeys(); + void resumeHotkeys(); + sol::table makeTabInfoTable(int index); + void handleTabButtonClick(int index); + void handleTabContextMenu(int index, const wxPoint& screenPos); + void popupContextMenu(const sol::function& callback, sol::table info, wxWindow* window, const wxPoint& screenPos); + + void onWidgetChange(const std::string& id); + void onButtonClick(const std::string& id); + void onWidgetDoubleClick(const std::string& id); + + // Event handlers + void OnClose(wxCloseEvent& event); + + DECLARE_EVENT_TABLE() + + int reqWidth = -1; + int reqHeight = -1; + int reqX = -1; + int reqY = -1; + +public: + wxPanel* dockPanel = nullptr; +}; + +namespace LuaAPI { + // Register the Dialog class with Lua + void registerDialog(sol::state& lua); +} + +#endif // RME_LUA_DIALOG_H diff --git a/source/lua/lua_engine.cpp b/source/lua/lua_engine.cpp new file mode 100644 index 000000000..48ff5543a --- /dev/null +++ b/source/lua/lua_engine.cpp @@ -0,0 +1,234 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_engine.h" + +#include +#include + +LuaEngine::LuaEngine() : initialized(false) { +} + +LuaEngine::~LuaEngine() { + shutdown(); +} + +bool LuaEngine::initialize() { + if (initialized) { + return true; + } + + try { + // Open standard libraries + lua.open_libraries( + sol::lib::base, + sol::lib::package, + sol::lib::coroutine, + sol::lib::string, + sol::lib::table, + sol::lib::math, + sol::lib::utf8, + sol::lib::io, + sol::lib::os + ); + + // Setup sandbox (restrict dangerous functions) + setupSandbox(); + + // Register base libraries + registerBaseLibraries(); + + initialized = true; + return true; + } catch (const sol::error& e) { + lastError = std::string("Failed to initialize Lua engine: ") + e.what(); + return false; + } catch (const std::exception& e) { + lastError = std::string("Failed to initialize Lua engine: ") + e.what(); + return false; + } +} + +void LuaEngine::shutdown() { + if (!initialized) { + return; + } + + // Clear the Lua state + lua = sol::state(); + initialized = false; +} + +void LuaEngine::setupSandbox() { + // Remove dangerous functions for security + // We don't want scripts to be able to execute system commands or access arbitrary files + + // Sandbox restrictions conditioned or removed for development flexibility + // if (lua["os"].valid()) { + // lua["os"]["execute"] = sol::nil; + // lua["os"]["exit"] = sol::nil; + // } + + // Enable standard libraries for versatile scripting + // lua["io"] = sol::nil; + // lua["loadfile"] = sol::nil; + // lua["dofile"] = sol::nil; + // lua["package"]["loadlib"] = sol::nil; +} + +void LuaEngine::registerBaseLibraries() { + // Register print function that outputs to the script console + // Capture 'this' pointer for callback access + lua["print"] = [this](sol::variadic_args va) { + std::ostringstream oss; + bool first = true; + for (auto v : va) { + if (!first) { + oss << "\t"; + } + first = false; + + // Convert value to string + sol::state_view lua(v.lua_state()); + sol::function tostring = lua["tostring"]; + std::string str = tostring(v); + oss << str; + } + + std::string output = oss.str(); + + // Output to callback if set, otherwise stdout + if (printCallback) { + printCallback(output); + } else { + std::cout << "[Lua] " << output << std::endl; + } + }; + + // Register MouseButton enum + lua.new_enum("MouseButton", + "LEFT", 1, + "RIGHT", 2, + "MIDDLE", 3 + ); +} + +void LuaEngine::setPrintCallback(PrintCallback callback) { + printCallback = callback; + + // Re-register print function with new callback + lua["print"] = [this](sol::variadic_args va) { + std::ostringstream oss; + bool first = true; + for (auto v : va) { + if (!first) { + oss << "\t"; + } + first = false; + + sol::state_view lua(v.lua_state()); + sol::function tostring = lua["tostring"]; + std::string str = tostring(v); + oss << str; + } + + std::string output = oss.str(); + + if (printCallback) { + printCallback(output); + } else { + std::cout << "[Lua] " << output << std::endl; + } + }; +} + +bool LuaEngine::executeFile(const std::string& filepath) { + if (!initialized) { + lastError = "Lua engine not initialized"; + return false; + } + + try { + // Extract the directory from the filepath and set SCRIPT_DIR global + std::string scriptDir; + size_t lastSlash = filepath.find_last_of("/\\"); + if (lastSlash != std::string::npos) { + scriptDir = filepath.substr(0, lastSlash); + } else { + scriptDir = "."; + } + lua["SCRIPT_DIR"] = scriptDir; + + sol::load_result loaded = lua.load_file(filepath); + if (!loaded.valid()) { + sol::error err = loaded; + lastError = std::string("Failed to load script '") + filepath + "': " + err.what(); + return false; + } + + sol::protected_function script = loaded; + sol::protected_function_result result = script(); + + if (!result.valid()) { + sol::error err = result; + lastError = std::string("Error executing script '") + filepath + "': " + err.what(); + return false; + } + + return true; + } catch (const sol::error& e) { + lastError = std::string("Exception executing script '") + filepath + "': " + e.what(); + return false; + } catch (const std::exception& e) { + lastError = std::string("Exception executing script '") + filepath + "': " + e.what(); + return false; + } +} + +bool LuaEngine::executeString(const std::string& code, const std::string& chunkName) { + if (!initialized) { + lastError = "Lua engine not initialized"; + return false; + } + + try { + sol::load_result loaded = lua.load(code, chunkName); + if (!loaded.valid()) { + sol::error err = loaded; + lastError = std::string("Failed to load code: ") + err.what(); + return false; + } + + sol::protected_function script = loaded; + sol::protected_function_result result = script(); + + if (!result.valid()) { + sol::error err = result; + lastError = std::string("Error executing code: ") + err.what(); + return false; + } + + return true; + } catch (const sol::error& e) { + lastError = std::string("Exception executing code: ") + e.what(); + return false; + } catch (const std::exception& e) { + lastError = std::string("Exception executing code: ") + e.what(); + return false; + } +} diff --git a/source/lua/lua_engine.h b/source/lua/lua_engine.h new file mode 100644 index 000000000..33be324dd --- /dev/null +++ b/source/lua/lua_engine.h @@ -0,0 +1,102 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_ENGINE_H +#define RME_LUA_ENGINE_H + +#define SOL_ALL_SAFETIES_ON 1 + +#include + +#include +#include +#include + +class LuaEngine { +public: + LuaEngine(); + ~LuaEngine(); + + // Initialize the Lua state and register base APIs + bool initialize(); + void shutdown(); + + // Check if engine is ready + bool isInitialized() const { return initialized; } + + // Script execution + bool executeFile(const std::string& filepath); + bool executeString(const std::string& code, const std::string& chunkName = "chunk"); + + // Get underlying sol state for API registration + sol::state& getState() { return lua; } + const sol::state& getState() const { return lua; } + + // Error handling + std::string getLastError() const { return lastError; } + void clearError() { lastError.clear(); } + + // Safe call wrapper with error handling + template + bool safeCall(Func&& func) { + try { + auto result = func(); + if (!result.valid()) { + sol::error err = result; + lastError = err.what(); + return false; + } + return true; + } catch (const sol::error& e) { + lastError = e.what(); + return false; + } catch (const std::exception& e) { + lastError = e.what(); + return false; + } + } + + // Safe call that doesn't return a result + template + bool safeCallVoid(Func&& func) { + try { + func(); + return true; + } catch (const sol::error& e) { + lastError = e.what(); + return false; + } catch (const std::exception& e) { + lastError = e.what(); + return false; + } + } + + // Print callback for console output + using PrintCallback = std::function; + void setPrintCallback(PrintCallback callback); + +private: + sol::state lua; + std::string lastError; + bool initialized; + PrintCallback printCallback; + + void setupSandbox(); + void registerBaseLibraries(); +}; + +#endif // RME_LUA_ENGINE_H diff --git a/source/lua/lua_script.cpp b/source/lua/lua_script.cpp new file mode 100644 index 000000000..fe0cfd7b3 --- /dev/null +++ b/source/lua/lua_script.cpp @@ -0,0 +1,287 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_script.h" + +#include +#include +#include + +// Constructor for single .lua file +LuaScript::LuaScript(const std::string& filepath) : + filepath(filepath), + enabled(true), + autorun(false), + isPackageScript(false) { + + // Extract filename from path + size_t lastSlash = filepath.find_last_of("/\\"); + if (lastSlash != std::string::npos) { + filename = filepath.substr(lastSlash + 1); + directory = filepath.substr(0, lastSlash); + } else { + filename = filepath; + directory = "."; + } + + // Default display name is filename without extension + size_t lastDot = filename.find_last_of('.'); + if (lastDot != std::string::npos) { + displayName = filename.substr(0, lastDot); + } else { + displayName = filename; + } + + // Replace underscores with spaces for display + std::replace(displayName.begin(), displayName.end(), '_', ' '); + + // Parse metadata from script comments + parseMetadata(); +} + +// Constructor for directory with manifest.lua +LuaScript::LuaScript(const std::string& dir, bool isDirectory) : + directory(dir), + enabled(true), + autorun(false), + isPackageScript(true) { + + // Extract folder name for default display name + size_t lastSlash = dir.find_last_of("/\\"); + if (lastSlash != std::string::npos) { + displayName = dir.substr(lastSlash + 1); + } else { + displayName = dir; + } + + // Replace underscores with spaces for display + std::replace(displayName.begin(), displayName.end(), '_', ' '); + + // Parse metadata from manifest.lua + parseMetadata(); +} + +void LuaScript::parseMetadata() { + if (isPackageScript) { + parseMetadataFromManifest(); + } else { + parseMetadataFromComments(); + } +} + +void LuaScript::parseMetadataFromManifest() { + // Parse manifest.lua which returns a table with metadata + // Format: + // return { + // name = "Script Name", + // description = "Description", + // author = "Author", + // version = "1.0.0", + // main = "main_script", + // shortcut = "Ctrl+Shift+H" + // } + + std::string manifestPath = directory + "/manifest.lua"; + std::ifstream file(manifestPath); + if (!file.is_open()) { + // Try default main.lua + filepath = directory + "/main.lua"; + filename = "main.lua"; + return; + } + + std::stringstream buffer; + buffer << file.rdbuf(); + std::string content = buffer.str(); + file.close(); + + // Simple Lua table parser for manifest + auto getValue = [&content](const std::string& key) -> std::string { + // Look for: key = "value" or key = 'value' + std::string pattern1 = key + " = \""; + std::string pattern2 = key + " = '"; + std::string pattern3 = key + "=\""; + std::string pattern4 = key + "='"; + + size_t pos = content.find(pattern1); + char quote = '"'; + if (pos == std::string::npos) { + pos = content.find(pattern2); + quote = '\''; + } + if (pos == std::string::npos) { + pos = content.find(pattern3); + quote = '"'; + } + if (pos == std::string::npos) { + pos = content.find(pattern4); + quote = '\''; + } + + if (pos == std::string::npos) return ""; + + size_t valueStart = content.find(quote, pos) + 1; + size_t valueEnd = content.find(quote, valueStart); + if (valueEnd == std::string::npos) return ""; + + return content.substr(valueStart, valueEnd - valueStart); + }; + + std::string val; + + val = getValue("name"); + if (!val.empty()) displayName = val; + + val = getValue("description"); + if (!val.empty()) description = val; + + val = getValue("author"); + if (!val.empty()) author = val; + + val = getValue("version"); + if (!val.empty()) version = val; + + val = getValue("shortcut"); + if (!val.empty()) shortcut = val; + + val = getValue("autorun"); + if (!val.empty()) { + if (val == "true") autorun = true; + } else { + // Also check boolean literal + if (content.find("autorun = true") != std::string::npos || content.find("autorun=true") != std::string::npos) { + autorun = true; + } + } + + val = getValue("main"); + if (!val.empty()) { + // Add .lua extension if not present + if (val.size() < 4 || val.substr(val.size() - 4) != ".lua") { + val += ".lua"; + } + filepath = directory + "/" + val; + filename = val; + } else { + // Default to main.lua + filepath = directory + "/main.lua"; + filename = "main.lua"; + } +} + +void LuaScript::parseMetadataFromComments() { + // Try to read script header comments for metadata + // Format: + // -- @Title: Script Name + // -- @Description: Description... + // -- @Author: Author Name + // -- @Version: Version Number + // -- @Shortcut: Ctrl+K + // -- Or implicit: First line name, subsequent lines description + + std::ifstream file(filepath); + if (!file.is_open()) { + return; + } + + std::string line; + bool foundName = false; + std::ostringstream descBuilder; + int lineNum = 0; + const int maxHeaderLines = 20; // Only check first 20 lines for metadata + + while (std::getline(file, line) && lineNum < maxHeaderLines) { + lineNum++; + + // Trim whitespace + size_t start = line.find_first_not_of(" \t"); + if (start == std::string::npos) { + continue; + } + line = line.substr(start); + + // Check if it's a comment + if (line.substr(0, 2) != "--") { + // Stop at first non-comment line + break; + } + + // Remove comment prefix and trim + std::string comment = line.substr(2); + start = comment.find_first_not_of(" \t"); + if (start == std::string::npos) { + continue; + } + comment = comment.substr(start); + + // Skip empty comments + if (comment.empty()) { + continue; + } + + // Check for tags + if (comment.size() > 7 && comment.substr(0, 7) == "@Title:") { + displayName = comment.substr(7); + size_t s = displayName.find_first_not_of(" \t"); + if (s != std::string::npos) displayName = displayName.substr(s); + foundName = true; + continue; + } else if (comment.size() > 13 && comment.substr(0, 13) == "@Description:") { + std::string descPart = comment.substr(13); + size_t s = descPart.find_first_not_of(" \t"); + if (s != std::string::npos) descPart = descPart.substr(s); + + if (descBuilder.tellp() > 0) descBuilder << " "; + descBuilder << descPart; + continue; + } else if (comment.size() > 8 && comment.substr(0, 8) == "@Author:") { + author = comment.substr(8); + size_t s = author.find_first_not_of(" \t"); + if (s != std::string::npos) author = author.substr(s); + continue; + } else if (comment.size() > 9 && comment.substr(0, 9) == "@Version:") { + version = comment.substr(9); + size_t s = version.find_first_not_of(" \t"); + if (s != std::string::npos) version = version.substr(s); + continue; + } else if (comment.size() > 10 && comment.substr(0, 10) == "@Shortcut:") { + shortcut = comment.substr(10); + size_t s = shortcut.find_first_not_of(" \t"); + if (s != std::string::npos) shortcut = shortcut.substr(s); + continue; + } else if (comment.size() > 9 && (comment.substr(0, 9) == "@AutoRun:" || comment.substr(0, 9) == "@Autorun:")) { + std::string ar = comment.substr(9); + if (ar.find("true") != std::string::npos) autorun = true; + continue; + } + + // First meaningful comment line (that isn't a tag) is the name + if (!foundName) { + displayName = comment; + foundName = true; + } else { + // Subsequent lines are description + if (descBuilder.tellp() > 0) { + descBuilder << " "; + } + descBuilder << comment; + } + } + + description = descBuilder.str(); +} diff --git a/source/lua/lua_script.h b/source/lua/lua_script.h new file mode 100644 index 000000000..48c2a0f1f --- /dev/null +++ b/source/lua/lua_script.h @@ -0,0 +1,72 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_SCRIPT_H +#define RME_LUA_SCRIPT_H + +#include + +// Represents a single Lua script (file or directory with manifest.json) +class LuaScript { +public: + // Constructor for single .lua file + LuaScript(const std::string& filepath); + + // Constructor for directory with manifest.json + LuaScript(const std::string& directory, bool isDirectory); + + ~LuaScript() = default; + + // Script info + const std::string& getFilePath() const { return filepath; } + const std::string& getDirectory() const { return directory; } + const std::string& getFileName() const { return filename; } + const std::string& getDisplayName() const { return displayName; } + const std::string& getDescription() const { return description; } + const std::string& getAuthor() const { return author; } + const std::string& getVersion() const { return version; } + const std::string& getShortcut() const { return shortcut; } + bool shouldAutoRun() const { return autorun; } + + // Is this a directory-based script with manifest? + bool isPackage() const { return isPackageScript; } + + // State + bool isEnabled() const { return enabled; } + void setEnabled(bool value) { enabled = value; } + + // Parse metadata from comments or manifest.json + void parseMetadata(); + +private: + void parseMetadataFromComments(); + void parseMetadataFromManifest(); + + std::string filepath; // Full path to the main .lua file + std::string directory; // Directory containing the script (for packages) + std::string filename; // Just the filename + std::string displayName; // Display name (from metadata or filename) + std::string description; // Description + std::string author; // Author + std::string version; // Version + std::string shortcut; // Keyboard shortcut + bool enabled; + bool autorun; + bool isPackageScript; // True if loaded from directory with manifest.json +}; + +#endif // RME_LUA_SCRIPT_H diff --git a/source/lua/lua_script_manager.cpp b/source/lua/lua_script_manager.cpp new file mode 100644 index 000000000..44e65a6f6 --- /dev/null +++ b/source/lua/lua_script_manager.cpp @@ -0,0 +1,649 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_script_manager.h" +#include "lua_api.h" +#include "../gui.h" +#include "../tile.h" + +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#endif + +LuaScriptManager& LuaScriptManager::getInstance() { + static LuaScriptManager instance; + return instance; +} + +bool LuaScriptManager::initialize() { + if (initialized) { + return true; + } + + try { + // Initialize the Lua engine + if (!engine.initialize()) { + lastError = "Failed to initialize Lua engine: " + engine.getLastError(); + return false; + } + + // Register all APIs + registerAPIs(); + + initialized = true; + + // Discover scripts + discoverScripts(); + return true; + } catch (const std::exception& e) { + lastError = "Exception during Lua initialization: " + std::string(e.what()); + return false; + } catch (...) { + lastError = "Unknown exception during Lua initialization"; + return false; + } +} + +void LuaScriptManager::shutdown() { + if (!initialized) { + return; + } + + scripts.clear(); + clearAllCallbacks(); + engine.shutdown(); + initialized = false; +} + +void LuaScriptManager::registerContextMenuItem(const std::string& label, sol::function callback) { + ContextMenuItem item; + item.label = label; + item.callback = callback; + contextMenuItems.push_back(item); +} + +int LuaScriptManager::addEventListener(const std::string& eventName, sol::function callback) { + EventListener listener; + listener.id = nextListenerId++; + listener.eventName = eventName; + listener.callback = callback; + eventListeners.push_back(listener); + return listener.id; +} + +bool LuaScriptManager::removeEventListener(int listenerId) { + for (auto it = eventListeners.begin(); it != eventListeners.end(); ++it) { + if (it->id == listenerId) { + eventListeners.erase(it); + return true; + } + } + return false; +} + + + +void LuaScriptManager::clearAllCallbacks() { + contextMenuItems.clear(); + eventListeners.clear(); + nextListenerId = 1; + mapOverlays.clear(); + mapOverlayShows.clear(); + mapOverlayHover = MapOverlayHoverState{}; +} + +void LuaScriptManager::registerAPIs() { + // Register all Lua APIs + LuaAPI::registerAll(engine.getState()); +} + +static wxColor parseColor(const sol::object& obj, const wxColor& fallback) { + if (!obj.valid()) { + return fallback; + } + + if (obj.is()) { + sol::table tbl = obj.as(); + int r = tbl.get_or("r", tbl.get_or("red", 255)); + int g = tbl.get_or("g", tbl.get_or("green", 255)); + int b = tbl.get_or("b", tbl.get_or("blue", 255)); + int a = tbl.get_or("a", tbl.get_or("alpha", 255)); + if (tbl[1].valid()) r = tbl.get_or(1, r); + if (tbl[2].valid()) g = tbl.get_or(2, g); + if (tbl[3].valid()) b = tbl.get_or(3, b); + if (tbl[4].valid()) a = tbl.get_or(4, a); + return wxColor(r, g, b, a); + } + + return fallback; +} + +bool LuaScriptManager::addMapOverlay(const std::string& id, sol::table options) { + if (id.empty()) { + return false; + } + + MapOverlay overlay; + overlay.id = id; + overlay.enabled = options.get_or("enabled", true); + overlay.order = options.get_or("order", 0); + if (options["ondraw"].valid()) { + overlay.ondraw = options["ondraw"]; + } + if (options["onhover"].valid()) { + overlay.onhover = options["onhover"]; + } + for (const auto& showItem : mapOverlayShows) { + if (showItem.overlayId == id) { + overlay.enabled = showItem.enabled; + break; + } + } + + for (auto& existing : mapOverlays) { + if (existing.id == id) { + existing = overlay; + return true; + } + } + + mapOverlays.push_back(overlay); + return true; +} + +bool LuaScriptManager::removeMapOverlay(const std::string& id) { + for (auto it = mapOverlays.begin(); it != mapOverlays.end(); ++it) { + if (it->id == id) { + mapOverlays.erase(it); + return true; + } + } + return false; +} + +bool LuaScriptManager::setMapOverlayEnabled(const std::string& id, bool enabled) { + for (auto& overlay : mapOverlays) { + if (overlay.id == id) { + overlay.enabled = enabled; + for (auto& showItem : mapOverlayShows) { + if (showItem.overlayId == id) { + showItem.enabled = enabled; + } + } + return true; + } + } + return false; +} + +bool LuaScriptManager::registerMapOverlayShow(const std::string& label, const std::string& overlayId, bool enabled, sol::function ontoggle) { + if (label.empty() || overlayId.empty()) { + return false; + } + + auto refreshMenus = []() { + if (g_gui.root) { + g_gui.UpdateMenubar(); + } + }; + + for (auto& item : mapOverlayShows) { + if (item.overlayId == overlayId || item.label == label) { + item.label = label; + item.overlayId = overlayId; + item.enabled = enabled; + if (ontoggle.valid()) { + item.ontoggle = ontoggle; + } + setMapOverlayEnabled(overlayId, enabled); + refreshMenus(); + return true; + } + } + + MapOverlayShowItem item; + item.label = label; + item.overlayId = overlayId; + item.enabled = enabled; + item.ontoggle = ontoggle; + mapOverlayShows.push_back(item); + setMapOverlayEnabled(overlayId, enabled); + refreshMenus(); + return true; +} + +bool LuaScriptManager::setMapOverlayShowEnabled(const std::string& overlayId, bool enabled) { + bool updated = setMapOverlayEnabled(overlayId, enabled); + for (auto& item : mapOverlayShows) { + if (item.overlayId == overlayId) { + item.enabled = enabled; + if (item.ontoggle.valid()) { + try { + item.ontoggle(enabled); + } catch (const sol::error& e) { + logOutput("Overlay show '" + item.label + "' error: " + std::string(e.what()), true); + } + } + if (g_gui.root) { + g_gui.UpdateMenubar(); + } + return updated; + } + } + return updated; +} + +bool LuaScriptManager::isMapOverlayEnabled(const std::string& id) const { + for (const auto& overlay : mapOverlays) { + if (overlay.id == id) { + return overlay.enabled; + } + } + return false; +} + +void LuaScriptManager::collectMapOverlayCommands(const MapViewInfo& view, std::vector& out) { + out.clear(); + if (!initialized) { + return; + } + + sol::state& lua = engine.getState(); + sol::table ctx = lua.create_table(); + sol::table viewTable = lua.create_table(); + viewTable["x1"] = view.start_x; + viewTable["y1"] = view.start_y; + viewTable["x2"] = view.end_x; + viewTable["y2"] = view.end_y; + viewTable["z"] = view.floor; + viewTable["zoom"] = view.zoom; + viewTable["screenWidth"] = view.screen_width; + viewTable["screenHeight"] = view.screen_height; + ctx["view"] = viewTable; + + auto getOptsTable = [](sol::variadic_args va) -> sol::table { + for (size_t i = 0; i < va.size(); ++i) { + if (va[i].is()) { + sol::table t = va[i].as(); + // Skip the ctx table (it has 'view', 'rect', 'line', 'text' fields) + if (t["view"].valid() && t["rect"].valid()) { + continue; + } + return t; + } + } + return sol::table(); + }; + + ctx["rect"] = [&, getOptsTable](sol::variadic_args va) { + sol::table opts = getOptsTable(va); + if (!opts.valid()) return; + MapOverlayCommand cmd; + cmd.type = MapOverlayCommand::Type::Rect; + cmd.screen_space = opts.get_or("screen", false); + cmd.filled = opts.get_or("filled", true); + cmd.width = opts.get_or("width", 1); + cmd.x = opts.get_or("x", 0); + cmd.y = opts.get_or("y", 0); + cmd.z = opts.get_or("z", view.floor); + cmd.w = opts.get_or("w", 1); + cmd.h = opts.get_or("h", 1); + cmd.color = parseColor(opts["color"], wxColor(255, 255, 255, 128)); + out.push_back(cmd); + }; + + ctx["line"] = [&, getOptsTable](sol::variadic_args va) { + sol::table opts = getOptsTable(va); + if (!opts.valid()) return; + MapOverlayCommand cmd; + cmd.type = MapOverlayCommand::Type::Line; + cmd.screen_space = opts.get_or("screen", false); + cmd.width = opts.get_or("width", 1); + cmd.x = opts.get_or("x1", 0); + cmd.y = opts.get_or("y1", 0); + cmd.z = opts.get_or("z1", view.floor); + cmd.x2 = opts.get_or("x2", 0); + cmd.y2 = opts.get_or("y2", 0); + cmd.z2 = opts.get_or("z2", view.floor); + cmd.color = parseColor(opts["color"], wxColor(255, 255, 255, 200)); + out.push_back(cmd); + }; + + ctx["text"] = [&, getOptsTable](sol::variadic_args va) { + sol::table opts = getOptsTable(va); + if (!opts.valid()) return; + MapOverlayCommand cmd; + cmd.type = MapOverlayCommand::Type::Text; + cmd.screen_space = opts.get_or("screen", false); + cmd.x = opts.get_or("x", 0); + cmd.y = opts.get_or("y", 0); + cmd.z = opts.get_or("z", view.floor); + cmd.text = opts.get_or("text", std::string()); + cmd.color = parseColor(opts["color"], wxColor(255, 255, 255, 255)); + if (!cmd.text.empty()) { + out.push_back(cmd); + } + }; + + std::vector sorted = mapOverlays; + std::sort(sorted.begin(), sorted.end(), [](const MapOverlay& a, const MapOverlay& b) { + if (a.order == b.order) { + return a.id < b.id; + } + return a.order < b.order; + }); + + for (const auto& overlay : sorted) { + if (!overlay.enabled || !overlay.ondraw.valid()) { + continue; + } + try { + overlay.ondraw(ctx); + } catch (const sol::error& e) { + logOutput("Overlay '" + overlay.id + "' error: " + std::string(e.what()), true); + } + } +} + +void LuaScriptManager::updateMapOverlayHover(int map_x, int map_y, int map_z, int screen_x, int screen_y, Tile* tile, Item* topItem) { + mapOverlayHover = MapOverlayHoverState{}; + if (!initialized) { + return; + } + if (map_x < 0 || map_y < 0) { + return; + } + + bool any = false; + sol::state& lua = engine.getState(); + sol::table info = lua.create_table(); + sol::table pos = lua.create_table(); + pos["x"] = map_x; + pos["y"] = map_y; + pos["z"] = map_z; + info["pos"] = pos; + info["screen"] = lua.create_table_with("x", screen_x, "y", screen_y); + if (tile) { + info["tile"] = tile; + } + if (topItem) { + info["topItem"] = topItem; + } + + for (const auto& overlay : mapOverlays) { + if (!overlay.enabled || !overlay.onhover.valid()) { + continue; + } + + try { + sol::object result = overlay.onhover(info); + if (!result.valid() || result.is()) { + continue; + } + + MapOverlayCommand highlight; + bool hasHighlight = false; + MapOverlayTooltip tooltip; + bool hasTooltip = false; + + if (result.is()) { + hasTooltip = true; + tooltip.text = result.as(); + tooltip.color = wxColor(255, 255, 255, 255); + tooltip.x = map_x; + tooltip.y = map_y; + tooltip.z = map_z; + } else if (result.is()) { + sol::table table = result.as(); + if (table["highlight"].valid()) { + sol::table h = table["highlight"]; + highlight.type = MapOverlayCommand::Type::Rect; + highlight.x = h.get_or("x", map_x); + highlight.y = h.get_or("y", map_y); + highlight.z = h.get_or("z", map_z); + highlight.w = h.get_or("w", 1); + highlight.h = h.get_or("h", 1); + highlight.filled = h.get_or("filled", false); + highlight.width = h.get_or("width", 1); + highlight.color = parseColor(h["color"], wxColor(255, 255, 0, 128)); + hasHighlight = true; + } + + if (table["tooltip"].valid()) { + if (table["tooltip"].is()) { + tooltip.text = table["tooltip"].get(); + tooltip.color = wxColor(255, 255, 255, 255); + } else if (table["tooltip"].is()) { + sol::table t = table["tooltip"]; + tooltip.text = t.get_or("text", std::string()); + tooltip.color = parseColor(t["color"], wxColor(255, 255, 255, 255)); + } + + if (!tooltip.text.empty()) { + tooltip.x = map_x; + tooltip.y = map_y; + tooltip.z = map_z; + hasTooltip = true; + } + } + } + + if (hasHighlight) { + mapOverlayHover.commands.push_back(highlight); + any = true; + } + if (hasTooltip) { + mapOverlayHover.tooltips.push_back(tooltip); + any = true; + } + } catch (const sol::error& e) { + logOutput("Overlay hover '" + overlay.id + "' error: " + std::string(e.what()), true); + } + } + + mapOverlayHover.valid = any; + mapOverlayHover.x = map_x; + mapOverlayHover.y = map_y; + mapOverlayHover.z = map_z; +} + +std::string LuaScriptManager::getScriptsDirectory() const { + // Get the executable directory + wxFileName execPath(wxStandardPaths::Get().GetExecutablePath()); + wxString scriptsDir = execPath.GetPath() + wxFileName::GetPathSeparator() + "scripts"; + + return scriptsDir.ToStdString(); +} + +void LuaScriptManager::discoverScripts() { + scripts.clear(); + clearAllCallbacks(); + + std::string scriptsDir = getScriptsDirectory(); + scanDirectory(scriptsDir); + + // Sort scripts by display name + std::sort(scripts.begin(), scripts.end(), + [](const std::unique_ptr& a, const std::unique_ptr& b) { + return a->getDisplayName() < b->getDisplayName(); + }); + + runAutoScripts(); +} + +void LuaScriptManager::scanDirectory(const std::string& directory) { + wxDir dir(directory); + if (!dir.IsOpened()) { + return; + } + +#ifdef _WIN32 + const std::string sep = "\\"; +#else + const std::string sep = "/"; +#endif + + wxString name; + + // First, scan for directories with manifest.lua (package scripts) + bool cont = dir.GetFirst(&name, wxEmptyString, wxDIR_DIRS); + while (cont) { + std::string subdir = directory + sep + name.ToStdString(); + std::string manifestPath = subdir + sep + "manifest.lua"; + + // Check if manifest.lua exists + if (wxFileExists(manifestPath)) { + auto script = std::make_unique(subdir, true); + scripts.push_back(std::move(script)); + } + + cont = dir.GetNext(&name); + } + + // Then, scan for standalone .lua files + cont = dir.GetFirst(&name, "*.lua", wxDIR_FILES); + while (cont) { + std::string filepath = directory + sep + name.ToStdString(); + + auto script = std::make_unique(filepath); + scripts.push_back(std::move(script)); + + cont = dir.GetNext(&name); + } +} + +void LuaScriptManager::reloadScripts() { + discoverScripts(); +} + +void LuaScriptManager::runAutoScripts() { + for (const auto& script : scripts) { + if (!script->isEnabled() || !script->shouldAutoRun()) { + continue; + } + if (!executeScript(script.get())) { + logOutput("AutoRun '" + script->getDisplayName() + "' error: " + lastError, true); + } + } +} + +bool LuaScriptManager::executeScript(const std::string& filepath) { + if (!initialized) { + lastError = "Script manager not initialized"; + return false; + } + + bool result = engine.executeFile(filepath); + if (!result) { + lastError = engine.getLastError(); + } + return result; +} + +bool LuaScriptManager::executeScript(LuaScript* script) { + if (!script) { + lastError = "Invalid script"; + return false; + } + + if (!script->isEnabled()) { + lastError = "Script is disabled"; + return false; + } + + return executeScript(script->getFilePath()); +} + +bool LuaScriptManager::executeScript(size_t index, std::string& errorOut) { + if (index >= scripts.size()) { + errorOut = "Invalid script index"; + return false; + } + + LuaScript* script = scripts[index].get(); + if (!script->isEnabled()) { + errorOut = "Script is disabled"; + return false; + } + + bool result = executeScript(script->getFilePath()); + if (!result) { + errorOut = lastError; + } + return result; +} + +void LuaScriptManager::setScriptEnabled(size_t index, bool enabled) { + if (index < scripts.size()) { + scripts[index]->setEnabled(enabled); + } +} + +bool LuaScriptManager::isScriptEnabled(size_t index) const { + if (index < scripts.size()) { + return scripts[index]->isEnabled(); + } + return false; +} + +void LuaScriptManager::setOutputCallback(LuaOutputCallback callback) { + outputCallback = callback; + + // Also set up the engine's print callback + engine.setPrintCallback([this](const std::string& msg) { + logOutput(msg, false); + }); +} + +void LuaScriptManager::logOutput(const std::string& message, bool isError) { + if (outputCallback) { + outputCallback(message, isError); + } +} + +LuaScript* LuaScriptManager::getScript(const std::string& filepath) { + for (auto& script : scripts) { + if (script->getFilePath() == filepath) { + return script.get(); + } + } + return nullptr; +} + +void LuaScriptManager::openScriptsFolder() { + std::string scriptsDir = getScriptsDirectory(); + + // Create directory if it doesn't exist + wxFileName::Mkdir(scriptsDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL); + +#ifdef _WIN32 + ShellExecuteA(nullptr, "explore", scriptsDir.c_str(), nullptr, nullptr, SW_SHOWNORMAL); +#elif defined(__APPLE__) + std::string cmd = "open \"" + scriptsDir + "\""; + system(cmd.c_str()); +#else + std::string cmd = "xdg-open \"" + scriptsDir + "\""; + system(cmd.c_str()); +#endif +} diff --git a/source/lua/lua_script_manager.h b/source/lua/lua_script_manager.h new file mode 100644 index 000000000..1b69df6b8 --- /dev/null +++ b/source/lua/lua_script_manager.h @@ -0,0 +1,167 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_SCRIPT_MANAGER_H +#define RME_LUA_SCRIPT_MANAGER_H + +#include "lua_engine.h" +#include "lua_script.h" + +#define SOL_ALL_SAFETIES_ON 1 +#include + +#include +#include +#include +#include +#include + +#include "../map_overlay.h" + +class Tile; +class Item; + +// Callback type for output messages +using LuaOutputCallback = std::function; + +class LuaScriptManager { +public: + static LuaScriptManager& getInstance(); + + // Lifecycle + bool initialize(); + void shutdown(); + bool isInitialized() const { return initialized; } + + // Script discovery and management + void discoverScripts(); + void reloadScripts(); + + // Script execution + bool executeScript(const std::string& filepath); + bool executeScript(LuaScript* script); + bool executeScript(size_t index, std::string& errorOut); + + // Access scripts + const std::vector>& getScripts() const { return scripts; } + LuaScript* getScript(const std::string& filepath); + + // Enable/disable scripts + void setScriptEnabled(size_t index, bool enabled); + bool isScriptEnabled(size_t index) const; + + // Engine access + LuaEngine& getEngine() { return engine; } + + // Error handling + std::string getLastError() const { return lastError; } + + // Scripts directory + std::string getScriptsDirectory() const; + void openScriptsFolder(); + + // Output callback for Script Manager window + void setOutputCallback(LuaOutputCallback callback); + void logOutput(const std::string& message, bool isError = false); + + // Dynamic Context Menu + struct ContextMenuItem { + std::string label; + sol::function callback; + }; + void registerContextMenuItem(const std::string& label, sol::function callback); + const std::vector& getContextMenuItems() const { return contextMenuItems; } + + // Event System + struct EventListener { + int id; + std::string eventName; + sol::function callback; + }; + int addEventListener(const std::string& eventName, sol::function callback); + bool removeEventListener(int listenerId); + + template + void emit(const std::string& eventName, Args&&... args) { + if (!initialized) return; + + for (auto& listener : eventListeners) { + if (listener.eventName == eventName && listener.callback.valid()) { + try { + listener.callback(std::forward(args)...); + } catch (const sol::error& e) { + logOutput("Event '" + eventName + "' error: " + std::string(e.what()), true); + } + } + } + } + + // Clear all registered callbacks (called before script reload) + void clearAllCallbacks(); + + // Map Overlay System + struct MapOverlay { + std::string id; + bool enabled = true; + int order = 0; + sol::function ondraw; + sol::function onhover; + }; + struct MapOverlayShowItem { + std::string label; + std::string overlayId; + bool enabled = true; + sol::function ontoggle; + }; + bool addMapOverlay(const std::string& id, sol::table options); + bool removeMapOverlay(const std::string& id); + bool setMapOverlayEnabled(const std::string& id, bool enabled); + bool registerMapOverlayShow(const std::string& label, const std::string& overlayId, bool enabled, sol::function ontoggle); + bool setMapOverlayShowEnabled(const std::string& overlayId, bool enabled); + bool isMapOverlayEnabled(const std::string& id) const; + const std::vector& getMapOverlayShows() const { return mapOverlayShows; } + void collectMapOverlayCommands(const MapViewInfo& view, std::vector& out); + void updateMapOverlayHover(int map_x, int map_y, int map_z, int screen_x, int screen_y, Tile* tile, Item* topItem); + const MapOverlayHoverState& getMapOverlayHover() const { return mapOverlayHover; } + +private: + LuaScriptManager() = default; + ~LuaScriptManager() = default; + LuaScriptManager(const LuaScriptManager&) = delete; + LuaScriptManager& operator=(const LuaScriptManager&) = delete; + + LuaEngine engine; + std::vector> scripts; + std::string lastError; + bool initialized = false; + LuaOutputCallback outputCallback; + std::vector contextMenuItems; + std::vector eventListeners; + int nextListenerId = 1; + std::vector mapOverlays; + std::vector mapOverlayShows; + MapOverlayHoverState mapOverlayHover; + + void registerAPIs(); + void scanDirectory(const std::string& directory); + void runAutoScripts(); +}; + +// Global accessor macro +#define g_luaScripts LuaScriptManager::getInstance() + +#endif // RME_LUA_SCRIPT_MANAGER_H diff --git a/source/lua/lua_scripts_window.cpp b/source/lua/lua_scripts_window.cpp new file mode 100644 index 000000000..92eb3ae0e --- /dev/null +++ b/source/lua/lua_scripts_window.cpp @@ -0,0 +1,300 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" +#include "lua_scripts_window.h" +#include "lua_script_manager.h" +#include "../gui_ids.h" + +#include +#include + +// Static instance +LuaScriptsWindow* LuaScriptsWindow::instance = nullptr; + +BEGIN_EVENT_TABLE(LuaScriptsWindow, wxPanel) + EVT_LIST_ITEM_ACTIVATED(SCRIPT_MANAGER_LIST, LuaScriptsWindow::OnScriptActivated) + EVT_LIST_ITEM_SELECTED(SCRIPT_MANAGER_LIST, LuaScriptsWindow::OnScriptSelected) + EVT_BUTTON(SCRIPT_MANAGER_RELOAD, LuaScriptsWindow::OnReloadScripts) + EVT_BUTTON(SCRIPT_MANAGER_OPEN_FOLDER, LuaScriptsWindow::OnOpenFolder) + EVT_BUTTON(SCRIPT_MANAGER_CLEAR_CONSOLE, LuaScriptsWindow::OnClearConsole) + EVT_BUTTON(SCRIPT_MANAGER_RUN_SCRIPT, LuaScriptsWindow::OnRunScript) +END_EVENT_TABLE() + +LuaScriptsWindow::LuaScriptsWindow(wxWindow* parent) : + wxPanel(parent, wxID_ANY), + script_list(nullptr), + console_output(nullptr), + reload_button(nullptr), + open_folder_button(nullptr), + clear_console_button(nullptr), + run_script_button(nullptr) { + BuildUI(); + RefreshScriptList(); + + // Set up output callback + g_luaScripts.setOutputCallback([this](const std::string& msg, bool isError) { + // Must be called from main thread + if (wxThread::IsMain()) { + LogMessage(wxString::FromUTF8(msg), isError); + } else { + // Post event to main thread + wxTheApp->CallAfter([this, msg, isError]() { + LogMessage(wxString::FromUTF8(msg), isError); + }); + } + }); +} + +LuaScriptsWindow::~LuaScriptsWindow() { + // Clear the callback + g_luaScripts.setOutputCallback(nullptr); + + if (instance == this) { + instance = nullptr; + } +} + +void LuaScriptsWindow::BuildUI() { + wxBoxSizer* mainSizer = newd wxBoxSizer(wxVERTICAL); + + // Button bar + wxBoxSizer* buttonSizer = newd wxBoxSizer(wxHORIZONTAL); + + reload_button = newd wxButton(this, SCRIPT_MANAGER_RELOAD, "Reload All"); + reload_button->SetToolTip("Reload all scripts from disk"); + buttonSizer->Add(reload_button, 0, wxALL, 2); + + open_folder_button = newd wxButton(this, SCRIPT_MANAGER_OPEN_FOLDER, "Open Folder"); + open_folder_button->SetToolTip("Open scripts folder in file explorer"); + buttonSizer->Add(open_folder_button, 0, wxALL, 2); + + run_script_button = newd wxButton(this, SCRIPT_MANAGER_RUN_SCRIPT, "Run"); + run_script_button->SetToolTip("Run selected script"); + run_script_button->Enable(false); + buttonSizer->Add(run_script_button, 0, wxALL, 2); + + buttonSizer->AddStretchSpacer(); + + clear_console_button = newd wxButton(this, SCRIPT_MANAGER_CLEAR_CONSOLE, "Clear"); + clear_console_button->SetToolTip("Clear console output"); + buttonSizer->Add(clear_console_button, 0, wxALL, 2); + + mainSizer->Add(buttonSizer, 0, wxEXPAND | wxALL, 2); + + // Script list + script_list = newd wxListCtrl(this, SCRIPT_MANAGER_LIST, + wxDefaultPosition, wxSize(-1, 150), + wxLC_REPORT | wxLC_SINGLE_SEL); + + script_list->InsertColumn(0, "Status", wxLIST_FORMAT_CENTER, 40); + script_list->InsertColumn(1, "Title", wxLIST_FORMAT_LEFT, 70); + script_list->InsertColumn(2, "Description", wxLIST_FORMAT_LEFT, 150); + script_list->InsertColumn(3, "Author", wxLIST_FORMAT_LEFT, 70); + script_list->InsertColumn(4, "Version", wxLIST_FORMAT_LEFT, 40); + script_list->InsertColumn(5, "Shortcut", wxLIST_FORMAT_LEFT, 50); + + mainSizer->Add(script_list, 1, wxEXPAND | wxALL, 2); + + // Console label + wxStaticText* consoleLabel = newd wxStaticText(this, wxID_ANY, "Console Output:"); + mainSizer->Add(consoleLabel, 0, wxLEFT | wxTOP, 4); + + // Console output + console_output = newd wxTextCtrl(this, wxID_ANY, wxEmptyString, + wxDefaultPosition, wxSize(-1, 100), + wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2 | wxHSCROLL); + + // Set monospace font for console + wxFont consoleFont(9, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); + console_output->SetFont(consoleFont); + console_output->SetBackgroundColour(wxColour(30, 30, 30)); + console_output->SetForegroundColour(wxColour(200, 200, 200)); + + mainSizer->Add(console_output, 1, wxEXPAND | wxALL, 2); + + SetSizer(mainSizer); +} + +void LuaScriptsWindow::RefreshScriptList() { + if (!script_list) return; + + script_list->DeleteAllItems(); + + const auto& scripts = g_luaScripts.getScripts(); + for (size_t i = 0; i < scripts.size(); ++i) { + const auto& script = scripts[i]; + + long index = script_list->InsertItem(i, script->isEnabled() ? "On" : "Off"); + + + script_list->SetItem(index, 1, wxString::FromUTF8(script->getDisplayName())); + script_list->SetItem(index, 2, wxString::FromUTF8(script->getDescription())); + script_list->SetItem(index, 3, wxString::FromUTF8(script->getAuthor())); + script_list->SetItem(index, 4, wxString::FromUTF8(script->getVersion())); + script_list->SetItem(index, 5, wxString::FromUTF8(script->getShortcut())); + + // Store script index as item data + script_list->SetItemData(index, static_cast(i)); + + // Color based on enabled state + if (!script->isEnabled()) { + script_list->SetItemTextColour(index, wxColour(128, 128, 128)); + } + } + + // Auto-resize columns if needed + if (scripts.size() > 0) { + script_list->SetColumnWidth(1, wxLIST_AUTOSIZE); + // Description (index 2) keeps fixed width + script_list->SetColumnWidth(3, wxLIST_AUTOSIZE); + script_list->SetColumnWidth(4, wxLIST_AUTOSIZE); + script_list->SetColumnWidth(5, wxLIST_AUTOSIZE); + } +} + +void LuaScriptsWindow::LogMessage(const wxString& message, bool isError) { + if (!console_output) return; + + // Set color based on message type + wxTextAttr attr; + if (isError) { + attr.SetTextColour(wxColour(255, 100, 100)); // Red for errors + } else { + attr.SetTextColour(wxColour(200, 200, 200)); // Light gray for normal + } + + console_output->SetDefaultStyle(attr); + + // Add timestamp + wxDateTime now = wxDateTime::Now(); + wxString timestamp = now.Format("[%H:%M:%S] "); + + console_output->AppendText(timestamp + message); + if (!message.EndsWith("\n")) { + console_output->AppendText("\n"); + } + + // Scroll to end + console_output->ShowPosition(console_output->GetLastPosition()); +} + +void LuaScriptsWindow::ClearConsole() { + if (console_output) { + console_output->Clear(); + } +} + +void LuaScriptsWindow::UpdateScriptState(long index) { + if (!script_list || index < 0) return; + + size_t scriptIndex = static_cast(script_list->GetItemData(index)); + const auto& scripts = g_luaScripts.getScripts(); + + if (scriptIndex < scripts.size()) { + const auto& script = scripts[scriptIndex]; + script_list->SetItem(index, 0, script->isEnabled() ? "On" : "Off"); + + if (script->isEnabled()) { + script_list->SetItemTextColour(index, wxColour(0, 0, 0)); + } else { + script_list->SetItemTextColour(index, wxColour(128, 128, 128)); + } + } +} + +void LuaScriptsWindow::OnScriptActivated(wxListEvent& event) { + // Double-click to run the script + long index = event.GetIndex(); + if (index < 0) return; + + size_t scriptIndex = static_cast(script_list->GetItemData(index)); + const auto& scripts = g_luaScripts.getScripts(); + + if (scriptIndex < scripts.size()) { + const auto& script = scripts[scriptIndex]; + if (script->isEnabled()) { + LogMessage("Running: " + wxString::FromUTF8(script->getDisplayName())); + std::string error; + if (!g_luaScripts.executeScript(scriptIndex, error)) { + LogMessage("Error: " + wxString::FromUTF8(error), true); + } + } else { + LogMessage("Script is disabled: " + wxString::FromUTF8(script->getDisplayName()), true); + } + } +} + +void LuaScriptsWindow::OnScriptSelected(wxListEvent& event) { + // Enable/disable run button based on selection + run_script_button->Enable(event.GetIndex() >= 0); +} + +void LuaScriptsWindow::OnReloadScripts(wxCommandEvent& event) { + LogMessage("Reloading scripts..."); + g_luaScripts.reloadScripts(); + RefreshScriptList(); + LogMessage("Scripts reloaded. Found " + wxString::Format("%zu", g_luaScripts.getScripts().size()) + " scripts."); +} + +void LuaScriptsWindow::OnOpenFolder(wxCommandEvent& event) { + wxString scriptsPath = g_luaScripts.getScriptsDirectory(); + + // Ensure directory exists + if (!wxDirExists(scriptsPath)) { + wxMkdir(scriptsPath); + } + +#ifdef _WIN32 + wxExecute("explorer \"" + scriptsPath + "\"", wxEXEC_ASYNC); +#elif defined(__APPLE__) + wxExecute("open \"" + scriptsPath + "\"", wxEXEC_ASYNC); +#else + wxExecute("xdg-open \"" + scriptsPath + "\"", wxEXEC_ASYNC); +#endif +} + +void LuaScriptsWindow::OnClearConsole(wxCommandEvent& event) { + ClearConsole(); +} + +void LuaScriptsWindow::OnRunScript(wxCommandEvent& event) { + long selected = script_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selected < 0) return; + + size_t scriptIndex = static_cast(script_list->GetItemData(selected)); + const auto& scripts = g_luaScripts.getScripts(); + + if (scriptIndex < scripts.size()) { + const auto& script = scripts[scriptIndex]; + LogMessage("Running: " + wxString::FromUTF8(script->getDisplayName())); + std::string error; + if (!g_luaScripts.executeScript(scriptIndex, error)) { + LogMessage("Error: " + wxString::FromUTF8(error), true); + } + } +} + +void LuaScriptsWindow::OnScriptCheckToggle(wxListEvent& event) { + // Toggle script enabled state (would need checkbox implementation) + long index = event.GetIndex(); + if (index < 0) return; + + size_t scriptIndex = static_cast(script_list->GetItemData(index)); + g_luaScripts.setScriptEnabled(scriptIndex, !g_luaScripts.isScriptEnabled(scriptIndex)); + UpdateScriptState(index); +} diff --git a/source/lua/lua_scripts_window.h b/source/lua/lua_scripts_window.h new file mode 100644 index 000000000..000eda824 --- /dev/null +++ b/source/lua/lua_scripts_window.h @@ -0,0 +1,71 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_LUA_SCRIPTS_WINDOW_H +#define RME_LUA_SCRIPTS_WINDOW_H + +#include +#include + +class LuaScriptsWindow : public wxPanel { +public: + LuaScriptsWindow(wxWindow* parent); + virtual ~LuaScriptsWindow(); + + // Refresh the script list from LuaScriptManager + void RefreshScriptList(); + + // Log a message to the console + void LogMessage(const wxString& message, bool isError = false); + + // Clear the console + void ClearConsole(); + + // Get singleton instance (created by application) + static LuaScriptsWindow* Get() { return instance; } + static void SetInstance(LuaScriptsWindow* win) { instance = win; } + +protected: + // Event handlers + void OnScriptActivated(wxListEvent& event); + void OnScriptSelected(wxListEvent& event); + void OnReloadScripts(wxCommandEvent& event); + void OnOpenFolder(wxCommandEvent& event); + void OnClearConsole(wxCommandEvent& event); + void OnRunScript(wxCommandEvent& event); + void OnScriptCheckToggle(wxListEvent& event); + + // Build the UI + void BuildUI(); + + // Update script enable state in list + void UpdateScriptState(long index); + +private: + wxListCtrl* script_list; + wxTextCtrl* console_output; + wxButton* reload_button; + wxButton* open_folder_button; + wxButton* clear_console_button; + wxButton* run_script_button; + + static LuaScriptsWindow* instance; + + DECLARE_EVENT_TABLE() +}; + +#endif // RME_LUA_SCRIPTS_WINDOW_H diff --git a/vcproj/Project/Editor.vcxproj b/vcproj/Project/Editor.vcxproj index bdcc85043..a7cb59f55 100644 --- a/vcproj/Project/Editor.vcxproj +++ b/vcproj/Project/Editor.vcxproj @@ -344,6 +344,7 @@ + @@ -422,6 +423,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -456,4 +497,4 @@ - \ No newline at end of file + From c310f8092870ba69c948473557be80db2ef14ca2 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 19:00:32 +0100 Subject: [PATCH 03/41] chore: bump version --- source/definitions.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/definitions.h b/source/definitions.h index 31b1b76aa..c1c40eb62 100644 --- a/source/definitions.h +++ b/source/definitions.h @@ -24,8 +24,8 @@ // Version info // xxyyzzt (major, minor, subversion) #define __RME_VERSION_MAJOR__ 4 -#define __RME_VERSION_MINOR__ 1 -#define __RME_SUBVERSION__ 2 +#define __RME_VERSION_MINOR__ 2 +#define __RME_SUBVERSION__ 0 #define __LIVE_NET_VERSION__ 5 From 92dc1c5a59d49a52019d7a6d97ceef92f3ff9e0e Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 19:02:23 +0100 Subject: [PATCH 04/41] feat: add script manager and menu section --- data/menubar.xml | 8 ++ source/application.cpp | 30 +++++ source/gui_ids.h | 12 +- source/main_menubar.cpp | 269 +++++++++++++++++++++++++++++++++++++++- source/main_menubar.h | 32 +++++ 5 files changed, 344 insertions(+), 7 deletions(-) diff --git a/data/menubar.xml b/data/menubar.xml index f49671231..618045940 100644 --- a/data/menubar.xml +++ b/data/menubar.xml @@ -235,6 +235,14 @@ + + + + + + + + diff --git a/source/application.cpp b/source/application.cpp index cb7602317..67d06652d 100644 --- a/source/application.cpp +++ b/source/application.cpp @@ -34,6 +34,8 @@ #include "map.h" #include "complexitem.h" #include "creature.h" +#include "lua/lua_script_manager.h" +#include "lua/lua_scripts_window.h" #include @@ -173,6 +175,13 @@ bool Application::OnInit() { // Load palette g_gui.LoadPerspective(); + // Initialize Lua scripting system + if (!g_luaScripts.initialize()) { + wxLogWarning("Failed to initialize Lua scripting: %s", g_luaScripts.getLastError()); + } else if (g_gui.root && g_gui.root->menu_bar) { + g_gui.root->menu_bar->LoadScriptsMenu(); + } + wxIcon icon(editor_icon); g_gui.root->SetIcon(icon); @@ -331,6 +340,9 @@ void Application::Unload() { } int Application::OnExit() { + // Shutdown Lua scripting system + g_luaScripts.shutdown(); + #ifdef _USE_PROCESS_COM wxDELETE(m_proc_server); wxDELETE(m_single_instance_checker); @@ -390,6 +402,24 @@ MainFrame::MainFrame(const wxString& title, const wxPoint& pos, const wxSize& si tool_bar = newd MainToolBar(this, g_gui.aui_manager); g_gui.aui_manager->AddPane(g_gui.tabbook, wxAuiPaneInfo().CenterPane().Floatable(false).CloseButton(false).PaneBorder(false)); + + // Create Script Manager panel (dockable) + LuaScriptsWindow* scriptsWindow = newd LuaScriptsWindow(this); + LuaScriptsWindow::SetInstance(scriptsWindow); + g_gui.aui_manager->AddPane(scriptsWindow, + wxAuiPaneInfo() + .Name("ScriptManager") + .Caption("Script Manager") + .Right() + .CloseButton(true) + .MaximizeButton(false) + .MinimizeButton(false) + .Floatable(true) + .BestSize(450, 350) + .MinSize(300, 200) + .Hide() // Hidden by default, show from menu + ); + g_gui.aui_manager->Update(); UpdateMenubar(); diff --git a/source/gui_ids.h b/source/gui_ids.h index e5761573a..ebedf517c 100644 --- a/source/gui_ids.h +++ b/source/gui_ids.h @@ -180,7 +180,17 @@ enum EditorActionID { TOOLBAR_SIZES_4, TOOLBAR_SIZES_5, TOOLBAR_SIZES_6, - TOOLBAR_SIZES_7 + TOOLBAR_SIZES_7, + + // Script Manager Window IDs + SCRIPT_MANAGER_LIST, + SCRIPT_MANAGER_RELOAD, + SCRIPT_MANAGER_OPEN_FOLDER, + SCRIPT_MANAGER_CLEAR_CONSOLE, + SCRIPT_MANAGER_RUN_SCRIPT, + + MAP_POPUP_MENU_SCRIPT_FIRST, + MAP_POPUP_MENU_SCRIPT_LAST = MAP_POPUP_MENU_SCRIPT_FIRST + 20 }; enum ToolBarID { diff --git a/source/main_menubar.cpp b/source/main_menubar.cpp index 08147f01b..9a7cb72da 100644 --- a/source/main_menubar.cpp +++ b/source/main_menubar.cpp @@ -36,6 +36,8 @@ #include "materials.h" #include "live_client.h" #include "live_server.h" +#include "lua/lua_script_manager.h" +#include "lua/lua_scripts_window.h" BEGIN_EVENT_TABLE(MainMenuBar, wxEvtHandler) END_EVENT_TABLE() @@ -199,6 +201,11 @@ MainMenuBar::MainMenuBar(MainFrame* frame) : MAKE_ACTION(GOTO_WEBSITE, wxITEM_NORMAL, OnGotoWebsite); MAKE_ACTION(ABOUT, wxITEM_NORMAL, OnAbout); + // Scripts menu actions + MAKE_ACTION(SCRIPTS_OPEN_FOLDER, wxITEM_NORMAL, OnScriptsOpenFolder); + MAKE_ACTION(SCRIPTS_RELOAD, wxITEM_NORMAL, OnScriptsReload); + MAKE_ACTION(SCRIPTS_MANAGER, wxITEM_NORMAL, OnScriptsManager); + // A deleter, this way the frame does not need // to bother deleting us. class CustomMenuBar : public wxMenuBar { @@ -224,6 +231,16 @@ MainMenuBar::MainMenuBar(MainFrame* frame) : for (size_t i = 0; i < 10; ++i) { frame->Connect(recentFiles.GetBaseId() + i, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainMenuBar::OnOpenRecent), nullptr, this); } + + // Connect script execution events (dynamic range) + for (int i = SCRIPTS_FIRST; i <= SCRIPTS_LAST; ++i) { + frame->Connect(MAIN_FRAME_MENU + i, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainMenuBar::OnScriptExecute), nullptr, this); + } + + // Connect show overlay toggle events (dynamic range) + for (int i = SHOW_CUSTOM_FIRST; i <= SHOW_CUSTOM_LAST; ++i) { + frame->Connect(MAIN_FRAME_MENU + i, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainMenuBar::OnShowOverlayToggle), nullptr, this); + } } MainMenuBar::~MainMenuBar() { @@ -301,6 +318,40 @@ void MainMenuBar::Update() { bool enable = !g_gui.IsWelcomeDialogShown(); menubar->Enable(enable); + + if (showMenu) { + const auto& shows = g_luaScripts.getMapOverlayShows(); + const size_t maxCount = static_cast(MenuBar::SHOW_CUSTOM_LAST - MenuBar::SHOW_CUSTOM_FIRST); + const size_t desiredCount = std::min(shows.size(), maxCount); + bool needsReload = showMenuCount != desiredCount; + + if (!needsReload) { + for (size_t i = 0; i < desiredCount; ++i) { + wxMenuItem* item = showMenu->FindItem(MAIN_FRAME_MENU + MenuBar::SHOW_CUSTOM_FIRST + static_cast(i)); + if (!item) { + needsReload = true; + break; + } + wxString label = wxString::FromUTF8(shows[i].label); + if (item->GetItemLabelText() != label) { + needsReload = true; + break; + } + } + } + + if (needsReload) { + LoadShowMenu(); + } else { + for (size_t i = 0; i < desiredCount; ++i) { + wxMenuItem* item = showMenu->FindItem(MAIN_FRAME_MENU + MenuBar::SHOW_CUSTOM_FIRST + static_cast(i)); + if (item) { + item->Check(g_luaScripts.isMapOverlayEnabled(shows[i].overlayId)); + } + } + } + } + if (!enable) { return; } @@ -621,11 +672,24 @@ wxObject* MainMenuBar::LoadItem(pugi::xml_node node, wxMenu* parent, wxArrayStri } std::string name = attribute.as_string(); + std::string menuName = name; std::replace(name.begin(), name.end(), '$', '&'); wxMenu* menu = newd wxMenu; - if ((attribute = node.attribute("special")) && std::string(attribute.as_string()) == "RECENT_FILES") { - recentFiles.UseMenu(menu); + if ((attribute = node.attribute("special"))) { + std::string special = attribute.as_string(); + if (special == "RECENT_FILES") { + recentFiles.UseMenu(menu); + } else if (special == "SCRIPTS") { + // Store reference to scripts menu for dynamic population + scriptsMenu = menu; + // Add static items + for (pugi::xml_node menuNode = node.first_child(); menuNode; menuNode = menuNode.next_sibling()) { + LoadItem(menuNode, menu, warnings, error); + } + // Load script entries + LoadScriptsMenu(); + } } else { for (pugi::xml_node menuNode = node.first_child(); menuNode; menuNode = menuNode.next_sibling()) { // Load an add each item in order @@ -633,6 +697,13 @@ wxObject* MainMenuBar::LoadItem(pugi::xml_node node, wxMenu* parent, wxArrayStri } } + if (menuName == "Show") { + showMenu = menu; + showMenuCount = 0; + showMenuHasSeparator = false; + LoadShowMenu(); + } + // If we have a parent, add ourselves. // If not, we just return the item and the parent function // is responsible for adding us to wherever @@ -774,15 +845,15 @@ void MainMenuBar::OnImportMonsterData(wxCommandEvent& WXUNUSED(event)) { wxString error; wxArrayString warnings; bool ok = g_creatures.importXMLFromOT(FileName(paths[i]), error, warnings); - if (ok) { - g_gui.ListDialog("Monster loader errors", warnings); - } else { - wxMessageBox("Error OT data file \"" + paths[i] + "\".\n" + error, "Error", wxOK | wxICON_INFORMATION, g_gui.root); + if (!ok) { + g_gui.PopupDialog("Error", error, wxOK); } } } } + + void MainMenuBar::OnImportMinimap(wxCommandEvent& WXUNUSED(event)) { ASSERT(g_gui.IsEditorOpen()); // wxDialog* importmap = newd ImportMapWindow(); @@ -2087,3 +2158,189 @@ void MainMenuBar::SearchItems(bool unique, bool action, bool container, bool wri result->AddPosition(searcher.desc(iter->second), iter->first->getPosition()); } } + +// ============================================================================ +// Scripts Menu Handlers + +void MainMenuBar::OnScriptsOpenFolder(wxCommandEvent& WXUNUSED(event)) { + g_luaScripts.openScriptsFolder(); +} + +void MainMenuBar::OnScriptsReload(wxCommandEvent& WXUNUSED(event)) { + g_luaScripts.reloadScripts(); + RefreshScriptsMenu(); + g_gui.SetStatusText("Scripts reloaded"); + + // Also refresh the Script Manager window if open + LuaScriptsWindow* scriptsWindow = LuaScriptsWindow::Get(); + if (scriptsWindow) { + scriptsWindow->RefreshScriptList(); + } +} + +void MainMenuBar::OnScriptsManager(wxCommandEvent& WXUNUSED(event)) { + // Toggle visibility of Script Manager panel + wxAuiPaneInfo& pane = g_gui.aui_manager->GetPane("ScriptManager"); + if (pane.IsOk()) { + pane.Show(!pane.IsShown()); + g_gui.aui_manager->Update(); + } +} + +void MainMenuBar::OnScriptExecute(wxCommandEvent& event) { + using namespace MenuBar; + + int scriptIndex = event.GetId() - MAIN_FRAME_MENU - SCRIPTS_FIRST; + const auto& scripts = g_luaScripts.getScripts(); + + if (scriptIndex >= 0 && scriptIndex < (int)scripts.size()) { + LuaScript* script = scripts[scriptIndex].get(); + if (!g_luaScripts.executeScript(script)) { + wxMessageBox( + wxString("Script error:\n") + g_luaScripts.getLastError(), + "Lua Script Error", + wxOK | wxICON_ERROR + ); + } + } +} + +void MainMenuBar::OnShowOverlayToggle(wxCommandEvent& event) { + using namespace MenuBar; + + int showIndex = event.GetId() - MAIN_FRAME_MENU - SHOW_CUSTOM_FIRST; + const auto& shows = g_luaScripts.getMapOverlayShows(); + if (showIndex < 0 || showIndex >= static_cast(shows.size())) { + return; + } + + const auto& showItem = shows[showIndex]; + g_luaScripts.setMapOverlayShowEnabled(showItem.overlayId, event.IsChecked()); + g_gui.RefreshView(); +} + +void MainMenuBar::LoadScriptsMenu() { + using namespace MenuBar; + + if (!scriptsMenu) { + return; + } + + // But let's be safe and look for the second separator + int separatorCount = 0; + size_t clearFrom = 0; + bool found = false; + + for (size_t i = 0; i < scriptsMenu->GetMenuItemCount(); ++i) { + wxMenuItem* item = scriptsMenu->FindItemByPosition(i); + if (item && item->IsSeparator()) { + separatorCount++; + if (separatorCount == 2) { + clearFrom = i + 1; + found = true; + break; + } + } + } + + // If we found the second separator, delete everything after it + if (!found) { + clearFrom = 5; + } + + while (scriptsMenu->GetMenuItemCount() > clearFrom) { + scriptsMenu->Delete(scriptsMenu->FindItemByPosition(clearFrom)); + } + + // Add scripts + const auto& scripts = g_luaScripts.getScripts(); + if (!scripts.empty()) { + // scriptsMenu->AppendSeparator(); // Separator is already there ideally + + int scriptIndex = 0; + for (const auto& script : scripts) { + if (scriptIndex >= (SCRIPTS_LAST - SCRIPTS_FIRST)) { + break; // Maximum scripts reached + } + + if (!script->isEnabled()) { + scriptIndex++; + continue; + } + + wxString label = wxString::FromUTF8(script->getDisplayName()); + wxString shortcut = wxString::FromUTF8(script->getShortcut()); + + if (!shortcut.IsEmpty()) { + label += "\t" + shortcut; + } + + wxMenuItem* item = scriptsMenu->Append( + MAIN_FRAME_MENU + SCRIPTS_FIRST + scriptIndex, + label, + wxString::FromUTF8(script->getDescription()) + ); + scriptIndex++; + } + } +} + +void MainMenuBar::RefreshScriptsMenu() { + LoadScriptsMenu(); +} + +void MainMenuBar::LoadShowMenu() { + using namespace MenuBar; + + if (!showMenu) { + return; + } + + size_t total = showMenu->GetMenuItemCount(); + if (showMenuCount > 0) { + for (size_t i = 0; i < showMenuCount && total > 0; ++i) { + showMenu->Delete(showMenu->FindItemByPosition(total - 1)); + --total; + } + } + if (showMenuHasSeparator && total > 0) { + showMenu->Delete(showMenu->FindItemByPosition(total - 1)); + --total; + } + + showMenuCount = 0; + showMenuHasSeparator = false; + + const auto& shows = g_luaScripts.getMapOverlayShows(); + if (shows.empty()) { + return; + } + + showMenu->AppendSeparator(); + showMenuHasSeparator = true; + + const size_t maxCount = static_cast(SHOW_CUSTOM_LAST - SHOW_CUSTOM_FIRST); + size_t count = 0; + for (const auto& show : shows) { + if (count >= maxCount) { + break; + } + + wxMenuItem* item = showMenu->Append( + MAIN_FRAME_MENU + SHOW_CUSTOM_FIRST + static_cast(count), + wxString::FromUTF8(show.label), + wxString(), + wxITEM_CHECK + ); + if (item) { + item->Check(show.enabled); + } + ++count; + } + + showMenuCount = count; +} + +void MainMenuBar::RefreshShowMenu() { + LoadShowMenu(); +} diff --git a/source/main_menubar.h b/source/main_menubar.h index 04606eeba..7c2d03ece 100644 --- a/source/main_menubar.h +++ b/source/main_menubar.h @@ -156,6 +156,17 @@ namespace MenuBar { ABOUT, EXPERIMENTAL_FOG, + + // Scripts menu + SCRIPTS_OPEN_FOLDER, + SCRIPTS_RELOAD, + SCRIPTS_MANAGER, // Show/hide Script Manager window + SCRIPTS_FIRST, // Dynamic script IDs start here + SCRIPTS_LAST = SCRIPTS_FIRST + 100, // Allow up to 100 scripts + + // Show menu (custom overlays) + SHOW_CUSTOM_FIRST, + SHOW_CUSTOM_LAST = SHOW_CUSTOM_FIRST + 200, // Allow up to 200 custom show entries }; } @@ -288,6 +299,21 @@ class MainMenuBar : public wxEvtHandler { void OnGotoWebsite(wxCommandEvent& event); void OnAbout(wxCommandEvent& event); + // Scripts Menu + void OnScriptsOpenFolder(wxCommandEvent& event); + void OnScriptsReload(wxCommandEvent& event); + void OnScriptsManager(wxCommandEvent& event); + void OnScriptExecute(wxCommandEvent& event); + + // Show Menu (custom overlays) + void OnShowOverlayToggle(wxCommandEvent& event); + + // Scripts menu management + void LoadScriptsMenu(); + void RefreshScriptsMenu(); + void LoadShowMenu(); + void RefreshShowMenu(); + protected: // Load and returns a menu item, also sets accelerator wxObject* LoadItem(pugi::xml_node node, wxMenu* parent, wxArrayString& warnings, wxString& error); @@ -309,6 +335,12 @@ class MainMenuBar : public wxEvtHandler { std::map actions; + // Scripts menu + wxMenu* scriptsMenu = nullptr; + wxMenu* showMenu = nullptr; + size_t showMenuCount = 0; + bool showMenuHasSeparator = false; + DECLARE_EVENT_TABLE(); }; From b55ce29c4af983709c95359b2fb1b03977a75c89 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 19:03:55 +0100 Subject: [PATCH 05/41] feat: add event emitters --- source/action.cpp | 6 ++++++ source/action.h | 31 +++++++++++++++++++++++++++++++ source/brush.h | 5 +++++ source/editor.cpp | 3 +++ source/editor.h | 1 + source/gui.cpp | 6 ++++++ source/map.cpp | 6 ++++++ source/selection.cpp | 8 ++++++++ 8 files changed, 66 insertions(+) diff --git a/source/action.cpp b/source/action.cpp index 888a40c61..a0e524f80 100644 --- a/source/action.cpp +++ b/source/action.cpp @@ -22,6 +22,7 @@ #include "map.h" #include "editor.h" #include "gui.h" +#include "lua/lua_script_manager.h" Change::Change() : type(CHANGE_NONE), data(nullptr) { @@ -602,6 +603,9 @@ void ActionQueue::addBatch(BatchAction* batch, int stacking_delay) { batch->timestamp = time(nullptr); current++; } while (false); + + // Notify Lua scripts about action change + g_luaScripts.emit("actionChange"); } void ActionQueue::addAction(Action* action, int stacking_delay) { @@ -620,6 +624,7 @@ void ActionQueue::undo() { current--; BatchAction* batch = actions[current]; batch->undo(); + g_luaScripts.emit("actionChange"); } } @@ -628,6 +633,7 @@ void ActionQueue::redo() { BatchAction* batch = actions[current]; batch->redo(); current++; + g_luaScripts.emit("actionChange"); } } diff --git a/source/action.h b/source/action.h index 5c73b1f47..9c7cc663f 100644 --- a/source/action.h +++ b/source/action.h @@ -117,6 +117,7 @@ enum ActionIdentifier { ACTION_ROTATE_ITEM, ACTION_REPLACE_ITEMS, ACTION_CHANGE_PROPERTIES, + ACTION_LUA_SCRIPT, }; class Action { @@ -224,6 +225,36 @@ class ActionQueue { return current < actions.size(); } + size_t getCurrentIndex() const { + return current; + } + size_t getSize() const { + return actions.size(); + } + + // Get action name for a given index + std::string getActionName(size_t index) const { + if (index >= actions.size()) return ""; + ActionIdentifier type = actions[index]->getType(); + switch (type) { + case ACTION_MOVE: return "Move"; + case ACTION_REMOTE: return "Remote Edit"; + case ACTION_SELECT: return "Selection"; + case ACTION_DELETE_TILES: return "Delete"; + case ACTION_CUT_TILES: return "Cut"; + case ACTION_PASTE_TILES: return "Paste"; + case ACTION_RANDOMIZE: return "Randomize"; + case ACTION_BORDERIZE: return "Borderize"; + case ACTION_DRAW: return "Draw"; + case ACTION_SWITCHDOOR: return "Switch Door"; + case ACTION_ROTATE_ITEM: return "Rotate"; + case ACTION_REPLACE_ITEMS: return "Replace"; + case ACTION_CHANGE_PROPERTIES: return "Properties"; + case ACTION_LUA_SCRIPT: return "Script"; + default: return "Unknown"; + } + } + protected: size_t current; size_t memory_size; diff --git a/source/brush.h b/source/brush.h index 1d104a31f..c2df4d2e2 100644 --- a/source/brush.h +++ b/source/brush.h @@ -76,6 +76,11 @@ class Brushes { return brushes; } + typedef std::map BorderMap; + const BorderMap& getBorders() const { + return borders; + } + protected: typedef std::map BorderMap; BrushMap brushes; diff --git a/source/editor.cpp b/source/editor.cpp index 46bcc28e2..338368894 100644 --- a/source/editor.cpp +++ b/source/editor.cpp @@ -34,6 +34,7 @@ #include "doodad_brush.h" #include "creature_brush.h" #include "spawn_brush.h" +#include "lua/lua_script_manager.h" #include "live_server.h" #include "live_client.h" @@ -276,6 +277,8 @@ void Editor::saveMap(FileName filename, bool showdialog) { g_gui.DestroyLoadBar(); } + g_luaScripts.emit("mapSave", savefile); + // Check for errors... if (!success) { // Rename the temporary backup files back to their previous names diff --git a/source/editor.h b/source/editor.h index 5210f8d37..5669e64d5 100644 --- a/source/editor.h +++ b/source/editor.h @@ -57,6 +57,7 @@ class Editor { LiveClient* GetLiveClient() const; LiveServer* GetLiveServer() const; LiveSocket& GetLive() const; + Map* getMap() { return ↦ } bool CanEdit() const { return true; } diff --git a/source/gui.cpp b/source/gui.cpp index 84faddf58..5d5f82b3f 100644 --- a/source/gui.cpp +++ b/source/gui.cpp @@ -41,6 +41,7 @@ #include "live_client.h" #include "live_tab.h" #include "live_server.h" +#include "lua/lua_script_manager.h" #ifdef __WXOSX__ #include @@ -1140,6 +1141,7 @@ void GUI::FinishWelcomeDialog() { welcomeDialog->Destroy(); welcomeDialog = nullptr; } + UpdateMenubar(); } bool GUI::IsWelcomeDialogShown() { @@ -1643,6 +1645,10 @@ void GUI::SelectBrushInternal(Brush* brush) { SetDrawingMode(); RefreshView(); + + if (g_luaScripts.isInitialized()) { + g_luaScripts.emit("brushChange", current_brush->getName()); + } } void GUI::SelectPreviousBrush() { diff --git a/source/map.cpp b/source/map.cpp index a22973c5c..185c6ba55 100644 --- a/source/map.cpp +++ b/source/map.cpp @@ -18,6 +18,7 @@ #include "main.h" #include "gui.h" // loadbar +#include "lua/lua_script_manager.h" #include "map.h" @@ -67,6 +68,9 @@ bool Map::open(const std::string file) { filename = fn.GetFullPath().mb_str(wxConvUTF8); name = fn.GetFullName().mb_str(wxConvUTF8); + name = fn.GetFullName().mb_str(wxConvUTF8); + g_luaScripts.emit("mapLoad"); + // convert(getReplacementMapClassic(), true); #if 0 // This will create a replacement map out of one of SO's template files @@ -405,6 +409,7 @@ bool Map::addSpawn(Tile* tile) { } } spawns.addSpawn(tile); + g_luaScripts.emit("spawnChange", "add", tile); return true; } return false; @@ -434,6 +439,7 @@ void Map::removeSpawn(Tile* tile) { if (tile->spawn) { removeSpawnInternal(tile); spawns.removeSpawn(tile); + g_luaScripts.emit("spawnChange", "remove", tile); } } diff --git a/source/selection.cpp b/source/selection.cpp index 2fec035ce..89833ac68 100644 --- a/source/selection.cpp +++ b/source/selection.cpp @@ -23,6 +23,7 @@ #include "item.h" #include "editor.h" #include "gui.h" +#include "lua/lua_script_manager.h" Selection::Selection(Editor& editor) : busy(false), @@ -269,6 +270,13 @@ void Selection::finish(SessionFlags flags) { } } busy = false; + + // Notify Lua scripts only if we're on the main thread and it's a "real" selection change + if (!(flags & (INTERNAL | SUBTHREAD))) { + if (g_luaScripts.isInitialized()) { + g_luaScripts.emit("selectionChange"); + } + } } void Selection::updateSelectionCount() { From 501ab0c57653355e5bf5f97d8f8d55097e4e6bbe Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 19:04:43 +0100 Subject: [PATCH 06/41] feat: add map overlay, a way to show custom data in the mapView --- source/map_drawer.cpp | 194 +++++++++++++++++++++++++++++++++++++++++- source/map_drawer.h | 6 ++ source/map_overlay.h | 81 ++++++++++++++++++ 3 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 source/map_overlay.h diff --git a/source/map_drawer.cpp b/source/map_drawer.cpp index 81c31bce7..8005caa91 100644 --- a/source/map_drawer.cpp +++ b/source/map_drawer.cpp @@ -16,6 +16,7 @@ ////////////////////////////////////////////////////////////////////// #include "main.h" +#include "lua/lua_script_manager.h" #ifdef __APPLE__ #include @@ -205,6 +206,7 @@ void MapDrawer::Draw() { } DrawDraggingShadow(); DrawHigherFloors(); + bool overlayHasTooltips = false; if (options.dragging) { DrawSelectionBox(); } @@ -216,11 +218,201 @@ void MapDrawer::Draw() { if (options.show_ingame_box) { DrawIngameBox(); } - if (options.show_tooltips) { + if (g_luaScripts.isInitialized()) { + std::vector overlayCommands; + g_luaScripts.collectMapOverlayCommands(getViewInfo(), overlayCommands); + overlayHasTooltips = drawOverlayCommands(overlayCommands) || overlayHasTooltips; + + const MapOverlayHoverState& hoverState = g_luaScripts.getMapOverlayHover(); + if (hoverState.valid) { + overlayHasTooltips = addOverlayTooltips(hoverState.tooltips) || overlayHasTooltips; + overlayHasTooltips = drawOverlayCommands(hoverState.commands) || overlayHasTooltips; + } + } + if (options.show_tooltips || overlayHasTooltips) { DrawTooltips(); } } +MapViewInfo MapDrawer::getViewInfo() const { + MapViewInfo info; + info.start_x = start_x; + info.start_y = start_y; + info.end_x = end_x; + info.end_y = end_y; + info.floor = floor; + info.zoom = zoom; + info.view_scroll_x = view_scroll_x; + info.view_scroll_y = view_scroll_y; + info.tile_size = tile_size; + info.screen_width = screensize_x; + info.screen_height = screensize_y; + return info; +} + +static bool mapToScreen(const MapDrawer* drawer, int map_x, int map_y, int map_z, int& screen_x, int& screen_y) { + if (!drawer) { + return false; + } + + int offset = 0; + if (map_z <= GROUND_LAYER) { + offset = (GROUND_LAYER - map_z) * TileSize; + } else { + offset = TileSize * (drawer->getViewInfo().floor - map_z); + } + + screen_x = ((map_x * TileSize) - drawer->getViewInfo().view_scroll_x) - offset; + screen_y = ((map_y * TileSize) - drawer->getViewInfo().view_scroll_y) - offset; + return true; +} + +static void DrawDirectText(int x, int y, const std::string& text, const wxColor& color) { + // Drop shadow + glColor4ub(0, 0, 0, 255); + glRasterPos2i(x + 1, y + 13); + for (const char* c = text.c_str(); *c != '\0'; c++) { + if (*c != '\n') glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, *c); + } + + // Text + glColor4ub(color.Red(), color.Green(), color.Blue(), color.Alpha()); + glRasterPos2i(x, y + 12); + for (const char* c = text.c_str(); *c != '\0'; c++) { + if (*c != '\n') glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, *c); + } +} + +bool MapDrawer::drawOverlayCommands(const std::vector& commands) { + bool hasTooltips = false; + if (commands.empty()) { + return false; + } + + int vPort[4]; + glGetIntegerv(GL_VIEWPORT, vPort); + + glDisable(GL_TEXTURE_2D); + for (const auto& cmd : commands) { + bool isScreenSpace = cmd.screen_space; + + if (isScreenSpace) { + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, vPort[2], vPort[3], 0, -1, 1); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glTranslatef(0.375f, 0.375f, 0.0f); + } + + if (cmd.type == MapOverlayCommand::Type::Rect) { + int screen_x = 0; + int screen_y = 0; + int screen_w = 0; + int screen_h = 0; + + if (isScreenSpace) { + screen_x = static_cast(cmd.x); + screen_y = static_cast(cmd.y); + screen_w = static_cast(cmd.w); + screen_h = static_cast(cmd.h); + } else if (mapToScreen(this, cmd.x, cmd.y, cmd.z, screen_x, screen_y)) { + int w_tiles = cmd.w > 0 ? cmd.w : 1; + int h_tiles = cmd.h > 0 ? cmd.h : 1; + screen_w = w_tiles * TileSize; + screen_h = h_tiles * TileSize; + } else { + if (isScreenSpace) { + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + continue; + } + + if (cmd.filled) { + drawFilledRect(screen_x, screen_y, screen_w, screen_h, cmd.color); + } else { + drawRect(screen_x, screen_y, screen_w, screen_h, cmd.color, cmd.width); + } + } else if (cmd.type == MapOverlayCommand::Type::Line) { + int x1 = 0; + int y1 = 0; + int x2 = 0; + int y2 = 0; + + if (isScreenSpace) { + x1 = static_cast(cmd.x); + y1 = static_cast(cmd.y); + x2 = static_cast(cmd.x2); + y2 = static_cast(cmd.y2); + } else if (mapToScreen(this, cmd.x, cmd.y, cmd.z, x1, y1) && + mapToScreen(this, cmd.x2, cmd.y2, cmd.z2, x2, y2)) { + // use map coords as-is + } else { + if (isScreenSpace) { + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + continue; + } + + glLineWidth(cmd.width); + glColor4ub(cmd.color.Red(), cmd.color.Green(), cmd.color.Blue(), cmd.color.Alpha()); + glBegin(GL_LINES); + glVertex2f(x1, y1); + glVertex2f(x2, y2); + glEnd(); + } else if (cmd.type == MapOverlayCommand::Type::Text) { + if (!cmd.text.empty()) { + if (isScreenSpace) { + int screen_x = static_cast(cmd.x); + int screen_y = static_cast(cmd.y); + DrawDirectText(screen_x, screen_y, cmd.text, cmd.color); + } else { + int screen_x = 0; + int screen_y = 0; + if (mapToScreen(this, cmd.x, cmd.y, cmd.z, screen_x, screen_y)) { + MakeTooltip(screen_x, screen_y, cmd.text, cmd.color.Red(), cmd.color.Green(), cmd.color.Blue()); + hasTooltips = true; + } + } + } + } + + if (isScreenSpace) { + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + } + glEnable(GL_TEXTURE_2D); + return hasTooltips; +} + +bool MapDrawer::addOverlayTooltips(const std::vector& tooltips) { + bool hasTooltips = false; + for (const auto& tooltip : tooltips) { + int screen_x = 0; + int screen_y = 0; + if (!mapToScreen(this, tooltip.x, tooltip.y, tooltip.z, screen_x, screen_y)) { + continue; + } + + if (!tooltip.text.empty()) { + MakeTooltip(screen_x, screen_y, tooltip.text, tooltip.color.Red(), tooltip.color.Green(), tooltip.color.Blue()); + hasTooltips = true; + } + } + return hasTooltips; +} + void MapDrawer::DrawBackground() { // Black Background glClearColor(0.0f, 0.0f, 0.0f, 0.0f); diff --git a/source/map_drawer.h b/source/map_drawer.h index b1e8e43ce..8a42a483c 100644 --- a/source/map_drawer.h +++ b/source/map_drawer.h @@ -18,6 +18,8 @@ #ifndef RME_MAP_DRAWER_H_ #define RME_MAP_DRAWER_H_ +#include "map_overlay.h" + class GameSprite; struct MapTooltip { @@ -142,6 +144,10 @@ class MapDrawer { return options; } + MapViewInfo getViewInfo() const; + bool drawOverlayCommands(const std::vector& commands); + bool addOverlayTooltips(const std::vector& tooltips); + protected: void BlitItem(int& screenx, int& screeny, const Tile* tile, Item* item, bool ephemeral = false, int red = 255, int green = 255, int blue = 255, int alpha = 255); void BlitItem(int& screenx, int& screeny, const Position& pos, Item* item, bool ephemeral = false, int red = 255, int green = 255, int blue = 255, int alpha = 255, const Tile* tile = nullptr); diff --git a/source/map_overlay.h b/source/map_overlay.h new file mode 100644 index 000000000..40be36228 --- /dev/null +++ b/source/map_overlay.h @@ -0,0 +1,81 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_MAP_OVERLAY_H +#define RME_MAP_OVERLAY_H + +#include +#include +#include + +struct MapViewInfo { + int start_x = 0; + int start_y = 0; + int end_x = 0; + int end_y = 0; + int floor = 0; + float zoom = 1.0f; + int view_scroll_x = 0; + int view_scroll_y = 0; + int tile_size = 32; + int screen_width = 0; + int screen_height = 0; +}; + +struct MapOverlayCommand { + enum class Type { + Rect, + Line, + Text, + }; + + Type type = Type::Rect; + bool screen_space = false; + bool filled = true; + int width = 1; + + int x = 0; + int y = 0; + int z = 0; + int w = 0; + int h = 0; + int x2 = 0; + int y2 = 0; + int z2 = 0; + + std::string text; + wxColor color = wxColor(255, 255, 255, 255); +}; + +struct MapOverlayTooltip { + int x = 0; + int y = 0; + int z = 0; + std::string text; + wxColor color = wxColor(255, 255, 255, 255); +}; + +struct MapOverlayHoverState { + bool valid = false; + int x = 0; + int y = 0; + int z = 0; + std::vector commands; + std::vector tooltips; +}; + +#endif // RME_MAP_OVERLAY_H From ec537ba8a3c28ba8e223c8b3651bacbe9f11229f Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 19:05:12 +0100 Subject: [PATCH 07/41] feat: expose methods to the lua engine --- source/map_display.cpp | 68 +++++++++++++++++++++++++++++++++++++++--- source/map_display.h | 19 +++++++----- 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/source/map_display.cpp b/source/map_display.cpp index ec4de7b9a..697c46f96 100644 --- a/source/map_display.cpp +++ b/source/map_display.cpp @@ -47,6 +47,8 @@ #include "waypoint_brush.h" #include "raw_brush.h" #include "carpet_brush.h" +#include "lua/lua_script.h" +#include "lua/lua_script_manager.h" #include "table_brush.h" BEGIN_EVENT_TABLE(MapCanvas, wxGLCanvas) @@ -101,11 +103,12 @@ EVT_MENU(MAP_POPUP_MENU_MOVE_TO_TILESET, MapCanvas::OnSelectMoveTo) EVT_MENU(MAP_POPUP_MENU_PROPERTIES, MapCanvas::OnProperties) // ---- EVT_MENU(MAP_POPUP_MENU_BROWSE_TILE, MapCanvas::OnBrowseTile) +EVT_MENU_RANGE(MAP_POPUP_MENU_SCRIPT_FIRST, MAP_POPUP_MENU_SCRIPT_LAST, MapCanvas::OnScriptMenu) END_EVENT_TABLE() bool MapCanvas::processed[] = { 0 }; -MapCanvas::MapCanvas(MapWindow* parent, Editor& editor, int* attriblist) : +MapCanvas::MapCanvas(wxWindow* parent, Editor& editor, int* attriblist) : wxGLCanvas(parent, wxID_ANY, nullptr, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS), editor(editor), floor(GROUND_LAYER), @@ -160,6 +163,7 @@ void MapCanvas::Refresh() { wxGLCanvas::Refresh(); } +// Virtual implementation (base assumes parent is MapWindow) void MapCanvas::SetZoom(double value) { if (value < 0.125) { value = 0.125; @@ -174,7 +178,10 @@ void MapCanvas::SetZoom(double value) { GetScreenCenter(¢er_x, ¢er_y); zoom = value; - static_cast(GetParent())->SetScreenCenterPosition(Position(center_x, center_y, floor)); + + // Unsafe cast if parent isn't MapWindow, but this is the base implementation + if (GetParent()) + static_cast(GetParent())->SetScreenCenterPosition(Position(center_x, center_y, floor)); UpdatePositionStatus(); UpdateZoomStatus(); @@ -335,6 +342,8 @@ void MapCanvas::TakeScreenshot(wxFileName path, wxString format) { void MapCanvas::ScreenToMap(int screen_x, int screen_y, int* map_x, int* map_y) { int start_x, start_y; + + // Base implementation calls MapWindow parent static_cast(GetParent())->GetViewStart(&start_x, &start_y); screen_x *= GetContentScaleFactor(); @@ -432,8 +441,12 @@ void MapCanvas::UpdateZoomStatus() { void MapCanvas::OnMouseMove(wxMouseEvent& event) { if (screendragging) { - static_cast(GetParent())->ScrollRelative(int(g_settings.getFloat(Config::SCROLL_SPEED) * zoom * (event.GetX() - cursor_x)), int(g_settings.getFloat(Config::SCROLL_SPEED) * zoom * (event.GetY() - cursor_y))); - Refresh(); + + MapWindow* mw = dynamic_cast(GetParent()); + if (mw) { + mw->ScrollRelative(int(g_settings.getFloat(Config::SCROLL_SPEED) * zoom * (event.GetX() - cursor_x)), int(g_settings.getFloat(Config::SCROLL_SPEED) * zoom * (event.GetY() - cursor_y))); + Refresh(); + } } cursor_x = event.GetX(); @@ -453,6 +466,11 @@ void MapCanvas::OnMouseMove(wxMouseEvent& event) { if (map_update) { UpdatePositionStatus(cursor_x, cursor_y); UpdateZoomStatus(); + if (g_luaScripts.isInitialized()) { + Tile* tile = editor.map.getTile(mouse_map_x, mouse_map_y, floor); + Item* topItem = tile ? tile->getTopItem() : nullptr; + g_luaScripts.updateMapOverlayHover(mouse_map_x, mouse_map_y, floor, cursor_x, cursor_y, tile, topItem); + } } if (g_gui.IsSelectionMode()) { @@ -1515,6 +1533,9 @@ void MapCanvas::OnWheel(wxMouseEvent& event) { } void MapCanvas::OnLoseMouse(wxMouseEvent& event) { + if (g_luaScripts.isInitialized()) { + g_luaScripts.updateMapOverlayHover(-1, -1, floor, -1, -1, nullptr, nullptr); + } Refresh(); } @@ -2303,6 +2324,9 @@ void MapCanvas::ChangeFloor(int new_floor) { int old_floor = floor; floor = new_floor; if (old_floor != new_floor) { + if (g_luaScripts.isInitialized()) { + g_luaScripts.emit("floorChange", new_floor, old_floor); + } UpdatePositionStatus(); g_gui.root->UpdateFloorMenu(); g_gui.UpdateMinimap(true); @@ -2564,6 +2588,42 @@ void MapPopupMenu::Update() { wxMenuItem* browseTile = Append(MAP_POPUP_MENU_BROWSE_TILE, "Browse Field", "Navigate from tile items"); browseTile->Enable(anything_selected); + + // Add Lua Context Menu Items + const auto& menuItems = g_luaScripts.getContextMenuItems(); + if (!menuItems.empty()) { + AppendSeparator(); + + for (size_t i = 0; i < menuItems.size(); ++i) { + int id = MAP_POPUP_MENU_SCRIPT_FIRST + i; + if (id > MAP_POPUP_MENU_SCRIPT_LAST) break; + + Append(id, wxString::FromUTF8(menuItems[i].label)); + } + } + } + } +} + +void MapCanvas::OnScriptMenu(wxCommandEvent& event) { + int index = event.GetId() - MAP_POPUP_MENU_SCRIPT_FIRST; + const auto& menuItems = g_luaScripts.getContextMenuItems(); + + if (index >= 0 && index < static_cast(menuItems.size())) { + try { + // Call the callback + const auto& item = menuItems[index]; + if (item.callback.valid()) { + // Pass the clicked tile/position to the callback + Tile* tile = editor.map.getTile(last_click_map_x, last_click_map_y, floor); + if (tile) { + item.callback(tile); + } else { + item.callback(Position(last_click_map_x, last_click_map_y, floor)); + } + } + } catch (const sol::error& e) { + wxMessageBox(wxString("Script execution error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR); } } } diff --git a/source/map_display.h b/source/map_display.h index 865d05df0..18dd6f075 100644 --- a/source/map_display.h +++ b/source/map_display.h @@ -31,7 +31,7 @@ class MapDrawer; class MapCanvas : public wxGLCanvas { public: - MapCanvas(MapWindow* parent, Editor& editor, int* attriblist); + MapCanvas(wxWindow* parent, Editor& editor, int* attriblist); virtual ~MapCanvas(); void Reset(); @@ -91,14 +91,18 @@ class MapCanvas : public wxGLCanvas { void OnSelectMoveTo(wxCommandEvent& event); // --- void OnProperties(wxCommandEvent& event); + void OnScriptMenu(wxCommandEvent& event); void Refresh(); - void ScreenToMap(int screen_x, int screen_y, int* map_x, int* map_y); + virtual void ScreenToMap(int screen_x, int screen_y, int* map_x, int* map_y); void MouseToMap(int* map_x, int* map_y) { ScreenToMap(cursor_x, cursor_y, map_x, map_y); } - void GetScreenCenter(int* map_x, int* map_y); + virtual void GetScreenCenter(int* map_x, int* map_y); + + virtual int GetClientWidth() const { return ClientMapWidth; } + virtual int GetClientHeight() const { return ClientMapHeight; } void StartPasting(); void EndPasting(); @@ -109,16 +113,17 @@ class MapCanvas : public wxGLCanvas { void UpdateZoomStatus(); void ChangeFloor(int new_floor); + void SetFloor(int new_floor) { floor = new_floor; } int GetFloor() const { return floor; } double GetZoom() const { return zoom; } - void SetZoom(double value); - void GetViewBox(int* view_scroll_x, int* view_scroll_y, int* screensize_x, int* screensize_y) const; + virtual void SetZoom(double value); + virtual void GetViewBox(int* view_scroll_x, int* view_scroll_y, int* screensize_x, int* screensize_y) const; - Position GetCursorPosition() const; + virtual Position GetCursorPosition() const; void TakeScreenshot(wxFileName path, wxString format); @@ -126,7 +131,7 @@ class MapCanvas : public wxGLCanvas { void getTilesToDraw(int mouse_map_x, int mouse_map_y, int floor, PositionVector* tilestodraw, PositionVector* tilestoborder, bool fill = false); bool floodFill(Map* map, const Position& center, int x, int y, GroundBrush* brush, PositionVector* positions); -private: +protected: enum { BLOCK_SIZE = 100 }; From 7b841000ef93e65dc23960c7dc915f37e4ebbfdf Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 19:05:25 +0100 Subject: [PATCH 08/41] doc: add documentation related to the lua engine --- scripts/README.md | 1011 ++++++++++++++++++++++++++++++++++++++++++++ scripts/linter.lua | 572 +++++++++++++++++++++++++ 2 files changed, 1583 insertions(+) create mode 100644 scripts/README.md create mode 100644 scripts/linter.lua diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 000000000..413acdad5 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,1011 @@ +# Lua Scripting Documentation for Remere's Map Editor + +This guide provides a comprehensive overview of the Lua scripting capabilities within Remere's Map Editor (RME). It covers the file structure, API reference, and examples to help you create tools, generators, and extensions. + +--- + +## Table of Contents + +1. [Getting Started](#getting-started) +2. [Script Structure](#script-structure) +3. [API Reference](#api-reference) + * [App (Global)](#app-global) + * [Global Variables](#global-variables) + * [Map](#map) + * [Tile](#tile) + * [Item](#item) + * [Creature & Spawn](#creature--spawn) + * [Selection](#selection) + * [Brushes](#brushes) + * [Image](#image) + * [UI / Dialogs](#ui--dialogs) +4. [Examples](#examples) + +--- + +## Getting Started + +Scripts allow you to automate tasks, create custom tools, and extend the editor's functionality. + +### Location +All Lua scripts must be placed in the `scripts` directory of your RME installation. +The editor scans this directory on startup. Files must have the `.lua` extension. + +### Hello World +Create a file `scripts/hello.lua`: + +```lua +-- Simple script that shows an alert +app.alert("Hello from RME Lua!") +``` + +--- + +## Script Structure + +Scripts are executed sequentially when the editor loads. You can define global functions or use the API immediately. + + +### Script Metadata +Scripts can define metadata in their header to be displayed in the editor's menu and logs. + +```lua +-- My Script Name v1.0 +-- A description of what this script does. +-- Additional description lines... + +-- Code starts here +``` + +The first line is treated as the script name (and optional version). +Subsequent lines starting with `--` are treated as the description. +Metadata parsing stops at the first empty line or code line. + +You can also use special tags for specific fields: +* `@Tile: Script Name` - Sets the display name. +* `@Description: Text` - Sets the description. +* `@Author: Name` - Sets the script author. +* `@Version: 1.0` - Sets the script version. +* `@Shortcut: KeyCombination` - Assigns a default keyboard shortcut (e.g., `Ctrl+Shift+K`). + +```lua +-- @Title: My Script v1.0 +-- @Description: A description of what this script does. +-- @Author: John Doe +-- @Version: 1.0 +-- @Shortcut: Ctrl+Shift+G +``` + +### Best Practices +* Wrap your functionality in local functions. +* Use `app.transaction` for any operations that modify the map to ensure Undo/Redo support works correctly. +* Use `app.alert` for simple user feedback. + +--- + +## API Reference + +### App (Global) + +The `app` table provides access to global editor state and utility functions. + +| Property/Function | Type | Description | +| :--- | :--- | :--- | +| `app.version` | string | The current RME version. | +| `app.apiVersion` | number | The API version number (currently 2). | +| `app.map` | [Map](#map) | Returns the currently active Map object (or nil). | +| `app.selection` | [Selection](#selection) | Returns the current Selection object. | +| `app.borders` | table | Table of all available borders. | +| `app.brush` | [Brush](#brushes) | The currently selected brush. | +| `app.brushSize` | number | Current brush size (radius). | +| `app.brushShape` | string | Current brush shape ("circle" or "square"). | +| `app.spawnTime` | number | Default spawn time for creatures. | +| `app.getDataDirectory()` | function | Returns the absolute path to the data directory. | +| `app.hasMap()` | function | Returns `true` if a map is currently open. | +| `app.refresh()` | function | Refreshes the map view. | +| `app.setClipboard(text)` | function | Sets the system clipboard text. | +| `app.setCameraPosition(x, y, z)` | function | Moves the camera to the specified map coordinates. | +| `app.storage(name)` | function | Returns a per-script storage helper (`load/save/clear`) backed by JSON. | +| `app.mapView` | table | Access to map overlay APIs (`addOverlay/removeOverlay/setEnabled/registerShow`). | +| `app.events` | [Events](#events) | Access to the event system. | +| `app.yield()` | function | Yields to process pending UI events. Use in long-running loops to prevent UI freeze. | +| `app.sleep(ms)` | function | Sleeps for the given milliseconds (max 10000). Blocks the UI thread. | + +#### Events +The `app.events` object allows scripts to listen for global editor events. + +```lua +-- Register an event listener +local listenerId = app.events:on("spawnChange", function(action, tile) + if action == "add" then + print("Spawn added at " .. tile.x .. "," .. tile.y) + end +end) + +-- Remove an event listener +app.events:off(listenerId) +``` + +**Available Events:** + +| Event Name | Arguments | Description | +| :--- | :--- | :--- | +| `spawnChange` | `action` ("add"\|"remove"), `tile` | Triggered when a spawn is added to or removed from a tile. | +| `mapLoad` | - | Triggered when a new map is loaded. | +| `mapSave` | `filename` | Triggered when the current map is saved. | +| `selectionChange` | - | Triggered when the tile selection changes. | +| `brushChange` | `brushName` | Triggered when the active brush changes. | +| `floorChange` | `newFloor`, `oldFloor` | Triggered when the visible map floor changes. | +```lua +-- Simple +app.alert("Message") + +-- Advanced +app.alert({ + title = "Confirmation", + text = "Are you sure?", + buttons = {"Yes", "No"} +}) +``` + +#### Transaction (Undo/Redo) +All map modifications **must** be wrapped in a transaction. + +```lua +app.transaction("My Script Action", function() + -- Modify map here + local tile = app.map:getTile(100, 100, 7) + if tile then + tile:addItem(2160) -- Add Crystal Coin + end +end) +``` + +--- + +### Global Variables + +These variables are automatically set by the engine before your script runs. + +| Variable | Type | Description | +| :--- | :--- | :--- | +| `SCRIPT_DIR` | string | The directory containing the currently executing script. Use this to load resources (images, data files) relative to your script location. **Note:** This is only available when running scripts from a file. | + +**Usage:** +```lua +-- Load an image from the script's directory +local myImage = Image.fromFile(SCRIPT_DIR .. "/my_image.png") + +-- Load a data file +local file = io.open(SCRIPT_DIR .. "/config.txt", "r") +``` + +--- + +### Map + +The `Map` object represents the open map file. + +| Property/Method | Description | +| :--- | :--- | +| `name` | Name of the map. | +| `width`, `height` | Dimensions of the map. | +| `tileCount` | Total number of tiles. | +| `getTile(x, y, z)` | Returns a [Tile](#tile) or `nil`. | +| `getTile(position)` | Same as above, using a position table/object. | +| `getOrCreateTile(x, y, z)` | Returns a Tile, creating it if it doesn't exist. | +| `tiles` | Iterator for looping through all tiles. | + +**Usage:** +```lua +for tile in app.map.tiles do + -- Process tile +end +``` + +--- + +### Tile + +A `Tile` represents a specific coordinate (x, y, z) on the map. + +| Property/Method | Description | +| :--- | :--- | +| `position` | Returns `{x, y, z}` table. | +| `x`, `y`, `z` | Coordinate properties. | +| `ground` | The ground [Item](#item) (read/write). | +| `items` | Lua table of all items on the tile (excluding ground). | +| `itemCount` | Number of items on the tile. | +| `creature` | The [Creature](#creature--spawn) on the tile (or nil). | +| `spawn` | The [Spawn](#creature--spawn) on the tile (or nil). | +| `houseId` | ID of the house this tile belongs to. | +| `isPZ`, `isBlocking` | Flags (Protection Zone, etc.). | +| `addItem(id, [count])` | Adds an item by ID. Returns the new [Item](#item). | +| `removeItem(item)` | Removes a specific item object. | +| `setCreature(name)` | Sets the creature. | +| `removeCreature()` | Removes the creature. | +| `setSpawn(radius)` | Sets a spawn zone. | +| `removeSpawn()` | Removes the spawn zone. | +| `getTopItem()` | Returns the item visually on top. | + +--- + +### Item + +Represents an item on a tile or in a container. + +| Property/Method | Description | +| :--- | :--- | +| `id` | The item ID (read-only). | +| `name` | The item name. | +| `count` | Stack count (or subtype). | +| `actionId`, `uniqueId` | Properties for scripting. | +| `text`, `description` | Text properties (e.g., for signs). | +| `isStackable`, `isMoveable` | Type checks. | +| `clone()` | Returns a copy of the item. | +| `rotate()` | Rotates the item if possible. | + +--- + +### Creature & Spawn + +**Creature** +* `name`: Creature name. +* `spawnTime`: Time in seconds. +* `direction`: Enum (`Direction.NORTH`, `Direction.EAST`, etc.). +* `isNpc`: Boolean. + +**Spawn** +* `radius` (or `size`): The spawn radius/size (1-50). + +--- + +### Selection + +Access via `app.selection`. + +| Property/Method | Description | +| :--- | :--- | +| `tiles` | Table of selected [Tile](#tile) objects. | +| `size` | Number of selected tiles. | +| `bounds` | Table `{min={x,y,z}, max={x,y,z}}`. | +| `clear()` | Deselects everything. | +| `add(tile, [item])` | Adds a tile (and optional item) to selection. | +| `remove(tile, [item])` | Removes from selection. | + +--- + +### Brushes + +Scripts can interact with the current brush. + +* `app.setBrush(name)`: Switches the active brush. +* `Brushes.get(name)`: Returns a Brush object. +* `Brushes.getNames()`: Returns a list of all brush names. + +**Brush Object:** +* `name`, `id`, `type` (e.g., "terrain", "doodad"). + +--- + +### Image + +The `Image` class allows you to load and manipulate images from files or game sprites. + +#### Creating Images + +```lua +-- From file +local img = Image.fromFile("path/to/image.png") +local img = Image("path/to/image.png") +local img = Image{path = "path/to/image.png"} + +-- From item sprite (by item ID) +local img = Image.fromItemSprite(2160) -- Crystal Coin +local img = Image{itemid = 2160} + +-- From raw sprite ID +local img = Image.fromSprite(100) +local img = Image{spriteid = 100} + +-- Using SCRIPT_DIR for relative paths +local img = Image.fromFile(SCRIPT_DIR .. "/my_image.png") +``` + +#### Properties + +| Property | Type | Description | +| :--- | :--- | :--- | +| `width` | number | Width of the image in pixels. | +| `height` | number | Height of the image in pixels. | +| `valid` | boolean | `true` if the image was loaded successfully. | +| `path` | string | File path (if loaded from file). | +| `spriteId` | number | Sprite ID (if loaded from sprite). | +| `isFromSprite` | boolean | `true` if the image was loaded from a game sprite. | + +#### Methods + +| Method | Description | +| :--- | :--- | +| `resize(width, height, [smooth])` | Returns a new resized image. `smooth` defaults to `true`. Set to `false` for pixel-perfect scaling. | +| `scale(factor, [smooth])` | Returns a new scaled image by the given factor. | + +**Scaling Modes:** +* `smooth = true` (default): Uses bilinear interpolation. Good for photos, may look blurry on pixel art. +* `smooth = false`: Uses nearest-neighbor interpolation. Keeps pixels sharp, ideal for sprites. + +```lua +-- Smooth scaling (may look blurry) +local large = img:resize(64, 64, true) + +-- Pixel-perfect scaling (sharp pixels) +local large = img:resize(64, 64, false) +local doubled = img:scale(2.0, false) +``` + +--- + +### UI / Dialogs + +You can create custom dialogs using the `Dialog` class. + +**Constructor:** +`Dialog(title)` or `Dialog({title="...", resizable=true, dockable=true, onclose=function...})` + +* `onclose`: Optional callback function executed when the dialog is closed. + +**Widgets (Chainable):** +* `label({text="My Label"})` +* `input({id="my_input", text="Default", label="Prompt"})` +* `number({id="num", value=10, min=0, max=100})` +* `check({id="cb", text="Enable Feature", selected=true})` +* `button({text="Click Me", onclick=function() ... end})` +* `combobox({id="combo", options={"A", "B"}, option="A"})` +* `color({id="col", label="Pick Color"})` +* `item({id="itm", itemid=100})` +* `image({id="img", itemid=100})` or `image({path="...", smooth=false})` +* `file({id="f", filename="test.txt", save=false})` +* `mapCanvas({id="preview"})` + +**Layout:** +* `box({orient="horizontal"})` / `endbox()` +* `wrap()` / `endwrap()` + +**Showing:** +* `show()`: Displays the dialog modally. The script waits here until the dialog is closed. +* `close()`: Closes the dialog programmatically. +* `show({wait=true, center="parent"|"screen", center=false, bounds=...})`: `center` defaults to parent when no explicit `x/y` or `bounds` are set. + +**Dialog Properties:** +* `dlg.activeTab`: The current tab label (string) or `nil` if no tab is active. + +**List/Grid Options (Common)** +* `onchange`, `ondoubleclick`, `onleftclick`, `onrightclick`, `oncontextmenu`. +* `list` supports `icon_size`, `item_height`, `show_text`, `smooth`, and per-item `tooltip`/`image`/`icon`. +* `grid` supports `item_size`, `cell_size`, `label_wrap`, `show_text`, and per-item `tooltip`/`image`. + +### Widget Reference + +All widget methods return the `Dialog` object itself, allowing for method chaining. Most widgets accept an `options` table. + +#### **Layout & Grouping** +| Widget | Options | Description | +| :--- | :--- | :--- | +| `box(options)` | `{orient="vertical"\|"horizontal", label="Title"}` | Starts a container box. If `label` is provided, draws a border with a title. | +| `endbox()` | - | Ends the current box. | +| `wrap()` | - | Starts a horizontal wrapper. Elements will be placed side-by-side. | +| `endwrap()` | - | Ends the wrapper. | +| `newrow()` | - | Forces the next widget to start on a new line (in default layout). | +| `separator()` | - | Draws a horizontal line separator. | +| `tab(options)` | `{id="tab_id", text="Tab Name", button=false, index=1, onclick=func, oncontextmenu=func}` | Starts a new tab page or a tab-like button (`button=true`). | +| `endtabs()` | - | Ends the tab definition. | + +#### **Input Widgets** +| Widget | Options | Description | +| :--- | :--- | :--- | +| `label(options)` | `{text="Text"}` | Displays static text. | +| `input(options)` | `{id="key", label="Label", text="Default", onchange=func}` | Single-line text input. | +| `number(options)` | `{id="key", label="Label", value=0, min=0, max=100, decimals=0}` | Numeric input spinner. | +| `slider(options)` | `{id="key", label="Label", value=0, min=0, max=100}` | Horizontal slider bar. | +| `check(options)` | `{id="key", text="Label", selected=false, onclick=func}` | Checkbox (boolean). | +| `radio(options)` | `{id="key", text="Label", selected=false}` | Radio button (boolean). | +| `combobox(options)` | `{id="key", label="Label", options={"A", "B"}, option="A"}` | Dropdown selection list. | +| `color(options)` | `{id="key", label="Label", color={red=255, green=0, blue=0}}` | Color picker button. Returns `{red,green,blue}` table. | +| `file(options)` | `{id="key", label="Label", filename="file.txt", save=false}` | File picker. `save=true` for save dialog. | +| `item(options)` | `{id="key", label="Label", itemid=100}` | Button showing an item sprite. Click to change item. | +| `image(options)` | `{id="key", label="Label", image=Image, path="...", itemid=100, spriteid=100, width=32, height=32, smooth=true}` | Displays an image. Can load from file, item sprite, or raw sprite. Use `smooth=false` for pixel-perfect scaling. | +| `mapCanvas(options)` | `{id="key", label="Label"}` | Renders a live preview of the current map view. | + +#### **Action Widgets** +| Widget | Options | Description | +| :--- | :--- | :--- | +| `button(options)` | `{id="btn_id", text="Click Me", onclick=func, focus=false}` | Standard button. `onclick` is a callback function receiving `(dlg)`. | +| `list(options)` | `{id="lst", items={{text="A", image=Image, tooltip="..."}, {text="B", icon=123}}, oncontextmenu=func}` | List box with tooltips and images (`image` or `icon`). Supports `onchange`, `ondoubleclick`, `onleftclick`, `onrightclick`, `oncontextmenu`. | +| `grid(options)` | `{id="grid", items={{text="", image=Image, tooltip="..."}}, item_size=32, cell_size=32}` | Icon grid with tooltips, per-item images, and size controls. Supports `onchange`, `ondoubleclick`, `onleftclick`, `onrightclick`, `oncontextmenu`. | + +#### **Data Access** +* `dlg.data`: Table of current values `{id = value}`. Can be read or written to. +* `dlg.values`: Alias for `data`. + +#### **Dynamic Updates** +* `dlg:modify({ widget_id = { property = new_value } })` + * Example: `dlg:modify({ my_label = { text = "New Text" } })` + * Grid size updates: you can pass `icon_size`/`item_size`/`item_width`/`item_height` and `cell_size`/`cell_width`/`cell_height`. To apply size changes, resend `items` so the image list can be rebuilt. +* `dlg:repaint()`: Forces a UI refresh. + +**Context Menus (Tabs/List/Grid)** +Callbacks can return a menu table: +```lua +oncontextmenu = function(dlg, info) + return { + { text = "Action", onclick = function() end }, + { separator = true }, + { text = "Disabled", enabled = false } + } +end +``` + +### Map View Overlays + +Use `app.mapView` to draw overlays on the map and react to hover changes. Overlays run without requiring a dialog and can still expose UI if desired. + +```lua +app.mapView:addOverlay("Light Strength", { + ondraw = function(ctx) + -- ctx.view: {x1,y1,x2,y2,z,zoom} + -- ctx:rect{ x,y,z,w,h,color={r,g,b,a}, filled=true } + end, + onhover = function(info) + -- info.pos, info.screen, info.tile, info.topItem + return { + tooltip = { text = "Light: 6", color = {255, 200, 50} }, + highlight = { color = {255, 200, 50, 120}, filled = false, width = 1 } + } + end +}) +``` + +To add a toggle in the **Show** menu, register a show input (custom entries are appended after a separator): + +```lua +app.mapView:registerShow("Light Strength", "Light Strength", { + enabled = true, + ontoggle = function(enabled) + -- Persist or react to state change + end +}) +``` + +### Script Storage + +```lua +local store = app.storage("settings") +local data = store:load() or {} +data.enabled = true +store:save(data) +``` + +### HTTP + +The `http` table provides HTTP request functionality for scripts. + +#### Basic Requests + +| Function | Description | +| :--- | :--- | +| `http.get(url, [headers])` | Performs a GET request. Returns response table. | +| `http.post(url, body, [headers])` | Performs a POST request with string body. | +| `http.postJson(url, table, [headers])` | Performs a POST request with JSON body. | + +**Response Table:** +```lua +{ + ok = true, -- true if status is 2xx + status = 200, -- HTTP status code + body = "...", -- Response body as string + error = "", -- Error message if failed + headers = {} -- Response headers table +} +``` + +**Example:** +```lua +local result = http.get("https://api.example.com/data") +if result.ok then + local data = json.decode(result.body) + app.alert("Got: " .. data.message) +else + app.alert("Error: " .. result.error) +end +``` + +#### Streaming Requests + +For large responses or real-time data (like AI APIs), use streaming: + +| Function | Description | +| :--- | :--- | +| `http.postStream(url, body, headers)` | Starts streaming POST request. Returns `{sessionId, ok}`. | +| `http.postJsonStream(url, table, headers)` | Starts streaming POST with JSON body. | +| `http.streamRead(sessionId)` | Reads available data from stream. | +| `http.streamStatus(sessionId)` | Checks stream status without reading. | +| `http.streamClose(sessionId)` | Closes and cleans up stream session. | + +**streamRead Response:** +```lua +{ + data = "...", -- New data received since last read + finished = false, -- true when stream is complete + hasError = false, -- true if an error occurred + error = "", -- Error message if hasError + ok = true, -- false if error + status = 200, -- HTTP status (only when finished) + headers = {} -- Response headers (only when finished) +} +``` + +**Streaming Example:** +```lua +local stream = http.postJsonStream("https://api.openai.com/v1/chat/completions", { + model = "gpt-4", + messages = {{ role = "user", content = "Hello" }}, + stream = true +}, { + ["Authorization"] = "Bearer " .. apiKey +}) + +if stream.ok then + local content = "" + while true do + local result = http.streamRead(stream.sessionId) + if result.data ~= "" then + content = content .. result.data + end + if result.finished or result.hasError then + break + end + app.yield() -- Prevent UI freeze + end + http.streamClose(stream.sessionId) +end +``` + +--- + +## Examples + +### 1. Mass Item Replacer +Replaces all items of ID 100 with ID 200 in the current selection. + +```lua +local function replaceItems() + local sel = app.selection + if sel.isEmpty then + app.alert("Select an area first!") + return + end + + app.transaction("Replace Items", function() + for _, tile in pairs(sel.tiles) do + for _, item in pairs(tile.items) do + if item.id == 100 then + -- Create new item + tile:addItem(200, item.count) + -- Remove old + tile:removeItem(item) + end + end + end + end) + app.alert("Done!") +end + +-- Currently, to run this, you can assign it to a menu or run via console if available. +-- Or simply executing the file runs main scope. +replaceItems() +``` + +### 2. Terrain Generator UI +A simple UI to generate grass. + +```lua +local dlg = Dialog("Grass Generator") + +dlg:label({text="Settings"}) +dlg:box({orient="horizontal"}) + dlg:number({id="density", label="Density %", value=50, min=0, max=100}) + dlg:check({id="flowers", text="Add Flowers", selected=true}) +dlg:endbox() + +dlg:button({text="Generate", onclick=function() + local density = dlg.values.density -- Access values + local flowers = dlg.values.flowers + + app.transaction("Generate Grass", function() + -- Logic to place grass... + app.alert("Generated with density: " .. density) + end) +end}) + +dlg:show() +``` + +*(Note: `dlg.values` is the table where widget values are stored by ID)* + +### 3. Image Display Demo + +Display images from files and game sprites with different scaling modes. + +```lua +-- Load an image from the script's directory +local myImage = Image.fromFile(SCRIPT_DIR .. "/logo.png") + +-- Load item sprites +local goldCoin = Image.fromItemSprite(2148) +local crystalCoin = Image{itemid = 2160} + +local dlg = Dialog{title = "Image Demo", width = 400} + +-- Display image from file +if myImage.valid then + dlg:label{text = "Logo (" .. myImage.width .. "x" .. myImage.height .. "):"} + dlg:newrow() + dlg:image{image = myImage, width = 128, height = 128, smooth = false} + dlg:newrow() +end + +-- Display item sprites with pixel-perfect scaling +dlg:label{text = "Item sprites (64x64, pixel-perfect):"} +dlg:newrow() +dlg:image{itemid = 2148, width = 64, height = 64, smooth = false} +dlg:image{itemid = 2152, width = 64, height = 64, smooth = false} +dlg:image{itemid = 2160, width = 64, height = 64, smooth = false} +dlg:newrow() + +-- Dynamic image update +local currentItem = 2148 +dlg:image{id = "preview", itemid = currentItem, width = 64, height = 64, smooth = false} +dlg:button{text = "Next Item", onclick = function() + local items = {2148, 2152, 2160} + for i, v in ipairs(items) do + if v == currentItem then + currentItem = items[(i % #items) + 1] + break + end + end + dlg:modify{preview = {itemid = currentItem, width = 64, height = 64, smooth = false}} +end} + +dlg:button{id = "close", text = "Close"} +dlg:show() +``` + +--- + +## Procedural Generation APIs + +RME includes powerful procedural generation APIs for creating terrain, caves, dungeons, and more. + +### Noise + +The `noise` table provides various noise functions for procedural terrain generation. + +#### Basic Noise Functions + +| Function | Description | +| :--- | :--- | +| `noise.perlin(x, y, seed?, frequency?)` | 2D Perlin noise, returns [-1, 1] | +| `noise.perlin3d(x, y, z, seed?, frequency?)` | 3D Perlin noise | +| `noise.simplex(x, y, seed?, frequency?)` | OpenSimplex2 noise (recommended) | +| `noise.simplex3d(x, y, z, seed?, frequency?)` | 3D OpenSimplex2 noise | +| `noise.simplexSmooth(x, y, seed?, frequency?)` | Smoother OpenSimplex2S variant | +| `noise.cellular(x, y, seed?, frequency?, distanceFunc?, returnType?)` | Cellular/Voronoi noise | +| `noise.value(x, y, seed?, frequency?)` | Value noise (blocky) | +| `noise.valueCubic(x, y, seed?, frequency?)` | Cubic interpolated value noise | + +#### Fractal Noise + +| Function | Description | +| :--- | :--- | +| `noise.fbm(x, y, seed?, options?)` | Fractional Brownian Motion - layered noise | +| `noise.fbm3d(x, y, z, seed?, options?)` | 3D FBM | +| `noise.ridged(x, y, seed?, options?)` | Ridged fractal noise (mountains, veins) | + +**FBM Options:** +```lua +{ + frequency = 0.01, -- Base frequency + octaves = 4, -- Number of noise layers + lacunarity = 2.0, -- Frequency multiplier per octave + gain = 0.5, -- Amplitude multiplier per octave + noiseType = "simplex" -- "perlin", "simplex", "value", "cellular" +} +``` + +#### Domain Warping + +```lua +-- Distort coordinates for organic effects +local warped = noise.warp(x, y, seed, { + amplitude = 30, + frequency = 0.01, + type = "simplex" -- "simplex", "simplexReduced", "basic" +}) +-- warped.x, warped.y contain the distorted coordinates +``` + +#### Utility Functions + +| Function | Description | +| :--- | :--- | +| `noise.normalize(value, min?, max?)` | Normalize [-1,1] to [min, max] (default [0, 1]) | +| `noise.threshold(value, threshold)` | Returns true if value >= threshold | +| `noise.map(value, inMin, inMax, outMin, outMax)` | Map value between ranges | +| `noise.clamp(value, min, max)` | Clamp value to range | +| `noise.lerp(a, b, t)` | Linear interpolation | +| `noise.smoothstep(edge0, edge1, x)` | Smooth interpolation | + +#### Batch Generation + +```lua +-- Generate noise grid (faster than individual calls) +local grid = noise.generateGrid(0, 0, 100, 100, { + seed = 1337, + frequency = 0.02, + noiseType = "simplex", + fractal = "fbm", + octaves = 4 +}) +-- grid[y][x] contains noise values +``` + +**Example - Island Generator:** +```lua +local seed = os.time() +local sel = app.selection.bounds +local minX, minY, z = sel.min.x, sel.min.y, sel.min.z +local maxX, maxY = sel.max.x, sel.max.y + +app.transaction("Generate Island", function() + for y = minY, maxY do + for x = minX, maxX do + local n = noise.fbm(x, y, seed, {frequency = 0.03, octaves = 4}) + + -- Radial falloff for island shape + local cx, cy = (maxX - minX) / 2, (maxY - minY) / 2 + local dx, dy = (x - minX) - cx, (y - minY) - cy + local dist = math.sqrt(dx*dx + dy*dy) / math.min(cx, cy) + n = n * (1 - dist * dist) + + local tile = app.map:getOrCreateTile(x, y, z) + if n < -0.2 then + tile:addItem(4608) -- Water + elseif n < 0.3 then + tile:addItem(4526) -- Grass + else + tile:addItem(919) -- Mountain + end + end + end +end) +``` + +--- + +### Algo (Algorithms) + +The `algo` table provides procedural generation algorithms. + +#### Cellular Automata + +```lua +-- Run cellular automata on existing grid +local result = algo.cellularAutomata(grid, { + iterations = 4, + birthLimit = 4, -- Become wall if neighbors >= this + deathLimit = 3, -- Stay wall if neighbors >= this + width = 100, + height = 100 +}) + +-- Generate a cave map directly +local caveMap = algo.generateCave(100, 100, { + fillProbability = 0.45, + iterations = 5, + birthLimit = 4, + deathLimit = 3, + seed = os.time() +}) +-- caveMap[y][x] == 1 means wall, 0 means floor +``` + +#### Erosion + +```lua +-- Hydraulic erosion for realistic terrain +local eroded = algo.erode(heightmap, { + iterations = 50000, + erosionRadius = 3, + inertia = 0.05, + sedimentCapacity = 4.0, + erosionSpeed = 0.3, + depositSpeed = 0.3, + evaporateSpeed = 0.01, + gravity = 4.0, + seed = os.time() +}) + +-- Thermal erosion (talus/slope erosion) +local thermal = algo.thermalErode(heightmap, { + iterations = 50, + talusAngle = 0.5, + erosionAmount = 0.5 +}) +``` + +#### Voronoi Diagram + +```lua +-- Generate random seed points +local points = algo.generateRandomPoints(100, 100, 20, seed) + +-- Generate Voronoi regions +local voronoi = algo.voronoi(100, 100, points) +-- voronoi[y][x] contains region index (1-based) +``` + +#### Dungeon Generation (BSP) + +```lua +local result = algo.generateDungeon(100, 100, { + minRoomSize = 5, + maxRoomSize = 15, + maxDepth = 4, + seed = os.time() +}) + +-- result.grid[y][x] == 1 means wall, 0 means floor +-- result.rooms is array of {x, y, width, height} +``` + +#### Maze Generation + +```lua +local maze = algo.generateMaze(51, 51, {seed = os.time()}) +-- maze[y][x] == 1 means wall, 0 means passage +``` + +#### Smoothing + +```lua +local smoothed = algo.smooth(grid, { + iterations = 2, + kernelSize = 3 +}) +``` + +--- + +### Geo (Geometry) + +The `geo` table provides geometric algorithms for drawing shapes and paths. + +#### Line Drawing (Bresenham) + +```lua +-- 2D line +local points = geo.bresenhamLine(x1, y1, x2, y2) +-- points = {{x=.., y=..}, {x=.., y=..}, ...} + +-- 3D line +local points3d = geo.bresenhamLine3d(x1, y1, z1, x2, y2, z2) +``` + +#### Bezier Curves + +```lua +-- Bezier curve through control points +local controlPoints = { + {x = 0, y = 0}, + {x = 50, y = 100}, -- Control point + {x = 150, y = 100}, -- Control point + {x = 200, y = 0} +} +local curve = geo.bezierCurve(controlPoints, 50) -- 50 steps + +-- 3D Bezier +local curve3d = geo.bezierCurve3d(controlPoints3d, 50) +``` + +#### Flood Fill + +```lua +-- Fill region with new value +local filled = geo.floodFill(grid, startX, startY, newValue, { + eightConnected = false -- true for 8-directional +}) + +-- Get positions without modifying grid +local positions = geo.getFloodFillPositions(grid, startX, startY, { + eightConnected = false +}) +``` + +#### Shapes + +```lua +-- Circle (outline or filled) +local circle = geo.circle(centerX, centerY, radius, {filled = true}) + +-- Ellipse +local ellipse = geo.ellipse(centerX, centerY, radiusX, radiusY, {filled = false}) + +-- Rectangle +local rect = geo.rectangle(x1, y1, x2, y2, {filled = true}) + +-- Polygon (outline) +local polygon = geo.polygon({{x=0,y=0}, {x=100,y=0}, {x=50,y=100}}) +``` + +#### Distance Functions + +```lua +geo.distance(x1, y1, x2, y2) -- Euclidean +geo.distanceSq(x1, y1, x2, y2) -- Squared (faster) +geo.distanceManhattan(x1, y1, x2, y2) -- Manhattan +geo.distanceChebyshev(x1, y1, x2, y2) -- Chebyshev (king's move) +``` + +#### Point-in-Shape Tests + +```lua +geo.pointInCircle(px, py, cx, cy, radius) +geo.pointInRectangle(px, py, x1, y1, x2, y2) +geo.pointInPolygon(px, py, vertices) +``` + +#### Random Point Generation + +```lua +-- Simple random scatter +local points = geo.randomScatter(x1, y1, x2, y2, count, { + seed = os.time(), + minDistance = 5 -- Minimum spacing +}) + +-- Poisson disk sampling (blue noise - evenly distributed) +local blueNoise = geo.poissonDiskSampling(x1, y1, x2, y2, minDistance, { + seed = os.time(), + maxAttempts = 30 +}) +``` + +**Example - Draw River with Bezier:** +```lua +local sel = app.selection.bounds +local minX, minY, z = sel.min.x, sel.min.y, sel.min.z +local maxX, maxY = sel.max.x, sel.max.y + +-- Create curved river path +local controlPoints = { + {x = minX, y = (minY + maxY) / 2}, + {x = minX + 30, y = minY + 20}, + {x = maxX - 30, y = maxY - 20}, + {x = maxX, y = (minY + maxY) / 2} +} + +local riverPath = geo.bezierCurve(controlPoints, 100) + +app.transaction("Draw River", function() + for _, point in ipairs(riverPath) do + -- Draw circle at each point for river width + local circle = geo.circle(point.x, point.y, 2, {filled = true}) + for _, cp in ipairs(circle) do + local tile = app.map:getOrCreateTile(cp.x, cp.y, z) + if tile then + tile:addItem(4608) -- Water + end + end + end +end) +``` diff --git a/scripts/linter.lua b/scripts/linter.lua new file mode 100644 index 000000000..8d93c2476 --- /dev/null +++ b/scripts/linter.lua @@ -0,0 +1,572 @@ +--[[ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor 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 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor 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 this program. If not, see . +////////////////////////////////////////////////////////////////////// +]] + +---@class Position +---@field x number +---@field y number +---@field z number + +---@class Item +---@field id number +---@field clientId number +---@field name string +---@field fullName string +---@field count number +---@field actionId number +---@field uniqueId number +---@field description string +---@field isStackable boolean +---@field isMoveable boolean +---@field isBlocking boolean +---@field isGroundTile boolean +---@field isBorder boolean +---@field isWall boolean +---@field isDoor boolean +---@field hasElevation boolean +---@field zOrder number +local ItemClass = {} + +---@class Creature +---@field name string +---@field outfit number|table +---@field direction number +---@field spawnTime number + +---@class Spawn +---@field radius number + +---@class Tile +---@field x number +---@field y number +---@field z number +---@field items Item[] +---@field itemCount number +---@field ground Item|nil +---@field creature Creature|nil +---@field spawn Spawn|nil +---@field houseId number +---@field isPZ boolean +---@field isBlocking boolean +---@field hasCreature boolean +---@field isHouseTile boolean +---@field hasBorders boolean +---@field hasWall boolean +---@field hasTable boolean +---@field hasCarpet boolean +---@field groundZOrder number +---@field hasSpawn boolean +---@field isNoPvp boolean +---@field isNoLogout boolean +---@field isPvpZone boolean +local TileClass = {} + +--- Adds an item to the tile. +---@param id number Item ID. +---@param count? number Stack count or subtype (optional). +---@return Item +function TileClass:addItem(id, count) return {} --[[@as Item]] end + +--- Removes a specific item object from the tile. +---@param item Item +function TileClass:removeItem(item) end + +--- Moves an item within the tile stack or to another tile. +---@overload fun(self: Tile, fromIdx: integer, toIdx: integer) +---@overload fun(self: Tile, item: Item, toIdx: integer) +---@overload fun(self: Tile, item: Item, destTile: Tile, toIdx?: integer) +function TileClass:moveItem(fromIdxOrItem, toIdxOrDestTile, toIdx) end + +--- Sets the creature on the tile. +---@param name string +---@param spawnTime? number +---@param direction? number +function TileClass:setCreature(name, spawnTime, direction) end + +--- Removes the creature from the tile. +function TileClass:removeCreature() end + +--- Sets the spawn on the tile. +---@param radius number +function TileClass:setSpawn(radius) end + +--- Removes the spawn from the tile. +function TileClass:removeSpawn() end + +--- Applies wall auto-alignment to the tile. +function TileClass:wallize() end + +---@return Item +function TileClass:getTopItem() return {} --[[@as Item]] end + +---@return Position +function TileClass:getPosition() return {x=0,y=0,z=0} end + +---@class Selection +---@field size number +---@field isEmpty boolean +---@field tiles Tile[] +---@field bounds table +---@field minPosition Position +---@field maxPosition Position + +---@class Map +---@field name string +---@field width number +---@field height number +local MapClass = {} +---@param x number +---@param y number +---@param z number +---@return Tile|nil +function MapClass:getTile(x, y, z) return nil end + +---@class App +---@field version string +---@field map Map|nil +---@field selection Selection +---@field editor Editor +---@field events Events +---@field mapView MapView +---@field alert fun(message: string) +---@field refresh fun() +---@field transaction fun(name: string, callback: fun()) +---@field addContextMenu fun(label: string, callback: fun()) +---@field selectRaw fun(id: number) +---@field setCameraPosition fun(x: number, y: number, z: number) +---@field storage fun(name: string): ScriptStorage +---@field yield fun() Yields to process pending UI events (prevents freeze during long operations) +---@field sleep fun(milliseconds: number) Sleeps for the given milliseconds (max 10000). Blocks UI. +app = {} + +---@class Events +---@field on fun(self: Events, eventName: string, callback: fun()): number +---@field off fun(self: Events, listenerId: number): boolean +local Events = {} + +---@class Editor +---@field historyIndex number +---@field historySize number +---@field undo fun(self: Editor) +---@field redo fun(self: Editor) +---@field canUndo fun(self: Editor): boolean +---@field canRedo fun(self: Editor): boolean +---@field getHistory fun(self: Editor): table +---@field goToHistory fun(self: Editor, index: number) +local EditorClass = {} + +---@class Dialog +---@field data table +---@field values table +---@field bounds table +---@field dockable boolean +---@field activeTab string|nil +---@field onclose fun() +---@overload fun(title_or_config: string|{title?: string, resizable?: boolean, dockable?: boolean, id?: string, x?: number, y?: number, width?: number, height?: number, onclose?: fun()}): Dialog +local DialogClass = {} + +---@param options {text: string} +---@return Dialog +function DialogClass:label(options) return {} --[[@as Dialog]] end + +---@param options {id: string, label?: string, text?: string, onchange?: fun(val: string)} +---@return Dialog +function DialogClass:entry(options) return {} --[[@as Dialog]] end + +---@param options {id: string, label?: string, value?: number, min?: number, max?: number, decimals?: number} +---@return Dialog +function DialogClass:number(options) return {} --[[@as Dialog]] end + +---@param options {id: string, label?: string, value?: number, min?: number, max?: number} +---@return Dialog +function DialogClass:slider(options) return {} --[[@as Dialog]] end + +---@param options {id: string, text: string, selected?: boolean, onclick?: fun(val: boolean)} +---@return Dialog +function DialogClass:check(options) return {} --[[@as Dialog]] end + +---@param options {id: string, text: string, selected?: boolean} +---@return Dialog +function DialogClass:radio(options) return {} --[[@as Dialog]] end + +---@param options {id: string, label?: string, options: string[], option?: string} +---@return Dialog +function DialogClass:combobox(options) return {} --[[@as Dialog]] end + +---@param options {id: string, label?: string, color?: {red: number, green: number, blue: number}} +---@return Dialog +function DialogClass:color(options) return {} --[[@as Dialog]] end + +---@param options {id: string, label?: string, filename?: string, save?: boolean} +---@return Dialog +function DialogClass:file(options) return {} --[[@as Dialog]] end + +---@param options {id?: string, label?: string, image?: Image, path?: string, itemid?: number, spriteid?: number, width?: number, height?: number, smooth?: boolean} +---@return Dialog +function DialogClass:image(options) return {} --[[@as Dialog]] end + +---@param options {id: string, label?: string, itemid: number} +---@return Dialog +function DialogClass:item(options) return {} --[[@as Dialog]] end + +---@param options {id: string, label?: string} +---@return Dialog +function DialogClass:mapCanvas(options) return {} --[[@as Dialog]] end + +---@param options {id?: string, width?: number, height?: number, items: {text?: string, icon?: number, image?: Image|string, tooltip?: string}[], selection?: number, icon_size?: number, icon_width?: number, icon_height?: number, item_height?: number, show_text?: boolean, smooth?: boolean, onchange?: fun(dlg: Dialog), ondoubleclick?: fun(dlg: Dialog), oncontextmenu?: fun(dlg: Dialog, info: table), onleftclick?: fun(dlg: Dialog, info: table), onrightclick?: fun(dlg: Dialog, info: table)} +---@return Dialog +function DialogClass:list(options) return {} --[[@as Dialog]] end + +---@param options {id?: string, label?: string, items?: {text?: string, tooltip?: string, image?: Image|string}[], width?: number, height?: number, selection?: number, icon_size?: number, icon_width?: number, icon_height?: number, item_size?: number, item_width?: number, item_height?: number, cell_size?: number, cell_width?: number, cell_height?: number, show_text?: boolean, label_wrap?: boolean, ondoubleclick?: fun(dlg: Dialog), onchange?: fun(dlg: Dialog), oncontextmenu?: fun(dlg: Dialog, info: table), onleftclick?: fun(dlg: Dialog, info: table), onrightclick?: fun(dlg: Dialog, info: table)} +---@return Dialog +function DialogClass:grid(options) return {} --[[@as Dialog]] end + +---@param options {orient: "vertical"|"horizontal", label?: string} +---@return Dialog +function DialogClass:box(options) return {} --[[@as Dialog]] end + +---@return Dialog +function DialogClass:endbox() return {} --[[@as Dialog]] end + +---@return Dialog +function DialogClass:wrap() return {} --[[@as Dialog]] end + +---@return Dialog +function DialogClass:endwrap() return {} --[[@as Dialog]] end + +---@return Dialog +function DialogClass:newrow() return {} --[[@as Dialog]] end + +---@return Dialog +function DialogClass:separator() return {} --[[@as Dialog]] end + +---@param options {id?: string, text: string, button?: boolean, index?: number, onclick?: fun(dlg: Dialog, info: table), oncontextmenu?: fun(dlg: Dialog, info: table)} +---@return Dialog +function DialogClass:tab(options) return {} --[[@as Dialog]] end + +---@return Dialog +function DialogClass:endtabs() return {} --[[@as Dialog]] end + +---@param options {id?: string, text: string, onclick: fun(), focus?: boolean} +---@return Dialog +function DialogClass:button(options) return {} --[[@as Dialog]] end + +---@param options? {wait?: boolean, bounds?: table, center?: boolean|string, center_on?: string} +---@return Dialog +function DialogClass:show(options) return {} --[[@as Dialog]] end + +---@param options table +--- Grid size updates via modify: pass icon/item/cell size keys and resend `items` to rebuild icons. +---@return Dialog +function DialogClass:modify(options) return {} --[[@as Dialog]] end + +function DialogClass:close() end +function DialogClass:clear() end +function DialogClass:layout() end + +---@class ScriptStorage +---@field path string +---@field load fun(self: ScriptStorage): table|nil +---@field save fun(self: ScriptStorage, data: table|string): boolean +---@field clear fun(self: ScriptStorage): boolean + +---@class MapView +---@field addOverlay fun(self: MapView, id: string, options: MapOverlayOptions): boolean +---@field removeOverlay fun(self: MapView, id: string): boolean +---@field setEnabled fun(self: MapView, id: string, enabled: boolean): boolean +---@field registerShow fun(self: MapView, label: string, id: string, options?: MapOverlayShowOptions): boolean + +---@class MapOverlayOptions +---@field enabled? boolean +---@field order? number +---@field ondraw? fun(ctx: MapOverlayContext) +---@field onhover? fun(info: MapOverlayHoverInfo): string|table|nil + +---@class MapOverlayContext +---@field view {x1: number, y1: number, x2: number, y2: number, z: number, zoom: number} +---@field rect fun(self: MapOverlayContext, opts: MapOverlayRectOptions) +---@field line fun(self: MapOverlayContext, opts: MapOverlayLineOptions) +---@field text fun(self: MapOverlayContext, opts: MapOverlayTextOptions) + +---@class MapOverlayRectOptions +---@field x number +---@field y number +---@field z? number +---@field w? number +---@field h? number +---@field color? {r?: number, g?: number, b?: number, a?: number} +---@field filled? boolean +---@field width? number +---@field screen? boolean + +---@class MapOverlayLineOptions +---@field x1 number +---@field y1 number +---@field z1? number +---@field x2 number +---@field y2 number +---@field z2? number +---@field color? {r?: number, g?: number, b?: number, a?: number} +---@field width? number +---@field screen? boolean + +---@class MapOverlayTextOptions +---@field x number +---@field y number +---@field z? number +---@field text string +---@field color? {r?: number, g?: number, b?: number, a?: number} +---@field screen? boolean + +---@class MapOverlayHoverInfo +---@field pos Position +---@field screen {x: number, y: number} +---@field tile Tile|nil +---@field topItem Item|nil + +---@class MapOverlayShowOptions +---@field enabled? boolean +---@field ontoggle? fun(enabled: boolean) + +---@class Image +---@field width number +---@field height number +---@field valid boolean +---@field path string +---@field spriteId number +---@field isFromSprite boolean +---@overload fun(): Image +---@overload fun(path: string): Image +---@overload fun(options: {path?: string, itemid?: number, spriteid?: number}): Image +local ImageClass = {} + +--- Creates an Image from a file path +---@param path string +---@return Image +function ImageClass.fromFile(path) return {} --[[@as Image]] end + +--- Creates an Image from an item sprite ID +---@param itemId number +---@return Image +function ImageClass.fromItemSprite(itemId) return {} --[[@as Image]] end + +--- Creates an Image from a raw sprite ID +---@param spriteId number +---@return Image +function ImageClass.fromSprite(spriteId) return {} --[[@as Image]] end + +--- Resizes the image to the specified dimensions +---@param width number +---@param height number +---@param smooth? boolean Use smooth scaling (default true). Set to false for pixel-perfect scaling. +---@return Image +function ImageClass:resize(width, height, smooth) return {} --[[@as Image]] end + +--- Scales the image by the specified factor +---@param factor number +---@param smooth? boolean Use smooth scaling (default true). Set to false for pixel-perfect scaling. +---@return Image +function ImageClass:scale(factor, smooth) return {} --[[@as Image]] end + +---@type fun(path_or_options?: string|{path?: string, itemid?: number, spriteid?: number}): Image +Image = ImageClass + +-- Global variables set by the engine +---@type string The directory containing the currently executing script. Use this to load resources relative to your script. +---@type string The directory containing the currently executing script. Use this to load resources relative to your script. +SCRIPT_DIR = "" + +---@class Items +---@field getInfo fun(id: number): table|nil Returns item info table by ID +---@field exists fun(id: number): boolean Check if item ID exists +---@field getMaxId fun(): number Get maximum item ID +---@field findByName fun(name: string, maxResults?: number): table[] Find items by name (case-insensitive contains search) +---@field findIdByName fun(name: string): number|nil Find first item ID matching name (exact first, then contains) +---@field getName fun(id: number): string Get item name by ID +Items = {} + +-- Enums +NORTH = 0 +EAST = 1 +SOUTH = 2 +WEST = 3 +SOUTHWEST = 4 +SOUTHEAST = 5 +NORTHWEST = 6 +NORTHEAST = 7 + +-- ============================================ +-- PROCEDURAL GENERATION APIS +-- ============================================ + +---@class Noise +---@field perlin fun(x: number, y: number, seed?: number, frequency?: number): number Perlin noise 2D [-1, 1] +---@field perlin3d fun(x: number, y: number, z: number, seed?: number, frequency?: number): number Perlin noise 3D [-1, 1] +---@field simplex fun(x: number, y: number, seed?: number, frequency?: number): number OpenSimplex2 noise 2D [-1, 1] +---@field simplex3d fun(x: number, y: number, z: number, seed?: number, frequency?: number): number OpenSimplex2 noise 3D [-1, 1] +---@field simplexSmooth fun(x: number, y: number, seed?: number, frequency?: number): number OpenSimplex2S (smoother) [-1, 1] +---@field cellular fun(x: number, y: number, seed?: number, frequency?: number, distanceFunc?: string, returnType?: string): number Cellular/Voronoi noise +---@field cellular3d fun(x: number, y: number, z: number, seed?: number, frequency?: number): number Cellular noise 3D +---@field value fun(x: number, y: number, seed?: number, frequency?: number): number Value noise 2D [-1, 1] +---@field valueCubic fun(x: number, y: number, seed?: number, frequency?: number): number Cubic value noise 2D [-1, 1] +---@field fbm fun(x: number, y: number, seed?: number, options?: NoiseOptions): number Fractional Brownian Motion +---@field fbm3d fun(x: number, y: number, z: number, seed?: number, options?: NoiseOptions): number FBM 3D +---@field ridged fun(x: number, y: number, seed?: number, options?: NoiseOptions): number Ridged fractal noise +---@field warp fun(x: number, y: number, seed?: number, options?: WarpOptions): table Domain warping returns {x, y} +---@field normalize fun(value: number, min?: number, max?: number): number Normalize [-1,1] to [min, max] +---@field threshold fun(value: number, threshold: number): boolean Returns true if value >= threshold +---@field map fun(value: number, inMin: number, inMax: number, outMin: number, outMax: number): number Map value between ranges +---@field clamp fun(value: number, min: number, max: number): number Clamp value to range +---@field lerp fun(a: number, b: number, t: number): number Linear interpolation +---@field smoothstep fun(edge0: number, edge1: number, x: number): number Smooth interpolation +---@field clearCache fun() Clear noise generator cache +---@field generateGrid fun(x1: number, y1: number, x2: number, y2: number, options?: GridOptions): table[][] Generate noise grid +noise = {} + +---@class NoiseOptions +---@field frequency? number Base frequency (default 0.01) +---@field octaves? number Number of layers (default 4) +---@field lacunarity? number Frequency multiplier per octave (default 2.0) +---@field gain? number Amplitude multiplier per octave (default 0.5) +---@field noiseType? string "perlin" | "simplex" | "value" | "cellular" + +---@class WarpOptions +---@field amplitude? number Warp amplitude (default 30) +---@field frequency? number Warp frequency (default 0.01) +---@field type? string "simplex" | "simplexReduced" | "basic" + +---@class GridOptions +---@field seed? number Random seed +---@field frequency? number Noise frequency +---@field noiseType? string "perlin" | "simplex" | "cellular" | "value" +---@field fractal? string "none" | "fbm" | "ridged" +---@field octaves? number Fractal octaves +---@field lacunarity? number Fractal lacunarity +---@field gain? number Fractal gain + +---@class Algo +---@field cellularAutomata fun(grid: table[][], options?: CellularAutomataOptions): table[][] Run cellular automata +---@field generateCave fun(width: number, height: number, options?: CaveOptions): table[][] Generate cave map +---@field erode fun(heightmap: table[][], options?: ErosionOptions): table[][] Hydraulic erosion +---@field thermalErode fun(heightmap: table[][], options?: ThermalErosionOptions): table[][] Thermal erosion +---@field smooth fun(grid: table[][], options?: SmoothOptions): table[][] Gaussian-like smoothing +---@field voronoi fun(width: number, height: number, points: table[]): table[][] Generate Voronoi diagram +---@field generateRandomPoints fun(width: number, height: number, count: number, seed?: number): table[] Generate random points +---@field generateMaze fun(width: number, height: number, options?: MazeOptions): table[][] Generate maze +---@field generateDungeon fun(width: number, height: number, options?: DungeonOptions): DungeonResult Generate BSP dungeon +algo = {} + +---@class CellularAutomataOptions +---@field iterations? number Number of iterations (default 4) +---@field birthLimit? number Neighbors to become wall (default 4) +---@field deathLimit? number Neighbors to stay wall (default 3) +---@field width? number Grid width +---@field height? number Grid height + +---@class CaveOptions +---@field fillProbability? number Initial fill probability (default 0.45) +---@field iterations? number CA iterations (default 4) +---@field birthLimit? number Birth threshold (default 4) +---@field deathLimit? number Death threshold (default 3) +---@field seed? number Random seed + +---@class ErosionOptions +---@field iterations? number Number of droplets (default 50000) +---@field erosionRadius? number Erosion brush radius (default 3) +---@field inertia? number Droplet inertia (default 0.05) +---@field sedimentCapacity? number Max sediment capacity (default 4.0) +---@field minSlope? number Minimum slope (default 0.01) +---@field erosionSpeed? number Erosion rate (default 0.3) +---@field depositSpeed? number Deposit rate (default 0.3) +---@field evaporateSpeed? number Evaporation rate (default 0.01) +---@field gravity? number Gravity factor (default 4.0) +---@field seed? number Random seed +---@field maxDropletLifetime? number Max steps per droplet (default 30) + +---@class ThermalErosionOptions +---@field iterations? number Number of iterations (default 50) +---@field talusAngle? number Max slope before erosion (default 0.5) +---@field erosionAmount? number Erosion transfer rate (default 0.5) + +---@class SmoothOptions +---@field iterations? number Number of passes (default 1) +---@field kernelSize? number Kernel size (default 3) + +---@class MazeOptions +---@field seed? number Random seed + +---@class DungeonOptions +---@field minRoomSize? number Minimum room size (default 5) +---@field maxRoomSize? number Maximum room size (default 15) +---@field seed? number Random seed +---@field maxDepth? number BSP tree depth (default 4) + +---@class DungeonResult +---@field grid table[][] The dungeon grid (1=wall, 0=floor) +---@field rooms Room[] Array of room definitions + +---@class Room +---@field x number Room X position +---@field y number Room Y position +---@field width number Room width +---@field height number Room height + +---@class Geo +---@field bresenhamLine fun(x1: number, y1: number, x2: number, y2: number): Point[] Points on a line +---@field bresenhamLine3d fun(x1: number, y1: number, z1: number, x2: number, y2: number, z2: number): Point3D[] 3D line points +---@field bezierCurve fun(points: Point[], steps?: number): Point[] Bezier curve through control points +---@field bezierCurve3d fun(points: Point3D[], steps?: number): Point3D[] 3D Bezier curve +---@field floodFill fun(grid: table[][], startX: number, startY: number, newValue: number, options?: FloodFillOptions): table[][] Flood fill algorithm +---@field getFloodFillPositions fun(grid: table[][], startX: number, startY: number, options?: FloodFillOptions): Point[] Get positions without modifying +---@field circle fun(centerX: number, centerY: number, radius: number, options?: ShapeOptions): Point[] Circle points +---@field ellipse fun(centerX: number, centerY: number, radiusX: number, radiusY: number, options?: ShapeOptions): Point[] Ellipse points +---@field rectangle fun(x1: number, y1: number, x2: number, y2: number, options?: ShapeOptions): Point[] Rectangle points +---@field polygon fun(vertices: Point[], options?: ShapeOptions): Point[] Polygon outline +---@field distance fun(x1: number, y1: number, x2: number, y2: number): number Euclidean distance +---@field distanceSq fun(x1: number, y1: number, x2: number, y2: number): number Squared distance (faster) +---@field distanceManhattan fun(x1: number, y1: number, x2: number, y2: number): number Manhattan distance +---@field distanceChebyshev fun(x1: number, y1: number, x2: number, y2: number): number Chebyshev distance +---@field pointInCircle fun(px: number, py: number, cx: number, cy: number, radius: number): boolean Point in circle test +---@field pointInRectangle fun(px: number, py: number, x1: number, y1: number, x2: number, y2: number): boolean Point in rectangle test +---@field pointInPolygon fun(px: number, py: number, vertices: Point[]): boolean Point in polygon test (ray casting) +---@field randomScatter fun(x1: number, y1: number, x2: number, y2: number, count: number, options?: ScatterOptions): Point[] Random scatter +---@field poissonDiskSampling fun(x1: number, y1: number, x2: number, y2: number, minDistance: number, options?: PoissonOptions): Point[] Blue noise distribution +geo = {} + +---@class Point +---@field x number +---@field y number + +---@class Point3D +---@field x number +---@field y number +---@field z number + +---@class FloodFillOptions +---@field eightConnected? boolean Use 8-directional connectivity (default false) + +---@class ShapeOptions +---@field filled? boolean Fill the shape (default false for outline) + +---@class ScatterOptions +---@field seed? number Random seed +---@field minDistance? number Minimum distance between points (default 0) + +---@class PoissonOptions +---@field seed? number Random seed +---@field maxAttempts? number Attempts per point (default 30) From 565593680a2426389c9003007f2b6b13ae88d6c5 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 19:13:54 +0100 Subject: [PATCH 09/41] feat: add some lua script examples --- scripts/context_menu_demo.lua | 24 + scripts/fps_counter.lua | 170 +++++++ scripts/hello_world/hello_world.lua | 334 +++++++++++++ scripts/hello_world/manifest.lua | 9 + scripts/terrain_generator_demo.lua | 713 ++++++++++++++++++++++++++++ 5 files changed, 1250 insertions(+) create mode 100644 scripts/context_menu_demo.lua create mode 100644 scripts/fps_counter.lua create mode 100644 scripts/hello_world/hello_world.lua create mode 100644 scripts/hello_world/manifest.lua create mode 100644 scripts/terrain_generator_demo.lua diff --git a/scripts/context_menu_demo.lua b/scripts/context_menu_demo.lua new file mode 100644 index 000000000..e78574a58 --- /dev/null +++ b/scripts/context_menu_demo.lua @@ -0,0 +1,24 @@ +-- @Title: Context Menu Demo +-- @Author: Michy +-- @Description: Demonstrates adding a custom context menu item using the new app.addContextMenu API. +-- @Version: 1.0.0 +-- @Autorun: true + +-- Function to handle the menu click +local function onMyMenuClick() + if app.selection.isEmpty then + app.alert("You clicked the menu, but nothing is selected!") + else + local count = app.selection.size + app.alert("You clicked the menu! Selection size: " .. count) + end +end + +-- Register the menu item +-- This will appear in the right-click menu +app.addContextMenu("My Custom Tool", onMyMenuClick) + +-- Register another one +app.addContextMenu("Another Tool", function() + app.alert("Anonymous function callback works too!") +end) diff --git a/scripts/fps_counter.lua b/scripts/fps_counter.lua new file mode 100644 index 000000000..27adc8a89 --- /dev/null +++ b/scripts/fps_counter.lua @@ -0,0 +1,170 @@ +-- @Title: FPS Counter +-- @Author: Michy +-- @Description: Displays real-time FPS (frames per second) counter as an overlay in the top-right corner. Shows current FPS, average FPS, and frame time. Note: FPS reflects actual canvas redraw rate. +-- @Version: 1.0.0 +-- @Autorun: true + +-- FPS tracking variables +local frameCount = 0 +local lastTime = app.getTime() -- Use high-precision timer (milliseconds since app start) +local currentFPS = 0 +local fpsHistory = {} +local maxHistory = 60 -- Store last 60 FPS values for averaging + +-- Configuration +local config = { + updateInterval = 500, -- Update FPS display every 500 milliseconds + showBackground = true, + showAverage = true, + bgColor = { r = 20, g = 20, b = 20, a = 180 }, + textColor = { r = 0, g = 255, b = 100, a = 255 }, + warningColor = { r = 255, g = 200, b = 0, a = 255 }, -- Yellow for < 30 FPS + criticalColor = { r = 255, g = 80, b = 80, a = 255 }, -- Red for < 15 FPS + padding = 8, + margin = 10, + width = 85, + height = 38 +} + +-- Get the appropriate color based on FPS value +local function getFPSColor(fps) + if fps < 15 then + return config.criticalColor + elseif fps < 30 then + return config.warningColor + else + return config.textColor + end +end + +-- Calculate average FPS from history +local function getAverageFPS() + if #fpsHistory == 0 then + return 0 + end + local sum = 0 + for _, fps in ipairs(fpsHistory) do + sum = sum + fps + end + return sum / #fpsHistory +end + +-- Add FPS value to history +local function addToHistory(fps) + table.insert(fpsHistory, fps) + if #fpsHistory > maxHistory then + table.remove(fpsHistory, 1) + end +end + +-- The ondraw callback receives a context with drawing functions +local function onDraw(ctx) + -- Count this frame + frameCount = frameCount + 1 + + -- Calculate elapsed time using high-precision timer (milliseconds) + local currentTime = app.getTime() + local elapsed = currentTime - lastTime + + -- Update FPS calculation at configured interval + if elapsed >= config.updateInterval then + -- Convert elapsed from ms to seconds for FPS calculation + currentFPS = frameCount / (elapsed / 1000) + addToHistory(currentFPS) + frameCount = 0 + lastTime = currentTime + end + + -- Get screen dimensions from view context + local screenWidth = ctx.view.screenWidth or 800 + local screenHeight = ctx.view.screenHeight or 600 + + -- Position in top-right corner (screen space coordinates) + local x = screenWidth - config.width - config.margin + local y = config.margin + + -- Draw background panel if enabled + if config.showBackground then + ctx.rect({ + screen = true, + x = x, + y = y, + w = config.width, + h = config.showAverage and config.height + 14 or config.height, + filled = true, + color = config.bgColor + }) + + -- Draw border + ctx.rect({ + screen = true, + x = x, + y = y, + w = config.width, + h = config.showAverage and config.height + 14 or config.height, + filled = false, + width = 1, + color = { r = 60, g = 60, b = 60, a = 200 } + }) + end + + -- Get color based on current FPS + local fpsColor = getFPSColor(currentFPS) + + -- Draw FPS text + local fpsText = string.format("FPS: %.1f", currentFPS) + ctx.text({ + screen = true, + x = x + config.padding, + y = y + config.padding, + text = fpsText, + color = fpsColor + }) + + -- Draw average FPS if enabled + if config.showAverage then + local avgFPS = getAverageFPS() + local avgColor = getFPSColor(avgFPS) + local avgText = string.format("Avg: %.1f", avgFPS) + ctx.text({ + screen = true, + x = x + config.padding, + y = y + config.padding + 16, + text = avgText, + color = { r = 180, g = 180, b = 180, a = 255 } + }) + + -- Draw frame time in ms + local frameTimeMs = currentFPS > 0 and (1000 / currentFPS) or 0 + local frameTimeText = string.format("%.1fms", frameTimeMs) + ctx.text({ + screen = true, + x = x + config.padding, + y = y + config.padding + 32, + text = frameTimeText, + color = { r = 150, g = 150, b = 150, a = 255 } + }) + end +end + +-- Register the overlay +app.mapView.addOverlay("fps_counter", { + enabled = true, + order = 1000, -- High order to render on top + ondraw = onDraw +}) + +-- Register in the View > Show menu for toggling +app.mapView.registerShow("FPS Counter", "fps_counter", { + enabled = true, + ontoggle = function(enabled) + if enabled then + -- Reset counters when re-enabled + frameCount = 0 + lastTime = app.getTime() + fpsHistory = {} + end + end +}) + +print("FPS Counter overlay loaded!") diff --git a/scripts/hello_world/hello_world.lua b/scripts/hello_world/hello_world.lua new file mode 100644 index 000000000..9df19ddcb --- /dev/null +++ b/scripts/hello_world/hello_world.lua @@ -0,0 +1,334 @@ + +-- Check if app table exists (sanity check) +if not app then + print("Error: RME Lua API not found.") + return +end + +-- ============================================================================ +-- Helper Functions +-- ============================================================================ + +-- Helper to format color for display +local function colorToString(c) + if not c then return "nil" end + return string.format("R:%d G:%d B:%d", c.red, c.green, c.blue) +end + +-- Forward declaration +local createAndShowDialog + +-- ============================================================================ +-- Main Dialog Factory +-- ============================================================================ + +createAndShowDialog = function(is_dockable) + local dlg = Dialog { + title = "RME Lua API Capabilities Demo", + width = 600, + height = 500, + resizable = true, + dockable = is_dockable, + activeTab = "Basic Widgets", -- Set default tab + onclose = function() + print("Demo dialog closed.") + end + } + + -- ---------------------------------------------------------------------------- + -- Tab 1: Basic Widgets + -- ---------------------------------------------------------------------------- + dlg:tab { text = "Basic Widgets", oncontextmenu = function(d, info) + return { + { text = "Tab Context Menu", onclick = function() app.alert("Clicked Tab Context") end } + } + end } + dlg:box { orient = "vertical", label = "Input Fields" } + dlg:label { text = "Standard inputs available in the API:" } + dlg:newrow() + + dlg:input { id = "t_input", label = "Text Entry:", text = "Edit me!" } + dlg:number { id = "t_number", label = "Number Spinner:", value = 100, min = 0, max = 500 } + dlg:slider { id = "t_slider", label = "Slider:", value = 50, min = 0, max = 100 } + dlg:color { id = "t_color", label = "Color Picker:", color = { red = 100, green = 200, blue = 255 } } + dlg:file { id = "t_file", label = "File Picker:", filename = "test.txt", save = false } + dlg:endbox() + + dlg:separator() + + dlg:box { orient = "horizontal", label = "Toggles & Choices" } + dlg:box { orient = "vertical" } + dlg:check { id = "t_check_1", text = "Checkbox Option A", selected = true } + dlg:check { id = "t_check_2", text = "Checkbox Option B", selected = false } + dlg:endbox() + + dlg:box { orient = "vertical" } + dlg:radio { id = "t_radio_1", text = "Radio Mode 1", selected = true } + dlg:radio { id = "t_radio_2", text = "Radio Mode 2", selected = false } + dlg:endbox() + + dlg:combobox { id = "t_combo", label = "Combobox:", options = { "Red", "Green", "Blue", "Alpha" }, option = "Green" } + dlg:endbox() + + dlg:separator() + dlg:button { text = "Read Values", onclick = function(d) + local data = d.data + local info = string.format( + "Input: %s\nNumber: %d\nSlider: %d\nColor: %s\nCheck A: %s\nRadio 1: %s", + data.t_input, data.t_number, data.t_slider, colorToString(data.t_color), + tostring(data.t_check_1), tostring(data.t_radio_1) + ) + app.alert(info) + d:modify { t_input = { label = "Updated Label:" } } + end } + + -- ---------------------------------------------------------------------------- + -- Tab 2: Visuals & Lists + -- ---------------------------------------------------------------------------- + dlg:tab { text = "Visuals & Lists" } + + dlg:wrap({}) + -- Image Widget demos + dlg:box { orient = "vertical", label = "Images" } + dlg:label { text = "Item Sprite (2160):" } + dlg:image { id = "img_item", itemid = 2160, width = 32, height = 32, smooth = false } + + dlg:label { text = "Raw Sprite (100):" } + dlg:image { id = "img_sprite", spriteid = 100, width = 32, height = 32 } + dlg:endbox() + + -- Map Canvas + dlg:box { orient = "vertical", label = "Map Preview" } + dlg:mapCanvas { id = "preview_canvas", width = 150, height = 100 } + dlg:endbox() + dlg:endwrap() + + dlg:separator() + + -- LIST WIDGET + dlg:box { orient = "horizontal", label = "List & Grid" } + + local last_click_time = 0 + + dlg:list { + id = "demo_list", + width = 200, + height = 200, + show_text = true, + items = { + { text = "Item 1 (Plain)", tooltip = "Standard Item" }, + { text = "Item 2 (Icon)", icon = 2160, tooltip = "Item with Icon" }, + { text = "Item 3 (Tooltip)", tooltip = "I have a tooltip!" }, + { text = "Double Click Me", icon = 2152, tooltip = "Double click test" } + }, + onleftclick = function(d, info) + -- Mimic favorites behavior: simple selection logic if needed + print("List Left Click index: " .. tostring(info and info.index)) + end, + ondoubleclick = function(d) + -- WORKAROUND: Engine may trigger double click twice, using temporal debounce + local now = os.clock() + if now - last_click_time < 0.5 then + return + end + last_click_time = now + + local sel = d.data.demo_list + app.alert("List Double Click! Selection: " .. tostring(sel)) + end, + oncontextmenu = function(d, info) + -- Only show context menu if valid item clicked (favorites style) + if info and info.index and info.index > 0 then + return { + { + text = "List Action (Item " .. info.index .. ")", + onclick = function() + app.alert( + "Clicked List Item " .. info.index) + end + }, + { separator = true }, + { text = "Delete", onclick = function() app.alert("Delete Action") end } + } + else + -- Background context menu + return { + { text = "List Background Action", onclick = function() app.alert("Clicked List Background") end } + } + end + end + } + + -- GRID WIDGET + dlg:grid { + id = "demo_grid", + width = 200, + height = 200, + cell_size = 40, + item_size = 32, + show_text = true, -- Try forcing text if desired, though grid usually is icon-based + items = { + { tooltip = "Coins", image = Image.fromItemSprite(2148) }, + { tooltip = "Platinum", image = Image.fromItemSprite(2152) }, + { tooltip = "Crystal", image = Image.fromItemSprite(2160) }, + { tooltip = "Sprite 100", image = Image.fromSprite(100) }, + { tooltip = "Sprite 101", image = Image.fromSprite(101) } + }, + onleftclick = function(d, info) + print("Grid Left Click index: " .. tostring(info and info.index)) + end, + oncontextmenu = function(d, info) + -- Favorites style context menu + if info and info.index and info.index > 0 then + return { + { text = "Grid Item Action", onclick = function() app.alert("Grid Item " .. info.index) end } + } + end + return { + { text = "Grid Background Action", onclick = function() app.alert("Grid BG") end } + } + end + } + dlg:endbox() + + -- ---------------------------------------------------------------------------- + -- Tab 3: Environment Engine + -- ---------------------------------------------------------------------------- + dlg:tab { text = "Environment" } + + local map_info_text = "No map loaded." + if app.map then + map_info_text = string.format("Map: %s\nSize: %dx%d\nTiles: %d", + app.map.name or "Untitled", + app.map.width, app.map.height, + app.map.tileCount or 0 + ) + end + + dlg:label { id = "lbl_map_info", text = map_info_text } + + dlg:separator() + + dlg:button { text = "Inspect Selection", onclick = function() + local sel = app.selection + if not sel or sel.isEmpty then + app.alert("Selection is empty.\nSelect some tiles in the map editor first.") + else + local msg = string.format("Selected Tiles: %d\nBounds: (%d, %d, %d) to (%d, %d, %d)", + sel.size, + sel.minPosition.x, sel.minPosition.y, sel.minPosition.z, + sel.maxPosition.x, sel.maxPosition.y, sel.maxPosition.z + ) + app.alert(msg) + end + end } + + dlg:button { text = "Transaction Demo (Add Sparkles)", onclick = function() + if not app.map then + app.alert("No map loaded!") + return + end + + local sel = app.selection + if sel.isEmpty then + app.alert("Select area first to spawn sparkles (ID 2014)!") + return + end + + app.transaction("Demo Sparkles", function() + for _, tile in ipairs(sel.tiles) do + -- Add magic effect / sparkles + tile:addItem(2785, 1) + end + end) + app.alert("Added blueberry bushes to " .. sel.size .. " tiles.") + end } + + dlg:separator() + dlg:label { text = "Map Overlays (Scripting API)" } + dlg:button { text = "Toggle 'Demo Overlay'", onclick = function() + local overlay_id = "demo_overlay" + app.mapView:addOverlay(overlay_id, { + ondraw = function(ctx) + ctx:rect { x = 10, y = 10, w = 200, h = 50, color = { r = 0, g = 0, b = 0, a = 100 }, screen = true, filled = true } + ctx:text { x = 20, y = 25, text = "Demo Overlay Active", color = { r = 255, g = 255, b = 255 }, screen = true } + end + }) + app.alert("Overlay added. Move map to see updates if it was drawing world coords.") + end } + + -- ---------------------------------------------------------------------------- + -- Tab 4: System & Network + -- ---------------------------------------------------------------------------- + dlg:tab { text = "System" } + + dlg:label { text = "Editor Version: " .. app.version } + + dlg:separator() + + -- DOCKABLE TOGGLE + local dock_btn_text = is_dockable and "Reopen as Floating Window" or "Reopen as Dockable Window" + dlg:button { text = dock_btn_text, onclick = function(d) + d:close() + createAndShowDialog(not is_dockable) + end } + + dlg:separator() + + dlg:label { text = "HTTP Requests:" } + dlg:label { text = "Result will appear here...", id = "lbl_http_res" } + + dlg:button { text = "Get Random Quote (HTTP JSON)", onclick = function(d) + if not http then + d:modify { lbl_http_res = { text = "Error: HTTP module not available." } } + return + end + + d:modify { lbl_http_res = { text = "Fetching..." } } + + local res = http.get("https://dummyjson.com/quotes/random") + + if res.ok then + if json and json.decode then + local status, data = pcall(json.decode, res.body) + if status and data then + local quote = data.quote or "No quote found" + local author = data.author or "Unknown" + local fmt = string.format('"%s" - %s', quote, author) + d:modify { lbl_http_res = { text = fmt } } + else + d:modify { lbl_http_res = { text = "Invalid JSON: " .. string.sub(res.body, 1, 50) } } + end + else + d:modify { lbl_http_res = { text = "Raw: " .. string.sub(res.body, 1, 50) .. "..." } } + end + else + d:modify { lbl_http_res = { text = "HTTP Error: " .. (res.error or "Unknown") } } + end + end } + + dlg:separator() + + dlg:button { text = "Save Timestamp to Storage", onclick = function() + local store = app.storage("hello_world_demo") + local data = store:load() or {} + data.last_run = os.time() + store:save(data) + app.alert("Saved timestamp: " .. data.last_run) + end } + + dlg:endtabs() + dlg:separator() + + dlg:button { text = "Close Demo", onclick = function(d) d:close() end } + dlg:show { wait = false } +end + +-- ============================================================================ +-- Entry Point +-- ============================================================================ + +-- Start with defaults +createAndShowDialog(false) + +print("Hello World demo executed.") diff --git a/scripts/hello_world/manifest.lua b/scripts/hello_world/manifest.lua new file mode 100644 index 000000000..6d4546c5d --- /dev/null +++ b/scripts/hello_world/manifest.lua @@ -0,0 +1,9 @@ +return { + name = "Hello World", + description = "A comprehensive tour of the RME Lua API v1 capabilities properly linted and structured", + version = "1.0.0", + author = "Michy", + main = "hello_world", + shortcut = "Ctrl+Shift+H", + autorun = false +} diff --git a/scripts/terrain_generator_demo.lua b/scripts/terrain_generator_demo.lua new file mode 100644 index 000000000..caed1cfd5 --- /dev/null +++ b/scripts/terrain_generator_demo.lua @@ -0,0 +1,713 @@ +-- @Title: Terrain Generator Demo in-built +-- @Author: Michy +-- @Description: Demonstrates the procedural generation APIs (noise, algo, geo) with a rich UI for creating islands, caves, dungeons and more. +-- @Version: 1.0 +-- @Shortcut: Ctrl+Shift+T + +-- ============================================================================ +-- Configuration & State +-- ============================================================================ + +local config = { + -- General + seed = os.time(), + + -- Island generator + island = { + waterTile = 4608, -- Water + sandTile = 4526, -- Sand + grassTile = 4526, -- Grass + forestTile = 4526, -- Forest ground + mountainTile = 919, -- Mountain/rock + frequency = 0.03, + octaves = 4, + waterLevel = -0.2, + sandLevel = 0.0, + forestLevel = 0.4, + mountainLevel = 0.7, + addTrees = true, + treeDensity = 0.15, + }, + + -- Cave generator + cave = { + wallTile = 919, -- Rock wall + floorTile = 4526, -- Cave floor + fillProbability = 0.45, + iterations = 5, + birthLimit = 4, + deathLimit = 3, + }, + + -- Dungeon generator + dungeon = { + wallTile = 919, + floorTile = 4526, + minRoomSize = 4, + maxRoomSize = 10, + maxDepth = 4, + }, + + -- River/Path + river = { + waterTile = 4608, + pathWidth = 2, + curveStrength = 30, + }, +} + +-- Store for persistence +local store = app.storage and app.storage("terrain_generator_demo") +local savedConfig = store and store:load() +if savedConfig then + -- Merge saved config (shallow) + for k, v in pairs(savedConfig) do + if type(v) == "table" and config[k] then + for k2, v2 in pairs(v) do + config[k][k2] = v2 + end + else + config[k] = v + end + end +end + +-- ============================================================================ +-- Helper Functions +-- ============================================================================ + +local function saveConfig() + if store then + store:save(config) + end +end + +local function getSelection() + local sel = app.selection + if not sel or sel.isEmpty then + return nil + end + return sel.bounds +end + +local function requireSelection() + local bounds = getSelection() + if not bounds then + app.alert("Please select an area on the map first!") + return nil + end + return bounds +end + +-- Apply ground tile with auto-borderize +local function setGroundTile(x, y, z, tileId) + local tile = app.map:getOrCreateTile(x, y, z) + if tile then + tile:addItem(tileId) + end +end + +-- Borderize an area after placing grounds +local function borderizeArea(x1, y1, x2, y2, z) + for y = y1 - 1, y2 + 1 do + for x = x1 - 1, x2 + 1 do + local tile = app.map:getTile(x, y, z) + if tile then + tile:borderize() + end + end + end +end + +-- ============================================================================ +-- Island Generator +-- ============================================================================ + +local function generateIsland(bounds, cfg) + local minX, minY, z = bounds.min.x, bounds.min.y, bounds.min.z + local maxX, maxY = bounds.max.x, bounds.max.y + local width = maxX - minX + 1 + local height = maxY - minY + 1 + local centerX = width / 2 + local centerY = height / 2 + local maxRadius = math.min(centerX, centerY) + + local seed = cfg.seed or os.time() + local treesPlaced = 0 + + app.transaction("Generate Island", function() + for y = minY, maxY do + for x = minX, maxX do + local localX = x - minX + local localY = y - minY + + -- Get noise value + local n = noise.fbm(x, y, seed, { + frequency = cfg.frequency, + octaves = cfg.octaves, + noiseType = "simplex" + }) + + -- Apply radial falloff (island shape) + local dx = localX - centerX + local dy = localY - centerY + local distFromCenter = math.sqrt(dx * dx + dy * dy) / maxRadius + local falloff = 1 - distFromCenter * distFromCenter + n = n * falloff + + -- Determine terrain type + local tileId + local canPlaceTree = false + + if n < cfg.waterLevel then + tileId = cfg.waterTile + elseif n < cfg.sandLevel then + tileId = cfg.sandTile + elseif n < cfg.forestLevel then + tileId = cfg.grassTile + canPlaceTree = cfg.addTrees + elseif n < cfg.mountainLevel then + tileId = cfg.forestTile + canPlaceTree = cfg.addTrees + else + tileId = cfg.mountainTile + end + + setGroundTile(x, y, z, tileId) + + -- Place trees + if canPlaceTree and math.random() < cfg.treeDensity then + local tile = app.map:getTile(x, y, z) + if tile then + -- Use doodad brush for trees if available + local treeBrush = Brushes.get("palm trees") or Brushes.get("trees") or Brushes.get("green trees") + if treeBrush then + tile:applyBrush(treeBrush.name, false) + treesPlaced = treesPlaced + 1 + end + end + end + end + + -- Yield periodically to keep UI responsive + if y % 10 == 0 then + app.yield() + end + end + + -- Borderize + borderizeArea(minX, minY, maxX, maxY, z) + end) + + return { treesPlaced = treesPlaced } +end + +-- ============================================================================ +-- Cave Generator (Cellular Automata) +-- ============================================================================ + +local function generateCave(bounds, cfg) + local minX, minY, z = bounds.min.x, bounds.min.y, bounds.min.z + local maxX, maxY = bounds.max.x, bounds.max.y + local width = maxX - minX + 1 + local height = maxY - minY + 1 + + -- Generate cave map using algo API + local caveMap = algo.generateCave(width, height, { + fillProbability = cfg.fillProbability, + iterations = cfg.iterations, + birthLimit = cfg.birthLimit, + deathLimit = cfg.deathLimit, + seed = cfg.seed or os.time(), + }) + + app.transaction("Generate Cave", function() + for y = 1, height do + for x = 1, width do + local mapX = minX + x - 1 + local mapY = minY + y - 1 + + local isWall = caveMap[y] and caveMap[y][x] == 1 + local tileId = isWall and cfg.wallTile or cfg.floorTile + + setGroundTile(mapX, mapY, z, tileId) + end + + if y % 10 == 0 then + app.yield() + end + end + + borderizeArea(minX, minY, maxX, maxY, z) + end) +end + +-- ============================================================================ +-- Dungeon Generator (BSP) +-- ============================================================================ + +local function generateDungeon(bounds, cfg) + local minX, minY, z = bounds.min.x, bounds.min.y, bounds.min.z + local maxX, maxY = bounds.max.x, bounds.max.y + local width = maxX - minX + 1 + local height = maxY - minY + 1 + + -- Generate dungeon using algo API + local result = algo.generateDungeon(width, height, { + minRoomSize = cfg.minRoomSize, + maxRoomSize = cfg.maxRoomSize, + maxDepth = cfg.maxDepth, + seed = cfg.seed or os.time(), + }) + + local dungeonMap = result.grid + local rooms = result.rooms + + app.transaction("Generate Dungeon", function() + for y = 1, height do + for x = 1, width do + local mapX = minX + x - 1 + local mapY = minY + y - 1 + + local isWall = dungeonMap[y] and dungeonMap[y][x] == 1 + local tileId = isWall and cfg.wallTile or cfg.floorTile + + setGroundTile(mapX, mapY, z, tileId) + end + + if y % 10 == 0 then + app.yield() + end + end + + borderizeArea(minX, minY, maxX, maxY, z) + end) + + return { roomCount = #rooms } +end + +-- ============================================================================ +-- River/Path Generator (Bezier + Bresenham) +-- ============================================================================ + +local function generateRiver(bounds, cfg) + local minX, minY, z = bounds.min.x, bounds.min.y, bounds.min.z + local maxX, maxY = bounds.max.x, bounds.max.y + local width = maxX - minX + 1 + local height = maxY - minY + 1 + + -- Create control points for river curve + local seed = cfg.seed or os.time() + math.randomseed(seed) + + local controlPoints = { + { x = minX, y = minY + math.random(0, height - 1) }, + { x = minX + width * 0.25 + math.random(-cfg.curveStrength, cfg.curveStrength), y = minY + math.random(0, height - 1) }, + { x = minX + width * 0.75 + math.random(-cfg.curveStrength, cfg.curveStrength), y = minY + math.random(0, height - 1) }, + { x = maxX, y = minY + math.random(0, height - 1) }, + } + + -- Get bezier curve points + local riverPath = geo.bezierCurve(controlPoints, width * 2) + + app.transaction("Generate River", function() + -- Draw river with width + for _, point in ipairs(riverPath) do + local cx = math.floor(point.x) + local cy = math.floor(point.y) + + -- Draw circle at each point for river width + local circlePoints = geo.circle(cx, cy, cfg.pathWidth, { filled = true }) + for _, cp in ipairs(circlePoints) do + if cp.x >= minX and cp.x <= maxX and cp.y >= minY and cp.y <= maxY then + setGroundTile(cp.x, cp.y, z, cfg.waterTile) + end + end + end + + borderizeArea(minX, minY, maxX, maxY, z) + end) + + return { pointCount = #riverPath } +end + +-- ============================================================================ +-- Noise Preview +-- ============================================================================ + +local function generateNoisePreview(bounds, noiseType, seed, frequency) + local minX, minY, z = bounds.min.x, bounds.min.y, bounds.min.z + local maxX, maxY = bounds.max.x, bounds.max.y + + -- Define gradient from water to mountain + local gradientTiles = { + 4608, -- Deep water (n < -0.6) + 4608, -- Water (n < -0.2) + 4526, -- Sand (n < 0.0) + 4526, -- Grass (n < 0.3) + 4526, -- Forest (n < 0.6) + 919, -- Mountain (n >= 0.6) + } + + app.transaction("Noise Preview", function() + for y = minY, maxY do + for x = minX, maxX do + local n + + if noiseType == "perlin" then + n = noise.perlin(x, y, seed, frequency) + elseif noiseType == "simplex" then + n = noise.simplex(x, y, seed, frequency) + elseif noiseType == "cellular" then + n = noise.cellular(x, y, seed, frequency) + elseif noiseType == "fbm" then + n = noise.fbm(x, y, seed, { frequency = frequency, octaves = 4 }) + elseif noiseType == "ridged" then + n = noise.ridged(x, y, seed, { frequency = frequency }) + else + n = noise.simplex(x, y, seed, frequency) + end + + -- Map noise to gradient + local idx = math.floor(noise.normalize(n, 1, #gradientTiles)) + idx = math.max(1, math.min(idx, #gradientTiles)) + + setGroundTile(x, y, z, gradientTiles[idx]) + end + + if y % 10 == 0 then + app.yield() + end + end + + borderizeArea(minX, minY, maxX, maxY, z) + end) +end + +-- ============================================================================ +-- Voronoi Regions +-- ============================================================================ + +local function generateVoronoi(bounds, numRegions) + local minX, minY, z = bounds.min.x, bounds.min.y, bounds.min.z + local maxX, maxY = bounds.max.x, bounds.max.y + local width = maxX - minX + 1 + local height = maxY - minY + 1 + + -- Generate seed points + local points = algo.generateRandomPoints(width, height, numRegions, config.seed) + + -- Generate Voronoi diagram + local voronoiMap = algo.voronoi(width, height, points) + + -- Define tiles for each region (cycle through) + local regionTiles = { 4526, 4608, 919, 4526, 4608, 919 } + + app.transaction("Generate Voronoi", function() + for y = 1, height do + for x = 1, width do + local mapX = minX + x - 1 + local mapY = minY + y - 1 + + local region = voronoiMap[y] and voronoiMap[y][x] or 1 + local tileId = regionTiles[((region - 1) % #regionTiles) + 1] + + setGroundTile(mapX, mapY, z, tileId) + end + + if y % 10 == 0 then + app.yield() + end + end + + borderizeArea(minX, minY, maxX, maxY, z) + end) + + return { regionCount = numRegions } +end + +-- ============================================================================ +-- Maze Generator +-- ============================================================================ + +local function generateMaze(bounds, wallTile, floorTile) + local minX, minY, z = bounds.min.x, bounds.min.y, bounds.min.z + local maxX, maxY = bounds.max.x, bounds.max.y + local width = maxX - minX + 1 + local height = maxY - minY + 1 + + local mazeMap = algo.generateMaze(width, height, { seed = config.seed }) + local mazeHeight = #mazeMap + local mazeWidth = mazeMap[1] and #mazeMap[1] or 0 + + app.transaction("Generate Maze", function() + for y = 1, mazeHeight do + for x = 1, mazeWidth do + local mapX = minX + x - 1 + local mapY = minY + y - 1 + + if mapX <= maxX and mapY <= maxY then + local isWall = mazeMap[y] and mazeMap[y][x] == 1 + local tileId = isWall and wallTile or floorTile + setGroundTile(mapX, mapY, z, tileId) + end + end + + if y % 10 == 0 then + app.yield() + end + end + + borderizeArea(minX, minY, maxX, maxY, z) + end) +end + +-- ============================================================================ +-- Dialog UI +-- ============================================================================ + +local function createAndShowDialog() + local dlg = Dialog { + title = "Terrain Generator", + width = 500, + height = 550, + resizable = true, + dockable = true, + onclose = function() + saveConfig() + end + } + + -- ======================================== + -- Tab 1: Island Generator + -- ======================================== + dlg:tab { text = "Island" } + + dlg:box { orient = "vertical", label = "Noise Settings" } + dlg:number { id = "island_seed", label = "Seed:", value = config.seed, min = 0, max = 999999999 } + dlg:slider { id = "island_frequency", label = "Frequency (x100):", value = config.island.frequency * 100, min = 1, max = 20 } + dlg:slider { id = "island_octaves", label = "Octaves:", value = config.island.octaves, min = 1, max = 8 } + dlg:endbox() + + dlg:box { orient = "vertical", label = "Terrain Levels" } + dlg:slider { id = "island_water", label = "Water Level:", value = (config.island.waterLevel + 1) * 50, min = 0, max = 100 } + dlg:slider { id = "island_sand", label = "Sand Level:", value = (config.island.sandLevel + 1) * 50, min = 0, max = 100 } + dlg:slider { id = "island_forest", label = "Forest Level:", value = (config.island.forestLevel + 1) * 50, min = 0, max = 100 } + dlg:slider { id = "island_mountain", label = "Mountain Level:", value = (config.island.mountainLevel + 1) * 50, min = 0, max = 100 } + dlg:endbox() + + dlg:box { orient = "horizontal", label = "Tile IDs" } + dlg:item { id = "island_water_tile", label = "Water:", itemid = config.island.waterTile } + dlg:item { id = "island_grass_tile", label = "Grass:", itemid = config.island.grassTile } + dlg:item { id = "island_mountain_tile", label = "Mountain:", itemid = config.island.mountainTile } + dlg:endbox() + + dlg:check { id = "island_trees", text = "Add Trees", selected = config.island.addTrees } + dlg:slider { id = "island_tree_density", label = "Tree Density (%):", value = config.island.treeDensity * 100, min = 0, max = 50 } + + dlg:button { text = "Generate Island", onclick = function(d) + local bounds = requireSelection() + if not bounds then return end + + -- Update config from UI + config.seed = d.data.island_seed + config.island.frequency = d.data.island_frequency / 100 + config.island.octaves = d.data.island_octaves + config.island.waterLevel = d.data.island_water / 50 - 1 + config.island.sandLevel = d.data.island_sand / 50 - 1 + config.island.forestLevel = d.data.island_forest / 50 - 1 + config.island.mountainLevel = d.data.island_mountain / 50 - 1 + config.island.waterTile = d.data.island_water_tile + config.island.grassTile = d.data.island_grass_tile + config.island.mountainTile = d.data.island_mountain_tile + config.island.addTrees = d.data.island_trees + config.island.treeDensity = d.data.island_tree_density / 100 + + local result = generateIsland(bounds, config.island) + app.alert("Island generated! Trees placed: " .. result.treesPlaced) + app.refresh() + end } + + -- ======================================== + -- Tab 2: Cave Generator + -- ======================================== + dlg:tab { text = "Cave" } + + dlg:box { orient = "vertical", label = "Cellular Automata Settings" } + dlg:number { id = "cave_seed", label = "Seed:", value = config.seed, min = 0, max = 999999999 } + dlg:slider { id = "cave_fill", label = "Fill Probability (%):", value = config.cave.fillProbability * 100, min = 30, max = 60 } + dlg:slider { id = "cave_iterations", label = "Iterations:", value = config.cave.iterations, min = 1, max = 10 } + dlg:slider { id = "cave_birth", label = "Birth Limit:", value = config.cave.birthLimit, min = 2, max = 6 } + dlg:slider { id = "cave_death", label = "Death Limit:", value = config.cave.deathLimit, min = 2, max = 6 } + dlg:endbox() + + dlg:box { orient = "horizontal", label = "Tile IDs" } + dlg:item { id = "cave_wall_tile", label = "Wall:", itemid = config.cave.wallTile } + dlg:item { id = "cave_floor_tile", label = "Floor:", itemid = config.cave.floorTile } + dlg:endbox() + + dlg:button { text = "Generate Cave", onclick = function(d) + local bounds = requireSelection() + if not bounds then return end + + config.seed = d.data.cave_seed + config.cave.fillProbability = d.data.cave_fill / 100 + config.cave.iterations = d.data.cave_iterations + config.cave.birthLimit = d.data.cave_birth + config.cave.deathLimit = d.data.cave_death + config.cave.wallTile = d.data.cave_wall_tile + config.cave.floorTile = d.data.cave_floor_tile + + generateCave(bounds, config.cave) + app.alert("Cave generated!") + app.refresh() + end } + + -- ======================================== + -- Tab 3: Dungeon Generator + -- ======================================== + dlg:tab { text = "Dungeon" } + + dlg:box { orient = "vertical", label = "BSP Settings" } + dlg:number { id = "dungeon_seed", label = "Seed:", value = config.seed, min = 0, max = 999999999 } + dlg:slider { id = "dungeon_min_room", label = "Min Room Size:", value = config.dungeon.minRoomSize, min = 3, max = 10 } + dlg:slider { id = "dungeon_max_room", label = "Max Room Size:", value = config.dungeon.maxRoomSize, min = 5, max = 20 } + dlg:slider { id = "dungeon_depth", label = "BSP Depth:", value = config.dungeon.maxDepth, min = 2, max = 6 } + dlg:endbox() + + dlg:box { orient = "horizontal", label = "Tile IDs" } + dlg:item { id = "dungeon_wall_tile", label = "Wall:", itemid = config.dungeon.wallTile } + dlg:item { id = "dungeon_floor_tile", label = "Floor:", itemid = config.dungeon.floorTile } + dlg:endbox() + + dlg:button { text = "Generate Dungeon", onclick = function(d) + local bounds = requireSelection() + if not bounds then return end + + config.seed = d.data.dungeon_seed + config.dungeon.minRoomSize = d.data.dungeon_min_room + config.dungeon.maxRoomSize = d.data.dungeon_max_room + config.dungeon.maxDepth = d.data.dungeon_depth + config.dungeon.wallTile = d.data.dungeon_wall_tile + config.dungeon.floorTile = d.data.dungeon_floor_tile + + local result = generateDungeon(bounds, config.dungeon) + app.alert("Dungeon generated! Rooms: " .. result.roomCount) + app.refresh() + end } + + -- ======================================== + -- Tab 4: More Tools + -- ======================================== + dlg:tab { text = "More" } + + dlg:box { orient = "vertical", label = "River Generator" } + dlg:item { id = "river_tile", label = "Water Tile:", itemid = config.river.waterTile } + dlg:slider { id = "river_width", label = "Path Width:", value = config.river.pathWidth, min = 1, max = 5 } + dlg:slider { id = "river_curve", label = "Curve Strength:", value = config.river.curveStrength, min = 0, max = 50 } + dlg:button { text = "Generate River", onclick = function(d) + local bounds = requireSelection() + if not bounds then return end + + config.river.waterTile = d.data.river_tile + config.river.pathWidth = d.data.river_width + config.river.curveStrength = d.data.river_curve + + generateRiver(bounds, config.river) + app.alert("River generated!") + app.refresh() + end } + dlg:endbox() + + dlg:separator() + + dlg:box { orient = "vertical", label = "Voronoi Regions" } + dlg:slider { id = "voronoi_regions", label = "Num Regions:", value = 10, min = 3, max = 30 } + dlg:button { text = "Generate Voronoi", onclick = function(d) + local bounds = requireSelection() + if not bounds then return end + + local result = generateVoronoi(bounds, d.data.voronoi_regions) + app.alert("Voronoi generated! Regions: " .. result.regionCount) + app.refresh() + end } + dlg:endbox() + + dlg:separator() + + dlg:box { orient = "vertical", label = "Maze" } + dlg:item { id = "maze_wall", label = "Wall:", itemid = 919 } + dlg:item { id = "maze_floor", label = "Floor:", itemid = 4526 } + dlg:button { text = "Generate Maze", onclick = function(d) + local bounds = requireSelection() + if not bounds then return end + + generateMaze(bounds, d.data.maze_wall, d.data.maze_floor) + app.alert("Maze generated!") + app.refresh() + end } + dlg:endbox() + + -- ======================================== + -- Tab 5: Noise Preview + -- ======================================== + dlg:tab { text = "Noise Test" } + + dlg:box { orient = "vertical", label = "Noise Preview" } + dlg:label { text = "Visualize different noise types on the selected area." } + dlg:number { id = "preview_seed", label = "Seed:", value = config.seed, min = 0, max = 999999999 } + dlg:slider { id = "preview_freq", label = "Frequency (x100):", value = 3, min = 1, max = 20 } + dlg:combobox { id = "preview_type", label = "Noise Type:", options = { "perlin", "simplex", "cellular", "fbm", "ridged" }, option = "simplex" } + dlg:endbox() + + dlg:button { text = "Preview Noise", onclick = function(d) + local bounds = requireSelection() + if not bounds then return end + + generateNoisePreview(bounds, d.data.preview_type, d.data.preview_seed, d.data.preview_freq / 100) + app.refresh() + end } + + dlg:separator() + + dlg:label { text = "Tip: Use the noise, algo, and geo APIs directly in your scripts!" } + dlg:label { text = "Example: local n = noise.simplex(x, y, seed, 0.05)" } + + dlg:endtabs() + + dlg:separator() + + dlg:box { orient = "horizontal" } + dlg:button { text = "Randomize Seed", onclick = function(d) + local newSeed = os.time() + math.random(0, 10000) + d:modify { + island_seed = { value = newSeed }, + cave_seed = { value = newSeed }, + dungeon_seed = { value = newSeed }, + preview_seed = { value = newSeed }, + } + config.seed = newSeed + end } + dlg:button { text = "Close", onclick = function(d) + d:close() + end } + dlg:endbox() + + dlg:show { wait = false } +end + +-- ============================================================================ +-- Entry Point +-- ============================================================================ + +if not app then + app.alert("Error: RME Lua API not found.") + return +end + +if not noise then + app.alert("Error: The 'noise' API is not available.\nPlease update RME to the latest version.") + return +end + +createAndShowDialog() From 4dc1a3860dd1187e0edd548e3b9a246746ad61a8 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 19:14:06 +0100 Subject: [PATCH 10/41] fix: downgrade version to base version 2>1 --- source/lua/lua_api_app.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/lua/lua_api_app.cpp b/source/lua/lua_api_app.cpp index ce62a9e93..cdf8161ab 100644 --- a/source/lua/lua_api_app.cpp +++ b/source/lua/lua_api_app.cpp @@ -503,7 +503,7 @@ void registerApp(sol::state& lua) { // Version info app["version"] = __RME_VERSION__; - app["apiVersion"] = 2; // Bumped version for new APIs + app["apiVersion"] = 1; // Functions app["alert"] = showAlert; From 05ab989c1bf3514efd20b264bf9da71a140b5573 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 23 Jan 2026 20:39:41 +0000 Subject: [PATCH 11/41] Code format - (Clang-format) --- source/action.h | 49 +- source/application.cpp | 13 +- source/definitions.h | 2 +- source/editor.h | 4 +- source/ext/pugiconfig.hpp | 34 +- source/ext/pugixml.cpp | 11849 ++++++++++---------- source/ext/pugixml.hpp | 414 +- source/fast_noise_lite.h | 5512 ++++----- source/json/json_spirit.h | 2 +- source/json/json_spirit_error_position.h | 70 +- source/json/json_spirit_reader.cpp | 120 +- source/json/json_spirit_reader.h | 55 +- source/json/json_spirit_reader_template.h | 1104 +- source/json/json_spirit_stream_reader.h | 95 +- source/json/json_spirit_utils.h | 64 +- source/json/json_spirit_value.h | 848 +- source/json/json_spirit_writer.cpp | 80 +- source/json/json_spirit_writer.h | 41 +- source/json/json_spirit_writer_template.h | 463 +- source/lua/lua_api.cpp | 64 +- source/lua/lua_api_algo.cpp | 1363 +-- source/lua/lua_api_app.cpp | 1164 +- source/lua/lua_api_brush.cpp | 282 +- source/lua/lua_api_color.cpp | 111 +- source/lua/lua_api_color.h | 60 +- source/lua/lua_api_creature.cpp | 141 +- source/lua/lua_api_geo.cpp | 1532 +-- source/lua/lua_api_http.cpp | 685 +- source/lua/lua_api_http.h | 2 +- source/lua/lua_api_image.cpp | 434 +- source/lua/lua_api_image.h | 118 +- source/lua/lua_api_item.cpp | 373 +- source/lua/lua_api_json.cpp | 285 +- source/lua/lua_api_json.h | 2 +- source/lua/lua_api_map.cpp | 249 +- source/lua/lua_api_noise.cpp | 822 +- source/lua/lua_api_position.cpp | 71 +- source/lua/lua_api_selection.cpp | 170 +- source/lua/lua_api_tile.cpp | 278 +- source/lua/lua_dialog.cpp | 385 +- source/lua/lua_dialog.h | 4 +- source/lua/lua_engine.cpp | 9 +- source/lua/lua_engine.h | 24 +- source/lua/lua_script.cpp | 60 +- source/lua/lua_script.h | 66 +- source/lua/lua_script_manager.cpp | 45 +- source/lua/lua_script_manager.h | 32 +- source/lua/lua_scripts_window.cpp | 49 +- source/lua/lua_scripts_window.h | 8 +- source/main_menubar.cpp | 4 +- source/main_menubar.h | 8 +- source/map_display.cpp | 7 +- source/map_display.h | 12 +- source/map_drawer.cpp | 11 +- source/mt_rand.cpp | 2 +- 55 files changed, 15028 insertions(+), 14723 deletions(-) diff --git a/source/action.h b/source/action.h index 9c7cc663f..8a59f944f 100644 --- a/source/action.h +++ b/source/action.h @@ -234,24 +234,41 @@ class ActionQueue { // Get action name for a given index std::string getActionName(size_t index) const { - if (index >= actions.size()) return ""; + if (index >= actions.size()) { + return ""; + } ActionIdentifier type = actions[index]->getType(); switch (type) { - case ACTION_MOVE: return "Move"; - case ACTION_REMOTE: return "Remote Edit"; - case ACTION_SELECT: return "Selection"; - case ACTION_DELETE_TILES: return "Delete"; - case ACTION_CUT_TILES: return "Cut"; - case ACTION_PASTE_TILES: return "Paste"; - case ACTION_RANDOMIZE: return "Randomize"; - case ACTION_BORDERIZE: return "Borderize"; - case ACTION_DRAW: return "Draw"; - case ACTION_SWITCHDOOR: return "Switch Door"; - case ACTION_ROTATE_ITEM: return "Rotate"; - case ACTION_REPLACE_ITEMS: return "Replace"; - case ACTION_CHANGE_PROPERTIES: return "Properties"; - case ACTION_LUA_SCRIPT: return "Script"; - default: return "Unknown"; + case ACTION_MOVE: + return "Move"; + case ACTION_REMOTE: + return "Remote Edit"; + case ACTION_SELECT: + return "Selection"; + case ACTION_DELETE_TILES: + return "Delete"; + case ACTION_CUT_TILES: + return "Cut"; + case ACTION_PASTE_TILES: + return "Paste"; + case ACTION_RANDOMIZE: + return "Randomize"; + case ACTION_BORDERIZE: + return "Borderize"; + case ACTION_DRAW: + return "Draw"; + case ACTION_SWITCHDOOR: + return "Switch Door"; + case ACTION_ROTATE_ITEM: + return "Rotate"; + case ACTION_REPLACE_ITEMS: + return "Replace"; + case ACTION_CHANGE_PROPERTIES: + return "Properties"; + case ACTION_LUA_SCRIPT: + return "Script"; + default: + return "Unknown"; } } diff --git a/source/application.cpp b/source/application.cpp index 67d06652d..19321bc65 100644 --- a/source/application.cpp +++ b/source/application.cpp @@ -406,18 +406,7 @@ MainFrame::MainFrame(const wxString& title, const wxPoint& pos, const wxSize& si // Create Script Manager panel (dockable) LuaScriptsWindow* scriptsWindow = newd LuaScriptsWindow(this); LuaScriptsWindow::SetInstance(scriptsWindow); - g_gui.aui_manager->AddPane(scriptsWindow, - wxAuiPaneInfo() - .Name("ScriptManager") - .Caption("Script Manager") - .Right() - .CloseButton(true) - .MaximizeButton(false) - .MinimizeButton(false) - .Floatable(true) - .BestSize(450, 350) - .MinSize(300, 200) - .Hide() // Hidden by default, show from menu + g_gui.aui_manager->AddPane(scriptsWindow, wxAuiPaneInfo().Name("ScriptManager").Caption("Script Manager").Right().CloseButton(true).MaximizeButton(false).MinimizeButton(false).Floatable(true).BestSize(450, 350).MinSize(300, 200).Hide() // Hidden by default, show from menu ); g_gui.aui_manager->Update(); diff --git a/source/definitions.h b/source/definitions.h index c1c40eb62..e59cc8dac 100644 --- a/source/definitions.h +++ b/source/definitions.h @@ -30,7 +30,7 @@ #define __LIVE_NET_VERSION__ 5 #define MAKE_VERSION_ID(major, minor, subversion) \ - ((major) * 10000000 + (minor) * 100000 + (subversion) * 1000) + ((major)*10000000 + (minor)*100000 + (subversion)*1000) #define __RME_VERSION_ID__ MAKE_VERSION_ID( \ __RME_VERSION_MAJOR__, \ diff --git a/source/editor.h b/source/editor.h index 5669e64d5..0ed48514d 100644 --- a/source/editor.h +++ b/source/editor.h @@ -57,7 +57,9 @@ class Editor { LiveClient* GetLiveClient() const; LiveServer* GetLiveServer() const; LiveSocket& GetLive() const; - Map* getMap() { return ↦ } + Map* getMap() { + return ↦ + } bool CanEdit() const { return true; } diff --git a/source/ext/pugiconfig.hpp b/source/ext/pugiconfig.hpp index c8846bb7d..cdc9faef6 100644 --- a/source/ext/pugiconfig.hpp +++ b/source/ext/pugiconfig.hpp @@ -12,29 +12,29 @@ */ #ifndef HEADER_PUGICONFIG_HPP -#define HEADER_PUGICONFIG_HPP + #define HEADER_PUGICONFIG_HPP -// Uncomment this to enable wchar_t mode -// #define PUGIXML_WCHAR_MODE + // Uncomment this to enable wchar_t mode + // #define PUGIXML_WCHAR_MODE -// Uncomment this to disable XPath -#define PUGIXML_NO_XPATH + // Uncomment this to disable XPath + #define PUGIXML_NO_XPATH -// Uncomment this to disable STL -// #define PUGIXML_NO_STL + // Uncomment this to disable STL + // #define PUGIXML_NO_STL -// Uncomment this to disable exceptions -// #define PUGIXML_NO_EXCEPTIONS + // Uncomment this to disable exceptions + // #define PUGIXML_NO_EXCEPTIONS -// Set this to control attributes for public classes/functions, i.e.: -// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL -// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL -// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall -// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead + // Set this to control attributes for public classes/functions, i.e.: + // #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL + // #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL + // #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall + // In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead -// Uncomment this to switch to header-only version -#define PUGIXML_HEADER_ONLY -#include "pugixml.cpp" + // Uncomment this to switch to header-only version + #define PUGIXML_HEADER_ONLY + #include "pugixml.cpp" // Tune these constants to adjust memory-related behavior // #define PUGIXML_MEMORY_PAGE_SIZE 32768 diff --git a/source/ext/pugixml.cpp b/source/ext/pugixml.cpp index 36cd40fd6..152219605 100644 --- a/source/ext/pugixml.cpp +++ b/source/ext/pugixml.cpp @@ -12,3088 +12,3090 @@ */ #ifndef SOURCE_PUGIXML_CPP -#define SOURCE_PUGIXML_CPP - -#include "pugixml.hpp" - -#include -#include -#include -#include -#include - -#ifndef PUGIXML_NO_XPATH -# include -# include -# ifdef PUGIXML_NO_EXCEPTIONS -# include -# endif -#endif + #define SOURCE_PUGIXML_CPP -#ifndef PUGIXML_NO_STL -# include -# include -# include -#endif + #include "pugixml.hpp" -// For placement new -#include - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable: 4127) // conditional expression is constant -# pragma warning(disable: 4324) // structure was padded due to __declspec(align()) -# pragma warning(disable: 4611) // interaction between '_setjmp' and C++ object destruction is non-portable -# pragma warning(disable: 4702) // unreachable code -# pragma warning(disable: 4996) // this function or variable may be unsafe -# pragma warning(disable: 4793) // function compiled as native: presence of '_setjmp' makes a function unmanaged -#endif + #include + #include + #include + #include + #include -#ifdef __INTEL_COMPILER -# pragma warning(disable: 177) // function was declared but never referenced -# pragma warning(disable: 279) // controlling expression is constant -# pragma warning(disable: 1478 1786) // function was declared "deprecated" -# pragma warning(disable: 1684) // conversion from pointer to same-sized integral type -#endif + #ifndef PUGIXML_NO_XPATH + #include + #include + #ifdef PUGIXML_NO_EXCEPTIONS + #include + #endif + #endif -#if defined(__BORLANDC__) && defined(PUGIXML_HEADER_ONLY) -# pragma warn -8080 // symbol is declared but never used; disabling this inside push/pop bracket does not make the warning go away -#endif + #ifndef PUGIXML_NO_STL + #include + #include + #include + #endif -#ifdef __BORLANDC__ -# pragma option push -# pragma warn -8008 // condition is always false -# pragma warn -8066 // unreachable code -#endif + // For placement new + #include + + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4127) // conditional expression is constant + #pragma warning(disable : 4324) // structure was padded due to __declspec(align()) + #pragma warning(disable : 4611) // interaction between '_setjmp' and C++ object destruction is non-portable + #pragma warning(disable : 4702) // unreachable code + #pragma warning(disable : 4996) // this function or variable may be unsafe + #pragma warning(disable : 4793) // function compiled as native: presence of '_setjmp' makes a function unmanaged + #endif -#ifdef __SNC__ -// Using diag_push/diag_pop does not disable the warnings inside templates due to a compiler bug -# pragma diag_suppress=178 // function was declared but never referenced -# pragma diag_suppress=237 // controlling expression is constant -#endif + #ifdef __INTEL_COMPILER + #pragma warning(disable : 177) // function was declared but never referenced + #pragma warning(disable : 279) // controlling expression is constant + #pragma warning(disable : 1478 1786) // function was declared "deprecated" + #pragma warning(disable : 1684) // conversion from pointer to same-sized integral type + #endif -// Inlining controls -#if defined(_MSC_VER) && _MSC_VER >= 1300 -# define PUGI__NO_INLINE __declspec(noinline) -#elif defined(__GNUC__) -# define PUGI__NO_INLINE __attribute__((noinline)) -#else -# define PUGI__NO_INLINE -#endif + #if defined(__BORLANDC__) && defined(PUGIXML_HEADER_ONLY) + #pragma warn - 8080 // symbol is declared but never used; disabling this inside push/pop bracket does not make the warning go away + #endif + + #ifdef __BORLANDC__ + #pragma option push + #pragma warn - 8008 // condition is always false + #pragma warn - 8066 // unreachable code + #endif -// Simple static assertion -#define PUGI__STATIC_ASSERT(cond) { static const char condition_failed[(cond) ? 1 : -1] = {0}; (void)condition_failed[0]; } + #ifdef __SNC__ + // Using diag_push/diag_pop does not disable the warnings inside templates due to a compiler bug + #pragma diag_suppress = 178 // function was declared but never referenced + #pragma diag_suppress = 237 // controlling expression is constant + #endif -// Digital Mars C++ bug workaround for passing char loaded from memory via stack -#ifdef __DMC__ -# define PUGI__DMC_VOLATILE volatile -#else -# define PUGI__DMC_VOLATILE -#endif + // Inlining controls + #if defined(_MSC_VER) && _MSC_VER >= 1300 + #define PUGI__NO_INLINE __declspec(noinline) + #elif defined(__GNUC__) + #define PUGI__NO_INLINE __attribute__((noinline)) + #else + #define PUGI__NO_INLINE + #endif + + // Simple static assertion + #define PUGI__STATIC_ASSERT(cond) \ + { \ + static const char condition_failed[(cond) ? 1 : -1] = { 0 }; \ + (void)condition_failed[0]; \ + } + + // Digital Mars C++ bug workaround for passing char loaded from memory via stack + #ifdef __DMC__ + #define PUGI__DMC_VOLATILE volatile + #else + #define PUGI__DMC_VOLATILE + #endif -// Borland C++ bug workaround for not defining ::memcpy depending on header include order (can't always use std::memcpy because some compilers don't have it at all) -#if defined(__BORLANDC__) && !defined(__MEM_H_USING_LIST) + // Borland C++ bug workaround for not defining ::memcpy depending on header include order (can't always use std::memcpy because some compilers don't have it at all) + #if defined(__BORLANDC__) && !defined(__MEM_H_USING_LIST) using std::memcpy; using std::memmove; -#endif + #endif -// In some environments MSVC is a compiler but the CRT lacks certain MSVC-specific features -#if defined(_MSC_VER) && !defined(__S3E__) -# define PUGI__MSVC_CRT_VERSION _MSC_VER -#endif + // In some environments MSVC is a compiler but the CRT lacks certain MSVC-specific features + #if defined(_MSC_VER) && !defined(__S3E__) + #define PUGI__MSVC_CRT_VERSION _MSC_VER + #endif -#ifdef PUGIXML_HEADER_ONLY -# define PUGI__NS_BEGIN namespace pugi { namespace impl { -# define PUGI__NS_END } } -# define PUGI__FN inline -# define PUGI__FN_NO_INLINE inline -#else -# if defined(_MSC_VER) && _MSC_VER < 1300 // MSVC6 seems to have an amusing bug with anonymous namespaces inside namespaces -# define PUGI__NS_BEGIN namespace pugi { namespace impl { -# define PUGI__NS_END } } -# else -# define PUGI__NS_BEGIN namespace pugi { namespace impl { namespace { -# define PUGI__NS_END } } } -# endif -# define PUGI__FN -# define PUGI__FN_NO_INLINE PUGI__NO_INLINE -#endif + #ifdef PUGIXML_HEADER_ONLY + #define PUGI__NS_BEGIN \ + namespace pugi { \ + namespace impl { + #define PUGI__NS_END \ + } \ + } + #define PUGI__FN inline + #define PUGI__FN_NO_INLINE inline + #else + #if defined(_MSC_VER) && _MSC_VER < 1300 // MSVC6 seems to have an amusing bug with anonymous namespaces inside namespaces + #define PUGI__NS_BEGIN \ + namespace pugi { \ + namespace impl { + #define PUGI__NS_END \ + } \ + } + #else + #define PUGI__NS_BEGIN \ + namespace pugi { \ + namespace impl { \ + namespace { + #define PUGI__NS_END \ + } \ + } \ + } + #endif + #define PUGI__FN + #define PUGI__FN_NO_INLINE PUGI__NO_INLINE + #endif -// uintptr_t -#if !defined(_MSC_VER) || _MSC_VER >= 1600 -# include -#else -# ifndef _UINTPTR_T_DEFINED + // uintptr_t + #if !defined(_MSC_VER) || _MSC_VER >= 1600 + #include + #else + #ifndef _UINTPTR_T_DEFINED // No native uintptr_t in MSVC6 and in some WinCE versions typedef size_t uintptr_t; -#define _UINTPTR_T_DEFINED -# endif + #define _UINTPTR_T_DEFINED + #endif PUGI__NS_BEGIN - typedef unsigned __int8 uint8_t; - typedef unsigned __int16 uint16_t; - typedef unsigned __int32 uint32_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; PUGI__NS_END -#endif + #endif // Memory allocation PUGI__NS_BEGIN - PUGI__FN void* default_allocate(size_t size) - { - return malloc(size); - } +PUGI__FN void* default_allocate(size_t size) { + return malloc(size); +} - PUGI__FN void default_deallocate(void* ptr) - { - free(ptr); - } +PUGI__FN void default_deallocate(void* ptr) { + free(ptr); +} - template - struct xml_memory_management_function_storage - { - static allocation_function allocate; - static deallocation_function deallocate; - }; +template +struct xml_memory_management_function_storage { + static allocation_function allocate; + static deallocation_function deallocate; +}; - template allocation_function xml_memory_management_function_storage::allocate = default_allocate; - template deallocation_function xml_memory_management_function_storage::deallocate = default_deallocate; +template +allocation_function xml_memory_management_function_storage::allocate = default_allocate; +template +deallocation_function xml_memory_management_function_storage::deallocate = default_deallocate; - typedef xml_memory_management_function_storage xml_memory; +typedef xml_memory_management_function_storage xml_memory; PUGI__NS_END // String utilities PUGI__NS_BEGIN - // Get string length - PUGI__FN size_t strlength(const char_t* s) - { - assert(s); +// Get string length +PUGI__FN size_t strlength(const char_t* s) { + assert(s); #ifdef PUGIXML_WCHAR_MODE - return wcslen(s); + return wcslen(s); #else - return strlen(s); + return strlen(s); #endif - } +} - // Compare two strings - PUGI__FN bool strequal(const char_t* src, const char_t* dst) - { - assert(src && dst); +// Compare two strings +PUGI__FN bool strequal(const char_t* src, const char_t* dst) { + assert(src && dst); #ifdef PUGIXML_WCHAR_MODE - return wcscmp(src, dst) == 0; + return wcscmp(src, dst) == 0; #else - return strcmp(src, dst) == 0; + return strcmp(src, dst) == 0; #endif - } - - // Compare lhs with [rhs_begin, rhs_end) - PUGI__FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count) - { - for (size_t i = 0; i < count; ++i) - if (lhs[i] != rhs[i]) - return false; +} - return lhs[count] == 0; +// Compare lhs with [rhs_begin, rhs_end) +PUGI__FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count) { + for (size_t i = 0; i < count; ++i) { + if (lhs[i] != rhs[i]) { + return false; + } } -#ifdef PUGIXML_WCHAR_MODE - // Convert string to wide string, assuming all symbols are ASCII - PUGI__FN void widen_ascii(wchar_t* dest, const char* source) - { - for (const char* i = source; *i; ++i) *dest++ = *i; - *dest = 0; + return lhs[count] == 0; +} + + #ifdef PUGIXML_WCHAR_MODE +// Convert string to wide string, assuming all symbols are ASCII +PUGI__FN void widen_ascii(wchar_t* dest, const char* source) { + for (const char* i = source; *i; ++i) { + *dest++ = *i; } -#endif + *dest = 0; +} + #endif PUGI__NS_END -#if !defined(PUGIXML_NO_STL) || !defined(PUGIXML_NO_XPATH) + #if !defined(PUGIXML_NO_STL) || !defined(PUGIXML_NO_XPATH) // auto_ptr-like buffer holder for exception recovery PUGI__NS_BEGIN - struct buffer_holder - { - void* data; - void (*deleter)(void*); +struct buffer_holder { + void* data; + void (*deleter)(void*); - buffer_holder(void* data_, void (*deleter_)(void*)): data(data_), deleter(deleter_) - { - } + buffer_holder(void* data_, void (*deleter_)(void*)) : + data(data_), deleter(deleter_) { + } - ~buffer_holder() - { - if (data) deleter(data); + ~buffer_holder() { + if (data) { + deleter(data); } + } - void* release() - { - void* result = data; - data = 0; - return result; - } - }; + void* release() { + void* result = data; + data = 0; + return result; + } +}; PUGI__NS_END -#endif + #endif PUGI__NS_BEGIN - static const size_t xml_memory_page_size = +static const size_t xml_memory_page_size = #ifdef PUGIXML_MEMORY_PAGE_SIZE - PUGIXML_MEMORY_PAGE_SIZE + PUGIXML_MEMORY_PAGE_SIZE #else - 32768 + 32768 #endif - ; + ; - static const uintptr_t xml_memory_page_alignment = 32; - static const uintptr_t xml_memory_page_pointer_mask = ~(xml_memory_page_alignment - 1); - static const uintptr_t xml_memory_page_name_allocated_mask = 16; - static const uintptr_t xml_memory_page_value_allocated_mask = 8; - static const uintptr_t xml_memory_page_type_mask = 7; +static const uintptr_t xml_memory_page_alignment = 32; +static const uintptr_t xml_memory_page_pointer_mask = ~(xml_memory_page_alignment - 1); +static const uintptr_t xml_memory_page_name_allocated_mask = 16; +static const uintptr_t xml_memory_page_value_allocated_mask = 8; +static const uintptr_t xml_memory_page_type_mask = 7; - struct xml_allocator; +struct xml_allocator; - struct xml_memory_page - { - static xml_memory_page* construct(void* memory) - { - if (!memory) return 0; //$ redundant, left for performance +struct xml_memory_page { + static xml_memory_page* construct(void* memory) { + if (!memory) { + return 0; //$ redundant, left for performance + } - xml_memory_page* result = static_cast(memory); + xml_memory_page* result = static_cast(memory); - result->allocator = 0; - result->memory = 0; - result->prev = 0; - result->next = 0; - result->busy_size = 0; - result->freed_size = 0; + result->allocator = 0; + result->memory = 0; + result->prev = 0; + result->next = 0; + result->busy_size = 0; + result->freed_size = 0; - return result; - } + return result; + } - xml_allocator* allocator; + xml_allocator* allocator; - void* memory; + void* memory; - xml_memory_page* prev; - xml_memory_page* next; + xml_memory_page* prev; + xml_memory_page* next; - size_t busy_size; - size_t freed_size; + size_t busy_size; + size_t freed_size; - char data[1]; - }; + char data[1]; +}; - struct xml_memory_string_header - { - uint16_t page_offset; // offset from page->data - uint16_t full_size; // 0 if string occupies whole page - }; +struct xml_memory_string_header { + uint16_t page_offset; // offset from page->data + uint16_t full_size; // 0 if string occupies whole page +}; - struct xml_allocator - { - xml_allocator(xml_memory_page* root): _root(root), _busy_size(root->busy_size) - { +struct xml_allocator { + xml_allocator(xml_memory_page* root) : + _root(root), _busy_size(root->busy_size) { + } + + xml_memory_page* allocate_page(size_t data_size) { + size_t size = offsetof(xml_memory_page, data) + data_size; + + // allocate block with some alignment, leaving memory for worst-case padding + void* memory = xml_memory::allocate(size + xml_memory_page_alignment); + if (!memory) { + return 0; } - xml_memory_page* allocate_page(size_t data_size) - { - size_t size = offsetof(xml_memory_page, data) + data_size; + // align upwards to page boundary + void* page_memory = reinterpret_cast((reinterpret_cast(memory) + (xml_memory_page_alignment - 1)) & ~(xml_memory_page_alignment - 1)); - // allocate block with some alignment, leaving memory for worst-case padding - void* memory = xml_memory::allocate(size + xml_memory_page_alignment); - if (!memory) return 0; + // prepare page structure + xml_memory_page* page = xml_memory_page::construct(page_memory); - // align upwards to page boundary - void* page_memory = reinterpret_cast((reinterpret_cast(memory) + (xml_memory_page_alignment - 1)) & ~(xml_memory_page_alignment - 1)); + page->memory = memory; + page->allocator = _root->allocator; - // prepare page structure - xml_memory_page* page = xml_memory_page::construct(page_memory); + return page; + } - page->memory = memory; - page->allocator = _root->allocator; + static void deallocate_page(xml_memory_page* page) { + xml_memory::deallocate(page->memory); + } - return page; - } + void* allocate_memory_oob(size_t size, xml_memory_page*& out_page); - static void deallocate_page(xml_memory_page* page) - { - xml_memory::deallocate(page->memory); + void* allocate_memory(size_t size, xml_memory_page*& out_page) { + if (_busy_size + size > xml_memory_page_size) { + return allocate_memory_oob(size, out_page); } - void* allocate_memory_oob(size_t size, xml_memory_page*& out_page); + void* buf = _root->data + _busy_size; - void* allocate_memory(size_t size, xml_memory_page*& out_page) - { - if (_busy_size + size > xml_memory_page_size) return allocate_memory_oob(size, out_page); - - void* buf = _root->data + _busy_size; + _busy_size += size; - _busy_size += size; + out_page = _root; - out_page = _root; + return buf; + } - return buf; + void deallocate_memory(void* ptr, size_t size, xml_memory_page* page) { + if (page == _root) { + page->busy_size = _busy_size; } - void deallocate_memory(void* ptr, size_t size, xml_memory_page* page) - { - if (page == _root) page->busy_size = _busy_size; - - assert(ptr >= page->data && ptr < page->data + page->busy_size); - (void)!ptr; + assert(ptr >= page->data && ptr < page->data + page->busy_size); + (void)!ptr; - page->freed_size += size; - assert(page->freed_size <= page->busy_size); + page->freed_size += size; + assert(page->freed_size <= page->busy_size); - if (page->freed_size == page->busy_size) - { - if (page->next == 0) - { - assert(_root == page); + if (page->freed_size == page->busy_size) { + if (page->next == 0) { + assert(_root == page); - // top page freed, just reset sizes - page->busy_size = page->freed_size = 0; - _busy_size = 0; - } - else - { - assert(_root != page); - assert(page->prev); + // top page freed, just reset sizes + page->busy_size = page->freed_size = 0; + _busy_size = 0; + } else { + assert(_root != page); + assert(page->prev); - // remove from the list - page->prev->next = page->next; - page->next->prev = page->prev; + // remove from the list + page->prev->next = page->next; + page->next->prev = page->prev; - // deallocate - deallocate_page(page); - } + // deallocate + deallocate_page(page); } } + } - char_t* allocate_string(size_t length) - { - // allocate memory for string and header block - size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t); + char_t* allocate_string(size_t length) { + // allocate memory for string and header block + size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t); - // round size up to pointer alignment boundary - size_t full_size = (size + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1); + // round size up to pointer alignment boundary + size_t full_size = (size + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1); - xml_memory_page* page; - xml_memory_string_header* header = static_cast(allocate_memory(full_size, page)); + xml_memory_page* page; + xml_memory_string_header* header = static_cast(allocate_memory(full_size, page)); - if (!header) return 0; + if (!header) { + return 0; + } - // setup header - ptrdiff_t page_offset = reinterpret_cast(header) - page->data; + // setup header + ptrdiff_t page_offset = reinterpret_cast(header) - page->data; - assert(page_offset >= 0 && page_offset < (1 << 16)); - header->page_offset = static_cast(page_offset); + assert(page_offset >= 0 && page_offset < (1 << 16)); + header->page_offset = static_cast(page_offset); - // full_size == 0 for large strings that occupy the whole page - assert(full_size < (1 << 16) || (page->busy_size == full_size && page_offset == 0)); - header->full_size = static_cast(full_size < (1 << 16) ? full_size : 0); + // full_size == 0 for large strings that occupy the whole page + assert(full_size < (1 << 16) || (page->busy_size == full_size && page_offset == 0)); + header->full_size = static_cast(full_size < (1 << 16) ? full_size : 0); - // round-trip through void* to avoid 'cast increases required alignment of target type' warning - // header is guaranteed a pointer-sized alignment, which should be enough for char_t - return static_cast(static_cast(header + 1)); - } + // round-trip through void* to avoid 'cast increases required alignment of target type' warning + // header is guaranteed a pointer-sized alignment, which should be enough for char_t + return static_cast(static_cast(header + 1)); + } - void deallocate_string(char_t* string) - { - // this function casts pointers through void* to avoid 'cast increases required alignment of target type' warnings - // we're guaranteed the proper (pointer-sized) alignment on the input string if it was allocated via allocate_string + void deallocate_string(char_t* string) { + // this function casts pointers through void* to avoid 'cast increases required alignment of target type' warnings + // we're guaranteed the proper (pointer-sized) alignment on the input string if it was allocated via allocate_string - // get header - xml_memory_string_header* header = static_cast(static_cast(string)) - 1; + // get header + xml_memory_string_header* header = static_cast(static_cast(string)) - 1; - // deallocate - size_t page_offset = offsetof(xml_memory_page, data) + header->page_offset; - xml_memory_page* page = reinterpret_cast(static_cast(reinterpret_cast(header) - page_offset)); + // deallocate + size_t page_offset = offsetof(xml_memory_page, data) + header->page_offset; + xml_memory_page* page = reinterpret_cast(static_cast(reinterpret_cast(header) - page_offset)); - // if full_size == 0 then this string occupies the whole page - size_t full_size = header->full_size == 0 ? page->busy_size : header->full_size; + // if full_size == 0 then this string occupies the whole page + size_t full_size = header->full_size == 0 ? page->busy_size : header->full_size; - deallocate_memory(header, full_size, page); - } + deallocate_memory(header, full_size, page); + } - xml_memory_page* _root; - size_t _busy_size; - }; + xml_memory_page* _root; + size_t _busy_size; +}; - PUGI__FN_NO_INLINE void* xml_allocator::allocate_memory_oob(size_t size, xml_memory_page*& out_page) - { - const size_t large_allocation_threshold = xml_memory_page_size / 4; +PUGI__FN_NO_INLINE void* xml_allocator::allocate_memory_oob(size_t size, xml_memory_page*& out_page) { + const size_t large_allocation_threshold = xml_memory_page_size / 4; - xml_memory_page* page = allocate_page(size <= large_allocation_threshold ? xml_memory_page_size : size); - out_page = page; + xml_memory_page* page = allocate_page(size <= large_allocation_threshold ? xml_memory_page_size : size); + out_page = page; - if (!page) return 0; + if (!page) { + return 0; + } - if (size <= large_allocation_threshold) - { - _root->busy_size = _busy_size; + if (size <= large_allocation_threshold) { + _root->busy_size = _busy_size; - // insert page at the end of linked list - page->prev = _root; - _root->next = page; - _root = page; + // insert page at the end of linked list + page->prev = _root; + _root->next = page; + _root = page; - _busy_size = size; - } - else - { - // insert page before the end of linked list, so that it is deleted as soon as possible - // the last page is not deleted even if it's empty (see deallocate_memory) - assert(_root->prev); + _busy_size = size; + } else { + // insert page before the end of linked list, so that it is deleted as soon as possible + // the last page is not deleted even if it's empty (see deallocate_memory) + assert(_root->prev); - page->prev = _root->prev; - page->next = _root; + page->prev = _root->prev; + page->next = _root; - _root->prev->next = page; - _root->prev = page; - } + _root->prev->next = page; + _root->prev = page; + } - // allocate inside page - page->busy_size = size; + // allocate inside page + page->busy_size = size; - return page->data; - } + return page->data; +} PUGI__NS_END -namespace pugi -{ +namespace pugi { /// A 'name=value' XML attribute structure. - struct xml_attribute_struct - { + struct xml_attribute_struct { /// Default ctor - xml_attribute_struct(impl::xml_memory_page* page): header(reinterpret_cast(page)), name(0), value(0), prev_attribute_c(0), next_attribute(0) - { + xml_attribute_struct(impl::xml_memory_page* page) : + header(reinterpret_cast(page)), name(0), value(0), prev_attribute_c(0), next_attribute(0) { } uintptr_t header; - char_t* name; ///< Pointer to attribute name. - char_t* value; ///< Pointer to attribute value. + char_t* name; ///< Pointer to attribute name. + char_t* value; ///< Pointer to attribute value. - xml_attribute_struct* prev_attribute_c; ///< Previous attribute (cyclic list) - xml_attribute_struct* next_attribute; ///< Next attribute + xml_attribute_struct* prev_attribute_c; ///< Previous attribute (cyclic list) + xml_attribute_struct* next_attribute; ///< Next attribute }; /// An XML document tree node. - struct xml_node_struct - { + struct xml_node_struct { /// Default ctor /// \param type - node type - xml_node_struct(impl::xml_memory_page* page, xml_node_type type): header(reinterpret_cast(page) | (type - 1)), parent(0), name(0), value(0), first_child(0), prev_sibling_c(0), next_sibling(0), first_attribute(0) - { + xml_node_struct(impl::xml_memory_page* page, xml_node_type type) : + header(reinterpret_cast(page) | (type - 1)), parent(0), name(0), value(0), first_child(0), prev_sibling_c(0), next_sibling(0), first_attribute(0) { } uintptr_t header; - xml_node_struct* parent; ///< Pointer to parent + xml_node_struct* parent; ///< Pointer to parent - char_t* name; ///< Pointer to element name. - char_t* value; ///< Pointer to any associated string data. + char_t* name; ///< Pointer to element name. + char_t* value; ///< Pointer to any associated string data. - xml_node_struct* first_child; ///< First child + xml_node_struct* first_child; ///< First child - xml_node_struct* prev_sibling_c; ///< Left brother (cyclic list) - xml_node_struct* next_sibling; ///< Right brother + xml_node_struct* prev_sibling_c; ///< Left brother (cyclic list) + xml_node_struct* next_sibling; ///< Right brother - xml_attribute_struct* first_attribute; ///< First attribute + xml_attribute_struct* first_attribute; ///< First attribute }; } PUGI__NS_BEGIN - struct xml_document_struct: public xml_node_struct, public xml_allocator - { - xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(0) - { - } +struct xml_document_struct : public xml_node_struct, public xml_allocator { + xml_document_struct(xml_memory_page* page) : + xml_node_struct(page, node_document), xml_allocator(page), buffer(0) { + } - const char_t* buffer; - }; + const char_t* buffer; +}; - inline xml_allocator& get_allocator(const xml_node_struct* node) - { - assert(node); +inline xml_allocator& get_allocator(const xml_node_struct* node) { + assert(node); - return *reinterpret_cast(node->header & xml_memory_page_pointer_mask)->allocator; - } + return *reinterpret_cast(node->header & xml_memory_page_pointer_mask)->allocator; +} PUGI__NS_END // Low-level DOM operations PUGI__NS_BEGIN - inline xml_attribute_struct* allocate_attribute(xml_allocator& alloc) - { - xml_memory_page* page; - void* memory = alloc.allocate_memory(sizeof(xml_attribute_struct), page); - - return new (memory) xml_attribute_struct(page); - } +inline xml_attribute_struct* allocate_attribute(xml_allocator& alloc) { + xml_memory_page* page; + void* memory = alloc.allocate_memory(sizeof(xml_attribute_struct), page); - inline xml_node_struct* allocate_node(xml_allocator& alloc, xml_node_type type) - { - xml_memory_page* page; - void* memory = alloc.allocate_memory(sizeof(xml_node_struct), page); + return new (memory) xml_attribute_struct(page); +} - return new (memory) xml_node_struct(page, type); - } +inline xml_node_struct* allocate_node(xml_allocator& alloc, xml_node_type type) { + xml_memory_page* page; + void* memory = alloc.allocate_memory(sizeof(xml_node_struct), page); - inline void destroy_attribute(xml_attribute_struct* a, xml_allocator& alloc) - { - uintptr_t header = a->header; + return new (memory) xml_node_struct(page, type); +} - if (header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(a->name); - if (header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(a->value); +inline void destroy_attribute(xml_attribute_struct* a, xml_allocator& alloc) { + uintptr_t header = a->header; - alloc.deallocate_memory(a, sizeof(xml_attribute_struct), reinterpret_cast(header & xml_memory_page_pointer_mask)); + if (header & impl::xml_memory_page_name_allocated_mask) { + alloc.deallocate_string(a->name); + } + if (header & impl::xml_memory_page_value_allocated_mask) { + alloc.deallocate_string(a->value); } - inline void destroy_node(xml_node_struct* n, xml_allocator& alloc) - { - uintptr_t header = n->header; + alloc.deallocate_memory(a, sizeof(xml_attribute_struct), reinterpret_cast(header & xml_memory_page_pointer_mask)); +} - if (header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(n->name); - if (header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(n->value); +inline void destroy_node(xml_node_struct* n, xml_allocator& alloc) { + uintptr_t header = n->header; - for (xml_attribute_struct* attr = n->first_attribute; attr; ) - { - xml_attribute_struct* next = attr->next_attribute; + if (header & impl::xml_memory_page_name_allocated_mask) { + alloc.deallocate_string(n->name); + } + if (header & impl::xml_memory_page_value_allocated_mask) { + alloc.deallocate_string(n->value); + } - destroy_attribute(attr, alloc); + for (xml_attribute_struct* attr = n->first_attribute; attr;) { + xml_attribute_struct* next = attr->next_attribute; - attr = next; - } + destroy_attribute(attr, alloc); - for (xml_node_struct* child = n->first_child; child; ) - { - xml_node_struct* next = child->next_sibling; + attr = next; + } - destroy_node(child, alloc); + for (xml_node_struct* child = n->first_child; child;) { + xml_node_struct* next = child->next_sibling; - child = next; - } + destroy_node(child, alloc); - alloc.deallocate_memory(n, sizeof(xml_node_struct), reinterpret_cast(header & xml_memory_page_pointer_mask)); + child = next; } - PUGI__FN_NO_INLINE xml_node_struct* append_node(xml_node_struct* node, xml_allocator& alloc, xml_node_type type = node_element) - { - xml_node_struct* child = allocate_node(alloc, type); - if (!child) return 0; + alloc.deallocate_memory(n, sizeof(xml_node_struct), reinterpret_cast(header & xml_memory_page_pointer_mask)); +} - child->parent = node; +PUGI__FN_NO_INLINE xml_node_struct* append_node(xml_node_struct* node, xml_allocator& alloc, xml_node_type type = node_element) { + xml_node_struct* child = allocate_node(alloc, type); + if (!child) { + return 0; + } - xml_node_struct* first_child = node->first_child; + child->parent = node; - if (first_child) - { - xml_node_struct* last_child = first_child->prev_sibling_c; + xml_node_struct* first_child = node->first_child; - last_child->next_sibling = child; - child->prev_sibling_c = last_child; - first_child->prev_sibling_c = child; - } - else - { - node->first_child = child; - child->prev_sibling_c = child; - } + if (first_child) { + xml_node_struct* last_child = first_child->prev_sibling_c; - return child; + last_child->next_sibling = child; + child->prev_sibling_c = last_child; + first_child->prev_sibling_c = child; + } else { + node->first_child = child; + child->prev_sibling_c = child; } - PUGI__FN_NO_INLINE xml_attribute_struct* append_attribute_ll(xml_node_struct* node, xml_allocator& alloc) - { - xml_attribute_struct* a = allocate_attribute(alloc); - if (!a) return 0; + return child; +} - xml_attribute_struct* first_attribute = node->first_attribute; +PUGI__FN_NO_INLINE xml_attribute_struct* append_attribute_ll(xml_node_struct* node, xml_allocator& alloc) { + xml_attribute_struct* a = allocate_attribute(alloc); + if (!a) { + return 0; + } - if (first_attribute) - { - xml_attribute_struct* last_attribute = first_attribute->prev_attribute_c; + xml_attribute_struct* first_attribute = node->first_attribute; - last_attribute->next_attribute = a; - a->prev_attribute_c = last_attribute; - first_attribute->prev_attribute_c = a; - } - else - { - node->first_attribute = a; - a->prev_attribute_c = a; - } + if (first_attribute) { + xml_attribute_struct* last_attribute = first_attribute->prev_attribute_c; - return a; + last_attribute->next_attribute = a; + a->prev_attribute_c = last_attribute; + first_attribute->prev_attribute_c = a; + } else { + node->first_attribute = a; + a->prev_attribute_c = a; } + + return a; +} PUGI__NS_END // Helper classes for code generation PUGI__NS_BEGIN - struct opt_false - { - enum { value = 0 }; - }; +struct opt_false { + enum { value = 0 }; +}; - struct opt_true - { - enum { value = 1 }; - }; +struct opt_true { + enum { value = 1 }; +}; PUGI__NS_END // Unicode utilities PUGI__NS_BEGIN - inline uint16_t endian_swap(uint16_t value) - { - return static_cast(((value & 0xff) << 8) | (value >> 8)); - } - - inline uint32_t endian_swap(uint32_t value) - { - return ((value & 0xff) << 24) | ((value & 0xff00) << 8) | ((value & 0xff0000) >> 8) | (value >> 24); - } +inline uint16_t endian_swap(uint16_t value) { + return static_cast(((value & 0xff) << 8) | (value >> 8)); +} - struct utf8_counter - { - typedef size_t value_type; +inline uint32_t endian_swap(uint32_t value) { + return ((value & 0xff) << 24) | ((value & 0xff00) << 8) | ((value & 0xff0000) >> 8) | (value >> 24); +} - static value_type low(value_type result, uint32_t ch) - { - // U+0000..U+007F - if (ch < 0x80) return result + 1; - // U+0080..U+07FF - else if (ch < 0x800) return result + 2; - // U+0800..U+FFFF - else return result + 3; - } +struct utf8_counter { + typedef size_t value_type; - static value_type high(value_type result, uint32_t) - { - // U+10000..U+10FFFF - return result + 4; + static value_type low(value_type result, uint32_t ch) { + // U+0000..U+007F + if (ch < 0x80) { + return result + 1; } - }; - - struct utf8_writer - { - typedef uint8_t* value_type; - - static value_type low(value_type result, uint32_t ch) - { - // U+0000..U+007F - if (ch < 0x80) - { - *result = static_cast(ch); - return result + 1; - } - // U+0080..U+07FF - else if (ch < 0x800) - { - result[0] = static_cast(0xC0 | (ch >> 6)); - result[1] = static_cast(0x80 | (ch & 0x3F)); - return result + 2; - } - // U+0800..U+FFFF - else - { - result[0] = static_cast(0xE0 | (ch >> 12)); - result[1] = static_cast(0x80 | ((ch >> 6) & 0x3F)); - result[2] = static_cast(0x80 | (ch & 0x3F)); - return result + 3; - } + // U+0080..U+07FF + else if (ch < 0x800) { + return result + 2; } - - static value_type high(value_type result, uint32_t ch) - { - // U+10000..U+10FFFF - result[0] = static_cast(0xF0 | (ch >> 18)); - result[1] = static_cast(0x80 | ((ch >> 12) & 0x3F)); - result[2] = static_cast(0x80 | ((ch >> 6) & 0x3F)); - result[3] = static_cast(0x80 | (ch & 0x3F)); - return result + 4; + // U+0800..U+FFFF + else { + return result + 3; } + } - static value_type any(value_type result, uint32_t ch) - { - return (ch < 0x10000) ? low(result, ch) : high(result, ch); - } - }; + static value_type high(value_type result, uint32_t) { + // U+10000..U+10FFFF + return result + 4; + } +}; - struct utf16_counter - { - typedef size_t value_type; +struct utf8_writer { + typedef uint8_t* value_type; - static value_type low(value_type result, uint32_t) - { + static value_type low(value_type result, uint32_t ch) { + // U+0000..U+007F + if (ch < 0x80) { + *result = static_cast(ch); return result + 1; } - - static value_type high(value_type result, uint32_t) - { + // U+0080..U+07FF + else if (ch < 0x800) { + result[0] = static_cast(0xC0 | (ch >> 6)); + result[1] = static_cast(0x80 | (ch & 0x3F)); return result + 2; } - }; + // U+0800..U+FFFF + else { + result[0] = static_cast(0xE0 | (ch >> 12)); + result[1] = static_cast(0x80 | ((ch >> 6) & 0x3F)); + result[2] = static_cast(0x80 | (ch & 0x3F)); + return result + 3; + } + } - struct utf16_writer - { - typedef uint16_t* value_type; + static value_type high(value_type result, uint32_t ch) { + // U+10000..U+10FFFF + result[0] = static_cast(0xF0 | (ch >> 18)); + result[1] = static_cast(0x80 | ((ch >> 12) & 0x3F)); + result[2] = static_cast(0x80 | ((ch >> 6) & 0x3F)); + result[3] = static_cast(0x80 | (ch & 0x3F)); + return result + 4; + } - static value_type low(value_type result, uint32_t ch) - { - *result = static_cast(ch); + static value_type any(value_type result, uint32_t ch) { + return (ch < 0x10000) ? low(result, ch) : high(result, ch); + } +}; - return result + 1; - } +struct utf16_counter { + typedef size_t value_type; - static value_type high(value_type result, uint32_t ch) - { - uint32_t msh = static_cast(ch - 0x10000) >> 10; - uint32_t lsh = static_cast(ch - 0x10000) & 0x3ff; + static value_type low(value_type result, uint32_t) { + return result + 1; + } - result[0] = static_cast(0xD800 + msh); - result[1] = static_cast(0xDC00 + lsh); + static value_type high(value_type result, uint32_t) { + return result + 2; + } +}; - return result + 2; - } +struct utf16_writer { + typedef uint16_t* value_type; - static value_type any(value_type result, uint32_t ch) - { - return (ch < 0x10000) ? low(result, ch) : high(result, ch); - } - }; + static value_type low(value_type result, uint32_t ch) { + *result = static_cast(ch); - struct utf32_counter - { - typedef size_t value_type; + return result + 1; + } - static value_type low(value_type result, uint32_t) - { - return result + 1; - } + static value_type high(value_type result, uint32_t ch) { + uint32_t msh = static_cast(ch - 0x10000) >> 10; + uint32_t lsh = static_cast(ch - 0x10000) & 0x3ff; - static value_type high(value_type result, uint32_t) - { - return result + 1; - } - }; + result[0] = static_cast(0xD800 + msh); + result[1] = static_cast(0xDC00 + lsh); - struct utf32_writer - { - typedef uint32_t* value_type; + return result + 2; + } - static value_type low(value_type result, uint32_t ch) - { - *result = ch; + static value_type any(value_type result, uint32_t ch) { + return (ch < 0x10000) ? low(result, ch) : high(result, ch); + } +}; - return result + 1; - } +struct utf32_counter { + typedef size_t value_type; - static value_type high(value_type result, uint32_t ch) - { - *result = ch; + static value_type low(value_type result, uint32_t) { + return result + 1; + } - return result + 1; - } + static value_type high(value_type result, uint32_t) { + return result + 1; + } +}; - static value_type any(value_type result, uint32_t ch) - { - *result = ch; +struct utf32_writer { + typedef uint32_t* value_type; - return result + 1; - } - }; + static value_type low(value_type result, uint32_t ch) { + *result = ch; - struct latin1_writer - { - typedef uint8_t* value_type; + return result + 1; + } - static value_type low(value_type result, uint32_t ch) - { - *result = static_cast(ch > 255 ? '?' : ch); + static value_type high(value_type result, uint32_t ch) { + *result = ch; - return result + 1; - } + return result + 1; + } - static value_type high(value_type result, uint32_t ch) - { - (void)ch; + static value_type any(value_type result, uint32_t ch) { + *result = ch; - *result = '?'; + return result + 1; + } +}; - return result + 1; - } - }; +struct latin1_writer { + typedef uint8_t* value_type; - template struct wchar_selector; + static value_type low(value_type result, uint32_t ch) { + *result = static_cast(ch > 255 ? '?' : ch); - template <> struct wchar_selector<2> - { - typedef uint16_t type; - typedef utf16_counter counter; - typedef utf16_writer writer; - }; + return result + 1; + } - template <> struct wchar_selector<4> - { - typedef uint32_t type; - typedef utf32_counter counter; - typedef utf32_writer writer; - }; + static value_type high(value_type result, uint32_t ch) { + (void)ch; - typedef wchar_selector::counter wchar_counter; - typedef wchar_selector::writer wchar_writer; + *result = '?'; - template struct utf_decoder - { - static inline typename Traits::value_type decode_utf8_block(const uint8_t* data, size_t size, typename Traits::value_type result) - { - const uint8_t utf8_byte_mask = 0x3f; + return result + 1; + } +}; - while (size) - { - uint8_t lead = *data; +template +struct wchar_selector; - // 0xxxxxxx -> U+0000..U+007F - if (lead < 0x80) - { - result = Traits::low(result, lead); - data += 1; - size -= 1; +template <> +struct wchar_selector<2> { + typedef uint16_t type; + typedef utf16_counter counter; + typedef utf16_writer writer; +}; - // process aligned single-byte (ascii) blocks - if ((reinterpret_cast(data) & 3) == 0) - { - // round-trip through void* to silence 'cast increases required alignment of target type' warnings - while (size >= 4 && (*static_cast(static_cast(data)) & 0x80808080) == 0) - { - result = Traits::low(result, data[0]); - result = Traits::low(result, data[1]); - result = Traits::low(result, data[2]); - result = Traits::low(result, data[3]); - data += 4; - size -= 4; - } - } - } - // 110xxxxx -> U+0080..U+07FF - else if (static_cast(lead - 0xC0) < 0x20 && size >= 2 && (data[1] & 0xc0) == 0x80) - { - result = Traits::low(result, ((lead & ~0xC0) << 6) | (data[1] & utf8_byte_mask)); - data += 2; - size -= 2; - } - // 1110xxxx -> U+0800-U+FFFF - else if (static_cast(lead - 0xE0) < 0x10 && size >= 3 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80) - { - result = Traits::low(result, ((lead & ~0xE0) << 12) | ((data[1] & utf8_byte_mask) << 6) | (data[2] & utf8_byte_mask)); - data += 3; - size -= 3; - } - // 11110xxx -> U+10000..U+10FFFF - else if (static_cast(lead - 0xF0) < 0x08 && size >= 4 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80 && (data[3] & 0xc0) == 0x80) - { - result = Traits::high(result, ((lead & ~0xF0) << 18) | ((data[1] & utf8_byte_mask) << 12) | ((data[2] & utf8_byte_mask) << 6) | (data[3] & utf8_byte_mask)); - data += 4; - size -= 4; - } - // 10xxxxxx or 11111xxx -> invalid - else - { - data += 1; - size -= 1; - } - } +template <> +struct wchar_selector<4> { + typedef uint32_t type; + typedef utf32_counter counter; + typedef utf32_writer writer; +}; - return result; - } +typedef wchar_selector::counter wchar_counter; +typedef wchar_selector::writer wchar_writer; - static inline typename Traits::value_type decode_utf16_block(const uint16_t* data, size_t size, typename Traits::value_type result) - { - const uint16_t* end = data + size; +template +struct utf_decoder { + static inline typename Traits::value_type decode_utf8_block(const uint8_t* data, size_t size, typename Traits::value_type result) { + const uint8_t utf8_byte_mask = 0x3f; - while (data < end) - { - uint16_t lead = opt_swap::value ? endian_swap(*data) : *data; + while (size) { + uint8_t lead = *data; - // U+0000..U+D7FF - if (lead < 0xD800) - { - result = Traits::low(result, lead); - data += 1; - } - // U+E000..U+FFFF - else if (static_cast(lead - 0xE000) < 0x2000) - { - result = Traits::low(result, lead); - data += 1; - } - // surrogate pair lead - else if (static_cast(lead - 0xD800) < 0x400 && data + 1 < end) - { - uint16_t next = opt_swap::value ? endian_swap(data[1]) : data[1]; + // 0xxxxxxx -> U+0000..U+007F + if (lead < 0x80) { + result = Traits::low(result, lead); + data += 1; + size -= 1; - if (static_cast(next - 0xDC00) < 0x400) - { - result = Traits::high(result, 0x10000 + ((lead & 0x3ff) << 10) + (next & 0x3ff)); - data += 2; - } - else - { - data += 1; + // process aligned single-byte (ascii) blocks + if ((reinterpret_cast(data) & 3) == 0) { + // round-trip through void* to silence 'cast increases required alignment of target type' warnings + while (size >= 4 && (*static_cast(static_cast(data)) & 0x80808080) == 0) { + result = Traits::low(result, data[0]); + result = Traits::low(result, data[1]); + result = Traits::low(result, data[2]); + result = Traits::low(result, data[3]); + data += 4; + size -= 4; } } - else - { - data += 1; - } } - - return result; + // 110xxxxx -> U+0080..U+07FF + else if (static_cast(lead - 0xC0) < 0x20 && size >= 2 && (data[1] & 0xc0) == 0x80) { + result = Traits::low(result, ((lead & ~0xC0) << 6) | (data[1] & utf8_byte_mask)); + data += 2; + size -= 2; + } + // 1110xxxx -> U+0800-U+FFFF + else if (static_cast(lead - 0xE0) < 0x10 && size >= 3 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80) { + result = Traits::low(result, ((lead & ~0xE0) << 12) | ((data[1] & utf8_byte_mask) << 6) | (data[2] & utf8_byte_mask)); + data += 3; + size -= 3; + } + // 11110xxx -> U+10000..U+10FFFF + else if (static_cast(lead - 0xF0) < 0x08 && size >= 4 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80 && (data[3] & 0xc0) == 0x80) { + result = Traits::high(result, ((lead & ~0xF0) << 18) | ((data[1] & utf8_byte_mask) << 12) | ((data[2] & utf8_byte_mask) << 6) | (data[3] & utf8_byte_mask)); + data += 4; + size -= 4; + } + // 10xxxxxx or 11111xxx -> invalid + else { + data += 1; + size -= 1; + } } - static inline typename Traits::value_type decode_utf32_block(const uint32_t* data, size_t size, typename Traits::value_type result) - { - const uint32_t* end = data + size; + return result; + } - while (data < end) - { - uint32_t lead = opt_swap::value ? endian_swap(*data) : *data; + static inline typename Traits::value_type decode_utf16_block(const uint16_t* data, size_t size, typename Traits::value_type result) { + const uint16_t* end = data + size; - // U+0000..U+FFFF - if (lead < 0x10000) - { - result = Traits::low(result, lead); - data += 1; - } - // U+10000..U+10FFFF - else - { - result = Traits::high(result, lead); + while (data < end) { + uint16_t lead = opt_swap::value ? endian_swap(*data) : *data; + + // U+0000..U+D7FF + if (lead < 0xD800) { + result = Traits::low(result, lead); + data += 1; + } + // U+E000..U+FFFF + else if (static_cast(lead - 0xE000) < 0x2000) { + result = Traits::low(result, lead); + data += 1; + } + // surrogate pair lead + else if (static_cast(lead - 0xD800) < 0x400 && data + 1 < end) { + uint16_t next = opt_swap::value ? endian_swap(data[1]) : data[1]; + + if (static_cast(next - 0xDC00) < 0x400) { + result = Traits::high(result, 0x10000 + ((lead & 0x3ff) << 10) + (next & 0x3ff)); + data += 2; + } else { data += 1; } + } else { + data += 1; } - - return result; } - static inline typename Traits::value_type decode_latin1_block(const uint8_t* data, size_t size, typename Traits::value_type result) - { - for (size_t i = 0; i < size; ++i) - { - result = Traits::low(result, data[i]); - } + return result; + } - return result; - } + static inline typename Traits::value_type decode_utf32_block(const uint32_t* data, size_t size, typename Traits::value_type result) { + const uint32_t* end = data + size; - static inline typename Traits::value_type decode_wchar_block_impl(const uint16_t* data, size_t size, typename Traits::value_type result) - { - return decode_utf16_block(data, size, result); - } + while (data < end) { + uint32_t lead = opt_swap::value ? endian_swap(*data) : *data; - static inline typename Traits::value_type decode_wchar_block_impl(const uint32_t* data, size_t size, typename Traits::value_type result) - { - return decode_utf32_block(data, size, result); + // U+0000..U+FFFF + if (lead < 0x10000) { + result = Traits::low(result, lead); + data += 1; + } + // U+10000..U+10FFFF + else { + result = Traits::high(result, lead); + data += 1; + } } - static inline typename Traits::value_type decode_wchar_block(const wchar_t* data, size_t size, typename Traits::value_type result) - { - return decode_wchar_block_impl(reinterpret_cast::type*>(data), size, result); + return result; + } + + static inline typename Traits::value_type decode_latin1_block(const uint8_t* data, size_t size, typename Traits::value_type result) { + for (size_t i = 0; i < size; ++i) { + result = Traits::low(result, data[i]); } - }; - template PUGI__FN void convert_utf_endian_swap(T* result, const T* data, size_t length) - { - for (size_t i = 0; i < length; ++i) result[i] = endian_swap(data[i]); + return result; } -#ifdef PUGIXML_WCHAR_MODE - PUGI__FN void convert_wchar_endian_swap(wchar_t* result, const wchar_t* data, size_t length) - { - for (size_t i = 0; i < length; ++i) result[i] = static_cast(endian_swap(static_cast::type>(data[i]))); + static inline typename Traits::value_type decode_wchar_block_impl(const uint16_t* data, size_t size, typename Traits::value_type result) { + return decode_utf16_block(data, size, result); } -#endif -PUGI__NS_END -PUGI__NS_BEGIN - enum chartype_t - { - ct_parse_pcdata = 1, // \0, &, \r, < - ct_parse_attr = 2, // \0, &, \r, ', " - ct_parse_attr_ws = 4, // \0, &, \r, ', ", \n, tab - ct_space = 8, // \r, \n, space, tab - ct_parse_cdata = 16, // \0, ], >, \r - ct_parse_comment = 32, // \0, -, >, \r - ct_symbol = 64, // Any symbol > 127, a-z, A-Z, 0-9, _, :, -, . - ct_start_symbol = 128 // Any symbol > 127, a-z, A-Z, _, : - }; + static inline typename Traits::value_type decode_wchar_block_impl(const uint32_t* data, size_t size, typename Traits::value_type result) { + return decode_utf32_block(data, size, result); + } - static const unsigned char chartype_table[256] = - { - 55, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 63, 0, 0, // 0-15 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 - 8, 0, 6, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 96, 64, 0, // 32-47 - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 192, 0, 1, 0, 48, 0, // 48-63 - 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 64-79 - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 16, 0, 192, // 80-95 - 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 96-111 - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 0, 0, 0, // 112-127 - - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 128+ - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192 - }; + static inline typename Traits::value_type decode_wchar_block(const wchar_t* data, size_t size, typename Traits::value_type result) { + return decode_wchar_block_impl(reinterpret_cast::type*>(data), size, result); + } +}; - enum chartypex_t - { - ctx_special_pcdata = 1, // Any symbol >= 0 and < 32 (except \t, \r, \n), &, <, > - ctx_special_attr = 2, // Any symbol >= 0 and < 32 (except \t), &, <, >, " - ctx_start_symbol = 4, // Any symbol > 127, a-z, A-Z, _ - ctx_digit = 8, // 0-9 - ctx_symbol = 16 // Any symbol > 127, a-z, A-Z, 0-9, _, -, . - }; +template +PUGI__FN void convert_utf_endian_swap(T* result, const T* data, size_t length) { + for (size_t i = 0; i < length; ++i) { + result[i] = endian_swap(data[i]); + } +} - static const unsigned char chartypex_table[256] = - { - 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 2, 3, 3, 2, 3, 3, // 0-15 - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 16-31 - 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 16, 16, 0, // 32-47 - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 3, 0, 3, 0, // 48-63 - - 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 64-79 - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 20, // 80-95 - 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 96-111 - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 0, // 112-127 - - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 128+ - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 - }; + #ifdef PUGIXML_WCHAR_MODE +PUGI__FN void convert_wchar_endian_swap(wchar_t* result, const wchar_t* data, size_t length) { + for (size_t i = 0; i < length; ++i) { + result[i] = static_cast(endian_swap(static_cast::type>(data[i]))); + } +} + #endif +PUGI__NS_END -#ifdef PUGIXML_WCHAR_MODE - #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) ((static_cast(c) < 128 ? table[static_cast(c)] : table[128]) & (ct)) -#else - #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) (table[static_cast(c)] & (ct)) -#endif +PUGI__NS_BEGIN +enum chartype_t { + ct_parse_pcdata = 1, // \0, &, \r, < + ct_parse_attr = 2, // \0, &, \r, ', " + ct_parse_attr_ws = 4, // \0, &, \r, ', ", \n, tab + ct_space = 8, // \r, \n, space, tab + ct_parse_cdata = 16, // \0, ], >, \r + ct_parse_comment = 32, // \0, -, >, \r + ct_symbol = 64, // Any symbol > 127, a-z, A-Z, 0-9, _, :, -, . + ct_start_symbol = 128 // Any symbol > 127, a-z, A-Z, _, : +}; + +static const unsigned char chartype_table[256] = { + 55, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 63, 0, 0, // 0-15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 + 8, 0, 6, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 96, 64, 0, // 32-47 + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 192, 0, 1, 0, 48, 0, // 48-63 + 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 64-79 + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 16, 0, 192, // 80-95 + 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 96-111 + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 0, 0, 0, // 112-127 + + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 128+ + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192 +}; + +enum chartypex_t { + ctx_special_pcdata = 1, // Any symbol >= 0 and < 32 (except \t, \r, \n), &, <, > + ctx_special_attr = 2, // Any symbol >= 0 and < 32 (except \t), &, <, >, " + ctx_start_symbol = 4, // Any symbol > 127, a-z, A-Z, _ + ctx_digit = 8, // 0-9 + ctx_symbol = 16 // Any symbol > 127, a-z, A-Z, 0-9, _, -, . +}; + +static const unsigned char chartypex_table[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 2, 3, 3, 2, 3, 3, // 0-15 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 16-31 + 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 16, 16, 0, // 32-47 + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 3, 0, 3, 0, // 48-63 + + 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 64-79 + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 20, // 80-95 + 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 96-111 + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 0, // 112-127 + + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 128+ + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 +}; + + #ifdef PUGIXML_WCHAR_MODE + #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) ((static_cast(c) < 128 ? table[static_cast(c)] : table[128]) & (ct)) + #else + #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) (table[static_cast(c)] & (ct)) + #endif #define PUGI__IS_CHARTYPE(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartype_table) #define PUGI__IS_CHARTYPEX(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartypex_table) - PUGI__FN bool is_little_endian() - { - unsigned int ui = 1; +PUGI__FN bool is_little_endian() { + unsigned int ui = 1; - return *reinterpret_cast(&ui) == 1; - } + return *reinterpret_cast(&ui) == 1; +} - PUGI__FN xml_encoding get_wchar_encoding() - { - PUGI__STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4); +PUGI__FN xml_encoding get_wchar_encoding() { + PUGI__STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4); - if (sizeof(wchar_t) == 2) - return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - else - return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + if (sizeof(wchar_t) == 2) { + return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + } else { + return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; } +} - PUGI__FN xml_encoding guess_buffer_encoding(uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3) - { - // look for BOM in first few bytes - if (d0 == 0 && d1 == 0 && d2 == 0xfe && d3 == 0xff) return encoding_utf32_be; - if (d0 == 0xff && d1 == 0xfe && d2 == 0 && d3 == 0) return encoding_utf32_le; - if (d0 == 0xfe && d1 == 0xff) return encoding_utf16_be; - if (d0 == 0xff && d1 == 0xfe) return encoding_utf16_le; - if (d0 == 0xef && d1 == 0xbb && d2 == 0xbf) return encoding_utf8; - - // look for <, (contents); + // replace utf32 encoding with utf32 with specific endianness + if (encoding == encoding_utf32) { + return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + } - PUGI__DMC_VOLATILE uint8_t d0 = data[0], d1 = data[1], d2 = data[2], d3 = data[3]; + // only do autodetection if no explicit encoding is requested + if (encoding != encoding_auto) { + return encoding; + } - return guess_buffer_encoding(d0, d1, d2, d3); + // skip encoding autodetection if input buffer is too small + if (size < 4) { + return encoding_utf8; } - PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) - { - if (is_mutable) - { - out_buffer = static_cast(const_cast(contents)); - } - else - { - void* buffer = xml_memory::allocate(size > 0 ? size : 1); - if (!buffer) return false; + // try to guess encoding (based on XML specification, Appendix F.1) + const uint8_t* data = static_cast(contents); - memcpy(buffer, contents, size); + PUGI__DMC_VOLATILE uint8_t d0 = data[0], d1 = data[1], d2 = data[2], d3 = data[3]; - out_buffer = static_cast(buffer); - } + return guess_buffer_encoding(d0, d1, d2, d3); +} - out_length = size / sizeof(char_t); +PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) { + if (is_mutable) { + out_buffer = static_cast(const_cast(contents)); + } else { + void* buffer = xml_memory::allocate(size > 0 ? size : 1); + if (!buffer) { + return false; + } - return true; - } + memcpy(buffer, contents, size); -#ifdef PUGIXML_WCHAR_MODE - PUGI__FN bool need_endian_swap_utf(xml_encoding le, xml_encoding re) - { - return (le == encoding_utf16_be && re == encoding_utf16_le) || (le == encoding_utf16_le && re == encoding_utf16_be) || - (le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be); + out_buffer = static_cast(buffer); } - PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) - { - const char_t* data = static_cast(contents); + out_length = size / sizeof(char_t); - if (is_mutable) - { - out_buffer = const_cast(data); - } - else - { - out_buffer = static_cast(xml_memory::allocate(size > 0 ? size : 1)); - if (!out_buffer) return false; - } + return true; +} - out_length = size / sizeof(char_t); + #ifdef PUGIXML_WCHAR_MODE +PUGI__FN bool need_endian_swap_utf(xml_encoding le, xml_encoding re) { + return (le == encoding_utf16_be && re == encoding_utf16_le) || (le == encoding_utf16_le && re == encoding_utf16_be) || (le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be); +} - convert_wchar_endian_swap(out_buffer, data, out_length); +PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) { + const char_t* data = static_cast(contents); - return true; + if (is_mutable) { + out_buffer = const_cast(data); + } else { + out_buffer = static_cast(xml_memory::allocate(size > 0 ? size : 1)); + if (!out_buffer) { + return false; + } } - PUGI__FN bool convert_buffer_utf8(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size) - { - const uint8_t* data = static_cast(contents); + out_length = size / sizeof(char_t); - // first pass: get length in wchar_t units - out_length = utf_decoder::decode_utf8_block(data, size, 0); + convert_wchar_endian_swap(out_buffer, data, out_length); - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; + return true; +} - // second pass: convert utf8 input to wchar_t - wchar_writer::value_type out_begin = reinterpret_cast(out_buffer); - wchar_writer::value_type out_end = utf_decoder::decode_utf8_block(data, size, out_begin); +PUGI__FN bool convert_buffer_utf8(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size) { + const uint8_t* data = static_cast(contents); - assert(out_end == out_begin + out_length); - (void)!out_end; + // first pass: get length in wchar_t units + out_length = utf_decoder::decode_utf8_block(data, size, 0); - return true; + // allocate buffer of suitable length + out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); + if (!out_buffer) { + return false; } - template PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) - { - const uint16_t* data = static_cast(contents); - size_t length = size / sizeof(uint16_t); + // second pass: convert utf8 input to wchar_t + wchar_writer::value_type out_begin = reinterpret_cast(out_buffer); + wchar_writer::value_type out_end = utf_decoder::decode_utf8_block(data, size, out_begin); - // first pass: get length in wchar_t units - out_length = utf_decoder::decode_utf16_block(data, length, 0); + assert(out_end == out_begin + out_length); + (void)!out_end; - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; + return true; +} - // second pass: convert utf16 input to wchar_t - wchar_writer::value_type out_begin = reinterpret_cast(out_buffer); - wchar_writer::value_type out_end = utf_decoder::decode_utf16_block(data, length, out_begin); +template +PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) { + const uint16_t* data = static_cast(contents); + size_t length = size / sizeof(uint16_t); - assert(out_end == out_begin + out_length); - (void)!out_end; + // first pass: get length in wchar_t units + out_length = utf_decoder::decode_utf16_block(data, length, 0); - return true; + // allocate buffer of suitable length + out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); + if (!out_buffer) { + return false; } - template PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) - { - const uint32_t* data = static_cast(contents); - size_t length = size / sizeof(uint32_t); + // second pass: convert utf16 input to wchar_t + wchar_writer::value_type out_begin = reinterpret_cast(out_buffer); + wchar_writer::value_type out_end = utf_decoder::decode_utf16_block(data, length, out_begin); - // first pass: get length in wchar_t units - out_length = utf_decoder::decode_utf32_block(data, length, 0); + assert(out_end == out_begin + out_length); + (void)!out_end; - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; + return true; +} - // second pass: convert utf32 input to wchar_t - wchar_writer::value_type out_begin = reinterpret_cast(out_buffer); - wchar_writer::value_type out_end = utf_decoder::decode_utf32_block(data, length, out_begin); +template +PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) { + const uint32_t* data = static_cast(contents); + size_t length = size / sizeof(uint32_t); - assert(out_end == out_begin + out_length); - (void)!out_end; + // first pass: get length in wchar_t units + out_length = utf_decoder::decode_utf32_block(data, length, 0); - return true; + // allocate buffer of suitable length + out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); + if (!out_buffer) { + return false; } - PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size) - { - const uint8_t* data = static_cast(contents); + // second pass: convert utf32 input to wchar_t + wchar_writer::value_type out_begin = reinterpret_cast(out_buffer); + wchar_writer::value_type out_end = utf_decoder::decode_utf32_block(data, length, out_begin); - // get length in wchar_t units - out_length = size; + assert(out_end == out_begin + out_length); + (void)!out_end; - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; + return true; +} - // convert latin1 input to wchar_t - wchar_writer::value_type out_begin = reinterpret_cast(out_buffer); - wchar_writer::value_type out_end = utf_decoder::decode_latin1_block(data, size, out_begin); +PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size) { + const uint8_t* data = static_cast(contents); - assert(out_end == out_begin + out_length); - (void)!out_end; + // get length in wchar_t units + out_length = size; - return true; + // allocate buffer of suitable length + out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); + if (!out_buffer) { + return false; } - PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) - { - // get native encoding - xml_encoding wchar_encoding = get_wchar_encoding(); - - // fast path: no conversion required - if (encoding == wchar_encoding) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + // convert latin1 input to wchar_t + wchar_writer::value_type out_begin = reinterpret_cast(out_buffer); + wchar_writer::value_type out_end = utf_decoder::decode_latin1_block(data, size, out_begin); - // only endian-swapping is required - if (need_endian_swap_utf(encoding, wchar_encoding)) return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable); + assert(out_end == out_begin + out_length); + (void)!out_end; - // source encoding is utf8 - if (encoding == encoding_utf8) return convert_buffer_utf8(out_buffer, out_length, contents, size); + return true; +} - // source encoding is utf16 - if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; +PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) { + // get native encoding + xml_encoding wchar_encoding = get_wchar_encoding(); - return (native_encoding == encoding) ? - convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) : - convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true()); - } + // fast path: no conversion required + if (encoding == wchar_encoding) { + return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + } - // source encoding is utf32 - if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + // only endian-swapping is required + if (need_endian_swap_utf(encoding, wchar_encoding)) { + return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable); + } - return (native_encoding == encoding) ? - convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) : - convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true()); - } + // source encoding is utf8 + if (encoding == encoding_utf8) { + return convert_buffer_utf8(out_buffer, out_length, contents, size); + } - // source encoding is latin1 - if (encoding == encoding_latin1) return convert_buffer_latin1(out_buffer, out_length, contents, size); + // source encoding is utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) { + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - assert(!"Invalid encoding"); - return false; + return (native_encoding == encoding) ? convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) : convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true()); } -#else - template PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) - { - const uint16_t* data = static_cast(contents); - size_t length = size / sizeof(uint16_t); - // first pass: get length in utf8 units - out_length = utf_decoder::decode_utf16_block(data, length, 0); + // source encoding is utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) { + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; + return (native_encoding == encoding) ? convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) : convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true()); + } + + // source encoding is latin1 + if (encoding == encoding_latin1) { + return convert_buffer_latin1(out_buffer, out_length, contents, size); + } - // second pass: convert utf16 input to utf8 - uint8_t* out_begin = reinterpret_cast(out_buffer); - uint8_t* out_end = utf_decoder::decode_utf16_block(data, length, out_begin); + assert(!"Invalid encoding"); + return false; +} + #else +template +PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) { + const uint16_t* data = static_cast(contents); + size_t length = size / sizeof(uint16_t); - assert(out_end == out_begin + out_length); - (void)!out_end; + // first pass: get length in utf8 units + out_length = utf_decoder::decode_utf16_block(data, length, 0); - return true; + // allocate buffer of suitable length + out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); + if (!out_buffer) { + return false; } - template PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) - { - const uint32_t* data = static_cast(contents); - size_t length = size / sizeof(uint32_t); + // second pass: convert utf16 input to utf8 + uint8_t* out_begin = reinterpret_cast(out_buffer); + uint8_t* out_end = utf_decoder::decode_utf16_block(data, length, out_begin); - // first pass: get length in utf8 units - out_length = utf_decoder::decode_utf32_block(data, length, 0); + assert(out_end == out_begin + out_length); + (void)!out_end; - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; + return true; +} - // second pass: convert utf32 input to utf8 - uint8_t* out_begin = reinterpret_cast(out_buffer); - uint8_t* out_end = utf_decoder::decode_utf32_block(data, length, out_begin); +template +PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) { + const uint32_t* data = static_cast(contents); + size_t length = size / sizeof(uint32_t); - assert(out_end == out_begin + out_length); - (void)!out_end; + // first pass: get length in utf8 units + out_length = utf_decoder::decode_utf32_block(data, length, 0); - return true; + // allocate buffer of suitable length + out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); + if (!out_buffer) { + return false; } - PUGI__FN size_t get_latin1_7bit_prefix_length(const uint8_t* data, size_t size) - { - for (size_t i = 0; i < size; ++i) - if (data[i] > 127) - return i; - - return size; - } + // second pass: convert utf32 input to utf8 + uint8_t* out_begin = reinterpret_cast(out_buffer); + uint8_t* out_end = utf_decoder::decode_utf32_block(data, length, out_begin); - PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) - { - const uint8_t* data = static_cast(contents); + assert(out_end == out_begin + out_length); + (void)!out_end; - // get size of prefix that does not need utf8 conversion - size_t prefix_length = get_latin1_7bit_prefix_length(data, size); - assert(prefix_length <= size); + return true; +} - const uint8_t* postfix = data + prefix_length; - size_t postfix_length = size - prefix_length; +PUGI__FN size_t get_latin1_7bit_prefix_length(const uint8_t* data, size_t size) { + for (size_t i = 0; i < size; ++i) { + if (data[i] > 127) { + return i; + } + } - // if no conversion is needed, just return the original buffer - if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + return size; +} - // first pass: get length in utf8 units - out_length = prefix_length + utf_decoder::decode_latin1_block(postfix, postfix_length, 0); +PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) { + const uint8_t* data = static_cast(contents); - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; + // get size of prefix that does not need utf8 conversion + size_t prefix_length = get_latin1_7bit_prefix_length(data, size); + assert(prefix_length <= size); - // second pass: convert latin1 input to utf8 - memcpy(out_buffer, data, prefix_length); + const uint8_t* postfix = data + prefix_length; + size_t postfix_length = size - prefix_length; - uint8_t* out_begin = reinterpret_cast(out_buffer); - uint8_t* out_end = utf_decoder::decode_latin1_block(postfix, postfix_length, out_begin + prefix_length); + // if no conversion is needed, just return the original buffer + if (postfix_length == 0) { + return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + } - assert(out_end == out_begin + out_length); - (void)!out_end; + // first pass: get length in utf8 units + out_length = prefix_length + utf_decoder::decode_latin1_block(postfix, postfix_length, 0); - return true; + // allocate buffer of suitable length + out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); + if (!out_buffer) { + return false; } - PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) - { - // fast path: no conversion required - if (encoding == encoding_utf8) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + // second pass: convert latin1 input to utf8 + memcpy(out_buffer, data, prefix_length); - // source encoding is utf16 - if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + uint8_t* out_begin = reinterpret_cast(out_buffer); + uint8_t* out_end = utf_decoder::decode_latin1_block(postfix, postfix_length, out_begin + prefix_length); - return (native_encoding == encoding) ? - convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) : - convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true()); - } + assert(out_end == out_begin + out_length); + (void)!out_end; - // source encoding is utf32 - if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + return true; +} - return (native_encoding == encoding) ? - convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) : - convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true()); - } +PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) { + // fast path: no conversion required + if (encoding == encoding_utf8) { + return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + } - // source encoding is latin1 - if (encoding == encoding_latin1) return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable); + // source encoding is utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) { + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - assert(!"Invalid encoding"); - return false; + return (native_encoding == encoding) ? convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) : convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true()); } -#endif - PUGI__FN size_t as_utf8_begin(const wchar_t* str, size_t length) - { - // get length in utf8 characters - return utf_decoder::decode_wchar_block(str, length, 0); + // source encoding is utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) { + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + return (native_encoding == encoding) ? convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) : convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true()); } - PUGI__FN void as_utf8_end(char* buffer, size_t size, const wchar_t* str, size_t length) - { - // convert to utf8 - uint8_t* begin = reinterpret_cast(buffer); - uint8_t* end = utf_decoder::decode_wchar_block(str, length, begin); + // source encoding is latin1 + if (encoding == encoding_latin1) { + return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable); + } - assert(begin + size == end); - (void)!end; + assert(!"Invalid encoding"); + return false; +} + #endif - // zero-terminate - buffer[size] = 0; - } +PUGI__FN size_t as_utf8_begin(const wchar_t* str, size_t length) { + // get length in utf8 characters + return utf_decoder::decode_wchar_block(str, length, 0); +} -#ifndef PUGIXML_NO_STL - PUGI__FN std::string as_utf8_impl(const wchar_t* str, size_t length) - { - // first pass: get length in utf8 characters - size_t size = as_utf8_begin(str, length); +PUGI__FN void as_utf8_end(char* buffer, size_t size, const wchar_t* str, size_t length) { + // convert to utf8 + uint8_t* begin = reinterpret_cast(buffer); + uint8_t* end = utf_decoder::decode_wchar_block(str, length, begin); + + assert(begin + size == end); + (void)!end; - // allocate resulting string - std::string result; - result.resize(size); + // zero-terminate + buffer[size] = 0; +} - // second pass: convert to utf8 - if (size > 0) as_utf8_end(&result[0], size, str, length); + #ifndef PUGIXML_NO_STL +PUGI__FN std::string as_utf8_impl(const wchar_t* str, size_t length) { + // first pass: get length in utf8 characters + size_t size = as_utf8_begin(str, length); - return result; + // allocate resulting string + std::string result; + result.resize(size); + + // second pass: convert to utf8 + if (size > 0) { + as_utf8_end(&result[0], size, str, length); } - PUGI__FN std::basic_string as_wide_impl(const char* str, size_t size) - { - const uint8_t* data = reinterpret_cast(str); + return result; +} - // first pass: get length in wchar_t units - size_t length = utf_decoder::decode_utf8_block(data, size, 0); +PUGI__FN std::basic_string as_wide_impl(const char* str, size_t size) { + const uint8_t* data = reinterpret_cast(str); - // allocate resulting string - std::basic_string result; - result.resize(length); + // first pass: get length in wchar_t units + size_t length = utf_decoder::decode_utf8_block(data, size, 0); - // second pass: convert to wchar_t - if (length > 0) - { - wchar_writer::value_type begin = reinterpret_cast(&result[0]); - wchar_writer::value_type end = utf_decoder::decode_utf8_block(data, size, begin); + // allocate resulting string + std::basic_string result; + result.resize(length); - assert(begin + length == end); - (void)!end; - } + // second pass: convert to wchar_t + if (length > 0) { + wchar_writer::value_type begin = reinterpret_cast(&result[0]); + wchar_writer::value_type end = utf_decoder::decode_utf8_block(data, size, begin); - return result; + assert(begin + length == end); + (void)!end; } -#endif - inline bool strcpy_insitu_allow(size_t length, uintptr_t allocated, char_t* target) - { - assert(target); - size_t target_length = strlength(target); - - // always reuse document buffer memory if possible - if (!allocated) return target_length >= length; + return result; +} + #endif - // reuse heap memory if waste is not too great - const size_t reuse_threshold = 32; +inline bool strcpy_insitu_allow(size_t length, uintptr_t allocated, char_t* target) { + assert(target); + size_t target_length = strlength(target); - return target_length >= length && (target_length < reuse_threshold || target_length - length < target_length / 2); + // always reuse document buffer memory if possible + if (!allocated) { + return target_length >= length; } - PUGI__FN bool strcpy_insitu(char_t*& dest, uintptr_t& header, uintptr_t header_mask, const char_t* source) - { - size_t source_length = strlength(source); + // reuse heap memory if waste is not too great + const size_t reuse_threshold = 32; - if (source_length == 0) - { - // empty string and null pointer are equivalent, so just deallocate old memory - xml_allocator* alloc = reinterpret_cast(header & xml_memory_page_pointer_mask)->allocator; + return target_length >= length && (target_length < reuse_threshold || target_length - length < target_length / 2); +} - if (header & header_mask) alloc->deallocate_string(dest); +PUGI__FN bool strcpy_insitu(char_t*& dest, uintptr_t& header, uintptr_t header_mask, const char_t* source) { + size_t source_length = strlength(source); - // mark the string as not allocated - dest = 0; - header &= ~header_mask; + if (source_length == 0) { + // empty string and null pointer are equivalent, so just deallocate old memory + xml_allocator* alloc = reinterpret_cast(header & xml_memory_page_pointer_mask)->allocator; - return true; + if (header & header_mask) { + alloc->deallocate_string(dest); } - else if (dest && strcpy_insitu_allow(source_length, header & header_mask, dest)) - { - // we can reuse old buffer, so just copy the new data (including zero terminator) - memcpy(dest, source, (source_length + 1) * sizeof(char_t)); - return true; - } - else - { - xml_allocator* alloc = reinterpret_cast(header & xml_memory_page_pointer_mask)->allocator; + // mark the string as not allocated + dest = 0; + header &= ~header_mask; - // allocate new buffer - char_t* buf = alloc->allocate_string(source_length + 1); - if (!buf) return false; + return true; + } else if (dest && strcpy_insitu_allow(source_length, header & header_mask, dest)) { + // we can reuse old buffer, so just copy the new data (including zero terminator) + memcpy(dest, source, (source_length + 1) * sizeof(char_t)); - // copy the string (including zero terminator) - memcpy(buf, source, (source_length + 1) * sizeof(char_t)); + return true; + } else { + xml_allocator* alloc = reinterpret_cast(header & xml_memory_page_pointer_mask)->allocator; - // deallocate old buffer (*after* the above to protect against overlapping memory and/or allocation failures) - if (header & header_mask) alloc->deallocate_string(dest); + // allocate new buffer + char_t* buf = alloc->allocate_string(source_length + 1); + if (!buf) { + return false; + } - // the string is now allocated, so set the flag - dest = buf; - header |= header_mask; + // copy the string (including zero terminator) + memcpy(buf, source, (source_length + 1) * sizeof(char_t)); - return true; + // deallocate old buffer (*after* the above to protect against overlapping memory and/or allocation failures) + if (header & header_mask) { + alloc->deallocate_string(dest); } + + // the string is now allocated, so set the flag + dest = buf; + header |= header_mask; + + return true; } +} - struct gap - { - char_t* end; - size_t size; +struct gap { + char_t* end; + size_t size; - gap(): end(0), size(0) - { - } + gap() : + end(0), size(0) { + } - // Push new gap, move s count bytes further (skipping the gap). - // Collapse previous gap. - void push(char_t*& s, size_t count) + // Push new gap, move s count bytes further (skipping the gap). + // Collapse previous gap. + void push(char_t*& s, size_t count) { + if (end) // there was a gap already; collapse it { - if (end) // there was a gap already; collapse it - { - // Move [old_gap_end, new_gap_start) to [old_gap_start, ...) - assert(s >= end); - memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); - } + // Move [old_gap_end, new_gap_start) to [old_gap_start, ...) + assert(s >= end); + memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); + } - s += count; // end of current gap + s += count; // end of current gap - // "merge" two gaps - end = s; - size += count; - } + // "merge" two gaps + end = s; + size += count; + } - // Collapse all gaps, return past-the-end pointer - char_t* flush(char_t* s) - { - if (end) - { - // Move [old_gap_end, current_pos) to [old_gap_start, ...) - assert(s >= end); - memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); + // Collapse all gaps, return past-the-end pointer + char_t* flush(char_t* s) { + if (end) { + // Move [old_gap_end, current_pos) to [old_gap_start, ...) + assert(s >= end); + memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); - return s - size; - } - else return s; + return s - size; + } else { + return s; } - }; + } +}; - PUGI__FN char_t* strconv_escape(char_t* s, gap& g) - { - char_t* stre = s + 1; +PUGI__FN char_t* strconv_escape(char_t* s, gap& g) { + char_t* stre = s + 1; - switch (*stre) + switch (*stre) { + case '#': // &#... { - case '#': // &#... - { - unsigned int ucsc = 0; - - if (stre[1] == 'x') // &#x... (hex code) - { - stre += 2; + unsigned int ucsc = 0; - char_t ch = *stre; + if (stre[1] == 'x') // &#x... (hex code) + { + stre += 2; - if (ch == ';') return stre; + char_t ch = *stre; - for (;;) - { - if (static_cast(ch - '0') <= 9) - ucsc = 16 * ucsc + (ch - '0'); - else if (static_cast((ch | ' ') - 'a') <= 5) - ucsc = 16 * ucsc + ((ch | ' ') - 'a' + 10); - else if (ch == ';') - break; - else // cancel - return stre; + if (ch == ';') { + return stre; + } - ch = *++stre; + for (;;) { + if (static_cast(ch - '0') <= 9) { + ucsc = 16 * ucsc + (ch - '0'); + } else if (static_cast((ch | ' ') - 'a') <= 5) { + ucsc = 16 * ucsc + ((ch | ' ') - 'a' + 10); + } else if (ch == ';') { + break; + } else { // cancel + return stre; } - ++stre; + ch = *++stre; } - else // &#... (dec code) - { - char_t ch = *++stre; - if (ch == ';') return stre; + ++stre; + } else // &#... (dec code) + { + char_t ch = *++stre; - for (;;) - { - if (static_cast(ch - '0') <= 9) - ucsc = 10 * ucsc + (ch - '0'); - else if (ch == ';') - break; - else // cancel - return stre; + if (ch == ';') { + return stre; + } - ch = *++stre; + for (;;) { + if (static_cast(ch - '0') <= 9) { + ucsc = 10 * ucsc + (ch - '0'); + } else if (ch == ';') { + break; + } else { // cancel + return stre; } - ++stre; + ch = *++stre; } - #ifdef PUGIXML_WCHAR_MODE - s = reinterpret_cast(wchar_writer::any(reinterpret_cast(s), ucsc)); - #else - s = reinterpret_cast(utf8_writer::any(reinterpret_cast(s), ucsc)); - #endif - - g.push(s, stre - s); - return stre; - } - - case 'a': // &a - { ++stre; + } - if (*stre == 'm') // &am - { - if (*++stre == 'p' && *++stre == ';') // & - { - *s++ = '&'; - ++stre; + #ifdef PUGIXML_WCHAR_MODE + s = reinterpret_cast(wchar_writer::any(reinterpret_cast(s), ucsc)); + #else + s = reinterpret_cast(utf8_writer::any(reinterpret_cast(s), ucsc)); + #endif - g.push(s, stre - s); - return stre; - } - } - else if (*stre == 'p') // &ap - { - if (*++stre == 'o' && *++stre == 's' && *++stre == ';') // ' - { - *s++ = '\''; - ++stre; + g.push(s, stre - s); + return stre; + } - g.push(s, stre - s); - return stre; - } - } - break; - } + case 'a': // &a + { + ++stre; - case 'g': // &g + if (*stre == 'm') // &am { - if (*++stre == 't' && *++stre == ';') // > + if (*++stre == 'p' && *++stre == ';') // & { - *s++ = '>'; + *s++ = '&'; ++stre; g.push(s, stre - s); return stre; } - break; - } - - case 'l': // &l + } else if (*stre == 'p') // &ap { - if (*++stre == 't' && *++stre == ';') // < + if (*++stre == 'o' && *++stre == 's' && *++stre == ';') // ' { - *s++ = '<'; + *s++ = '\''; ++stre; g.push(s, stre - s); return stre; } - break; } + break; + } - case 'q': // &q + case 'g': // &g + { + if (*++stre == 't' && *++stre == ';') // > { - if (*++stre == 'u' && *++stre == 'o' && *++stre == 't' && *++stre == ';') // " - { - *s++ = '"'; - ++stre; + *s++ = '>'; + ++stre; - g.push(s, stre - s); - return stre; - } - break; + g.push(s, stre - s); + return stre; } + break; + } - default: - break; + case 'l': // &l + { + if (*++stre == 't' && *++stre == ';') // < + { + *s++ = '<'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; + } + + case 'q': // &q + { + if (*++stre == 'u' && *++stre == 'o' && *++stre == 't' && *++stre == ';') // " + { + *s++ = '"'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; } - return stre; + default: + break; } + return stre; +} + // Utility macro for last character handling #define ENDSWITH(c, e) ((c) == (e) || ((c) == 0 && endch == (e))) - PUGI__FN char_t* strconv_comment(char_t* s, char_t endch) - { - gap g; +PUGI__FN char_t* strconv_comment(char_t* s, char_t endch) { + gap g; - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_comment)) ++s; + while (true) { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_comment)) { + ++s; + } - if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair - { - *s++ = '\n'; // replace first one with 0x0a + if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a - if (*s == '\n') g.push(s, 1); + if (*s == '\n') { + g.push(s, 1); } - else if (s[0] == '-' && s[1] == '-' && ENDSWITH(s[2], '>')) // comment ends here - { - *g.flush(s) = 0; + } else if (s[0] == '-' && s[1] == '-' && ENDSWITH(s[2], '>')) // comment ends here + { + *g.flush(s) = 0; - return s + (s[2] == '>' ? 3 : 2); - } - else if (*s == 0) - { - return 0; - } - else ++s; + return s + (s[2] == '>' ? 3 : 2); + } else if (*s == 0) { + return 0; + } else { + ++s; } } +} - PUGI__FN char_t* strconv_cdata(char_t* s, char_t endch) - { - gap g; +PUGI__FN char_t* strconv_cdata(char_t* s, char_t endch) { + gap g; - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_cdata)) ++s; + while (true) { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_cdata)) { + ++s; + } - if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair - { - *s++ = '\n'; // replace first one with 0x0a + if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a - if (*s == '\n') g.push(s, 1); + if (*s == '\n') { + g.push(s, 1); } - else if (s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')) // CDATA ends here - { - *g.flush(s) = 0; + } else if (s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')) // CDATA ends here + { + *g.flush(s) = 0; - return s + 1; - } - else if (*s == 0) - { - return 0; - } - else ++s; + return s + 1; + } else if (*s == 0) { + return 0; + } else { + ++s; } } +} - typedef char_t* (*strconv_pcdata_t)(char_t*); +typedef char_t* (*strconv_pcdata_t)(char_t*); - template struct strconv_pcdata_impl - { - static char_t* parse(char_t* s) - { - gap g; +template +struct strconv_pcdata_impl { + static char_t* parse(char_t* s) { + gap g; - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_pcdata)) ++s; + while (true) { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_pcdata)) { + ++s; + } - if (*s == '<') // PCDATA ends here - { - *g.flush(s) = 0; + if (*s == '<') // PCDATA ends here + { + *g.flush(s) = 0; - return s + 1; - } - else if (opt_eol::value && *s == '\r') // Either a single 0x0d or 0x0d 0x0a pair - { - *s++ = '\n'; // replace first one with 0x0a + return s + 1; + } else if (opt_eol::value && *s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a - if (*s == '\n') g.push(s, 1); - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); + if (*s == '\n') { + g.push(s, 1); } - else if (*s == 0) - { - return s; - } - else ++s; + } else if (opt_escape::value && *s == '&') { + s = strconv_escape(s, g); + } else if (*s == 0) { + return s; + } else { + ++s; } } - }; - - PUGI__FN strconv_pcdata_t get_strconv_pcdata(unsigned int optmask) - { - PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20); - - switch ((optmask >> 4) & 3) // get bitmask for flags (eol escapes) - { - case 0: return strconv_pcdata_impl::parse; - case 1: return strconv_pcdata_impl::parse; - case 2: return strconv_pcdata_impl::parse; - case 3: return strconv_pcdata_impl::parse; - default: return 0; // should not get here - } } +}; - typedef char_t* (*strconv_attribute_t)(char_t*, char_t); +PUGI__FN strconv_pcdata_t get_strconv_pcdata(unsigned int optmask) { + PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20); - template struct strconv_attribute_impl + switch ((optmask >> 4) & 3) // get bitmask for flags (eol escapes) { - static char_t* parse_wnorm(char_t* s, char_t end_quote) - { - gap g; + case 0: + return strconv_pcdata_impl::parse; + case 1: + return strconv_pcdata_impl::parse; + case 2: + return strconv_pcdata_impl::parse; + case 3: + return strconv_pcdata_impl::parse; + default: + return 0; // should not get here + } +} - // trim leading whitespaces - if (PUGI__IS_CHARTYPE(*s, ct_space)) - { - char_t* str = s; +typedef char_t* (*strconv_attribute_t)(char_t*, char_t); - do ++str; - while (PUGI__IS_CHARTYPE(*str, ct_space)); +template +struct strconv_attribute_impl { + static char_t* parse_wnorm(char_t* s, char_t end_quote) { + gap g; - g.push(s, str - s); - } + // trim leading whitespaces + if (PUGI__IS_CHARTYPE(*s, ct_space)) { + char_t* str = s; - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr_ws | ct_space)) ++s; + do { + ++str; + } while (PUGI__IS_CHARTYPE(*str, ct_space)); - if (*s == end_quote) - { - char_t* str = g.flush(s); + g.push(s, str - s); + } - do *str-- = 0; - while (PUGI__IS_CHARTYPE(*str, ct_space)); + while (true) { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr_ws | ct_space)) { + ++s; + } - return s + 1; - } - else if (PUGI__IS_CHARTYPE(*s, ct_space)) - { - *s++ = ' '; + if (*s == end_quote) { + char_t* str = g.flush(s); - if (PUGI__IS_CHARTYPE(*s, ct_space)) - { - char_t* str = s + 1; - while (PUGI__IS_CHARTYPE(*str, ct_space)) ++str; + do { + *str-- = 0; + } while (PUGI__IS_CHARTYPE(*str, ct_space)); - g.push(s, str - s); + return s + 1; + } else if (PUGI__IS_CHARTYPE(*s, ct_space)) { + *s++ = ' '; + + if (PUGI__IS_CHARTYPE(*s, ct_space)) { + char_t* str = s + 1; + while (PUGI__IS_CHARTYPE(*str, ct_space)) { + ++str; } + + g.push(s, str - s); } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); - } - else if (!*s) - { - return 0; - } - else ++s; + } else if (opt_escape::value && *s == '&') { + s = strconv_escape(s, g); + } else if (!*s) { + return 0; + } else { + ++s; } } + } - static char_t* parse_wconv(char_t* s, char_t end_quote) - { - gap g; + static char_t* parse_wconv(char_t* s, char_t end_quote) { + gap g; - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr_ws)) ++s; + while (true) { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr_ws)) { + ++s; + } - if (*s == end_quote) - { - *g.flush(s) = 0; + if (*s == end_quote) { + *g.flush(s) = 0; - return s + 1; - } - else if (PUGI__IS_CHARTYPE(*s, ct_space)) - { - if (*s == '\r') - { - *s++ = ' '; + return s + 1; + } else if (PUGI__IS_CHARTYPE(*s, ct_space)) { + if (*s == '\r') { + *s++ = ' '; - if (*s == '\n') g.push(s, 1); + if (*s == '\n') { + g.push(s, 1); } - else *s++ = ' '; - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); - } - else if (!*s) - { - return 0; + } else { + *s++ = ' '; } - else ++s; + } else if (opt_escape::value && *s == '&') { + s = strconv_escape(s, g); + } else if (!*s) { + return 0; + } else { + ++s; } } + } - static char_t* parse_eol(char_t* s, char_t end_quote) - { - gap g; + static char_t* parse_eol(char_t* s, char_t end_quote) { + gap g; - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr)) ++s; + while (true) { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr)) { + ++s; + } - if (*s == end_quote) - { - *g.flush(s) = 0; + if (*s == end_quote) { + *g.flush(s) = 0; - return s + 1; - } - else if (*s == '\r') - { - *s++ = '\n'; + return s + 1; + } else if (*s == '\r') { + *s++ = '\n'; - if (*s == '\n') g.push(s, 1); - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); + if (*s == '\n') { + g.push(s, 1); } - else if (!*s) - { - return 0; - } - else ++s; + } else if (opt_escape::value && *s == '&') { + s = strconv_escape(s, g); + } else if (!*s) { + return 0; + } else { + ++s; } } + } - static char_t* parse_simple(char_t* s, char_t end_quote) - { - gap g; + static char_t* parse_simple(char_t* s, char_t end_quote) { + gap g; - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr)) ++s; + while (true) { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr)) { + ++s; + } - if (*s == end_quote) - { - *g.flush(s) = 0; + if (*s == end_quote) { + *g.flush(s) = 0; - return s + 1; - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); - } - else if (!*s) - { - return 0; - } - else ++s; + return s + 1; + } else if (opt_escape::value && *s == '&') { + s = strconv_escape(s, g); + } else if (!*s) { + return 0; + } else { + ++s; } } - }; - - PUGI__FN strconv_attribute_t get_strconv_attribute(unsigned int optmask) - { - PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_wconv_attribute == 0x40 && parse_wnorm_attribute == 0x80); - - switch ((optmask >> 4) & 15) // get bitmask for flags (wconv wnorm eol escapes) - { - case 0: return strconv_attribute_impl::parse_simple; - case 1: return strconv_attribute_impl::parse_simple; - case 2: return strconv_attribute_impl::parse_eol; - case 3: return strconv_attribute_impl::parse_eol; - case 4: return strconv_attribute_impl::parse_wconv; - case 5: return strconv_attribute_impl::parse_wconv; - case 6: return strconv_attribute_impl::parse_wconv; - case 7: return strconv_attribute_impl::parse_wconv; - case 8: return strconv_attribute_impl::parse_wnorm; - case 9: return strconv_attribute_impl::parse_wnorm; - case 10: return strconv_attribute_impl::parse_wnorm; - case 11: return strconv_attribute_impl::parse_wnorm; - case 12: return strconv_attribute_impl::parse_wnorm; - case 13: return strconv_attribute_impl::parse_wnorm; - case 14: return strconv_attribute_impl::parse_wnorm; - case 15: return strconv_attribute_impl::parse_wnorm; - default: return 0; // should not get here - } - } - - inline xml_parse_result make_parse_result(xml_parse_status status, ptrdiff_t offset = 0) - { - xml_parse_result result; - result.status = status; - result.offset = offset; - - return result; } +}; + +PUGI__FN strconv_attribute_t get_strconv_attribute(unsigned int optmask) { + PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_wconv_attribute == 0x40 && parse_wnorm_attribute == 0x80); + + switch ((optmask >> 4) & 15) // get bitmask for flags (wconv wnorm eol escapes) + { + case 0: + return strconv_attribute_impl::parse_simple; + case 1: + return strconv_attribute_impl::parse_simple; + case 2: + return strconv_attribute_impl::parse_eol; + case 3: + return strconv_attribute_impl::parse_eol; + case 4: + return strconv_attribute_impl::parse_wconv; + case 5: + return strconv_attribute_impl::parse_wconv; + case 6: + return strconv_attribute_impl::parse_wconv; + case 7: + return strconv_attribute_impl::parse_wconv; + case 8: + return strconv_attribute_impl::parse_wnorm; + case 9: + return strconv_attribute_impl::parse_wnorm; + case 10: + return strconv_attribute_impl::parse_wnorm; + case 11: + return strconv_attribute_impl::parse_wnorm; + case 12: + return strconv_attribute_impl::parse_wnorm; + case 13: + return strconv_attribute_impl::parse_wnorm; + case 14: + return strconv_attribute_impl::parse_wnorm; + case 15: + return strconv_attribute_impl::parse_wnorm; + default: + return 0; // should not get here + } +} - struct xml_parser - { - xml_allocator alloc; - char_t* error_offset; - xml_parse_status error_status; - - // Parser utilities. - #define PUGI__SKIPWS() { while (PUGI__IS_CHARTYPE(*s, ct_space)) ++s; } - #define PUGI__OPTSET(OPT) ( optmsk & (OPT) ) - #define PUGI__PUSHNODE(TYPE) { cursor = append_node(cursor, alloc, TYPE); if (!cursor) PUGI__THROW_ERROR(status_out_of_memory, s); } - #define PUGI__POPNODE() { cursor = cursor->parent; } - #define PUGI__SCANFOR(X) { while (*s != 0 && !(X)) ++s; } - #define PUGI__SCANWHILE(X) { while ((X)) ++s; } - #define PUGI__ENDSEG() { ch = *s; *s = 0; ++s; } - #define PUGI__THROW_ERROR(err, m) return error_offset = m, error_status = err, static_cast(0) - #define PUGI__CHECK_ERROR(err, m) { if (*s == 0) PUGI__THROW_ERROR(err, m); } - - xml_parser(const xml_allocator& alloc_): alloc(alloc_), error_offset(0), error_status(status_ok) - { - } +inline xml_parse_result make_parse_result(xml_parse_status status, ptrdiff_t offset = 0) { + xml_parse_result result; + result.status = status; + result.offset = offset; - // DOCTYPE consists of nested sections of the following possible types: - // , , "...", '...' - // - // - // First group can not contain nested groups - // Second group can contain nested groups of the same type - // Third group can contain all other groups - char_t* parse_doctype_primitive(char_t* s) - { - if (*s == '"' || *s == '\'') - { - // quoted string - char_t ch = *s++; - PUGI__SCANFOR(*s == ch); - if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + return result; +} - s++; +struct xml_parser { + xml_allocator alloc; + char_t* error_offset; + xml_parse_status error_status; + + // Parser utilities. + #define PUGI__SKIPWS() \ + { \ + while (PUGI__IS_CHARTYPE(*s, ct_space)) \ + ++s; \ + } + #define PUGI__OPTSET(OPT) (optmsk & (OPT)) + #define PUGI__PUSHNODE(TYPE) \ + { \ + cursor = append_node(cursor, alloc, TYPE); \ + if (!cursor) \ + PUGI__THROW_ERROR(status_out_of_memory, s); \ + } + #define PUGI__POPNODE() \ + { cursor = cursor->parent; } + #define PUGI__SCANFOR(X) \ + { \ + while (*s != 0 && !(X)) \ + ++s; \ + } + #define PUGI__SCANWHILE(X) \ + { \ + while ((X)) \ + ++s; \ + } + #define PUGI__ENDSEG() \ + { \ + ch = *s; \ + *s = 0; \ + ++s; \ + } + #define PUGI__THROW_ERROR(err, m) return error_offset = m, error_status = err, static_cast(0) + #define PUGI__CHECK_ERROR(err, m) \ + { \ + if (*s == 0) \ + PUGI__THROW_ERROR(err, m); \ + } + + xml_parser(const xml_allocator& alloc_) : + alloc(alloc_), error_offset(0), error_status(status_ok) { + } + + // DOCTYPE consists of nested sections of the following possible types: + // , , "...", '...' + // + // + // First group can not contain nested groups + // Second group can contain nested groups of the same type + // Third group can contain all other groups + char_t* parse_doctype_primitive(char_t* s) { + if (*s == '"' || *s == '\'') { + // quoted string + char_t ch = *s++; + PUGI__SCANFOR(*s == ch); + if (!*s) { + PUGI__THROW_ERROR(status_bad_doctype, s); } - else if (s[0] == '<' && s[1] == '?') - { - // - s += 2; - PUGI__SCANFOR(s[0] == '?' && s[1] == '>'); // no need for ENDSWITH because ?> can't terminate proper doctype - if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); - s += 2; + s++; + } else if (s[0] == '<' && s[1] == '?') { + // + s += 2; + PUGI__SCANFOR(s[0] == '?' && s[1] == '>'); // no need for ENDSWITH because ?> can't terminate proper doctype + if (!*s) { + PUGI__THROW_ERROR(status_bad_doctype, s); } - else if (s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-') - { - s += 4; - PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && s[2] == '>'); // no need for ENDSWITH because --> can't terminate proper doctype - if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); - s += 4; + s += 2; + } else if (s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-') { + s += 4; + PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && s[2] == '>'); // no need for ENDSWITH because --> can't terminate proper doctype + if (!*s) { + PUGI__THROW_ERROR(status_bad_doctype, s); } - else PUGI__THROW_ERROR(status_bad_doctype, s); - return s; + s += 4; + } else { + PUGI__THROW_ERROR(status_bad_doctype, s); } - char_t* parse_doctype_ignore(char_t* s) - { - assert(s[0] == '<' && s[1] == '!' && s[2] == '['); - s++; + return s; + } - while (*s) - { - if (s[0] == '<' && s[1] == '!' && s[2] == '[') - { - // nested ignore section - s = parse_doctype_ignore(s); - if (!s) return s; - } - else if (s[0] == ']' && s[1] == ']' && s[2] == '>') - { - // ignore section end - s += 3; + char_t* parse_doctype_ignore(char_t* s) { + assert(s[0] == '<' && s[1] == '!' && s[2] == '['); + s++; + while (*s) { + if (s[0] == '<' && s[1] == '!' && s[2] == '[') { + // nested ignore section + s = parse_doctype_ignore(s); + if (!s) { return s; } - else s++; - } + } else if (s[0] == ']' && s[1] == ']' && s[2] == '>') { + // ignore section end + s += 3; - PUGI__THROW_ERROR(status_bad_doctype, s); + return s; + } else { + s++; + } } - char_t* parse_doctype_group(char_t* s, char_t endch, bool toplevel) - { - assert(s[0] == '<' && s[1] == '!'); - s++; + PUGI__THROW_ERROR(status_bad_doctype, s); + } - while (*s) - { - if (s[0] == '<' && s[1] == '!' && s[2] != '-') - { - if (s[2] == '[') - { - // ignore - s = parse_doctype_ignore(s); - if (!s) return s; + char_t* parse_doctype_group(char_t* s, char_t endch, bool toplevel) { + assert(s[0] == '<' && s[1] == '!'); + s++; + + while (*s) { + if (s[0] == '<' && s[1] == '!' && s[2] != '-') { + if (s[2] == '[') { + // ignore + s = parse_doctype_ignore(s); + if (!s) { + return s; } - else - { - // some control group - s = parse_doctype_group(s, endch, false); - if (!s) return s; + } else { + // some control group + s = parse_doctype_group(s, endch, false); + if (!s) { + return s; } } - else if (s[0] == '<' || s[0] == '"' || s[0] == '\'') - { - // unknown tag (forbidden), or some primitive group - s = parse_doctype_primitive(s); - if (!s) return s; - } - else if (*s == '>') - { - s++; - + } else if (s[0] == '<' || s[0] == '"' || s[0] == '\'') { + // unknown tag (forbidden), or some primitive group + s = parse_doctype_primitive(s); + if (!s) { return s; } - else s++; - } + } else if (*s == '>') { + s++; - if (!toplevel || endch != '>') PUGI__THROW_ERROR(status_bad_doctype, s); + return s; + } else { + s++; + } + } - return s; + if (!toplevel || endch != '>') { + PUGI__THROW_ERROR(status_bad_doctype, s); } - char_t* parse_exclamation(char_t* s, xml_node_struct* cursor, unsigned int optmsk, char_t endch) + return s; + } + + char_t* parse_exclamation(char_t* s, xml_node_struct* cursor, unsigned int optmsk, char_t endch) { + // parse node contents, starting with exclamation mark + ++s; + + if (*s == '-') // 'value = s; // Save the offset. - } + if (PUGI__OPTSET(parse_comments)) { + PUGI__PUSHNODE(node_comment); // Append a new node on the tree. + cursor->value = s; // Save the offset. + } - if (PUGI__OPTSET(parse_eol) && PUGI__OPTSET(parse_comments)) - { - s = strconv_comment(s, endch); + if (PUGI__OPTSET(parse_eol) && PUGI__OPTSET(parse_comments)) { + s = strconv_comment(s, endch); - if (!s) PUGI__THROW_ERROR(status_bad_comment, cursor->value); + if (!s) { + PUGI__THROW_ERROR(status_bad_comment, cursor->value); } - else - { - // Scan for terminating '-->'. - PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && ENDSWITH(s[2], '>')); - PUGI__CHECK_ERROR(status_bad_comment, s); + } else { + // Scan for terminating '-->'. + PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_comment, s); - if (PUGI__OPTSET(parse_comments)) - *s = 0; // Zero-terminate this segment at the first terminating '-'. - - s += (s[2] == '>' ? 3 : 2); // Step over the '\0->'. + if (PUGI__OPTSET(parse_comments)) { + *s = 0; // Zero-terminate this segment at the first terminating '-'. } + + s += (s[2] == '>' ? 3 : 2); // Step over the '\0->'. } - else PUGI__THROW_ERROR(status_bad_comment, s); + } else { + PUGI__THROW_ERROR(status_bad_comment, s); } - else if (*s == '[') - { - // 'value = s; // Save the offset. + if (PUGI__OPTSET(parse_cdata)) { + PUGI__PUSHNODE(node_cdata); // Append a new node on the tree. + cursor->value = s; // Save the offset. - if (PUGI__OPTSET(parse_eol)) - { - s = strconv_cdata(s, endch); + if (PUGI__OPTSET(parse_eol)) { + s = strconv_cdata(s, endch); - if (!s) PUGI__THROW_ERROR(status_bad_cdata, cursor->value); + if (!s) { + PUGI__THROW_ERROR(status_bad_cdata, cursor->value); } - else - { - // Scan for terminating ']]>'. - PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')); - PUGI__CHECK_ERROR(status_bad_cdata, s); - - *s++ = 0; // Zero-terminate this segment. - } - } - else // Flagged for discard, but we still have to scan for the terminator. - { + } else { // Scan for terminating ']]>'. PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')); PUGI__CHECK_ERROR(status_bad_cdata, s); - ++s; + *s++ = 0; // Zero-terminate this segment. } + } else // Flagged for discard, but we still have to scan for the terminator. + { + // Scan for terminating ']]>'. + PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_cdata, s); - s += (s[1] == '>' ? 2 : 1); // Step over the last ']>'. + ++s; } - else PUGI__THROW_ERROR(status_bad_cdata, s); + + s += (s[1] == '>' ? 2 : 1); // Step over the last ']>'. + } else { + PUGI__THROW_ERROR(status_bad_cdata, s); } - else if (s[0] == 'D' && s[1] == 'O' && s[2] == 'C' && s[3] == 'T' && s[4] == 'Y' && s[5] == 'P' && ENDSWITH(s[6], 'E')) - { - s -= 2; + } else if (s[0] == 'D' && s[1] == 'O' && s[2] == 'C' && s[3] == 'T' && s[4] == 'Y' && s[5] == 'P' && ENDSWITH(s[6], 'E')) { + s -= 2; - if (cursor->parent) PUGI__THROW_ERROR(status_bad_doctype, s); + if (cursor->parent) { + PUGI__THROW_ERROR(status_bad_doctype, s); + } - char_t* mark = s + 9; + char_t* mark = s + 9; - s = parse_doctype_group(s, endch, true); - if (!s) return s; + s = parse_doctype_group(s, endch, true); + if (!s) { + return s; + } - if (PUGI__OPTSET(parse_doctype)) - { - while (PUGI__IS_CHARTYPE(*mark, ct_space)) ++mark; + if (PUGI__OPTSET(parse_doctype)) { + while (PUGI__IS_CHARTYPE(*mark, ct_space)) { + ++mark; + } - PUGI__PUSHNODE(node_doctype); + PUGI__PUSHNODE(node_doctype); - cursor->value = mark; + cursor->value = mark; - assert((s[0] == 0 && endch == '>') || s[-1] == '>'); - s[*s == 0 ? 0 : -1] = 0; + assert((s[0] == 0 && endch == '>') || s[-1] == '>'); + s[*s == 0 ? 0 : -1] = 0; - PUGI__POPNODE(); - } + PUGI__POPNODE(); } - else if (*s == 0 && endch == '-') PUGI__THROW_ERROR(status_bad_comment, s); - else if (*s == 0 && endch == '[') PUGI__THROW_ERROR(status_bad_cdata, s); - else PUGI__THROW_ERROR(status_unrecognized_tag, s); - - return s; + } else if (*s == 0 && endch == '-') { + PUGI__THROW_ERROR(status_bad_comment, s); + } else if (*s == 0 && endch == '[') { + PUGI__THROW_ERROR(status_bad_cdata, s); + } else { + PUGI__THROW_ERROR(status_unrecognized_tag, s); } - char_t* parse_question(char_t* s, xml_node_struct*& ref_cursor, unsigned int optmsk, char_t endch) - { - // load into registers - xml_node_struct* cursor = ref_cursor; - char_t ch = 0; + return s; + } - // parse node contents, starting with question mark - ++s; + char_t* parse_question(char_t* s, xml_node_struct*& ref_cursor, unsigned int optmsk, char_t endch) { + // load into registers + xml_node_struct* cursor = ref_cursor; + char_t ch = 0; - // read PI target - char_t* target = s; + // parse node contents, starting with question mark + ++s; - if (!PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_pi, s); + // read PI target + char_t* target = s; - PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); - PUGI__CHECK_ERROR(status_bad_pi, s); + if (!PUGI__IS_CHARTYPE(*s, ct_start_symbol)) { + PUGI__THROW_ERROR(status_bad_pi, s); + } - // determine node type; stricmp / strcasecmp is not portable - bool declaration = (target[0] | ' ') == 'x' && (target[1] | ' ') == 'm' && (target[2] | ' ') == 'l' && target + 3 == s; + PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); + PUGI__CHECK_ERROR(status_bad_pi, s); - if (declaration ? PUGI__OPTSET(parse_declaration) : PUGI__OPTSET(parse_pi)) - { - if (declaration) - { - // disallow non top-level declarations - if (cursor->parent) PUGI__THROW_ERROR(status_bad_pi, s); + // determine node type; stricmp / strcasecmp is not portable + bool declaration = (target[0] | ' ') == 'x' && (target[1] | ' ') == 'm' && (target[2] | ' ') == 'l' && target + 3 == s; - PUGI__PUSHNODE(node_declaration); - } - else - { - PUGI__PUSHNODE(node_pi); + if (declaration ? PUGI__OPTSET(parse_declaration) : PUGI__OPTSET(parse_pi)) { + if (declaration) { + // disallow non top-level declarations + if (cursor->parent) { + PUGI__THROW_ERROR(status_bad_pi, s); } - cursor->name = target; + PUGI__PUSHNODE(node_declaration); + } else { + PUGI__PUSHNODE(node_pi); + } - PUGI__ENDSEG(); + cursor->name = target; - // parse value/attributes - if (ch == '?') - { - // empty node - if (!ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_pi, s); - s += (*s == '>'); + PUGI__ENDSEG(); - PUGI__POPNODE(); + // parse value/attributes + if (ch == '?') { + // empty node + if (!ENDSWITH(*s, '>')) { + PUGI__THROW_ERROR(status_bad_pi, s); } - else if (PUGI__IS_CHARTYPE(ch, ct_space)) - { - PUGI__SKIPWS(); + s += (*s == '>'); + + PUGI__POPNODE(); + } else if (PUGI__IS_CHARTYPE(ch, ct_space)) { + PUGI__SKIPWS(); - // scan for tag end - char_t* value = s; + // scan for tag end + char_t* value = s; - PUGI__SCANFOR(s[0] == '?' && ENDSWITH(s[1], '>')); - PUGI__CHECK_ERROR(status_bad_pi, s); + PUGI__SCANFOR(s[0] == '?' && ENDSWITH(s[1], '>')); + PUGI__CHECK_ERROR(status_bad_pi, s); - if (declaration) - { - // replace ending ? with / so that 'element' terminates properly - *s = '/'; + if (declaration) { + // replace ending ? with / so that 'element' terminates properly + *s = '/'; - // we exit from this function with cursor at node_declaration, which is a signal to parse() to go to LOC_ATTRIBUTES - s = value; - } - else - { - // store value and step over > - cursor->value = value; - PUGI__POPNODE(); + // we exit from this function with cursor at node_declaration, which is a signal to parse() to go to LOC_ATTRIBUTES + s = value; + } else { + // store value and step over > + cursor->value = value; + PUGI__POPNODE(); - PUGI__ENDSEG(); + PUGI__ENDSEG(); - s += (*s == '>'); - } + s += (*s == '>'); } - else PUGI__THROW_ERROR(status_bad_pi, s); + } else { + PUGI__THROW_ERROR(status_bad_pi, s); } - else - { - // scan for tag end - PUGI__SCANFOR(s[0] == '?' && ENDSWITH(s[1], '>')); - PUGI__CHECK_ERROR(status_bad_pi, s); + } else { + // scan for tag end + PUGI__SCANFOR(s[0] == '?' && ENDSWITH(s[1], '>')); + PUGI__CHECK_ERROR(status_bad_pi, s); - s += (s[1] == '>' ? 2 : 1); - } + s += (s[1] == '>' ? 2 : 1); + } - // store from registers - ref_cursor = cursor; + // store from registers + ref_cursor = cursor; - return s; - } + return s; + } - char_t* parse(char_t* s, xml_node_struct* xmldoc, unsigned int optmsk, char_t endch) - { - strconv_attribute_t strconv_attribute = get_strconv_attribute(optmsk); - strconv_pcdata_t strconv_pcdata = get_strconv_pcdata(optmsk); + char_t* parse(char_t* s, xml_node_struct* xmldoc, unsigned int optmsk, char_t endch) { + strconv_attribute_t strconv_attribute = get_strconv_attribute(optmsk); + strconv_pcdata_t strconv_pcdata = get_strconv_pcdata(optmsk); - char_t ch = 0; - xml_node_struct* cursor = xmldoc; - char_t* mark = s; + char_t ch = 0; + xml_node_struct* cursor = xmldoc; + char_t* mark = s; - while (*s != 0) - { - if (*s == '<') + while (*s != 0) { + if (*s == '<') { + ++s; + + LOC_TAG: + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // '<#...' { - ++s; + PUGI__PUSHNODE(node_element); // Append a new node to the tree. - LOC_TAG: - if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // '<#...' - { - PUGI__PUSHNODE(node_element); // Append a new node to the tree. + cursor->name = s; - cursor->name = s; + PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); // Scan for a terminator. + PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. - PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); // Scan for a terminator. - PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. + if (ch == '>') { + // end of tag + } else if (PUGI__IS_CHARTYPE(ch, ct_space)) { + LOC_ATTRIBUTES: + while (true) { + PUGI__SKIPWS(); // Eat any whitespace. - if (ch == '>') - { - // end of tag - } - else if (PUGI__IS_CHARTYPE(ch, ct_space)) - { - LOC_ATTRIBUTES: - while (true) + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // <... #... { - PUGI__SKIPWS(); // Eat any whitespace. + xml_attribute_struct* a = append_attribute_ll(cursor, alloc); // Make space for this attribute. + if (!a) { + PUGI__THROW_ERROR(status_out_of_memory, s); + } - if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // <... #... - { - xml_attribute_struct* a = append_attribute_ll(cursor, alloc); // Make space for this attribute. - if (!a) PUGI__THROW_ERROR(status_out_of_memory, s); + a->name = s; // Save the offset. - a->name = s; // Save the offset. + PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); // Scan for a terminator. + PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance - PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); // Scan for a terminator. - PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance + PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. + PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance - PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. + if (PUGI__IS_CHARTYPE(ch, ct_space)) { + PUGI__SKIPWS(); // Eat any whitespace. PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance - if (PUGI__IS_CHARTYPE(ch, ct_space)) - { - PUGI__SKIPWS(); // Eat any whitespace. - PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance + ch = *s; + ++s; + } - ch = *s; - ++s; - } + if (ch == '=') // '<... #=...' + { + PUGI__SKIPWS(); // Eat any whitespace. - if (ch == '=') // '<... #=...' + if (*s == '"' || *s == '\'') // '<... #="...' { - PUGI__SKIPWS(); // Eat any whitespace. - - if (*s == '"' || *s == '\'') // '<... #="...' - { - ch = *s; // Save quote char to avoid breaking on "''" -or- '""'. - ++s; // Step over the quote. - a->value = s; // Save the offset. + ch = *s; // Save quote char to avoid breaking on "''" -or- '""'. + ++s; // Step over the quote. + a->value = s; // Save the offset. - s = strconv_attribute(s, ch); + s = strconv_attribute(s, ch); - if (!s) PUGI__THROW_ERROR(status_bad_attribute, a->value); - - // After this line the loop continues from the start; - // Whitespaces, / and > are ok, symbols and EOF are wrong, - // everything else will be detected - if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_attribute, s); + if (!s) { + PUGI__THROW_ERROR(status_bad_attribute, a->value); } - else PUGI__THROW_ERROR(status_bad_attribute, s); - } - else PUGI__THROW_ERROR(status_bad_attribute, s); - } - else if (*s == '/') - { - ++s; - if (*s == '>') - { - PUGI__POPNODE(); - s++; - break; - } - else if (*s == 0 && endch == '>') - { - PUGI__POPNODE(); - break; + // After this line the loop continues from the start; + // Whitespaces, / and > are ok, symbols and EOF are wrong, + // everything else will be detected + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) { + PUGI__THROW_ERROR(status_bad_attribute, s); + } + } else { + PUGI__THROW_ERROR(status_bad_attribute, s); } - else PUGI__THROW_ERROR(status_bad_start_element, s); + } else { + PUGI__THROW_ERROR(status_bad_attribute, s); } - else if (*s == '>') - { - ++s; + } else if (*s == '/') { + ++s; + if (*s == '>') { + PUGI__POPNODE(); + s++; break; - } - else if (*s == 0 && endch == '>') - { + } else if (*s == 0 && endch == '>') { + PUGI__POPNODE(); break; + } else { + PUGI__THROW_ERROR(status_bad_start_element, s); } - else PUGI__THROW_ERROR(status_bad_start_element, s); + } else if (*s == '>') { + ++s; + + break; + } else if (*s == 0 && endch == '>') { + break; + } else { + PUGI__THROW_ERROR(status_bad_start_element, s); } + } - // !!! + // !!! + } else if (ch == '/') // '<#.../' + { + if (!ENDSWITH(*s, '>')) { + PUGI__THROW_ERROR(status_bad_start_element, s); } - else if (ch == '/') // '<#.../' - { - if (!ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_start_element, s); - PUGI__POPNODE(); // Pop. + PUGI__POPNODE(); // Pop. - s += (*s == '>'); - } - else if (ch == 0) - { - // we stepped over null terminator, backtrack & handle closing tag - --s; + s += (*s == '>'); + } else if (ch == 0) { + // we stepped over null terminator, backtrack & handle closing tag + --s; - if (endch != '>') PUGI__THROW_ERROR(status_bad_start_element, s); + if (endch != '>') { + PUGI__THROW_ERROR(status_bad_start_element, s); } - else PUGI__THROW_ERROR(status_bad_start_element, s); + } else { + PUGI__THROW_ERROR(status_bad_start_element, s); } - else if (*s == '/') - { - ++s; + } else if (*s == '/') { + ++s; - char_t* name = cursor->name; - if (!name) PUGI__THROW_ERROR(status_end_element_mismatch, s); + char_t* name = cursor->name; + if (!name) { + PUGI__THROW_ERROR(status_end_element_mismatch, s); + } - while (PUGI__IS_CHARTYPE(*s, ct_symbol)) - { - if (*s++ != *name++) PUGI__THROW_ERROR(status_end_element_mismatch, s); + while (PUGI__IS_CHARTYPE(*s, ct_symbol)) { + if (*s++ != *name++) { + PUGI__THROW_ERROR(status_end_element_mismatch, s); } + } - if (*name) - { - if (*s == 0 && name[0] == endch && name[1] == 0) PUGI__THROW_ERROR(status_bad_end_element, s); - else PUGI__THROW_ERROR(status_end_element_mismatch, s); + if (*name) { + if (*s == 0 && name[0] == endch && name[1] == 0) { + PUGI__THROW_ERROR(status_bad_end_element, s); + } else { + PUGI__THROW_ERROR(status_end_element_mismatch, s); } + } - PUGI__POPNODE(); // Pop. + PUGI__POPNODE(); // Pop. - PUGI__SKIPWS(); + PUGI__SKIPWS(); - if (*s == 0) - { - if (endch != '>') PUGI__THROW_ERROR(status_bad_end_element, s); + if (*s == 0) { + if (endch != '>') { + PUGI__THROW_ERROR(status_bad_end_element, s); } - else - { - if (*s != '>') PUGI__THROW_ERROR(status_bad_end_element, s); - ++s; + } else { + if (*s != '>') { + PUGI__THROW_ERROR(status_bad_end_element, s); } + ++s; + } + } else if (*s == '?') // 'header & xml_memory_page_type_mask) + 1 == node_declaration) goto LOC_ATTRIBUTES; + assert(cursor); + if ((cursor->header & xml_memory_page_type_mask) + 1 == node_declaration) { + goto LOC_ATTRIBUTES; } - else if (*s == '!') // 'first_child) { continue; } - else if (PUGI__OPTSET(parse_ws_pcdata_single)) - { - if (s[1] != '/' || cursor->first_child) continue; - } } + } - s = mark; + s = mark; - if (cursor->parent) - { - PUGI__PUSHNODE(node_pcdata); // Append a new node on the tree. - cursor->value = s; // Save the offset. + if (cursor->parent) { + PUGI__PUSHNODE(node_pcdata); // Append a new node on the tree. + cursor->value = s; // Save the offset. - s = strconv_pcdata(s); + s = strconv_pcdata(s); - PUGI__POPNODE(); // Pop since this is a standalone. + PUGI__POPNODE(); // Pop since this is a standalone. - if (!*s) break; + if (!*s) { + break; } - else - { - PUGI__SCANFOR(*s == '<'); // '...<' - if (!*s) break; - - ++s; + } else { + PUGI__SCANFOR(*s == '<'); // '...<' + if (!*s) { + break; } - // We're after '<' - goto LOC_TAG; + ++s; } - } - // check that last tag is closed - if (cursor != xmldoc) PUGI__THROW_ERROR(status_end_element_mismatch, s); + // We're after '<' + goto LOC_TAG; + } + } - return s; + // check that last tag is closed + if (cursor != xmldoc) { + PUGI__THROW_ERROR(status_end_element_mismatch, s); } - static xml_parse_result parse(char_t* buffer, size_t length, xml_node_struct* root, unsigned int optmsk) - { - xml_document_struct* xmldoc = static_cast(root); + return s; + } - // store buffer for offset_debug - xmldoc->buffer = buffer; + static xml_parse_result parse(char_t* buffer, size_t length, xml_node_struct* root, unsigned int optmsk) { + xml_document_struct* xmldoc = static_cast(root); - // early-out for empty documents - if (length == 0) return make_parse_result(status_ok); + // store buffer for offset_debug + xmldoc->buffer = buffer; - // create parser on stack - xml_parser parser(*xmldoc); + // early-out for empty documents + if (length == 0) { + return make_parse_result(status_ok); + } - // save last character and make buffer zero-terminated (speeds up parsing) - char_t endch = buffer[length - 1]; - buffer[length - 1] = 0; + // create parser on stack + xml_parser parser(*xmldoc); - // perform actual parsing - parser.parse(buffer, xmldoc, optmsk, endch); + // save last character and make buffer zero-terminated (speeds up parsing) + char_t endch = buffer[length - 1]; + buffer[length - 1] = 0; - xml_parse_result result = make_parse_result(parser.error_status, parser.error_offset ? parser.error_offset - buffer : 0); - assert(result.offset >= 0 && static_cast(result.offset) <= length); + // perform actual parsing + parser.parse(buffer, xmldoc, optmsk, endch); - // update allocator state - *static_cast(xmldoc) = parser.alloc; + xml_parse_result result = make_parse_result(parser.error_status, parser.error_offset ? parser.error_offset - buffer : 0); + assert(result.offset >= 0 && static_cast(result.offset) <= length); - // since we removed last character, we have to handle the only possible false positive - if (result && endch == '<') - { - // there's no possible well-formed document with < at the end - return make_parse_result(status_unrecognized_tag, length); - } + // update allocator state + *static_cast(xmldoc) = parser.alloc; - return result; + // since we removed last character, we have to handle the only possible false positive + if (result && endch == '<') { + // there's no possible well-formed document with < at the end + return make_parse_result(status_unrecognized_tag, length); } - }; - // Output facilities - PUGI__FN xml_encoding get_write_native_encoding() - { + return result; + } +}; + +// Output facilities +PUGI__FN xml_encoding get_write_native_encoding() { #ifdef PUGIXML_WCHAR_MODE - return get_wchar_encoding(); + return get_wchar_encoding(); #else - return encoding_utf8; + return encoding_utf8; #endif - } - - PUGI__FN xml_encoding get_write_encoding(xml_encoding encoding) - { - // replace wchar encoding with utf implementation - if (encoding == encoding_wchar) return get_wchar_encoding(); +} - // replace utf16 encoding with utf16 with specific endianness - if (encoding == encoding_utf16) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; +PUGI__FN xml_encoding get_write_encoding(xml_encoding encoding) { + // replace wchar encoding with utf implementation + if (encoding == encoding_wchar) { + return get_wchar_encoding(); + } - // replace utf32 encoding with utf32 with specific endianness - if (encoding == encoding_utf32) return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + // replace utf16 encoding with utf16 with specific endianness + if (encoding == encoding_utf16) { + return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + } - // only do autodetection if no explicit encoding is requested - if (encoding != encoding_auto) return encoding; + // replace utf32 encoding with utf32 with specific endianness + if (encoding == encoding_utf32) { + return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + } - // assume utf8 encoding - return encoding_utf8; + // only do autodetection if no explicit encoding is requested + if (encoding != encoding_auto) { + return encoding; } -#ifdef PUGIXML_WCHAR_MODE - PUGI__FN size_t get_valid_length(const char_t* data, size_t length) - { - assert(length > 0); + // assume utf8 encoding + return encoding_utf8; +} - // discard last character if it's the lead of a surrogate pair - return (sizeof(wchar_t) == 2 && static_cast(static_cast(data[length - 1]) - 0xD800) < 0x400) ? length - 1 : length; - } + #ifdef PUGIXML_WCHAR_MODE +PUGI__FN size_t get_valid_length(const char_t* data, size_t length) { + assert(length > 0); - PUGI__FN size_t convert_buffer(char_t* r_char, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) - { - // only endian-swapping is required - if (need_endian_swap_utf(encoding, get_wchar_encoding())) - { - convert_wchar_endian_swap(r_char, data, length); + // discard last character if it's the lead of a surrogate pair + return (sizeof(wchar_t) == 2 && static_cast(static_cast(data[length - 1]) - 0xD800) < 0x400) ? length - 1 : length; +} - return length * sizeof(char_t); - } +PUGI__FN size_t convert_buffer(char_t* r_char, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) { + // only endian-swapping is required + if (need_endian_swap_utf(encoding, get_wchar_encoding())) { + convert_wchar_endian_swap(r_char, data, length); - // convert to utf8 - if (encoding == encoding_utf8) - { - uint8_t* dest = r_u8; - uint8_t* end = utf_decoder::decode_wchar_block(data, length, dest); + return length * sizeof(char_t); + } - return static_cast(end - dest); - } + // convert to utf8 + if (encoding == encoding_utf8) { + uint8_t* dest = r_u8; + uint8_t* end = utf_decoder::decode_wchar_block(data, length, dest); - // convert to utf16 - if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) - { - uint16_t* dest = r_u16; + return static_cast(end - dest); + } - // convert to native utf16 - uint16_t* end = utf_decoder::decode_wchar_block(data, length, dest); + // convert to utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) { + uint16_t* dest = r_u16; - // swap if necessary - xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + // convert to native utf16 + uint16_t* end = utf_decoder::decode_wchar_block(data, length, dest); - if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - return static_cast(end - dest) * sizeof(uint16_t); + if (native_encoding != encoding) { + convert_utf_endian_swap(dest, dest, static_cast(end - dest)); } - // convert to utf32 - if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) - { - uint32_t* dest = r_u32; + return static_cast(end - dest) * sizeof(uint16_t); + } - // convert to native utf32 - uint32_t* end = utf_decoder::decode_wchar_block(data, length, dest); + // convert to utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) { + uint32_t* dest = r_u32; - // swap if necessary - xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + // convert to native utf32 + uint32_t* end = utf_decoder::decode_wchar_block(data, length, dest); - if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - return static_cast(end - dest) * sizeof(uint32_t); + if (native_encoding != encoding) { + convert_utf_endian_swap(dest, dest, static_cast(end - dest)); } - // convert to latin1 - if (encoding == encoding_latin1) - { - uint8_t* dest = r_u8; - uint8_t* end = utf_decoder::decode_wchar_block(data, length, dest); + return static_cast(end - dest) * sizeof(uint32_t); + } - return static_cast(end - dest); - } + // convert to latin1 + if (encoding == encoding_latin1) { + uint8_t* dest = r_u8; + uint8_t* end = utf_decoder::decode_wchar_block(data, length, dest); - assert(!"Invalid encoding"); - return 0; + return static_cast(end - dest); } -#else - PUGI__FN size_t get_valid_length(const char_t* data, size_t length) - { - assert(length > 4); - for (size_t i = 1; i <= 4; ++i) - { - uint8_t ch = static_cast(data[length - i]); + assert(!"Invalid encoding"); + return 0; +} + #else +PUGI__FN size_t get_valid_length(const char_t* data, size_t length) { + assert(length > 4); - // either a standalone character or a leading one - if ((ch & 0xc0) != 0x80) return length - i; - } + for (size_t i = 1; i <= 4; ++i) { + uint8_t ch = static_cast(data[length - i]); - // there are four non-leading characters at the end, sequence tail is broken so might as well process the whole chunk - return length; + // either a standalone character or a leading one + if ((ch & 0xc0) != 0x80) { + return length - i; + } } - PUGI__FN size_t convert_buffer(char_t* /* r_char */, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) - { - if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) - { - uint16_t* dest = r_u16; + // there are four non-leading characters at the end, sequence tail is broken so might as well process the whole chunk + return length; +} - // convert to native utf16 - uint16_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); +PUGI__FN size_t convert_buffer(char_t* /* r_char */, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) { + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) { + uint16_t* dest = r_u16; - // swap if necessary - xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + // convert to native utf16 + uint16_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); - if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - return static_cast(end - dest) * sizeof(uint16_t); + if (native_encoding != encoding) { + convert_utf_endian_swap(dest, dest, static_cast(end - dest)); } - if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) - { - uint32_t* dest = r_u32; + return static_cast(end - dest) * sizeof(uint16_t); + } - // convert to native utf32 - uint32_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) { + uint32_t* dest = r_u32; - // swap if necessary - xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + // convert to native utf32 + uint32_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); - if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - return static_cast(end - dest) * sizeof(uint32_t); + if (native_encoding != encoding) { + convert_utf_endian_swap(dest, dest, static_cast(end - dest)); } - if (encoding == encoding_latin1) - { - uint8_t* dest = r_u8; - uint8_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); + return static_cast(end - dest) * sizeof(uint32_t); + } - return static_cast(end - dest); - } + if (encoding == encoding_latin1) { + uint8_t* dest = r_u8; + uint8_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); - assert(!"Invalid encoding"); - return 0; + return static_cast(end - dest); } -#endif - - class xml_buffered_writer - { - xml_buffered_writer(const xml_buffered_writer&); - xml_buffered_writer& operator=(const xml_buffered_writer&); - public: - xml_buffered_writer(xml_writer& writer_, xml_encoding user_encoding): writer(writer_), bufsize(0), encoding(get_write_encoding(user_encoding)) - { - PUGI__STATIC_ASSERT(bufcapacity >= 8); - } + assert(!"Invalid encoding"); + return 0; +} + #endif - ~xml_buffered_writer() - { - flush(); - } +class xml_buffered_writer { + xml_buffered_writer(const xml_buffered_writer&); + xml_buffered_writer& operator=(const xml_buffered_writer&); - void flush() - { - flush(buffer, bufsize); - bufsize = 0; - } +public: + xml_buffered_writer(xml_writer& writer_, xml_encoding user_encoding) : + writer(writer_), bufsize(0), encoding(get_write_encoding(user_encoding)) { + PUGI__STATIC_ASSERT(bufcapacity >= 8); + } - void flush(const char_t* data, size_t size) - { - if (size == 0) return; + ~xml_buffered_writer() { + flush(); + } - // fast path, just write data - if (encoding == get_write_native_encoding()) - writer.write(data, size * sizeof(char_t)); - else - { - // convert chunk - size_t result = convert_buffer(scratch.data_char, scratch.data_u8, scratch.data_u16, scratch.data_u32, data, size, encoding); - assert(result <= sizeof(scratch)); + void flush() { + flush(buffer, bufsize); + bufsize = 0; + } - // write data - writer.write(scratch.data_u8, result); - } + void flush(const char_t* data, size_t size) { + if (size == 0) { + return; } - void write(const char_t* data, size_t length) - { - if (bufsize + length > bufcapacity) - { - // flush the remaining buffer contents - flush(); + // fast path, just write data + if (encoding == get_write_native_encoding()) { + writer.write(data, size * sizeof(char_t)); + } else { + // convert chunk + size_t result = convert_buffer(scratch.data_char, scratch.data_u8, scratch.data_u16, scratch.data_u32, data, size, encoding); + assert(result <= sizeof(scratch)); - // handle large chunks - if (length > bufcapacity) - { - if (encoding == get_write_native_encoding()) - { - // fast path, can just write data chunk - writer.write(data, length * sizeof(char_t)); - return; - } + // write data + writer.write(scratch.data_u8, result); + } + } - // need to convert in suitable chunks - while (length > bufcapacity) - { - // get chunk size by selecting such number of characters that are guaranteed to fit into scratch buffer - // and form a complete codepoint sequence (i.e. discard start of last codepoint if necessary) - size_t chunk_size = get_valid_length(data, bufcapacity); + void write(const char_t* data, size_t length) { + if (bufsize + length > bufcapacity) { + // flush the remaining buffer contents + flush(); + + // handle large chunks + if (length > bufcapacity) { + if (encoding == get_write_native_encoding()) { + // fast path, can just write data chunk + writer.write(data, length * sizeof(char_t)); + return; + } - // convert chunk and write - flush(data, chunk_size); + // need to convert in suitable chunks + while (length > bufcapacity) { + // get chunk size by selecting such number of characters that are guaranteed to fit into scratch buffer + // and form a complete codepoint sequence (i.e. discard start of last codepoint if necessary) + size_t chunk_size = get_valid_length(data, bufcapacity); - // iterate - data += chunk_size; - length -= chunk_size; - } + // convert chunk and write + flush(data, chunk_size); - // small tail is copied below - bufsize = 0; + // iterate + data += chunk_size; + length -= chunk_size; } - } - memcpy(buffer + bufsize, data, length * sizeof(char_t)); - bufsize += length; + // small tail is copied below + bufsize = 0; + } } - void write(const char_t* data) - { - write(data, strlength(data)); - } + memcpy(buffer + bufsize, data, length * sizeof(char_t)); + bufsize += length; + } - void write(char_t d0) - { - if (bufsize + 1 > bufcapacity) flush(); + void write(const char_t* data) { + write(data, strlength(data)); + } - buffer[bufsize + 0] = d0; - bufsize += 1; + void write(char_t d0) { + if (bufsize + 1 > bufcapacity) { + flush(); } - void write(char_t d0, char_t d1) - { - if (bufsize + 2 > bufcapacity) flush(); + buffer[bufsize + 0] = d0; + bufsize += 1; + } - buffer[bufsize + 0] = d0; - buffer[bufsize + 1] = d1; - bufsize += 2; + void write(char_t d0, char_t d1) { + if (bufsize + 2 > bufcapacity) { + flush(); } - void write(char_t d0, char_t d1, char_t d2) - { - if (bufsize + 3 > bufcapacity) flush(); + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + bufsize += 2; + } - buffer[bufsize + 0] = d0; - buffer[bufsize + 1] = d1; - buffer[bufsize + 2] = d2; - bufsize += 3; + void write(char_t d0, char_t d1, char_t d2) { + if (bufsize + 3 > bufcapacity) { + flush(); } - void write(char_t d0, char_t d1, char_t d2, char_t d3) - { - if (bufsize + 4 > bufcapacity) flush(); + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + buffer[bufsize + 2] = d2; + bufsize += 3; + } - buffer[bufsize + 0] = d0; - buffer[bufsize + 1] = d1; - buffer[bufsize + 2] = d2; - buffer[bufsize + 3] = d3; - bufsize += 4; + void write(char_t d0, char_t d1, char_t d2, char_t d3) { + if (bufsize + 4 > bufcapacity) { + flush(); } - void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4) - { - if (bufsize + 5 > bufcapacity) flush(); + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + buffer[bufsize + 2] = d2; + buffer[bufsize + 3] = d3; + bufsize += 4; + } - buffer[bufsize + 0] = d0; - buffer[bufsize + 1] = d1; - buffer[bufsize + 2] = d2; - buffer[bufsize + 3] = d3; - buffer[bufsize + 4] = d4; - bufsize += 5; + void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4) { + if (bufsize + 5 > bufcapacity) { + flush(); } - void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4, char_t d5) - { - if (bufsize + 6 > bufcapacity) flush(); + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + buffer[bufsize + 2] = d2; + buffer[bufsize + 3] = d3; + buffer[bufsize + 4] = d4; + bufsize += 5; + } - buffer[bufsize + 0] = d0; - buffer[bufsize + 1] = d1; - buffer[bufsize + 2] = d2; - buffer[bufsize + 3] = d3; - buffer[bufsize + 4] = d4; - buffer[bufsize + 5] = d5; - bufsize += 6; + void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4, char_t d5) { + if (bufsize + 6 > bufcapacity) { + flush(); } - // utf8 maximum expansion: x4 (-> utf32) - // utf16 maximum expansion: x2 (-> utf32) - // utf32 maximum expansion: x1 - enum - { - bufcapacitybytes = - #ifdef PUGIXML_MEMORY_OUTPUT_STACK - PUGIXML_MEMORY_OUTPUT_STACK - #else - 10240 - #endif - , - bufcapacity = bufcapacitybytes / (sizeof(char_t) + 4) - }; - - char_t buffer[bufcapacity]; - - union - { - uint8_t data_u8[4 * bufcapacity]; - uint16_t data_u16[2 * bufcapacity]; - uint32_t data_u32[bufcapacity]; - char_t data_char[bufcapacity]; - } scratch; - - xml_writer& writer; - size_t bufsize; - xml_encoding encoding; + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + buffer[bufsize + 2] = d2; + buffer[bufsize + 3] = d3; + buffer[bufsize + 4] = d4; + buffer[bufsize + 5] = d5; + bufsize += 6; + } + + // utf8 maximum expansion: x4 (-> utf32) + // utf16 maximum expansion: x2 (-> utf32) + // utf32 maximum expansion: x1 + enum { + bufcapacitybytes = + #ifdef PUGIXML_MEMORY_OUTPUT_STACK + PUGIXML_MEMORY_OUTPUT_STACK + #else + 10240 + #endif + , + bufcapacity = bufcapacitybytes / (sizeof(char_t) + 4) }; - PUGI__FN void text_output_escaped(xml_buffered_writer& writer, const char_t* s, chartypex_t type) - { - while (*s) - { - const char_t* prev = s; + char_t buffer[bufcapacity]; + + union { + uint8_t data_u8[4 * bufcapacity]; + uint16_t data_u16[2 * bufcapacity]; + uint32_t data_u32[bufcapacity]; + char_t data_char[bufcapacity]; + } scratch; + + xml_writer& writer; + size_t bufsize; + xml_encoding encoding; +}; + +PUGI__FN void text_output_escaped(xml_buffered_writer& writer, const char_t* s, chartypex_t type) { + while (*s) { + const char_t* prev = s; - // While *s is a usual symbol - while (!PUGI__IS_CHARTYPEX(*s, type)) ++s; + // While *s is a usual symbol + while (!PUGI__IS_CHARTYPEX(*s, type)) { + ++s; + } - writer.write(prev, static_cast(s - prev)); + writer.write(prev, static_cast(s - prev)); - switch (*s) + switch (*s) { + case 0: + break; + case '&': + writer.write('&', 'a', 'm', 'p', ';'); + ++s; + break; + case '<': + writer.write('&', 'l', 't', ';'); + ++s; + break; + case '>': + writer.write('&', 'g', 't', ';'); + ++s; + break; + case '"': + writer.write('&', 'q', 'u', 'o', 't', ';'); + ++s; + break; + default: // s is not a usual symbol { - case 0: break; - case '&': - writer.write('&', 'a', 'm', 'p', ';'); - ++s; - break; - case '<': - writer.write('&', 'l', 't', ';'); - ++s; - break; - case '>': - writer.write('&', 'g', 't', ';'); - ++s; - break; - case '"': - writer.write('&', 'q', 'u', 'o', 't', ';'); - ++s; - break; - default: // s is not a usual symbol - { - unsigned int ch = static_cast(*s++); - assert(ch < 32); + unsigned int ch = static_cast(*s++); + assert(ch < 32); - writer.write('&', '#', static_cast((ch / 10) + '0'), static_cast((ch % 10) + '0'), ';'); - } + writer.write('&', '#', static_cast((ch / 10) + '0'), static_cast((ch % 10) + '0'), ';'); } } } +} - PUGI__FN void text_output(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) - { - if (flags & format_no_escapes) - writer.write(s); - else - text_output_escaped(writer, s, type); +PUGI__FN void text_output(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) { + if (flags & format_no_escapes) { + writer.write(s); + } else { + text_output_escaped(writer, s, type); } +} - PUGI__FN void text_output_cdata(xml_buffered_writer& writer, const char_t* s) - { - do - { - writer.write('<', '!', '[', 'C', 'D'); - writer.write('A', 'T', 'A', '['); +PUGI__FN void text_output_cdata(xml_buffered_writer& writer, const char_t* s) { + do { + writer.write('<', '!', '[', 'C', 'D'); + writer.write('A', 'T', 'A', '['); - const char_t* prev = s; + const char_t* prev = s; - // look for ]]> sequence - we can't output it as is since it terminates CDATA - while (*s && !(s[0] == ']' && s[1] == ']' && s[2] == '>')) ++s; + // look for ]]> sequence - we can't output it as is since it terminates CDATA + while (*s && !(s[0] == ']' && s[1] == ']' && s[2] == '>')) { + ++s; + } - // skip ]] if we stopped at ]]>, > will go to the next CDATA section - if (*s) s += 2; + // skip ]] if we stopped at ]]>, > will go to the next CDATA section + if (*s) { + s += 2; + } - writer.write(prev, static_cast(s - prev)); + writer.write(prev, static_cast(s - prev)); - writer.write(']', ']', '>'); - } - while (*s); - } + writer.write(']', ']', '>'); + } while (*s); +} - PUGI__FN void node_output_attributes(xml_buffered_writer& writer, const xml_node& node, unsigned int flags) - { - const char_t* default_name = PUGIXML_TEXT(":anonymous"); +PUGI__FN void node_output_attributes(xml_buffered_writer& writer, const xml_node& node, unsigned int flags) { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); - for (xml_attribute a = node.first_attribute(); a; a = a.next_attribute()) - { - writer.write(' '); - writer.write(a.name()[0] ? a.name() : default_name); - writer.write('=', '"'); + for (xml_attribute a = node.first_attribute(); a; a = a.next_attribute()) { + writer.write(' '); + writer.write(a.name()[0] ? a.name() : default_name); + writer.write('=', '"'); - text_output(writer, a.value(), ctx_special_attr, flags); + text_output(writer, a.value(), ctx_special_attr, flags); - writer.write('"'); - } + writer.write('"'); } +} - PUGI__FN void node_output(xml_buffered_writer& writer, const xml_node& node, const char_t* indent, unsigned int flags, unsigned int depth) - { - const char_t* default_name = PUGIXML_TEXT(":anonymous"); +PUGI__FN void node_output(xml_buffered_writer& writer, const xml_node& node, const char_t* indent, unsigned int flags, unsigned int depth) { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); - if ((flags & format_indent) != 0 && (flags & format_raw) == 0) - for (unsigned int i = 0; i < depth; ++i) writer.write(indent); + if ((flags & format_indent) != 0 && (flags & format_raw) == 0) { + for (unsigned int i = 0; i < depth; ++i) { + writer.write(indent); + } + } - switch (node.type()) - { - case node_document: - { - for (xml_node n = node.first_child(); n; n = n.next_sibling()) + switch (node.type()) { + case node_document: { + for (xml_node n = node.first_child(); n; n = n.next_sibling()) { node_output(writer, n, indent, flags, depth); + } break; } - case node_element: - { + case node_element: { const char_t* name = node.name()[0] ? node.name() : default_name; writer.write('<'); @@ -3101,46 +3103,46 @@ PUGI__NS_BEGIN node_output_attributes(writer, node, flags); - if (flags & format_raw) - { - if (!node.first_child()) + if (flags & format_raw) { + if (!node.first_child()) { writer.write(' ', '/', '>'); - else - { + } else { writer.write('>'); - for (xml_node n = node.first_child(); n; n = n.next_sibling()) + for (xml_node n = node.first_child(); n; n = n.next_sibling()) { node_output(writer, n, indent, flags, depth + 1); + } writer.write('<', '/'); writer.write(name); writer.write('>'); } - } - else if (!node.first_child()) + } else if (!node.first_child()) { writer.write(' ', '/', '>', '\n'); - else if (node.first_child() == node.last_child() && (node.first_child().type() == node_pcdata || node.first_child().type() == node_cdata)) - { + } else if (node.first_child() == node.last_child() && (node.first_child().type() == node_pcdata || node.first_child().type() == node_cdata)) { writer.write('>'); - if (node.first_child().type() == node_pcdata) + if (node.first_child().type() == node_pcdata) { text_output(writer, node.first_child().value(), ctx_special_pcdata, flags); - else + } else { text_output_cdata(writer, node.first_child().value()); + } writer.write('<', '/'); writer.write(name); writer.write('>', '\n'); - } - else - { + } else { writer.write('>', '\n'); - for (xml_node n = node.first_child(); n; n = n.next_sibling()) + for (xml_node n = node.first_child(); n; n = n.next_sibling()) { node_output(writer, n, indent, flags, depth + 1); + } - if ((flags & format_indent) != 0 && (flags & format_raw) == 0) - for (unsigned int i = 0; i < depth; ++i) writer.write(indent); + if ((flags & format_indent) != 0 && (flags & format_raw) == 0) { + for (unsigned int i = 0; i < depth; ++i) { + writer.write(indent); + } + } writer.write('<', '/'); writer.write(name); @@ -3152,19 +3154,25 @@ PUGI__NS_BEGIN case node_pcdata: text_output(writer, node.value(), ctx_special_pcdata, flags); - if ((flags & format_raw) == 0) writer.write('\n'); + if ((flags & format_raw) == 0) { + writer.write('\n'); + } break; case node_cdata: text_output_cdata(writer, node.value()); - if ((flags & format_raw) == 0) writer.write('\n'); + if ((flags & format_raw) == 0) { + writer.write('\n'); + } break; case node_comment: writer.write('<', '!', '-', '-'); writer.write(node.value()); writer.write('-', '-', '>'); - if ((flags & format_raw) == 0) writer.write('\n'); + if ((flags & format_raw) == 0) { + writer.write('\n'); + } break; case node_pi: @@ -3172,77 +3180,83 @@ PUGI__NS_BEGIN writer.write('<', '?'); writer.write(node.name()[0] ? node.name() : default_name); - if (node.type() == node_declaration) - { + if (node.type() == node_declaration) { node_output_attributes(writer, node, flags); - } - else if (node.value()[0]) - { + } else if (node.value()[0]) { writer.write(' '); writer.write(node.value()); } writer.write('?', '>'); - if ((flags & format_raw) == 0) writer.write('\n'); + if ((flags & format_raw) == 0) { + writer.write('\n'); + } break; case node_doctype: writer.write('<', '!', 'D', 'O', 'C'); writer.write('T', 'Y', 'P', 'E'); - if (node.value()[0]) - { + if (node.value()[0]) { writer.write(' '); writer.write(node.value()); } writer.write('>'); - if ((flags & format_raw) == 0) writer.write('\n'); + if ((flags & format_raw) == 0) { + writer.write('\n'); + } break; default: assert(!"Invalid node type"); - } } +} - inline bool has_declaration(const xml_node& node) - { - for (xml_node child = node.first_child(); child; child = child.next_sibling()) - { - xml_node_type type = child.type(); +inline bool has_declaration(const xml_node& node) { + for (xml_node child = node.first_child(); child; child = child.next_sibling()) { + xml_node_type type = child.type(); - if (type == node_declaration) return true; - if (type == node_element) return false; + if (type == node_declaration) { + return true; + } + if (type == node_element) { + return false; } - - return false; } - inline bool allow_insert_child(xml_node_type parent, xml_node_type child) - { - if (parent != node_document && parent != node_element) return false; - if (child == node_document || child == node_null) return false; - if (parent != node_document && (child == node_declaration || child == node_doctype)) return false; + return false; +} - return true; +inline bool allow_insert_child(xml_node_type parent, xml_node_type child) { + if (parent != node_document && parent != node_element) { + return false; + } + if (child == node_document || child == node_null) { + return false; + } + if (parent != node_document && (child == node_declaration || child == node_doctype)) { + return false; } - PUGI__FN void recursive_copy_skip(xml_node& dest, const xml_node& source, const xml_node& skip) - { - assert(dest.type() == source.type()); + return true; +} - switch (source.type()) - { - case node_element: - { +PUGI__FN void recursive_copy_skip(xml_node& dest, const xml_node& source, const xml_node& skip) { + assert(dest.type() == source.type()); + + switch (source.type()) { + case node_element: { dest.set_name(source.name()); - for (xml_attribute a = source.first_attribute(); a; a = a.next_attribute()) + for (xml_attribute a = source.first_attribute(); a; a = a.next_attribute()) { dest.append_attribute(a.name()).set_value(a.value()); + } - for (xml_node c = source.first_child(); c; c = c.next_sibling()) - { - if (c == skip) continue; + for (xml_node c = source.first_child(); c; c = c.next_sibling()) { + if (c == skip) { + continue; + } xml_node cc = dest.append_child(c.type()); assert(cc); @@ -3265,923 +3279,907 @@ PUGI__NS_BEGIN dest.set_value(source.value()); break; - case node_declaration: - { + case node_declaration: { dest.set_name(source.name()); - for (xml_attribute a = source.first_attribute(); a; a = a.next_attribute()) + for (xml_attribute a = source.first_attribute(); a; a = a.next_attribute()) { dest.append_attribute(a.name()).set_value(a.value()); + } break; } default: assert(!"Invalid node type"); - } } +} - inline bool is_text_node(xml_node_struct* node) - { - xml_node_type type = static_cast((node->header & impl::xml_memory_page_type_mask) + 1); +inline bool is_text_node(xml_node_struct* node) { + xml_node_type type = static_cast((node->header & impl::xml_memory_page_type_mask) + 1); + + return type == node_pcdata || type == node_cdata; +} - return type == node_pcdata || type == node_cdata; +// get value with conversion functions +PUGI__FN unsigned short get_value_ushort(const char_t* value, unsigned short def) { + if (!value) { + return def; } - // get value with conversion functions - PUGI__FN unsigned short get_value_ushort(const char_t* value, unsigned short def) - { - if (!value) return def; - #ifdef PUGIXML_WCHAR_MODE - return static_cast(wcstoul(value, 0, 10)); + return static_cast(wcstoul(value, 0, 10)); #else - return static_cast(strtoul(value, 0, 10)); + return static_cast(strtoul(value, 0, 10)); #endif - } +} - PUGI__FN int get_value_int(const char_t* value, int def) - { - if (!value) return def; +PUGI__FN int get_value_int(const char_t* value, int def) { + if (!value) { + return def; + } #ifdef PUGIXML_WCHAR_MODE - return static_cast(wcstol(value, 0, 10)); + return static_cast(wcstol(value, 0, 10)); #else - return static_cast(strtol(value, 0, 10)); + return static_cast(strtol(value, 0, 10)); #endif - } +} - PUGI__FN unsigned int get_value_uint(const char_t* value, unsigned int def) - { - if (!value) return def; +PUGI__FN unsigned int get_value_uint(const char_t* value, unsigned int def) { + if (!value) { + return def; + } #ifdef PUGIXML_WCHAR_MODE - return static_cast(wcstoul(value, 0, 10)); + return static_cast(wcstoul(value, 0, 10)); #else - return static_cast(strtoul(value, 0, 10)); + return static_cast(strtoul(value, 0, 10)); #endif - } +} - PUGI__FN double get_value_double(const char_t* value, double def) - { - if (!value) return def; +PUGI__FN double get_value_double(const char_t* value, double def) { + if (!value) { + return def; + } #ifdef PUGIXML_WCHAR_MODE - return wcstod(value, 0); + return wcstod(value, 0); #else - return strtod(value, 0); + return strtod(value, 0); #endif - } +} - PUGI__FN float get_value_float(const char_t* value, float def) - { - if (!value) return def; +PUGI__FN float get_value_float(const char_t* value, float def) { + if (!value) { + return def; + } #ifdef PUGIXML_WCHAR_MODE - return static_cast(wcstod(value, 0)); + return static_cast(wcstod(value, 0)); #else - return static_cast(strtod(value, 0)); + return static_cast(strtod(value, 0)); #endif - } +} - PUGI__FN bool get_value_bool(const char_t* value, bool def) - { - if (!value) return def; +PUGI__FN bool get_value_bool(const char_t* value, bool def) { + if (!value) { + return def; + } - // only look at first char - char_t first = *value; + // only look at first char + char_t first = *value; - // 1*, t* (true), T* (True), y* (yes), Y* (YES) - return (first == '1' || first == 't' || first == 'T' || first == 'y' || first == 'Y'); - } + // 1*, t* (true), T* (True), y* (yes), Y* (YES) + return (first == '1' || first == 't' || first == 'T' || first == 'y' || first == 'Y'); +} - // set value with conversion functions - PUGI__FN bool set_value_buffer(char_t*& dest, uintptr_t& header, uintptr_t header_mask, char (&buf)[128]) - { +// set value with conversion functions +PUGI__FN bool set_value_buffer(char_t*& dest, uintptr_t& header, uintptr_t header_mask, char (&buf)[128]) { #ifdef PUGIXML_WCHAR_MODE - char_t wbuf[128]; - impl::widen_ascii(wbuf, buf); + char_t wbuf[128]; + impl::widen_ascii(wbuf, buf); - return strcpy_insitu(dest, header, header_mask, wbuf); + return strcpy_insitu(dest, header, header_mask, wbuf); #else - return strcpy_insitu(dest, header, header_mask, buf); + return strcpy_insitu(dest, header, header_mask, buf); #endif - } +} - PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, int value) - { - char buf[128]; - sprintf(buf, "%d", value); +PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, int value) { + char buf[128]; + sprintf(buf, "%d", value); - return set_value_buffer(dest, header, header_mask, buf); - } + return set_value_buffer(dest, header, header_mask, buf); +} - PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, unsigned int value) - { - char buf[128]; - sprintf(buf, "%u", value); +PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, unsigned int value) { + char buf[128]; + sprintf(buf, "%u", value); - return set_value_buffer(dest, header, header_mask, buf); - } + return set_value_buffer(dest, header, header_mask, buf); +} - PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, double value) - { - char buf[128]; - sprintf(buf, "%g", value); +PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, double value) { + char buf[128]; + sprintf(buf, "%g", value); - return set_value_buffer(dest, header, header_mask, buf); - } + return set_value_buffer(dest, header, header_mask, buf); +} - PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, bool value) - { - return strcpy_insitu(dest, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); - } +PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, bool value) { + return strcpy_insitu(dest, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); +} - // we need to get length of entire file to load it in memory; the only (relatively) sane way to do it is via seek/tell trick - PUGI__FN xml_parse_status get_file_size(FILE* file, size_t& out_result) - { +// we need to get length of entire file to load it in memory; the only (relatively) sane way to do it is via seek/tell trick +PUGI__FN xml_parse_status get_file_size(FILE* file, size_t& out_result) { #if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE) - // there are 64-bit versions of fseek/ftell, let's use them - typedef __int64 length_type; + // there are 64-bit versions of fseek/ftell, let's use them + typedef __int64 length_type; - _fseeki64(file, 0, SEEK_END); - length_type length = _ftelli64(file); - _fseeki64(file, 0, SEEK_SET); + _fseeki64(file, 0, SEEK_END); + length_type length = _ftelli64(file); + _fseeki64(file, 0, SEEK_SET); #elif defined(__MINGW32__) && !defined(__NO_MINGW_LFS) && !defined(__STRICT_ANSI__) - // there are 64-bit versions of fseek/ftell, let's use them - typedef off64_t length_type; + // there are 64-bit versions of fseek/ftell, let's use them + typedef off64_t length_type; - fseeko64(file, 0, SEEK_END); - length_type length = ftello64(file); - fseeko64(file, 0, SEEK_SET); + fseeko64(file, 0, SEEK_END); + length_type length = ftello64(file); + fseeko64(file, 0, SEEK_SET); #else - // if this is a 32-bit OS, long is enough; if this is a unix system, long is 64-bit, which is enough; otherwise we can't do anything anyway. - typedef long length_type; + // if this is a 32-bit OS, long is enough; if this is a unix system, long is 64-bit, which is enough; otherwise we can't do anything anyway. + typedef long length_type; - fseek(file, 0, SEEK_END); - length_type length = ftell(file); - fseek(file, 0, SEEK_SET); + fseek(file, 0, SEEK_END); + length_type length = ftell(file); + fseek(file, 0, SEEK_SET); #endif - // check for I/O errors - if (length < 0) return status_io_error; - - // check for overflow - size_t result = static_cast(length); - - if (static_cast(result) != length) return status_out_of_memory; + // check for I/O errors + if (length < 0) { + return status_io_error; + } - // finalize - out_result = result; + // check for overflow + size_t result = static_cast(length); - return status_ok; + if (static_cast(result) != length) { + return status_out_of_memory; } - PUGI__FN xml_parse_result load_file_impl(xml_document& doc, FILE* file, unsigned int options, xml_encoding encoding) - { - if (!file) return make_parse_result(status_file_not_found); - - // get file size (can result in I/O errors) - size_t size = 0; - xml_parse_status size_status = get_file_size(file, size); + // finalize + out_result = result; - if (size_status != status_ok) - { - fclose(file); - return make_parse_result(size_status); - } + return status_ok; +} - // allocate buffer for the whole file - char* contents = static_cast(xml_memory::allocate(size > 0 ? size : 1)); +PUGI__FN xml_parse_result load_file_impl(xml_document& doc, FILE* file, unsigned int options, xml_encoding encoding) { + if (!file) { + return make_parse_result(status_file_not_found); + } - if (!contents) - { - fclose(file); - return make_parse_result(status_out_of_memory); - } + // get file size (can result in I/O errors) + size_t size = 0; + xml_parse_status size_status = get_file_size(file, size); - // read file in memory - size_t read_size = fread(contents, 1, size, file); + if (size_status != status_ok) { fclose(file); + return make_parse_result(size_status); + } - if (read_size != size) - { - xml_memory::deallocate(contents); - return make_parse_result(status_io_error); - } + // allocate buffer for the whole file + char* contents = static_cast(xml_memory::allocate(size > 0 ? size : 1)); - return doc.load_buffer_inplace_own(contents, size, options, encoding); + if (!contents) { + fclose(file); + return make_parse_result(status_out_of_memory); } -#ifndef PUGIXML_NO_STL - template struct xml_stream_chunk - { - static xml_stream_chunk* create() - { - void* memory = xml_memory::allocate(sizeof(xml_stream_chunk)); + // read file in memory + size_t read_size = fread(contents, 1, size, file); + fclose(file); - return new (memory) xml_stream_chunk(); - } + if (read_size != size) { + xml_memory::deallocate(contents); + return make_parse_result(status_io_error); + } - static void destroy(void* ptr) - { - xml_stream_chunk* chunk = static_cast(ptr); + return doc.load_buffer_inplace_own(contents, size, options, encoding); +} - // free chunk chain - while (chunk) - { - xml_stream_chunk* next = chunk->next; - xml_memory::deallocate(chunk); - chunk = next; - } - } + #ifndef PUGIXML_NO_STL +template +struct xml_stream_chunk { + static xml_stream_chunk* create() { + void* memory = xml_memory::allocate(sizeof(xml_stream_chunk)); - xml_stream_chunk(): next(0), size(0) - { - } + return new (memory) xml_stream_chunk(); + } - xml_stream_chunk* next; - size_t size; + static void destroy(void* ptr) { + xml_stream_chunk* chunk = static_cast(ptr); - T data[xml_memory_page_size / sizeof(T)]; - }; + // free chunk chain + while (chunk) { + xml_stream_chunk* next = chunk->next; + xml_memory::deallocate(chunk); + chunk = next; + } + } - template PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream& stream, void** out_buffer, size_t* out_size) - { - buffer_holder chunks(0, xml_stream_chunk::destroy); + xml_stream_chunk() : + next(0), size(0) { + } - // read file to a chunk list - size_t total = 0; - xml_stream_chunk* last = 0; + xml_stream_chunk* next; + size_t size; - while (!stream.eof()) - { - // allocate new chunk - xml_stream_chunk* chunk = xml_stream_chunk::create(); - if (!chunk) return status_out_of_memory; + T data[xml_memory_page_size / sizeof(T)]; +}; - // append chunk to list - if (last) last = last->next = chunk; - else chunks.data = last = chunk; +template +PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream& stream, void** out_buffer, size_t* out_size) { + buffer_holder chunks(0, xml_stream_chunk::destroy); - // read data to chunk - stream.read(chunk->data, static_cast(sizeof(chunk->data) / sizeof(T))); - chunk->size = static_cast(stream.gcount()) * sizeof(T); + // read file to a chunk list + size_t total = 0; + xml_stream_chunk* last = 0; - // read may set failbit | eofbit in case gcount() is less than read length, so check for other I/O errors - if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; + while (!stream.eof()) { + // allocate new chunk + xml_stream_chunk* chunk = xml_stream_chunk::create(); + if (!chunk) { + return status_out_of_memory; + } - // guard against huge files (chunk size is small enough to make this overflow check work) - if (total + chunk->size < total) return status_out_of_memory; - total += chunk->size; + // append chunk to list + if (last) { + last = last->next = chunk; + } else { + chunks.data = last = chunk; } - // copy chunk list to a contiguous buffer - char* buffer = static_cast(xml_memory::allocate(total)); - if (!buffer) return status_out_of_memory; + // read data to chunk + stream.read(chunk->data, static_cast(sizeof(chunk->data) / sizeof(T))); + chunk->size = static_cast(stream.gcount()) * sizeof(T); - char* write = buffer; + // read may set failbit | eofbit in case gcount() is less than read length, so check for other I/O errors + if (stream.bad() || (!stream.eof() && stream.fail())) { + return status_io_error; + } - for (xml_stream_chunk* chunk = static_cast*>(chunks.data); chunk; chunk = chunk->next) - { - assert(write + chunk->size <= buffer + total); - memcpy(write, chunk->data, chunk->size); - write += chunk->size; + // guard against huge files (chunk size is small enough to make this overflow check work) + if (total + chunk->size < total) { + return status_out_of_memory; } + total += chunk->size; + } - assert(write == buffer + total); + // copy chunk list to a contiguous buffer + char* buffer = static_cast(xml_memory::allocate(total)); + if (!buffer) { + return status_out_of_memory; + } - // return buffer - *out_buffer = buffer; - *out_size = total; + char* write = buffer; - return status_ok; + for (xml_stream_chunk* chunk = static_cast*>(chunks.data); chunk; chunk = chunk->next) { + assert(write + chunk->size <= buffer + total); + memcpy(write, chunk->data, chunk->size); + write += chunk->size; } - template PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream& stream, void** out_buffer, size_t* out_size) - { - // get length of remaining data in stream - typename std::basic_istream::pos_type pos = stream.tellg(); - stream.seekg(0, std::ios::end); - std::streamoff length = stream.tellg() - pos; - stream.seekg(pos); + assert(write == buffer + total); - if (stream.fail() || pos < 0) return status_io_error; + // return buffer + *out_buffer = buffer; + *out_size = total; - // guard against huge files - size_t read_length = static_cast(length); + return status_ok; +} - if (static_cast(read_length) != length || length < 0) return status_out_of_memory; +template +PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream& stream, void** out_buffer, size_t* out_size) { + // get length of remaining data in stream + typename std::basic_istream::pos_type pos = stream.tellg(); + stream.seekg(0, std::ios::end); + std::streamoff length = stream.tellg() - pos; + stream.seekg(pos); - // read stream data into memory (guard against stream exceptions with buffer holder) - buffer_holder buffer(xml_memory::allocate((read_length > 0 ? read_length : 1) * sizeof(T)), xml_memory::deallocate); - if (!buffer.data) return status_out_of_memory; + if (stream.fail() || pos < 0) { + return status_io_error; + } - stream.read(static_cast(buffer.data), static_cast(read_length)); + // guard against huge files + size_t read_length = static_cast(length); - // read may set failbit | eofbit in case gcount() is less than read_length (i.e. line ending conversion), so check for other I/O errors - if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; + if (static_cast(read_length) != length || length < 0) { + return status_out_of_memory; + } - // return buffer - size_t actual_length = static_cast(stream.gcount()); - assert(actual_length <= read_length); + // read stream data into memory (guard against stream exceptions with buffer holder) + buffer_holder buffer(xml_memory::allocate((read_length > 0 ? read_length : 1) * sizeof(T)), xml_memory::deallocate); + if (!buffer.data) { + return status_out_of_memory; + } - *out_buffer = buffer.release(); - *out_size = actual_length * sizeof(T); + stream.read(static_cast(buffer.data), static_cast(read_length)); - return status_ok; + // read may set failbit | eofbit in case gcount() is less than read_length (i.e. line ending conversion), so check for other I/O errors + if (stream.bad() || (!stream.eof() && stream.fail())) { + return status_io_error; } - template PUGI__FN xml_parse_result load_stream_impl(xml_document& doc, std::basic_istream& stream, unsigned int options, xml_encoding encoding) - { - void* buffer = 0; - size_t size = 0; + // return buffer + size_t actual_length = static_cast(stream.gcount()); + assert(actual_length <= read_length); - // load stream to memory (using seek-based implementation if possible, since it's faster and takes less memory) - xml_parse_status status = (stream.tellg() < 0) ? load_stream_data_noseek(stream, &buffer, &size) : load_stream_data_seek(stream, &buffer, &size); - if (status != status_ok) return make_parse_result(status); + *out_buffer = buffer.release(); + *out_size = actual_length * sizeof(T); - return doc.load_buffer_inplace_own(buffer, size, options, encoding); - } -#endif + return status_ok; +} -#if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) || (defined(__MINGW32__) && !defined(__STRICT_ANSI__)) - PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) - { - return _wfopen(path, mode); +template +PUGI__FN xml_parse_result load_stream_impl(xml_document& doc, std::basic_istream& stream, unsigned int options, xml_encoding encoding) { + void* buffer = 0; + size_t size = 0; + + // load stream to memory (using seek-based implementation if possible, since it's faster and takes less memory) + xml_parse_status status = (stream.tellg() < 0) ? load_stream_data_noseek(stream, &buffer, &size) : load_stream_data_seek(stream, &buffer, &size); + if (status != status_ok) { + return make_parse_result(status); } -#else - PUGI__FN char* convert_path_heap(const wchar_t* str) - { - assert(str); - // first pass: get length in utf8 characters - size_t length = wcslen(str); - size_t size = as_utf8_begin(str, length); + return doc.load_buffer_inplace_own(buffer, size, options, encoding); +} + #endif - // allocate resulting string - char* result = static_cast(xml_memory::allocate(size + 1)); - if (!result) return 0; + #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) || (defined(__MINGW32__) && !defined(__STRICT_ANSI__)) +PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) { + return _wfopen(path, mode); +} + #else +PUGI__FN char* convert_path_heap(const wchar_t* str) { + assert(str); - // second pass: convert to utf8 - as_utf8_end(result, size, str, length); + // first pass: get length in utf8 characters + size_t length = wcslen(str); + size_t size = as_utf8_begin(str, length); - return result; + // allocate resulting string + char* result = static_cast(xml_memory::allocate(size + 1)); + if (!result) { + return 0; } - PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) - { - // there is no standard function to open wide paths, so our best bet is to try utf8 path - char* path_utf8 = convert_path_heap(path); - if (!path_utf8) return 0; - - // convert mode to ASCII (we mirror _wfopen interface) - char mode_ascii[4] = {0}; - for (size_t i = 0; mode[i]; ++i) mode_ascii[i] = static_cast(mode[i]); - - // try to open the utf8 path - FILE* result = fopen(path_utf8, mode_ascii); + // second pass: convert to utf8 + as_utf8_end(result, size, str, length); - // free dummy buffer - xml_memory::deallocate(path_utf8); + return result; +} - return result; +PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) { + // there is no standard function to open wide paths, so our best bet is to try utf8 path + char* path_utf8 = convert_path_heap(path); + if (!path_utf8) { + return 0; } -#endif - PUGI__FN bool save_file_impl(const xml_document& doc, FILE* file, const char_t* indent, unsigned int flags, xml_encoding encoding) - { - if (!file) return false; + // convert mode to ASCII (we mirror _wfopen interface) + char mode_ascii[4] = { 0 }; + for (size_t i = 0; mode[i]; ++i) { + mode_ascii[i] = static_cast(mode[i]); + } - xml_writer_file writer(file); - doc.save(writer, indent, flags, encoding); + // try to open the utf8 path + FILE* result = fopen(path_utf8, mode_ascii); - int result = ferror(file); + // free dummy buffer + xml_memory::deallocate(path_utf8); - fclose(file); + return result; +} + #endif - return result == 0; +PUGI__FN bool save_file_impl(const xml_document& doc, FILE* file, const char_t* indent, unsigned int flags, xml_encoding encoding) { + if (!file) { + return false; } + + xml_writer_file writer(file); + doc.save(writer, indent, flags, encoding); + + int result = ferror(file); + + fclose(file); + + return result == 0; +} PUGI__NS_END -namespace pugi -{ - PUGI__FN xml_writer_file::xml_writer_file(void* file_): file(file_) - { +namespace pugi { + PUGI__FN xml_writer_file::xml_writer_file(void* file_) : + file(file_) { } - PUGI__FN void xml_writer_file::write(const void* data, size_t size) - { + PUGI__FN void xml_writer_file::write(const void* data, size_t size) { size_t result = fwrite(data, 1, size, static_cast(file)); (void)!result; // unfortunately we can't do proper error handling here } -#ifndef PUGIXML_NO_STL - PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(&stream), wide_stream(0) - { + #ifndef PUGIXML_NO_STL + PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream>& stream) : + narrow_stream(&stream), wide_stream(0) { } - PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(0), wide_stream(&stream) - { + PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream>& stream) : + narrow_stream(0), wide_stream(&stream) { } - PUGI__FN void xml_writer_stream::write(const void* data, size_t size) - { - if (narrow_stream) - { + PUGI__FN void xml_writer_stream::write(const void* data, size_t size) { + if (narrow_stream) { assert(!wide_stream); narrow_stream->write(reinterpret_cast(data), static_cast(size)); - } - else - { + } else { assert(wide_stream); assert(size % sizeof(wchar_t) == 0); wide_stream->write(reinterpret_cast(data), static_cast(size / sizeof(wchar_t))); } } -#endif + #endif - PUGI__FN xml_tree_walker::xml_tree_walker(): _depth(0) - { + PUGI__FN xml_tree_walker::xml_tree_walker() : + _depth(0) { } - PUGI__FN xml_tree_walker::~xml_tree_walker() - { + PUGI__FN xml_tree_walker::~xml_tree_walker() { } - PUGI__FN int xml_tree_walker::depth() const - { + PUGI__FN int xml_tree_walker::depth() const { return _depth; } - PUGI__FN bool xml_tree_walker::begin(xml_node&) - { + PUGI__FN bool xml_tree_walker::begin(xml_node&) { return true; } - PUGI__FN bool xml_tree_walker::end(xml_node&) - { + PUGI__FN bool xml_tree_walker::end(xml_node&) { return true; } - PUGI__FN xml_attribute::xml_attribute(): _attr(0) - { + PUGI__FN xml_attribute::xml_attribute() : + _attr(0) { } - PUGI__FN xml_attribute::xml_attribute(xml_attribute_struct* attr): _attr(attr) - { + PUGI__FN xml_attribute::xml_attribute(xml_attribute_struct* attr) : + _attr(attr) { } - PUGI__FN static void unspecified_bool_xml_attribute(xml_attribute***) - { + PUGI__FN static void unspecified_bool_xml_attribute(xml_attribute***) { } - PUGI__FN xml_attribute::operator xml_attribute::unspecified_bool_type() const - { + PUGI__FN xml_attribute::operator xml_attribute::unspecified_bool_type() const { return _attr ? unspecified_bool_xml_attribute : 0; } - PUGI__FN bool xml_attribute::operator!() const - { + PUGI__FN bool xml_attribute::operator!() const { return !_attr; } - PUGI__FN bool xml_attribute::operator==(const xml_attribute& r) const - { + PUGI__FN bool xml_attribute::operator==(const xml_attribute& r) const { return (_attr == r._attr); } - PUGI__FN bool xml_attribute::operator!=(const xml_attribute& r) const - { + PUGI__FN bool xml_attribute::operator!=(const xml_attribute& r) const { return (_attr != r._attr); } - PUGI__FN bool xml_attribute::operator<(const xml_attribute& r) const - { + PUGI__FN bool xml_attribute::operator<(const xml_attribute& r) const { return (_attr < r._attr); } - PUGI__FN bool xml_attribute::operator>(const xml_attribute& r) const - { + PUGI__FN bool xml_attribute::operator>(const xml_attribute& r) const { return (_attr > r._attr); } - PUGI__FN bool xml_attribute::operator<=(const xml_attribute& r) const - { + PUGI__FN bool xml_attribute::operator<=(const xml_attribute& r) const { return (_attr <= r._attr); } - PUGI__FN bool xml_attribute::operator>=(const xml_attribute& r) const - { + PUGI__FN bool xml_attribute::operator>=(const xml_attribute& r) const { return (_attr >= r._attr); } - PUGI__FN xml_attribute xml_attribute::next_attribute() const - { + PUGI__FN xml_attribute xml_attribute::next_attribute() const { return _attr ? xml_attribute(_attr->next_attribute) : xml_attribute(); } - PUGI__FN xml_attribute xml_attribute::previous_attribute() const - { + PUGI__FN xml_attribute xml_attribute::previous_attribute() const { return _attr && _attr->prev_attribute_c->next_attribute ? xml_attribute(_attr->prev_attribute_c) : xml_attribute(); } - PUGI__FN const char_t* xml_attribute::as_string(const char_t* def) const - { + PUGI__FN const char_t* xml_attribute::as_string(const char_t* def) const { return (_attr && _attr->value) ? _attr->value : def; } - PUGI__FN unsigned short xml_attribute::as_ushort(unsigned short def) const - { + PUGI__FN unsigned short xml_attribute::as_ushort(unsigned short def) const { return impl::get_value_ushort(_attr ? _attr->value : 0, def); } - PUGI__FN int xml_attribute::as_int(int def) const - { + PUGI__FN int xml_attribute::as_int(int def) const { return impl::get_value_int(_attr ? _attr->value : 0, def); } - PUGI__FN unsigned int xml_attribute::as_uint(unsigned int def) const - { + PUGI__FN unsigned int xml_attribute::as_uint(unsigned int def) const { return impl::get_value_uint(_attr ? _attr->value : 0, def); } - PUGI__FN double xml_attribute::as_double(double def) const - { + PUGI__FN double xml_attribute::as_double(double def) const { return impl::get_value_double(_attr ? _attr->value : 0, def); } - PUGI__FN float xml_attribute::as_float(float def) const - { + PUGI__FN float xml_attribute::as_float(float def) const { return impl::get_value_float(_attr ? _attr->value : 0, def); } - PUGI__FN bool xml_attribute::as_bool(bool def) const - { + PUGI__FN bool xml_attribute::as_bool(bool def) const { return impl::get_value_bool(_attr ? _attr->value : 0, def); } - PUGI__FN bool xml_attribute::empty() const - { + PUGI__FN bool xml_attribute::empty() const { return !_attr; } - PUGI__FN const char_t* xml_attribute::name() const - { + PUGI__FN const char_t* xml_attribute::name() const { return (_attr && _attr->name) ? _attr->name : PUGIXML_TEXT(""); } - PUGI__FN const char_t* xml_attribute::value() const - { + PUGI__FN const char_t* xml_attribute::value() const { return (_attr && _attr->value) ? _attr->value : PUGIXML_TEXT(""); } - PUGI__FN size_t xml_attribute::hash_value() const - { + PUGI__FN size_t xml_attribute::hash_value() const { return static_cast(reinterpret_cast(_attr) / sizeof(xml_attribute_struct)); } - PUGI__FN xml_attribute_struct* xml_attribute::internal_object() const - { + PUGI__FN xml_attribute_struct* xml_attribute::internal_object() const { return _attr; } - PUGI__FN xml_attribute& xml_attribute::operator=(const char_t* rhs) - { + PUGI__FN xml_attribute& xml_attribute::operator=(const char_t* rhs) { set_value(rhs); return *this; } - PUGI__FN xml_attribute& xml_attribute::operator=(int rhs) - { + PUGI__FN xml_attribute& xml_attribute::operator=(int rhs) { set_value(rhs); return *this; } - PUGI__FN xml_attribute& xml_attribute::operator=(unsigned int rhs) - { + PUGI__FN xml_attribute& xml_attribute::operator=(unsigned int rhs) { set_value(rhs); return *this; } - PUGI__FN xml_attribute& xml_attribute::operator=(double rhs) - { + PUGI__FN xml_attribute& xml_attribute::operator=(double rhs) { set_value(rhs); return *this; } - PUGI__FN xml_attribute& xml_attribute::operator=(bool rhs) - { + PUGI__FN xml_attribute& xml_attribute::operator=(bool rhs) { set_value(rhs); return *this; } - PUGI__FN bool xml_attribute::set_name(const char_t* rhs) - { - if (!_attr) return false; + PUGI__FN bool xml_attribute::set_name(const char_t* rhs) { + if (!_attr) { + return false; + } return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs); } - PUGI__FN bool xml_attribute::set_value(const char_t* rhs) - { - if (!_attr) return false; + PUGI__FN bool xml_attribute::set_value(const char_t* rhs) { + if (!_attr) { + return false; + } return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); } - PUGI__FN bool xml_attribute::set_value(int rhs) - { - if (!_attr) return false; + PUGI__FN bool xml_attribute::set_value(int rhs) { + if (!_attr) { + return false; + } return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); } - PUGI__FN bool xml_attribute::set_value(unsigned int rhs) - { - if (!_attr) return false; + PUGI__FN bool xml_attribute::set_value(unsigned int rhs) { + if (!_attr) { + return false; + } return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); } - PUGI__FN bool xml_attribute::set_value(double rhs) - { - if (!_attr) return false; + PUGI__FN bool xml_attribute::set_value(double rhs) { + if (!_attr) { + return false; + } return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); } - PUGI__FN bool xml_attribute::set_value(bool rhs) - { - if (!_attr) return false; + PUGI__FN bool xml_attribute::set_value(bool rhs) { + if (!_attr) { + return false; + } return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); } -#ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xml_attribute& lhs, bool rhs) - { + #ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_attribute& lhs, bool rhs) { return (bool)lhs && rhs; } - PUGI__FN bool operator||(const xml_attribute& lhs, bool rhs) - { + PUGI__FN bool operator||(const xml_attribute& lhs, bool rhs) { return (bool)lhs || rhs; } -#endif + #endif - PUGI__FN xml_node::xml_node(): _root(0) - { + PUGI__FN xml_node::xml_node() : + _root(0) { } - PUGI__FN xml_node::xml_node(xml_node_struct* p): _root(p) - { + PUGI__FN xml_node::xml_node(xml_node_struct* p) : + _root(p) { } - PUGI__FN static void unspecified_bool_xml_node(xml_node***) - { + PUGI__FN static void unspecified_bool_xml_node(xml_node***) { } - PUGI__FN xml_node::operator xml_node::unspecified_bool_type() const - { + PUGI__FN xml_node::operator xml_node::unspecified_bool_type() const { return _root ? unspecified_bool_xml_node : 0; } - PUGI__FN bool xml_node::operator!() const - { + PUGI__FN bool xml_node::operator!() const { return !_root; } - PUGI__FN xml_node::iterator xml_node::begin() const - { + PUGI__FN xml_node::iterator xml_node::begin() const { return iterator(_root ? _root->first_child : 0, _root); } - PUGI__FN xml_node::iterator xml_node::end() const - { + PUGI__FN xml_node::iterator xml_node::end() const { return iterator(0, _root); } - PUGI__FN xml_node::attribute_iterator xml_node::attributes_begin() const - { + PUGI__FN xml_node::attribute_iterator xml_node::attributes_begin() const { return attribute_iterator(_root ? _root->first_attribute : 0, _root); } - PUGI__FN xml_node::attribute_iterator xml_node::attributes_end() const - { + PUGI__FN xml_node::attribute_iterator xml_node::attributes_end() const { return attribute_iterator(0, _root); } - PUGI__FN xml_object_range xml_node::children() const - { + PUGI__FN xml_object_range xml_node::children() const { return xml_object_range(begin(), end()); } - PUGI__FN xml_object_range xml_node::children(const char_t* name_) const - { + PUGI__FN xml_object_range xml_node::children(const char_t* name_) const { return xml_object_range(xml_named_node_iterator(child(name_), name_), xml_named_node_iterator()); } - PUGI__FN xml_object_range xml_node::attributes() const - { + PUGI__FN xml_object_range xml_node::attributes() const { return xml_object_range(attributes_begin(), attributes_end()); } - PUGI__FN bool xml_node::operator==(const xml_node& r) const - { + PUGI__FN bool xml_node::operator==(const xml_node& r) const { return (_root == r._root); } - PUGI__FN bool xml_node::operator!=(const xml_node& r) const - { + PUGI__FN bool xml_node::operator!=(const xml_node& r) const { return (_root != r._root); } - PUGI__FN bool xml_node::operator<(const xml_node& r) const - { + PUGI__FN bool xml_node::operator<(const xml_node& r) const { return (_root < r._root); } - PUGI__FN bool xml_node::operator>(const xml_node& r) const - { + PUGI__FN bool xml_node::operator>(const xml_node& r) const { return (_root > r._root); } - PUGI__FN bool xml_node::operator<=(const xml_node& r) const - { + PUGI__FN bool xml_node::operator<=(const xml_node& r) const { return (_root <= r._root); } - PUGI__FN bool xml_node::operator>=(const xml_node& r) const - { + PUGI__FN bool xml_node::operator>=(const xml_node& r) const { return (_root >= r._root); } - PUGI__FN bool xml_node::empty() const - { + PUGI__FN bool xml_node::empty() const { return !_root; } - PUGI__FN const char_t* xml_node::name() const - { + PUGI__FN const char_t* xml_node::name() const { return (_root && _root->name) ? _root->name : PUGIXML_TEXT(""); } - PUGI__FN xml_node_type xml_node::type() const - { + PUGI__FN xml_node_type xml_node::type() const { return _root ? static_cast((_root->header & impl::xml_memory_page_type_mask) + 1) : node_null; } - PUGI__FN const char_t* xml_node::value() const - { + PUGI__FN const char_t* xml_node::value() const { return (_root && _root->value) ? _root->value : PUGIXML_TEXT(""); } - PUGI__FN xml_node xml_node::child(const char_t* name_) const - { - if (!_root) return xml_node(); + PUGI__FN xml_node xml_node::child(const char_t* name_) const { + if (!_root) { + return xml_node(); + } - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) { + if (i->name && impl::strequal(name_, i->name)) { + return xml_node(i); + } + } return xml_node(); } - PUGI__FN xml_attribute xml_node::attribute(const char_t* name_) const - { - if (!_root) return xml_attribute(); + PUGI__FN xml_attribute xml_node::attribute(const char_t* name_) const { + if (!_root) { + return xml_attribute(); + } - for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) - if (i->name && impl::strequal(name_, i->name)) + for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) { + if (i->name && impl::strequal(name_, i->name)) { return xml_attribute(i); + } + } return xml_attribute(); } - PUGI__FN xml_node xml_node::next_sibling(const char_t* name_) const - { - if (!_root) return xml_node(); + PUGI__FN xml_node xml_node::next_sibling(const char_t* name_) const { + if (!_root) { + return xml_node(); + } - for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling) - if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling) { + if (i->name && impl::strequal(name_, i->name)) { + return xml_node(i); + } + } return xml_node(); } - PUGI__FN xml_node xml_node::next_sibling() const - { - if (!_root) return xml_node(); + PUGI__FN xml_node xml_node::next_sibling() const { + if (!_root) { + return xml_node(); + } - if (_root->next_sibling) return xml_node(_root->next_sibling); - else return xml_node(); + if (_root->next_sibling) { + return xml_node(_root->next_sibling); + } else { + return xml_node(); + } } - PUGI__FN xml_node xml_node::previous_sibling(const char_t* name_) const - { - if (!_root) return xml_node(); + PUGI__FN xml_node xml_node::previous_sibling(const char_t* name_) const { + if (!_root) { + return xml_node(); + } - for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c) - if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c) { + if (i->name && impl::strequal(name_, i->name)) { + return xml_node(i); + } + } return xml_node(); } - PUGI__FN xml_node xml_node::previous_sibling() const - { - if (!_root) return xml_node(); + PUGI__FN xml_node xml_node::previous_sibling() const { + if (!_root) { + return xml_node(); + } - if (_root->prev_sibling_c->next_sibling) return xml_node(_root->prev_sibling_c); - else return xml_node(); + if (_root->prev_sibling_c->next_sibling) { + return xml_node(_root->prev_sibling_c); + } else { + return xml_node(); + } } - PUGI__FN xml_node xml_node::parent() const - { + PUGI__FN xml_node xml_node::parent() const { return _root ? xml_node(_root->parent) : xml_node(); } - PUGI__FN xml_node xml_node::root() const - { - if (!_root) return xml_node(); + PUGI__FN xml_node xml_node::root() const { + if (!_root) { + return xml_node(); + } impl::xml_memory_page* page = reinterpret_cast(_root->header & impl::xml_memory_page_pointer_mask); return xml_node(static_cast(page->allocator)); } - PUGI__FN xml_text xml_node::text() const - { + PUGI__FN xml_text xml_node::text() const { return xml_text(_root); } - PUGI__FN const char_t* xml_node::child_value() const - { - if (!_root) return PUGIXML_TEXT(""); + PUGI__FN const char_t* xml_node::child_value() const { + if (!_root) { + return PUGIXML_TEXT(""); + } - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if (i->value && impl::is_text_node(i)) + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) { + if (i->value && impl::is_text_node(i)) { return i->value; + } + } return PUGIXML_TEXT(""); } - PUGI__FN const char_t* xml_node::child_value(const char_t* name_) const - { + PUGI__FN const char_t* xml_node::child_value(const char_t* name_) const { return child(name_).child_value(); } - PUGI__FN xml_attribute xml_node::first_attribute() const - { + PUGI__FN xml_attribute xml_node::first_attribute() const { return _root ? xml_attribute(_root->first_attribute) : xml_attribute(); } - PUGI__FN xml_attribute xml_node::last_attribute() const - { + PUGI__FN xml_attribute xml_node::last_attribute() const { return _root && _root->first_attribute ? xml_attribute(_root->first_attribute->prev_attribute_c) : xml_attribute(); } - PUGI__FN xml_node xml_node::first_child() const - { + PUGI__FN xml_node xml_node::first_child() const { return _root ? xml_node(_root->first_child) : xml_node(); } - PUGI__FN xml_node xml_node::last_child() const - { + PUGI__FN xml_node xml_node::last_child() const { return _root && _root->first_child ? xml_node(_root->first_child->prev_sibling_c) : xml_node(); - } - - PUGI__FN bool xml_node::set_name(const char_t* rhs) - { - switch (type()) - { - case node_pi: - case node_declaration: - case node_element: - return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs); + } - default: - return false; + PUGI__FN bool xml_node::set_name(const char_t* rhs) { + switch (type()) { + case node_pi: + case node_declaration: + case node_element: + return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs); + + default: + return false; } } - PUGI__FN bool xml_node::set_value(const char_t* rhs) - { - switch (type()) - { - case node_pi: - case node_cdata: - case node_pcdata: - case node_comment: - case node_doctype: - return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs); + PUGI__FN bool xml_node::set_value(const char_t* rhs) { + switch (type()) { + case node_pi: + case node_cdata: + case node_pcdata: + case node_comment: + case node_doctype: + return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs); - default: - return false; + default: + return false; } } - PUGI__FN xml_attribute xml_node::append_attribute(const char_t* name_) - { - if (type() != node_element && type() != node_declaration) return xml_attribute(); + PUGI__FN xml_attribute xml_node::append_attribute(const char_t* name_) { + if (type() != node_element && type() != node_declaration) { + return xml_attribute(); + } xml_attribute a(impl::append_attribute_ll(_root, impl::get_allocator(_root))); a.set_name(name_); @@ -4189,24 +4187,26 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::prepend_attribute(const char_t* name_) - { - if (type() != node_element && type() != node_declaration) return xml_attribute(); + PUGI__FN xml_attribute xml_node::prepend_attribute(const char_t* name_) { + if (type() != node_element && type() != node_declaration) { + return xml_attribute(); + } xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); - if (!a) return xml_attribute(); + if (!a) { + return xml_attribute(); + } a.set_name(name_); xml_attribute_struct* head = _root->first_attribute; - if (head) - { + if (head) { a._attr->prev_attribute_c = head->prev_attribute_c; head->prev_attribute_c = a._attr; - } - else + } else { a._attr->prev_attribute_c = a._attr; + } a._attr->next_attribute = head; _root->first_attribute = a._attr; @@ -4214,26 +4214,34 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::insert_attribute_before(const char_t* name_, const xml_attribute& attr) - { - if ((type() != node_element && type() != node_declaration) || attr.empty()) return xml_attribute(); + PUGI__FN xml_attribute xml_node::insert_attribute_before(const char_t* name_, const xml_attribute& attr) { + if ((type() != node_element && type() != node_declaration) || attr.empty()) { + return xml_attribute(); + } // check that attribute belongs to *this xml_attribute_struct* cur = attr._attr; - while (cur->prev_attribute_c->next_attribute) cur = cur->prev_attribute_c; + while (cur->prev_attribute_c->next_attribute) { + cur = cur->prev_attribute_c; + } - if (cur != _root->first_attribute) return xml_attribute(); + if (cur != _root->first_attribute) { + return xml_attribute(); + } xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); - if (!a) return xml_attribute(); + if (!a) { + return xml_attribute(); + } a.set_name(name_); - if (attr._attr->prev_attribute_c->next_attribute) + if (attr._attr->prev_attribute_c->next_attribute) { attr._attr->prev_attribute_c->next_attribute = a._attr; - else + } else { _root->first_attribute = a._attr; + } a._attr->prev_attribute_c = attr._attr->prev_attribute_c; a._attr->next_attribute = attr._attr; @@ -4242,26 +4250,34 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::insert_attribute_after(const char_t* name_, const xml_attribute& attr) - { - if ((type() != node_element && type() != node_declaration) || attr.empty()) return xml_attribute(); + PUGI__FN xml_attribute xml_node::insert_attribute_after(const char_t* name_, const xml_attribute& attr) { + if ((type() != node_element && type() != node_declaration) || attr.empty()) { + return xml_attribute(); + } // check that attribute belongs to *this xml_attribute_struct* cur = attr._attr; - while (cur->prev_attribute_c->next_attribute) cur = cur->prev_attribute_c; + while (cur->prev_attribute_c->next_attribute) { + cur = cur->prev_attribute_c; + } - if (cur != _root->first_attribute) return xml_attribute(); + if (cur != _root->first_attribute) { + return xml_attribute(); + } xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); - if (!a) return xml_attribute(); + if (!a) { + return xml_attribute(); + } a.set_name(name_); - if (attr._attr->next_attribute) + if (attr._attr->next_attribute) { attr._attr->next_attribute->prev_attribute_c = a._attr; - else + } else { _root->first_attribute->prev_attribute_c = a._attr; + } a._attr->next_attribute = attr._attr->next_attribute; a._attr->prev_attribute_c = attr._attr; @@ -4270,9 +4286,10 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::append_copy(const xml_attribute& proto) - { - if (!proto) return xml_attribute(); + PUGI__FN xml_attribute xml_node::append_copy(const xml_attribute& proto) { + if (!proto) { + return xml_attribute(); + } xml_attribute result = append_attribute(proto.name()); result.set_value(proto.value()); @@ -4280,9 +4297,10 @@ namespace pugi return result; } - PUGI__FN xml_attribute xml_node::prepend_copy(const xml_attribute& proto) - { - if (!proto) return xml_attribute(); + PUGI__FN xml_attribute xml_node::prepend_copy(const xml_attribute& proto) { + if (!proto) { + return xml_attribute(); + } xml_attribute result = prepend_attribute(proto.name()); result.set_value(proto.value()); @@ -4290,9 +4308,10 @@ namespace pugi return result; } - PUGI__FN xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr) - { - if (!proto) return xml_attribute(); + PUGI__FN xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr) { + if (!proto) { + return xml_attribute(); + } xml_attribute result = insert_attribute_after(proto.name(), attr); result.set_value(proto.value()); @@ -4300,9 +4319,10 @@ namespace pugi return result; } - PUGI__FN xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr) - { - if (!proto) return xml_attribute(); + PUGI__FN xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr) { + if (!proto) { + return xml_attribute(); + } xml_attribute result = insert_attribute_before(proto.name(), attr); result.set_value(proto.value()); @@ -4310,94 +4330,116 @@ namespace pugi return result; } - PUGI__FN xml_node xml_node::append_child(xml_node_type type_) - { - if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); + PUGI__FN xml_node xml_node::append_child(xml_node_type type_) { + if (!impl::allow_insert_child(this->type(), type_)) { + return xml_node(); + } xml_node n(impl::append_node(_root, impl::get_allocator(_root), type_)); - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + if (type_ == node_declaration) { + n.set_name(PUGIXML_TEXT("xml")); + } return n; } - PUGI__FN xml_node xml_node::prepend_child(xml_node_type type_) - { - if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); + PUGI__FN xml_node xml_node::prepend_child(xml_node_type type_) { + if (!impl::allow_insert_child(this->type(), type_)) { + return xml_node(); + } xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); - if (!n) return xml_node(); + if (!n) { + return xml_node(); + } n._root->parent = _root; xml_node_struct* head = _root->first_child; - if (head) - { + if (head) { n._root->prev_sibling_c = head->prev_sibling_c; head->prev_sibling_c = n._root; - } - else + } else { n._root->prev_sibling_c = n._root; + } n._root->next_sibling = head; _root->first_child = n._root; - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + if (type_ == node_declaration) { + n.set_name(PUGIXML_TEXT("xml")); + } return n; } - PUGI__FN xml_node xml_node::insert_child_before(xml_node_type type_, const xml_node& node) - { - if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); - if (!node._root || node._root->parent != _root) return xml_node(); + PUGI__FN xml_node xml_node::insert_child_before(xml_node_type type_, const xml_node& node) { + if (!impl::allow_insert_child(this->type(), type_)) { + return xml_node(); + } + if (!node._root || node._root->parent != _root) { + return xml_node(); + } xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); - if (!n) return xml_node(); + if (!n) { + return xml_node(); + } n._root->parent = _root; - if (node._root->prev_sibling_c->next_sibling) + if (node._root->prev_sibling_c->next_sibling) { node._root->prev_sibling_c->next_sibling = n._root; - else + } else { _root->first_child = n._root; + } n._root->prev_sibling_c = node._root->prev_sibling_c; n._root->next_sibling = node._root; node._root->prev_sibling_c = n._root; - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + if (type_ == node_declaration) { + n.set_name(PUGIXML_TEXT("xml")); + } return n; } - PUGI__FN xml_node xml_node::insert_child_after(xml_node_type type_, const xml_node& node) - { - if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); - if (!node._root || node._root->parent != _root) return xml_node(); + PUGI__FN xml_node xml_node::insert_child_after(xml_node_type type_, const xml_node& node) { + if (!impl::allow_insert_child(this->type(), type_)) { + return xml_node(); + } + if (!node._root || node._root->parent != _root) { + return xml_node(); + } xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); - if (!n) return xml_node(); + if (!n) { + return xml_node(); + } n._root->parent = _root; - if (node._root->next_sibling) + if (node._root->next_sibling) { node._root->next_sibling->prev_sibling_c = n._root; - else + } else { _root->first_child->prev_sibling_c = n._root; + } n._root->next_sibling = node._root->next_sibling; n._root->prev_sibling_c = node._root; node._root->next_sibling = n._root; - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + if (type_ == node_declaration) { + n.set_name(PUGIXML_TEXT("xml")); + } return n; } - PUGI__FN xml_node xml_node::append_child(const char_t* name_) - { + PUGI__FN xml_node xml_node::append_child(const char_t* name_) { xml_node result = append_child(node_element); result.set_name(name_); @@ -4405,8 +4447,7 @@ namespace pugi return result; } - PUGI__FN xml_node xml_node::prepend_child(const char_t* name_) - { + PUGI__FN xml_node xml_node::prepend_child(const char_t* name_) { xml_node result = prepend_child(node_element); result.set_name(name_); @@ -4414,8 +4455,7 @@ namespace pugi return result; } - PUGI__FN xml_node xml_node::insert_child_after(const char_t* name_, const xml_node& node) - { + PUGI__FN xml_node xml_node::insert_child_after(const char_t* name_, const xml_node& node) { xml_node result = insert_child_after(node_element, node); result.set_name(name_); @@ -4423,8 +4463,7 @@ namespace pugi return result; } - PUGI__FN xml_node xml_node::insert_child_before(const char_t* name_, const xml_node& node) - { + PUGI__FN xml_node xml_node::insert_child_before(const char_t* name_, const xml_node& node) { xml_node result = insert_child_before(node_element, node); result.set_name(name_); @@ -4432,125 +4471,150 @@ namespace pugi return result; } - PUGI__FN xml_node xml_node::append_copy(const xml_node& proto) - { + PUGI__FN xml_node xml_node::append_copy(const xml_node& proto) { xml_node result = append_child(proto.type()); - if (result) impl::recursive_copy_skip(result, proto, result); + if (result) { + impl::recursive_copy_skip(result, proto, result); + } return result; } - PUGI__FN xml_node xml_node::prepend_copy(const xml_node& proto) - { + PUGI__FN xml_node xml_node::prepend_copy(const xml_node& proto) { xml_node result = prepend_child(proto.type()); - if (result) impl::recursive_copy_skip(result, proto, result); + if (result) { + impl::recursive_copy_skip(result, proto, result); + } return result; } - PUGI__FN xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node) - { + PUGI__FN xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node) { xml_node result = insert_child_after(proto.type(), node); - if (result) impl::recursive_copy_skip(result, proto, result); + if (result) { + impl::recursive_copy_skip(result, proto, result); + } return result; } - PUGI__FN xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node) - { + PUGI__FN xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node) { xml_node result = insert_child_before(proto.type(), node); - if (result) impl::recursive_copy_skip(result, proto, result); + if (result) { + impl::recursive_copy_skip(result, proto, result); + } return result; } - PUGI__FN bool xml_node::remove_attribute(const char_t* name_) - { + PUGI__FN bool xml_node::remove_attribute(const char_t* name_) { return remove_attribute(attribute(name_)); } - PUGI__FN bool xml_node::remove_attribute(const xml_attribute& a) - { - if (!_root || !a._attr) return false; + PUGI__FN bool xml_node::remove_attribute(const xml_attribute& a) { + if (!_root || !a._attr) { + return false; + } // check that attribute belongs to *this xml_attribute_struct* attr = a._attr; - while (attr->prev_attribute_c->next_attribute) attr = attr->prev_attribute_c; + while (attr->prev_attribute_c->next_attribute) { + attr = attr->prev_attribute_c; + } - if (attr != _root->first_attribute) return false; + if (attr != _root->first_attribute) { + return false; + } - if (a._attr->next_attribute) a._attr->next_attribute->prev_attribute_c = a._attr->prev_attribute_c; - else if (_root->first_attribute) _root->first_attribute->prev_attribute_c = a._attr->prev_attribute_c; + if (a._attr->next_attribute) { + a._attr->next_attribute->prev_attribute_c = a._attr->prev_attribute_c; + } else if (_root->first_attribute) { + _root->first_attribute->prev_attribute_c = a._attr->prev_attribute_c; + } - if (a._attr->prev_attribute_c->next_attribute) a._attr->prev_attribute_c->next_attribute = a._attr->next_attribute; - else _root->first_attribute = a._attr->next_attribute; + if (a._attr->prev_attribute_c->next_attribute) { + a._attr->prev_attribute_c->next_attribute = a._attr->next_attribute; + } else { + _root->first_attribute = a._attr->next_attribute; + } impl::destroy_attribute(a._attr, impl::get_allocator(_root)); return true; } - PUGI__FN bool xml_node::remove_child(const char_t* name_) - { + PUGI__FN bool xml_node::remove_child(const char_t* name_) { return remove_child(child(name_)); } - PUGI__FN bool xml_node::remove_child(const xml_node& n) - { - if (!_root || !n._root || n._root->parent != _root) return false; + PUGI__FN bool xml_node::remove_child(const xml_node& n) { + if (!_root || !n._root || n._root->parent != _root) { + return false; + } - if (n._root->next_sibling) n._root->next_sibling->prev_sibling_c = n._root->prev_sibling_c; - else if (_root->first_child) _root->first_child->prev_sibling_c = n._root->prev_sibling_c; + if (n._root->next_sibling) { + n._root->next_sibling->prev_sibling_c = n._root->prev_sibling_c; + } else if (_root->first_child) { + _root->first_child->prev_sibling_c = n._root->prev_sibling_c; + } - if (n._root->prev_sibling_c->next_sibling) n._root->prev_sibling_c->next_sibling = n._root->next_sibling; - else _root->first_child = n._root->next_sibling; + if (n._root->prev_sibling_c->next_sibling) { + n._root->prev_sibling_c->next_sibling = n._root->next_sibling; + } else { + _root->first_child = n._root->next_sibling; + } impl::destroy_node(n._root, impl::get_allocator(_root)); return true; } - PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const - { - if (!_root) return xml_node(); + PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const { + if (!_root) { + return xml_node(); + } - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if (i->name && impl::strequal(name_, i->name)) - { - for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) - if (impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value)) + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) { + if (i->name && impl::strequal(name_, i->name)) { + for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) { + if (impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value)) { return xml_node(i); + } + } } + } return xml_node(); } - PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const - { - if (!_root) return xml_node(); + PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const { + if (!_root) { + return xml_node(); + } - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) - if (impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value)) + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) { + for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) { + if (impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value)) { return xml_node(i); + } + } + } return xml_node(); } -#ifndef PUGIXML_NO_STL - PUGI__FN string_t xml_node::path(char_t delimiter) const - { + #ifndef PUGIXML_NO_STL + PUGI__FN string_t xml_node::path(char_t delimiter) const { xml_node cursor = *this; // Make a copy. string_t result = cursor.name(); - while (cursor.parent()) - { + while (cursor.parent()) { cursor = cursor.parent(); string_t temp = cursor.name(); @@ -4561,16 +4625,16 @@ namespace pugi return result; } -#endif + #endif - PUGI__FN xml_node xml_node::first_element_by_path(const char_t* path_, char_t delimiter) const - { + PUGI__FN xml_node xml_node::first_element_by_path(const char_t* path_, char_t delimiter) const { xml_node found = *this; // Current search context. - if (!_root || !path_ || !path_[0]) return found; + if (!_root || !path_ || !path_[0]) { + return found; + } - if (path_[0] == delimiter) - { + if (path_[0] == delimiter) { // Absolute path; e.g. '/foo/bar' found = found.root(); ++path_; @@ -4578,31 +4642,38 @@ namespace pugi const char_t* path_segment = path_; - while (*path_segment == delimiter) ++path_segment; + while (*path_segment == delimiter) { + ++path_segment; + } const char_t* path_segment_end = path_segment; - while (*path_segment_end && *path_segment_end != delimiter) ++path_segment_end; + while (*path_segment_end && *path_segment_end != delimiter) { + ++path_segment_end; + } - if (path_segment == path_segment_end) return found; + if (path_segment == path_segment_end) { + return found; + } const char_t* next_segment = path_segment_end; - while (*next_segment == delimiter) ++next_segment; + while (*next_segment == delimiter) { + ++next_segment; + } - if (*path_segment == '.' && path_segment + 1 == path_segment_end) + if (*path_segment == '.' && path_segment + 1 == path_segment_end) { return found.first_element_by_path(next_segment, delimiter); - else if (*path_segment == '.' && *(path_segment+1) == '.' && path_segment + 2 == path_segment_end) + } else if (*path_segment == '.' && *(path_segment + 1) == '.' && path_segment + 2 == path_segment_end) { return found.parent().first_element_by_path(next_segment, delimiter); - else - { - for (xml_node_struct* j = found._root->first_child; j; j = j->next_sibling) - { - if (j->name && impl::strequalrange(j->name, path_segment, static_cast(path_segment_end - path_segment))) - { + } else { + for (xml_node_struct* j = found._root->first_child; j; j = j->next_sibling) { + if (j->name && impl::strequalrange(j->name, path_segment, static_cast(path_segment_end - path_segment))) { xml_node subsearch = xml_node(j).first_element_by_path(next_segment, delimiter); - if (subsearch) return subsearch; + if (subsearch) { + return subsearch; + } } } @@ -4610,46 +4681,42 @@ namespace pugi } } - PUGI__FN bool xml_node::traverse(xml_tree_walker& walker) - { + PUGI__FN bool xml_node::traverse(xml_tree_walker& walker) { walker._depth = -1; xml_node arg_begin = *this; - if (!walker.begin(arg_begin)) return false; + if (!walker.begin(arg_begin)) { + return false; + } xml_node cur = first_child(); - if (cur) - { + if (cur) { ++walker._depth; - do - { + do { xml_node arg_for_each = cur; - if (!walker.for_each(arg_for_each)) + if (!walker.for_each(arg_for_each)) { return false; + } - if (cur.first_child()) - { + if (cur.first_child()) { ++walker._depth; cur = cur.first_child(); - } - else if (cur.next_sibling()) + } else if (cur.next_sibling()) { cur = cur.next_sibling(); - else - { + } else { // Borland C++ workaround - while (!cur.next_sibling() && cur != *this && !cur.parent().empty()) - { + while (!cur.next_sibling() && cur != *this && !cur.parent().empty()) { --walker._depth; cur = cur.parent(); } - if (cur != *this) + if (cur != *this) { cur = cur.next_sibling(); + } } - } - while (cur && cur != *this); + } while (cur && cur != *this); } assert(walker._depth == -1); @@ -4658,489 +4725,452 @@ namespace pugi return walker.end(arg_end); } - PUGI__FN size_t xml_node::hash_value() const - { + PUGI__FN size_t xml_node::hash_value() const { return static_cast(reinterpret_cast(_root) / sizeof(xml_node_struct)); } - PUGI__FN xml_node_struct* xml_node::internal_object() const - { + PUGI__FN xml_node_struct* xml_node::internal_object() const { return _root; } - PUGI__FN void xml_node::print(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const - { - if (!_root) return; + PUGI__FN void xml_node::print(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const { + if (!_root) { + return; + } impl::xml_buffered_writer buffered_writer(writer, encoding); impl::node_output(buffered_writer, *this, indent, flags, depth); } -#ifndef PUGIXML_NO_STL - PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const - { + #ifndef PUGIXML_NO_STL + PUGI__FN void xml_node::print(std::basic_ostream>& stream, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const { xml_writer_stream writer(stream); print(writer, indent, flags, encoding, depth); } - PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, unsigned int depth) const - { + PUGI__FN void xml_node::print(std::basic_ostream>& stream, const char_t* indent, unsigned int flags, unsigned int depth) const { xml_writer_stream writer(stream); print(writer, indent, flags, encoding_wchar, depth); } -#endif + #endif - PUGI__FN ptrdiff_t xml_node::offset_debug() const - { + PUGI__FN ptrdiff_t xml_node::offset_debug() const { xml_node_struct* r = root()._root; - if (!r) return -1; + if (!r) { + return -1; + } const char_t* buffer = static_cast(r)->buffer; - if (!buffer) return -1; + if (!buffer) { + return -1; + } - switch (type()) - { - case node_document: - return 0; + switch (type()) { + case node_document: + return 0; - case node_element: - case node_declaration: - case node_pi: - return (_root->header & impl::xml_memory_page_name_allocated_mask) ? -1 : _root->name - buffer; + case node_element: + case node_declaration: + case node_pi: + return (_root->header & impl::xml_memory_page_name_allocated_mask) ? -1 : _root->name - buffer; - case node_pcdata: - case node_cdata: - case node_comment: - case node_doctype: - return (_root->header & impl::xml_memory_page_value_allocated_mask) ? -1 : _root->value - buffer; + case node_pcdata: + case node_cdata: + case node_comment: + case node_doctype: + return (_root->header & impl::xml_memory_page_value_allocated_mask) ? -1 : _root->value - buffer; - default: - return -1; + default: + return -1; } } -#ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xml_node& lhs, bool rhs) - { + #ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_node& lhs, bool rhs) { return (bool)lhs && rhs; } - PUGI__FN bool operator||(const xml_node& lhs, bool rhs) - { + PUGI__FN bool operator||(const xml_node& lhs, bool rhs) { return (bool)lhs || rhs; } -#endif + #endif - PUGI__FN xml_text::xml_text(xml_node_struct* root): _root(root) - { + PUGI__FN xml_text::xml_text(xml_node_struct* root) : + _root(root) { } - PUGI__FN xml_node_struct* xml_text::_data() const - { - if (!_root || impl::is_text_node(_root)) return _root; + PUGI__FN xml_node_struct* xml_text::_data() const { + if (!_root || impl::is_text_node(_root)) { + return _root; + } - for (xml_node_struct* node = _root->first_child; node; node = node->next_sibling) - if (impl::is_text_node(node)) + for (xml_node_struct* node = _root->first_child; node; node = node->next_sibling) { + if (impl::is_text_node(node)) { return node; + } + } return 0; } - PUGI__FN xml_node_struct* xml_text::_data_new() - { + PUGI__FN xml_node_struct* xml_text::_data_new() { xml_node_struct* d = _data(); - if (d) return d; + if (d) { + return d; + } return xml_node(_root).append_child(node_pcdata).internal_object(); } - PUGI__FN xml_text::xml_text(): _root(0) - { + PUGI__FN xml_text::xml_text() : + _root(0) { } - PUGI__FN static void unspecified_bool_xml_text(xml_text***) - { + PUGI__FN static void unspecified_bool_xml_text(xml_text***) { } - PUGI__FN xml_text::operator xml_text::unspecified_bool_type() const - { + PUGI__FN xml_text::operator xml_text::unspecified_bool_type() const { return _data() ? unspecified_bool_xml_text : 0; } - PUGI__FN bool xml_text::operator!() const - { + PUGI__FN bool xml_text::operator!() const { return !_data(); } - PUGI__FN bool xml_text::empty() const - { + PUGI__FN bool xml_text::empty() const { return _data() == 0; } - PUGI__FN const char_t* xml_text::get() const - { + PUGI__FN const char_t* xml_text::get() const { xml_node_struct* d = _data(); return (d && d->value) ? d->value : PUGIXML_TEXT(""); } - PUGI__FN const char_t* xml_text::as_string(const char_t* def) const - { + PUGI__FN const char_t* xml_text::as_string(const char_t* def) const { xml_node_struct* d = _data(); return (d && d->value) ? d->value : def; } - PUGI__FN int xml_text::as_int(int def) const - { + PUGI__FN int xml_text::as_int(int def) const { xml_node_struct* d = _data(); return impl::get_value_int(d ? d->value : 0, def); } - PUGI__FN unsigned int xml_text::as_uint(unsigned int def) const - { + PUGI__FN unsigned int xml_text::as_uint(unsigned int def) const { xml_node_struct* d = _data(); return impl::get_value_uint(d ? d->value : 0, def); } - PUGI__FN double xml_text::as_double(double def) const - { + PUGI__FN double xml_text::as_double(double def) const { xml_node_struct* d = _data(); return impl::get_value_double(d ? d->value : 0, def); } - PUGI__FN float xml_text::as_float(float def) const - { + PUGI__FN float xml_text::as_float(float def) const { xml_node_struct* d = _data(); return impl::get_value_float(d ? d->value : 0, def); } - PUGI__FN bool xml_text::as_bool(bool def) const - { + PUGI__FN bool xml_text::as_bool(bool def) const { xml_node_struct* d = _data(); return impl::get_value_bool(d ? d->value : 0, def); } - PUGI__FN bool xml_text::set(const char_t* rhs) - { + PUGI__FN bool xml_text::set(const char_t* rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; } - PUGI__FN bool xml_text::set(int rhs) - { + PUGI__FN bool xml_text::set(int rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; } - PUGI__FN bool xml_text::set(unsigned int rhs) - { + PUGI__FN bool xml_text::set(unsigned int rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; } - PUGI__FN bool xml_text::set(double rhs) - { + PUGI__FN bool xml_text::set(double rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; } - PUGI__FN bool xml_text::set(bool rhs) - { + PUGI__FN bool xml_text::set(bool rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; } - PUGI__FN xml_text& xml_text::operator=(const char_t* rhs) - { + PUGI__FN xml_text& xml_text::operator=(const char_t* rhs) { set(rhs); return *this; } - PUGI__FN xml_text& xml_text::operator=(int rhs) - { + PUGI__FN xml_text& xml_text::operator=(int rhs) { set(rhs); return *this; } - PUGI__FN xml_text& xml_text::operator=(unsigned int rhs) - { + PUGI__FN xml_text& xml_text::operator=(unsigned int rhs) { set(rhs); return *this; } - PUGI__FN xml_text& xml_text::operator=(double rhs) - { + PUGI__FN xml_text& xml_text::operator=(double rhs) { set(rhs); return *this; } - PUGI__FN xml_text& xml_text::operator=(bool rhs) - { + PUGI__FN xml_text& xml_text::operator=(bool rhs) { set(rhs); return *this; } - PUGI__FN xml_node xml_text::data() const - { + PUGI__FN xml_node xml_text::data() const { return xml_node(_data()); } -#ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xml_text& lhs, bool rhs) - { + #ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_text& lhs, bool rhs) { return (bool)lhs && rhs; } - PUGI__FN bool operator||(const xml_text& lhs, bool rhs) - { + PUGI__FN bool operator||(const xml_text& lhs, bool rhs) { return (bool)lhs || rhs; } -#endif + #endif - PUGI__FN xml_node_iterator::xml_node_iterator() - { + PUGI__FN xml_node_iterator::xml_node_iterator() { } - PUGI__FN xml_node_iterator::xml_node_iterator(const xml_node& node): _wrap(node), _parent(node.parent()) - { + PUGI__FN xml_node_iterator::xml_node_iterator(const xml_node& node) : + _wrap(node), _parent(node.parent()) { } - PUGI__FN xml_node_iterator::xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) - { + PUGI__FN xml_node_iterator::xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent) : + _wrap(ref), _parent(parent) { } - PUGI__FN bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const - { + PUGI__FN bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const { return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; } - PUGI__FN bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const - { + PUGI__FN bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const { return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; } - PUGI__FN xml_node& xml_node_iterator::operator*() const - { + PUGI__FN xml_node& xml_node_iterator::operator*() const { assert(_wrap._root); return _wrap; } - PUGI__FN xml_node* xml_node_iterator::operator->() const - { + PUGI__FN xml_node* xml_node_iterator::operator->() const { assert(_wrap._root); return const_cast(&_wrap); // BCC32 workaround } - PUGI__FN const xml_node_iterator& xml_node_iterator::operator++() - { + PUGI__FN const xml_node_iterator& xml_node_iterator::operator++() { assert(_wrap._root); _wrap._root = _wrap._root->next_sibling; return *this; } - PUGI__FN xml_node_iterator xml_node_iterator::operator++(int) - { + PUGI__FN xml_node_iterator xml_node_iterator::operator++(int) { xml_node_iterator temp = *this; ++*this; return temp; } - PUGI__FN const xml_node_iterator& xml_node_iterator::operator--() - { + PUGI__FN const xml_node_iterator& xml_node_iterator::operator--() { _wrap = _wrap._root ? _wrap.previous_sibling() : _parent.last_child(); return *this; } - PUGI__FN xml_node_iterator xml_node_iterator::operator--(int) - { + PUGI__FN xml_node_iterator xml_node_iterator::operator--(int) { xml_node_iterator temp = *this; --*this; return temp; } - PUGI__FN xml_attribute_iterator::xml_attribute_iterator() - { + PUGI__FN xml_attribute_iterator::xml_attribute_iterator() { } - PUGI__FN xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent): _wrap(attr), _parent(parent) - { + PUGI__FN xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent) : + _wrap(attr), _parent(parent) { } - PUGI__FN xml_attribute_iterator::xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) - { + PUGI__FN xml_attribute_iterator::xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent) : + _wrap(ref), _parent(parent) { } - PUGI__FN bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const - { + PUGI__FN bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const { return _wrap._attr == rhs._wrap._attr && _parent._root == rhs._parent._root; } - PUGI__FN bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const - { + PUGI__FN bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const { return _wrap._attr != rhs._wrap._attr || _parent._root != rhs._parent._root; } - PUGI__FN xml_attribute& xml_attribute_iterator::operator*() const - { + PUGI__FN xml_attribute& xml_attribute_iterator::operator*() const { assert(_wrap._attr); return _wrap; } - PUGI__FN xml_attribute* xml_attribute_iterator::operator->() const - { + PUGI__FN xml_attribute* xml_attribute_iterator::operator->() const { assert(_wrap._attr); return const_cast(&_wrap); // BCC32 workaround } - PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator++() - { + PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator++() { assert(_wrap._attr); _wrap._attr = _wrap._attr->next_attribute; return *this; } - PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator++(int) - { + PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator++(int) { xml_attribute_iterator temp = *this; ++*this; return temp; } - PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator--() - { + PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator--() { _wrap = _wrap._attr ? _wrap.previous_attribute() : _parent.last_attribute(); return *this; } - PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator--(int) - { + PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator--(int) { xml_attribute_iterator temp = *this; --*this; return temp; } - PUGI__FN xml_named_node_iterator::xml_named_node_iterator(): _name(0) - { + PUGI__FN xml_named_node_iterator::xml_named_node_iterator() : + _name(0) { } - PUGI__FN xml_named_node_iterator::xml_named_node_iterator(const xml_node& node, const char_t* name): _node(node), _name(name) - { + PUGI__FN xml_named_node_iterator::xml_named_node_iterator(const xml_node& node, const char_t* name) : + _node(node), _name(name) { } - PUGI__FN bool xml_named_node_iterator::operator==(const xml_named_node_iterator& rhs) const - { + PUGI__FN bool xml_named_node_iterator::operator==(const xml_named_node_iterator& rhs) const { return _node == rhs._node; } - PUGI__FN bool xml_named_node_iterator::operator!=(const xml_named_node_iterator& rhs) const - { + PUGI__FN bool xml_named_node_iterator::operator!=(const xml_named_node_iterator& rhs) const { return _node != rhs._node; } - PUGI__FN xml_node& xml_named_node_iterator::operator*() const - { + PUGI__FN xml_node& xml_named_node_iterator::operator*() const { assert(_node._root); return _node; } - PUGI__FN xml_node* xml_named_node_iterator::operator->() const - { + PUGI__FN xml_node* xml_named_node_iterator::operator->() const { assert(_node._root); return const_cast(&_node); // BCC32 workaround } - PUGI__FN const xml_named_node_iterator& xml_named_node_iterator::operator++() - { + PUGI__FN const xml_named_node_iterator& xml_named_node_iterator::operator++() { assert(_node._root); _node = _node.next_sibling(_name); return *this; } - PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator++(int) - { + PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator++(int) { xml_named_node_iterator temp = *this; ++*this; return temp; } - PUGI__FN xml_parse_result::xml_parse_result(): status(status_internal_error), offset(0), encoding(encoding_auto) - { + PUGI__FN xml_parse_result::xml_parse_result() : + status(status_internal_error), offset(0), encoding(encoding_auto) { } - PUGI__FN xml_parse_result::operator bool() const - { + PUGI__FN xml_parse_result::operator bool() const { return status == status_ok; } - PUGI__FN const char* xml_parse_result::description() const - { - switch (status) - { - case status_ok: return "No error"; - - case status_file_not_found: return "File was not found"; - case status_io_error: return "Error reading from file/stream"; - case status_out_of_memory: return "Could not allocate memory"; - case status_internal_error: return "Internal error occurred"; - - case status_unrecognized_tag: return "Could not determine tag type"; + PUGI__FN const char* xml_parse_result::description() const { + switch (status) { + case status_ok: + return "No error"; + + case status_file_not_found: + return "File was not found"; + case status_io_error: + return "Error reading from file/stream"; + case status_out_of_memory: + return "Could not allocate memory"; + case status_internal_error: + return "Internal error occurred"; + + case status_unrecognized_tag: + return "Could not determine tag type"; + + case status_bad_pi: + return "Error parsing document declaration/processing instruction"; + case status_bad_comment: + return "Error parsing comment"; + case status_bad_cdata: + return "Error parsing CDATA section"; + case status_bad_doctype: + return "Error parsing document type declaration"; + case status_bad_pcdata: + return "Error parsing PCDATA section"; + case status_bad_start_element: + return "Error parsing start element tag"; + case status_bad_attribute: + return "Error parsing element attribute"; + case status_bad_end_element: + return "Error parsing end element tag"; + case status_end_element_mismatch: + return "Start-end tags mismatch"; - case status_bad_pi: return "Error parsing document declaration/processing instruction"; - case status_bad_comment: return "Error parsing comment"; - case status_bad_cdata: return "Error parsing CDATA section"; - case status_bad_doctype: return "Error parsing document type declaration"; - case status_bad_pcdata: return "Error parsing PCDATA section"; - case status_bad_start_element: return "Error parsing start element tag"; - case status_bad_attribute: return "Error parsing element attribute"; - case status_bad_end_element: return "Error parsing end element tag"; - case status_end_element_mismatch: return "Start-end tags mismatch"; - - default: return "Unknown error"; + default: + return "Unknown error"; } } - PUGI__FN xml_document::xml_document(): _buffer(0) - { + PUGI__FN xml_document::xml_document() : + _buffer(0) { create(); } - PUGI__FN xml_document::~xml_document() - { + PUGI__FN xml_document::~xml_document() { destroy(); } - PUGI__FN void xml_document::reset() - { + PUGI__FN void xml_document::reset() { destroy(); create(); } - PUGI__FN void xml_document::reset(const xml_document& proto) - { + PUGI__FN void xml_document::reset(const xml_document& proto) { reset(); - for (xml_node cur = proto.first_child(); cur; cur = cur.next_sibling()) + for (xml_node cur = proto.first_child(); cur; cur = cur.next_sibling()) { append_copy(cur); + } } - PUGI__FN void xml_document::create() - { + PUGI__FN void xml_document::create() { // initialize sentinel page PUGI__STATIC_ASSERT(offsetof(impl::xml_memory_page, data) + sizeof(impl::xml_document_struct) + impl::xml_memory_page_alignment <= sizeof(_memory)); @@ -5160,24 +5190,20 @@ namespace pugi page->allocator = static_cast(_root); } - PUGI__FN void xml_document::destroy() - { + PUGI__FN void xml_document::destroy() { // destroy static storage - if (_buffer) - { + if (_buffer) { impl::xml_memory::deallocate(_buffer); _buffer = 0; } // destroy dynamic storage, leave sentinel page (it's in static memory) - if (_root) - { + if (_root) { impl::xml_memory_page* root_page = reinterpret_cast(_root->header & impl::xml_memory_page_pointer_mask); assert(root_page && !root_page->prev && !root_page->memory); // destroy all pages - for (impl::xml_memory_page* page = root_page->next; page; ) - { + for (impl::xml_memory_page* page = root_page->next; page;) { impl::xml_memory_page* next = page->next; impl::xml_allocator::deallocate_page(page); @@ -5194,24 +5220,21 @@ namespace pugi } } -#ifndef PUGIXML_NO_STL - PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options, xml_encoding encoding) - { + #ifndef PUGIXML_NO_STL + PUGI__FN xml_parse_result xml_document::load(std::basic_istream>& stream, unsigned int options, xml_encoding encoding) { reset(); return impl::load_stream_impl(*this, stream, options, encoding); } - PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options) - { + PUGI__FN xml_parse_result xml_document::load(std::basic_istream>& stream, unsigned int options) { reset(); return impl::load_stream_impl(*this, stream, options, encoding_wchar); } -#endif + #endif - PUGI__FN xml_parse_result xml_document::load(const char_t* contents, unsigned int options) - { + PUGI__FN xml_parse_result xml_document::load(const char_t* contents, unsigned int options) { // Force native encoding (skip autodetection) #ifdef PUGIXML_WCHAR_MODE xml_encoding encoding = encoding_wchar; @@ -5222,8 +5245,7 @@ namespace pugi return load_buffer(contents, impl::strlength(contents) * sizeof(char_t), options, encoding); } - PUGI__FN xml_parse_result xml_document::load_file(const char* path_, unsigned int options, xml_encoding encoding) - { + PUGI__FN xml_parse_result xml_document::load_file(const char* path_, unsigned int options, xml_encoding encoding) { reset(); FILE* file = fopen(path_, "rb"); @@ -5231,8 +5253,7 @@ namespace pugi return impl::load_file_impl(*this, file, options, encoding); } - PUGI__FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding) - { + PUGI__FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding) { reset(); FILE* file = impl::open_file_wide(path_, L"rb"); @@ -5240,8 +5261,7 @@ namespace pugi return impl::load_file_impl(*this, file, options, encoding); } - PUGI__FN xml_parse_result xml_document::load_buffer_impl(void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own) - { + PUGI__FN xml_parse_result xml_document::load_buffer_impl(void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own) { reset(); // check input buffer @@ -5254,10 +5274,14 @@ namespace pugi char_t* buffer = 0; size_t length = 0; - if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable)) return impl::make_parse_result(status_out_of_memory); + if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable)) { + return impl::make_parse_result(status_out_of_memory); + } // delete original buffer if we performed a conversion - if (own && buffer != contents && contents) impl::xml_memory::deallocate(contents); + if (own && buffer != contents && contents) { + impl::xml_memory::deallocate(contents); + } // parse xml_parse_result res = impl::xml_parser::parse(buffer, length, _root, options); @@ -5266,794 +5290,749 @@ namespace pugi res.encoding = buffer_encoding; // grab onto buffer if it's our buffer, user is responsible for deallocating contens himself - if (own || buffer != contents) _buffer = buffer; + if (own || buffer != contents) { + _buffer = buffer; + } return res; } - PUGI__FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) - { + PUGI__FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) { return load_buffer_impl(const_cast(contents), size, options, encoding, false, false); } - PUGI__FN xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options, xml_encoding encoding) - { + PUGI__FN xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options, xml_encoding encoding) { return load_buffer_impl(contents, size, options, encoding, true, false); } - PUGI__FN xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options, xml_encoding encoding) - { + PUGI__FN xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options, xml_encoding encoding) { return load_buffer_impl(contents, size, options, encoding, true, true); } - PUGI__FN void xml_document::save(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding) const - { + PUGI__FN void xml_document::save(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding) const { impl::xml_buffered_writer buffered_writer(writer, encoding); - if ((flags & format_write_bom) && encoding != encoding_latin1) - { + if ((flags & format_write_bom) && encoding != encoding_latin1) { // BOM always represents the codepoint U+FEFF, so just write it in native encoding - #ifdef PUGIXML_WCHAR_MODE + #ifdef PUGIXML_WCHAR_MODE unsigned int bom = 0xfeff; buffered_writer.write(static_cast(bom)); - #else + #else buffered_writer.write('\xef', '\xbb', '\xbf'); - #endif + #endif } - if (!(flags & format_no_declaration) && !impl::has_declaration(*this)) - { + if (!(flags & format_no_declaration) && !impl::has_declaration(*this)) { buffered_writer.write(PUGIXML_TEXT("'); - if (!(flags & format_raw)) buffered_writer.write('\n'); + if (!(flags & format_raw)) { + buffered_writer.write('\n'); + } } impl::node_output(buffered_writer, *this, indent, flags, 0); } -#ifndef PUGIXML_NO_STL - PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding) const - { + #ifndef PUGIXML_NO_STL + PUGI__FN void xml_document::save(std::basic_ostream>& stream, const char_t* indent, unsigned int flags, xml_encoding encoding) const { xml_writer_stream writer(stream); save(writer, indent, flags, encoding); } - PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags) const - { + PUGI__FN void xml_document::save(std::basic_ostream>& stream, const char_t* indent, unsigned int flags) const { xml_writer_stream writer(stream); save(writer, indent, flags, encoding_wchar); } -#endif + #endif - PUGI__FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const - { + PUGI__FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const { FILE* file = fopen(path_, (flags & format_save_file_text) ? "w" : "wb"); return impl::save_file_impl(*this, file, indent, flags, encoding); } - PUGI__FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const - { + PUGI__FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const { FILE* file = impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb"); return impl::save_file_impl(*this, file, indent, flags, encoding); } - PUGI__FN xml_node xml_document::document_element() const - { - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if ((i->header & impl::xml_memory_page_type_mask) + 1 == node_element) + PUGI__FN xml_node xml_document::document_element() const { + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) { + if ((i->header & impl::xml_memory_page_type_mask) + 1 == node_element) { return xml_node(i); + } + } return xml_node(); } -#ifndef PUGIXML_NO_STL - PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str) - { + #ifndef PUGIXML_NO_STL + PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str) { assert(str); return impl::as_utf8_impl(str, wcslen(str)); } - PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const std::basic_string& str) - { + PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const std::basic_string& str) { return impl::as_utf8_impl(str.c_str(), str.size()); } - PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const char* str) - { + PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const char* str) { assert(str); return impl::as_wide_impl(str, strlen(str)); } - PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const std::string& str) - { + PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const std::string& str) { return impl::as_wide_impl(str.c_str(), str.size()); } -#endif + #endif - PUGI__FN void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate) - { + PUGI__FN void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate) { impl::xml_memory::allocate = allocate; impl::xml_memory::deallocate = deallocate; } - PUGI__FN allocation_function PUGIXML_FUNCTION get_memory_allocation_function() - { + PUGI__FN allocation_function PUGIXML_FUNCTION get_memory_allocation_function() { return impl::xml_memory::allocate; } - PUGI__FN deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function() - { + PUGI__FN deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function() { return impl::xml_memory::deallocate; } } -#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) -namespace std -{ + #if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) +namespace std { // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) - PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_node_iterator&) - { + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_node_iterator&) { return std::bidirectional_iterator_tag(); } - PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_attribute_iterator&) - { + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_attribute_iterator&) { return std::bidirectional_iterator_tag(); } - PUGI__FN std::forward_iterator_tag _Iter_cat(const pugi::xml_named_node_iterator&) - { + PUGI__FN std::forward_iterator_tag _Iter_cat(const pugi::xml_named_node_iterator&) { return std::forward_iterator_tag(); } } -#endif + #endif -#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) -namespace std -{ + #if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) +namespace std { // Workarounds for (non-standard) iterator category detection - PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_node_iterator&) - { + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_node_iterator&) { return std::bidirectional_iterator_tag(); } - PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_attribute_iterator&) - { + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_attribute_iterator&) { return std::bidirectional_iterator_tag(); } - PUGI__FN std::forward_iterator_tag __iterator_category(const pugi::xml_named_node_iterator&) - { + PUGI__FN std::forward_iterator_tag __iterator_category(const pugi::xml_named_node_iterator&) { return std::forward_iterator_tag(); } } -#endif + #endif -#ifndef PUGIXML_NO_XPATH + #ifndef PUGIXML_NO_XPATH // STL replacements PUGI__NS_BEGIN - struct equal_to - { - template bool operator()(const T& lhs, const T& rhs) const - { - return lhs == rhs; - } - }; - - struct not_equal_to - { - template bool operator()(const T& lhs, const T& rhs) const - { - return lhs != rhs; - } - }; +struct equal_to { + template + bool operator()(const T& lhs, const T& rhs) const { + return lhs == rhs; + } +}; - struct less - { - template bool operator()(const T& lhs, const T& rhs) const - { - return lhs < rhs; - } - }; +struct not_equal_to { + template + bool operator()(const T& lhs, const T& rhs) const { + return lhs != rhs; + } +}; - struct less_equal - { - template bool operator()(const T& lhs, const T& rhs) const - { - return lhs <= rhs; - } - }; +struct less { + template + bool operator()(const T& lhs, const T& rhs) const { + return lhs < rhs; + } +}; - template void swap(T& lhs, T& rhs) - { - T temp = lhs; - lhs = rhs; - rhs = temp; +struct less_equal { + template + bool operator()(const T& lhs, const T& rhs) const { + return lhs <= rhs; } +}; - template I min_element(I begin, I end, const Pred& pred) - { - I result = begin; +template +void swap(T& lhs, T& rhs) { + T temp = lhs; + lhs = rhs; + rhs = temp; +} - for (I it = begin + 1; it != end; ++it) - if (pred(*it, *result)) - result = it; +template +I min_element(I begin, I end, const Pred& pred) { + I result = begin; - return result; + for (I it = begin + 1; it != end; ++it) { + if (pred(*it, *result)) { + result = it; + } } - template void reverse(I begin, I end) - { - while (begin + 1 < end) swap(*begin++, *--end); + return result; +} + +template +void reverse(I begin, I end) { + while (begin + 1 < end) { + swap(*begin++, *--end); } +} - template I unique(I begin, I end) - { - // fast skip head - while (begin + 1 < end && *begin != *(begin + 1)) begin++; +template +I unique(I begin, I end) { + // fast skip head + while (begin + 1 < end && *begin != *(begin + 1)) { + begin++; + } - if (begin == end) return begin; + if (begin == end) { + return begin; + } - // last written element - I write = begin++; + // last written element + I write = begin++; - // merge unique elements - while (begin != end) - { - if (*begin != *write) - *++write = *begin++; - else - begin++; + // merge unique elements + while (begin != end) { + if (*begin != *write) { + *++write = *begin++; + } else { + begin++; } - - // past-the-end (write points to live element) - return write + 1; } - template void copy_backwards(I begin, I end, I target) - { - while (begin != end) *--target = *--end; - } + // past-the-end (write points to live element) + return write + 1; +} - template void insertion_sort(I begin, I end, const Pred& pred, T*) - { - assert(begin != end); +template +void copy_backwards(I begin, I end, I target) { + while (begin != end) { + *--target = *--end; + } +} - for (I it = begin + 1; it != end; ++it) - { - T val = *it; +template +void insertion_sort(I begin, I end, const Pred& pred, T*) { + assert(begin != end); - if (pred(val, *begin)) - { - // move to front - copy_backwards(begin, it, it + 1); - *begin = val; - } - else - { - I hole = it; + for (I it = begin + 1; it != end; ++it) { + T val = *it; - // move hole backwards - while (pred(val, *(hole - 1))) - { - *hole = *(hole - 1); - hole--; - } + if (pred(val, *begin)) { + // move to front + copy_backwards(begin, it, it + 1); + *begin = val; + } else { + I hole = it; - // fill hole with element - *hole = val; + // move hole backwards + while (pred(val, *(hole - 1))) { + *hole = *(hole - 1); + hole--; } + + // fill hole with element + *hole = val; } } +} - // std variant for elements with == - template void partition(I begin, I middle, I end, const Pred& pred, I* out_eqbeg, I* out_eqend) - { - I eqbeg = middle, eqend = middle + 1; +// std variant for elements with == +template +void partition(I begin, I middle, I end, const Pred& pred, I* out_eqbeg, I* out_eqend) { + I eqbeg = middle, eqend = middle + 1; - // expand equal range - while (eqbeg != begin && *(eqbeg - 1) == *eqbeg) --eqbeg; - while (eqend != end && *eqend == *eqbeg) ++eqend; + // expand equal range + while (eqbeg != begin && *(eqbeg - 1) == *eqbeg) { + --eqbeg; + } + while (eqend != end && *eqend == *eqbeg) { + ++eqend; + } - // process outer elements - I ltend = eqbeg, gtbeg = eqend; + // process outer elements + I ltend = eqbeg, gtbeg = eqend; - for (;;) - { - // find the element from the right side that belongs to the left one - for (; gtbeg != end; ++gtbeg) - if (!pred(*eqbeg, *gtbeg)) - { - if (*gtbeg == *eqbeg) swap(*gtbeg, *eqend++); - else break; + for (;;) { + // find the element from the right side that belongs to the left one + for (; gtbeg != end; ++gtbeg) { + if (!pred(*eqbeg, *gtbeg)) { + if (*gtbeg == *eqbeg) { + swap(*gtbeg, *eqend++); + } else { + break; } + } + } - // find the element from the left side that belongs to the right one - for (; ltend != begin; --ltend) - if (!pred(*(ltend - 1), *eqbeg)) - { - if (*eqbeg == *(ltend - 1)) swap(*(ltend - 1), *--eqbeg); - else break; + // find the element from the left side that belongs to the right one + for (; ltend != begin; --ltend) { + if (!pred(*(ltend - 1), *eqbeg)) { + if (*eqbeg == *(ltend - 1)) { + swap(*(ltend - 1), *--eqbeg); + } else { + break; } - - // scanned all elements - if (gtbeg == end && ltend == begin) - { - *out_eqbeg = eqbeg; - *out_eqend = eqend; - return; } + } - // make room for elements by moving equal area - if (gtbeg == end) - { - if (--ltend != --eqbeg) swap(*ltend, *eqbeg); - swap(*eqbeg, *--eqend); + // scanned all elements + if (gtbeg == end && ltend == begin) { + *out_eqbeg = eqbeg; + *out_eqend = eqend; + return; + } + + // make room for elements by moving equal area + if (gtbeg == end) { + if (--ltend != --eqbeg) { + swap(*ltend, *eqbeg); } - else if (ltend == begin) - { - if (eqend != gtbeg) swap(*eqbeg, *eqend); - ++eqend; - swap(*gtbeg++, *eqbeg++); + swap(*eqbeg, *--eqend); + } else if (ltend == begin) { + if (eqend != gtbeg) { + swap(*eqbeg, *eqend); } - else swap(*gtbeg++, *--ltend); + ++eqend; + swap(*gtbeg++, *eqbeg++); + } else { + swap(*gtbeg++, *--ltend); } } +} - template void median3(I first, I middle, I last, const Pred& pred) - { - if (pred(*middle, *first)) swap(*middle, *first); - if (pred(*last, *middle)) swap(*last, *middle); - if (pred(*middle, *first)) swap(*middle, *first); +template +void median3(I first, I middle, I last, const Pred& pred) { + if (pred(*middle, *first)) { + swap(*middle, *first); + } + if (pred(*last, *middle)) { + swap(*last, *middle); } + if (pred(*middle, *first)) { + swap(*middle, *first); + } +} - template void median(I first, I middle, I last, const Pred& pred) - { - if (last - first <= 40) - { - // median of three for small chunks - median3(first, middle, last, pred); - } - else - { - // median of nine - size_t step = (last - first + 1) / 8; +template +void median(I first, I middle, I last, const Pred& pred) { + if (last - first <= 40) { + // median of three for small chunks + median3(first, middle, last, pred); + } else { + // median of nine + size_t step = (last - first + 1) / 8; - median3(first, first + step, first + 2 * step, pred); - median3(middle - step, middle, middle + step, pred); - median3(last - 2 * step, last - step, last, pred); - median3(first + step, middle, last - step, pred); - } + median3(first, first + step, first + 2 * step, pred); + median3(middle - step, middle, middle + step, pred); + median3(last - 2 * step, last - step, last, pred); + median3(first + step, middle, last - step, pred); } +} - template void sort(I begin, I end, const Pred& pred) - { - // sort large chunks - while (end - begin > 32) - { - // find median element - I middle = begin + (end - begin) / 2; - median(begin, middle, end - 1, pred); +template +void sort(I begin, I end, const Pred& pred) { + // sort large chunks + while (end - begin > 32) { + // find median element + I middle = begin + (end - begin) / 2; + median(begin, middle, end - 1, pred); - // partition in three chunks (< = >) - I eqbeg, eqend; - partition(begin, middle, end, pred, &eqbeg, &eqend); + // partition in three chunks (< = >) + I eqbeg, eqend; + partition(begin, middle, end, pred, &eqbeg, &eqend); - // loop on larger half - if (eqbeg - begin > end - eqend) - { - sort(eqend, end, pred); - end = eqbeg; - } - else - { - sort(begin, eqbeg, pred); - begin = eqend; - } + // loop on larger half + if (eqbeg - begin > end - eqend) { + sort(eqend, end, pred); + end = eqbeg; + } else { + sort(begin, eqbeg, pred); + begin = eqend; } + } - // insertion sort small chunk - if (begin != end) insertion_sort(begin, end, pred, &*begin); + // insertion sort small chunk + if (begin != end) { + insertion_sort(begin, end, pred, &*begin); } +} PUGI__NS_END // Allocator used for AST and evaluation stacks PUGI__NS_BEGIN - struct xpath_memory_block - { - xpath_memory_block* next; +struct xpath_memory_block { + xpath_memory_block* next; - char data[ - #ifdef PUGIXML_MEMORY_XPATH_PAGE_SIZE - PUGIXML_MEMORY_XPATH_PAGE_SIZE - #else - 4096 - #endif - ]; - }; + char data[ + #ifdef PUGIXML_MEMORY_XPATH_PAGE_SIZE + PUGIXML_MEMORY_XPATH_PAGE_SIZE + #else + 4096 + #endif + ]; +}; - class xpath_allocator - { - xpath_memory_block* _root; - size_t _root_size; +class xpath_allocator { + xpath_memory_block* _root; + size_t _root_size; - public: - #ifdef PUGIXML_NO_EXCEPTIONS - jmp_buf* error_handler; - #endif +public: + #ifdef PUGIXML_NO_EXCEPTIONS + jmp_buf* error_handler; + #endif - xpath_allocator(xpath_memory_block* root, size_t root_size = 0): _root(root), _root_size(root_size) - { + xpath_allocator(xpath_memory_block* root, size_t root_size = 0) : + _root(root), _root_size(root_size) { #ifdef PUGIXML_NO_EXCEPTIONS - error_handler = 0; + error_handler = 0; #endif - } + } - void* allocate_nothrow(size_t size) - { - const size_t block_capacity = sizeof(_root->data); + void* allocate_nothrow(size_t size) { + const size_t block_capacity = sizeof(_root->data); - // align size so that we're able to store pointers in subsequent blocks - size = (size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); + // align size so that we're able to store pointers in subsequent blocks + size = (size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); - if (_root_size + size <= block_capacity) - { - void* buf = _root->data + _root_size; - _root_size += size; - return buf; - } - else - { - size_t block_data_size = (size > block_capacity) ? size : block_capacity; - size_t block_size = block_data_size + offsetof(xpath_memory_block, data); + if (_root_size + size <= block_capacity) { + void* buf = _root->data + _root_size; + _root_size += size; + return buf; + } else { + size_t block_data_size = (size > block_capacity) ? size : block_capacity; + size_t block_size = block_data_size + offsetof(xpath_memory_block, data); - xpath_memory_block* block = static_cast(xml_memory::allocate(block_size)); - if (!block) return 0; + xpath_memory_block* block = static_cast(xml_memory::allocate(block_size)); + if (!block) { + return 0; + } - block->next = _root; + block->next = _root; - _root = block; - _root_size = size; + _root = block; + _root_size = size; - return block->data; - } + return block->data; } + } - void* allocate(size_t size) - { - void* result = allocate_nothrow(size); - - if (!result) - { - #ifdef PUGIXML_NO_EXCEPTIONS - assert(error_handler); - longjmp(*error_handler, 1); - #else - throw std::bad_alloc(); - #endif - } + void* allocate(size_t size) { + void* result = allocate_nothrow(size); - return result; + if (!result) { + #ifdef PUGIXML_NO_EXCEPTIONS + assert(error_handler); + longjmp(*error_handler, 1); + #else + throw std::bad_alloc(); + #endif } - void* reallocate(void* ptr, size_t old_size, size_t new_size) - { - // align size so that we're able to store pointers in subsequent blocks - old_size = (old_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); - new_size = (new_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); + return result; + } - // we can only reallocate the last object - assert(ptr == 0 || static_cast(ptr) + old_size == _root->data + _root_size); + void* reallocate(void* ptr, size_t old_size, size_t new_size) { + // align size so that we're able to store pointers in subsequent blocks + old_size = (old_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); + new_size = (new_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); - // adjust root size so that we have not allocated the object at all - bool only_object = (_root_size == old_size); + // we can only reallocate the last object + assert(ptr == 0 || static_cast(ptr) + old_size == _root->data + _root_size); - if (ptr) _root_size -= old_size; + // adjust root size so that we have not allocated the object at all + bool only_object = (_root_size == old_size); - // allocate a new version (this will obviously reuse the memory if possible) - void* result = allocate(new_size); - assert(result); + if (ptr) { + _root_size -= old_size; + } - // we have a new block - if (result != ptr && ptr) - { - // copy old data - assert(new_size > old_size); - memcpy(result, ptr, old_size); + // allocate a new version (this will obviously reuse the memory if possible) + void* result = allocate(new_size); + assert(result); - // free the previous page if it had no other objects - if (only_object) - { - assert(_root->data == result); - assert(_root->next); + // we have a new block + if (result != ptr && ptr) { + // copy old data + assert(new_size > old_size); + memcpy(result, ptr, old_size); - xpath_memory_block* next = _root->next->next; + // free the previous page if it had no other objects + if (only_object) { + assert(_root->data == result); + assert(_root->next); - if (next) - { - // deallocate the whole page, unless it was the first one - xml_memory::deallocate(_root->next); - _root->next = next; - } + xpath_memory_block* next = _root->next->next; + + if (next) { + // deallocate the whole page, unless it was the first one + xml_memory::deallocate(_root->next); + _root->next = next; } } - - return result; } - void revert(const xpath_allocator& state) - { - // free all new pages - xpath_memory_block* cur = _root; + return result; + } - while (cur != state._root) - { - xpath_memory_block* next = cur->next; + void revert(const xpath_allocator& state) { + // free all new pages + xpath_memory_block* cur = _root; - xml_memory::deallocate(cur); + while (cur != state._root) { + xpath_memory_block* next = cur->next; - cur = next; - } + xml_memory::deallocate(cur); - // restore state - _root = state._root; - _root_size = state._root_size; + cur = next; } - void release() - { - xpath_memory_block* cur = _root; - assert(cur); + // restore state + _root = state._root; + _root_size = state._root_size; + } - while (cur->next) - { - xpath_memory_block* next = cur->next; + void release() { + xpath_memory_block* cur = _root; + assert(cur); - xml_memory::deallocate(cur); + while (cur->next) { + xpath_memory_block* next = cur->next; - cur = next; - } - } - }; + xml_memory::deallocate(cur); - struct xpath_allocator_capture - { - xpath_allocator_capture(xpath_allocator* alloc): _target(alloc), _state(*alloc) - { + cur = next; } + } +}; - ~xpath_allocator_capture() - { - _target->revert(_state); - } +struct xpath_allocator_capture { + xpath_allocator_capture(xpath_allocator* alloc) : + _target(alloc), _state(*alloc) { + } - xpath_allocator* _target; - xpath_allocator _state; - }; + ~xpath_allocator_capture() { + _target->revert(_state); + } - struct xpath_stack - { - xpath_allocator* result; - xpath_allocator* temp; - }; + xpath_allocator* _target; + xpath_allocator _state; +}; - struct xpath_stack_data - { - xpath_memory_block blocks[2]; - xpath_allocator result; - xpath_allocator temp; - xpath_stack stack; +struct xpath_stack { + xpath_allocator* result; + xpath_allocator* temp; +}; - #ifdef PUGIXML_NO_EXCEPTIONS - jmp_buf error_handler; - #endif +struct xpath_stack_data { + xpath_memory_block blocks[2]; + xpath_allocator result; + xpath_allocator temp; + xpath_stack stack; - xpath_stack_data(): result(blocks + 0), temp(blocks + 1) - { - blocks[0].next = blocks[1].next = 0; + #ifdef PUGIXML_NO_EXCEPTIONS + jmp_buf error_handler; + #endif - stack.result = &result; - stack.temp = &temp; + xpath_stack_data() : + result(blocks + 0), temp(blocks + 1) { + blocks[0].next = blocks[1].next = 0; + + stack.result = &result; + stack.temp = &temp; #ifdef PUGIXML_NO_EXCEPTIONS - result.error_handler = temp.error_handler = &error_handler; + result.error_handler = temp.error_handler = &error_handler; #endif - } + } - ~xpath_stack_data() - { - result.release(); - temp.release(); - } - }; + ~xpath_stack_data() { + result.release(); + temp.release(); + } +}; PUGI__NS_END // String class PUGI__NS_BEGIN - class xpath_string - { - const char_t* _buffer; - bool _uses_heap; - - static char_t* duplicate_string(const char_t* string, size_t length, xpath_allocator* alloc) - { - char_t* result = static_cast(alloc->allocate((length + 1) * sizeof(char_t))); - assert(result); - - memcpy(result, string, length * sizeof(char_t)); - result[length] = 0; - - return result; - } - - static char_t* duplicate_string(const char_t* string, xpath_allocator* alloc) - { - return duplicate_string(string, strlength(string), alloc); - } - - public: - xpath_string(): _buffer(PUGIXML_TEXT("")), _uses_heap(false) - { - } +class xpath_string { + const char_t* _buffer; + bool _uses_heap; - explicit xpath_string(const char_t* str, xpath_allocator* alloc) - { - bool empty_ = (*str == 0); - - _buffer = empty_ ? PUGIXML_TEXT("") : duplicate_string(str, alloc); - _uses_heap = !empty_; - } + static char_t* duplicate_string(const char_t* string, size_t length, xpath_allocator* alloc) { + char_t* result = static_cast(alloc->allocate((length + 1) * sizeof(char_t))); + assert(result); - explicit xpath_string(const char_t* str, bool use_heap): _buffer(str), _uses_heap(use_heap) - { - } + memcpy(result, string, length * sizeof(char_t)); + result[length] = 0; - xpath_string(const char_t* begin, const char_t* end, xpath_allocator* alloc) - { - assert(begin <= end); + return result; + } - bool empty_ = (begin == end); + static char_t* duplicate_string(const char_t* string, xpath_allocator* alloc) { + return duplicate_string(string, strlength(string), alloc); + } - _buffer = empty_ ? PUGIXML_TEXT("") : duplicate_string(begin, static_cast(end - begin), alloc); - _uses_heap = !empty_; - } +public: + xpath_string() : + _buffer(PUGIXML_TEXT("")), _uses_heap(false) { + } - void append(const xpath_string& o, xpath_allocator* alloc) - { - // skip empty sources - if (!*o._buffer) return; + explicit xpath_string(const char_t* str, xpath_allocator* alloc) { + bool empty_ = (*str == 0); - // fast append for constant empty target and constant source - if (!*_buffer && !_uses_heap && !o._uses_heap) - { - _buffer = o._buffer; - } - else - { - // need to make heap copy - size_t target_length = strlength(_buffer); - size_t source_length = strlength(o._buffer); - size_t result_length = target_length + source_length; + _buffer = empty_ ? PUGIXML_TEXT("") : duplicate_string(str, alloc); + _uses_heap = !empty_; + } - // allocate new buffer - char_t* result = static_cast(alloc->reallocate(_uses_heap ? const_cast(_buffer) : 0, (target_length + 1) * sizeof(char_t), (result_length + 1) * sizeof(char_t))); - assert(result); + explicit xpath_string(const char_t* str, bool use_heap) : + _buffer(str), _uses_heap(use_heap) { + } - // append first string to the new buffer in case there was no reallocation - if (!_uses_heap) memcpy(result, _buffer, target_length * sizeof(char_t)); + xpath_string(const char_t* begin, const char_t* end, xpath_allocator* alloc) { + assert(begin <= end); - // append second string to the new buffer - memcpy(result + target_length, o._buffer, source_length * sizeof(char_t)); - result[result_length] = 0; + bool empty_ = (begin == end); - // finalize - _buffer = result; - _uses_heap = true; - } - } + _buffer = empty_ ? PUGIXML_TEXT("") : duplicate_string(begin, static_cast(end - begin), alloc); + _uses_heap = !empty_; + } - const char_t* c_str() const - { - return _buffer; + void append(const xpath_string& o, xpath_allocator* alloc) { + // skip empty sources + if (!*o._buffer) { + return; } - size_t length() const - { - return strlength(_buffer); - } + // fast append for constant empty target and constant source + if (!*_buffer && !_uses_heap && !o._uses_heap) { + _buffer = o._buffer; + } else { + // need to make heap copy + size_t target_length = strlength(_buffer); + size_t source_length = strlength(o._buffer); + size_t result_length = target_length + source_length; - char_t* data(xpath_allocator* alloc) - { - // make private heap copy - if (!_uses_heap) - { - _buffer = duplicate_string(_buffer, alloc); - _uses_heap = true; + // allocate new buffer + char_t* result = static_cast(alloc->reallocate(_uses_heap ? const_cast(_buffer) : 0, (target_length + 1) * sizeof(char_t), (result_length + 1) * sizeof(char_t))); + assert(result); + + // append first string to the new buffer in case there was no reallocation + if (!_uses_heap) { + memcpy(result, _buffer, target_length * sizeof(char_t)); } - return const_cast(_buffer); - } + // append second string to the new buffer + memcpy(result + target_length, o._buffer, source_length * sizeof(char_t)); + result[result_length] = 0; - bool empty() const - { - return *_buffer == 0; + // finalize + _buffer = result; + _uses_heap = true; } + } - bool operator==(const xpath_string& o) const - { - return strequal(_buffer, o._buffer); - } + const char_t* c_str() const { + return _buffer; + } - bool operator!=(const xpath_string& o) const - { - return !strequal(_buffer, o._buffer); - } + size_t length() const { + return strlength(_buffer); + } - bool uses_heap() const - { - return _uses_heap; + char_t* data(xpath_allocator* alloc) { + // make private heap copy + if (!_uses_heap) { + _buffer = duplicate_string(_buffer, alloc); + _uses_heap = true; } - }; - PUGI__FN xpath_string xpath_string_const(const char_t* str) - { - return xpath_string(str, false); + return const_cast(_buffer); } -PUGI__NS_END -PUGI__NS_BEGIN - PUGI__FN bool starts_with(const char_t* string, const char_t* pattern) - { - while (*pattern && *string == *pattern) - { - string++; - pattern++; - } + bool empty() const { + return *_buffer == 0; + } - return *pattern == 0; + bool operator==(const xpath_string& o) const { + return strequal(_buffer, o._buffer); } - PUGI__FN const char_t* find_char(const char_t* s, char_t c) - { - #ifdef PUGIXML_WCHAR_MODE - return wcschr(s, c); - #else - return strchr(s, c); - #endif + bool operator!=(const xpath_string& o) const { + return !strequal(_buffer, o._buffer); } - PUGI__FN const char_t* find_substring(const char_t* s, const char_t* p) - { - #ifdef PUGIXML_WCHAR_MODE - // MSVC6 wcsstr bug workaround (if s is empty it always returns 0) - return (*p == 0) ? s : wcsstr(s, p); - #else - return strstr(s, p); - #endif + bool uses_heap() const { + return _uses_heap; } +}; - // Converts symbol to lower case, if it is an ASCII one - PUGI__FN char_t tolower_ascii(char_t ch) - { - return static_cast(ch - 'A') < 26 ? static_cast(ch | ' ') : ch; +PUGI__FN xpath_string xpath_string_const(const char_t* str) { + return xpath_string(str, false); +} +PUGI__NS_END + +PUGI__NS_BEGIN +PUGI__FN bool starts_with(const char_t* string, const char_t* pattern) { + while (*pattern && *string == *pattern) { + string++; + pattern++; } - PUGI__FN xpath_string string_value(const xpath_node& na, xpath_allocator* alloc) - { - if (na.attribute()) - return xpath_string_const(na.attribute().value()); - else - { - const xml_node& n = na.node(); + return *pattern == 0; +} - switch (n.type()) - { +PUGI__FN const char_t* find_char(const char_t* s, char_t c) { + #ifdef PUGIXML_WCHAR_MODE + return wcschr(s, c); + #else + return strchr(s, c); + #endif +} + +PUGI__FN const char_t* find_substring(const char_t* s, const char_t* p) { + #ifdef PUGIXML_WCHAR_MODE + // MSVC6 wcsstr bug workaround (if s is empty it always returns 0) + return (*p == 0) ? s : wcsstr(s, p); + #else + return strstr(s, p); + #endif +} + +// Converts symbol to lower case, if it is an ASCII one +PUGI__FN char_t tolower_ascii(char_t ch) { + return static_cast(ch - 'A') < 26 ? static_cast(ch | ' ') : ch; +} + +PUGI__FN xpath_string string_value(const xpath_node& na, xpath_allocator* alloc) { + if (na.attribute()) { + return xpath_string_const(na.attribute().value()); + } else { + const xml_node& n = na.node(); + + switch (n.type()) { case node_pcdata: case node_cdata: case node_comment: @@ -6061,27 +6040,28 @@ PUGI__NS_BEGIN return xpath_string_const(n.value()); case node_document: - case node_element: - { + case node_element: { xpath_string result; xml_node cur = n.first_child(); - while (cur && cur != n) - { - if (cur.type() == node_pcdata || cur.type() == node_cdata) + while (cur && cur != n) { + if (cur.type() == node_pcdata || cur.type() == node_cdata) { result.append(xpath_string_const(cur.value()), alloc); + } - if (cur.first_child()) + if (cur.first_child()) { cur = cur.first_child(); - else if (cur.next_sibling()) + } else if (cur.next_sibling()) { cur = cur.next_sibling(); - else - { - while (!cur.next_sibling() && cur != n) + } else { + while (!cur.next_sibling() && cur != n) { cur = cur.parent(); + } - if (cur != n) cur = cur.next_sibling(); + if (cur != n) { + cur = cur.next_sibling(); + } } } @@ -6090,179 +6070,197 @@ PUGI__NS_BEGIN default: return xpath_string(); - } } } +} - PUGI__FN unsigned int node_height(xml_node n) - { - unsigned int result = 0; - - while (n) - { - ++result; - n = n.parent(); - } +PUGI__FN unsigned int node_height(xml_node n) { + unsigned int result = 0; - return result; + while (n) { + ++result; + n = n.parent(); } - PUGI__FN bool node_is_before(xml_node ln, unsigned int lh, xml_node rn, unsigned int rh) - { - // normalize heights - for (unsigned int i = rh; i < lh; i++) ln = ln.parent(); - for (unsigned int j = lh; j < rh; j++) rn = rn.parent(); + return result; +} - // one node is the ancestor of the other - if (ln == rn) return lh < rh; +PUGI__FN bool node_is_before(xml_node ln, unsigned int lh, xml_node rn, unsigned int rh) { + // normalize heights + for (unsigned int i = rh; i < lh; i++) { + ln = ln.parent(); + } + for (unsigned int j = lh; j < rh; j++) { + rn = rn.parent(); + } - // find common ancestor - while (ln.parent() != rn.parent()) - { - ln = ln.parent(); - rn = rn.parent(); - } + // one node is the ancestor of the other + if (ln == rn) { + return lh < rh; + } - // there is no common ancestor (the shared parent is null), nodes are from different documents - if (!ln.parent()) return ln < rn; + // find common ancestor + while (ln.parent() != rn.parent()) { + ln = ln.parent(); + rn = rn.parent(); + } - // determine sibling order - for (; ln; ln = ln.next_sibling()) - if (ln == rn) - return true; + // there is no common ancestor (the shared parent is null), nodes are from different documents + if (!ln.parent()) { + return ln < rn; + } - return false; + // determine sibling order + for (; ln; ln = ln.next_sibling()) { + if (ln == rn) { + return true; + } } - PUGI__FN bool node_is_ancestor(xml_node parent, xml_node node) - { - while (node && node != parent) node = node.parent(); + return false; +} - return parent && node == parent; +PUGI__FN bool node_is_ancestor(xml_node parent, xml_node node) { + while (node && node != parent) { + node = node.parent(); } - PUGI__FN const void* document_order(const xpath_node& xnode) - { - xml_node_struct* node = xnode.node().internal_object(); + return parent && node == parent; +} - if (node) - { - if (node->name && (node->header & xml_memory_page_name_allocated_mask) == 0) return node->name; - if (node->value && (node->header & xml_memory_page_value_allocated_mask) == 0) return node->value; - return 0; +PUGI__FN const void* document_order(const xpath_node& xnode) { + xml_node_struct* node = xnode.node().internal_object(); + + if (node) { + if (node->name && (node->header & xml_memory_page_name_allocated_mask) == 0) { + return node->name; + } + if (node->value && (node->header & xml_memory_page_value_allocated_mask) == 0) { + return node->value; } + return 0; + } - xml_attribute_struct* attr = xnode.attribute().internal_object(); + xml_attribute_struct* attr = xnode.attribute().internal_object(); - if (attr) - { - if ((attr->header & xml_memory_page_name_allocated_mask) == 0) return attr->name; - if ((attr->header & xml_memory_page_value_allocated_mask) == 0) return attr->value; - return 0; + if (attr) { + if ((attr->header & xml_memory_page_name_allocated_mask) == 0) { + return attr->name; + } + if ((attr->header & xml_memory_page_value_allocated_mask) == 0) { + return attr->value; } - return 0; } - struct document_order_comparator - { - bool operator()(const xpath_node& lhs, const xpath_node& rhs) const - { - // optimized document order based check - const void* lo = document_order(lhs); - const void* ro = document_order(rhs); + return 0; +} - if (lo && ro) return lo < ro; +struct document_order_comparator { + bool operator()(const xpath_node& lhs, const xpath_node& rhs) const { + // optimized document order based check + const void* lo = document_order(lhs); + const void* ro = document_order(rhs); - // slow comparison - xml_node ln = lhs.node(), rn = rhs.node(); + if (lo && ro) { + return lo < ro; + } - // compare attributes - if (lhs.attribute() && rhs.attribute()) - { - // shared parent - if (lhs.parent() == rhs.parent()) - { - // determine sibling order - for (xml_attribute a = lhs.attribute(); a; a = a.next_attribute()) - if (a == rhs.attribute()) - return true; + // slow comparison + xml_node ln = lhs.node(), rn = rhs.node(); - return false; + // compare attributes + if (lhs.attribute() && rhs.attribute()) { + // shared parent + if (lhs.parent() == rhs.parent()) { + // determine sibling order + for (xml_attribute a = lhs.attribute(); a; a = a.next_attribute()) { + if (a == rhs.attribute()) { + return true; + } } - // compare attribute parents - ln = lhs.parent(); - rn = rhs.parent(); + return false; } - else if (lhs.attribute()) - { - // attributes go after the parent element - if (lhs.parent() == rhs.node()) return false; - ln = lhs.parent(); + // compare attribute parents + ln = lhs.parent(); + rn = rhs.parent(); + } else if (lhs.attribute()) { + // attributes go after the parent element + if (lhs.parent() == rhs.node()) { + return false; } - else if (rhs.attribute()) - { - // attributes go after the parent element - if (rhs.parent() == lhs.node()) return true; - rn = rhs.parent(); + ln = lhs.parent(); + } else if (rhs.attribute()) { + // attributes go after the parent element + if (rhs.parent() == lhs.node()) { + return true; } - if (ln == rn) return false; - - unsigned int lh = node_height(ln); - unsigned int rh = node_height(rn); - - return node_is_before(ln, lh, rn, rh); + rn = rhs.parent(); } - }; - struct duplicate_comparator - { - bool operator()(const xpath_node& lhs, const xpath_node& rhs) const - { - if (lhs.attribute()) return rhs.attribute() ? lhs.attribute() < rhs.attribute() : true; - else return rhs.attribute() ? false : lhs.node() < rhs.node(); + if (ln == rn) { + return false; } - }; - PUGI__FN double gen_nan() - { - #if defined(__STDC_IEC_559__) || ((FLT_RADIX - 0 == 2) && (FLT_MAX_EXP - 0 == 128) && (FLT_MANT_DIG - 0 == 24)) - union { float f; uint32_t i; } u[sizeof(float) == sizeof(uint32_t) ? 1 : -1]; - u[0].i = 0x7fc00000; - return u[0].f; - #else - // fallback - const volatile double zero = 0.0; - return zero / zero; - #endif + unsigned int lh = node_height(ln); + unsigned int rh = node_height(rn); + + return node_is_before(ln, lh, rn, rh); } +}; - PUGI__FN bool is_nan(double value) - { - #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) - return !!_isnan(value); - #elif defined(fpclassify) && defined(FP_NAN) - return fpclassify(value) == FP_NAN; - #else - // fallback - const volatile double v = value; - return v != v; - #endif +struct duplicate_comparator { + bool operator()(const xpath_node& lhs, const xpath_node& rhs) const { + if (lhs.attribute()) { + return rhs.attribute() ? lhs.attribute() < rhs.attribute() : true; + } else { + return rhs.attribute() ? false : lhs.node() < rhs.node(); + } } +}; - PUGI__FN const char_t* convert_number_to_string_special(double value) - { - #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) - if (_finite(value)) return (value == 0) ? PUGIXML_TEXT("0") : 0; - if (_isnan(value)) return PUGIXML_TEXT("NaN"); - return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); - #elif defined(fpclassify) && defined(FP_NAN) && defined(FP_INFINITE) && defined(FP_ZERO) - switch (fpclassify(value)) - { +PUGI__FN double gen_nan() { + #if defined(__STDC_IEC_559__) || ((FLT_RADIX - 0 == 2) && (FLT_MAX_EXP - 0 == 128) && (FLT_MANT_DIG - 0 == 24)) + union { + float f; + uint32_t i; + } u[sizeof(float) == sizeof(uint32_t) ? 1 : -1]; + u[0].i = 0x7fc00000; + return u[0].f; + #else + // fallback + const volatile double zero = 0.0; + return zero / zero; + #endif +} + +PUGI__FN bool is_nan(double value) { + #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) + return !!_isnan(value); + #elif defined(fpclassify) && defined(FP_NAN) + return fpclassify(value) == FP_NAN; + #else + // fallback + const volatile double v = value; + return v != v; + #endif +} + +PUGI__FN const char_t* convert_number_to_string_special(double value) { + #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) + if (_finite(value)) { + return (value == 0) ? PUGIXML_TEXT("0") : 0; + } + if (_isnan(value)) { + return PUGIXML_TEXT("NaN"); + } + return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + #elif defined(fpclassify) && defined(FP_NAN) && defined(FP_INFINITE) && defined(FP_ZERO) + switch (fpclassify(value)) { case FP_NAN: return PUGIXML_TEXT("NaN"); @@ -6274,431 +6272,442 @@ PUGI__NS_BEGIN default: return 0; - } - #else - // fallback - const volatile double v = value; - - if (v == 0) return PUGIXML_TEXT("0"); - if (v != v) return PUGIXML_TEXT("NaN"); - if (v * 2 == v) return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); - return 0; - #endif } + #else + // fallback + const volatile double v = value; - PUGI__FN bool convert_number_to_boolean(double value) - { - return (value != 0 && !is_nan(value)); + if (v == 0) { + return PUGIXML_TEXT("0"); + } + if (v != v) { + return PUGIXML_TEXT("NaN"); } + if (v * 2 == v) { + return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + } + return 0; + #endif +} - PUGI__FN void truncate_zeros(char* begin, char* end) - { - while (begin != end && end[-1] == '0') end--; +PUGI__FN bool convert_number_to_boolean(double value) { + return (value != 0 && !is_nan(value)); +} - *end = 0; +PUGI__FN void truncate_zeros(char* begin, char* end) { + while (begin != end && end[-1] == '0') { + end--; } - // gets mantissa digits in the form of 0.xxxxx with 0. implied and the exponent -#if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE) - PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) - { - // get base values - int sign, exponent; - _ecvt_s(buffer, buffer_size, value, DBL_DIG + 1, &exponent, &sign); + *end = 0; +} - // truncate redundant zeros - truncate_zeros(buffer, buffer + strlen(buffer)); + // gets mantissa digits in the form of 0.xxxxx with 0. implied and the exponent + #if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE) +PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) { + // get base values + int sign, exponent; + _ecvt_s(buffer, buffer_size, value, DBL_DIG + 1, &exponent, &sign); + + // truncate redundant zeros + truncate_zeros(buffer, buffer + strlen(buffer)); + + // fill results + *out_mantissa = buffer; + *out_exponent = exponent; +} + #else +PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) { + // get a scientific notation value with IEEE DBL_DIG decimals + sprintf(buffer, "%.*e", DBL_DIG, value); + assert(strlen(buffer) < buffer_size); + (void)!buffer_size; - // fill results - *out_mantissa = buffer; - *out_exponent = exponent; - } -#else - PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) - { - // get a scientific notation value with IEEE DBL_DIG decimals - sprintf(buffer, "%.*e", DBL_DIG, value); - assert(strlen(buffer) < buffer_size); - (void)!buffer_size; + // get the exponent (possibly negative) + char* exponent_string = strchr(buffer, 'e'); + assert(exponent_string); - // get the exponent (possibly negative) - char* exponent_string = strchr(buffer, 'e'); - assert(exponent_string); + int exponent = atoi(exponent_string + 1); - int exponent = atoi(exponent_string + 1); + // extract mantissa string: skip sign + char* mantissa = buffer[0] == '-' ? buffer + 1 : buffer; + assert(mantissa[0] != '0' && mantissa[1] == '.'); - // extract mantissa string: skip sign - char* mantissa = buffer[0] == '-' ? buffer + 1 : buffer; - assert(mantissa[0] != '0' && mantissa[1] == '.'); + // divide mantissa by 10 to eliminate integer part + mantissa[1] = mantissa[0]; + mantissa++; + exponent++; - // divide mantissa by 10 to eliminate integer part - mantissa[1] = mantissa[0]; - mantissa++; - exponent++; + // remove extra mantissa digits and zero-terminate mantissa + truncate_zeros(mantissa, exponent_string); - // remove extra mantissa digits and zero-terminate mantissa - truncate_zeros(mantissa, exponent_string); + // fill results + *out_mantissa = mantissa; + *out_exponent = exponent; +} + #endif - // fill results - *out_mantissa = mantissa; - *out_exponent = exponent; +PUGI__FN xpath_string convert_number_to_string(double value, xpath_allocator* alloc) { + // try special number conversion + const char_t* special = convert_number_to_string_special(value); + if (special) { + return xpath_string_const(special); } -#endif - - PUGI__FN xpath_string convert_number_to_string(double value, xpath_allocator* alloc) - { - // try special number conversion - const char_t* special = convert_number_to_string_special(value); - if (special) return xpath_string_const(special); - // get mantissa + exponent form - char mantissa_buffer[64]; + // get mantissa + exponent form + char mantissa_buffer[64]; - char* mantissa; - int exponent; - convert_number_to_mantissa_exponent(value, mantissa_buffer, sizeof(mantissa_buffer), &mantissa, &exponent); + char* mantissa; + int exponent; + convert_number_to_mantissa_exponent(value, mantissa_buffer, sizeof(mantissa_buffer), &mantissa, &exponent); - // make the number! - char_t result[512]; - char_t* s = result; + // make the number! + char_t result[512]; + char_t* s = result; - // sign - if (value < 0) *s++ = '-'; + // sign + if (value < 0) { + *s++ = '-'; + } - // integer part - if (exponent <= 0) - { - *s++ = '0'; - } - else - { - while (exponent > 0) - { - assert(*mantissa == 0 || static_cast(*mantissa - '0') <= 9); - *s++ = *mantissa ? *mantissa++ : '0'; - exponent--; - } + // integer part + if (exponent <= 0) { + *s++ = '0'; + } else { + while (exponent > 0) { + assert(*mantissa == 0 || static_cast(*mantissa - '0') <= 9); + *s++ = *mantissa ? *mantissa++ : '0'; + exponent--; } + } - // fractional part - if (*mantissa) - { - // decimal point - *s++ = '.'; + // fractional part + if (*mantissa) { + // decimal point + *s++ = '.'; - // extra zeroes from negative exponent - while (exponent < 0) - { - *s++ = '0'; - exponent++; - } + // extra zeroes from negative exponent + while (exponent < 0) { + *s++ = '0'; + exponent++; + } - // extra mantissa digits - while (*mantissa) - { - assert(static_cast(*mantissa - '0') <= 9); - *s++ = *mantissa++; - } + // extra mantissa digits + while (*mantissa) { + assert(static_cast(*mantissa - '0') <= 9); + *s++ = *mantissa++; } + } + + // zero-terminate + assert(s < result + sizeof(result) / sizeof(result[0])); + *s = 0; - // zero-terminate - assert(s < result + sizeof(result) / sizeof(result[0])); - *s = 0; + return xpath_string(result, alloc); +} - return xpath_string(result, alloc); +PUGI__FN bool check_string_to_number_format(const char_t* string) { + // parse leading whitespace + while (PUGI__IS_CHARTYPE(*string, ct_space)) { + ++string; } - PUGI__FN bool check_string_to_number_format(const char_t* string) - { - // parse leading whitespace - while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; + // parse sign + if (*string == '-') { + ++string; + } - // parse sign - if (*string == '-') ++string; + if (!*string) { + return false; + } - if (!*string) return false; + // if there is no integer part, there should be a decimal part with at least one digit + if (!PUGI__IS_CHARTYPEX(string[0], ctx_digit) && (string[0] != '.' || !PUGI__IS_CHARTYPEX(string[1], ctx_digit))) { + return false; + } - // if there is no integer part, there should be a decimal part with at least one digit - if (!PUGI__IS_CHARTYPEX(string[0], ctx_digit) && (string[0] != '.' || !PUGI__IS_CHARTYPEX(string[1], ctx_digit))) return false; + // parse integer part + while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) { + ++string; + } - // parse integer part - while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; + // parse decimal part + if (*string == '.') { + ++string; - // parse decimal part - if (*string == '.') - { + while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) { ++string; - - while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; } + } - // parse trailing whitespace - while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; - - return *string == 0; + // parse trailing whitespace + while (PUGI__IS_CHARTYPE(*string, ct_space)) { + ++string; } - PUGI__FN double convert_string_to_number(const char_t* string) - { - // check string format - if (!check_string_to_number_format(string)) return gen_nan(); + return *string == 0; +} - // parse string - #ifdef PUGIXML_WCHAR_MODE - return wcstod(string, 0); - #else - return atof(string); - #endif +PUGI__FN double convert_string_to_number(const char_t* string) { + // check string format + if (!check_string_to_number_format(string)) { + return gen_nan(); } - PUGI__FN bool convert_string_to_number(const char_t* begin, const char_t* end, double* out_result) - { - char_t buffer[32]; + // parse string + #ifdef PUGIXML_WCHAR_MODE + return wcstod(string, 0); + #else + return atof(string); + #endif +} - size_t length = static_cast(end - begin); - char_t* scratch = buffer; +PUGI__FN bool convert_string_to_number(const char_t* begin, const char_t* end, double* out_result) { + char_t buffer[32]; - if (length >= sizeof(buffer) / sizeof(buffer[0])) - { - // need to make dummy on-heap copy - scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); - if (!scratch) return false; - } + size_t length = static_cast(end - begin); + char_t* scratch = buffer; - // copy string to zero-terminated buffer and perform conversion - memcpy(scratch, begin, length * sizeof(char_t)); - scratch[length] = 0; + if (length >= sizeof(buffer) / sizeof(buffer[0])) { + // need to make dummy on-heap copy + scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!scratch) { + return false; + } + } - *out_result = convert_string_to_number(scratch); + // copy string to zero-terminated buffer and perform conversion + memcpy(scratch, begin, length * sizeof(char_t)); + scratch[length] = 0; - // free dummy buffer - if (scratch != buffer) xml_memory::deallocate(scratch); + *out_result = convert_string_to_number(scratch); - return true; + // free dummy buffer + if (scratch != buffer) { + xml_memory::deallocate(scratch); } - PUGI__FN double round_nearest(double value) - { - return floor(value + 0.5); - } + return true; +} - PUGI__FN double round_nearest_nzero(double value) - { - // same as round_nearest, but returns -0 for [-0.5, -0] - // ceil is used to differentiate between +0 and -0 (we return -0 for [-0.5, -0] and +0 for +0) - return (value >= -0.5 && value <= 0) ? ceil(value) : floor(value + 0.5); - } +PUGI__FN double round_nearest(double value) { + return floor(value + 0.5); +} - PUGI__FN const char_t* qualified_name(const xpath_node& node) - { - return node.attribute() ? node.attribute().name() : node.node().name(); - } +PUGI__FN double round_nearest_nzero(double value) { + // same as round_nearest, but returns -0 for [-0.5, -0] + // ceil is used to differentiate between +0 and -0 (we return -0 for [-0.5, -0] and +0 for +0) + return (value >= -0.5 && value <= 0) ? ceil(value) : floor(value + 0.5); +} - PUGI__FN const char_t* local_name(const xpath_node& node) - { - const char_t* name = qualified_name(node); - const char_t* p = find_char(name, ':'); +PUGI__FN const char_t* qualified_name(const xpath_node& node) { + return node.attribute() ? node.attribute().name() : node.node().name(); +} - return p ? p + 1 : name; - } +PUGI__FN const char_t* local_name(const xpath_node& node) { + const char_t* name = qualified_name(node); + const char_t* p = find_char(name, ':'); - struct namespace_uri_predicate - { - const char_t* prefix; - size_t prefix_length; + return p ? p + 1 : name; +} - namespace_uri_predicate(const char_t* name) - { - const char_t* pos = find_char(name, ':'); +struct namespace_uri_predicate { + const char_t* prefix; + size_t prefix_length; - prefix = pos ? name : 0; - prefix_length = pos ? static_cast(pos - name) : 0; - } + namespace_uri_predicate(const char_t* name) { + const char_t* pos = find_char(name, ':'); - bool operator()(const xml_attribute& a) const - { - const char_t* name = a.name(); + prefix = pos ? name : 0; + prefix_length = pos ? static_cast(pos - name) : 0; + } - if (!starts_with(name, PUGIXML_TEXT("xmlns"))) return false; + bool operator()(const xml_attribute& a) const { + const char_t* name = a.name(); - return prefix ? name[5] == ':' && strequalrange(name + 6, prefix, prefix_length) : name[5] == 0; + if (!starts_with(name, PUGIXML_TEXT("xmlns"))) { + return false; } - }; - PUGI__FN const char_t* namespace_uri(const xml_node& node) - { - namespace_uri_predicate pred = node.name(); + return prefix ? name[5] == ':' && strequalrange(name + 6, prefix, prefix_length) : name[5] == 0; + } +}; - xml_node p = node; +PUGI__FN const char_t* namespace_uri(const xml_node& node) { + namespace_uri_predicate pred = node.name(); - while (p) - { - xml_attribute a = p.find_attribute(pred); + xml_node p = node; - if (a) return a.value(); + while (p) { + xml_attribute a = p.find_attribute(pred); - p = p.parent(); + if (a) { + return a.value(); } - return PUGIXML_TEXT(""); + p = p.parent(); } - PUGI__FN const char_t* namespace_uri(const xml_attribute& attr, const xml_node& parent) - { - namespace_uri_predicate pred = attr.name(); + return PUGIXML_TEXT(""); +} - // Default namespace does not apply to attributes - if (!pred.prefix) return PUGIXML_TEXT(""); +PUGI__FN const char_t* namespace_uri(const xml_attribute& attr, const xml_node& parent) { + namespace_uri_predicate pred = attr.name(); - xml_node p = parent; + // Default namespace does not apply to attributes + if (!pred.prefix) { + return PUGIXML_TEXT(""); + } - while (p) - { - xml_attribute a = p.find_attribute(pred); + xml_node p = parent; - if (a) return a.value(); + while (p) { + xml_attribute a = p.find_attribute(pred); - p = p.parent(); + if (a) { + return a.value(); } - return PUGIXML_TEXT(""); + p = p.parent(); } - PUGI__FN const char_t* namespace_uri(const xpath_node& node) - { - return node.attribute() ? namespace_uri(node.attribute(), node.parent()) : namespace_uri(node.node()); - } + return PUGIXML_TEXT(""); +} - PUGI__FN void normalize_space(char_t* buffer) - { - char_t* write = buffer; +PUGI__FN const char_t* namespace_uri(const xpath_node& node) { + return node.attribute() ? namespace_uri(node.attribute(), node.parent()) : namespace_uri(node.node()); +} - for (char_t* it = buffer; *it; ) - { - char_t ch = *it++; +PUGI__FN void normalize_space(char_t* buffer) { + char_t* write = buffer; + + for (char_t* it = buffer; *it;) { + char_t ch = *it++; - if (PUGI__IS_CHARTYPE(ch, ct_space)) - { - // replace whitespace sequence with single space - while (PUGI__IS_CHARTYPE(*it, ct_space)) it++; + if (PUGI__IS_CHARTYPE(ch, ct_space)) { + // replace whitespace sequence with single space + while (PUGI__IS_CHARTYPE(*it, ct_space)) { + it++; + } - // avoid leading spaces - if (write != buffer) *write++ = ' '; + // avoid leading spaces + if (write != buffer) { + *write++ = ' '; } - else *write++ = ch; + } else { + *write++ = ch; } + } - // remove trailing space - if (write != buffer && PUGI__IS_CHARTYPE(write[-1], ct_space)) write--; - - // zero-terminate - *write = 0; + // remove trailing space + if (write != buffer && PUGI__IS_CHARTYPE(write[-1], ct_space)) { + write--; } - PUGI__FN void translate(char_t* buffer, const char_t* from, const char_t* to) - { - size_t to_length = strlength(to); + // zero-terminate + *write = 0; +} - char_t* write = buffer; +PUGI__FN void translate(char_t* buffer, const char_t* from, const char_t* to) { + size_t to_length = strlength(to); - while (*buffer) - { - PUGI__DMC_VOLATILE char_t ch = *buffer++; + char_t* write = buffer; + + while (*buffer) { + PUGI__DMC_VOLATILE char_t ch = *buffer++; - const char_t* pos = find_char(from, ch); + const char_t* pos = find_char(from, ch); - if (!pos) - *write++ = ch; // do not process - else if (static_cast(pos - from) < to_length) - *write++ = to[pos - from]; // replace + if (!pos) { + *write++ = ch; // do not process + } else if (static_cast(pos - from) < to_length) { + *write++ = to[pos - from]; // replace } + } + + // zero-terminate + *write = 0; +} - // zero-terminate - *write = 0; +struct xpath_variable_boolean : xpath_variable { + xpath_variable_boolean() : + value(false) { } - struct xpath_variable_boolean: xpath_variable - { - xpath_variable_boolean(): value(false) - { - } + bool value; + char_t name[1]; +}; - bool value; - char_t name[1]; - }; +struct xpath_variable_number : xpath_variable { + xpath_variable_number() : + value(0) { + } - struct xpath_variable_number: xpath_variable - { - xpath_variable_number(): value(0) - { - } + double value; + char_t name[1]; +}; - double value; - char_t name[1]; - }; +struct xpath_variable_string : xpath_variable { + xpath_variable_string() : + value(0) { + } - struct xpath_variable_string: xpath_variable - { - xpath_variable_string(): value(0) - { + ~xpath_variable_string() { + if (value) { + xml_memory::deallocate(value); } + } - ~xpath_variable_string() - { - if (value) xml_memory::deallocate(value); - } + char_t* value; + char_t name[1]; +}; - char_t* value; - char_t name[1]; - }; +struct xpath_variable_node_set : xpath_variable { + xpath_node_set value; + char_t name[1]; +}; - struct xpath_variable_node_set: xpath_variable - { - xpath_node_set value; - char_t name[1]; - }; +static const xpath_node_set dummy_node_set; - static const xpath_node_set dummy_node_set; +PUGI__FN unsigned int hash_string(const char_t* str) { + // Jenkins one-at-a-time hash (http://en.wikipedia.org/wiki/Jenkins_hash_function#one-at-a-time) + unsigned int result = 0; - PUGI__FN unsigned int hash_string(const char_t* str) - { - // Jenkins one-at-a-time hash (http://en.wikipedia.org/wiki/Jenkins_hash_function#one-at-a-time) - unsigned int result = 0; + while (*str) { + result += static_cast(*str++); + result += result << 10; + result ^= result >> 6; + } - while (*str) - { - result += static_cast(*str++); - result += result << 10; - result ^= result >> 6; - } + result += result << 3; + result ^= result >> 11; + result += result << 15; - result += result << 3; - result ^= result >> 11; - result += result << 15; + return result; +} - return result; +template +PUGI__FN T* new_xpath_variable(const char_t* name) { + size_t length = strlength(name); + if (length == 0) { + return 0; // empty variable names are invalid } - template PUGI__FN T* new_xpath_variable(const char_t* name) - { - size_t length = strlength(name); - if (length == 0) return 0; // empty variable names are invalid - - // $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters - void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t)); - if (!memory) return 0; + // $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters + void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t)); + if (!memory) { + return 0; + } - T* result = new (memory) T(); + T* result = new (memory) T(); - memcpy(result->name, name, (length + 1) * sizeof(char_t)); + memcpy(result->name, name, (length + 1) * sizeof(char_t)); - return result; - } + return result; +} - PUGI__FN xpath_variable* new_xpath_variable(xpath_value_type type, const char_t* name) - { - switch (type) - { +PUGI__FN xpath_variable* new_xpath_variable(xpath_value_type type, const char_t* name) { + switch (type) { case xpath_type_node_set: return new_xpath_variable(name); @@ -6713,19 +6722,17 @@ PUGI__NS_BEGIN default: return 0; - } } +} - template PUGI__FN void delete_xpath_variable(T* var) - { - var->~T(); - xml_memory::deallocate(var); - } +template +PUGI__FN void delete_xpath_variable(T* var) { + var->~T(); + xml_memory::deallocate(var); +} - PUGI__FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var) - { - switch (type) - { +PUGI__FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var) { + switch (type) { case xpath_type_node_set: delete_xpath_variable(static_cast(var)); break; @@ -6744,60 +6751,62 @@ PUGI__NS_BEGIN default: assert(!"Invalid variable type"); - } } +} - PUGI__FN xpath_variable* get_variable(xpath_variable_set* set, const char_t* begin, const char_t* end) - { - char_t buffer[32]; +PUGI__FN xpath_variable* get_variable(xpath_variable_set* set, const char_t* begin, const char_t* end) { + char_t buffer[32]; - size_t length = static_cast(end - begin); - char_t* scratch = buffer; + size_t length = static_cast(end - begin); + char_t* scratch = buffer; - if (length >= sizeof(buffer) / sizeof(buffer[0])) - { - // need to make dummy on-heap copy - scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); - if (!scratch) return 0; + if (length >= sizeof(buffer) / sizeof(buffer[0])) { + // need to make dummy on-heap copy + scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!scratch) { + return 0; } + } - // copy string to zero-terminated buffer and perform lookup - memcpy(scratch, begin, length * sizeof(char_t)); - scratch[length] = 0; - - xpath_variable* result = set->get(scratch); + // copy string to zero-terminated buffer and perform lookup + memcpy(scratch, begin, length * sizeof(char_t)); + scratch[length] = 0; - // free dummy buffer - if (scratch != buffer) xml_memory::deallocate(scratch); + xpath_variable* result = set->get(scratch); - return result; + // free dummy buffer + if (scratch != buffer) { + xml_memory::deallocate(scratch); } + + return result; +} PUGI__NS_END // Internal node set class PUGI__NS_BEGIN - PUGI__FN xpath_node_set::type_t xpath_sort(xpath_node* begin, xpath_node* end, xpath_node_set::type_t type, bool rev) - { - xpath_node_set::type_t order = rev ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; - - if (type == xpath_node_set::type_unsorted) - { - sort(begin, end, document_order_comparator()); +PUGI__FN xpath_node_set::type_t xpath_sort(xpath_node* begin, xpath_node* end, xpath_node_set::type_t type, bool rev) { + xpath_node_set::type_t order = rev ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; - type = xpath_node_set::type_sorted; - } + if (type == xpath_node_set::type_unsorted) { + sort(begin, end, document_order_comparator()); - if (type != order) reverse(begin, end); + type = xpath_node_set::type_sorted; + } - return order; + if (type != order) { + reverse(begin, end); } - PUGI__FN xpath_node xpath_first(const xpath_node* begin, const xpath_node* end, xpath_node_set::type_t type) - { - if (begin == end) return xpath_node(); + return order; +} - switch (type) - { +PUGI__FN xpath_node xpath_first(const xpath_node* begin, const xpath_node* end, xpath_node_set::type_t type) { + if (begin == end) { + return xpath_node(); + } + + switch (type) { case xpath_node_set::type_sorted: return *begin; @@ -6810,250 +6819,221 @@ PUGI__NS_BEGIN default: assert(!"Invalid node set type"); return xpath_node(); - } } +} - class xpath_node_set_raw - { - xpath_node_set::type_t _type; - - xpath_node* _begin; - xpath_node* _end; - xpath_node* _eos; +class xpath_node_set_raw { + xpath_node_set::type_t _type; - public: - xpath_node_set_raw(): _type(xpath_node_set::type_unsorted), _begin(0), _end(0), _eos(0) - { - } + xpath_node* _begin; + xpath_node* _end; + xpath_node* _eos; - xpath_node* begin() const - { - return _begin; - } +public: + xpath_node_set_raw() : + _type(xpath_node_set::type_unsorted), _begin(0), _end(0), _eos(0) { + } - xpath_node* end() const - { - return _end; - } + xpath_node* begin() const { + return _begin; + } - bool empty() const - { - return _begin == _end; - } + xpath_node* end() const { + return _end; + } - size_t size() const - { - return static_cast(_end - _begin); - } + bool empty() const { + return _begin == _end; + } - xpath_node first() const - { - return xpath_first(_begin, _end, _type); - } + size_t size() const { + return static_cast(_end - _begin); + } - void push_back(const xpath_node& node, xpath_allocator* alloc) - { - if (_end == _eos) - { - size_t capacity = static_cast(_eos - _begin); + xpath_node first() const { + return xpath_first(_begin, _end, _type); + } - // get new capacity (1.5x rule) - size_t new_capacity = capacity + capacity / 2 + 1; + void push_back(const xpath_node& node, xpath_allocator* alloc) { + if (_end == _eos) { + size_t capacity = static_cast(_eos - _begin); - // reallocate the old array or allocate a new one - xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), new_capacity * sizeof(xpath_node))); - assert(data); + // get new capacity (1.5x rule) + size_t new_capacity = capacity + capacity / 2 + 1; - // finalize - _begin = data; - _end = data + capacity; - _eos = data + new_capacity; - } + // reallocate the old array or allocate a new one + xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), new_capacity * sizeof(xpath_node))); + assert(data); - *_end++ = node; + // finalize + _begin = data; + _end = data + capacity; + _eos = data + new_capacity; } - void append(const xpath_node* begin_, const xpath_node* end_, xpath_allocator* alloc) - { - size_t size_ = static_cast(_end - _begin); - size_t capacity = static_cast(_eos - _begin); - size_t count = static_cast(end_ - begin_); + *_end++ = node; + } - if (size_ + count > capacity) - { - // reallocate the old array or allocate a new one - xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), (size_ + count) * sizeof(xpath_node))); - assert(data); + void append(const xpath_node* begin_, const xpath_node* end_, xpath_allocator* alloc) { + size_t size_ = static_cast(_end - _begin); + size_t capacity = static_cast(_eos - _begin); + size_t count = static_cast(end_ - begin_); - // finalize - _begin = data; - _end = data + size_; - _eos = data + size_ + count; - } + if (size_ + count > capacity) { + // reallocate the old array or allocate a new one + xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), (size_ + count) * sizeof(xpath_node))); + assert(data); - memcpy(_end, begin_, count * sizeof(xpath_node)); - _end += count; + // finalize + _begin = data; + _end = data + size_; + _eos = data + size_ + count; } - void sort_do() - { - _type = xpath_sort(_begin, _end, _type, false); - } + memcpy(_end, begin_, count * sizeof(xpath_node)); + _end += count; + } - void truncate(xpath_node* pos) - { - assert(_begin <= pos && pos <= _end); + void sort_do() { + _type = xpath_sort(_begin, _end, _type, false); + } - _end = pos; - } + void truncate(xpath_node* pos) { + assert(_begin <= pos && pos <= _end); - void remove_duplicates() - { - if (_type == xpath_node_set::type_unsorted) - sort(_begin, _end, duplicate_comparator()); + _end = pos; + } - _end = unique(_begin, _end); + void remove_duplicates() { + if (_type == xpath_node_set::type_unsorted) { + sort(_begin, _end, duplicate_comparator()); } - xpath_node_set::type_t type() const - { - return _type; - } + _end = unique(_begin, _end); + } - void set_type(xpath_node_set::type_t value) - { - _type = value; - } - }; + xpath_node_set::type_t type() const { + return _type; + } + + void set_type(xpath_node_set::type_t value) { + _type = value; + } +}; PUGI__NS_END PUGI__NS_BEGIN - struct xpath_context - { - xpath_node n; - size_t position, size; - - xpath_context(const xpath_node& n_, size_t position_, size_t size_): n(n_), position(position_), size(size_) - { - } - }; - - enum lexeme_t - { - lex_none = 0, - lex_equal, - lex_not_equal, - lex_less, - lex_greater, - lex_less_or_equal, - lex_greater_or_equal, - lex_plus, - lex_minus, - lex_multiply, - lex_union, - lex_var_ref, - lex_open_brace, - lex_close_brace, - lex_quoted_string, - lex_number, - lex_slash, - lex_double_slash, - lex_open_square_brace, - lex_close_square_brace, - lex_string, - lex_comma, - lex_axis_attribute, - lex_dot, - lex_double_dot, - lex_double_colon, - lex_eof - }; - - struct xpath_lexer_string - { - const char_t* begin; - const char_t* end; +struct xpath_context { + xpath_node n; + size_t position, size; + + xpath_context(const xpath_node& n_, size_t position_, size_t size_) : + n(n_), position(position_), size(size_) { + } +}; + +enum lexeme_t { + lex_none = 0, + lex_equal, + lex_not_equal, + lex_less, + lex_greater, + lex_less_or_equal, + lex_greater_or_equal, + lex_plus, + lex_minus, + lex_multiply, + lex_union, + lex_var_ref, + lex_open_brace, + lex_close_brace, + lex_quoted_string, + lex_number, + lex_slash, + lex_double_slash, + lex_open_square_brace, + lex_close_square_brace, + lex_string, + lex_comma, + lex_axis_attribute, + lex_dot, + lex_double_dot, + lex_double_colon, + lex_eof +}; + +struct xpath_lexer_string { + const char_t* begin; + const char_t* end; + + xpath_lexer_string() : + begin(0), end(0) { + } + + bool operator==(const char_t* other) const { + size_t length = static_cast(end - begin); - xpath_lexer_string(): begin(0), end(0) - { - } + return strequalrange(other, begin, length); + } +}; - bool operator==(const char_t* other) const - { - size_t length = static_cast(end - begin); +class xpath_lexer { + const char_t* _cur; + const char_t* _cur_lexeme_pos; + xpath_lexer_string _cur_lexeme_contents; - return strequalrange(other, begin, length); - } - }; + lexeme_t _cur_lexeme; - class xpath_lexer - { - const char_t* _cur; - const char_t* _cur_lexeme_pos; - xpath_lexer_string _cur_lexeme_contents; +public: + explicit xpath_lexer(const char_t* query) : + _cur(query) { + next(); + } - lexeme_t _cur_lexeme; + const char_t* state() const { + return _cur; + } - public: - explicit xpath_lexer(const char_t* query): _cur(query) - { - next(); - } + void next() { + const char_t* cur = _cur; - const char_t* state() const - { - return _cur; + while (PUGI__IS_CHARTYPE(*cur, ct_space)) { + ++cur; } - void next() - { - const char_t* cur = _cur; - - while (PUGI__IS_CHARTYPE(*cur, ct_space)) ++cur; - - // save lexeme position for error reporting - _cur_lexeme_pos = cur; + // save lexeme position for error reporting + _cur_lexeme_pos = cur; - switch (*cur) - { + switch (*cur) { case 0: _cur_lexeme = lex_eof; break; case '>': - if (*(cur+1) == '=') - { + if (*(cur + 1) == '=') { cur += 2; _cur_lexeme = lex_greater_or_equal; - } - else - { + } else { cur += 1; _cur_lexeme = lex_greater; } break; case '<': - if (*(cur+1) == '=') - { + if (*(cur + 1) == '=') { cur += 2; _cur_lexeme = lex_less_or_equal; - } - else - { + } else { cur += 1; _cur_lexeme = lex_less; } break; case '!': - if (*(cur+1) == '=') - { + if (*(cur + 1) == '=') { cur += 2; _cur_lexeme = lex_not_equal; - } - else - { + } else { _cur_lexeme = lex_none; } break; @@ -7091,25 +7071,26 @@ PUGI__NS_BEGIN case '$': cur += 1; - if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) - { + if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) { _cur_lexeme_contents.begin = cur; - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) { + cur++; + } if (cur[0] == ':' && PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // qname { cur++; // : - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) { + cur++; + } } _cur_lexeme_contents.end = cur; _cur_lexeme = lex_var_ref; - } - else - { + } else { _cur_lexeme = lex_none; } @@ -7146,38 +7127,32 @@ PUGI__NS_BEGIN break; case '/': - if (*(cur+1) == '/') - { + if (*(cur + 1) == '/') { cur += 2; _cur_lexeme = lex_double_slash; - } - else - { + } else { cur += 1; _cur_lexeme = lex_slash; } break; case '.': - if (*(cur+1) == '.') - { + if (*(cur + 1) == '.') { cur += 2; _cur_lexeme = lex_double_dot; - } - else if (PUGI__IS_CHARTYPEX(*(cur+1), ctx_digit)) - { + } else if (PUGI__IS_CHARTYPEX(*(cur + 1), ctx_digit)) { _cur_lexeme_contents.begin = cur; // . ++cur; - while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) { + cur++; + } _cur_lexeme_contents.end = cur; _cur_lexeme = lex_number; - } - else - { + } else { cur += 1; _cur_lexeme = lex_dot; } @@ -7190,20 +7165,20 @@ PUGI__NS_BEGIN break; case '"': - case '\'': - { + case '\'': { char_t terminator = *cur; ++cur; _cur_lexeme_contents.begin = cur; - while (*cur && *cur != terminator) cur++; + while (*cur && *cur != terminator) { + cur++; + } _cur_lexeme_contents.end = cur; - if (!*cur) + if (!*cur) { _cur_lexeme = lex_none; - else - { + } else { cur += 1; _cur_lexeme = lex_quoted_string; } @@ -7212,425 +7187,400 @@ PUGI__NS_BEGIN } case ':': - if (*(cur+1) == ':') - { + if (*(cur + 1) == ':') { cur += 2; _cur_lexeme = lex_double_colon; - } - else - { + } else { _cur_lexeme = lex_none; } break; default: - if (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) - { + if (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) { _cur_lexeme_contents.begin = cur; - while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) { + cur++; + } - if (*cur == '.') - { + if (*cur == '.') { cur++; - while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) { + cur++; + } } _cur_lexeme_contents.end = cur; _cur_lexeme = lex_number; - } - else if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) - { + } else if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) { _cur_lexeme_contents.begin = cur; - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) { + cur++; + } - if (cur[0] == ':') - { + if (cur[0] == ':') { if (cur[1] == '*') // namespace test ncname:* { cur += 2; // :* - } - else if (PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // namespace test qname + } else if (PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // namespace test qname { cur++; // : - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) { + cur++; + } } } _cur_lexeme_contents.end = cur; _cur_lexeme = lex_string; - } - else - { + } else { _cur_lexeme = lex_none; } - } - - _cur = cur; - } - - lexeme_t current() const - { - return _cur_lexeme; - } - - const char_t* current_pos() const - { - return _cur_lexeme_pos; - } - - const xpath_lexer_string& contents() const - { - assert(_cur_lexeme == lex_var_ref || _cur_lexeme == lex_number || _cur_lexeme == lex_string || _cur_lexeme == lex_quoted_string); - - return _cur_lexeme_contents; } - }; - - enum ast_type_t - { - ast_op_or, // left or right - ast_op_and, // left and right - ast_op_equal, // left = right - ast_op_not_equal, // left != right - ast_op_less, // left < right - ast_op_greater, // left > right - ast_op_less_or_equal, // left <= right - ast_op_greater_or_equal, // left >= right - ast_op_add, // left + right - ast_op_subtract, // left - right - ast_op_multiply, // left * right - ast_op_divide, // left / right - ast_op_mod, // left % right - ast_op_negate, // left - right - ast_op_union, // left | right - ast_predicate, // apply predicate to set; next points to next predicate - ast_filter, // select * from left where right - ast_filter_posinv, // select * from left where right; proximity position invariant - ast_string_constant, // string constant - ast_number_constant, // number constant - ast_variable, // variable - ast_func_last, // last() - ast_func_position, // position() - ast_func_count, // count(left) - ast_func_id, // id(left) - ast_func_local_name_0, // local-name() - ast_func_local_name_1, // local-name(left) - ast_func_namespace_uri_0, // namespace-uri() - ast_func_namespace_uri_1, // namespace-uri(left) - ast_func_name_0, // name() - ast_func_name_1, // name(left) - ast_func_string_0, // string() - ast_func_string_1, // string(left) - ast_func_concat, // concat(left, right, siblings) - ast_func_starts_with, // starts_with(left, right) - ast_func_contains, // contains(left, right) - ast_func_substring_before, // substring-before(left, right) - ast_func_substring_after, // substring-after(left, right) - ast_func_substring_2, // substring(left, right) - ast_func_substring_3, // substring(left, right, third) - ast_func_string_length_0, // string-length() - ast_func_string_length_1, // string-length(left) - ast_func_normalize_space_0, // normalize-space() - ast_func_normalize_space_1, // normalize-space(left) - ast_func_translate, // translate(left, right, third) - ast_func_boolean, // boolean(left) - ast_func_not, // not(left) - ast_func_true, // true() - ast_func_false, // false() - ast_func_lang, // lang(left) - ast_func_number_0, // number() - ast_func_number_1, // number(left) - ast_func_sum, // sum(left) - ast_func_floor, // floor(left) - ast_func_ceiling, // ceiling(left) - ast_func_round, // round(left) - ast_step, // process set left with step - ast_step_root // select root node - }; - - enum axis_t - { - axis_ancestor, - axis_ancestor_or_self, - axis_attribute, - axis_child, - axis_descendant, - axis_descendant_or_self, - axis_following, - axis_following_sibling, - axis_namespace, - axis_parent, - axis_preceding, - axis_preceding_sibling, - axis_self - }; - - enum nodetest_t - { - nodetest_none, - nodetest_name, - nodetest_type_node, - nodetest_type_comment, - nodetest_type_pi, - nodetest_type_text, - nodetest_pi, - nodetest_all, - nodetest_all_in_namespace - }; - - template struct axis_to_type - { - static const axis_t axis; - }; - - template const axis_t axis_to_type::axis = N; - class xpath_ast_node - { - private: - // node type - char _type; - char _rettype; - - // for ast_step / ast_predicate - char _axis; - char _test; - - // tree node structure - xpath_ast_node* _left; - xpath_ast_node* _right; - xpath_ast_node* _next; - - union - { - // value for ast_string_constant - const char_t* string; - // value for ast_number_constant - double number; - // variable for ast_variable - xpath_variable* variable; - // node test for ast_step (node name/namespace/node type/pi target) - const char_t* nodetest; - } _data; - - xpath_ast_node(const xpath_ast_node&); - xpath_ast_node& operator=(const xpath_ast_node&); - - template static bool compare_eq(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) - { - xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); - - if (lt != xpath_type_node_set && rt != xpath_type_node_set) - { - if (lt == xpath_type_boolean || rt == xpath_type_boolean) - return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); - else if (lt == xpath_type_number || rt == xpath_type_number) - return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); - else if (lt == xpath_type_string || rt == xpath_type_string) - { - xpath_allocator_capture cr(stack.result); - - xpath_string ls = lhs->eval_string(c, stack); - xpath_string rs = rhs->eval_string(c, stack); - - return comp(ls, rs); - } - } - else if (lt == xpath_type_node_set && rt == xpath_type_node_set) - { + _cur = cur; + } + + lexeme_t current() const { + return _cur_lexeme; + } + + const char_t* current_pos() const { + return _cur_lexeme_pos; + } + + const xpath_lexer_string& contents() const { + assert(_cur_lexeme == lex_var_ref || _cur_lexeme == lex_number || _cur_lexeme == lex_string || _cur_lexeme == lex_quoted_string); + + return _cur_lexeme_contents; + } +}; + +enum ast_type_t { + ast_op_or, // left or right + ast_op_and, // left and right + ast_op_equal, // left = right + ast_op_not_equal, // left != right + ast_op_less, // left < right + ast_op_greater, // left > right + ast_op_less_or_equal, // left <= right + ast_op_greater_or_equal, // left >= right + ast_op_add, // left + right + ast_op_subtract, // left - right + ast_op_multiply, // left * right + ast_op_divide, // left / right + ast_op_mod, // left % right + ast_op_negate, // left - right + ast_op_union, // left | right + ast_predicate, // apply predicate to set; next points to next predicate + ast_filter, // select * from left where right + ast_filter_posinv, // select * from left where right; proximity position invariant + ast_string_constant, // string constant + ast_number_constant, // number constant + ast_variable, // variable + ast_func_last, // last() + ast_func_position, // position() + ast_func_count, // count(left) + ast_func_id, // id(left) + ast_func_local_name_0, // local-name() + ast_func_local_name_1, // local-name(left) + ast_func_namespace_uri_0, // namespace-uri() + ast_func_namespace_uri_1, // namespace-uri(left) + ast_func_name_0, // name() + ast_func_name_1, // name(left) + ast_func_string_0, // string() + ast_func_string_1, // string(left) + ast_func_concat, // concat(left, right, siblings) + ast_func_starts_with, // starts_with(left, right) + ast_func_contains, // contains(left, right) + ast_func_substring_before, // substring-before(left, right) + ast_func_substring_after, // substring-after(left, right) + ast_func_substring_2, // substring(left, right) + ast_func_substring_3, // substring(left, right, third) + ast_func_string_length_0, // string-length() + ast_func_string_length_1, // string-length(left) + ast_func_normalize_space_0, // normalize-space() + ast_func_normalize_space_1, // normalize-space(left) + ast_func_translate, // translate(left, right, third) + ast_func_boolean, // boolean(left) + ast_func_not, // not(left) + ast_func_true, // true() + ast_func_false, // false() + ast_func_lang, // lang(left) + ast_func_number_0, // number() + ast_func_number_1, // number(left) + ast_func_sum, // sum(left) + ast_func_floor, // floor(left) + ast_func_ceiling, // ceiling(left) + ast_func_round, // round(left) + ast_step, // process set left with step + ast_step_root // select root node +}; + +enum axis_t { + axis_ancestor, + axis_ancestor_or_self, + axis_attribute, + axis_child, + axis_descendant, + axis_descendant_or_self, + axis_following, + axis_following_sibling, + axis_namespace, + axis_parent, + axis_preceding, + axis_preceding_sibling, + axis_self +}; + +enum nodetest_t { + nodetest_none, + nodetest_name, + nodetest_type_node, + nodetest_type_comment, + nodetest_type_pi, + nodetest_type_text, + nodetest_pi, + nodetest_all, + nodetest_all_in_namespace +}; + +template +struct axis_to_type { + static const axis_t axis; +}; + +template +const axis_t axis_to_type::axis = N; + +class xpath_ast_node { +private: + // node type + char _type; + char _rettype; + + // for ast_step / ast_predicate + char _axis; + char _test; + + // tree node structure + xpath_ast_node* _left; + xpath_ast_node* _right; + xpath_ast_node* _next; + + union { + // value for ast_string_constant + const char_t* string; + // value for ast_number_constant + double number; + // variable for ast_variable + xpath_variable* variable; + // node test for ast_step (node name/namespace/node type/pi target) + const char_t* nodetest; + } _data; + + xpath_ast_node(const xpath_ast_node&); + xpath_ast_node& operator=(const xpath_ast_node&); + + template + static bool compare_eq(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) { + xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); + + if (lt != xpath_type_node_set && rt != xpath_type_node_set) { + if (lt == xpath_type_boolean || rt == xpath_type_boolean) { + return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); + } else if (lt == xpath_type_number || rt == xpath_type_number) { + return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); + } else if (lt == xpath_type_string || rt == xpath_type_string) { xpath_allocator_capture cr(stack.result); - xpath_node_set_raw ls = lhs->eval_node_set(c, stack); - xpath_node_set_raw rs = rhs->eval_node_set(c, stack); - - for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture cri(stack.result); - - if (comp(string_value(*li, stack.result), string_value(*ri, stack.result))) - return true; - } - - return false; - } - else - { - if (lt == xpath_type_node_set) - { - swap(lhs, rhs); - swap(lt, rt); - } - - if (lt == xpath_type_boolean) - return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); - else if (lt == xpath_type_number) - { - xpath_allocator_capture cr(stack.result); - - double l = lhs->eval_number(c, stack); - xpath_node_set_raw rs = rhs->eval_node_set(c, stack); - - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture cri(stack.result); - - if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) - return true; - } + xpath_string ls = lhs->eval_string(c, stack); + xpath_string rs = rhs->eval_string(c, stack); - return false; - } - else if (lt == xpath_type_string) - { - xpath_allocator_capture cr(stack.result); + return comp(ls, rs); + } + } else if (lt == xpath_type_node_set && rt == xpath_type_node_set) { + xpath_allocator_capture cr(stack.result); - xpath_string l = lhs->eval_string(c, stack); - xpath_node_set_raw rs = rhs->eval_node_set(c, stack); + xpath_node_set_raw ls = lhs->eval_node_set(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack); - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture cri(stack.result); + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) { + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) { + xpath_allocator_capture cri(stack.result); - if (comp(l, string_value(*ri, stack.result))) - return true; + if (comp(string_value(*li, stack.result), string_value(*ri, stack.result))) { + return true; } - - return false; } } - assert(!"Wrong types"); return false; - } - - template static bool compare_rel(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) - { - xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); + } else { + if (lt == xpath_type_node_set) { + swap(lhs, rhs); + swap(lt, rt); + } - if (lt != xpath_type_node_set && rt != xpath_type_node_set) - return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); - else if (lt == xpath_type_node_set && rt == xpath_type_node_set) - { + if (lt == xpath_type_boolean) { + return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); + } else if (lt == xpath_type_number) { xpath_allocator_capture cr(stack.result); - xpath_node_set_raw ls = lhs->eval_node_set(c, stack); + double l = lhs->eval_number(c, stack); xpath_node_set_raw rs = rhs->eval_node_set(c, stack); - for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) - { + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) { xpath_allocator_capture cri(stack.result); - double l = convert_string_to_number(string_value(*li, stack.result).c_str()); - - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture crii(stack.result); - - if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) - return true; + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) { + return true; } } return false; - } - else if (lt != xpath_type_node_set && rt == xpath_type_node_set) - { + } else if (lt == xpath_type_string) { xpath_allocator_capture cr(stack.result); - double l = lhs->eval_number(c, stack); + xpath_string l = lhs->eval_string(c, stack); xpath_node_set_raw rs = rhs->eval_node_set(c, stack); - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) { xpath_allocator_capture cri(stack.result); - if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) + if (comp(l, string_value(*ri, stack.result))) { return true; + } } return false; } - else if (lt == xpath_type_node_set && rt != xpath_type_node_set) - { - xpath_allocator_capture cr(stack.result); + } + + assert(!"Wrong types"); + return false; + } - xpath_node_set_raw ls = lhs->eval_node_set(c, stack); - double r = rhs->eval_number(c, stack); + template + static bool compare_rel(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) { + xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); - for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) - { - xpath_allocator_capture cri(stack.result); + if (lt != xpath_type_node_set && rt != xpath_type_node_set) { + return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); + } else if (lt == xpath_type_node_set && rt == xpath_type_node_set) { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) { + xpath_allocator_capture cri(stack.result); + + double l = convert_string_to_number(string_value(*li, stack.result).c_str()); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) { + xpath_allocator_capture crii(stack.result); - if (comp(convert_string_to_number(string_value(*li, stack.result).c_str()), r)) + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) { return true; + } } + } - return false; + return false; + } else if (lt != xpath_type_node_set && rt == xpath_type_node_set) { + xpath_allocator_capture cr(stack.result); + + double l = lhs->eval_number(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) { + xpath_allocator_capture cri(stack.result); + + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) { + return true; + } } - else - { - assert(!"Wrong types"); - return false; + + return false; + } else if (lt == xpath_type_node_set && rt != xpath_type_node_set) { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack); + double r = rhs->eval_number(c, stack); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) { + xpath_allocator_capture cri(stack.result); + + if (comp(convert_string_to_number(string_value(*li, stack.result).c_str()), r)) { + return true; + } } + + return false; + } else { + assert(!"Wrong types"); + return false; } + } - void apply_predicate(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack) - { - assert(ns.size() >= first); + void apply_predicate(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack) { + assert(ns.size() >= first); - size_t i = 1; - size_t size = ns.size() - first; + size_t i = 1; + size_t size = ns.size() - first; - xpath_node* last = ns.begin() + first; + xpath_node* last = ns.begin() + first; - // remove_if... or well, sort of - for (xpath_node* it = last; it != ns.end(); ++it, ++i) - { - xpath_context c(*it, i, size); + // remove_if... or well, sort of + for (xpath_node* it = last; it != ns.end(); ++it, ++i) { + xpath_context c(*it, i, size); - if (expr->rettype() == xpath_type_number) - { - if (expr->eval_number(c, stack) == i) - *last++ = *it; - } - else if (expr->eval_boolean(c, stack)) + if (expr->rettype() == xpath_type_number) { + if (expr->eval_number(c, stack) == i) { *last++ = *it; + } + } else if (expr->eval_boolean(c, stack)) { + *last++ = *it; } - - ns.truncate(last); } - void apply_predicates(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack) - { - if (ns.size() == first) return; + ns.truncate(last); + } - for (xpath_ast_node* pred = _right; pred; pred = pred->_next) - { - apply_predicate(ns, first, pred->_left, stack); - } + void apply_predicates(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack) { + if (ns.size() == first) { + return; } - void step_push(xpath_node_set_raw& ns, const xml_attribute& a, const xml_node& parent, xpath_allocator* alloc) - { - if (!a) return; + for (xpath_ast_node* pred = _right; pred; pred = pred->_next) { + apply_predicate(ns, first, pred->_left, stack); + } + } - const char_t* name = a.name(); + void step_push(xpath_node_set_raw& ns, const xml_attribute& a, const xml_node& parent, xpath_allocator* alloc) { + if (!a) { + return; + } - // There are no attribute nodes corresponding to attributes that declare namespaces - // That is, "xmlns:..." or "xmlns" - if (starts_with(name, PUGIXML_TEXT("xmlns")) && (name[5] == 0 || name[5] == ':')) return; + const char_t* name = a.name(); - switch (_test) - { + // There are no attribute nodes corresponding to attributes that declare namespaces + // That is, "xmlns:..." or "xmlns" + if (starts_with(name, PUGIXML_TEXT("xmlns")) && (name[5] == 0 || name[5] == ':')) { + return; + } + + switch (_test) { case nodetest_name: - if (strequal(name, _data.nodetest)) ns.push_back(xpath_node(a, parent), alloc); + if (strequal(name, _data.nodetest)) { + ns.push_back(xpath_node(a, parent), alloc); + } break; case nodetest_type_node: @@ -7639,23 +7589,25 @@ PUGI__NS_BEGIN break; case nodetest_all_in_namespace: - if (starts_with(name, _data.nodetest)) + if (starts_with(name, _data.nodetest)) { ns.push_back(xpath_node(a, parent), alloc); + } break; - default: - ; - } + default:; } + } - void step_push(xpath_node_set_raw& ns, const xml_node& n, xpath_allocator* alloc) - { - if (!n) return; + void step_push(xpath_node_set_raw& ns, const xml_node& n, xpath_allocator* alloc) { + if (!n) { + return; + } - switch (_test) - { + switch (_test) { case nodetest_name: - if (n.type() == node_element && strequal(n.name(), _data.nodetest)) ns.push_back(n, alloc); + if (n.type() == node_element && strequal(n.name(), _data.nodetest)) { + ns.push_back(n, alloc); + } break; case nodetest_type_node: @@ -7663,166 +7615,177 @@ PUGI__NS_BEGIN break; case nodetest_type_comment: - if (n.type() == node_comment) + if (n.type() == node_comment) { ns.push_back(n, alloc); + } break; case nodetest_type_text: - if (n.type() == node_pcdata || n.type() == node_cdata) + if (n.type() == node_pcdata || n.type() == node_cdata) { ns.push_back(n, alloc); + } break; case nodetest_type_pi: - if (n.type() == node_pi) + if (n.type() == node_pi) { ns.push_back(n, alloc); + } break; case nodetest_pi: - if (n.type() == node_pi && strequal(n.name(), _data.nodetest)) + if (n.type() == node_pi && strequal(n.name(), _data.nodetest)) { ns.push_back(n, alloc); + } break; case nodetest_all: - if (n.type() == node_element) + if (n.type() == node_element) { ns.push_back(n, alloc); + } break; case nodetest_all_in_namespace: - if (n.type() == node_element && starts_with(n.name(), _data.nodetest)) + if (n.type() == node_element && starts_with(n.name(), _data.nodetest)) { ns.push_back(n, alloc); + } break; default: assert(!"Unknown axis"); - } } + } - template void step_fill(xpath_node_set_raw& ns, const xml_node& n, xpath_allocator* alloc, T) - { - const axis_t axis = T::axis; + template + void step_fill(xpath_node_set_raw& ns, const xml_node& n, xpath_allocator* alloc, T) { + const axis_t axis = T::axis; - switch (axis) - { - case axis_attribute: - { - for (xml_attribute a = n.first_attribute(); a; a = a.next_attribute()) + switch (axis) { + case axis_attribute: { + for (xml_attribute a = n.first_attribute(); a; a = a.next_attribute()) { step_push(ns, a, n, alloc); + } break; } - case axis_child: - { - for (xml_node c = n.first_child(); c; c = c.next_sibling()) + case axis_child: { + for (xml_node c = n.first_child(); c; c = c.next_sibling()) { step_push(ns, c, alloc); + } break; } case axis_descendant: - case axis_descendant_or_self: - { - if (axis == axis_descendant_or_self) + case axis_descendant_or_self: { + if (axis == axis_descendant_or_self) { step_push(ns, n, alloc); + } xml_node cur = n.first_child(); - while (cur && cur != n) - { + while (cur && cur != n) { step_push(ns, cur, alloc); - if (cur.first_child()) + if (cur.first_child()) { cur = cur.first_child(); - else if (cur.next_sibling()) + } else if (cur.next_sibling()) { cur = cur.next_sibling(); - else - { - while (!cur.next_sibling() && cur != n) + } else { + while (!cur.next_sibling() && cur != n) { cur = cur.parent(); + } - if (cur != n) cur = cur.next_sibling(); + if (cur != n) { + cur = cur.next_sibling(); + } } } break; } - case axis_following_sibling: - { - for (xml_node c = n.next_sibling(); c; c = c.next_sibling()) + case axis_following_sibling: { + for (xml_node c = n.next_sibling(); c; c = c.next_sibling()) { step_push(ns, c, alloc); + } break; } - case axis_preceding_sibling: - { - for (xml_node c = n.previous_sibling(); c; c = c.previous_sibling()) + case axis_preceding_sibling: { + for (xml_node c = n.previous_sibling(); c; c = c.previous_sibling()) { step_push(ns, c, alloc); + } break; } - case axis_following: - { + case axis_following: { xml_node cur = n; // exit from this node so that we don't include descendants - while (cur && !cur.next_sibling()) cur = cur.parent(); + while (cur && !cur.next_sibling()) { + cur = cur.parent(); + } cur = cur.next_sibling(); - for (;;) - { + for (;;) { step_push(ns, cur, alloc); - if (cur.first_child()) + if (cur.first_child()) { cur = cur.first_child(); - else if (cur.next_sibling()) + } else if (cur.next_sibling()) { cur = cur.next_sibling(); - else - { - while (cur && !cur.next_sibling()) cur = cur.parent(); + } else { + while (cur && !cur.next_sibling()) { + cur = cur.parent(); + } cur = cur.next_sibling(); - if (!cur) break; + if (!cur) { + break; + } } } break; } - case axis_preceding: - { + case axis_preceding: { xml_node cur = n; - while (cur && !cur.previous_sibling()) cur = cur.parent(); + while (cur && !cur.previous_sibling()) { + cur = cur.parent(); + } cur = cur.previous_sibling(); - for (;;) - { - if (cur.last_child()) + for (;;) { + if (cur.last_child()) { cur = cur.last_child(); - else - { + } else { // leaf node, can't be ancestor step_push(ns, cur, alloc); - if (cur.previous_sibling()) + if (cur.previous_sibling()) { cur = cur.previous_sibling(); - else - { - do - { + } else { + do { cur = cur.parent(); - if (!cur) break; + if (!cur) { + break; + } - if (!node_is_ancestor(cur, n)) step_push(ns, cur, alloc); - } - while (!cur.previous_sibling()); + if (!node_is_ancestor(cur, n)) { + step_push(ns, cur, alloc); + } + } while (!cur.previous_sibling()); cur = cur.previous_sibling(); - if (!cur) break; + if (!cur) { + break; + } } } } @@ -7831,15 +7794,14 @@ PUGI__NS_BEGIN } case axis_ancestor: - case axis_ancestor_or_self: - { - if (axis == axis_ancestor_or_self) + case axis_ancestor_or_self: { + if (axis == axis_ancestor_or_self) { step_push(ns, n, alloc); + } xml_node cur = n.parent(); - while (cur) - { + while (cur) { step_push(ns, cur, alloc); cur = cur.parent(); @@ -7848,41 +7810,39 @@ PUGI__NS_BEGIN break; } - case axis_self: - { + case axis_self: { step_push(ns, n, alloc); break; } - case axis_parent: - { - if (n.parent()) step_push(ns, n.parent(), alloc); + case axis_parent: { + if (n.parent()) { + step_push(ns, n.parent(), alloc); + } break; } default: assert(!"Unimplemented axis"); - } } + } - template void step_fill(xpath_node_set_raw& ns, const xml_attribute& a, const xml_node& p, xpath_allocator* alloc, T v) - { - const axis_t axis = T::axis; + template + void step_fill(xpath_node_set_raw& ns, const xml_attribute& a, const xml_node& p, xpath_allocator* alloc, T v) { + const axis_t axis = T::axis; - switch (axis) - { + switch (axis) { case axis_ancestor: - case axis_ancestor_or_self: - { - if (axis == axis_ancestor_or_self && _test == nodetest_type_node) // reject attributes based on principal node type test + case axis_ancestor_or_self: { + if (axis == axis_ancestor_or_self && _test == nodetest_type_node) { // reject attributes based on principal node type test step_push(ns, a, p, alloc); + } xml_node cur = p; - while (cur) - { + while (cur) { step_push(ns, cur, alloc); cur = cur.parent(); @@ -7892,30 +7852,31 @@ PUGI__NS_BEGIN } case axis_descendant_or_self: - case axis_self: - { - if (_test == nodetest_type_node) // reject attributes based on principal node type test + case axis_self: { + if (_test == nodetest_type_node) { // reject attributes based on principal node type test step_push(ns, a, p, alloc); + } break; } - case axis_following: - { + case axis_following: { xml_node cur = p; - for (;;) - { - if (cur.first_child()) + for (;;) { + if (cur.first_child()) { cur = cur.first_child(); - else if (cur.next_sibling()) + } else if (cur.next_sibling()) { cur = cur.next_sibling(); - else - { - while (cur && !cur.next_sibling()) cur = cur.parent(); + } else { + while (cur && !cur.next_sibling()) { + cur = cur.parent(); + } cur = cur.next_sibling(); - if (!cur) break; + if (!cur) { + break; + } } step_push(ns, cur, alloc); @@ -7924,15 +7885,13 @@ PUGI__NS_BEGIN break; } - case axis_parent: - { + case axis_parent: { step_push(ns, p, alloc); break; } - case axis_preceding: - { + case axis_preceding: { // preceding:: axis does not include attribute nodes and attribute ancestors (they are the same as parent's ancestors), so we can reuse node preceding step_fill(ns, p, alloc, v); break; @@ -7940,104 +7899,98 @@ PUGI__NS_BEGIN default: assert(!"Unimplemented axis"); - } } + } - template xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, T v) - { - const axis_t axis = T::axis; - bool attributes = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_descendant_or_self || axis == axis_following || axis == axis_parent || axis == axis_preceding || axis == axis_self); - - xpath_node_set_raw ns; - ns.set_type((axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_preceding || axis == axis_preceding_sibling) ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted); + template + xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, T v) { + const axis_t axis = T::axis; + bool attributes = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_descendant_or_self || axis == axis_following || axis == axis_parent || axis == axis_preceding || axis == axis_self); - if (_left) - { - xpath_node_set_raw s = _left->eval_node_set(c, stack); + xpath_node_set_raw ns; + ns.set_type((axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_preceding || axis == axis_preceding_sibling) ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted); - // self axis preserves the original order - if (axis == axis_self) ns.set_type(s.type()); + if (_left) { + xpath_node_set_raw s = _left->eval_node_set(c, stack); - for (const xpath_node* it = s.begin(); it != s.end(); ++it) - { - size_t size = ns.size(); + // self axis preserves the original order + if (axis == axis_self) { + ns.set_type(s.type()); + } - // in general, all axes generate elements in a particular order, but there is no order guarantee if axis is applied to two nodes - if (axis != axis_self && size != 0) ns.set_type(xpath_node_set::type_unsorted); + for (const xpath_node* it = s.begin(); it != s.end(); ++it) { + size_t size = ns.size(); - if (it->node()) - step_fill(ns, it->node(), stack.result, v); - else if (attributes) - step_fill(ns, it->attribute(), it->parent(), stack.result, v); + // in general, all axes generate elements in a particular order, but there is no order guarantee if axis is applied to two nodes + if (axis != axis_self && size != 0) { + ns.set_type(xpath_node_set::type_unsorted); + } - apply_predicates(ns, size, stack); + if (it->node()) { + step_fill(ns, it->node(), stack.result, v); + } else if (attributes) { + step_fill(ns, it->attribute(), it->parent(), stack.result, v); } - } - else - { - if (c.n.node()) - step_fill(ns, c.n.node(), stack.result, v); - else if (attributes) - step_fill(ns, c.n.attribute(), c.n.parent(), stack.result, v); - apply_predicates(ns, 0, stack); + apply_predicates(ns, size, stack); + } + } else { + if (c.n.node()) { + step_fill(ns, c.n.node(), stack.result, v); + } else if (attributes) { + step_fill(ns, c.n.attribute(), c.n.parent(), stack.result, v); } - // child, attribute and self axes always generate unique set of nodes - // for other axis, if the set stayed sorted, it stayed unique because the traversal algorithms do not visit the same node twice - if (axis != axis_child && axis != axis_attribute && axis != axis_self && ns.type() == xpath_node_set::type_unsorted) - ns.remove_duplicates(); - - return ns; + apply_predicates(ns, 0, stack); } - public: - xpath_ast_node(ast_type_t type, xpath_value_type rettype_, const char_t* value): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) - { - assert(type == ast_string_constant); - _data.string = value; + // child, attribute and self axes always generate unique set of nodes + // for other axis, if the set stayed sorted, it stayed unique because the traversal algorithms do not visit the same node twice + if (axis != axis_child && axis != axis_attribute && axis != axis_self && ns.type() == xpath_node_set::type_unsorted) { + ns.remove_duplicates(); } - xpath_ast_node(ast_type_t type, xpath_value_type rettype_, double value): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) - { - assert(type == ast_number_constant); - _data.number = value; - } + return ns; + } - xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_variable* value): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) - { - assert(type == ast_variable); - _data.variable = value; - } +public: + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, const char_t* value) : + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) { + assert(type == ast_string_constant); + _data.string = value; + } - xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_ast_node* left = 0, xpath_ast_node* right = 0): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(left), _right(right), _next(0) - { - } + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, double value) : + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) { + assert(type == ast_number_constant); + _data.number = value; + } - xpath_ast_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents): - _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(static_cast(axis)), _test(static_cast(test)), _left(left), _right(0), _next(0) - { - _data.nodetest = contents; - } + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_variable* value) : + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) { + assert(type == ast_variable); + _data.variable = value; + } - void set_next(xpath_ast_node* value) - { - _next = value; - } + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_ast_node* left = 0, xpath_ast_node* right = 0) : + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(left), _right(right), _next(0) { + } - void set_right(xpath_ast_node* value) - { - _right = value; - } + xpath_ast_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents) : + _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(static_cast(axis)), _test(static_cast(test)), _left(left), _right(0), _next(0) { + _data.nodetest = contents; + } - bool eval_boolean(const xpath_context& c, const xpath_stack& stack) - { - switch (_type) - { + void set_next(xpath_ast_node* value) { + _next = value; + } + + void set_right(xpath_ast_node* value) { + _right = value; + } + + bool eval_boolean(const xpath_context& c, const xpath_stack& stack) { + switch (_type) { case ast_op_or: return _left->eval_boolean(c, stack) || _right->eval_boolean(c, stack); @@ -8062,8 +8015,7 @@ PUGI__NS_BEGIN case ast_op_greater_or_equal: return compare_rel(_right, _left, c, stack, less_equal()); - case ast_func_starts_with: - { + case ast_func_starts_with: { xpath_allocator_capture cr(stack.result); xpath_string lr = _left->eval_string(c, stack); @@ -8072,8 +8024,7 @@ PUGI__NS_BEGIN return starts_with(lr.c_str(), rr.c_str()); } - case ast_func_contains: - { + case ast_func_contains: { xpath_allocator_capture cr(stack.result); xpath_string lr = _left->eval_string(c, stack); @@ -8094,26 +8045,26 @@ PUGI__NS_BEGIN case ast_func_false: return false; - case ast_func_lang: - { - if (c.n.attribute()) return false; + case ast_func_lang: { + if (c.n.attribute()) { + return false; + } xpath_allocator_capture cr(stack.result); xpath_string lang = _left->eval_string(c, stack); - for (xml_node n = c.n.node(); n; n = n.parent()) - { + for (xml_node n = c.n.node(); n; n = n.parent()) { xml_attribute a = n.attribute(PUGIXML_TEXT("xml:lang")); - if (a) - { + if (a) { const char_t* value = a.value(); // strnicmp / strncasecmp is not portable - for (const char_t* lit = lang.c_str(); *lit; ++lit) - { - if (tolower_ascii(*lit) != tolower_ascii(*value)) return false; + for (const char_t* lit = lang.c_str(); *lit; ++lit) { + if (tolower_ascii(*lit) != tolower_ascii(*value)) { + return false; + } ++value; } @@ -8124,49 +8075,43 @@ PUGI__NS_BEGIN return false; } - case ast_variable: - { + case ast_variable: { assert(_rettype == _data.variable->type()); - if (_rettype == xpath_type_boolean) + if (_rettype == xpath_type_boolean) { return _data.variable->get_boolean(); + } // fallthrough to type conversion } - default: - { - switch (_rettype) - { - case xpath_type_number: - return convert_number_to_boolean(eval_number(c, stack)); + default: { + switch (_rettype) { + case xpath_type_number: + return convert_number_to_boolean(eval_number(c, stack)); - case xpath_type_string: - { - xpath_allocator_capture cr(stack.result); + case xpath_type_string: { + xpath_allocator_capture cr(stack.result); - return !eval_string(c, stack).empty(); - } + return !eval_string(c, stack).empty(); + } - case xpath_type_node_set: - { - xpath_allocator_capture cr(stack.result); + case xpath_type_node_set: { + xpath_allocator_capture cr(stack.result); - return !eval_node_set(c, stack).empty(); - } + return !eval_node_set(c, stack).empty(); + } - default: - assert(!"Wrong expression for return type boolean"); - return false; + default: + assert(!"Wrong expression for return type boolean"); + return false; } } - } } + } - double eval_number(const xpath_context& c, const xpath_stack& stack) - { - switch (_type) - { + double eval_number(const xpath_context& c, const xpath_stack& stack) { + switch (_type) { case ast_op_add: return _left->eval_number(c, stack) + _right->eval_number(c, stack); @@ -8194,29 +8139,25 @@ PUGI__NS_BEGIN case ast_func_position: return static_cast(c.position); - case ast_func_count: - { + case ast_func_count: { xpath_allocator_capture cr(stack.result); return static_cast(_left->eval_node_set(c, stack).size()); } - case ast_func_string_length_0: - { + case ast_func_string_length_0: { xpath_allocator_capture cr(stack.result); return static_cast(string_value(c.n, stack.result).length()); } - case ast_func_string_length_1: - { + case ast_func_string_length_1: { xpath_allocator_capture cr(stack.result); return static_cast(_left->eval_string(c, stack).length()); } - case ast_func_number_0: - { + case ast_func_number_0: { xpath_allocator_capture cr(stack.result); return convert_string_to_number(string_value(c.n, stack.result).c_str()); @@ -8225,16 +8166,14 @@ PUGI__NS_BEGIN case ast_func_number_1: return _left->eval_number(c, stack); - case ast_func_sum: - { + case ast_func_sum: { xpath_allocator_capture cr(stack.result); double r = 0; xpath_node_set_raw ns = _left->eval_node_set(c, stack); - for (const xpath_node* it = ns.begin(); it != ns.end(); ++it) - { + for (const xpath_node* it = ns.begin(); it != ns.end(); ++it) { xpath_allocator_capture cri(stack.result); r += convert_string_to_number(string_value(*it, stack.result).c_str()); @@ -8243,15 +8182,13 @@ PUGI__NS_BEGIN return r; } - case ast_func_floor: - { + case ast_func_floor: { double r = _left->eval_number(c, stack); return r == r ? floor(r) : r; } - case ast_func_ceiling: - { + case ast_func_ceiling: { double r = _left->eval_number(c, stack); return r == r ? ceil(r) : r; @@ -8260,111 +8197,108 @@ PUGI__NS_BEGIN case ast_func_round: return round_nearest_nzero(_left->eval_number(c, stack)); - case ast_variable: - { + case ast_variable: { assert(_rettype == _data.variable->type()); - if (_rettype == xpath_type_number) + if (_rettype == xpath_type_number) { return _data.variable->get_number(); + } // fallthrough to type conversion } - default: - { - switch (_rettype) - { - case xpath_type_boolean: - return eval_boolean(c, stack) ? 1 : 0; + default: { + switch (_rettype) { + case xpath_type_boolean: + return eval_boolean(c, stack) ? 1 : 0; - case xpath_type_string: - { - xpath_allocator_capture cr(stack.result); + case xpath_type_string: { + xpath_allocator_capture cr(stack.result); - return convert_string_to_number(eval_string(c, stack).c_str()); - } + return convert_string_to_number(eval_string(c, stack).c_str()); + } - case xpath_type_node_set: - { - xpath_allocator_capture cr(stack.result); + case xpath_type_node_set: { + xpath_allocator_capture cr(stack.result); - return convert_string_to_number(eval_string(c, stack).c_str()); - } + return convert_string_to_number(eval_string(c, stack).c_str()); + } - default: - assert(!"Wrong expression for return type number"); - return 0; + default: + assert(!"Wrong expression for return type number"); + return 0; } - - } } } + } - xpath_string eval_string_concat(const xpath_context& c, const xpath_stack& stack) - { - assert(_type == ast_func_concat); + xpath_string eval_string_concat(const xpath_context& c, const xpath_stack& stack) { + assert(_type == ast_func_concat); - xpath_allocator_capture ct(stack.temp); + xpath_allocator_capture ct(stack.temp); - // count the string number - size_t count = 1; - for (xpath_ast_node* nc = _right; nc; nc = nc->_next) count++; + // count the string number + size_t count = 1; + for (xpath_ast_node* nc = _right; nc; nc = nc->_next) { + count++; + } - // gather all strings - xpath_string static_buffer[4]; - xpath_string* buffer = static_buffer; + // gather all strings + xpath_string static_buffer[4]; + xpath_string* buffer = static_buffer; - // allocate on-heap for large concats - if (count > sizeof(static_buffer) / sizeof(static_buffer[0])) - { - buffer = static_cast(stack.temp->allocate(count * sizeof(xpath_string))); - assert(buffer); - } + // allocate on-heap for large concats + if (count > sizeof(static_buffer) / sizeof(static_buffer[0])) { + buffer = static_cast(stack.temp->allocate(count * sizeof(xpath_string))); + assert(buffer); + } - // evaluate all strings to temporary stack - xpath_stack swapped_stack = {stack.temp, stack.result}; + // evaluate all strings to temporary stack + xpath_stack swapped_stack = { stack.temp, stack.result }; - buffer[0] = _left->eval_string(c, swapped_stack); + buffer[0] = _left->eval_string(c, swapped_stack); - size_t pos = 1; - for (xpath_ast_node* n = _right; n; n = n->_next, ++pos) buffer[pos] = n->eval_string(c, swapped_stack); - assert(pos == count); + size_t pos = 1; + for (xpath_ast_node* n = _right; n; n = n->_next, ++pos) { + buffer[pos] = n->eval_string(c, swapped_stack); + } + assert(pos == count); - // get total length - size_t length = 0; - for (size_t i = 0; i < count; ++i) length += buffer[i].length(); + // get total length + size_t length = 0; + for (size_t i = 0; i < count; ++i) { + length += buffer[i].length(); + } - // create final string - char_t* result = static_cast(stack.result->allocate((length + 1) * sizeof(char_t))); - assert(result); + // create final string + char_t* result = static_cast(stack.result->allocate((length + 1) * sizeof(char_t))); + assert(result); - char_t* ri = result; + char_t* ri = result; - for (size_t j = 0; j < count; ++j) - for (const char_t* bi = buffer[j].c_str(); *bi; ++bi) - *ri++ = *bi; + for (size_t j = 0; j < count; ++j) { + for (const char_t* bi = buffer[j].c_str(); *bi; ++bi) { + *ri++ = *bi; + } + } - *ri = 0; + *ri = 0; - return xpath_string(result, true); - } + return xpath_string(result, true); + } - xpath_string eval_string(const xpath_context& c, const xpath_stack& stack) - { - switch (_type) - { + xpath_string eval_string(const xpath_context& c, const xpath_stack& stack) { + switch (_type) { case ast_string_constant: return xpath_string_const(_data.string); - case ast_func_local_name_0: - { + case ast_func_local_name_0: { xpath_node na = c.n; return xpath_string_const(local_name(na)); } - case ast_func_local_name_1: - { + case ast_func_local_name_1: { xpath_allocator_capture cr(stack.result); xpath_node_set_raw ns = _left->eval_node_set(c, stack); @@ -8373,15 +8307,13 @@ PUGI__NS_BEGIN return xpath_string_const(local_name(na)); } - case ast_func_name_0: - { + case ast_func_name_0: { xpath_node na = c.n; return xpath_string_const(qualified_name(na)); } - case ast_func_name_1: - { + case ast_func_name_1: { xpath_allocator_capture cr(stack.result); xpath_node_set_raw ns = _left->eval_node_set(c, stack); @@ -8390,15 +8322,13 @@ PUGI__NS_BEGIN return xpath_string_const(qualified_name(na)); } - case ast_func_namespace_uri_0: - { + case ast_func_namespace_uri_0: { xpath_node na = c.n; return xpath_string_const(namespace_uri(na)); } - case ast_func_namespace_uri_1: - { + case ast_func_namespace_uri_1: { xpath_allocator_capture cr(stack.result); xpath_node_set_raw ns = _left->eval_node_set(c, stack); @@ -8416,11 +8346,10 @@ PUGI__NS_BEGIN case ast_func_concat: return eval_string_concat(c, stack); - case ast_func_substring_before: - { + case ast_func_substring_before: { xpath_allocator_capture cr(stack.temp); - xpath_stack swapped_stack = {stack.temp, stack.result}; + xpath_stack swapped_stack = { stack.temp, stack.result }; xpath_string s = _left->eval_string(c, swapped_stack); xpath_string p = _right->eval_string(c, swapped_stack); @@ -8430,36 +8359,39 @@ PUGI__NS_BEGIN return pos ? xpath_string(s.c_str(), pos, stack.result) : xpath_string(); } - case ast_func_substring_after: - { + case ast_func_substring_after: { xpath_allocator_capture cr(stack.temp); - xpath_stack swapped_stack = {stack.temp, stack.result}; + xpath_stack swapped_stack = { stack.temp, stack.result }; xpath_string s = _left->eval_string(c, swapped_stack); xpath_string p = _right->eval_string(c, swapped_stack); const char_t* pos = find_substring(s.c_str(), p.c_str()); - if (!pos) return xpath_string(); + if (!pos) { + return xpath_string(); + } const char_t* result = pos + p.length(); return s.uses_heap() ? xpath_string(result, stack.result) : xpath_string_const(result); } - case ast_func_substring_2: - { + case ast_func_substring_2: { xpath_allocator_capture cr(stack.temp); - xpath_stack swapped_stack = {stack.temp, stack.result}; + xpath_stack swapped_stack = { stack.temp, stack.result }; xpath_string s = _left->eval_string(c, swapped_stack); size_t s_length = s.length(); double first = round_nearest(_right->eval_number(c, stack)); - if (is_nan(first)) return xpath_string(); // NaN - else if (first >= s_length + 1) return xpath_string(); + if (is_nan(first)) { + return xpath_string(); // NaN + } else if (first >= s_length + 1) { + return xpath_string(); + } size_t pos = first < 1 ? 1 : static_cast(first); assert(1 <= pos && pos <= s_length + 1); @@ -8469,11 +8401,10 @@ PUGI__NS_BEGIN return s.uses_heap() ? xpath_string(rbegin, stack.result) : xpath_string_const(rbegin); } - case ast_func_substring_3: - { + case ast_func_substring_3: { xpath_allocator_capture cr(stack.temp); - xpath_stack swapped_stack = {stack.temp, stack.result}; + xpath_stack swapped_stack = { stack.temp, stack.result }; xpath_string s = _left->eval_string(c, swapped_stack); size_t s_length = s.length(); @@ -8481,10 +8412,15 @@ PUGI__NS_BEGIN double first = round_nearest(_right->eval_number(c, stack)); double last = first + round_nearest(_right->_next->eval_number(c, stack)); - if (is_nan(first) || is_nan(last)) return xpath_string(); - else if (first >= s_length + 1) return xpath_string(); - else if (first >= last) return xpath_string(); - else if (last < 1) return xpath_string(); + if (is_nan(first) || is_nan(last)) { + return xpath_string(); + } else if (first >= s_length + 1) { + return xpath_string(); + } else if (first >= last) { + return xpath_string(); + } else if (last < 1) { + return xpath_string(); + } size_t pos = first < 1 ? 1 : static_cast(first); size_t end = last >= s_length + 1 ? s_length + 1 : static_cast(last); @@ -8496,8 +8432,7 @@ PUGI__NS_BEGIN return (end == s_length + 1 && !s.uses_heap()) ? xpath_string_const(rbegin) : xpath_string(rbegin, rend, stack.result); } - case ast_func_normalize_space_0: - { + case ast_func_normalize_space_0: { xpath_string s = string_value(c.n, stack.result); normalize_space(s.data(stack.result)); @@ -8505,8 +8440,7 @@ PUGI__NS_BEGIN return s; } - case ast_func_normalize_space_1: - { + case ast_func_normalize_space_1: { xpath_string s = _left->eval_string(c, stack); normalize_space(s.data(stack.result)); @@ -8514,11 +8448,10 @@ PUGI__NS_BEGIN return s; } - case ast_func_translate: - { + case ast_func_translate: { xpath_allocator_capture cr(stack.temp); - xpath_stack swapped_stack = {stack.temp, stack.result}; + xpath_stack swapped_stack = { stack.temp, stack.result }; xpath_string s = _left->eval_string(c, stack); xpath_string from = _right->eval_string(c, swapped_stack); @@ -8529,53 +8462,47 @@ PUGI__NS_BEGIN return s; } - case ast_variable: - { + case ast_variable: { assert(_rettype == _data.variable->type()); - if (_rettype == xpath_type_string) + if (_rettype == xpath_type_string) { return xpath_string_const(_data.variable->get_string()); + } // fallthrough to type conversion } - default: - { - switch (_rettype) - { - case xpath_type_boolean: - return xpath_string_const(eval_boolean(c, stack) ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); + default: { + switch (_rettype) { + case xpath_type_boolean: + return xpath_string_const(eval_boolean(c, stack) ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); - case xpath_type_number: - return convert_number_to_string(eval_number(c, stack), stack.result); + case xpath_type_number: + return convert_number_to_string(eval_number(c, stack), stack.result); - case xpath_type_node_set: - { - xpath_allocator_capture cr(stack.temp); + case xpath_type_node_set: { + xpath_allocator_capture cr(stack.temp); - xpath_stack swapped_stack = {stack.temp, stack.result}; + xpath_stack swapped_stack = { stack.temp, stack.result }; - xpath_node_set_raw ns = eval_node_set(c, swapped_stack); - return ns.empty() ? xpath_string() : string_value(ns.first(), stack.result); - } + xpath_node_set_raw ns = eval_node_set(c, swapped_stack); + return ns.empty() ? xpath_string() : string_value(ns.first(), stack.result); + } - default: - assert(!"Wrong expression for return type string"); - return xpath_string(); + default: + assert(!"Wrong expression for return type string"); + return xpath_string(); } } - } } + } - xpath_node_set_raw eval_node_set(const xpath_context& c, const xpath_stack& stack) - { - switch (_type) - { - case ast_op_union: - { + xpath_node_set_raw eval_node_set(const xpath_context& c, const xpath_stack& stack) { + switch (_type) { + case ast_op_union: { xpath_allocator_capture cr(stack.temp); - xpath_stack swapped_stack = {stack.temp, stack.result}; + xpath_stack swapped_stack = { stack.temp, stack.result }; xpath_node_set_raw ls = _left->eval_node_set(c, swapped_stack); xpath_node_set_raw rs = _right->eval_node_set(c, stack); @@ -8590,12 +8517,13 @@ PUGI__NS_BEGIN } case ast_filter: - case ast_filter_posinv: - { + case ast_filter_posinv: { xpath_node_set_raw set = _left->eval_node_set(c, stack); // either expression is a number or it contains position() call; sort by document order - if (_type == ast_filter) set.sort_do(); + if (_type == ast_filter) { + set.sort_do(); + } apply_predicate(set, 0, _right, stack); @@ -8605,76 +8533,74 @@ PUGI__NS_BEGIN case ast_func_id: return xpath_node_set_raw(); - case ast_step: - { - switch (_axis) - { - case axis_ancestor: - return step_do(c, stack, axis_to_type()); + case ast_step: { + switch (_axis) { + case axis_ancestor: + return step_do(c, stack, axis_to_type()); - case axis_ancestor_or_self: - return step_do(c, stack, axis_to_type()); + case axis_ancestor_or_self: + return step_do(c, stack, axis_to_type()); - case axis_attribute: - return step_do(c, stack, axis_to_type()); + case axis_attribute: + return step_do(c, stack, axis_to_type()); - case axis_child: - return step_do(c, stack, axis_to_type()); + case axis_child: + return step_do(c, stack, axis_to_type()); - case axis_descendant: - return step_do(c, stack, axis_to_type()); + case axis_descendant: + return step_do(c, stack, axis_to_type()); - case axis_descendant_or_self: - return step_do(c, stack, axis_to_type()); + case axis_descendant_or_self: + return step_do(c, stack, axis_to_type()); - case axis_following: - return step_do(c, stack, axis_to_type()); + case axis_following: + return step_do(c, stack, axis_to_type()); - case axis_following_sibling: - return step_do(c, stack, axis_to_type()); + case axis_following_sibling: + return step_do(c, stack, axis_to_type()); - case axis_namespace: - // namespaced axis is not supported - return xpath_node_set_raw(); + case axis_namespace: + // namespaced axis is not supported + return xpath_node_set_raw(); - case axis_parent: - return step_do(c, stack, axis_to_type()); + case axis_parent: + return step_do(c, stack, axis_to_type()); - case axis_preceding: - return step_do(c, stack, axis_to_type()); + case axis_preceding: + return step_do(c, stack, axis_to_type()); - case axis_preceding_sibling: - return step_do(c, stack, axis_to_type()); + case axis_preceding_sibling: + return step_do(c, stack, axis_to_type()); - case axis_self: - return step_do(c, stack, axis_to_type()); + case axis_self: + return step_do(c, stack, axis_to_type()); - default: - assert(!"Unknown axis"); - return xpath_node_set_raw(); + default: + assert(!"Unknown axis"); + return xpath_node_set_raw(); } } - case ast_step_root: - { + case ast_step_root: { assert(!_right); // root step can't have any predicates xpath_node_set_raw ns; ns.set_type(xpath_node_set::type_sorted); - if (c.n.node()) ns.push_back(c.n.node().root(), stack.result); - else if (c.n.attribute()) ns.push_back(c.n.parent().root(), stack.result); + if (c.n.node()) { + ns.push_back(c.n.node().root(), stack.result); + } else if (c.n.attribute()) { + ns.push_back(c.n.parent().root(), stack.result); + } return ns; } - case ast_variable: - { + case ast_variable: { assert(_rettype == _data.variable->type()); - if (_rettype == xpath_type_node_set) - { + if (_rettype == xpath_type_node_set) { const xpath_node_set& s = _data.variable->get_node_set(); xpath_node_set_raw ns; @@ -8691,13 +8617,11 @@ PUGI__NS_BEGIN default: assert(!"Wrong expression for return type node set"); return xpath_node_set_raw(); - } } + } - bool is_posinv() - { - switch (_type) - { + bool is_posinv() { + switch (_type) { case ast_func_position: return false; @@ -8716,345 +8640,362 @@ PUGI__NS_BEGIN return true; default: - if (_left && !_left->is_posinv()) return false; + if (_left && !_left->is_posinv()) { + return false; + } - for (xpath_ast_node* n = _right; n; n = n->_next) - if (!n->is_posinv()) return false; + for (xpath_ast_node* n = _right; n; n = n->_next) { + if (!n->is_posinv()) { + return false; + } + } return true; - } } + } - xpath_value_type rettype() const - { - return static_cast(_rettype); - } - }; + xpath_value_type rettype() const { + return static_cast(_rettype); + } +}; - struct xpath_parser - { - xpath_allocator* _alloc; - xpath_lexer _lexer; +struct xpath_parser { + xpath_allocator* _alloc; + xpath_lexer _lexer; - const char_t* _query; - xpath_variable_set* _variables; + const char_t* _query; + xpath_variable_set* _variables; - xpath_parse_result* _result; + xpath_parse_result* _result; - #ifdef PUGIXML_NO_EXCEPTIONS - jmp_buf _error_handler; - #endif + #ifdef PUGIXML_NO_EXCEPTIONS + jmp_buf _error_handler; + #endif - void throw_error(const char* message) - { - _result->error = message; - _result->offset = _lexer.current_pos() - _query; + void throw_error(const char* message) { + _result->error = message; + _result->offset = _lexer.current_pos() - _query; #ifdef PUGIXML_NO_EXCEPTIONS - longjmp(_error_handler, 1); + longjmp(_error_handler, 1); #else - throw xpath_exception(*_result); + throw xpath_exception(*_result); #endif - } + } - void throw_error_oom() - { + void throw_error_oom() { #ifdef PUGIXML_NO_EXCEPTIONS - throw_error("Out of memory"); + throw_error("Out of memory"); #else - throw std::bad_alloc(); + throw std::bad_alloc(); #endif - } - - void* alloc_node() - { - void* result = _alloc->allocate_nothrow(sizeof(xpath_ast_node)); + } - if (!result) throw_error_oom(); + void* alloc_node() { + void* result = _alloc->allocate_nothrow(sizeof(xpath_ast_node)); - return result; + if (!result) { + throw_error_oom(); } - const char_t* alloc_string(const xpath_lexer_string& value) - { - if (value.begin) - { - size_t length = static_cast(value.end - value.begin); - - char_t* c = static_cast(_alloc->allocate_nothrow((length + 1) * sizeof(char_t))); - if (!c) throw_error_oom(); + return result; + } - memcpy(c, value.begin, length * sizeof(char_t)); - c[length] = 0; + const char_t* alloc_string(const xpath_lexer_string& value) { + if (value.begin) { + size_t length = static_cast(value.end - value.begin); - return c; + char_t* c = static_cast(_alloc->allocate_nothrow((length + 1) * sizeof(char_t))); + if (!c) { + throw_error_oom(); } - else return 0; - } - xpath_ast_node* parse_function_helper(ast_type_t type0, ast_type_t type1, size_t argc, xpath_ast_node* args[2]) - { - assert(argc <= 1); + memcpy(c, value.begin, length * sizeof(char_t)); + c[length] = 0; + + return c; + } else { + return 0; + } + } - if (argc == 1 && args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); + xpath_ast_node* parse_function_helper(ast_type_t type0, ast_type_t type1, size_t argc, xpath_ast_node* args[2]) { + assert(argc <= 1); - return new (alloc_node()) xpath_ast_node(argc == 0 ? type0 : type1, xpath_type_string, args[0]); + if (argc == 1 && args[0]->rettype() != xpath_type_node_set) { + throw_error("Function has to be applied to node set"); } - xpath_ast_node* parse_function(const xpath_lexer_string& name, size_t argc, xpath_ast_node* args[2]) - { - switch (name.begin[0]) - { + return new (alloc_node()) xpath_ast_node(argc == 0 ? type0 : type1, xpath_type_string, args[0]); + } + + xpath_ast_node* parse_function(const xpath_lexer_string& name, size_t argc, xpath_ast_node* args[2]) { + switch (name.begin[0]) { case 'b': - if (name == PUGIXML_TEXT("boolean") && argc == 1) + if (name == PUGIXML_TEXT("boolean") && argc == 1) { return new (alloc_node()) xpath_ast_node(ast_func_boolean, xpath_type_boolean, args[0]); + } break; case 'c': - if (name == PUGIXML_TEXT("count") && argc == 1) - { - if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); + if (name == PUGIXML_TEXT("count") && argc == 1) { + if (args[0]->rettype() != xpath_type_node_set) { + throw_error("Function has to be applied to node set"); + } return new (alloc_node()) xpath_ast_node(ast_func_count, xpath_type_number, args[0]); - } - else if (name == PUGIXML_TEXT("contains") && argc == 2) + } else if (name == PUGIXML_TEXT("contains") && argc == 2) { return new (alloc_node()) xpath_ast_node(ast_func_contains, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("concat") && argc >= 2) + } else if (name == PUGIXML_TEXT("concat") && argc >= 2) { return new (alloc_node()) xpath_ast_node(ast_func_concat, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("ceiling") && argc == 1) + } else if (name == PUGIXML_TEXT("ceiling") && argc == 1) { return new (alloc_node()) xpath_ast_node(ast_func_ceiling, xpath_type_number, args[0]); + } break; case 'f': - if (name == PUGIXML_TEXT("false") && argc == 0) + if (name == PUGIXML_TEXT("false") && argc == 0) { return new (alloc_node()) xpath_ast_node(ast_func_false, xpath_type_boolean); - else if (name == PUGIXML_TEXT("floor") && argc == 1) + } else if (name == PUGIXML_TEXT("floor") && argc == 1) { return new (alloc_node()) xpath_ast_node(ast_func_floor, xpath_type_number, args[0]); + } break; case 'i': - if (name == PUGIXML_TEXT("id") && argc == 1) + if (name == PUGIXML_TEXT("id") && argc == 1) { return new (alloc_node()) xpath_ast_node(ast_func_id, xpath_type_node_set, args[0]); + } break; case 'l': - if (name == PUGIXML_TEXT("last") && argc == 0) + if (name == PUGIXML_TEXT("last") && argc == 0) { return new (alloc_node()) xpath_ast_node(ast_func_last, xpath_type_number); - else if (name == PUGIXML_TEXT("lang") && argc == 1) + } else if (name == PUGIXML_TEXT("lang") && argc == 1) { return new (alloc_node()) xpath_ast_node(ast_func_lang, xpath_type_boolean, args[0]); - else if (name == PUGIXML_TEXT("local-name") && argc <= 1) + } else if (name == PUGIXML_TEXT("local-name") && argc <= 1) { return parse_function_helper(ast_func_local_name_0, ast_func_local_name_1, argc, args); + } break; case 'n': - if (name == PUGIXML_TEXT("name") && argc <= 1) + if (name == PUGIXML_TEXT("name") && argc <= 1) { return parse_function_helper(ast_func_name_0, ast_func_name_1, argc, args); - else if (name == PUGIXML_TEXT("namespace-uri") && argc <= 1) + } else if (name == PUGIXML_TEXT("namespace-uri") && argc <= 1) { return parse_function_helper(ast_func_namespace_uri_0, ast_func_namespace_uri_1, argc, args); - else if (name == PUGIXML_TEXT("normalize-space") && argc <= 1) + } else if (name == PUGIXML_TEXT("normalize-space") && argc <= 1) { return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_normalize_space_0 : ast_func_normalize_space_1, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("not") && argc == 1) + } else if (name == PUGIXML_TEXT("not") && argc == 1) { return new (alloc_node()) xpath_ast_node(ast_func_not, xpath_type_boolean, args[0]); - else if (name == PUGIXML_TEXT("number") && argc <= 1) + } else if (name == PUGIXML_TEXT("number") && argc <= 1) { return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_number_0 : ast_func_number_1, xpath_type_number, args[0]); + } break; case 'p': - if (name == PUGIXML_TEXT("position") && argc == 0) + if (name == PUGIXML_TEXT("position") && argc == 0) { return new (alloc_node()) xpath_ast_node(ast_func_position, xpath_type_number); + } break; case 'r': - if (name == PUGIXML_TEXT("round") && argc == 1) + if (name == PUGIXML_TEXT("round") && argc == 1) { return new (alloc_node()) xpath_ast_node(ast_func_round, xpath_type_number, args[0]); + } break; case 's': - if (name == PUGIXML_TEXT("string") && argc <= 1) + if (name == PUGIXML_TEXT("string") && argc <= 1) { return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_0 : ast_func_string_1, xpath_type_string, args[0]); - else if (name == PUGIXML_TEXT("string-length") && argc <= 1) + } else if (name == PUGIXML_TEXT("string-length") && argc <= 1) { return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_length_0 : ast_func_string_length_1, xpath_type_string, args[0]); - else if (name == PUGIXML_TEXT("starts-with") && argc == 2) + } else if (name == PUGIXML_TEXT("starts-with") && argc == 2) { return new (alloc_node()) xpath_ast_node(ast_func_starts_with, xpath_type_boolean, args[0], args[1]); - else if (name == PUGIXML_TEXT("substring-before") && argc == 2) + } else if (name == PUGIXML_TEXT("substring-before") && argc == 2) { return new (alloc_node()) xpath_ast_node(ast_func_substring_before, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("substring-after") && argc == 2) + } else if (name == PUGIXML_TEXT("substring-after") && argc == 2) { return new (alloc_node()) xpath_ast_node(ast_func_substring_after, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("substring") && (argc == 2 || argc == 3)) + } else if (name == PUGIXML_TEXT("substring") && (argc == 2 || argc == 3)) { return new (alloc_node()) xpath_ast_node(argc == 2 ? ast_func_substring_2 : ast_func_substring_3, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("sum") && argc == 1) - { - if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); + } else if (name == PUGIXML_TEXT("sum") && argc == 1) { + if (args[0]->rettype() != xpath_type_node_set) { + throw_error("Function has to be applied to node set"); + } return new (alloc_node()) xpath_ast_node(ast_func_sum, xpath_type_number, args[0]); } break; case 't': - if (name == PUGIXML_TEXT("translate") && argc == 3) + if (name == PUGIXML_TEXT("translate") && argc == 3) { return new (alloc_node()) xpath_ast_node(ast_func_translate, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("true") && argc == 0) + } else if (name == PUGIXML_TEXT("true") && argc == 0) { return new (alloc_node()) xpath_ast_node(ast_func_true, xpath_type_boolean); + } break; default: break; - } + } - throw_error("Unrecognized function or wrong parameter count"); + throw_error("Unrecognized function or wrong parameter count"); - return 0; - } + return 0; + } - axis_t parse_axis_name(const xpath_lexer_string& name, bool& specified) - { - specified = true; + axis_t parse_axis_name(const xpath_lexer_string& name, bool& specified) { + specified = true; - switch (name.begin[0]) - { + switch (name.begin[0]) { case 'a': - if (name == PUGIXML_TEXT("ancestor")) + if (name == PUGIXML_TEXT("ancestor")) { return axis_ancestor; - else if (name == PUGIXML_TEXT("ancestor-or-self")) + } else if (name == PUGIXML_TEXT("ancestor-or-self")) { return axis_ancestor_or_self; - else if (name == PUGIXML_TEXT("attribute")) + } else if (name == PUGIXML_TEXT("attribute")) { return axis_attribute; + } break; case 'c': - if (name == PUGIXML_TEXT("child")) + if (name == PUGIXML_TEXT("child")) { return axis_child; + } break; case 'd': - if (name == PUGIXML_TEXT("descendant")) + if (name == PUGIXML_TEXT("descendant")) { return axis_descendant; - else if (name == PUGIXML_TEXT("descendant-or-self")) + } else if (name == PUGIXML_TEXT("descendant-or-self")) { return axis_descendant_or_self; + } break; case 'f': - if (name == PUGIXML_TEXT("following")) + if (name == PUGIXML_TEXT("following")) { return axis_following; - else if (name == PUGIXML_TEXT("following-sibling")) + } else if (name == PUGIXML_TEXT("following-sibling")) { return axis_following_sibling; + } break; case 'n': - if (name == PUGIXML_TEXT("namespace")) + if (name == PUGIXML_TEXT("namespace")) { return axis_namespace; + } break; case 'p': - if (name == PUGIXML_TEXT("parent")) + if (name == PUGIXML_TEXT("parent")) { return axis_parent; - else if (name == PUGIXML_TEXT("preceding")) + } else if (name == PUGIXML_TEXT("preceding")) { return axis_preceding; - else if (name == PUGIXML_TEXT("preceding-sibling")) + } else if (name == PUGIXML_TEXT("preceding-sibling")) { return axis_preceding_sibling; + } break; case 's': - if (name == PUGIXML_TEXT("self")) + if (name == PUGIXML_TEXT("self")) { return axis_self; + } break; default: break; - } - - specified = false; - return axis_child; } - nodetest_t parse_node_test_type(const xpath_lexer_string& name) - { - switch (name.begin[0]) - { + specified = false; + return axis_child; + } + + nodetest_t parse_node_test_type(const xpath_lexer_string& name) { + switch (name.begin[0]) { case 'c': - if (name == PUGIXML_TEXT("comment")) + if (name == PUGIXML_TEXT("comment")) { return nodetest_type_comment; + } break; case 'n': - if (name == PUGIXML_TEXT("node")) + if (name == PUGIXML_TEXT("node")) { return nodetest_type_node; + } break; case 'p': - if (name == PUGIXML_TEXT("processing-instruction")) + if (name == PUGIXML_TEXT("processing-instruction")) { return nodetest_type_pi; + } break; case 't': - if (name == PUGIXML_TEXT("text")) + if (name == PUGIXML_TEXT("text")) { return nodetest_type_text; + } break; default: break; - } - - return nodetest_none; } - // PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall - xpath_ast_node* parse_primary_expression() - { - switch (_lexer.current()) - { - case lex_var_ref: - { + return nodetest_none; + } + + // PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall + xpath_ast_node* parse_primary_expression() { + switch (_lexer.current()) { + case lex_var_ref: { xpath_lexer_string name = _lexer.contents(); - if (!_variables) + if (!_variables) { throw_error("Unknown variable: variable set is not provided"); + } xpath_variable* var = get_variable(_variables, name.begin, name.end); - if (!var) + if (!var) { throw_error("Unknown variable: variable set does not contain the given name"); + } _lexer.next(); return new (alloc_node()) xpath_ast_node(ast_variable, var->type(), var); } - case lex_open_brace: - { + case lex_open_brace: { _lexer.next(); xpath_ast_node* n = parse_expression(); - if (_lexer.current() != lex_close_brace) + if (_lexer.current() != lex_close_brace) { throw_error("Unmatched braces"); + } _lexer.next(); return n; } - case lex_quoted_string: - { + case lex_quoted_string: { const char_t* value = alloc_string(_lexer.contents()); xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_string_constant, xpath_type_string, value); @@ -9063,12 +9004,12 @@ PUGI__NS_BEGIN return n; } - case lex_number: - { + case lex_number: { double value = 0; - if (!convert_string_to_number(_lexer.contents().begin, _lexer.contents().end, &value)) + if (!convert_string_to_number(_lexer.contents().begin, _lexer.contents().end, &value)) { throw_error_oom(); + } xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_number_constant, xpath_type_number, value); _lexer.next(); @@ -9076,9 +9017,8 @@ PUGI__NS_BEGIN return n; } - case lex_string: - { - xpath_ast_node* args[2] = {0}; + case lex_string: { + xpath_ast_node* args[2] = { 0 }; size_t argc = 0; xpath_lexer_string function = _lexer.contents(); @@ -9086,23 +9026,28 @@ PUGI__NS_BEGIN xpath_ast_node* last_arg = 0; - if (_lexer.current() != lex_open_brace) + if (_lexer.current() != lex_open_brace) { throw_error("Unrecognized function call"); + } _lexer.next(); - if (_lexer.current() != lex_close_brace) + if (_lexer.current() != lex_close_brace) { args[argc++] = parse_expression(); + } - while (_lexer.current() != lex_close_brace) - { - if (_lexer.current() != lex_comma) + while (_lexer.current() != lex_close_brace) { + if (_lexer.current() != lex_comma) { throw_error("No comma between function arguments"); + } _lexer.next(); xpath_ast_node* n = parse_expression(); - if (argc < 2) args[argc] = n; - else last_arg->set_next(n); + if (argc < 2) { + args[argc] = n; + } else { + last_arg->set_next(n); + } argc++; last_arg = n; @@ -9117,644 +9062,610 @@ PUGI__NS_BEGIN throw_error("Unrecognizable primary expression"); return 0; - } } + } - // FilterExpr ::= PrimaryExpr | FilterExpr Predicate - // Predicate ::= '[' PredicateExpr ']' - // PredicateExpr ::= Expr - xpath_ast_node* parse_filter_expression() - { - xpath_ast_node* n = parse_primary_expression(); - - while (_lexer.current() == lex_open_square_brace) - { - _lexer.next(); + // FilterExpr ::= PrimaryExpr | FilterExpr Predicate + // Predicate ::= '[' PredicateExpr ']' + // PredicateExpr ::= Expr + xpath_ast_node* parse_filter_expression() { + xpath_ast_node* n = parse_primary_expression(); - xpath_ast_node* expr = parse_expression(); + while (_lexer.current() == lex_open_square_brace) { + _lexer.next(); - if (n->rettype() != xpath_type_node_set) throw_error("Predicate has to be applied to node set"); + xpath_ast_node* expr = parse_expression(); - bool posinv = expr->rettype() != xpath_type_number && expr->is_posinv(); + if (n->rettype() != xpath_type_node_set) { + throw_error("Predicate has to be applied to node set"); + } - n = new (alloc_node()) xpath_ast_node(posinv ? ast_filter_posinv : ast_filter, xpath_type_node_set, n, expr); + bool posinv = expr->rettype() != xpath_type_number && expr->is_posinv(); - if (_lexer.current() != lex_close_square_brace) - throw_error("Unmatched square brace"); + n = new (alloc_node()) xpath_ast_node(posinv ? ast_filter_posinv : ast_filter, xpath_type_node_set, n, expr); - _lexer.next(); + if (_lexer.current() != lex_close_square_brace) { + throw_error("Unmatched square brace"); } - return n; + _lexer.next(); } - // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep - // AxisSpecifier ::= AxisName '::' | '@'? - // NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')' - // NameTest ::= '*' | NCName ':' '*' | QName - // AbbreviatedStep ::= '.' | '..' - xpath_ast_node* parse_step(xpath_ast_node* set) - { - if (set && set->rettype() != xpath_type_node_set) - throw_error("Step has to be applied to node set"); + return n; + } - bool axis_specified = false; - axis_t axis = axis_child; // implied child axis + // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep + // AxisSpecifier ::= AxisName '::' | '@'? + // NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')' + // NameTest ::= '*' | NCName ':' '*' | QName + // AbbreviatedStep ::= '.' | '..' + xpath_ast_node* parse_step(xpath_ast_node* set) { + if (set && set->rettype() != xpath_type_node_set) { + throw_error("Step has to be applied to node set"); + } - if (_lexer.current() == lex_axis_attribute) - { - axis = axis_attribute; - axis_specified = true; + bool axis_specified = false; + axis_t axis = axis_child; // implied child axis - _lexer.next(); - } - else if (_lexer.current() == lex_dot) - { - _lexer.next(); + if (_lexer.current() == lex_axis_attribute) { + axis = axis_attribute; + axis_specified = true; - return new (alloc_node()) xpath_ast_node(ast_step, set, axis_self, nodetest_type_node, 0); - } - else if (_lexer.current() == lex_double_dot) - { - _lexer.next(); + _lexer.next(); + } else if (_lexer.current() == lex_dot) { + _lexer.next(); - return new (alloc_node()) xpath_ast_node(ast_step, set, axis_parent, nodetest_type_node, 0); - } + return new (alloc_node()) xpath_ast_node(ast_step, set, axis_self, nodetest_type_node, 0); + } else if (_lexer.current() == lex_double_dot) { + _lexer.next(); - nodetest_t nt_type = nodetest_none; - xpath_lexer_string nt_name; + return new (alloc_node()) xpath_ast_node(ast_step, set, axis_parent, nodetest_type_node, 0); + } - if (_lexer.current() == lex_string) - { - // node name test - nt_name = _lexer.contents(); - _lexer.next(); + nodetest_t nt_type = nodetest_none; + xpath_lexer_string nt_name; - // was it an axis name? - if (_lexer.current() == lex_double_colon) - { - // parse axis name - if (axis_specified) throw_error("Two axis specifiers in one step"); + if (_lexer.current() == lex_string) { + // node name test + nt_name = _lexer.contents(); + _lexer.next(); - axis = parse_axis_name(nt_name, axis_specified); + // was it an axis name? + if (_lexer.current() == lex_double_colon) { + // parse axis name + if (axis_specified) { + throw_error("Two axis specifiers in one step"); + } - if (!axis_specified) throw_error("Unknown axis"); + axis = parse_axis_name(nt_name, axis_specified); - // read actual node test - _lexer.next(); + if (!axis_specified) { + throw_error("Unknown axis"); + } - if (_lexer.current() == lex_multiply) - { - nt_type = nodetest_all; - nt_name = xpath_lexer_string(); - _lexer.next(); - } - else if (_lexer.current() == lex_string) - { - nt_name = _lexer.contents(); - _lexer.next(); - } - else throw_error("Unrecognized node test"); + // read actual node test + _lexer.next(); + + if (_lexer.current() == lex_multiply) { + nt_type = nodetest_all; + nt_name = xpath_lexer_string(); + _lexer.next(); + } else if (_lexer.current() == lex_string) { + nt_name = _lexer.contents(); + _lexer.next(); + } else { + throw_error("Unrecognized node test"); } + } - if (nt_type == nodetest_none) - { - // node type test or processing-instruction - if (_lexer.current() == lex_open_brace) - { - _lexer.next(); + if (nt_type == nodetest_none) { + // node type test or processing-instruction + if (_lexer.current() == lex_open_brace) { + _lexer.next(); - if (_lexer.current() == lex_close_brace) - { - _lexer.next(); + if (_lexer.current() == lex_close_brace) { + _lexer.next(); - nt_type = parse_node_test_type(nt_name); + nt_type = parse_node_test_type(nt_name); - if (nt_type == nodetest_none) throw_error("Unrecognized node type"); + if (nt_type == nodetest_none) { + throw_error("Unrecognized node type"); + } - nt_name = xpath_lexer_string(); + nt_name = xpath_lexer_string(); + } else if (nt_name == PUGIXML_TEXT("processing-instruction")) { + if (_lexer.current() != lex_quoted_string) { + throw_error("Only literals are allowed as arguments to processing-instruction()"); } - else if (nt_name == PUGIXML_TEXT("processing-instruction")) - { - if (_lexer.current() != lex_quoted_string) - throw_error("Only literals are allowed as arguments to processing-instruction()"); - nt_type = nodetest_pi; - nt_name = _lexer.contents(); - _lexer.next(); + nt_type = nodetest_pi; + nt_name = _lexer.contents(); + _lexer.next(); - if (_lexer.current() != lex_close_brace) - throw_error("Unmatched brace near processing-instruction()"); - _lexer.next(); + if (_lexer.current() != lex_close_brace) { + throw_error("Unmatched brace near processing-instruction()"); } - else - throw_error("Unmatched brace near node type test"); - + _lexer.next(); + } else { + throw_error("Unmatched brace near node type test"); } - // QName or NCName:* - else + + } + // QName or NCName:* + else { + if (nt_name.end - nt_name.begin > 2 && nt_name.end[-2] == ':' && nt_name.end[-1] == '*') // NCName:* { - if (nt_name.end - nt_name.begin > 2 && nt_name.end[-2] == ':' && nt_name.end[-1] == '*') // NCName:* - { - nt_name.end--; // erase * + nt_name.end--; // erase * - nt_type = nodetest_all_in_namespace; - } - else nt_type = nodetest_name; + nt_type = nodetest_all_in_namespace; + } else { + nt_type = nodetest_name; } } } - else if (_lexer.current() == lex_multiply) - { - nt_type = nodetest_all; - _lexer.next(); - } - else throw_error("Unrecognized node test"); - - xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step, set, axis, nt_type, alloc_string(nt_name)); + } else if (_lexer.current() == lex_multiply) { + nt_type = nodetest_all; + _lexer.next(); + } else { + throw_error("Unrecognized node test"); + } - xpath_ast_node* last = 0; + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step, set, axis, nt_type, alloc_string(nt_name)); - while (_lexer.current() == lex_open_square_brace) - { - _lexer.next(); + xpath_ast_node* last = 0; - xpath_ast_node* expr = parse_expression(); + while (_lexer.current() == lex_open_square_brace) { + _lexer.next(); - xpath_ast_node* pred = new (alloc_node()) xpath_ast_node(ast_predicate, xpath_type_node_set, expr); + xpath_ast_node* expr = parse_expression(); - if (_lexer.current() != lex_close_square_brace) - throw_error("Unmatched square brace"); - _lexer.next(); + xpath_ast_node* pred = new (alloc_node()) xpath_ast_node(ast_predicate, xpath_type_node_set, expr); - if (last) last->set_next(pred); - else n->set_right(pred); + if (_lexer.current() != lex_close_square_brace) { + throw_error("Unmatched square brace"); + } + _lexer.next(); - last = pred; + if (last) { + last->set_next(pred); + } else { + n->set_right(pred); } - return n; + last = pred; } - // RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step - xpath_ast_node* parse_relative_location_path(xpath_ast_node* set) - { - xpath_ast_node* n = parse_step(set); + return n; + } - while (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) - { - lexeme_t l = _lexer.current(); - _lexer.next(); + // RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step + xpath_ast_node* parse_relative_location_path(xpath_ast_node* set) { + xpath_ast_node* n = parse_step(set); - if (l == lex_double_slash) - n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + while (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) { + lexeme_t l = _lexer.current(); + _lexer.next(); - n = parse_step(n); + if (l == lex_double_slash) { + n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); } - return n; + n = parse_step(n); } - // LocationPath ::= RelativeLocationPath | AbsoluteLocationPath - // AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath - xpath_ast_node* parse_location_path() - { - if (_lexer.current() == lex_slash) - { - _lexer.next(); - - xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); + return n; + } - // relative location path can start from axis_attribute, dot, double_dot, multiply and string lexemes; any other lexeme means standalone root path - lexeme_t l = _lexer.current(); + // LocationPath ::= RelativeLocationPath | AbsoluteLocationPath + // AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath + xpath_ast_node* parse_location_path() { + if (_lexer.current() == lex_slash) { + _lexer.next(); - if (l == lex_string || l == lex_axis_attribute || l == lex_dot || l == lex_double_dot || l == lex_multiply) - return parse_relative_location_path(n); - else - return n; - } - else if (_lexer.current() == lex_double_slash) - { - _lexer.next(); + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); - xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); - n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + // relative location path can start from axis_attribute, dot, double_dot, multiply and string lexemes; any other lexeme means standalone root path + lexeme_t l = _lexer.current(); + if (l == lex_string || l == lex_axis_attribute || l == lex_dot || l == lex_double_dot || l == lex_multiply) { return parse_relative_location_path(n); + } else { + return n; } + } else if (_lexer.current() == lex_double_slash) { + _lexer.next(); - // else clause moved outside of if because of bogus warning 'control may reach end of non-void function being inlined' in gcc 4.0.1 - return parse_relative_location_path(0); - } - - // PathExpr ::= LocationPath - // | FilterExpr - // | FilterExpr '/' RelativeLocationPath - // | FilterExpr '//' RelativeLocationPath - xpath_ast_node* parse_path_expression() - { - // Clarification. - // PathExpr begins with either LocationPath or FilterExpr. - // FilterExpr begins with PrimaryExpr - // PrimaryExpr begins with '$' in case of it being a variable reference, - // '(' in case of it being an expression, string literal, number constant or - // function call. - - if (_lexer.current() == lex_var_ref || _lexer.current() == lex_open_brace || - _lexer.current() == lex_quoted_string || _lexer.current() == lex_number || - _lexer.current() == lex_string) - { - if (_lexer.current() == lex_string) - { - // This is either a function call, or not - if not, we shall proceed with location path - const char_t* state = _lexer.state(); - - while (PUGI__IS_CHARTYPE(*state, ct_space)) ++state; + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); + n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); - if (*state != '(') return parse_location_path(); - - // This looks like a function call; however this still can be a node-test. Check it. - if (parse_node_test_type(_lexer.contents()) != nodetest_none) return parse_location_path(); - } + return parse_relative_location_path(n); + } - xpath_ast_node* n = parse_filter_expression(); + // else clause moved outside of if because of bogus warning 'control may reach end of non-void function being inlined' in gcc 4.0.1 + return parse_relative_location_path(0); + } - if (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) - { - lexeme_t l = _lexer.current(); - _lexer.next(); + // PathExpr ::= LocationPath + // | FilterExpr + // | FilterExpr '/' RelativeLocationPath + // | FilterExpr '//' RelativeLocationPath + xpath_ast_node* parse_path_expression() { + // Clarification. + // PathExpr begins with either LocationPath or FilterExpr. + // FilterExpr begins with PrimaryExpr + // PrimaryExpr begins with '$' in case of it being a variable reference, + // '(' in case of it being an expression, string literal, number constant or + // function call. - if (l == lex_double_slash) - { - if (n->rettype() != xpath_type_node_set) throw_error("Step has to be applied to node set"); + if (_lexer.current() == lex_var_ref || _lexer.current() == lex_open_brace || _lexer.current() == lex_quoted_string || _lexer.current() == lex_number || _lexer.current() == lex_string) { + if (_lexer.current() == lex_string) { + // This is either a function call, or not - if not, we shall proceed with location path + const char_t* state = _lexer.state(); - n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); - } + while (PUGI__IS_CHARTYPE(*state, ct_space)) { + ++state; + } - // select from location path - return parse_relative_location_path(n); + if (*state != '(') { + return parse_location_path(); } - return n; + // This looks like a function call; however this still can be a node-test. Check it. + if (parse_node_test_type(_lexer.contents()) != nodetest_none) { + return parse_location_path(); + } } - else return parse_location_path(); - } - // UnionExpr ::= PathExpr | UnionExpr '|' PathExpr - xpath_ast_node* parse_union_expression() - { - xpath_ast_node* n = parse_path_expression(); + xpath_ast_node* n = parse_filter_expression(); - while (_lexer.current() == lex_union) - { + if (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) { + lexeme_t l = _lexer.current(); _lexer.next(); - xpath_ast_node* expr = parse_union_expression(); + if (l == lex_double_slash) { + if (n->rettype() != xpath_type_node_set) { + throw_error("Step has to be applied to node set"); + } - if (n->rettype() != xpath_type_node_set || expr->rettype() != xpath_type_node_set) - throw_error("Union operator has to be applied to node sets"); + n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + } - n = new (alloc_node()) xpath_ast_node(ast_op_union, xpath_type_node_set, n, expr); + // select from location path + return parse_relative_location_path(n); } return n; + } else { + return parse_location_path(); } + } - // UnaryExpr ::= UnionExpr | '-' UnaryExpr - xpath_ast_node* parse_unary_expression() - { - if (_lexer.current() == lex_minus) - { - _lexer.next(); + // UnionExpr ::= PathExpr | UnionExpr '|' PathExpr + xpath_ast_node* parse_union_expression() { + xpath_ast_node* n = parse_path_expression(); + + while (_lexer.current() == lex_union) { + _lexer.next(); - xpath_ast_node* expr = parse_unary_expression(); + xpath_ast_node* expr = parse_union_expression(); - return new (alloc_node()) xpath_ast_node(ast_op_negate, xpath_type_number, expr); + if (n->rettype() != xpath_type_node_set || expr->rettype() != xpath_type_node_set) { + throw_error("Union operator has to be applied to node sets"); } - else return parse_union_expression(); + + n = new (alloc_node()) xpath_ast_node(ast_op_union, xpath_type_node_set, n, expr); } - // MultiplicativeExpr ::= UnaryExpr - // | MultiplicativeExpr '*' UnaryExpr - // | MultiplicativeExpr 'div' UnaryExpr - // | MultiplicativeExpr 'mod' UnaryExpr - xpath_ast_node* parse_multiplicative_expression() - { - xpath_ast_node* n = parse_unary_expression(); + return n; + } - while (_lexer.current() == lex_multiply || (_lexer.current() == lex_string && - (_lexer.contents() == PUGIXML_TEXT("mod") || _lexer.contents() == PUGIXML_TEXT("div")))) - { - ast_type_t op = _lexer.current() == lex_multiply ? ast_op_multiply : - _lexer.contents().begin[0] == 'd' ? ast_op_divide : ast_op_mod; - _lexer.next(); + // UnaryExpr ::= UnionExpr | '-' UnaryExpr + xpath_ast_node* parse_unary_expression() { + if (_lexer.current() == lex_minus) { + _lexer.next(); - xpath_ast_node* expr = parse_unary_expression(); + xpath_ast_node* expr = parse_unary_expression(); - n = new (alloc_node()) xpath_ast_node(op, xpath_type_number, n, expr); - } + return new (alloc_node()) xpath_ast_node(ast_op_negate, xpath_type_number, expr); + } else { + return parse_union_expression(); + } + } - return n; + // MultiplicativeExpr ::= UnaryExpr + // | MultiplicativeExpr '*' UnaryExpr + // | MultiplicativeExpr 'div' UnaryExpr + // | MultiplicativeExpr 'mod' UnaryExpr + xpath_ast_node* parse_multiplicative_expression() { + xpath_ast_node* n = parse_unary_expression(); + + while (_lexer.current() == lex_multiply || (_lexer.current() == lex_string && (_lexer.contents() == PUGIXML_TEXT("mod") || _lexer.contents() == PUGIXML_TEXT("div")))) { + ast_type_t op = _lexer.current() == lex_multiply ? ast_op_multiply : _lexer.contents().begin[0] == 'd' ? ast_op_divide + : ast_op_mod; + _lexer.next(); + + xpath_ast_node* expr = parse_unary_expression(); + + n = new (alloc_node()) xpath_ast_node(op, xpath_type_number, n, expr); } - // AdditiveExpr ::= MultiplicativeExpr - // | AdditiveExpr '+' MultiplicativeExpr - // | AdditiveExpr '-' MultiplicativeExpr - xpath_ast_node* parse_additive_expression() - { - xpath_ast_node* n = parse_multiplicative_expression(); + return n; + } - while (_lexer.current() == lex_plus || _lexer.current() == lex_minus) - { - lexeme_t l = _lexer.current(); + // AdditiveExpr ::= MultiplicativeExpr + // | AdditiveExpr '+' MultiplicativeExpr + // | AdditiveExpr '-' MultiplicativeExpr + xpath_ast_node* parse_additive_expression() { + xpath_ast_node* n = parse_multiplicative_expression(); - _lexer.next(); + while (_lexer.current() == lex_plus || _lexer.current() == lex_minus) { + lexeme_t l = _lexer.current(); - xpath_ast_node* expr = parse_multiplicative_expression(); + _lexer.next(); - n = new (alloc_node()) xpath_ast_node(l == lex_plus ? ast_op_add : ast_op_subtract, xpath_type_number, n, expr); - } + xpath_ast_node* expr = parse_multiplicative_expression(); - return n; + n = new (alloc_node()) xpath_ast_node(l == lex_plus ? ast_op_add : ast_op_subtract, xpath_type_number, n, expr); } - // RelationalExpr ::= AdditiveExpr - // | RelationalExpr '<' AdditiveExpr - // | RelationalExpr '>' AdditiveExpr - // | RelationalExpr '<=' AdditiveExpr - // | RelationalExpr '>=' AdditiveExpr - xpath_ast_node* parse_relational_expression() - { - xpath_ast_node* n = parse_additive_expression(); + return n; + } - while (_lexer.current() == lex_less || _lexer.current() == lex_less_or_equal || - _lexer.current() == lex_greater || _lexer.current() == lex_greater_or_equal) - { - lexeme_t l = _lexer.current(); - _lexer.next(); + // RelationalExpr ::= AdditiveExpr + // | RelationalExpr '<' AdditiveExpr + // | RelationalExpr '>' AdditiveExpr + // | RelationalExpr '<=' AdditiveExpr + // | RelationalExpr '>=' AdditiveExpr + xpath_ast_node* parse_relational_expression() { + xpath_ast_node* n = parse_additive_expression(); - xpath_ast_node* expr = parse_additive_expression(); + while (_lexer.current() == lex_less || _lexer.current() == lex_less_or_equal || _lexer.current() == lex_greater || _lexer.current() == lex_greater_or_equal) { + lexeme_t l = _lexer.current(); + _lexer.next(); - n = new (alloc_node()) xpath_ast_node(l == lex_less ? ast_op_less : l == lex_greater ? ast_op_greater : - l == lex_less_or_equal ? ast_op_less_or_equal : ast_op_greater_or_equal, xpath_type_boolean, n, expr); - } + xpath_ast_node* expr = parse_additive_expression(); - return n; + n = new (alloc_node()) xpath_ast_node(l == lex_less ? ast_op_less : l == lex_greater ? ast_op_greater + : l == lex_less_or_equal ? ast_op_less_or_equal + : ast_op_greater_or_equal, + xpath_type_boolean, n, expr); } - // EqualityExpr ::= RelationalExpr - // | EqualityExpr '=' RelationalExpr - // | EqualityExpr '!=' RelationalExpr - xpath_ast_node* parse_equality_expression() - { - xpath_ast_node* n = parse_relational_expression(); + return n; + } - while (_lexer.current() == lex_equal || _lexer.current() == lex_not_equal) - { - lexeme_t l = _lexer.current(); + // EqualityExpr ::= RelationalExpr + // | EqualityExpr '=' RelationalExpr + // | EqualityExpr '!=' RelationalExpr + xpath_ast_node* parse_equality_expression() { + xpath_ast_node* n = parse_relational_expression(); - _lexer.next(); + while (_lexer.current() == lex_equal || _lexer.current() == lex_not_equal) { + lexeme_t l = _lexer.current(); - xpath_ast_node* expr = parse_relational_expression(); + _lexer.next(); - n = new (alloc_node()) xpath_ast_node(l == lex_equal ? ast_op_equal : ast_op_not_equal, xpath_type_boolean, n, expr); - } + xpath_ast_node* expr = parse_relational_expression(); - return n; + n = new (alloc_node()) xpath_ast_node(l == lex_equal ? ast_op_equal : ast_op_not_equal, xpath_type_boolean, n, expr); } - // AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr - xpath_ast_node* parse_and_expression() - { - xpath_ast_node* n = parse_equality_expression(); + return n; + } - while (_lexer.current() == lex_string && _lexer.contents() == PUGIXML_TEXT("and")) - { - _lexer.next(); + // AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr + xpath_ast_node* parse_and_expression() { + xpath_ast_node* n = parse_equality_expression(); - xpath_ast_node* expr = parse_equality_expression(); + while (_lexer.current() == lex_string && _lexer.contents() == PUGIXML_TEXT("and")) { + _lexer.next(); - n = new (alloc_node()) xpath_ast_node(ast_op_and, xpath_type_boolean, n, expr); - } + xpath_ast_node* expr = parse_equality_expression(); - return n; + n = new (alloc_node()) xpath_ast_node(ast_op_and, xpath_type_boolean, n, expr); } - // OrExpr ::= AndExpr | OrExpr 'or' AndExpr - xpath_ast_node* parse_or_expression() - { - xpath_ast_node* n = parse_and_expression(); + return n; + } - while (_lexer.current() == lex_string && _lexer.contents() == PUGIXML_TEXT("or")) - { - _lexer.next(); + // OrExpr ::= AndExpr | OrExpr 'or' AndExpr + xpath_ast_node* parse_or_expression() { + xpath_ast_node* n = parse_and_expression(); - xpath_ast_node* expr = parse_and_expression(); + while (_lexer.current() == lex_string && _lexer.contents() == PUGIXML_TEXT("or")) { + _lexer.next(); - n = new (alloc_node()) xpath_ast_node(ast_op_or, xpath_type_boolean, n, expr); - } + xpath_ast_node* expr = parse_and_expression(); - return n; + n = new (alloc_node()) xpath_ast_node(ast_op_or, xpath_type_boolean, n, expr); } - // Expr ::= OrExpr - xpath_ast_node* parse_expression() - { - return parse_or_expression(); - } + return n; + } - xpath_parser(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result): _alloc(alloc), _lexer(query), _query(query), _variables(variables), _result(result) - { - } + // Expr ::= OrExpr + xpath_ast_node* parse_expression() { + return parse_or_expression(); + } - xpath_ast_node* parse() - { - xpath_ast_node* result = parse_expression(); + xpath_parser(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result) : + _alloc(alloc), _lexer(query), _query(query), _variables(variables), _result(result) { + } - if (_lexer.current() != lex_eof) - { - // there are still unparsed tokens left, error - throw_error("Incorrect query"); - } + xpath_ast_node* parse() { + xpath_ast_node* result = parse_expression(); - return result; + if (_lexer.current() != lex_eof) { + // there are still unparsed tokens left, error + throw_error("Incorrect query"); } - static xpath_ast_node* parse(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result) - { - xpath_parser parser(query, variables, alloc, result); + return result; + } + + static xpath_ast_node* parse(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result) { + xpath_parser parser(query, variables, alloc, result); #ifdef PUGIXML_NO_EXCEPTIONS - int error = setjmp(parser._error_handler); + int error = setjmp(parser._error_handler); - return (error == 0) ? parser.parse() : 0; + return (error == 0) ? parser.parse() : 0; #else - return parser.parse(); + return parser.parse(); #endif - } - }; - - struct xpath_query_impl - { - static xpath_query_impl* create() - { - void* memory = xml_memory::allocate(sizeof(xpath_query_impl)); - - return new (memory) xpath_query_impl(); - } + } +}; - static void destroy(void* ptr) - { - if (!ptr) return; +struct xpath_query_impl { + static xpath_query_impl* create() { + void* memory = xml_memory::allocate(sizeof(xpath_query_impl)); - // free all allocated pages - static_cast(ptr)->alloc.release(); + return new (memory) xpath_query_impl(); + } - // free allocator memory (with the first page) - xml_memory::deallocate(ptr); + static void destroy(void* ptr) { + if (!ptr) { + return; } - xpath_query_impl(): root(0), alloc(&block) - { - block.next = 0; - } + // free all allocated pages + static_cast(ptr)->alloc.release(); - xpath_ast_node* root; - xpath_allocator alloc; - xpath_memory_block block; - }; + // free allocator memory (with the first page) + xml_memory::deallocate(ptr); + } - PUGI__FN xpath_string evaluate_string_impl(xpath_query_impl* impl, const xpath_node& n, xpath_stack_data& sd) - { - if (!impl) return xpath_string(); + xpath_query_impl() : + root(0), alloc(&block) { + block.next = 0; + } - #ifdef PUGIXML_NO_EXCEPTIONS - if (setjmp(sd.error_handler)) return xpath_string(); - #endif + xpath_ast_node* root; + xpath_allocator alloc; + xpath_memory_block block; +}; - xpath_context c(n, 1, 1); +PUGI__FN xpath_string evaluate_string_impl(xpath_query_impl* impl, const xpath_node& n, xpath_stack_data& sd) { + if (!impl) { + return xpath_string(); + } - return impl->root->eval_string(c, sd.stack); + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) { + return xpath_string(); } + #endif + + xpath_context c(n, 1, 1); + + return impl->root->eval_string(c, sd.stack); +} PUGI__NS_END -namespace pugi -{ -#ifndef PUGIXML_NO_EXCEPTIONS - PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_) - { +namespace pugi { + #ifndef PUGIXML_NO_EXCEPTIONS + PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_) : + _result(result_) { assert(_result.error); } - PUGI__FN const char* xpath_exception::what() const throw() - { + PUGI__FN const char* xpath_exception::what() const throw() { return _result.error; } - PUGI__FN const xpath_parse_result& xpath_exception::result() const - { + PUGI__FN const xpath_parse_result& xpath_exception::result() const { return _result; } -#endif + #endif - PUGI__FN xpath_node::xpath_node() - { + PUGI__FN xpath_node::xpath_node() { } - PUGI__FN xpath_node::xpath_node(const xml_node& node_): _node(node_) - { + PUGI__FN xpath_node::xpath_node(const xml_node& node_) : + _node(node_) { } - PUGI__FN xpath_node::xpath_node(const xml_attribute& attribute_, const xml_node& parent_): _node(attribute_ ? parent_ : xml_node()), _attribute(attribute_) - { + PUGI__FN xpath_node::xpath_node(const xml_attribute& attribute_, const xml_node& parent_) : + _node(attribute_ ? parent_ : xml_node()), _attribute(attribute_) { } - PUGI__FN xml_node xpath_node::node() const - { + PUGI__FN xml_node xpath_node::node() const { return _attribute ? xml_node() : _node; } - PUGI__FN xml_attribute xpath_node::attribute() const - { + PUGI__FN xml_attribute xpath_node::attribute() const { return _attribute; } - PUGI__FN xml_node xpath_node::parent() const - { + PUGI__FN xml_node xpath_node::parent() const { return _attribute ? _node : _node.parent(); } - PUGI__FN static void unspecified_bool_xpath_node(xpath_node***) - { + PUGI__FN static void unspecified_bool_xpath_node(xpath_node***) { } - PUGI__FN xpath_node::operator xpath_node::unspecified_bool_type() const - { + PUGI__FN xpath_node::operator xpath_node::unspecified_bool_type() const { return (_node || _attribute) ? unspecified_bool_xpath_node : 0; } - PUGI__FN bool xpath_node::operator!() const - { + PUGI__FN bool xpath_node::operator!() const { return !(_node || _attribute); } - PUGI__FN bool xpath_node::operator==(const xpath_node& n) const - { + PUGI__FN bool xpath_node::operator==(const xpath_node& n) const { return _node == n._node && _attribute == n._attribute; } - PUGI__FN bool xpath_node::operator!=(const xpath_node& n) const - { + PUGI__FN bool xpath_node::operator!=(const xpath_node& n) const { return _node != n._node || _attribute != n._attribute; } -#ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xpath_node& lhs, bool rhs) - { + #ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xpath_node& lhs, bool rhs) { return (bool)lhs && rhs; } - PUGI__FN bool operator||(const xpath_node& lhs, bool rhs) - { + PUGI__FN bool operator||(const xpath_node& lhs, bool rhs) { return (bool)lhs || rhs; } -#endif + #endif - PUGI__FN void xpath_node_set::_assign(const_iterator begin_, const_iterator end_) - { + PUGI__FN void xpath_node_set::_assign(const_iterator begin_, const_iterator end_) { assert(begin_ <= end_); size_t size_ = static_cast(end_ - begin_); - if (size_ <= 1) - { + if (size_ <= 1) { // deallocate old buffer - if (_begin != &_storage) impl::xml_memory::deallocate(_begin); + if (_begin != &_storage) { + impl::xml_memory::deallocate(_begin); + } // use internal buffer - if (begin_ != end_) _storage = *begin_; + if (begin_ != end_) { + _storage = *begin_; + } _begin = &_storage; _end = &_storage + size_; - } - else - { + } else { // make heap copy xpath_node* storage = static_cast(impl::xml_memory::allocate(size_ * sizeof(xpath_node))); - if (!storage) - { - #ifdef PUGIXML_NO_EXCEPTIONS + if (!storage) { + #ifdef PUGIXML_NO_EXCEPTIONS return; - #else + #else throw std::bad_alloc(); - #endif + #endif } memcpy(storage, begin_, size_ * sizeof(xpath_node)); // deallocate old buffer - if (_begin != &_storage) impl::xml_memory::deallocate(_begin); + if (_begin != &_storage) { + impl::xml_memory::deallocate(_begin); + } // finalize _begin = storage; @@ -9762,28 +9673,30 @@ namespace pugi } } - PUGI__FN xpath_node_set::xpath_node_set(): _type(type_unsorted), _begin(&_storage), _end(&_storage) - { + PUGI__FN xpath_node_set::xpath_node_set() : + _type(type_unsorted), _begin(&_storage), _end(&_storage) { } - PUGI__FN xpath_node_set::xpath_node_set(const_iterator begin_, const_iterator end_, type_t type_): _type(type_), _begin(&_storage), _end(&_storage) - { + PUGI__FN xpath_node_set::xpath_node_set(const_iterator begin_, const_iterator end_, type_t type_) : + _type(type_), _begin(&_storage), _end(&_storage) { _assign(begin_, end_); } - PUGI__FN xpath_node_set::~xpath_node_set() - { - if (_begin != &_storage) impl::xml_memory::deallocate(_begin); + PUGI__FN xpath_node_set::~xpath_node_set() { + if (_begin != &_storage) { + impl::xml_memory::deallocate(_begin); + } } - PUGI__FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(ns._type), _begin(&_storage), _end(&_storage) - { + PUGI__FN xpath_node_set::xpath_node_set(const xpath_node_set& ns) : + _type(ns._type), _begin(&_storage), _end(&_storage) { _assign(ns._begin, ns._end); } - PUGI__FN xpath_node_set& xpath_node_set::operator=(const xpath_node_set& ns) - { - if (this == &ns) return *this; + PUGI__FN xpath_node_set& xpath_node_set::operator=(const xpath_node_set& ns) { + if (this == &ns) { + return *this; + } _type = ns._type; _assign(ns._begin, ns._end); @@ -9791,132 +9704,117 @@ namespace pugi return *this; } - PUGI__FN xpath_node_set::type_t xpath_node_set::type() const - { + PUGI__FN xpath_node_set::type_t xpath_node_set::type() const { return _type; } - PUGI__FN size_t xpath_node_set::size() const - { + PUGI__FN size_t xpath_node_set::size() const { return _end - _begin; } - PUGI__FN bool xpath_node_set::empty() const - { + PUGI__FN bool xpath_node_set::empty() const { return _begin == _end; } - PUGI__FN const xpath_node& xpath_node_set::operator[](size_t index) const - { + PUGI__FN const xpath_node& xpath_node_set::operator[](size_t index) const { assert(index < size()); return _begin[index]; } - PUGI__FN xpath_node_set::const_iterator xpath_node_set::begin() const - { + PUGI__FN xpath_node_set::const_iterator xpath_node_set::begin() const { return _begin; } - PUGI__FN xpath_node_set::const_iterator xpath_node_set::end() const - { + PUGI__FN xpath_node_set::const_iterator xpath_node_set::end() const { return _end; } - PUGI__FN void xpath_node_set::sort(bool reverse) - { + PUGI__FN void xpath_node_set::sort(bool reverse) { _type = impl::xpath_sort(_begin, _end, _type, reverse); } - PUGI__FN xpath_node xpath_node_set::first() const - { + PUGI__FN xpath_node xpath_node_set::first() const { return impl::xpath_first(_begin, _end, _type); } - PUGI__FN xpath_parse_result::xpath_parse_result(): error("Internal error"), offset(0) - { + PUGI__FN xpath_parse_result::xpath_parse_result() : + error("Internal error"), offset(0) { } - PUGI__FN xpath_parse_result::operator bool() const - { + PUGI__FN xpath_parse_result::operator bool() const { return error == 0; } - PUGI__FN const char* xpath_parse_result::description() const - { + PUGI__FN const char* xpath_parse_result::description() const { return error ? error : "No error"; } - PUGI__FN xpath_variable::xpath_variable() - { + PUGI__FN xpath_variable::xpath_variable() { } - PUGI__FN const char_t* xpath_variable::name() const - { - switch (_type) - { - case xpath_type_node_set: - return static_cast(this)->name; + PUGI__FN const char_t* xpath_variable::name() const { + switch (_type) { + case xpath_type_node_set: + return static_cast(this)->name; - case xpath_type_number: - return static_cast(this)->name; + case xpath_type_number: + return static_cast(this)->name; - case xpath_type_string: - return static_cast(this)->name; + case xpath_type_string: + return static_cast(this)->name; - case xpath_type_boolean: - return static_cast(this)->name; + case xpath_type_boolean: + return static_cast(this)->name; - default: - assert(!"Invalid variable type"); - return 0; + default: + assert(!"Invalid variable type"); + return 0; } } - PUGI__FN xpath_value_type xpath_variable::type() const - { + PUGI__FN xpath_value_type xpath_variable::type() const { return _type; } - PUGI__FN bool xpath_variable::get_boolean() const - { + PUGI__FN bool xpath_variable::get_boolean() const { return (_type == xpath_type_boolean) ? static_cast(this)->value : false; } - PUGI__FN double xpath_variable::get_number() const - { + PUGI__FN double xpath_variable::get_number() const { return (_type == xpath_type_number) ? static_cast(this)->value : impl::gen_nan(); } - PUGI__FN const char_t* xpath_variable::get_string() const - { + PUGI__FN const char_t* xpath_variable::get_string() const { const char_t* value = (_type == xpath_type_string) ? static_cast(this)->value : 0; return value ? value : PUGIXML_TEXT(""); } - PUGI__FN const xpath_node_set& xpath_variable::get_node_set() const - { + PUGI__FN const xpath_node_set& xpath_variable::get_node_set() const { return (_type == xpath_type_node_set) ? static_cast(this)->value : impl::dummy_node_set; } - PUGI__FN bool xpath_variable::set(bool value) - { - if (_type != xpath_type_boolean) return false; + PUGI__FN bool xpath_variable::set(bool value) { + if (_type != xpath_type_boolean) { + return false; + } static_cast(this)->value = value; return true; } - PUGI__FN bool xpath_variable::set(double value) - { - if (_type != xpath_type_number) return false; + PUGI__FN bool xpath_variable::set(double value) { + if (_type != xpath_type_number) { + return false; + } static_cast(this)->value = value; return true; } - PUGI__FN bool xpath_variable::set(const char_t* value) - { - if (_type != xpath_type_string) return false; + PUGI__FN bool xpath_variable::set(const char_t* value) { + if (_type != xpath_type_string) { + return false; + } impl::xpath_variable_string* var = static_cast(this); @@ -9924,38 +9822,41 @@ namespace pugi size_t size = (impl::strlength(value) + 1) * sizeof(char_t); char_t* copy = static_cast(impl::xml_memory::allocate(size)); - if (!copy) return false; + if (!copy) { + return false; + } memcpy(copy, value, size); // replace old string - if (var->value) impl::xml_memory::deallocate(var->value); + if (var->value) { + impl::xml_memory::deallocate(var->value); + } var->value = copy; return true; } - PUGI__FN bool xpath_variable::set(const xpath_node_set& value) - { - if (_type != xpath_type_node_set) return false; + PUGI__FN bool xpath_variable::set(const xpath_node_set& value) { + if (_type != xpath_type_node_set) { + return false; + } static_cast(this)->value = value; return true; } - PUGI__FN xpath_variable_set::xpath_variable_set() - { - for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) _data[i] = 0; + PUGI__FN xpath_variable_set::xpath_variable_set() { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) { + _data[i] = 0; + } } - PUGI__FN xpath_variable_set::~xpath_variable_set() - { - for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) - { + PUGI__FN xpath_variable_set::~xpath_variable_set() { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) { xpath_variable* var = _data[i]; - while (var) - { + while (var) { xpath_variable* next = var->_next; impl::delete_xpath_variable(var->_type, var); @@ -9965,34 +9866,35 @@ namespace pugi } } - PUGI__FN xpath_variable* xpath_variable_set::find(const char_t* name) const - { + PUGI__FN xpath_variable* xpath_variable_set::find(const char_t* name) const { const size_t hash_size = sizeof(_data) / sizeof(_data[0]); size_t hash = impl::hash_string(name) % hash_size; // look for existing variable - for (xpath_variable* var = _data[hash]; var; var = var->_next) - if (impl::strequal(var->name(), name)) + for (xpath_variable* var = _data[hash]; var; var = var->_next) { + if (impl::strequal(var->name(), name)) { return var; + } + } return 0; } - PUGI__FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type) - { + PUGI__FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type) { const size_t hash_size = sizeof(_data) / sizeof(_data[0]); size_t hash = impl::hash_string(name) % hash_size; // look for existing variable - for (xpath_variable* var = _data[hash]; var; var = var->_next) - if (impl::strequal(var->name(), name)) + for (xpath_variable* var = _data[hash]; var; var = var->_next) { + if (impl::strequal(var->name(), name)) { return var->type() == type ? var : 0; + } + } // add new variable xpath_variable* result = impl::new_xpath_variable(type, name); - if (result) - { + if (result) { result->_type = type; result->_next = _data[hash]; @@ -10002,125 +9904,118 @@ namespace pugi return result; } - PUGI__FN bool xpath_variable_set::set(const char_t* name, bool value) - { + PUGI__FN bool xpath_variable_set::set(const char_t* name, bool value) { xpath_variable* var = add(name, xpath_type_boolean); return var ? var->set(value) : false; } - PUGI__FN bool xpath_variable_set::set(const char_t* name, double value) - { + PUGI__FN bool xpath_variable_set::set(const char_t* name, double value) { xpath_variable* var = add(name, xpath_type_number); return var ? var->set(value) : false; } - PUGI__FN bool xpath_variable_set::set(const char_t* name, const char_t* value) - { + PUGI__FN bool xpath_variable_set::set(const char_t* name, const char_t* value) { xpath_variable* var = add(name, xpath_type_string); return var ? var->set(value) : false; } - PUGI__FN bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value) - { + PUGI__FN bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value) { xpath_variable* var = add(name, xpath_type_node_set); return var ? var->set(value) : false; } - PUGI__FN xpath_variable* xpath_variable_set::get(const char_t* name) - { + PUGI__FN xpath_variable* xpath_variable_set::get(const char_t* name) { return find(name); } - PUGI__FN const xpath_variable* xpath_variable_set::get(const char_t* name) const - { + PUGI__FN const xpath_variable* xpath_variable_set::get(const char_t* name) const { return find(name); } - PUGI__FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables): _impl(0) - { + PUGI__FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables) : + _impl(0) { impl::xpath_query_impl* qimpl = impl::xpath_query_impl::create(); - if (!qimpl) - { + if (!qimpl) { #ifdef PUGIXML_NO_EXCEPTIONS _result.error = "Out of memory"; #else throw std::bad_alloc(); #endif - } - else - { + } else { impl::buffer_holder impl_holder(qimpl, impl::xpath_query_impl::destroy); qimpl->root = impl::xpath_parser::parse(query, variables, &qimpl->alloc, &_result); - if (qimpl->root) - { + if (qimpl->root) { _impl = static_cast(impl_holder.release()); _result.error = 0; } } } - PUGI__FN xpath_query::~xpath_query() - { + PUGI__FN xpath_query::~xpath_query() { impl::xpath_query_impl::destroy(_impl); } - PUGI__FN xpath_value_type xpath_query::return_type() const - { - if (!_impl) return xpath_type_none; + PUGI__FN xpath_value_type xpath_query::return_type() const { + if (!_impl) { + return xpath_type_none; + } return static_cast(_impl)->root->rettype(); } - PUGI__FN bool xpath_query::evaluate_boolean(const xpath_node& n) const - { - if (!_impl) return false; + PUGI__FN bool xpath_query::evaluate_boolean(const xpath_node& n) const { + if (!_impl) { + return false; + } impl::xpath_context c(n, 1, 1); impl::xpath_stack_data sd; - #ifdef PUGIXML_NO_EXCEPTIONS - if (setjmp(sd.error_handler)) return false; - #endif + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) { + return false; + } + #endif return static_cast(_impl)->root->eval_boolean(c, sd.stack); } - PUGI__FN double xpath_query::evaluate_number(const xpath_node& n) const - { - if (!_impl) return impl::gen_nan(); + PUGI__FN double xpath_query::evaluate_number(const xpath_node& n) const { + if (!_impl) { + return impl::gen_nan(); + } impl::xpath_context c(n, 1, 1); impl::xpath_stack_data sd; - #ifdef PUGIXML_NO_EXCEPTIONS - if (setjmp(sd.error_handler)) return impl::gen_nan(); - #endif + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) { + return impl::gen_nan(); + } + #endif return static_cast(_impl)->root->eval_number(c, sd.stack); } -#ifndef PUGIXML_NO_STL - PUGI__FN string_t xpath_query::evaluate_string(const xpath_node& n) const - { + #ifndef PUGIXML_NO_STL + PUGI__FN string_t xpath_query::evaluate_string(const xpath_node& n) const { impl::xpath_stack_data sd; return impl::evaluate_string_impl(static_cast(_impl), n, sd).c_str(); } -#endif + #endif - PUGI__FN size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const - { + PUGI__FN size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const { impl::xpath_stack_data sd; impl::xpath_string r = impl::evaluate_string_impl(static_cast(_impl), n, sd); size_t full_size = r.length() + 1; - if (capacity > 0) - { + if (capacity > 0) { size_t size = (full_size < capacity) ? full_size : capacity; assert(size > 0); @@ -10131,14 +10026,14 @@ namespace pugi return full_size; } - PUGI__FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const - { - if (!_impl) return xpath_node_set(); + PUGI__FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const { + if (!_impl) { + return xpath_node_set(); + } impl::xpath_ast_node* root = static_cast(_impl)->root; - if (root->rettype() != xpath_type_node_set) - { + if (root->rettype() != xpath_type_node_set) { #ifdef PUGIXML_NO_EXCEPTIONS return xpath_node_set(); #else @@ -10152,91 +10047,85 @@ namespace pugi impl::xpath_context c(n, 1, 1); impl::xpath_stack_data sd; - #ifdef PUGIXML_NO_EXCEPTIONS - if (setjmp(sd.error_handler)) return xpath_node_set(); - #endif + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) { + return xpath_node_set(); + } + #endif impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack); return xpath_node_set(r.begin(), r.end(), r.type()); } - PUGI__FN const xpath_parse_result& xpath_query::result() const - { + PUGI__FN const xpath_parse_result& xpath_query::result() const { return _result; } - PUGI__FN static void unspecified_bool_xpath_query(xpath_query***) - { + PUGI__FN static void unspecified_bool_xpath_query(xpath_query***) { } - PUGI__FN xpath_query::operator xpath_query::unspecified_bool_type() const - { + PUGI__FN xpath_query::operator xpath_query::unspecified_bool_type() const { return _impl ? unspecified_bool_xpath_query : 0; } - PUGI__FN bool xpath_query::operator!() const - { + PUGI__FN bool xpath_query::operator!() const { return !_impl; } - PUGI__FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const - { + PUGI__FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const { xpath_query q(query, variables); return select_single_node(q); } - PUGI__FN xpath_node xml_node::select_single_node(const xpath_query& query) const - { + PUGI__FN xpath_node xml_node::select_single_node(const xpath_query& query) const { xpath_node_set s = query.evaluate_node_set(*this); return s.empty() ? xpath_node() : s.first(); } - PUGI__FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const - { + PUGI__FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const { xpath_query q(query, variables); return select_nodes(q); } - PUGI__FN xpath_node_set xml_node::select_nodes(const xpath_query& query) const - { + PUGI__FN xpath_node_set xml_node::select_nodes(const xpath_query& query) const { return query.evaluate_node_set(*this); } } -#endif + #endif -#ifdef __BORLANDC__ -# pragma option pop -#endif + #ifdef __BORLANDC__ + #pragma option pop + #endif -// Intel C++ does not properly keep warning state for function templates, -// so popping warning state at the end of translation unit leads to warnings in the middle. -#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) -# pragma warning(pop) -#endif + // Intel C++ does not properly keep warning state for function templates, + // so popping warning state at the end of translation unit leads to warnings in the middle. + #if defined(_MSC_VER) && !defined(__INTEL_COMPILER) + #pragma warning(pop) + #endif -// Undefine all local macros (makes sure we're not leaking macros in header-only mode) -#undef PUGI__NO_INLINE -#undef PUGI__STATIC_ASSERT -#undef PUGI__DMC_VOLATILE -#undef PUGI__MSVC_CRT_VERSION -#undef PUGI__NS_BEGIN -#undef PUGI__NS_END -#undef PUGI__FN -#undef PUGI__FN_NO_INLINE -#undef PUGI__IS_CHARTYPE_IMPL -#undef PUGI__IS_CHARTYPE -#undef PUGI__IS_CHARTYPEX -#undef PUGI__SKIPWS -#undef PUGI__OPTSET -#undef PUGI__PUSHNODE -#undef PUGI__POPNODE -#undef PUGI__SCANFOR -#undef PUGI__SCANWHILE -#undef PUGI__ENDSEG -#undef PUGI__THROW_ERROR -#undef PUGI__CHECK_ERROR + // Undefine all local macros (makes sure we're not leaking macros in header-only mode) + #undef PUGI__NO_INLINE + #undef PUGI__STATIC_ASSERT + #undef PUGI__DMC_VOLATILE + #undef PUGI__MSVC_CRT_VERSION + #undef PUGI__NS_BEGIN + #undef PUGI__NS_END + #undef PUGI__FN + #undef PUGI__FN_NO_INLINE + #undef PUGI__IS_CHARTYPE_IMPL + #undef PUGI__IS_CHARTYPE + #undef PUGI__IS_CHARTYPEX + #undef PUGI__SKIPWS + #undef PUGI__OPTSET + #undef PUGI__PUSHNODE + #undef PUGI__POPNODE + #undef PUGI__SCANFOR + #undef PUGI__SCANWHILE + #undef PUGI__ENDSEG + #undef PUGI__THROW_ERROR + #undef PUGI__CHECK_ERROR #endif diff --git a/source/ext/pugixml.hpp b/source/ext/pugixml.hpp index 331b9aadf..56d31a5bc 100644 --- a/source/ext/pugixml.hpp +++ b/source/ext/pugixml.hpp @@ -12,92 +12,89 @@ */ #ifndef PUGIXML_VERSION -// Define version macro; evaluates to major * 100 + minor so that it's safe to use in less-than comparisons -# define PUGIXML_VERSION 120 + // Define version macro; evaluates to major * 100 + minor so that it's safe to use in less-than comparisons + #define PUGIXML_VERSION 120 #endif // Include user configuration file (this can define various configuration macros) #include "pugiconfig.hpp" #ifndef HEADER_PUGIXML_HPP -#define HEADER_PUGIXML_HPP + #define HEADER_PUGIXML_HPP -// Include stddef.h for size_t and ptrdiff_t -#include + // Include stddef.h for size_t and ptrdiff_t + #include -// Include exception header for XPath -#if !defined(PUGIXML_NO_XPATH) && !defined(PUGIXML_NO_EXCEPTIONS) -# include -#endif + // Include exception header for XPath + #if !defined(PUGIXML_NO_XPATH) && !defined(PUGIXML_NO_EXCEPTIONS) + #include + #endif -// Include STL headers -#ifndef PUGIXML_NO_STL -# include -# include -# include -#endif + // Include STL headers + #ifndef PUGIXML_NO_STL + #include + #include + #include + #endif -// Macro for deprecated features -#ifndef PUGIXML_DEPRECATED -# if defined(__GNUC__) -# define PUGIXML_DEPRECATED __attribute__((deprecated)) -# elif defined(_MSC_VER) && _MSC_VER >= 1300 -# define PUGIXML_DEPRECATED __declspec(deprecated) -# else -# define PUGIXML_DEPRECATED -# endif -#endif + // Macro for deprecated features + #ifndef PUGIXML_DEPRECATED + #if defined(__GNUC__) + #define PUGIXML_DEPRECATED __attribute__((deprecated)) + #elif defined(_MSC_VER) && _MSC_VER >= 1300 + #define PUGIXML_DEPRECATED __declspec(deprecated) + #else + #define PUGIXML_DEPRECATED + #endif + #endif -// If no API is defined, assume default -#ifndef PUGIXML_API -# define PUGIXML_API -#endif + // If no API is defined, assume default + #ifndef PUGIXML_API + #define PUGIXML_API + #endif -// If no API for classes is defined, assume default -#ifndef PUGIXML_CLASS -# define PUGIXML_CLASS PUGIXML_API -#endif + // If no API for classes is defined, assume default + #ifndef PUGIXML_CLASS + #define PUGIXML_CLASS PUGIXML_API + #endif -// If no API for functions is defined, assume default -#ifndef PUGIXML_FUNCTION -# define PUGIXML_FUNCTION PUGIXML_API -#endif + // If no API for functions is defined, assume default + #ifndef PUGIXML_FUNCTION + #define PUGIXML_FUNCTION PUGIXML_API + #endif -// Character interface macros -#ifdef PUGIXML_WCHAR_MODE -# define PUGIXML_TEXT(t) L ## t -# define PUGIXML_CHAR wchar_t -#else -# define PUGIXML_TEXT(t) t -# define PUGIXML_CHAR char -#endif + // Character interface macros + #ifdef PUGIXML_WCHAR_MODE + #define PUGIXML_TEXT(t) L##t + #define PUGIXML_CHAR wchar_t + #else + #define PUGIXML_TEXT(t) t + #define PUGIXML_CHAR char + #endif -namespace pugi -{ +namespace pugi { // Character type used for all internal storage and operations; depends on PUGIXML_WCHAR_MODE typedef PUGIXML_CHAR char_t; -#ifndef PUGIXML_NO_STL + #ifndef PUGIXML_NO_STL // String type used for operations that work with STL string; depends on PUGIXML_WCHAR_MODE - typedef std::basic_string, std::allocator > string_t; -#endif + typedef std::basic_string, std::allocator> string_t; + #endif } // The PugiXML namespace -namespace pugi -{ +namespace pugi { // Tree node types - enum xml_node_type - { - node_null, // Empty (null) node handle - node_document, // A document tree's absolute root - node_element, // Element tag, i.e. '' - node_pcdata, // Plain character data, i.e. 'text' - node_cdata, // Character data, i.e. '' - node_comment, // Comment tag, i.e. '' - node_pi, // Processing instruction, i.e. '' - node_declaration, // Document declaration, i.e. '' - node_doctype // Document type declaration, i.e. '' + enum xml_node_type { + node_null, // Empty (null) node handle + node_document, // A document tree's absolute root + node_element, // Element tag, i.e. '' + node_pcdata, // Plain character data, i.e. 'text' + node_cdata, // Character data, i.e. '' + node_comment, // Comment tag, i.e. '' + node_pi, // Processing instruction, i.e. '' + node_declaration, // Document declaration, i.e. '' + node_doctype // Document type declaration, i.e. '' }; // Parsing options @@ -153,17 +150,16 @@ namespace pugi const unsigned int parse_full = parse_default | parse_pi | parse_comments | parse_declaration | parse_doctype; // These flags determine the encoding of input data for XML document - enum xml_encoding - { - encoding_auto, // Auto-detect input encoding using BOM or < / class xml_object_range - { + template + class xml_object_range { public: typedef It const_iterator; - xml_object_range(It b, It e): _begin(b), _end(e) - { + xml_object_range(It b, It e) : + _begin(b), _end(e) { } - It begin() const { return _begin; } - It end() const { return _end; } + It begin() const { + return _begin; + } + It end() const { + return _end; + } private: It _begin, _end; }; // Writer interface for node printing (see xml_node::print) - class PUGIXML_CLASS xml_writer - { + class PUGIXML_CLASS xml_writer { public: - virtual ~xml_writer() {} + virtual ~xml_writer() { } // Write memory chunk into stream/file/whatever virtual void write(const void* data, size_t size) = 0; }; // xml_writer implementation for FILE* - class PUGIXML_CLASS xml_writer_file: public xml_writer - { + class PUGIXML_CLASS xml_writer_file : public xml_writer { public: // Construct writer from a FILE* object; void* is used to avoid header dependencies on stdio xml_writer_file(void* file); @@ -254,24 +252,22 @@ namespace pugi #ifndef PUGIXML_NO_STL // xml_writer implementation for streams - class PUGIXML_CLASS xml_writer_stream: public xml_writer - { + class PUGIXML_CLASS xml_writer_stream : public xml_writer { public: // Construct writer from an output stream object - xml_writer_stream(std::basic_ostream >& stream); - xml_writer_stream(std::basic_ostream >& stream); + xml_writer_stream(std::basic_ostream>& stream); + xml_writer_stream(std::basic_ostream>& stream); virtual void write(const void* data, size_t size); private: - std::basic_ostream >* narrow_stream; - std::basic_ostream >* wide_stream; + std::basic_ostream>* narrow_stream; + std::basic_ostream>* wide_stream; }; #endif // A light-weight handle for manipulating attributes in DOM tree - class PUGIXML_CLASS xml_attribute - { + class PUGIXML_CLASS xml_attribute { friend class xml_attribute_iterator; friend class xml_node; @@ -349,15 +345,14 @@ namespace pugi xml_attribute_struct* internal_object() const; }; -#ifdef __BORLANDC__ + #ifdef __BORLANDC__ // Borland C++ workaround bool PUGIXML_FUNCTION operator&&(const xml_attribute& lhs, bool rhs); bool PUGIXML_FUNCTION operator||(const xml_attribute& lhs, bool rhs); -#endif + #endif // A light-weight handle for manipulating nodes in DOM tree - class PUGIXML_CLASS xml_node - { + class PUGIXML_CLASS xml_node { friend class xml_attribute_iterator; friend class xml_node_iterator; friend class xml_named_node_iterator; @@ -474,47 +469,63 @@ namespace pugi bool remove_child(const char_t* name); // Find attribute using predicate. Returns first attribute for which predicate returned true. - template xml_attribute find_attribute(Predicate pred) const - { - if (!_root) return xml_attribute(); + template + xml_attribute find_attribute(Predicate pred) const { + if (!_root) { + return xml_attribute(); + } - for (xml_attribute attrib = first_attribute(); attrib; attrib = attrib.next_attribute()) - if (pred(attrib)) + for (xml_attribute attrib = first_attribute(); attrib; attrib = attrib.next_attribute()) { + if (pred(attrib)) { return attrib; + } + } return xml_attribute(); } // Find child node using predicate. Returns first child for which predicate returned true. - template xml_node find_child(Predicate pred) const - { - if (!_root) return xml_node(); + template + xml_node find_child(Predicate pred) const { + if (!_root) { + return xml_node(); + } - for (xml_node node = first_child(); node; node = node.next_sibling()) - if (pred(node)) + for (xml_node node = first_child(); node; node = node.next_sibling()) { + if (pred(node)) { return node; + } + } return xml_node(); } // Find node from subtree using predicate. Returns first node from subtree (depth-first), for which predicate returned true. - template xml_node find_node(Predicate pred) const - { - if (!_root) return xml_node(); + template + xml_node find_node(Predicate pred) const { + if (!_root) { + return xml_node(); + } xml_node cur = first_child(); - while (cur._root && cur._root != _root) - { - if (pred(cur)) return cur; - - if (cur.first_child()) cur = cur.first_child(); - else if (cur.next_sibling()) cur = cur.next_sibling(); - else - { - while (!cur.next_sibling() && cur._root != _root) cur = cur.parent(); + while (cur._root && cur._root != _root) { + if (pred(cur)) { + return cur; + } - if (cur._root != _root) cur = cur.next_sibling(); + if (cur.first_child()) { + cur = cur.first_child(); + } else if (cur.next_sibling()) { + cur = cur.next_sibling(); + } else { + while (!cur.next_sibling() && cur._root != _root) { + cur = cur.parent(); + } + + if (cur._root != _root) { + cur = cur.next_sibling(); + } } } @@ -551,8 +562,8 @@ namespace pugi #ifndef PUGIXML_NO_STL // Print subtree to stream - void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; - void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const; + void print(std::basic_ostream>& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; + void print(std::basic_ostream>& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const; #endif // Child nodes iterators @@ -582,15 +593,14 @@ namespace pugi xml_node_struct* internal_object() const; }; -#ifdef __BORLANDC__ + #ifdef __BORLANDC__ // Borland C++ workaround bool PUGIXML_FUNCTION operator&&(const xml_node& lhs, bool rhs); bool PUGIXML_FUNCTION operator||(const xml_node& lhs, bool rhs); -#endif + #endif // A helper for working with text inside PCDATA nodes - class PUGIXML_CLASS xml_text - { + class PUGIXML_CLASS xml_text { friend class xml_node; xml_node_struct* _root; @@ -650,15 +660,14 @@ namespace pugi xml_node data() const; }; -#ifdef __BORLANDC__ + #ifdef __BORLANDC__ // Borland C++ workaround bool PUGIXML_FUNCTION operator&&(const xml_text& lhs, bool rhs); bool PUGIXML_FUNCTION operator||(const xml_text& lhs, bool rhs); -#endif + #endif // Child node iterator (a bidirectional iterator over a collection of xml_node) - class PUGIXML_CLASS xml_node_iterator - { + class PUGIXML_CLASS xml_node_iterator { friend class xml_node; private: @@ -699,8 +708,7 @@ namespace pugi }; // Attribute iterator (a bidirectional iterator over a collection of xml_attribute) - class PUGIXML_CLASS xml_attribute_iterator - { + class PUGIXML_CLASS xml_attribute_iterator { friend class xml_node; private: @@ -741,8 +749,7 @@ namespace pugi }; // Named node range helper - class xml_named_node_iterator - { + class xml_named_node_iterator { public: // Iterator traits typedef ptrdiff_t difference_type; @@ -776,8 +783,7 @@ namespace pugi }; // Abstract tree walker class (see xml_node::traverse) - class PUGIXML_CLASS xml_tree_walker - { + class PUGIXML_CLASS xml_tree_walker { friend class xml_node; private: @@ -802,31 +808,29 @@ namespace pugi }; // Parsing status, returned as part of xml_parse_result object - enum xml_parse_status - { - status_ok = 0, // No error - - status_file_not_found, // File was not found during load_file() - status_io_error, // Error reading from file/stream - status_out_of_memory, // Could not allocate memory - status_internal_error, // Internal error occurred - - status_unrecognized_tag, // Parser could not determine tag type - - status_bad_pi, // Parsing error occurred while parsing document declaration/processing instruction - status_bad_comment, // Parsing error occurred while parsing comment - status_bad_cdata, // Parsing error occurred while parsing CDATA section - status_bad_doctype, // Parsing error occurred while parsing document type declaration - status_bad_pcdata, // Parsing error occurred while parsing PCDATA section - status_bad_start_element, // Parsing error occurred while parsing start element tag - status_bad_attribute, // Parsing error occurred while parsing element attribute - status_bad_end_element, // Parsing error occurred while parsing end element tag + enum xml_parse_status { + status_ok = 0, // No error + + status_file_not_found, // File was not found during load_file() + status_io_error, // Error reading from file/stream + status_out_of_memory, // Could not allocate memory + status_internal_error, // Internal error occurred + + status_unrecognized_tag, // Parser could not determine tag type + + status_bad_pi, // Parsing error occurred while parsing document declaration/processing instruction + status_bad_comment, // Parsing error occurred while parsing comment + status_bad_cdata, // Parsing error occurred while parsing CDATA section + status_bad_doctype, // Parsing error occurred while parsing document type declaration + status_bad_pcdata, // Parsing error occurred while parsing PCDATA section + status_bad_start_element, // Parsing error occurred while parsing start element tag + status_bad_attribute, // Parsing error occurred while parsing element attribute + status_bad_end_element, // Parsing error occurred while parsing end element tag status_end_element_mismatch // There was a mismatch of start-end tags (closing tag had incorrect name, some tag was not closed or there was an excessive closing tag) }; // Parsing result - struct PUGIXML_CLASS xml_parse_result - { + struct PUGIXML_CLASS xml_parse_result { // Parsing status (see xml_parse_status) xml_parse_status status; @@ -847,8 +851,7 @@ namespace pugi }; // Document class (DOM tree root) - class PUGIXML_CLASS xml_document: public xml_node - { + class PUGIXML_CLASS xml_document : public xml_node { private: char_t* _buffer; @@ -878,8 +881,8 @@ namespace pugi #ifndef PUGIXML_NO_STL // Load document from stream. - xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default); + xml_parse_result load(std::basic_istream>& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + xml_parse_result load(std::basic_istream>& stream, unsigned int options = parse_default); #endif // Load document from zero-terminated string. No encoding conversions are applied. @@ -905,8 +908,8 @@ namespace pugi #ifndef PUGIXML_NO_STL // Save XML document to stream (semantics is slightly different from xml_node::print, see documentation for details). - void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; - void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const; + void save(std::basic_ostream>& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + void save(std::basic_ostream>& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const; #endif // Save XML to file @@ -917,20 +920,18 @@ namespace pugi xml_node document_element() const; }; -#ifndef PUGIXML_NO_XPATH + #ifndef PUGIXML_NO_XPATH // XPath query return type - enum xpath_value_type - { - xpath_type_none, // Unknown type (query failed to compile) - xpath_type_node_set, // Node set (xpath_node_set) - xpath_type_number, // Number - xpath_type_string, // String - xpath_type_boolean // Boolean + enum xpath_value_type { + xpath_type_none, // Unknown type (query failed to compile) + xpath_type_node_set, // Node set (xpath_node_set) + xpath_type_number, // Number + xpath_type_string, // String + xpath_type_boolean // Boolean }; // XPath parsing result - struct PUGIXML_CLASS xpath_parse_result - { + struct PUGIXML_CLASS xpath_parse_result { // Error message (0 if no error) const char* error; @@ -948,8 +949,7 @@ namespace pugi }; // A single XPath variable - class PUGIXML_CLASS xpath_variable - { + class PUGIXML_CLASS xpath_variable { friend class xpath_variable_set; protected: @@ -983,8 +983,7 @@ namespace pugi }; // A set of XPath variables - class PUGIXML_CLASS xpath_variable_set - { + class PUGIXML_CLASS xpath_variable_set { private: xpath_variable* _data[64]; @@ -1014,8 +1013,7 @@ namespace pugi }; // A compiled XPath query object - class PUGIXML_CLASS xpath_query - { + class PUGIXML_CLASS xpath_query { private: void* _impl; xpath_parse_result _result; @@ -1045,11 +1043,11 @@ namespace pugi // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. double evaluate_number(const xpath_node& n) const; - #ifndef PUGIXML_NO_STL + #ifndef PUGIXML_NO_STL // Evaluate expression as string value in the specified context; performs type conversion if necessary. // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. string_t evaluate_string(const xpath_node& n) const; - #endif + #endif // Evaluate expression as string value in the specified context; performs type conversion if necessary. // At most capacity characters are written to the destination buffer, full result size is returned (includes terminating zero). @@ -1072,10 +1070,9 @@ namespace pugi bool operator!() const; }; - #ifndef PUGIXML_NO_EXCEPTIONS + #ifndef PUGIXML_NO_EXCEPTIONS // XPath exception class - class PUGIXML_CLASS xpath_exception: public std::exception - { + class PUGIXML_CLASS xpath_exception : public std::exception { private: xpath_parse_result _result; @@ -1089,11 +1086,10 @@ namespace pugi // Get parse result const xpath_parse_result& result() const; }; - #endif + #endif // XPath node class (either xml_node or xml_attribute) - class PUGIXML_CLASS xpath_node - { + class PUGIXML_CLASS xpath_node { private: xml_node _node; xml_attribute _attribute; @@ -1126,22 +1122,20 @@ namespace pugi bool operator!=(const xpath_node& n) const; }; -#ifdef __BORLANDC__ + #ifdef __BORLANDC__ // Borland C++ workaround bool PUGIXML_FUNCTION operator&&(const xpath_node& lhs, bool rhs); bool PUGIXML_FUNCTION operator||(const xpath_node& lhs, bool rhs); -#endif + #endif // A fixed-size collection of XPath nodes - class PUGIXML_CLASS xpath_node_set - { + class PUGIXML_CLASS xpath_node_set { public: // Collection type - enum type_t - { - type_unsorted, // Not ordered - type_sorted, // Sorted by document order (ascending) - type_sorted_reverse // Sorted by document order (descending) + enum type_t { + type_unsorted, // Not ordered + type_sorted, // Sorted by document order (ascending) + type_sorted_reverse // Sorted by document order (descending) }; // Constant iterator type @@ -1192,17 +1186,17 @@ namespace pugi void _assign(const_iterator begin, const_iterator end); }; -#endif + #endif -#ifndef PUGIXML_NO_STL + #ifndef PUGIXML_NO_STL // Convert wide string to UTF8 - std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const wchar_t* str); - std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const std::basic_string, std::allocator >& str); + std::basic_string, std::allocator> PUGIXML_FUNCTION as_utf8(const wchar_t* str); + std::basic_string, std::allocator> PUGIXML_FUNCTION as_utf8(const std::basic_string, std::allocator>& str); // Convert UTF8 to wide string - std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const char* str); - std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const std::basic_string, std::allocator >& str); -#endif + std::basic_string, std::allocator> PUGIXML_FUNCTION as_wide(const char* str); + std::basic_string, std::allocator> PUGIXML_FUNCTION as_wide(const std::basic_string, std::allocator>& str); + #endif // Memory allocation function interface; returns pointer to allocated memory or NULL on failure typedef void* (*allocation_function)(size_t size); @@ -1218,25 +1212,23 @@ namespace pugi deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function(); } -#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) -namespace std -{ + #if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) +namespace std { // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_node_iterator&); std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_attribute_iterator&); std::forward_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_named_node_iterator&); } -#endif + #endif -#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) -namespace std -{ + #if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) +namespace std { // Workarounds for (non-standard) iterator category detection std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_node_iterator&); std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_attribute_iterator&); std::forward_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_named_node_iterator&); } -#endif + #endif #endif diff --git a/source/fast_noise_lite.h b/source/fast_noise_lite.h index c67f2e5ce..2b66999aa 100644 --- a/source/fast_noise_lite.h +++ b/source/fast_noise_lite.h @@ -52,2535 +52,3027 @@ #include -class FastNoiseLite -{ +class FastNoiseLite { public: - enum NoiseType - { - NoiseType_OpenSimplex2, - NoiseType_OpenSimplex2S, - NoiseType_Cellular, - NoiseType_Perlin, - NoiseType_ValueCubic, - NoiseType_Value - }; - - enum RotationType3D - { - RotationType3D_None, - RotationType3D_ImproveXYPlanes, - RotationType3D_ImproveXZPlanes - }; - - enum FractalType - { - FractalType_None, - FractalType_FBm, - FractalType_Ridged, - FractalType_PingPong, - FractalType_DomainWarpProgressive, - FractalType_DomainWarpIndependent - }; - - enum CellularDistanceFunction - { - CellularDistanceFunction_Euclidean, - CellularDistanceFunction_EuclideanSq, - CellularDistanceFunction_Manhattan, - CellularDistanceFunction_Hybrid - }; - - enum CellularReturnType - { - CellularReturnType_CellValue, - CellularReturnType_Distance, - CellularReturnType_Distance2, - CellularReturnType_Distance2Add, - CellularReturnType_Distance2Sub, - CellularReturnType_Distance2Mul, - CellularReturnType_Distance2Div - }; - - enum DomainWarpType - { - DomainWarpType_OpenSimplex2, - DomainWarpType_OpenSimplex2Reduced, - DomainWarpType_BasicGrid - }; - - /// - /// Create new FastNoise object with optional seed - /// - FastNoiseLite(int seed = 1337) - { - mSeed = seed; - mFrequency = 0.01f; - mNoiseType = NoiseType_OpenSimplex2; - mRotationType3D = RotationType3D_None; - mTransformType3D = TransformType3D_DefaultOpenSimplex2; - - mFractalType = FractalType_None; - mOctaves = 3; - mLacunarity = 2.0f; - mGain = 0.5f; - mWeightedStrength = 0.0f; - mPingPongStrength = 2.0f; - - mFractalBounding = 1 / 1.75f; - - mCellularDistanceFunction = CellularDistanceFunction_EuclideanSq; - mCellularReturnType = CellularReturnType_Distance; - mCellularJitterModifier = 1.0f; - - mDomainWarpType = DomainWarpType_OpenSimplex2; - mWarpTransformType3D = TransformType3D_DefaultOpenSimplex2; - mDomainWarpAmp = 1.0f; - } - - /// - /// Sets seed used for all noise types - /// - /// - /// Default: 1337 - /// - void SetSeed(int seed) { mSeed = seed; } - - /// - /// Sets frequency for all noise types - /// - /// - /// Default: 0.01 - /// - void SetFrequency(float frequency) { mFrequency = frequency; } - - /// - /// Sets noise algorithm used for GetNoise(...) - /// - /// - /// Default: OpenSimplex2 - /// - void SetNoiseType(NoiseType noiseType) - { - mNoiseType = noiseType; - UpdateTransformType3D(); - } - - /// - /// Sets domain rotation type for 3D Noise and 3D DomainWarp. - /// Can aid in reducing directional artifacts when sampling a 2D plane in 3D - /// - /// - /// Default: None - /// - void SetRotationType3D(RotationType3D rotationType3D) - { - mRotationType3D = rotationType3D; - UpdateTransformType3D(); - UpdateWarpTransformType3D(); - } - - /// - /// Sets method for combining octaves in all fractal noise types - /// - /// - /// Default: None - /// Note: FractalType_DomainWarp... only affects DomainWarp(...) - /// - void SetFractalType(FractalType fractalType) { mFractalType = fractalType; } - - /// - /// Sets octave count for all fractal noise types - /// - /// - /// Default: 3 - /// - void SetFractalOctaves(int octaves) - { - mOctaves = octaves; - CalculateFractalBounding(); - } - - /// - /// Sets octave lacunarity for all fractal noise types - /// - /// - /// Default: 2.0 - /// - void SetFractalLacunarity(float lacunarity) { mLacunarity = lacunarity; } - - /// - /// Sets octave gain for all fractal noise types - /// - /// - /// Default: 0.5 - /// - void SetFractalGain(float gain) - { - mGain = gain; - CalculateFractalBounding(); - } - - /// - /// Sets octave weighting for all none DomainWarp fratal types - /// - /// - /// Default: 0.0 - /// Note: Keep between 0...1 to maintain -1...1 output bounding - /// - void SetFractalWeightedStrength(float weightedStrength) { mWeightedStrength = weightedStrength; } - - /// - /// Sets strength of the fractal ping pong effect - /// - /// - /// Default: 2.0 - /// - void SetFractalPingPongStrength(float pingPongStrength) { mPingPongStrength = pingPongStrength; } - - - /// - /// Sets distance function used in cellular noise calculations - /// - /// - /// Default: Distance - /// - void SetCellularDistanceFunction(CellularDistanceFunction cellularDistanceFunction) { mCellularDistanceFunction = cellularDistanceFunction; } - - /// - /// Sets return type from cellular noise calculations - /// - /// - /// Default: EuclideanSq - /// - void SetCellularReturnType(CellularReturnType cellularReturnType) { mCellularReturnType = cellularReturnType; } - - /// - /// Sets the maximum distance a cellular point can move from it's grid position - /// - /// - /// Default: 1.0 - /// Note: Setting this higher than 1 will cause artifacts - /// - void SetCellularJitter(float cellularJitter) { mCellularJitterModifier = cellularJitter; } - - - /// - /// Sets the warp algorithm when using DomainWarp(...) - /// - /// - /// Default: OpenSimplex2 - /// - void SetDomainWarpType(DomainWarpType domainWarpType) - { - mDomainWarpType = domainWarpType; - UpdateWarpTransformType3D(); - } - - - /// - /// Sets the maximum warp distance from original position when using DomainWarp(...) - /// - /// - /// Default: 1.0 - /// - void SetDomainWarpAmp(float domainWarpAmp) { mDomainWarpAmp = domainWarpAmp; } - - - /// - /// 2D noise at given position using current settings - /// - /// - /// Noise output bounded between -1...1 - /// - template - float GetNoise(FNfloat x, FNfloat y) const - { - Arguments_must_be_floating_point_values(); - - TransformNoiseCoordinate(x, y); - - switch (mFractalType) - { - default: - return GenNoiseSingle(mSeed, x, y); - case FractalType_FBm: - return GenFractalFBm(x, y); - case FractalType_Ridged: - return GenFractalRidged(x, y); - case FractalType_PingPong: - return GenFractalPingPong(x, y); - } - } - - /// - /// 3D noise at given position using current settings - /// - /// - /// Noise output bounded between -1...1 - /// - template - float GetNoise(FNfloat x, FNfloat y, FNfloat z) const - { - Arguments_must_be_floating_point_values(); - - TransformNoiseCoordinate(x, y, z); - - switch (mFractalType) - { - default: - return GenNoiseSingle(mSeed, x, y, z); - case FractalType_FBm: - return GenFractalFBm(x, y, z); - case FractalType_Ridged: - return GenFractalRidged(x, y, z); - case FractalType_PingPong: - return GenFractalPingPong(x, y, z); - } - } - - - /// - /// 2D warps the input position using current domain warp settings - /// - /// - /// Example usage with GetNoise - /// DomainWarp(x, y) - /// noise = GetNoise(x, y) - /// - template - void DomainWarp(FNfloat& x, FNfloat& y) const - { - Arguments_must_be_floating_point_values(); - - switch (mFractalType) - { - default: - DomainWarpSingle(x, y); - break; - case FractalType_DomainWarpProgressive: - DomainWarpFractalProgressive(x, y); - break; - case FractalType_DomainWarpIndependent: - DomainWarpFractalIndependent(x, y); - break; - } - } - - /// - /// 3D warps the input position using current domain warp settings - /// - /// - /// Example usage with GetNoise - /// DomainWarp(x, y, z) - /// noise = GetNoise(x, y, z) - /// - template - void DomainWarp(FNfloat& x, FNfloat& y, FNfloat& z) const - { - Arguments_must_be_floating_point_values(); - - switch (mFractalType) - { - default: - DomainWarpSingle(x, y, z); - break; - case FractalType_DomainWarpProgressive: - DomainWarpFractalProgressive(x, y, z); - break; - case FractalType_DomainWarpIndependent: - DomainWarpFractalIndependent(x, y, z); - break; - } - } + enum NoiseType { + NoiseType_OpenSimplex2, + NoiseType_OpenSimplex2S, + NoiseType_Cellular, + NoiseType_Perlin, + NoiseType_ValueCubic, + NoiseType_Value + }; + + enum RotationType3D { + RotationType3D_None, + RotationType3D_ImproveXYPlanes, + RotationType3D_ImproveXZPlanes + }; + + enum FractalType { + FractalType_None, + FractalType_FBm, + FractalType_Ridged, + FractalType_PingPong, + FractalType_DomainWarpProgressive, + FractalType_DomainWarpIndependent + }; + + enum CellularDistanceFunction { + CellularDistanceFunction_Euclidean, + CellularDistanceFunction_EuclideanSq, + CellularDistanceFunction_Manhattan, + CellularDistanceFunction_Hybrid + }; + + enum CellularReturnType { + CellularReturnType_CellValue, + CellularReturnType_Distance, + CellularReturnType_Distance2, + CellularReturnType_Distance2Add, + CellularReturnType_Distance2Sub, + CellularReturnType_Distance2Mul, + CellularReturnType_Distance2Div + }; + + enum DomainWarpType { + DomainWarpType_OpenSimplex2, + DomainWarpType_OpenSimplex2Reduced, + DomainWarpType_BasicGrid + }; + + /// + /// Create new FastNoise object with optional seed + /// + FastNoiseLite(int seed = 1337) { + mSeed = seed; + mFrequency = 0.01f; + mNoiseType = NoiseType_OpenSimplex2; + mRotationType3D = RotationType3D_None; + mTransformType3D = TransformType3D_DefaultOpenSimplex2; + + mFractalType = FractalType_None; + mOctaves = 3; + mLacunarity = 2.0f; + mGain = 0.5f; + mWeightedStrength = 0.0f; + mPingPongStrength = 2.0f; + + mFractalBounding = 1 / 1.75f; + + mCellularDistanceFunction = CellularDistanceFunction_EuclideanSq; + mCellularReturnType = CellularReturnType_Distance; + mCellularJitterModifier = 1.0f; + + mDomainWarpType = DomainWarpType_OpenSimplex2; + mWarpTransformType3D = TransformType3D_DefaultOpenSimplex2; + mDomainWarpAmp = 1.0f; + } + + /// + /// Sets seed used for all noise types + /// + /// + /// Default: 1337 + /// + void SetSeed(int seed) { + mSeed = seed; + } + + /// + /// Sets frequency for all noise types + /// + /// + /// Default: 0.01 + /// + void SetFrequency(float frequency) { + mFrequency = frequency; + } + + /// + /// Sets noise algorithm used for GetNoise(...) + /// + /// + /// Default: OpenSimplex2 + /// + void SetNoiseType(NoiseType noiseType) { + mNoiseType = noiseType; + UpdateTransformType3D(); + } + + /// + /// Sets domain rotation type for 3D Noise and 3D DomainWarp. + /// Can aid in reducing directional artifacts when sampling a 2D plane in 3D + /// + /// + /// Default: None + /// + void SetRotationType3D(RotationType3D rotationType3D) { + mRotationType3D = rotationType3D; + UpdateTransformType3D(); + UpdateWarpTransformType3D(); + } + + /// + /// Sets method for combining octaves in all fractal noise types + /// + /// + /// Default: None + /// Note: FractalType_DomainWarp... only affects DomainWarp(...) + /// + void SetFractalType(FractalType fractalType) { + mFractalType = fractalType; + } + + /// + /// Sets octave count for all fractal noise types + /// + /// + /// Default: 3 + /// + void SetFractalOctaves(int octaves) { + mOctaves = octaves; + CalculateFractalBounding(); + } + + /// + /// Sets octave lacunarity for all fractal noise types + /// + /// + /// Default: 2.0 + /// + void SetFractalLacunarity(float lacunarity) { + mLacunarity = lacunarity; + } + + /// + /// Sets octave gain for all fractal noise types + /// + /// + /// Default: 0.5 + /// + void SetFractalGain(float gain) { + mGain = gain; + CalculateFractalBounding(); + } + + /// + /// Sets octave weighting for all none DomainWarp fratal types + /// + /// + /// Default: 0.0 + /// Note: Keep between 0...1 to maintain -1...1 output bounding + /// + void SetFractalWeightedStrength(float weightedStrength) { + mWeightedStrength = weightedStrength; + } + + /// + /// Sets strength of the fractal ping pong effect + /// + /// + /// Default: 2.0 + /// + void SetFractalPingPongStrength(float pingPongStrength) { + mPingPongStrength = pingPongStrength; + } + + /// + /// Sets distance function used in cellular noise calculations + /// + /// + /// Default: Distance + /// + void SetCellularDistanceFunction(CellularDistanceFunction cellularDistanceFunction) { + mCellularDistanceFunction = cellularDistanceFunction; + } + + /// + /// Sets return type from cellular noise calculations + /// + /// + /// Default: EuclideanSq + /// + void SetCellularReturnType(CellularReturnType cellularReturnType) { + mCellularReturnType = cellularReturnType; + } + + /// + /// Sets the maximum distance a cellular point can move from it's grid position + /// + /// + /// Default: 1.0 + /// Note: Setting this higher than 1 will cause artifacts + /// + void SetCellularJitter(float cellularJitter) { + mCellularJitterModifier = cellularJitter; + } + + /// + /// Sets the warp algorithm when using DomainWarp(...) + /// + /// + /// Default: OpenSimplex2 + /// + void SetDomainWarpType(DomainWarpType domainWarpType) { + mDomainWarpType = domainWarpType; + UpdateWarpTransformType3D(); + } + + /// + /// Sets the maximum warp distance from original position when using DomainWarp(...) + /// + /// + /// Default: 1.0 + /// + void SetDomainWarpAmp(float domainWarpAmp) { + mDomainWarpAmp = domainWarpAmp; + } + + /// + /// 2D noise at given position using current settings + /// + /// + /// Noise output bounded between -1...1 + /// + template + float GetNoise(FNfloat x, FNfloat y) const { + Arguments_must_be_floating_point_values(); + + TransformNoiseCoordinate(x, y); + + switch (mFractalType) { + default: + return GenNoiseSingle(mSeed, x, y); + case FractalType_FBm: + return GenFractalFBm(x, y); + case FractalType_Ridged: + return GenFractalRidged(x, y); + case FractalType_PingPong: + return GenFractalPingPong(x, y); + } + } + + /// + /// 3D noise at given position using current settings + /// + /// + /// Noise output bounded between -1...1 + /// + template + float GetNoise(FNfloat x, FNfloat y, FNfloat z) const { + Arguments_must_be_floating_point_values(); + + TransformNoiseCoordinate(x, y, z); + + switch (mFractalType) { + default: + return GenNoiseSingle(mSeed, x, y, z); + case FractalType_FBm: + return GenFractalFBm(x, y, z); + case FractalType_Ridged: + return GenFractalRidged(x, y, z); + case FractalType_PingPong: + return GenFractalPingPong(x, y, z); + } + } + + /// + /// 2D warps the input position using current domain warp settings + /// + /// + /// Example usage with GetNoise + /// DomainWarp(x, y) + /// noise = GetNoise(x, y) + /// + template + void DomainWarp(FNfloat& x, FNfloat& y) const { + Arguments_must_be_floating_point_values(); + + switch (mFractalType) { + default: + DomainWarpSingle(x, y); + break; + case FractalType_DomainWarpProgressive: + DomainWarpFractalProgressive(x, y); + break; + case FractalType_DomainWarpIndependent: + DomainWarpFractalIndependent(x, y); + break; + } + } + + /// + /// 3D warps the input position using current domain warp settings + /// + /// + /// Example usage with GetNoise + /// DomainWarp(x, y, z) + /// noise = GetNoise(x, y, z) + /// + template + void DomainWarp(FNfloat& x, FNfloat& y, FNfloat& z) const { + Arguments_must_be_floating_point_values(); + + switch (mFractalType) { + default: + DomainWarpSingle(x, y, z); + break; + case FractalType_DomainWarpProgressive: + DomainWarpFractalProgressive(x, y, z); + break; + case FractalType_DomainWarpIndependent: + DomainWarpFractalIndependent(x, y, z); + break; + } + } private: - template - struct Arguments_must_be_floating_point_values; - - enum TransformType3D - { - TransformType3D_None, - TransformType3D_ImproveXYPlanes, - TransformType3D_ImproveXZPlanes, - TransformType3D_DefaultOpenSimplex2 - }; - - int mSeed; - float mFrequency; - NoiseType mNoiseType; - RotationType3D mRotationType3D; - TransformType3D mTransformType3D; - - FractalType mFractalType; - int mOctaves; - float mLacunarity; - float mGain; - float mWeightedStrength; - float mPingPongStrength; - - float mFractalBounding; - - CellularDistanceFunction mCellularDistanceFunction; - CellularReturnType mCellularReturnType; - float mCellularJitterModifier; - - DomainWarpType mDomainWarpType; - TransformType3D mWarpTransformType3D; - float mDomainWarpAmp; - - - template - struct Lookup - { - static const T Gradients2D[]; - static const T Gradients3D[]; - static const T RandVecs2D[]; - static const T RandVecs3D[]; - }; - - static float FastMin(float a, float b) { return a < b ? a : b; } - - static float FastMax(float a, float b) { return a > b ? a : b; } - - static float FastAbs(float f) { return f < 0 ? -f : f; } - - static float FastSqrt(float f) { return sqrtf(f); } - - template - static int FastFloor(FNfloat f) { return f >= 0 ? (int)f : (int)f - 1; } - - template - static int FastRound(FNfloat f) { return f >= 0 ? (int)(f + 0.5f) : (int)(f - 0.5f); } - - static float Lerp(float a, float b, float t) { return a + t * (b - a); } - - static float InterpHermite(float t) { return t * t * (3 - 2 * t); } - - static float InterpQuintic(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } - - static float CubicLerp(float a, float b, float c, float d, float t) - { - float p = (d - c) - (a - b); - return t * t * t * p + t * t * ((a - b) - p) + t * (c - a) + b; - } - - static float PingPong(float t) - { - t -= (int)(t * 0.5f) * 2; - return t < 1 ? t : 2 - t; - } - - void CalculateFractalBounding() - { - float gain = FastAbs(mGain); - float amp = gain; - float ampFractal = 1.0f; - for (int i = 1; i < mOctaves; i++) - { - ampFractal += amp; - amp *= gain; - } - mFractalBounding = 1 / ampFractal; - } - - // Hashing - static const int PrimeX = 501125321; - static const int PrimeY = 1136930381; - static const int PrimeZ = 1720413743; - - static int Hash(int seed, int xPrimed, int yPrimed) - { - int hash = seed ^ xPrimed ^ yPrimed; - - hash *= 0x27d4eb2d; - return hash; - } - - - static int Hash(int seed, int xPrimed, int yPrimed, int zPrimed) - { - int hash = seed ^ xPrimed ^ yPrimed ^ zPrimed; - - hash *= 0x27d4eb2d; - return hash; - } - - - static float ValCoord(int seed, int xPrimed, int yPrimed) - { - int hash = Hash(seed, xPrimed, yPrimed); - - hash *= hash; - hash ^= hash << 19; - return hash * (1 / 2147483648.0f); - } - - - static float ValCoord(int seed, int xPrimed, int yPrimed, int zPrimed) - { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); - - hash *= hash; - hash ^= hash << 19; - return hash * (1 / 2147483648.0f); - } - - - float GradCoord(int seed, int xPrimed, int yPrimed, float xd, float yd) const - { - int hash = Hash(seed, xPrimed, yPrimed); - hash ^= hash >> 15; - hash &= 127 << 1; - - float xg = Lookup::Gradients2D[hash]; - float yg = Lookup::Gradients2D[hash | 1]; - - return xd * xg + yd * yg; - } - - - float GradCoord(int seed, int xPrimed, int yPrimed, int zPrimed, float xd, float yd, float zd) const - { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); - hash ^= hash >> 15; - hash &= 63 << 2; - - float xg = Lookup::Gradients3D[hash]; - float yg = Lookup::Gradients3D[hash | 1]; - float zg = Lookup::Gradients3D[hash | 2]; - - return xd * xg + yd * yg + zd * zg; - } - - - void GradCoordOut(int seed, int xPrimed, int yPrimed, float& xo, float& yo) const - { - int hash = Hash(seed, xPrimed, yPrimed) & (255 << 1); - - xo = Lookup::RandVecs2D[hash]; - yo = Lookup::RandVecs2D[hash | 1]; - } - - - void GradCoordOut(int seed, int xPrimed, int yPrimed, int zPrimed, float& xo, float& yo, float& zo) const - { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed) & (255 << 2); - - xo = Lookup::RandVecs3D[hash]; - yo = Lookup::RandVecs3D[hash | 1]; - zo = Lookup::RandVecs3D[hash | 2]; - } - - - void GradCoordDual(int seed, int xPrimed, int yPrimed, float xd, float yd, float& xo, float& yo) const - { - int hash = Hash(seed, xPrimed, yPrimed); - int index1 = hash & (127 << 1); - int index2 = (hash >> 7) & (255 << 1); - - float xg = Lookup::Gradients2D[index1]; - float yg = Lookup::Gradients2D[index1 | 1]; - float value = xd * xg + yd * yg; - - float xgo = Lookup::RandVecs2D[index2]; - float ygo = Lookup::RandVecs2D[index2 | 1]; - - xo = value * xgo; - yo = value * ygo; - } - - - void GradCoordDual(int seed, int xPrimed, int yPrimed, int zPrimed, float xd, float yd, float zd, float& xo, float& yo, float& zo) const - { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); - int index1 = hash & (63 << 2); - int index2 = (hash >> 6) & (255 << 2); - - float xg = Lookup::Gradients3D[index1]; - float yg = Lookup::Gradients3D[index1 | 1]; - float zg = Lookup::Gradients3D[index1 | 2]; - float value = xd * xg + yd * yg + zd * zg; - - float xgo = Lookup::RandVecs3D[index2]; - float ygo = Lookup::RandVecs3D[index2 | 1]; - float zgo = Lookup::RandVecs3D[index2 | 2]; - - xo = value * xgo; - yo = value * ygo; - zo = value * zgo; - } - - - // Generic noise gen - - template - float GenNoiseSingle(int seed, FNfloat x, FNfloat y) const - { - switch (mNoiseType) - { - case NoiseType_OpenSimplex2: - return SingleSimplex(seed, x, y); - case NoiseType_OpenSimplex2S: - return SingleOpenSimplex2S(seed, x, y); - case NoiseType_Cellular: - return SingleCellular(seed, x, y); - case NoiseType_Perlin: - return SinglePerlin(seed, x, y); - case NoiseType_ValueCubic: - return SingleValueCubic(seed, x, y); - case NoiseType_Value: - return SingleValue(seed, x, y); - default: - return 0; - } - } - - template - float GenNoiseSingle(int seed, FNfloat x, FNfloat y, FNfloat z) const - { - switch (mNoiseType) - { - case NoiseType_OpenSimplex2: - return SingleOpenSimplex2(seed, x, y, z); - case NoiseType_OpenSimplex2S: - return SingleOpenSimplex2S(seed, x, y, z); - case NoiseType_Cellular: - return SingleCellular(seed, x, y, z); - case NoiseType_Perlin: - return SinglePerlin(seed, x, y, z); - case NoiseType_ValueCubic: - return SingleValueCubic(seed, x, y, z); - case NoiseType_Value: - return SingleValue(seed, x, y, z); - default: - return 0; - } - } - - - // Noise Coordinate Transforms (frequency, and possible skew or rotation) - - template - void TransformNoiseCoordinate(FNfloat& x, FNfloat& y) const - { - x *= mFrequency; - y *= mFrequency; - - switch (mNoiseType) - { - case NoiseType_OpenSimplex2: - case NoiseType_OpenSimplex2S: - { - const FNfloat SQRT3 = (FNfloat)1.7320508075688772935274463415059; - const FNfloat F2 = 0.5f * (SQRT3 - 1); - FNfloat t = (x + y) * F2; - x += t; - y += t; - } - break; - default: - break; - } - } - - template - void TransformNoiseCoordinate(FNfloat& x, FNfloat& y, FNfloat& z) const - { - x *= mFrequency; - y *= mFrequency; - z *= mFrequency; - - switch (mTransformType3D) - { - case TransformType3D_ImproveXYPlanes: - { - FNfloat xy = x + y; - FNfloat s2 = xy * -(FNfloat)0.211324865405187; - z *= (FNfloat)0.577350269189626; - x += s2 - z; - y = y + s2 - z; - z += xy * (FNfloat)0.577350269189626; - } - break; - case TransformType3D_ImproveXZPlanes: - { - FNfloat xz = x + z; - FNfloat s2 = xz * -(FNfloat)0.211324865405187; - y *= (FNfloat)0.577350269189626; - x += s2 - y; - z += s2 - y; - y += xz * (FNfloat)0.577350269189626; - } - break; - case TransformType3D_DefaultOpenSimplex2: - { - const FNfloat R3 = (FNfloat)(2.0 / 3.0); - FNfloat r = (x + y + z) * R3; // Rotation, not skew - x = r - x; - y = r - y; - z = r - z; - } - break; - default: - break; - } - } - - void UpdateTransformType3D() - { - switch (mRotationType3D) - { - case RotationType3D_ImproveXYPlanes: - mTransformType3D = TransformType3D_ImproveXYPlanes; - break; - case RotationType3D_ImproveXZPlanes: - mTransformType3D = TransformType3D_ImproveXZPlanes; - break; - default: - switch (mNoiseType) - { - case NoiseType_OpenSimplex2: - case NoiseType_OpenSimplex2S: - mTransformType3D = TransformType3D_DefaultOpenSimplex2; - break; - default: - mTransformType3D = TransformType3D_None; - break; - } - break; - } - } - - - // Domain Warp Coordinate Transforms - - template - void TransformDomainWarpCoordinate(FNfloat& x, FNfloat& y) const - { - switch (mDomainWarpType) - { - case DomainWarpType_OpenSimplex2: - case DomainWarpType_OpenSimplex2Reduced: - { - const FNfloat SQRT3 = (FNfloat)1.7320508075688772935274463415059; - const FNfloat F2 = 0.5f * (SQRT3 - 1); - FNfloat t = (x + y) * F2; - x += t; - y += t; - } - break; - default: - break; - } - } - - template - void TransformDomainWarpCoordinate(FNfloat& x, FNfloat& y, FNfloat& z) const - { - switch (mWarpTransformType3D) - { - case TransformType3D_ImproveXYPlanes: - { - FNfloat xy = x + y; - FNfloat s2 = xy * -(FNfloat)0.211324865405187; - z *= (FNfloat)0.577350269189626; - x += s2 - z; - y = y + s2 - z; - z += xy * (FNfloat)0.577350269189626; - } - break; - case TransformType3D_ImproveXZPlanes: - { - FNfloat xz = x + z; - FNfloat s2 = xz * -(FNfloat)0.211324865405187; - y *= (FNfloat)0.577350269189626; - x += s2 - y; - z += s2 - y; - y += xz * (FNfloat)0.577350269189626; - } - break; - case TransformType3D_DefaultOpenSimplex2: - { - const FNfloat R3 = (FNfloat)(2.0 / 3.0); - FNfloat r = (x + y + z) * R3; // Rotation, not skew - x = r - x; - y = r - y; - z = r - z; - } - break; - default: - break; - } - } - - void UpdateWarpTransformType3D() - { - switch (mRotationType3D) - { - case RotationType3D_ImproveXYPlanes: - mWarpTransformType3D = TransformType3D_ImproveXYPlanes; - break; - case RotationType3D_ImproveXZPlanes: - mWarpTransformType3D = TransformType3D_ImproveXZPlanes; - break; - default: - switch (mDomainWarpType) - { - case DomainWarpType_OpenSimplex2: - case DomainWarpType_OpenSimplex2Reduced: - mWarpTransformType3D = TransformType3D_DefaultOpenSimplex2; - break; - default: - mWarpTransformType3D = TransformType3D_None; - break; - } - break; - } - } - - - // Fractal FBm - - template - float GenFractalFBm(FNfloat x, FNfloat y) const - { - int seed = mSeed; - float sum = 0; - float amp = mFractalBounding; - - for (int i = 0; i < mOctaves; i++) - { - float noise = GenNoiseSingle(seed++, x, y); - sum += noise * amp; - amp *= Lerp(1.0f, FastMin(noise + 1, 2) * 0.5f, mWeightedStrength); - - x *= mLacunarity; - y *= mLacunarity; - amp *= mGain; - } - - return sum; - } - - template - float GenFractalFBm(FNfloat x, FNfloat y, FNfloat z) const - { - int seed = mSeed; - float sum = 0; - float amp = mFractalBounding; - - for (int i = 0; i < mOctaves; i++) - { - float noise = GenNoiseSingle(seed++, x, y, z); - sum += noise * amp; - amp *= Lerp(1.0f, (noise + 1) * 0.5f, mWeightedStrength); - - x *= mLacunarity; - y *= mLacunarity; - z *= mLacunarity; - amp *= mGain; - } - - return sum; - } - - - // Fractal Ridged - - template - float GenFractalRidged(FNfloat x, FNfloat y) const - { - int seed = mSeed; - float sum = 0; - float amp = mFractalBounding; - - for (int i = 0; i < mOctaves; i++) - { - float noise = FastAbs(GenNoiseSingle(seed++, x, y)); - sum += (noise * -2 + 1) * amp; - amp *= Lerp(1.0f, 1 - noise, mWeightedStrength); - - x *= mLacunarity; - y *= mLacunarity; - amp *= mGain; - } - - return sum; - } - - template - float GenFractalRidged(FNfloat x, FNfloat y, FNfloat z) const - { - int seed = mSeed; - float sum = 0; - float amp = mFractalBounding; - - for (int i = 0; i < mOctaves; i++) - { - float noise = FastAbs(GenNoiseSingle(seed++, x, y, z)); - sum += (noise * -2 + 1) * amp; - amp *= Lerp(1.0f, 1 - noise, mWeightedStrength); - - x *= mLacunarity; - y *= mLacunarity; - z *= mLacunarity; - amp *= mGain; - } - - return sum; - } - - - // Fractal PingPong - - template - float GenFractalPingPong(FNfloat x, FNfloat y) const - { - int seed = mSeed; - float sum = 0; - float amp = mFractalBounding; - - for (int i = 0; i < mOctaves; i++) - { - float noise = PingPong((GenNoiseSingle(seed++, x, y) + 1) * mPingPongStrength); - sum += (noise - 0.5f) * 2 * amp; - amp *= Lerp(1.0f, noise, mWeightedStrength); - - x *= mLacunarity; - y *= mLacunarity; - amp *= mGain; - } - - return sum; - } - - template - float GenFractalPingPong(FNfloat x, FNfloat y, FNfloat z) const - { - int seed = mSeed; - float sum = 0; - float amp = mFractalBounding; - - for (int i = 0; i < mOctaves; i++) - { - float noise = PingPong((GenNoiseSingle(seed++, x, y, z) + 1) * mPingPongStrength); - sum += (noise - 0.5f) * 2 * amp; - amp *= Lerp(1.0f, noise, mWeightedStrength); - - x *= mLacunarity; - y *= mLacunarity; - z *= mLacunarity; - amp *= mGain; - } - - return sum; - } - - - // Simplex/OpenSimplex2 Noise - - template - float SingleSimplex(int seed, FNfloat x, FNfloat y) const - { - // 2D OpenSimplex2 case uses the same algorithm as ordinary Simplex. - - const float SQRT3 = 1.7320508075688772935274463415059f; - const float G2 = (3 - SQRT3) / 6; - - /* - * --- Skew moved to TransformNoiseCoordinate method --- - * const FNfloat F2 = 0.5f * (SQRT3 - 1); - * FNfloat s = (x + y) * F2; - * x += s; y += s; - */ - - int i = FastFloor(x); - int j = FastFloor(y); - float xi = (float)(x - i); - float yi = (float)(y - j); - - float t = (xi + yi) * G2; - float x0 = (float)(xi - t); - float y0 = (float)(yi - t); - - i *= PrimeX; - j *= PrimeY; - - float n0, n1, n2; - - float a = 0.5f - x0 * x0 - y0 * y0; - if (a <= 0) n0 = 0; - else - { - n0 = (a * a) * (a * a) * GradCoord(seed, i, j, x0, y0); - } - - float c = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a); - if (c <= 0) n2 = 0; - else - { - float x2 = x0 + (2 * (float)G2 - 1); - float y2 = y0 + (2 * (float)G2 - 1); - n2 = (c * c) * (c * c) * GradCoord(seed, i + PrimeX, j + PrimeY, x2, y2); - } - - if (y0 > x0) - { - float x1 = x0 + (float)G2; - float y1 = y0 + ((float)G2 - 1); - float b = 0.5f - x1 * x1 - y1 * y1; - if (b <= 0) n1 = 0; - else - { - n1 = (b * b) * (b * b) * GradCoord(seed, i, j + PrimeY, x1, y1); - } - } - else - { - float x1 = x0 + ((float)G2 - 1); - float y1 = y0 + (float)G2; - float b = 0.5f - x1 * x1 - y1 * y1; - if (b <= 0) n1 = 0; - else - { - n1 = (b * b) * (b * b) * GradCoord(seed, i + PrimeX, j, x1, y1); - } - } - - return (n0 + n1 + n2) * 99.83685446303647f; - } - - template - float SingleOpenSimplex2(int seed, FNfloat x, FNfloat y, FNfloat z) const - { - // 3D OpenSimplex2 case uses two offset rotated cube grids. - - /* - * --- Rotation moved to TransformNoiseCoordinate method --- - * const FNfloat R3 = (FNfloat)(2.0 / 3.0); - * FNfloat r = (x + y + z) * R3; // Rotation, not skew - * x = r - x; y = r - y; z = r - z; - */ - - int i = FastRound(x); - int j = FastRound(y); - int k = FastRound(z); - float x0 = (float)(x - i); - float y0 = (float)(y - j); - float z0 = (float)(z - k); - - int xNSign = (int)(-1.0f - x0) | 1; - int yNSign = (int)(-1.0f - y0) | 1; - int zNSign = (int)(-1.0f - z0) | 1; - - float ax0 = xNSign * -x0; - float ay0 = yNSign * -y0; - float az0 = zNSign * -z0; - - i *= PrimeX; - j *= PrimeY; - k *= PrimeZ; - - float value = 0; - float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0); - - for (int l = 0; ; l++) - { - if (a > 0) - { - value += (a * a) * (a * a) * GradCoord(seed, i, j, k, x0, y0, z0); - } - - float b = a + 1; - int i1 = i; - int j1 = j; - int k1 = k; - float x1 = x0; - float y1 = y0; - float z1 = z0; - - if (ax0 >= ay0 && ax0 >= az0) - { - x1 += xNSign; - b -= xNSign * 2 * x1; - i1 -= xNSign * PrimeX; - } - else if (ay0 > ax0 && ay0 >= az0) - { - y1 += yNSign; - b -= yNSign * 2 * y1; - j1 -= yNSign * PrimeY; - } - else - { - z1 += zNSign; - b -= zNSign * 2 * z1; - k1 -= zNSign * PrimeZ; - } - - if (b > 0) - { - value += (b * b) * (b * b) * GradCoord(seed, i1, j1, k1, x1, y1, z1); - } - - if (l == 1) break; - - ax0 = 0.5f - ax0; - ay0 = 0.5f - ay0; - az0 = 0.5f - az0; - - x0 = xNSign * ax0; - y0 = yNSign * ay0; - z0 = zNSign * az0; - - a += (0.75f - ax0) - (ay0 + az0); - - i += (xNSign >> 1) & PrimeX; - j += (yNSign >> 1) & PrimeY; - k += (zNSign >> 1) & PrimeZ; - - xNSign = -xNSign; - yNSign = -yNSign; - zNSign = -zNSign; - - seed = ~seed; - } - - return value * 32.69428253173828125f; - } - - - // OpenSimplex2S Noise - - template - float SingleOpenSimplex2S(int seed, FNfloat x, FNfloat y) const - { - // 2D OpenSimplex2S case is a modified 2D simplex noise. - - const FNfloat SQRT3 = (FNfloat)1.7320508075688772935274463415059; - const FNfloat G2 = (3 - SQRT3) / 6; - - /* - * --- Skew moved to TransformNoiseCoordinate method --- - * const FNfloat F2 = 0.5f * (SQRT3 - 1); - * FNfloat s = (x + y) * F2; - * x += s; y += s; - */ - - int i = FastFloor(x); - int j = FastFloor(y); - float xi = (float)(x - i); - float yi = (float)(y - j); - - i *= PrimeX; - j *= PrimeY; - int i1 = i + PrimeX; - int j1 = j + PrimeY; - - float t = (xi + yi) * (float)G2; - float x0 = xi - t; - float y0 = yi - t; - - float a0 = (2.0f / 3.0f) - x0 * x0 - y0 * y0; - float value = (a0 * a0) * (a0 * a0) * GradCoord(seed, i, j, x0, y0); - - float a1 = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a0); - float x1 = x0 - (float)(1 - 2 * G2); - float y1 = y0 - (float)(1 - 2 * G2); - value += (a1 * a1) * (a1 * a1) * GradCoord(seed, i1, j1, x1, y1); - - // Nested conditionals were faster than compact bit logic/arithmetic. - float xmyi = xi - yi; - if (t > G2) - { - if (xi + xmyi > 1) - { - float x2 = x0 + (float)(3 * G2 - 2); - float y2 = y0 + (float)(3 * G2 - 1); - float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; - if (a2 > 0) - { - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i + (PrimeX << 1), j + PrimeY, x2, y2); - } - } - else - { - float x2 = x0 + (float)G2; - float y2 = y0 + (float)(G2 - 1); - float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; - if (a2 > 0) - { - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j + PrimeY, x2, y2); - } - } - - if (yi - xmyi > 1) - { - float x3 = x0 + (float)(3 * G2 - 1); - float y3 = y0 + (float)(3 * G2 - 2); - float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3; - if (a3 > 0) - { - value += (a3 * a3) * (a3 * a3) * GradCoord(seed, i + PrimeX, j + (PrimeY << 1), x3, y3); - } - } - else - { - float x3 = x0 + (float)(G2 - 1); - float y3 = y0 + (float)G2; - float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3; - if (a3 > 0) - { - value += (a3 * a3) * (a3 * a3) * GradCoord(seed, i + PrimeX, j, x3, y3); - } - } - } - else - { - if (xi + xmyi < 0) - { - float x2 = x0 + (float)(1 - G2); - float y2 = y0 - (float)G2; - float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; - if (a2 > 0) - { - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i - PrimeX, j, x2, y2); - } - } - else - { - float x2 = x0 + (float)(G2 - 1); - float y2 = y0 + (float)G2; - float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; - if (a2 > 0) - { - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i + PrimeX, j, x2, y2); - } - } - - if (yi < xmyi) - { - float x2 = x0 - (float)G2; - float y2 = y0 - (float)(G2 - 1); - float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; - if (a2 > 0) - { - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j - PrimeY, x2, y2); - } - } - else - { - float x2 = x0 + (float)G2; - float y2 = y0 + (float)(G2 - 1); - float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; - if (a2 > 0) - { - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j + PrimeY, x2, y2); - } - } - } - - return value * 18.24196194486065f; - } - - template - float SingleOpenSimplex2S(int seed, FNfloat x, FNfloat y, FNfloat z) const - { - // 3D OpenSimplex2S case uses two offset rotated cube grids. - - /* - * --- Rotation moved to TransformNoiseCoordinate method --- - * const FNfloat R3 = (FNfloat)(2.0 / 3.0); - * FNfloat r = (x + y + z) * R3; // Rotation, not skew - * x = r - x; y = r - y; z = r - z; - */ - - int i = FastFloor(x); - int j = FastFloor(y); - int k = FastFloor(z); - float xi = (float)(x - i); - float yi = (float)(y - j); - float zi = (float)(z - k); - - i *= PrimeX; - j *= PrimeY; - k *= PrimeZ; - int seed2 = seed + 1293373; - - int xNMask = (int)(-0.5f - xi); - int yNMask = (int)(-0.5f - yi); - int zNMask = (int)(-0.5f - zi); - - float x0 = xi + xNMask; - float y0 = yi + yNMask; - float z0 = zi + zNMask; - float a0 = 0.75f - x0 * x0 - y0 * y0 - z0 * z0; - float value = (a0 * a0) * (a0 * a0) * GradCoord(seed, - i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x0, y0, z0); - - float x1 = xi - 0.5f; - float y1 = yi - 0.5f; - float z1 = zi - 0.5f; - float a1 = 0.75f - x1 * x1 - y1 * y1 - z1 * z1; - value += (a1 * a1) * (a1 * a1) * GradCoord(seed2, - i + PrimeX, j + PrimeY, k + PrimeZ, x1, y1, z1); - - float xAFlipMask0 = ((xNMask | 1) << 1) * x1; - float yAFlipMask0 = ((yNMask | 1) << 1) * y1; - float zAFlipMask0 = ((zNMask | 1) << 1) * z1; - float xAFlipMask1 = (-2 - (xNMask << 2)) * x1 - 1.0f; - float yAFlipMask1 = (-2 - (yNMask << 2)) * y1 - 1.0f; - float zAFlipMask1 = (-2 - (zNMask << 2)) * z1 - 1.0f; - - bool skip5 = false; - float a2 = xAFlipMask0 + a0; - if (a2 > 0) - { - float x2 = x0 - (xNMask | 1); - float y2 = y0; - float z2 = z0; - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, - i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x2, y2, z2); - } - else - { - float a3 = yAFlipMask0 + zAFlipMask0 + a0; - if (a3 > 0) - { - float x3 = x0; - float y3 = y0 - (yNMask | 1); - float z3 = z0 - (zNMask | 1); - value += (a3 * a3) * (a3 * a3) * GradCoord(seed, - i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (~zNMask & PrimeZ), x3, y3, z3); - } - - float a4 = xAFlipMask1 + a1; - if (a4 > 0) - { - float x4 = (xNMask | 1) + x1; - float y4 = y1; - float z4 = z1; - value += (a4 * a4) * (a4 * a4) * GradCoord(seed2, - i + (xNMask & (PrimeX * 2)), j + PrimeY, k + PrimeZ, x4, y4, z4); - skip5 = true; - } - } - - bool skip9 = false; - float a6 = yAFlipMask0 + a0; - if (a6 > 0) - { - float x6 = x0; - float y6 = y0 - (yNMask | 1); - float z6 = z0; - value += (a6 * a6) * (a6 * a6) * GradCoord(seed, - i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), x6, y6, z6); - } - else - { - float a7 = xAFlipMask0 + zAFlipMask0 + a0; - if (a7 > 0) - { - float x7 = x0 - (xNMask | 1); - float y7 = y0; - float z7 = z0 - (zNMask | 1); - value += (a7 * a7) * (a7 * a7) * GradCoord(seed, - i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), x7, y7, z7); - } - - float a8 = yAFlipMask1 + a1; - if (a8 > 0) - { - float x8 = x1; - float y8 = (yNMask | 1) + y1; - float z8 = z1; - value += (a8 * a8) * (a8 * a8) * GradCoord(seed2, - i + PrimeX, j + (yNMask & (PrimeY << 1)), k + PrimeZ, x8, y8, z8); - skip9 = true; - } - } - - bool skipD = false; - float aA = zAFlipMask0 + a0; - if (aA > 0) - { - float xA = x0; - float yA = y0; - float zA = z0 - (zNMask | 1); - value += (aA * aA) * (aA * aA) * GradCoord(seed, - i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), xA, yA, zA); - } - else - { - float aB = xAFlipMask0 + yAFlipMask0 + a0; - if (aB > 0) - { - float xB = x0 - (xNMask | 1); - float yB = y0 - (yNMask | 1); - float zB = z0; - value += (aB * aB) * (aB * aB) * GradCoord(seed, - i + (~xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), xB, yB, zB); - } - - float aC = zAFlipMask1 + a1; - if (aC > 0) - { - float xC = x1; - float yC = y1; - float zC = (zNMask | 1) + z1; - value += (aC * aC) * (aC * aC) * GradCoord(seed2, - i + PrimeX, j + PrimeY, k + (zNMask & (PrimeZ << 1)), xC, yC, zC); - skipD = true; - } - } - - if (!skip5) - { - float a5 = yAFlipMask1 + zAFlipMask1 + a1; - if (a5 > 0) - { - float x5 = x1; - float y5 = (yNMask | 1) + y1; - float z5 = (zNMask | 1) + z1; - value += (a5 * a5) * (a5 * a5) * GradCoord(seed2, - i + PrimeX, j + (yNMask & (PrimeY << 1)), k + (zNMask & (PrimeZ << 1)), x5, y5, z5); - } - } - - if (!skip9) - { - float a9 = xAFlipMask1 + zAFlipMask1 + a1; - if (a9 > 0) - { - float x9 = (xNMask | 1) + x1; - float y9 = y1; - float z9 = (zNMask | 1) + z1; - value += (a9 * a9) * (a9 * a9) * GradCoord(seed2, - i + (xNMask & (PrimeX * 2)), j + PrimeY, k + (zNMask & (PrimeZ << 1)), x9, y9, z9); - } - } - - if (!skipD) - { - float aD = xAFlipMask1 + yAFlipMask1 + a1; - if (aD > 0) - { - float xD = (xNMask | 1) + x1; - float yD = (yNMask | 1) + y1; - float zD = z1; - value += (aD * aD) * (aD * aD) * GradCoord(seed2, - i + (xNMask & (PrimeX << 1)), j + (yNMask & (PrimeY << 1)), k + PrimeZ, xD, yD, zD); - } - } - - return value * 9.046026385208288f; - } - - - // Cellular Noise - - template - float SingleCellular(int seed, FNfloat x, FNfloat y) const - { - int xr = FastRound(x); - int yr = FastRound(y); - - float distance0 = 1e10f; - float distance1 = 1e10f; - int closestHash = 0; - - float cellularJitter = 0.43701595f * mCellularJitterModifier; - - int xPrimed = (xr - 1) * PrimeX; - int yPrimedBase = (yr - 1) * PrimeY; - - switch (mCellularDistanceFunction) - { - default: - case CellularDistanceFunction_Euclidean: - case CellularDistanceFunction_EuclideanSq: - for (int xi = xr - 1; xi <= xr + 1; xi++) - { - int yPrimed = yPrimedBase; - - for (int yi = yr - 1; yi <= yr + 1; yi++) - { - int hash = Hash(seed, xPrimed, yPrimed); - int idx = hash & (255 << 1); - - float vecX = (float)(xi - x) + Lookup::RandVecs2D[idx] * cellularJitter; - float vecY = (float)(yi - y) + Lookup::RandVecs2D[idx | 1] * cellularJitter; - - float newDistance = vecX * vecX + vecY * vecY; - - distance1 = FastMax(FastMin(distance1, newDistance), distance0); - if (newDistance < distance0) - { - distance0 = newDistance; - closestHash = hash; - } - yPrimed += PrimeY; - } - xPrimed += PrimeX; - } - break; - case CellularDistanceFunction_Manhattan: - for (int xi = xr - 1; xi <= xr + 1; xi++) - { - int yPrimed = yPrimedBase; - - for (int yi = yr - 1; yi <= yr + 1; yi++) - { - int hash = Hash(seed, xPrimed, yPrimed); - int idx = hash & (255 << 1); - - float vecX = (float)(xi - x) + Lookup::RandVecs2D[idx] * cellularJitter; - float vecY = (float)(yi - y) + Lookup::RandVecs2D[idx | 1] * cellularJitter; - - float newDistance = FastAbs(vecX) + FastAbs(vecY); - - distance1 = FastMax(FastMin(distance1, newDistance), distance0); - if (newDistance < distance0) - { - distance0 = newDistance; - closestHash = hash; - } - yPrimed += PrimeY; - } - xPrimed += PrimeX; - } - break; - case CellularDistanceFunction_Hybrid: - for (int xi = xr - 1; xi <= xr + 1; xi++) - { - int yPrimed = yPrimedBase; - - for (int yi = yr - 1; yi <= yr + 1; yi++) - { - int hash = Hash(seed, xPrimed, yPrimed); - int idx = hash & (255 << 1); - - float vecX = (float)(xi - x) + Lookup::RandVecs2D[idx] * cellularJitter; - float vecY = (float)(yi - y) + Lookup::RandVecs2D[idx | 1] * cellularJitter; - - float newDistance = (FastAbs(vecX) + FastAbs(vecY)) + (vecX * vecX + vecY * vecY); - - distance1 = FastMax(FastMin(distance1, newDistance), distance0); - if (newDistance < distance0) - { - distance0 = newDistance; - closestHash = hash; - } - yPrimed += PrimeY; - } - xPrimed += PrimeX; - } - break; - } - - if (mCellularDistanceFunction == CellularDistanceFunction_Euclidean && mCellularReturnType >= CellularReturnType_Distance) - { - distance0 = FastSqrt(distance0); - - if (mCellularReturnType >= CellularReturnType_Distance2) - { - distance1 = FastSqrt(distance1); - } - } - - switch (mCellularReturnType) - { - case CellularReturnType_CellValue: - return closestHash * (1 / 2147483648.0f); - case CellularReturnType_Distance: - return distance0 - 1; - case CellularReturnType_Distance2: - return distance1 - 1; - case CellularReturnType_Distance2Add: - return (distance1 + distance0) * 0.5f - 1; - case CellularReturnType_Distance2Sub: - return distance1 - distance0 - 1; - case CellularReturnType_Distance2Mul: - return distance1 * distance0 * 0.5f - 1; - case CellularReturnType_Distance2Div: - return distance0 / distance1 - 1; - default: - return 0; - } - } - - template - float SingleCellular(int seed, FNfloat x, FNfloat y, FNfloat z) const - { - int xr = FastRound(x); - int yr = FastRound(y); - int zr = FastRound(z); - - float distance0 = 1e10f; - float distance1 = 1e10f; - int closestHash = 0; - - float cellularJitter = 0.39614353f * mCellularJitterModifier; - - int xPrimed = (xr - 1) * PrimeX; - int yPrimedBase = (yr - 1) * PrimeY; - int zPrimedBase = (zr - 1) * PrimeZ; - - switch (mCellularDistanceFunction) - { - case CellularDistanceFunction_Euclidean: - case CellularDistanceFunction_EuclideanSq: - for (int xi = xr - 1; xi <= xr + 1; xi++) - { - int yPrimed = yPrimedBase; - - for (int yi = yr - 1; yi <= yr + 1; yi++) - { - int zPrimed = zPrimedBase; - - for (int zi = zr - 1; zi <= zr + 1; zi++) - { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); - int idx = hash & (255 << 2); - - float vecX = (float)(xi - x) + Lookup::RandVecs3D[idx] * cellularJitter; - float vecY = (float)(yi - y) + Lookup::RandVecs3D[idx | 1] * cellularJitter; - float vecZ = (float)(zi - z) + Lookup::RandVecs3D[idx | 2] * cellularJitter; - - float newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ; - - distance1 = FastMax(FastMin(distance1, newDistance), distance0); - if (newDistance < distance0) - { - distance0 = newDistance; - closestHash = hash; - } - zPrimed += PrimeZ; - } - yPrimed += PrimeY; - } - xPrimed += PrimeX; - } - break; - case CellularDistanceFunction_Manhattan: - for (int xi = xr - 1; xi <= xr + 1; xi++) - { - int yPrimed = yPrimedBase; - - for (int yi = yr - 1; yi <= yr + 1; yi++) - { - int zPrimed = zPrimedBase; - - for (int zi = zr - 1; zi <= zr + 1; zi++) - { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); - int idx = hash & (255 << 2); - - float vecX = (float)(xi - x) + Lookup::RandVecs3D[idx] * cellularJitter; - float vecY = (float)(yi - y) + Lookup::RandVecs3D[idx | 1] * cellularJitter; - float vecZ = (float)(zi - z) + Lookup::RandVecs3D[idx | 2] * cellularJitter; - - float newDistance = FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ); - - distance1 = FastMax(FastMin(distance1, newDistance), distance0); - if (newDistance < distance0) - { - distance0 = newDistance; - closestHash = hash; - } - zPrimed += PrimeZ; - } - yPrimed += PrimeY; - } - xPrimed += PrimeX; - } - break; - case CellularDistanceFunction_Hybrid: - for (int xi = xr - 1; xi <= xr + 1; xi++) - { - int yPrimed = yPrimedBase; - - for (int yi = yr - 1; yi <= yr + 1; yi++) - { - int zPrimed = zPrimedBase; - - for (int zi = zr - 1; zi <= zr + 1; zi++) - { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); - int idx = hash & (255 << 2); - - float vecX = (float)(xi - x) + Lookup::RandVecs3D[idx] * cellularJitter; - float vecY = (float)(yi - y) + Lookup::RandVecs3D[idx | 1] * cellularJitter; - float vecZ = (float)(zi - z) + Lookup::RandVecs3D[idx | 2] * cellularJitter; - - float newDistance = (FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ)) + (vecX * vecX + vecY * vecY + vecZ * vecZ); - - distance1 = FastMax(FastMin(distance1, newDistance), distance0); - if (newDistance < distance0) - { - distance0 = newDistance; - closestHash = hash; - } - zPrimed += PrimeZ; - } - yPrimed += PrimeY; - } - xPrimed += PrimeX; - } - break; - default: - break; - } - - if (mCellularDistanceFunction == CellularDistanceFunction_Euclidean && mCellularReturnType >= CellularReturnType_Distance) - { - distance0 = FastSqrt(distance0); - - if (mCellularReturnType >= CellularReturnType_Distance2) - { - distance1 = FastSqrt(distance1); - } - } - - switch (mCellularReturnType) - { - case CellularReturnType_CellValue: - return closestHash * (1 / 2147483648.0f); - case CellularReturnType_Distance: - return distance0 - 1; - case CellularReturnType_Distance2: - return distance1 - 1; - case CellularReturnType_Distance2Add: - return (distance1 + distance0) * 0.5f - 1; - case CellularReturnType_Distance2Sub: - return distance1 - distance0 - 1; - case CellularReturnType_Distance2Mul: - return distance1 * distance0 * 0.5f - 1; - case CellularReturnType_Distance2Div: - return distance0 / distance1 - 1; - default: - return 0; - } - } - - - // Perlin Noise - - template - float SinglePerlin(int seed, FNfloat x, FNfloat y) const - { - int x0 = FastFloor(x); - int y0 = FastFloor(y); - - float xd0 = (float)(x - x0); - float yd0 = (float)(y - y0); - float xd1 = xd0 - 1; - float yd1 = yd0 - 1; - - float xs = InterpQuintic(xd0); - float ys = InterpQuintic(yd0); - - x0 *= PrimeX; - y0 *= PrimeY; - int x1 = x0 + PrimeX; - int y1 = y0 + PrimeY; - - float xf0 = Lerp(GradCoord(seed, x0, y0, xd0, yd0), GradCoord(seed, x1, y0, xd1, yd0), xs); - float xf1 = Lerp(GradCoord(seed, x0, y1, xd0, yd1), GradCoord(seed, x1, y1, xd1, yd1), xs); - - return Lerp(xf0, xf1, ys) * 1.4247691104677813f; - } - - template - float SinglePerlin(int seed, FNfloat x, FNfloat y, FNfloat z) const - { - int x0 = FastFloor(x); - int y0 = FastFloor(y); - int z0 = FastFloor(z); - - float xd0 = (float)(x - x0); - float yd0 = (float)(y - y0); - float zd0 = (float)(z - z0); - float xd1 = xd0 - 1; - float yd1 = yd0 - 1; - float zd1 = zd0 - 1; - - float xs = InterpQuintic(xd0); - float ys = InterpQuintic(yd0); - float zs = InterpQuintic(zd0); - - x0 *= PrimeX; - y0 *= PrimeY; - z0 *= PrimeZ; - int x1 = x0 + PrimeX; - int y1 = y0 + PrimeY; - int z1 = z0 + PrimeZ; - - float xf00 = Lerp(GradCoord(seed, x0, y0, z0, xd0, yd0, zd0), GradCoord(seed, x1, y0, z0, xd1, yd0, zd0), xs); - float xf10 = Lerp(GradCoord(seed, x0, y1, z0, xd0, yd1, zd0), GradCoord(seed, x1, y1, z0, xd1, yd1, zd0), xs); - float xf01 = Lerp(GradCoord(seed, x0, y0, z1, xd0, yd0, zd1), GradCoord(seed, x1, y0, z1, xd1, yd0, zd1), xs); - float xf11 = Lerp(GradCoord(seed, x0, y1, z1, xd0, yd1, zd1), GradCoord(seed, x1, y1, z1, xd1, yd1, zd1), xs); - - float yf0 = Lerp(xf00, xf10, ys); - float yf1 = Lerp(xf01, xf11, ys); - - return Lerp(yf0, yf1, zs) * 0.964921414852142333984375f; - } - - - // Value Cubic Noise - - template - float SingleValueCubic(int seed, FNfloat x, FNfloat y) const - { - int x1 = FastFloor(x); - int y1 = FastFloor(y); - - float xs = (float)(x - x1); - float ys = (float)(y - y1); - - x1 *= PrimeX; - y1 *= PrimeY; - int x0 = x1 - PrimeX; - int y0 = y1 - PrimeY; - int x2 = x1 + PrimeX; - int y2 = y1 + PrimeY; - int x3 = x1 + (int)((long)PrimeX << 1); - int y3 = y1 + (int)((long)PrimeY << 1); - - return CubicLerp( - CubicLerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), ValCoord(seed, x2, y0), ValCoord(seed, x3, y0), - xs), - CubicLerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), ValCoord(seed, x2, y1), ValCoord(seed, x3, y1), - xs), - CubicLerp(ValCoord(seed, x0, y2), ValCoord(seed, x1, y2), ValCoord(seed, x2, y2), ValCoord(seed, x3, y2), - xs), - CubicLerp(ValCoord(seed, x0, y3), ValCoord(seed, x1, y3), ValCoord(seed, x2, y3), ValCoord(seed, x3, y3), - xs), - ys) * (1 / (1.5f * 1.5f)); - } - - template - float SingleValueCubic(int seed, FNfloat x, FNfloat y, FNfloat z) const - { - int x1 = FastFloor(x); - int y1 = FastFloor(y); - int z1 = FastFloor(z); - - float xs = (float)(x - x1); - float ys = (float)(y - y1); - float zs = (float)(z - z1); - - x1 *= PrimeX; - y1 *= PrimeY; - z1 *= PrimeZ; - - int x0 = x1 - PrimeX; - int y0 = y1 - PrimeY; - int z0 = z1 - PrimeZ; - int x2 = x1 + PrimeX; - int y2 = y1 + PrimeY; - int z2 = z1 + PrimeZ; - int x3 = x1 + (int)((long)PrimeX << 1); - int y3 = y1 + (int)((long)PrimeY << 1); - int z3 = z1 + (int)((long)PrimeZ << 1); - - - return CubicLerp( - CubicLerp( - CubicLerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), ValCoord(seed, x2, y0, z0), ValCoord(seed, x3, y0, z0), xs), - CubicLerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), ValCoord(seed, x2, y1, z0), ValCoord(seed, x3, y1, z0), xs), - CubicLerp(ValCoord(seed, x0, y2, z0), ValCoord(seed, x1, y2, z0), ValCoord(seed, x2, y2, z0), ValCoord(seed, x3, y2, z0), xs), - CubicLerp(ValCoord(seed, x0, y3, z0), ValCoord(seed, x1, y3, z0), ValCoord(seed, x2, y3, z0), ValCoord(seed, x3, y3, z0), xs), - ys), - CubicLerp( - CubicLerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), ValCoord(seed, x2, y0, z1), ValCoord(seed, x3, y0, z1), xs), - CubicLerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), ValCoord(seed, x2, y1, z1), ValCoord(seed, x3, y1, z1), xs), - CubicLerp(ValCoord(seed, x0, y2, z1), ValCoord(seed, x1, y2, z1), ValCoord(seed, x2, y2, z1), ValCoord(seed, x3, y2, z1), xs), - CubicLerp(ValCoord(seed, x0, y3, z1), ValCoord(seed, x1, y3, z1), ValCoord(seed, x2, y3, z1), ValCoord(seed, x3, y3, z1), xs), - ys), - CubicLerp( - CubicLerp(ValCoord(seed, x0, y0, z2), ValCoord(seed, x1, y0, z2), ValCoord(seed, x2, y0, z2), ValCoord(seed, x3, y0, z2), xs), - CubicLerp(ValCoord(seed, x0, y1, z2), ValCoord(seed, x1, y1, z2), ValCoord(seed, x2, y1, z2), ValCoord(seed, x3, y1, z2), xs), - CubicLerp(ValCoord(seed, x0, y2, z2), ValCoord(seed, x1, y2, z2), ValCoord(seed, x2, y2, z2), ValCoord(seed, x3, y2, z2), xs), - CubicLerp(ValCoord(seed, x0, y3, z2), ValCoord(seed, x1, y3, z2), ValCoord(seed, x2, y3, z2), ValCoord(seed, x3, y3, z2), xs), - ys), - CubicLerp( - CubicLerp(ValCoord(seed, x0, y0, z3), ValCoord(seed, x1, y0, z3), ValCoord(seed, x2, y0, z3), ValCoord(seed, x3, y0, z3), xs), - CubicLerp(ValCoord(seed, x0, y1, z3), ValCoord(seed, x1, y1, z3), ValCoord(seed, x2, y1, z3), ValCoord(seed, x3, y1, z3), xs), - CubicLerp(ValCoord(seed, x0, y2, z3), ValCoord(seed, x1, y2, z3), ValCoord(seed, x2, y2, z3), ValCoord(seed, x3, y2, z3), xs), - CubicLerp(ValCoord(seed, x0, y3, z3), ValCoord(seed, x1, y3, z3), ValCoord(seed, x2, y3, z3), ValCoord(seed, x3, y3, z3), xs), - ys), - zs) * (1 / (1.5f * 1.5f * 1.5f)); - } - - - // Value Noise - - template - float SingleValue(int seed, FNfloat x, FNfloat y) const - { - int x0 = FastFloor(x); - int y0 = FastFloor(y); - - float xs = InterpHermite((float)(x - x0)); - float ys = InterpHermite((float)(y - y0)); - - x0 *= PrimeX; - y0 *= PrimeY; - int x1 = x0 + PrimeX; - int y1 = y0 + PrimeY; - - float xf0 = Lerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), xs); - float xf1 = Lerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), xs); - - return Lerp(xf0, xf1, ys); - } - - template - float SingleValue(int seed, FNfloat x, FNfloat y, FNfloat z) const - { - int x0 = FastFloor(x); - int y0 = FastFloor(y); - int z0 = FastFloor(z); - - float xs = InterpHermite((float)(x - x0)); - float ys = InterpHermite((float)(y - y0)); - float zs = InterpHermite((float)(z - z0)); - - x0 *= PrimeX; - y0 *= PrimeY; - z0 *= PrimeZ; - int x1 = x0 + PrimeX; - int y1 = y0 + PrimeY; - int z1 = z0 + PrimeZ; - - float xf00 = Lerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), xs); - float xf10 = Lerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), xs); - float xf01 = Lerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), xs); - float xf11 = Lerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), xs); - - float yf0 = Lerp(xf00, xf10, ys); - float yf1 = Lerp(xf01, xf11, ys); - - return Lerp(yf0, yf1, zs); - } - - - // Domain Warp - - template - void DoSingleDomainWarp(int seed, float amp, float freq, FNfloat x, FNfloat y, FNfloat& xr, FNfloat& yr) const - { - switch (mDomainWarpType) - { - case DomainWarpType_OpenSimplex2: - SingleDomainWarpSimplexGradient(seed, amp * 38.283687591552734375f, freq, x, y, xr, yr, false); - break; - case DomainWarpType_OpenSimplex2Reduced: - SingleDomainWarpSimplexGradient(seed, amp * 16.0f, freq, x, y, xr, yr, true); - break; - case DomainWarpType_BasicGrid: - SingleDomainWarpBasicGrid(seed, amp, freq, x, y, xr, yr); - break; - } - } - - template - void DoSingleDomainWarp(int seed, float amp, float freq, FNfloat x, FNfloat y, FNfloat z, FNfloat& xr, FNfloat& yr, FNfloat& zr) const - { - switch (mDomainWarpType) - { - case DomainWarpType_OpenSimplex2: - SingleDomainWarpOpenSimplex2Gradient(seed, amp * 32.69428253173828125f, freq, x, y, z, xr, yr, zr, false); - break; - case DomainWarpType_OpenSimplex2Reduced: - SingleDomainWarpOpenSimplex2Gradient(seed, amp * 7.71604938271605f, freq, x, y, z, xr, yr, zr, true); - break; - case DomainWarpType_BasicGrid: - SingleDomainWarpBasicGrid(seed, amp, freq, x, y, z, xr, yr, zr); - break; - } - } - - - // Domain Warp Single Wrapper - - template - void DomainWarpSingle(FNfloat& x, FNfloat& y) const - { - int seed = mSeed; - float amp = mDomainWarpAmp * mFractalBounding; - float freq = mFrequency; - - FNfloat xs = x; - FNfloat ys = y; - TransformDomainWarpCoordinate(xs, ys); - - DoSingleDomainWarp(seed, amp, freq, xs, ys, x, y); - } - - template - void DomainWarpSingle(FNfloat& x, FNfloat& y, FNfloat& z) const - { - int seed = mSeed; - float amp = mDomainWarpAmp * mFractalBounding; - float freq = mFrequency; - - FNfloat xs = x; - FNfloat ys = y; - FNfloat zs = z; - TransformDomainWarpCoordinate(xs, ys, zs); - - DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, x, y, z); - } - - - // Domain Warp Fractal Progressive - - template - void DomainWarpFractalProgressive(FNfloat& x, FNfloat& y) const - { - int seed = mSeed; - float amp = mDomainWarpAmp * mFractalBounding; - float freq = mFrequency; - - for (int i = 0; i < mOctaves; i++) - { - FNfloat xs = x; - FNfloat ys = y; - TransformDomainWarpCoordinate(xs, ys); - - DoSingleDomainWarp(seed, amp, freq, xs, ys, x, y); - - seed++; - amp *= mGain; - freq *= mLacunarity; - } - } - - template - void DomainWarpFractalProgressive(FNfloat& x, FNfloat& y, FNfloat& z) const - { - int seed = mSeed; - float amp = mDomainWarpAmp * mFractalBounding; - float freq = mFrequency; - - for (int i = 0; i < mOctaves; i++) - { - FNfloat xs = x; - FNfloat ys = y; - FNfloat zs = z; - TransformDomainWarpCoordinate(xs, ys, zs); - - DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, x, y, z); - - seed++; - amp *= mGain; - freq *= mLacunarity; - } - } - - - // Domain Warp Fractal Independant - - template - void DomainWarpFractalIndependent(FNfloat& x, FNfloat& y) const - { - FNfloat xs = x; - FNfloat ys = y; - TransformDomainWarpCoordinate(xs, ys); - - int seed = mSeed; - float amp = mDomainWarpAmp * mFractalBounding; - float freq = mFrequency; - - for (int i = 0; i < mOctaves; i++) - { - DoSingleDomainWarp(seed, amp, freq, xs, ys, x, y); - - seed++; - amp *= mGain; - freq *= mLacunarity; - } - } - - template - void DomainWarpFractalIndependent(FNfloat& x, FNfloat& y, FNfloat& z) const - { - FNfloat xs = x; - FNfloat ys = y; - FNfloat zs = z; - TransformDomainWarpCoordinate(xs, ys, zs); - - int seed = mSeed; - float amp = mDomainWarpAmp * mFractalBounding; - float freq = mFrequency; - - for (int i = 0; i < mOctaves; i++) - { - DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, x, y, z); - - seed++; - amp *= mGain; - freq *= mLacunarity; - } - } - - - // Domain Warp Basic Grid - - template - void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, FNfloat x, FNfloat y, FNfloat& xr, FNfloat& yr) const - { - FNfloat xf = x * frequency; - FNfloat yf = y * frequency; - - int x0 = FastFloor(xf); - int y0 = FastFloor(yf); - - float xs = InterpHermite((float)(xf - x0)); - float ys = InterpHermite((float)(yf - y0)); - - x0 *= PrimeX; - y0 *= PrimeY; - int x1 = x0 + PrimeX; - int y1 = y0 + PrimeY; - - int hash0 = Hash(seed, x0, y0) & (255 << 1); - int hash1 = Hash(seed, x1, y0) & (255 << 1); - - float lx0x = Lerp(Lookup::RandVecs2D[hash0], Lookup::RandVecs2D[hash1], xs); - float ly0x = Lerp(Lookup::RandVecs2D[hash0 | 1], Lookup::RandVecs2D[hash1 | 1], xs); - - hash0 = Hash(seed, x0, y1) & (255 << 1); - hash1 = Hash(seed, x1, y1) & (255 << 1); - - float lx1x = Lerp(Lookup::RandVecs2D[hash0], Lookup::RandVecs2D[hash1], xs); - float ly1x = Lerp(Lookup::RandVecs2D[hash0 | 1], Lookup::RandVecs2D[hash1 | 1], xs); - - xr += Lerp(lx0x, lx1x, ys) * warpAmp; - yr += Lerp(ly0x, ly1x, ys) * warpAmp; - } - - template - void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, FNfloat x, FNfloat y, FNfloat z, FNfloat& xr, FNfloat& yr, FNfloat& zr) const - { - FNfloat xf = x * frequency; - FNfloat yf = y * frequency; - FNfloat zf = z * frequency; - - int x0 = FastFloor(xf); - int y0 = FastFloor(yf); - int z0 = FastFloor(zf); - - float xs = InterpHermite((float)(xf - x0)); - float ys = InterpHermite((float)(yf - y0)); - float zs = InterpHermite((float)(zf - z0)); - - x0 *= PrimeX; - y0 *= PrimeY; - z0 *= PrimeZ; - int x1 = x0 + PrimeX; - int y1 = y0 + PrimeY; - int z1 = z0 + PrimeZ; - - int hash0 = Hash(seed, x0, y0, z0) & (255 << 2); - int hash1 = Hash(seed, x1, y0, z0) & (255 << 2); - - float lx0x = Lerp(Lookup::RandVecs3D[hash0], Lookup::RandVecs3D[hash1], xs); - float ly0x = Lerp(Lookup::RandVecs3D[hash0 | 1], Lookup::RandVecs3D[hash1 | 1], xs); - float lz0x = Lerp(Lookup::RandVecs3D[hash0 | 2], Lookup::RandVecs3D[hash1 | 2], xs); - - hash0 = Hash(seed, x0, y1, z0) & (255 << 2); - hash1 = Hash(seed, x1, y1, z0) & (255 << 2); - - float lx1x = Lerp(Lookup::RandVecs3D[hash0], Lookup::RandVecs3D[hash1], xs); - float ly1x = Lerp(Lookup::RandVecs3D[hash0 | 1], Lookup::RandVecs3D[hash1 | 1], xs); - float lz1x = Lerp(Lookup::RandVecs3D[hash0 | 2], Lookup::RandVecs3D[hash1 | 2], xs); - - float lx0y = Lerp(lx0x, lx1x, ys); - float ly0y = Lerp(ly0x, ly1x, ys); - float lz0y = Lerp(lz0x, lz1x, ys); - - hash0 = Hash(seed, x0, y0, z1) & (255 << 2); - hash1 = Hash(seed, x1, y0, z1) & (255 << 2); - - lx0x = Lerp(Lookup::RandVecs3D[hash0], Lookup::RandVecs3D[hash1], xs); - ly0x = Lerp(Lookup::RandVecs3D[hash0 | 1], Lookup::RandVecs3D[hash1 | 1], xs); - lz0x = Lerp(Lookup::RandVecs3D[hash0 | 2], Lookup::RandVecs3D[hash1 | 2], xs); - - hash0 = Hash(seed, x0, y1, z1) & (255 << 2); - hash1 = Hash(seed, x1, y1, z1) & (255 << 2); - - lx1x = Lerp(Lookup::RandVecs3D[hash0], Lookup::RandVecs3D[hash1], xs); - ly1x = Lerp(Lookup::RandVecs3D[hash0 | 1], Lookup::RandVecs3D[hash1 | 1], xs); - lz1x = Lerp(Lookup::RandVecs3D[hash0 | 2], Lookup::RandVecs3D[hash1 | 2], xs); - - xr += Lerp(lx0y, Lerp(lx0x, lx1x, ys), zs) * warpAmp; - yr += Lerp(ly0y, Lerp(ly0x, ly1x, ys), zs) * warpAmp; - zr += Lerp(lz0y, Lerp(lz0x, lz1x, ys), zs) * warpAmp; - } - - - // Domain Warp Simplex/OpenSimplex2 - - template - void SingleDomainWarpSimplexGradient(int seed, float warpAmp, float frequency, FNfloat x, FNfloat y, FNfloat& xr, FNfloat& yr, bool outGradOnly) const - { - const float SQRT3 = 1.7320508075688772935274463415059f; - const float G2 = (3 - SQRT3) / 6; - - x *= frequency; - y *= frequency; - - /* - * --- Skew moved to TransformNoiseCoordinate method --- - * const FNfloat F2 = 0.5f * (SQRT3 - 1); - * FNfloat s = (x + y) * F2; - * x += s; y += s; - */ - - int i = FastFloor(x); - int j = FastFloor(y); - float xi = (float)(x - i); - float yi = (float)(y - j); - - float t = (xi + yi) * G2; - float x0 = (float)(xi - t); - float y0 = (float)(yi - t); - - i *= PrimeX; - j *= PrimeY; - - float vx, vy; - vx = vy = 0; - - float a = 0.5f - x0 * x0 - y0 * y0; - if (a > 0) - { - float aaaa = (a * a) * (a * a); - float xo, yo; - if (outGradOnly) - GradCoordOut(seed, i, j, xo, yo); - else - GradCoordDual(seed, i, j, x0, y0, xo, yo); - vx += aaaa * xo; - vy += aaaa * yo; - } - - float c = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a); - if (c > 0) - { - float x2 = x0 + (2 * (float)G2 - 1); - float y2 = y0 + (2 * (float)G2 - 1); - float cccc = (c * c) * (c * c); - float xo, yo; - if (outGradOnly) - GradCoordOut(seed, i + PrimeX, j + PrimeY, xo, yo); - else - GradCoordDual(seed, i + PrimeX, j + PrimeY, x2, y2, xo, yo); - vx += cccc * xo; - vy += cccc * yo; - } - - if (y0 > x0) - { - float x1 = x0 + (float)G2; - float y1 = y0 + ((float)G2 - 1); - float b = 0.5f - x1 * x1 - y1 * y1; - if (b > 0) - { - float bbbb = (b * b) * (b * b); - float xo, yo; - if (outGradOnly) - GradCoordOut(seed, i, j + PrimeY, xo, yo); - else - GradCoordDual(seed, i, j + PrimeY, x1, y1, xo, yo); - vx += bbbb * xo; - vy += bbbb * yo; - } - } - else - { - float x1 = x0 + ((float)G2 - 1); - float y1 = y0 + (float)G2; - float b = 0.5f - x1 * x1 - y1 * y1; - if (b > 0) - { - float bbbb = (b * b) * (b * b); - float xo, yo; - if (outGradOnly) - GradCoordOut(seed, i + PrimeX, j, xo, yo); - else - GradCoordDual(seed, i + PrimeX, j, x1, y1, xo, yo); - vx += bbbb * xo; - vy += bbbb * yo; - } - } - - xr += vx * warpAmp; - yr += vy * warpAmp; - } - - template - void SingleDomainWarpOpenSimplex2Gradient(int seed, float warpAmp, float frequency, FNfloat x, FNfloat y, FNfloat z, FNfloat& xr, FNfloat& yr, FNfloat& zr, bool outGradOnly) const - { - x *= frequency; - y *= frequency; - z *= frequency; - - /* - * --- Rotation moved to TransformDomainWarpCoordinate method --- - * const FNfloat R3 = (FNfloat)(2.0 / 3.0); - * FNfloat r = (x + y + z) * R3; // Rotation, not skew - * x = r - x; y = r - y; z = r - z; - */ - - int i = FastRound(x); - int j = FastRound(y); - int k = FastRound(z); - float x0 = (float)x - i; - float y0 = (float)y - j; - float z0 = (float)z - k; - - int xNSign = (int)(-x0 - 1.0f) | 1; - int yNSign = (int)(-y0 - 1.0f) | 1; - int zNSign = (int)(-z0 - 1.0f) | 1; - - float ax0 = xNSign * -x0; - float ay0 = yNSign * -y0; - float az0 = zNSign * -z0; - - i *= PrimeX; - j *= PrimeY; - k *= PrimeZ; - - float vx, vy, vz; - vx = vy = vz = 0; - - float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0); - for (int l = 0; l < 2; l++) - { - if (a > 0) - { - float aaaa = (a * a) * (a * a); - float xo, yo, zo; - if (outGradOnly) - GradCoordOut(seed, i, j, k, xo, yo, zo); - else - GradCoordDual(seed, i, j, k, x0, y0, z0, xo, yo, zo); - vx += aaaa * xo; - vy += aaaa * yo; - vz += aaaa * zo; - } - - float b = a + 1; - int i1 = i; - int j1 = j; - int k1 = k; - float x1 = x0; - float y1 = y0; - float z1 = z0; - - if (ax0 >= ay0 && ax0 >= az0) - { - x1 += xNSign; - b -= xNSign * 2 * x1; - i1 -= xNSign * PrimeX; - } - else if (ay0 > ax0 && ay0 >= az0) - { - y1 += yNSign; - b -= yNSign * 2 * y1; - j1 -= yNSign * PrimeY; - } - else - { - z1 += zNSign; - b -= zNSign * 2 * z1; - k1 -= zNSign * PrimeZ; - } - - if (b > 0) - { - float bbbb = (b * b) * (b * b); - float xo, yo, zo; - if (outGradOnly) - GradCoordOut(seed, i1, j1, k1, xo, yo, zo); - else - GradCoordDual(seed, i1, j1, k1, x1, y1, z1, xo, yo, zo); - vx += bbbb * xo; - vy += bbbb * yo; - vz += bbbb * zo; - } - - if (l == 1) break; - - ax0 = 0.5f - ax0; - ay0 = 0.5f - ay0; - az0 = 0.5f - az0; - - x0 = xNSign * ax0; - y0 = yNSign * ay0; - z0 = zNSign * az0; - - a += (0.75f - ax0) - (ay0 + az0); - - i += (xNSign >> 1) & PrimeX; - j += (yNSign >> 1) & PrimeY; - k += (zNSign >> 1) & PrimeZ; - - xNSign = -xNSign; - yNSign = -yNSign; - zNSign = -zNSign; - - seed += 1293373; - } - - xr += vx * warpAmp; - yr += vy * warpAmp; - zr += vz * warpAmp; - } + template + struct Arguments_must_be_floating_point_values; + + enum TransformType3D { + TransformType3D_None, + TransformType3D_ImproveXYPlanes, + TransformType3D_ImproveXZPlanes, + TransformType3D_DefaultOpenSimplex2 + }; + + int mSeed; + float mFrequency; + NoiseType mNoiseType; + RotationType3D mRotationType3D; + TransformType3D mTransformType3D; + + FractalType mFractalType; + int mOctaves; + float mLacunarity; + float mGain; + float mWeightedStrength; + float mPingPongStrength; + + float mFractalBounding; + + CellularDistanceFunction mCellularDistanceFunction; + CellularReturnType mCellularReturnType; + float mCellularJitterModifier; + + DomainWarpType mDomainWarpType; + TransformType3D mWarpTransformType3D; + float mDomainWarpAmp; + + template + struct Lookup { + static const T Gradients2D[]; + static const T Gradients3D[]; + static const T RandVecs2D[]; + static const T RandVecs3D[]; + }; + + static float FastMin(float a, float b) { + return a < b ? a : b; + } + + static float FastMax(float a, float b) { + return a > b ? a : b; + } + + static float FastAbs(float f) { + return f < 0 ? -f : f; + } + + static float FastSqrt(float f) { + return sqrtf(f); + } + + template + static int FastFloor(FNfloat f) { + return f >= 0 ? (int)f : (int)f - 1; + } + + template + static int FastRound(FNfloat f) { + return f >= 0 ? (int)(f + 0.5f) : (int)(f - 0.5f); + } + + static float Lerp(float a, float b, float t) { + return a + t * (b - a); + } + + static float InterpHermite(float t) { + return t * t * (3 - 2 * t); + } + + static float InterpQuintic(float t) { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + static float CubicLerp(float a, float b, float c, float d, float t) { + float p = (d - c) - (a - b); + return t * t * t * p + t * t * ((a - b) - p) + t * (c - a) + b; + } + + static float PingPong(float t) { + t -= (int)(t * 0.5f) * 2; + return t < 1 ? t : 2 - t; + } + + void CalculateFractalBounding() { + float gain = FastAbs(mGain); + float amp = gain; + float ampFractal = 1.0f; + for (int i = 1; i < mOctaves; i++) { + ampFractal += amp; + amp *= gain; + } + mFractalBounding = 1 / ampFractal; + } + + // Hashing + static const int PrimeX = 501125321; + static const int PrimeY = 1136930381; + static const int PrimeZ = 1720413743; + + static int Hash(int seed, int xPrimed, int yPrimed) { + int hash = seed ^ xPrimed ^ yPrimed; + + hash *= 0x27d4eb2d; + return hash; + } + + static int Hash(int seed, int xPrimed, int yPrimed, int zPrimed) { + int hash = seed ^ xPrimed ^ yPrimed ^ zPrimed; + + hash *= 0x27d4eb2d; + return hash; + } + + static float ValCoord(int seed, int xPrimed, int yPrimed) { + int hash = Hash(seed, xPrimed, yPrimed); + + hash *= hash; + hash ^= hash << 19; + return hash * (1 / 2147483648.0f); + } + + static float ValCoord(int seed, int xPrimed, int yPrimed, int zPrimed) { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + + hash *= hash; + hash ^= hash << 19; + return hash * (1 / 2147483648.0f); + } + + float GradCoord(int seed, int xPrimed, int yPrimed, float xd, float yd) const { + int hash = Hash(seed, xPrimed, yPrimed); + hash ^= hash >> 15; + hash &= 127 << 1; + + float xg = Lookup::Gradients2D[hash]; + float yg = Lookup::Gradients2D[hash | 1]; + + return xd * xg + yd * yg; + } + + float GradCoord(int seed, int xPrimed, int yPrimed, int zPrimed, float xd, float yd, float zd) const { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + hash ^= hash >> 15; + hash &= 63 << 2; + + float xg = Lookup::Gradients3D[hash]; + float yg = Lookup::Gradients3D[hash | 1]; + float zg = Lookup::Gradients3D[hash | 2]; + + return xd * xg + yd * yg + zd * zg; + } + + void GradCoordOut(int seed, int xPrimed, int yPrimed, float& xo, float& yo) const { + int hash = Hash(seed, xPrimed, yPrimed) & (255 << 1); + + xo = Lookup::RandVecs2D[hash]; + yo = Lookup::RandVecs2D[hash | 1]; + } + + void GradCoordOut(int seed, int xPrimed, int yPrimed, int zPrimed, float& xo, float& yo, float& zo) const { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed) & (255 << 2); + + xo = Lookup::RandVecs3D[hash]; + yo = Lookup::RandVecs3D[hash | 1]; + zo = Lookup::RandVecs3D[hash | 2]; + } + + void GradCoordDual(int seed, int xPrimed, int yPrimed, float xd, float yd, float& xo, float& yo) const { + int hash = Hash(seed, xPrimed, yPrimed); + int index1 = hash & (127 << 1); + int index2 = (hash >> 7) & (255 << 1); + + float xg = Lookup::Gradients2D[index1]; + float yg = Lookup::Gradients2D[index1 | 1]; + float value = xd * xg + yd * yg; + + float xgo = Lookup::RandVecs2D[index2]; + float ygo = Lookup::RandVecs2D[index2 | 1]; + + xo = value * xgo; + yo = value * ygo; + } + + void GradCoordDual(int seed, int xPrimed, int yPrimed, int zPrimed, float xd, float yd, float zd, float& xo, float& yo, float& zo) const { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int index1 = hash & (63 << 2); + int index2 = (hash >> 6) & (255 << 2); + + float xg = Lookup::Gradients3D[index1]; + float yg = Lookup::Gradients3D[index1 | 1]; + float zg = Lookup::Gradients3D[index1 | 2]; + float value = xd * xg + yd * yg + zd * zg; + + float xgo = Lookup::RandVecs3D[index2]; + float ygo = Lookup::RandVecs3D[index2 | 1]; + float zgo = Lookup::RandVecs3D[index2 | 2]; + + xo = value * xgo; + yo = value * ygo; + zo = value * zgo; + } + + // Generic noise gen + + template + float GenNoiseSingle(int seed, FNfloat x, FNfloat y) const { + switch (mNoiseType) { + case NoiseType_OpenSimplex2: + return SingleSimplex(seed, x, y); + case NoiseType_OpenSimplex2S: + return SingleOpenSimplex2S(seed, x, y); + case NoiseType_Cellular: + return SingleCellular(seed, x, y); + case NoiseType_Perlin: + return SinglePerlin(seed, x, y); + case NoiseType_ValueCubic: + return SingleValueCubic(seed, x, y); + case NoiseType_Value: + return SingleValue(seed, x, y); + default: + return 0; + } + } + + template + float GenNoiseSingle(int seed, FNfloat x, FNfloat y, FNfloat z) const { + switch (mNoiseType) { + case NoiseType_OpenSimplex2: + return SingleOpenSimplex2(seed, x, y, z); + case NoiseType_OpenSimplex2S: + return SingleOpenSimplex2S(seed, x, y, z); + case NoiseType_Cellular: + return SingleCellular(seed, x, y, z); + case NoiseType_Perlin: + return SinglePerlin(seed, x, y, z); + case NoiseType_ValueCubic: + return SingleValueCubic(seed, x, y, z); + case NoiseType_Value: + return SingleValue(seed, x, y, z); + default: + return 0; + } + } + + // Noise Coordinate Transforms (frequency, and possible skew or rotation) + + template + void TransformNoiseCoordinate(FNfloat& x, FNfloat& y) const { + x *= mFrequency; + y *= mFrequency; + + switch (mNoiseType) { + case NoiseType_OpenSimplex2: + case NoiseType_OpenSimplex2S: { + const FNfloat SQRT3 = (FNfloat)1.7320508075688772935274463415059; + const FNfloat F2 = 0.5f * (SQRT3 - 1); + FNfloat t = (x + y) * F2; + x += t; + y += t; + } break; + default: + break; + } + } + + template + void TransformNoiseCoordinate(FNfloat& x, FNfloat& y, FNfloat& z) const { + x *= mFrequency; + y *= mFrequency; + z *= mFrequency; + + switch (mTransformType3D) { + case TransformType3D_ImproveXYPlanes: { + FNfloat xy = x + y; + FNfloat s2 = xy * -(FNfloat)0.211324865405187; + z *= (FNfloat)0.577350269189626; + x += s2 - z; + y = y + s2 - z; + z += xy * (FNfloat)0.577350269189626; + } break; + case TransformType3D_ImproveXZPlanes: { + FNfloat xz = x + z; + FNfloat s2 = xz * -(FNfloat)0.211324865405187; + y *= (FNfloat)0.577350269189626; + x += s2 - y; + z += s2 - y; + y += xz * (FNfloat)0.577350269189626; + } break; + case TransformType3D_DefaultOpenSimplex2: { + const FNfloat R3 = (FNfloat)(2.0 / 3.0); + FNfloat r = (x + y + z) * R3; // Rotation, not skew + x = r - x; + y = r - y; + z = r - z; + } break; + default: + break; + } + } + + void UpdateTransformType3D() { + switch (mRotationType3D) { + case RotationType3D_ImproveXYPlanes: + mTransformType3D = TransformType3D_ImproveXYPlanes; + break; + case RotationType3D_ImproveXZPlanes: + mTransformType3D = TransformType3D_ImproveXZPlanes; + break; + default: + switch (mNoiseType) { + case NoiseType_OpenSimplex2: + case NoiseType_OpenSimplex2S: + mTransformType3D = TransformType3D_DefaultOpenSimplex2; + break; + default: + mTransformType3D = TransformType3D_None; + break; + } + break; + } + } + + // Domain Warp Coordinate Transforms + + template + void TransformDomainWarpCoordinate(FNfloat& x, FNfloat& y) const { + switch (mDomainWarpType) { + case DomainWarpType_OpenSimplex2: + case DomainWarpType_OpenSimplex2Reduced: { + const FNfloat SQRT3 = (FNfloat)1.7320508075688772935274463415059; + const FNfloat F2 = 0.5f * (SQRT3 - 1); + FNfloat t = (x + y) * F2; + x += t; + y += t; + } break; + default: + break; + } + } + + template + void TransformDomainWarpCoordinate(FNfloat& x, FNfloat& y, FNfloat& z) const { + switch (mWarpTransformType3D) { + case TransformType3D_ImproveXYPlanes: { + FNfloat xy = x + y; + FNfloat s2 = xy * -(FNfloat)0.211324865405187; + z *= (FNfloat)0.577350269189626; + x += s2 - z; + y = y + s2 - z; + z += xy * (FNfloat)0.577350269189626; + } break; + case TransformType3D_ImproveXZPlanes: { + FNfloat xz = x + z; + FNfloat s2 = xz * -(FNfloat)0.211324865405187; + y *= (FNfloat)0.577350269189626; + x += s2 - y; + z += s2 - y; + y += xz * (FNfloat)0.577350269189626; + } break; + case TransformType3D_DefaultOpenSimplex2: { + const FNfloat R3 = (FNfloat)(2.0 / 3.0); + FNfloat r = (x + y + z) * R3; // Rotation, not skew + x = r - x; + y = r - y; + z = r - z; + } break; + default: + break; + } + } + + void UpdateWarpTransformType3D() { + switch (mRotationType3D) { + case RotationType3D_ImproveXYPlanes: + mWarpTransformType3D = TransformType3D_ImproveXYPlanes; + break; + case RotationType3D_ImproveXZPlanes: + mWarpTransformType3D = TransformType3D_ImproveXZPlanes; + break; + default: + switch (mDomainWarpType) { + case DomainWarpType_OpenSimplex2: + case DomainWarpType_OpenSimplex2Reduced: + mWarpTransformType3D = TransformType3D_DefaultOpenSimplex2; + break; + default: + mWarpTransformType3D = TransformType3D_None; + break; + } + break; + } + } + + // Fractal FBm + + template + float GenFractalFBm(FNfloat x, FNfloat y) const { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) { + float noise = GenNoiseSingle(seed++, x, y); + sum += noise * amp; + amp *= Lerp(1.0f, FastMin(noise + 1, 2) * 0.5f, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + template + float GenFractalFBm(FNfloat x, FNfloat y, FNfloat z) const { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) { + float noise = GenNoiseSingle(seed++, x, y, z); + sum += noise * amp; + amp *= Lerp(1.0f, (noise + 1) * 0.5f, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + z *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + // Fractal Ridged + + template + float GenFractalRidged(FNfloat x, FNfloat y) const { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) { + float noise = FastAbs(GenNoiseSingle(seed++, x, y)); + sum += (noise * -2 + 1) * amp; + amp *= Lerp(1.0f, 1 - noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + template + float GenFractalRidged(FNfloat x, FNfloat y, FNfloat z) const { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) { + float noise = FastAbs(GenNoiseSingle(seed++, x, y, z)); + sum += (noise * -2 + 1) * amp; + amp *= Lerp(1.0f, 1 - noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + z *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + // Fractal PingPong + + template + float GenFractalPingPong(FNfloat x, FNfloat y) const { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) { + float noise = PingPong((GenNoiseSingle(seed++, x, y) + 1) * mPingPongStrength); + sum += (noise - 0.5f) * 2 * amp; + amp *= Lerp(1.0f, noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + template + float GenFractalPingPong(FNfloat x, FNfloat y, FNfloat z) const { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) { + float noise = PingPong((GenNoiseSingle(seed++, x, y, z) + 1) * mPingPongStrength); + sum += (noise - 0.5f) * 2 * amp; + amp *= Lerp(1.0f, noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + z *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + // Simplex/OpenSimplex2 Noise + + template + float SingleSimplex(int seed, FNfloat x, FNfloat y) const { + // 2D OpenSimplex2 case uses the same algorithm as ordinary Simplex. + + const float SQRT3 = 1.7320508075688772935274463415059f; + const float G2 = (3 - SQRT3) / 6; + + /* + * --- Skew moved to TransformNoiseCoordinate method --- + * const FNfloat F2 = 0.5f * (SQRT3 - 1); + * FNfloat s = (x + y) * F2; + * x += s; y += s; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + float xi = (float)(x - i); + float yi = (float)(y - j); + + float t = (xi + yi) * G2; + float x0 = (float)(xi - t); + float y0 = (float)(yi - t); + + i *= PrimeX; + j *= PrimeY; + + float n0, n1, n2; + + float a = 0.5f - x0 * x0 - y0 * y0; + if (a <= 0) { + n0 = 0; + } else { + n0 = (a * a) * (a * a) * GradCoord(seed, i, j, x0, y0); + } + + float c = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a); + if (c <= 0) { + n2 = 0; + } else { + float x2 = x0 + (2 * (float)G2 - 1); + float y2 = y0 + (2 * (float)G2 - 1); + n2 = (c * c) * (c * c) * GradCoord(seed, i + PrimeX, j + PrimeY, x2, y2); + } + + if (y0 > x0) { + float x1 = x0 + (float)G2; + float y1 = y0 + ((float)G2 - 1); + float b = 0.5f - x1 * x1 - y1 * y1; + if (b <= 0) { + n1 = 0; + } else { + n1 = (b * b) * (b * b) * GradCoord(seed, i, j + PrimeY, x1, y1); + } + } else { + float x1 = x0 + ((float)G2 - 1); + float y1 = y0 + (float)G2; + float b = 0.5f - x1 * x1 - y1 * y1; + if (b <= 0) { + n1 = 0; + } else { + n1 = (b * b) * (b * b) * GradCoord(seed, i + PrimeX, j, x1, y1); + } + } + + return (n0 + n1 + n2) * 99.83685446303647f; + } + + template + float SingleOpenSimplex2(int seed, FNfloat x, FNfloat y, FNfloat z) const { + // 3D OpenSimplex2 case uses two offset rotated cube grids. + + /* + * --- Rotation moved to TransformNoiseCoordinate method --- + * const FNfloat R3 = (FNfloat)(2.0 / 3.0); + * FNfloat r = (x + y + z) * R3; // Rotation, not skew + * x = r - x; y = r - y; z = r - z; + */ + + int i = FastRound(x); + int j = FastRound(y); + int k = FastRound(z); + float x0 = (float)(x - i); + float y0 = (float)(y - j); + float z0 = (float)(z - k); + + int xNSign = (int)(-1.0f - x0) | 1; + int yNSign = (int)(-1.0f - y0) | 1; + int zNSign = (int)(-1.0f - z0) | 1; + + float ax0 = xNSign * -x0; + float ay0 = yNSign * -y0; + float az0 = zNSign * -z0; + + i *= PrimeX; + j *= PrimeY; + k *= PrimeZ; + + float value = 0; + float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0); + + for (int l = 0;; l++) { + if (a > 0) { + value += (a * a) * (a * a) * GradCoord(seed, i, j, k, x0, y0, z0); + } + + float b = a + 1; + int i1 = i; + int j1 = j; + int k1 = k; + float x1 = x0; + float y1 = y0; + float z1 = z0; + + if (ax0 >= ay0 && ax0 >= az0) { + x1 += xNSign; + b -= xNSign * 2 * x1; + i1 -= xNSign * PrimeX; + } else if (ay0 > ax0 && ay0 >= az0) { + y1 += yNSign; + b -= yNSign * 2 * y1; + j1 -= yNSign * PrimeY; + } else { + z1 += zNSign; + b -= zNSign * 2 * z1; + k1 -= zNSign * PrimeZ; + } + + if (b > 0) { + value += (b * b) * (b * b) * GradCoord(seed, i1, j1, k1, x1, y1, z1); + } + + if (l == 1) { + break; + } + + ax0 = 0.5f - ax0; + ay0 = 0.5f - ay0; + az0 = 0.5f - az0; + + x0 = xNSign * ax0; + y0 = yNSign * ay0; + z0 = zNSign * az0; + + a += (0.75f - ax0) - (ay0 + az0); + + i += (xNSign >> 1) & PrimeX; + j += (yNSign >> 1) & PrimeY; + k += (zNSign >> 1) & PrimeZ; + + xNSign = -xNSign; + yNSign = -yNSign; + zNSign = -zNSign; + + seed = ~seed; + } + + return value * 32.69428253173828125f; + } + + // OpenSimplex2S Noise + + template + float SingleOpenSimplex2S(int seed, FNfloat x, FNfloat y) const { + // 2D OpenSimplex2S case is a modified 2D simplex noise. + + const FNfloat SQRT3 = (FNfloat)1.7320508075688772935274463415059; + const FNfloat G2 = (3 - SQRT3) / 6; + + /* + * --- Skew moved to TransformNoiseCoordinate method --- + * const FNfloat F2 = 0.5f * (SQRT3 - 1); + * FNfloat s = (x + y) * F2; + * x += s; y += s; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + float xi = (float)(x - i); + float yi = (float)(y - j); + + i *= PrimeX; + j *= PrimeY; + int i1 = i + PrimeX; + int j1 = j + PrimeY; + + float t = (xi + yi) * (float)G2; + float x0 = xi - t; + float y0 = yi - t; + + float a0 = (2.0f / 3.0f) - x0 * x0 - y0 * y0; + float value = (a0 * a0) * (a0 * a0) * GradCoord(seed, i, j, x0, y0); + + float a1 = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a0); + float x1 = x0 - (float)(1 - 2 * G2); + float y1 = y0 - (float)(1 - 2 * G2); + value += (a1 * a1) * (a1 * a1) * GradCoord(seed, i1, j1, x1, y1); + + // Nested conditionals were faster than compact bit logic/arithmetic. + float xmyi = xi - yi; + if (t > G2) { + if (xi + xmyi > 1) { + float x2 = x0 + (float)(3 * G2 - 2); + float y2 = y0 + (float)(3 * G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i + (PrimeX << 1), j + PrimeY, x2, y2); + } + } else { + float x2 = x0 + (float)G2; + float y2 = y0 + (float)(G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j + PrimeY, x2, y2); + } + } + + if (yi - xmyi > 1) { + float x3 = x0 + (float)(3 * G2 - 1); + float y3 = y0 + (float)(3 * G2 - 2); + float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3; + if (a3 > 0) { + value += (a3 * a3) * (a3 * a3) * GradCoord(seed, i + PrimeX, j + (PrimeY << 1), x3, y3); + } + } else { + float x3 = x0 + (float)(G2 - 1); + float y3 = y0 + (float)G2; + float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3; + if (a3 > 0) { + value += (a3 * a3) * (a3 * a3) * GradCoord(seed, i + PrimeX, j, x3, y3); + } + } + } else { + if (xi + xmyi < 0) { + float x2 = x0 + (float)(1 - G2); + float y2 = y0 - (float)G2; + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i - PrimeX, j, x2, y2); + } + } else { + float x2 = x0 + (float)(G2 - 1); + float y2 = y0 + (float)G2; + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i + PrimeX, j, x2, y2); + } + } + + if (yi < xmyi) { + float x2 = x0 - (float)G2; + float y2 = y0 - (float)(G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j - PrimeY, x2, y2); + } + } else { + float x2 = x0 + (float)G2; + float y2 = y0 + (float)(G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j + PrimeY, x2, y2); + } + } + } + + return value * 18.24196194486065f; + } + + template + float SingleOpenSimplex2S(int seed, FNfloat x, FNfloat y, FNfloat z) const { + // 3D OpenSimplex2S case uses two offset rotated cube grids. + + /* + * --- Rotation moved to TransformNoiseCoordinate method --- + * const FNfloat R3 = (FNfloat)(2.0 / 3.0); + * FNfloat r = (x + y + z) * R3; // Rotation, not skew + * x = r - x; y = r - y; z = r - z; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + int k = FastFloor(z); + float xi = (float)(x - i); + float yi = (float)(y - j); + float zi = (float)(z - k); + + i *= PrimeX; + j *= PrimeY; + k *= PrimeZ; + int seed2 = seed + 1293373; + + int xNMask = (int)(-0.5f - xi); + int yNMask = (int)(-0.5f - yi); + int zNMask = (int)(-0.5f - zi); + + float x0 = xi + xNMask; + float y0 = yi + yNMask; + float z0 = zi + zNMask; + float a0 = 0.75f - x0 * x0 - y0 * y0 - z0 * z0; + float value = (a0 * a0) * (a0 * a0) * GradCoord(seed, i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x0, y0, z0); + + float x1 = xi - 0.5f; + float y1 = yi - 0.5f; + float z1 = zi - 0.5f; + float a1 = 0.75f - x1 * x1 - y1 * y1 - z1 * z1; + value += (a1 * a1) * (a1 * a1) * GradCoord(seed2, i + PrimeX, j + PrimeY, k + PrimeZ, x1, y1, z1); + + float xAFlipMask0 = ((xNMask | 1) << 1) * x1; + float yAFlipMask0 = ((yNMask | 1) << 1) * y1; + float zAFlipMask0 = ((zNMask | 1) << 1) * z1; + float xAFlipMask1 = (-2 - (xNMask << 2)) * x1 - 1.0f; + float yAFlipMask1 = (-2 - (yNMask << 2)) * y1 - 1.0f; + float zAFlipMask1 = (-2 - (zNMask << 2)) * z1 - 1.0f; + + bool skip5 = false; + float a2 = xAFlipMask0 + a0; + if (a2 > 0) { + float x2 = x0 - (xNMask | 1); + float y2 = y0; + float z2 = z0; + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x2, y2, z2); + } else { + float a3 = yAFlipMask0 + zAFlipMask0 + a0; + if (a3 > 0) { + float x3 = x0; + float y3 = y0 - (yNMask | 1); + float z3 = z0 - (zNMask | 1); + value += (a3 * a3) * (a3 * a3) * GradCoord(seed, i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (~zNMask & PrimeZ), x3, y3, z3); + } + + float a4 = xAFlipMask1 + a1; + if (a4 > 0) { + float x4 = (xNMask | 1) + x1; + float y4 = y1; + float z4 = z1; + value += (a4 * a4) * (a4 * a4) * GradCoord(seed2, i + (xNMask & (PrimeX * 2)), j + PrimeY, k + PrimeZ, x4, y4, z4); + skip5 = true; + } + } + + bool skip9 = false; + float a6 = yAFlipMask0 + a0; + if (a6 > 0) { + float x6 = x0; + float y6 = y0 - (yNMask | 1); + float z6 = z0; + value += (a6 * a6) * (a6 * a6) * GradCoord(seed, i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), x6, y6, z6); + } else { + float a7 = xAFlipMask0 + zAFlipMask0 + a0; + if (a7 > 0) { + float x7 = x0 - (xNMask | 1); + float y7 = y0; + float z7 = z0 - (zNMask | 1); + value += (a7 * a7) * (a7 * a7) * GradCoord(seed, i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), x7, y7, z7); + } + + float a8 = yAFlipMask1 + a1; + if (a8 > 0) { + float x8 = x1; + float y8 = (yNMask | 1) + y1; + float z8 = z1; + value += (a8 * a8) * (a8 * a8) * GradCoord(seed2, i + PrimeX, j + (yNMask & (PrimeY << 1)), k + PrimeZ, x8, y8, z8); + skip9 = true; + } + } + + bool skipD = false; + float aA = zAFlipMask0 + a0; + if (aA > 0) { + float xA = x0; + float yA = y0; + float zA = z0 - (zNMask | 1); + value += (aA * aA) * (aA * aA) * GradCoord(seed, i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), xA, yA, zA); + } else { + float aB = xAFlipMask0 + yAFlipMask0 + a0; + if (aB > 0) { + float xB = x0 - (xNMask | 1); + float yB = y0 - (yNMask | 1); + float zB = z0; + value += (aB * aB) * (aB * aB) * GradCoord(seed, i + (~xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), xB, yB, zB); + } + + float aC = zAFlipMask1 + a1; + if (aC > 0) { + float xC = x1; + float yC = y1; + float zC = (zNMask | 1) + z1; + value += (aC * aC) * (aC * aC) * GradCoord(seed2, i + PrimeX, j + PrimeY, k + (zNMask & (PrimeZ << 1)), xC, yC, zC); + skipD = true; + } + } + + if (!skip5) { + float a5 = yAFlipMask1 + zAFlipMask1 + a1; + if (a5 > 0) { + float x5 = x1; + float y5 = (yNMask | 1) + y1; + float z5 = (zNMask | 1) + z1; + value += (a5 * a5) * (a5 * a5) * GradCoord(seed2, i + PrimeX, j + (yNMask & (PrimeY << 1)), k + (zNMask & (PrimeZ << 1)), x5, y5, z5); + } + } + + if (!skip9) { + float a9 = xAFlipMask1 + zAFlipMask1 + a1; + if (a9 > 0) { + float x9 = (xNMask | 1) + x1; + float y9 = y1; + float z9 = (zNMask | 1) + z1; + value += (a9 * a9) * (a9 * a9) * GradCoord(seed2, i + (xNMask & (PrimeX * 2)), j + PrimeY, k + (zNMask & (PrimeZ << 1)), x9, y9, z9); + } + } + + if (!skipD) { + float aD = xAFlipMask1 + yAFlipMask1 + a1; + if (aD > 0) { + float xD = (xNMask | 1) + x1; + float yD = (yNMask | 1) + y1; + float zD = z1; + value += (aD * aD) * (aD * aD) * GradCoord(seed2, i + (xNMask & (PrimeX << 1)), j + (yNMask & (PrimeY << 1)), k + PrimeZ, xD, yD, zD); + } + } + + return value * 9.046026385208288f; + } + + // Cellular Noise + + template + float SingleCellular(int seed, FNfloat x, FNfloat y) const { + int xr = FastRound(x); + int yr = FastRound(y); + + float distance0 = 1e10f; + float distance1 = 1e10f; + int closestHash = 0; + + float cellularJitter = 0.43701595f * mCellularJitterModifier; + + int xPrimed = (xr - 1) * PrimeX; + int yPrimedBase = (yr - 1) * PrimeY; + + switch (mCellularDistanceFunction) { + default: + case CellularDistanceFunction_Euclidean: + case CellularDistanceFunction_EuclideanSq: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int hash = Hash(seed, xPrimed, yPrimed); + int idx = hash & (255 << 1); + + float vecX = (float)(xi - x) + Lookup::RandVecs2D[idx] * cellularJitter; + float vecY = (float)(yi - y) + Lookup::RandVecs2D[idx | 1] * cellularJitter; + + float newDistance = vecX * vecX + vecY * vecY; + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case CellularDistanceFunction_Manhattan: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int hash = Hash(seed, xPrimed, yPrimed); + int idx = hash & (255 << 1); + + float vecX = (float)(xi - x) + Lookup::RandVecs2D[idx] * cellularJitter; + float vecY = (float)(yi - y) + Lookup::RandVecs2D[idx | 1] * cellularJitter; + + float newDistance = FastAbs(vecX) + FastAbs(vecY); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case CellularDistanceFunction_Hybrid: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int hash = Hash(seed, xPrimed, yPrimed); + int idx = hash & (255 << 1); + + float vecX = (float)(xi - x) + Lookup::RandVecs2D[idx] * cellularJitter; + float vecY = (float)(yi - y) + Lookup::RandVecs2D[idx | 1] * cellularJitter; + + float newDistance = (FastAbs(vecX) + FastAbs(vecY)) + (vecX * vecX + vecY * vecY); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + } + + if (mCellularDistanceFunction == CellularDistanceFunction_Euclidean && mCellularReturnType >= CellularReturnType_Distance) { + distance0 = FastSqrt(distance0); + + if (mCellularReturnType >= CellularReturnType_Distance2) { + distance1 = FastSqrt(distance1); + } + } + + switch (mCellularReturnType) { + case CellularReturnType_CellValue: + return closestHash * (1 / 2147483648.0f); + case CellularReturnType_Distance: + return distance0 - 1; + case CellularReturnType_Distance2: + return distance1 - 1; + case CellularReturnType_Distance2Add: + return (distance1 + distance0) * 0.5f - 1; + case CellularReturnType_Distance2Sub: + return distance1 - distance0 - 1; + case CellularReturnType_Distance2Mul: + return distance1 * distance0 * 0.5f - 1; + case CellularReturnType_Distance2Div: + return distance0 / distance1 - 1; + default: + return 0; + } + } + + template + float SingleCellular(int seed, FNfloat x, FNfloat y, FNfloat z) const { + int xr = FastRound(x); + int yr = FastRound(y); + int zr = FastRound(z); + + float distance0 = 1e10f; + float distance1 = 1e10f; + int closestHash = 0; + + float cellularJitter = 0.39614353f * mCellularJitterModifier; + + int xPrimed = (xr - 1) * PrimeX; + int yPrimedBase = (yr - 1) * PrimeY; + int zPrimedBase = (zr - 1) * PrimeZ; + + switch (mCellularDistanceFunction) { + case CellularDistanceFunction_Euclidean: + case CellularDistanceFunction_EuclideanSq: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & (255 << 2); + + float vecX = (float)(xi - x) + Lookup::RandVecs3D[idx] * cellularJitter; + float vecY = (float)(yi - y) + Lookup::RandVecs3D[idx | 1] * cellularJitter; + float vecZ = (float)(zi - z) + Lookup::RandVecs3D[idx | 2] * cellularJitter; + + float newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ; + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + zPrimed += PrimeZ; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case CellularDistanceFunction_Manhattan: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & (255 << 2); + + float vecX = (float)(xi - x) + Lookup::RandVecs3D[idx] * cellularJitter; + float vecY = (float)(yi - y) + Lookup::RandVecs3D[idx | 1] * cellularJitter; + float vecZ = (float)(zi - z) + Lookup::RandVecs3D[idx | 2] * cellularJitter; + + float newDistance = FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + zPrimed += PrimeZ; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case CellularDistanceFunction_Hybrid: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & (255 << 2); + + float vecX = (float)(xi - x) + Lookup::RandVecs3D[idx] * cellularJitter; + float vecY = (float)(yi - y) + Lookup::RandVecs3D[idx | 1] * cellularJitter; + float vecZ = (float)(zi - z) + Lookup::RandVecs3D[idx | 2] * cellularJitter; + + float newDistance = (FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ)) + (vecX * vecX + vecY * vecY + vecZ * vecZ); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + zPrimed += PrimeZ; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + default: + break; + } + + if (mCellularDistanceFunction == CellularDistanceFunction_Euclidean && mCellularReturnType >= CellularReturnType_Distance) { + distance0 = FastSqrt(distance0); + + if (mCellularReturnType >= CellularReturnType_Distance2) { + distance1 = FastSqrt(distance1); + } + } + + switch (mCellularReturnType) { + case CellularReturnType_CellValue: + return closestHash * (1 / 2147483648.0f); + case CellularReturnType_Distance: + return distance0 - 1; + case CellularReturnType_Distance2: + return distance1 - 1; + case CellularReturnType_Distance2Add: + return (distance1 + distance0) * 0.5f - 1; + case CellularReturnType_Distance2Sub: + return distance1 - distance0 - 1; + case CellularReturnType_Distance2Mul: + return distance1 * distance0 * 0.5f - 1; + case CellularReturnType_Distance2Div: + return distance0 / distance1 - 1; + default: + return 0; + } + } + + // Perlin Noise + + template + float SinglePerlin(int seed, FNfloat x, FNfloat y) const { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + + float xd0 = (float)(x - x0); + float yd0 = (float)(y - y0); + float xd1 = xd0 - 1; + float yd1 = yd0 - 1; + + float xs = InterpQuintic(xd0); + float ys = InterpQuintic(yd0); + + x0 *= PrimeX; + y0 *= PrimeY; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + + float xf0 = Lerp(GradCoord(seed, x0, y0, xd0, yd0), GradCoord(seed, x1, y0, xd1, yd0), xs); + float xf1 = Lerp(GradCoord(seed, x0, y1, xd0, yd1), GradCoord(seed, x1, y1, xd1, yd1), xs); + + return Lerp(xf0, xf1, ys) * 1.4247691104677813f; + } + + template + float SinglePerlin(int seed, FNfloat x, FNfloat y, FNfloat z) const { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + int z0 = FastFloor(z); + + float xd0 = (float)(x - x0); + float yd0 = (float)(y - y0); + float zd0 = (float)(z - z0); + float xd1 = xd0 - 1; + float yd1 = yd0 - 1; + float zd1 = zd0 - 1; + + float xs = InterpQuintic(xd0); + float ys = InterpQuintic(yd0); + float zs = InterpQuintic(zd0); + + x0 *= PrimeX; + y0 *= PrimeY; + z0 *= PrimeZ; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + int z1 = z0 + PrimeZ; + + float xf00 = Lerp(GradCoord(seed, x0, y0, z0, xd0, yd0, zd0), GradCoord(seed, x1, y0, z0, xd1, yd0, zd0), xs); + float xf10 = Lerp(GradCoord(seed, x0, y1, z0, xd0, yd1, zd0), GradCoord(seed, x1, y1, z0, xd1, yd1, zd0), xs); + float xf01 = Lerp(GradCoord(seed, x0, y0, z1, xd0, yd0, zd1), GradCoord(seed, x1, y0, z1, xd1, yd0, zd1), xs); + float xf11 = Lerp(GradCoord(seed, x0, y1, z1, xd0, yd1, zd1), GradCoord(seed, x1, y1, z1, xd1, yd1, zd1), xs); + + float yf0 = Lerp(xf00, xf10, ys); + float yf1 = Lerp(xf01, xf11, ys); + + return Lerp(yf0, yf1, zs) * 0.964921414852142333984375f; + } + + // Value Cubic Noise + + template + float SingleValueCubic(int seed, FNfloat x, FNfloat y) const { + int x1 = FastFloor(x); + int y1 = FastFloor(y); + + float xs = (float)(x - x1); + float ys = (float)(y - y1); + + x1 *= PrimeX; + y1 *= PrimeY; + int x0 = x1 - PrimeX; + int y0 = y1 - PrimeY; + int x2 = x1 + PrimeX; + int y2 = y1 + PrimeY; + int x3 = x1 + (int)((long)PrimeX << 1); + int y3 = y1 + (int)((long)PrimeY << 1); + + return CubicLerp( + CubicLerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), ValCoord(seed, x2, y0), ValCoord(seed, x3, y0), xs), + CubicLerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), ValCoord(seed, x2, y1), ValCoord(seed, x3, y1), xs), + CubicLerp(ValCoord(seed, x0, y2), ValCoord(seed, x1, y2), ValCoord(seed, x2, y2), ValCoord(seed, x3, y2), xs), + CubicLerp(ValCoord(seed, x0, y3), ValCoord(seed, x1, y3), ValCoord(seed, x2, y3), ValCoord(seed, x3, y3), xs), + ys + ) + * (1 / (1.5f * 1.5f)); + } + + template + float SingleValueCubic(int seed, FNfloat x, FNfloat y, FNfloat z) const { + int x1 = FastFloor(x); + int y1 = FastFloor(y); + int z1 = FastFloor(z); + + float xs = (float)(x - x1); + float ys = (float)(y - y1); + float zs = (float)(z - z1); + + x1 *= PrimeX; + y1 *= PrimeY; + z1 *= PrimeZ; + + int x0 = x1 - PrimeX; + int y0 = y1 - PrimeY; + int z0 = z1 - PrimeZ; + int x2 = x1 + PrimeX; + int y2 = y1 + PrimeY; + int z2 = z1 + PrimeZ; + int x3 = x1 + (int)((long)PrimeX << 1); + int y3 = y1 + (int)((long)PrimeY << 1); + int z3 = z1 + (int)((long)PrimeZ << 1); + + return CubicLerp( + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), ValCoord(seed, x2, y0, z0), ValCoord(seed, x3, y0, z0), xs), + CubicLerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), ValCoord(seed, x2, y1, z0), ValCoord(seed, x3, y1, z0), xs), + CubicLerp(ValCoord(seed, x0, y2, z0), ValCoord(seed, x1, y2, z0), ValCoord(seed, x2, y2, z0), ValCoord(seed, x3, y2, z0), xs), + CubicLerp(ValCoord(seed, x0, y3, z0), ValCoord(seed, x1, y3, z0), ValCoord(seed, x2, y3, z0), ValCoord(seed, x3, y3, z0), xs), + ys + ), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), ValCoord(seed, x2, y0, z1), ValCoord(seed, x3, y0, z1), xs), + CubicLerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), ValCoord(seed, x2, y1, z1), ValCoord(seed, x3, y1, z1), xs), + CubicLerp(ValCoord(seed, x0, y2, z1), ValCoord(seed, x1, y2, z1), ValCoord(seed, x2, y2, z1), ValCoord(seed, x3, y2, z1), xs), + CubicLerp(ValCoord(seed, x0, y3, z1), ValCoord(seed, x1, y3, z1), ValCoord(seed, x2, y3, z1), ValCoord(seed, x3, y3, z1), xs), + ys + ), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z2), ValCoord(seed, x1, y0, z2), ValCoord(seed, x2, y0, z2), ValCoord(seed, x3, y0, z2), xs), + CubicLerp(ValCoord(seed, x0, y1, z2), ValCoord(seed, x1, y1, z2), ValCoord(seed, x2, y1, z2), ValCoord(seed, x3, y1, z2), xs), + CubicLerp(ValCoord(seed, x0, y2, z2), ValCoord(seed, x1, y2, z2), ValCoord(seed, x2, y2, z2), ValCoord(seed, x3, y2, z2), xs), + CubicLerp(ValCoord(seed, x0, y3, z2), ValCoord(seed, x1, y3, z2), ValCoord(seed, x2, y3, z2), ValCoord(seed, x3, y3, z2), xs), + ys + ), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z3), ValCoord(seed, x1, y0, z3), ValCoord(seed, x2, y0, z3), ValCoord(seed, x3, y0, z3), xs), + CubicLerp(ValCoord(seed, x0, y1, z3), ValCoord(seed, x1, y1, z3), ValCoord(seed, x2, y1, z3), ValCoord(seed, x3, y1, z3), xs), + CubicLerp(ValCoord(seed, x0, y2, z3), ValCoord(seed, x1, y2, z3), ValCoord(seed, x2, y2, z3), ValCoord(seed, x3, y2, z3), xs), + CubicLerp(ValCoord(seed, x0, y3, z3), ValCoord(seed, x1, y3, z3), ValCoord(seed, x2, y3, z3), ValCoord(seed, x3, y3, z3), xs), + ys + ), + zs + ) + * (1 / (1.5f * 1.5f * 1.5f)); + } + + // Value Noise + + template + float SingleValue(int seed, FNfloat x, FNfloat y) const { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + + float xs = InterpHermite((float)(x - x0)); + float ys = InterpHermite((float)(y - y0)); + + x0 *= PrimeX; + y0 *= PrimeY; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + + float xf0 = Lerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), xs); + float xf1 = Lerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), xs); + + return Lerp(xf0, xf1, ys); + } + + template + float SingleValue(int seed, FNfloat x, FNfloat y, FNfloat z) const { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + int z0 = FastFloor(z); + + float xs = InterpHermite((float)(x - x0)); + float ys = InterpHermite((float)(y - y0)); + float zs = InterpHermite((float)(z - z0)); + + x0 *= PrimeX; + y0 *= PrimeY; + z0 *= PrimeZ; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + int z1 = z0 + PrimeZ; + + float xf00 = Lerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), xs); + float xf10 = Lerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), xs); + float xf01 = Lerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), xs); + float xf11 = Lerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), xs); + + float yf0 = Lerp(xf00, xf10, ys); + float yf1 = Lerp(xf01, xf11, ys); + + return Lerp(yf0, yf1, zs); + } + + // Domain Warp + + template + void DoSingleDomainWarp(int seed, float amp, float freq, FNfloat x, FNfloat y, FNfloat& xr, FNfloat& yr) const { + switch (mDomainWarpType) { + case DomainWarpType_OpenSimplex2: + SingleDomainWarpSimplexGradient(seed, amp * 38.283687591552734375f, freq, x, y, xr, yr, false); + break; + case DomainWarpType_OpenSimplex2Reduced: + SingleDomainWarpSimplexGradient(seed, amp * 16.0f, freq, x, y, xr, yr, true); + break; + case DomainWarpType_BasicGrid: + SingleDomainWarpBasicGrid(seed, amp, freq, x, y, xr, yr); + break; + } + } + + template + void DoSingleDomainWarp(int seed, float amp, float freq, FNfloat x, FNfloat y, FNfloat z, FNfloat& xr, FNfloat& yr, FNfloat& zr) const { + switch (mDomainWarpType) { + case DomainWarpType_OpenSimplex2: + SingleDomainWarpOpenSimplex2Gradient(seed, amp * 32.69428253173828125f, freq, x, y, z, xr, yr, zr, false); + break; + case DomainWarpType_OpenSimplex2Reduced: + SingleDomainWarpOpenSimplex2Gradient(seed, amp * 7.71604938271605f, freq, x, y, z, xr, yr, zr, true); + break; + case DomainWarpType_BasicGrid: + SingleDomainWarpBasicGrid(seed, amp, freq, x, y, z, xr, yr, zr); + break; + } + } + + // Domain Warp Single Wrapper + + template + void DomainWarpSingle(FNfloat& x, FNfloat& y) const { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + FNfloat xs = x; + FNfloat ys = y; + TransformDomainWarpCoordinate(xs, ys); + + DoSingleDomainWarp(seed, amp, freq, xs, ys, x, y); + } + + template + void DomainWarpSingle(FNfloat& x, FNfloat& y, FNfloat& z) const { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + FNfloat xs = x; + FNfloat ys = y; + FNfloat zs = z; + TransformDomainWarpCoordinate(xs, ys, zs); + + DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, x, y, z); + } + + // Domain Warp Fractal Progressive + + template + void DomainWarpFractalProgressive(FNfloat& x, FNfloat& y) const { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) { + FNfloat xs = x; + FNfloat ys = y; + TransformDomainWarpCoordinate(xs, ys); + + DoSingleDomainWarp(seed, amp, freq, xs, ys, x, y); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + template + void DomainWarpFractalProgressive(FNfloat& x, FNfloat& y, FNfloat& z) const { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) { + FNfloat xs = x; + FNfloat ys = y; + FNfloat zs = z; + TransformDomainWarpCoordinate(xs, ys, zs); + + DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, x, y, z); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + // Domain Warp Fractal Independant + + template + void DomainWarpFractalIndependent(FNfloat& x, FNfloat& y) const { + FNfloat xs = x; + FNfloat ys = y; + TransformDomainWarpCoordinate(xs, ys); + + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) { + DoSingleDomainWarp(seed, amp, freq, xs, ys, x, y); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + template + void DomainWarpFractalIndependent(FNfloat& x, FNfloat& y, FNfloat& z) const { + FNfloat xs = x; + FNfloat ys = y; + FNfloat zs = z; + TransformDomainWarpCoordinate(xs, ys, zs); + + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) { + DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, x, y, z); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + // Domain Warp Basic Grid + + template + void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, FNfloat x, FNfloat y, FNfloat& xr, FNfloat& yr) const { + FNfloat xf = x * frequency; + FNfloat yf = y * frequency; + + int x0 = FastFloor(xf); + int y0 = FastFloor(yf); + + float xs = InterpHermite((float)(xf - x0)); + float ys = InterpHermite((float)(yf - y0)); + + x0 *= PrimeX; + y0 *= PrimeY; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + + int hash0 = Hash(seed, x0, y0) & (255 << 1); + int hash1 = Hash(seed, x1, y0) & (255 << 1); + + float lx0x = Lerp(Lookup::RandVecs2D[hash0], Lookup::RandVecs2D[hash1], xs); + float ly0x = Lerp(Lookup::RandVecs2D[hash0 | 1], Lookup::RandVecs2D[hash1 | 1], xs); + + hash0 = Hash(seed, x0, y1) & (255 << 1); + hash1 = Hash(seed, x1, y1) & (255 << 1); + + float lx1x = Lerp(Lookup::RandVecs2D[hash0], Lookup::RandVecs2D[hash1], xs); + float ly1x = Lerp(Lookup::RandVecs2D[hash0 | 1], Lookup::RandVecs2D[hash1 | 1], xs); + + xr += Lerp(lx0x, lx1x, ys) * warpAmp; + yr += Lerp(ly0x, ly1x, ys) * warpAmp; + } + + template + void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, FNfloat x, FNfloat y, FNfloat z, FNfloat& xr, FNfloat& yr, FNfloat& zr) const { + FNfloat xf = x * frequency; + FNfloat yf = y * frequency; + FNfloat zf = z * frequency; + + int x0 = FastFloor(xf); + int y0 = FastFloor(yf); + int z0 = FastFloor(zf); + + float xs = InterpHermite((float)(xf - x0)); + float ys = InterpHermite((float)(yf - y0)); + float zs = InterpHermite((float)(zf - z0)); + + x0 *= PrimeX; + y0 *= PrimeY; + z0 *= PrimeZ; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + int z1 = z0 + PrimeZ; + + int hash0 = Hash(seed, x0, y0, z0) & (255 << 2); + int hash1 = Hash(seed, x1, y0, z0) & (255 << 2); + + float lx0x = Lerp(Lookup::RandVecs3D[hash0], Lookup::RandVecs3D[hash1], xs); + float ly0x = Lerp(Lookup::RandVecs3D[hash0 | 1], Lookup::RandVecs3D[hash1 | 1], xs); + float lz0x = Lerp(Lookup::RandVecs3D[hash0 | 2], Lookup::RandVecs3D[hash1 | 2], xs); + + hash0 = Hash(seed, x0, y1, z0) & (255 << 2); + hash1 = Hash(seed, x1, y1, z0) & (255 << 2); + + float lx1x = Lerp(Lookup::RandVecs3D[hash0], Lookup::RandVecs3D[hash1], xs); + float ly1x = Lerp(Lookup::RandVecs3D[hash0 | 1], Lookup::RandVecs3D[hash1 | 1], xs); + float lz1x = Lerp(Lookup::RandVecs3D[hash0 | 2], Lookup::RandVecs3D[hash1 | 2], xs); + + float lx0y = Lerp(lx0x, lx1x, ys); + float ly0y = Lerp(ly0x, ly1x, ys); + float lz0y = Lerp(lz0x, lz1x, ys); + + hash0 = Hash(seed, x0, y0, z1) & (255 << 2); + hash1 = Hash(seed, x1, y0, z1) & (255 << 2); + + lx0x = Lerp(Lookup::RandVecs3D[hash0], Lookup::RandVecs3D[hash1], xs); + ly0x = Lerp(Lookup::RandVecs3D[hash0 | 1], Lookup::RandVecs3D[hash1 | 1], xs); + lz0x = Lerp(Lookup::RandVecs3D[hash0 | 2], Lookup::RandVecs3D[hash1 | 2], xs); + + hash0 = Hash(seed, x0, y1, z1) & (255 << 2); + hash1 = Hash(seed, x1, y1, z1) & (255 << 2); + + lx1x = Lerp(Lookup::RandVecs3D[hash0], Lookup::RandVecs3D[hash1], xs); + ly1x = Lerp(Lookup::RandVecs3D[hash0 | 1], Lookup::RandVecs3D[hash1 | 1], xs); + lz1x = Lerp(Lookup::RandVecs3D[hash0 | 2], Lookup::RandVecs3D[hash1 | 2], xs); + + xr += Lerp(lx0y, Lerp(lx0x, lx1x, ys), zs) * warpAmp; + yr += Lerp(ly0y, Lerp(ly0x, ly1x, ys), zs) * warpAmp; + zr += Lerp(lz0y, Lerp(lz0x, lz1x, ys), zs) * warpAmp; + } + + // Domain Warp Simplex/OpenSimplex2 + + template + void SingleDomainWarpSimplexGradient(int seed, float warpAmp, float frequency, FNfloat x, FNfloat y, FNfloat& xr, FNfloat& yr, bool outGradOnly) const { + const float SQRT3 = 1.7320508075688772935274463415059f; + const float G2 = (3 - SQRT3) / 6; + + x *= frequency; + y *= frequency; + + /* + * --- Skew moved to TransformNoiseCoordinate method --- + * const FNfloat F2 = 0.5f * (SQRT3 - 1); + * FNfloat s = (x + y) * F2; + * x += s; y += s; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + float xi = (float)(x - i); + float yi = (float)(y - j); + + float t = (xi + yi) * G2; + float x0 = (float)(xi - t); + float y0 = (float)(yi - t); + + i *= PrimeX; + j *= PrimeY; + + float vx, vy; + vx = vy = 0; + + float a = 0.5f - x0 * x0 - y0 * y0; + if (a > 0) { + float aaaa = (a * a) * (a * a); + float xo, yo; + if (outGradOnly) { + GradCoordOut(seed, i, j, xo, yo); + } else { + GradCoordDual(seed, i, j, x0, y0, xo, yo); + } + vx += aaaa * xo; + vy += aaaa * yo; + } + + float c = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a); + if (c > 0) { + float x2 = x0 + (2 * (float)G2 - 1); + float y2 = y0 + (2 * (float)G2 - 1); + float cccc = (c * c) * (c * c); + float xo, yo; + if (outGradOnly) { + GradCoordOut(seed, i + PrimeX, j + PrimeY, xo, yo); + } else { + GradCoordDual(seed, i + PrimeX, j + PrimeY, x2, y2, xo, yo); + } + vx += cccc * xo; + vy += cccc * yo; + } + + if (y0 > x0) { + float x1 = x0 + (float)G2; + float y1 = y0 + ((float)G2 - 1); + float b = 0.5f - x1 * x1 - y1 * y1; + if (b > 0) { + float bbbb = (b * b) * (b * b); + float xo, yo; + if (outGradOnly) { + GradCoordOut(seed, i, j + PrimeY, xo, yo); + } else { + GradCoordDual(seed, i, j + PrimeY, x1, y1, xo, yo); + } + vx += bbbb * xo; + vy += bbbb * yo; + } + } else { + float x1 = x0 + ((float)G2 - 1); + float y1 = y0 + (float)G2; + float b = 0.5f - x1 * x1 - y1 * y1; + if (b > 0) { + float bbbb = (b * b) * (b * b); + float xo, yo; + if (outGradOnly) { + GradCoordOut(seed, i + PrimeX, j, xo, yo); + } else { + GradCoordDual(seed, i + PrimeX, j, x1, y1, xo, yo); + } + vx += bbbb * xo; + vy += bbbb * yo; + } + } + + xr += vx * warpAmp; + yr += vy * warpAmp; + } + + template + void SingleDomainWarpOpenSimplex2Gradient(int seed, float warpAmp, float frequency, FNfloat x, FNfloat y, FNfloat z, FNfloat& xr, FNfloat& yr, FNfloat& zr, bool outGradOnly) const { + x *= frequency; + y *= frequency; + z *= frequency; + + /* + * --- Rotation moved to TransformDomainWarpCoordinate method --- + * const FNfloat R3 = (FNfloat)(2.0 / 3.0); + * FNfloat r = (x + y + z) * R3; // Rotation, not skew + * x = r - x; y = r - y; z = r - z; + */ + + int i = FastRound(x); + int j = FastRound(y); + int k = FastRound(z); + float x0 = (float)x - i; + float y0 = (float)y - j; + float z0 = (float)z - k; + + int xNSign = (int)(-x0 - 1.0f) | 1; + int yNSign = (int)(-y0 - 1.0f) | 1; + int zNSign = (int)(-z0 - 1.0f) | 1; + + float ax0 = xNSign * -x0; + float ay0 = yNSign * -y0; + float az0 = zNSign * -z0; + + i *= PrimeX; + j *= PrimeY; + k *= PrimeZ; + + float vx, vy, vz; + vx = vy = vz = 0; + + float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0); + for (int l = 0; l < 2; l++) { + if (a > 0) { + float aaaa = (a * a) * (a * a); + float xo, yo, zo; + if (outGradOnly) { + GradCoordOut(seed, i, j, k, xo, yo, zo); + } else { + GradCoordDual(seed, i, j, k, x0, y0, z0, xo, yo, zo); + } + vx += aaaa * xo; + vy += aaaa * yo; + vz += aaaa * zo; + } + + float b = a + 1; + int i1 = i; + int j1 = j; + int k1 = k; + float x1 = x0; + float y1 = y0; + float z1 = z0; + + if (ax0 >= ay0 && ax0 >= az0) { + x1 += xNSign; + b -= xNSign * 2 * x1; + i1 -= xNSign * PrimeX; + } else if (ay0 > ax0 && ay0 >= az0) { + y1 += yNSign; + b -= yNSign * 2 * y1; + j1 -= yNSign * PrimeY; + } else { + z1 += zNSign; + b -= zNSign * 2 * z1; + k1 -= zNSign * PrimeZ; + } + + if (b > 0) { + float bbbb = (b * b) * (b * b); + float xo, yo, zo; + if (outGradOnly) { + GradCoordOut(seed, i1, j1, k1, xo, yo, zo); + } else { + GradCoordDual(seed, i1, j1, k1, x1, y1, z1, xo, yo, zo); + } + vx += bbbb * xo; + vy += bbbb * yo; + vz += bbbb * zo; + } + + if (l == 1) { + break; + } + + ax0 = 0.5f - ax0; + ay0 = 0.5f - ay0; + az0 = 0.5f - az0; + + x0 = xNSign * ax0; + y0 = yNSign * ay0; + z0 = zNSign * az0; + + a += (0.75f - ax0) - (ay0 + az0); + + i += (xNSign >> 1) & PrimeX; + j += (yNSign >> 1) & PrimeY; + k += (zNSign >> 1) & PrimeZ; + + xNSign = -xNSign; + yNSign = -yNSign; + zNSign = -zNSign; + + seed += 1293373; + } + + xr += vx * warpAmp; + yr += vy * warpAmp; + zr += vz * warpAmp; + } }; template <> -struct FastNoiseLite::Arguments_must_be_floating_point_values {}; +struct FastNoiseLite::Arguments_must_be_floating_point_values { }; template <> -struct FastNoiseLite::Arguments_must_be_floating_point_values {}; +struct FastNoiseLite::Arguments_must_be_floating_point_values { }; template <> -struct FastNoiseLite::Arguments_must_be_floating_point_values {}; +struct FastNoiseLite::Arguments_must_be_floating_point_values { }; template -const T FastNoiseLite::Lookup::Gradients2D[] = -{ - 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, - 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, - 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, - -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, - -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, - -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, - 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, - 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, - 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, - -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, - -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, - -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, - 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, - 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, - 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, - -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, - -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, - -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, - 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, - 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, - 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, - -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, - -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, - -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, - 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, - 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, - 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, - -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, - -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, - -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, - 0.38268343236509f, 0.923879532511287f, 0.923879532511287f, 0.38268343236509f, 0.923879532511287f, -0.38268343236509f, 0.38268343236509f, -0.923879532511287f, - -0.38268343236509f, -0.923879532511287f, -0.923879532511287f, -0.38268343236509f, -0.923879532511287f, 0.38268343236509f, -0.38268343236509f, 0.923879532511287f, +const T FastNoiseLite::Lookup::Gradients2D[] = { + 0.130526192220052f, + 0.99144486137381f, + 0.38268343236509f, + 0.923879532511287f, + 0.608761429008721f, + 0.793353340291235f, + 0.793353340291235f, + 0.608761429008721f, + 0.923879532511287f, + 0.38268343236509f, + 0.99144486137381f, + 0.130526192220051f, + 0.99144486137381f, + -0.130526192220051f, + 0.923879532511287f, + -0.38268343236509f, + 0.793353340291235f, + -0.60876142900872f, + 0.608761429008721f, + -0.793353340291235f, + 0.38268343236509f, + -0.923879532511287f, + 0.130526192220052f, + -0.99144486137381f, + -0.130526192220052f, + -0.99144486137381f, + -0.38268343236509f, + -0.923879532511287f, + -0.608761429008721f, + -0.793353340291235f, + -0.793353340291235f, + -0.608761429008721f, + -0.923879532511287f, + -0.38268343236509f, + -0.99144486137381f, + -0.130526192220052f, + -0.99144486137381f, + 0.130526192220051f, + -0.923879532511287f, + 0.38268343236509f, + -0.793353340291235f, + 0.608761429008721f, + -0.608761429008721f, + 0.793353340291235f, + -0.38268343236509f, + 0.923879532511287f, + -0.130526192220052f, + 0.99144486137381f, + 0.130526192220052f, + 0.99144486137381f, + 0.38268343236509f, + 0.923879532511287f, + 0.608761429008721f, + 0.793353340291235f, + 0.793353340291235f, + 0.608761429008721f, + 0.923879532511287f, + 0.38268343236509f, + 0.99144486137381f, + 0.130526192220051f, + 0.99144486137381f, + -0.130526192220051f, + 0.923879532511287f, + -0.38268343236509f, + 0.793353340291235f, + -0.60876142900872f, + 0.608761429008721f, + -0.793353340291235f, + 0.38268343236509f, + -0.923879532511287f, + 0.130526192220052f, + -0.99144486137381f, + -0.130526192220052f, + -0.99144486137381f, + -0.38268343236509f, + -0.923879532511287f, + -0.608761429008721f, + -0.793353340291235f, + -0.793353340291235f, + -0.608761429008721f, + -0.923879532511287f, + -0.38268343236509f, + -0.99144486137381f, + -0.130526192220052f, + -0.99144486137381f, + 0.130526192220051f, + -0.923879532511287f, + 0.38268343236509f, + -0.793353340291235f, + 0.608761429008721f, + -0.608761429008721f, + 0.793353340291235f, + -0.38268343236509f, + 0.923879532511287f, + -0.130526192220052f, + 0.99144486137381f, + 0.130526192220052f, + 0.99144486137381f, + 0.38268343236509f, + 0.923879532511287f, + 0.608761429008721f, + 0.793353340291235f, + 0.793353340291235f, + 0.608761429008721f, + 0.923879532511287f, + 0.38268343236509f, + 0.99144486137381f, + 0.130526192220051f, + 0.99144486137381f, + -0.130526192220051f, + 0.923879532511287f, + -0.38268343236509f, + 0.793353340291235f, + -0.60876142900872f, + 0.608761429008721f, + -0.793353340291235f, + 0.38268343236509f, + -0.923879532511287f, + 0.130526192220052f, + -0.99144486137381f, + -0.130526192220052f, + -0.99144486137381f, + -0.38268343236509f, + -0.923879532511287f, + -0.608761429008721f, + -0.793353340291235f, + -0.793353340291235f, + -0.608761429008721f, + -0.923879532511287f, + -0.38268343236509f, + -0.99144486137381f, + -0.130526192220052f, + -0.99144486137381f, + 0.130526192220051f, + -0.923879532511287f, + 0.38268343236509f, + -0.793353340291235f, + 0.608761429008721f, + -0.608761429008721f, + 0.793353340291235f, + -0.38268343236509f, + 0.923879532511287f, + -0.130526192220052f, + 0.99144486137381f, + 0.130526192220052f, + 0.99144486137381f, + 0.38268343236509f, + 0.923879532511287f, + 0.608761429008721f, + 0.793353340291235f, + 0.793353340291235f, + 0.608761429008721f, + 0.923879532511287f, + 0.38268343236509f, + 0.99144486137381f, + 0.130526192220051f, + 0.99144486137381f, + -0.130526192220051f, + 0.923879532511287f, + -0.38268343236509f, + 0.793353340291235f, + -0.60876142900872f, + 0.608761429008721f, + -0.793353340291235f, + 0.38268343236509f, + -0.923879532511287f, + 0.130526192220052f, + -0.99144486137381f, + -0.130526192220052f, + -0.99144486137381f, + -0.38268343236509f, + -0.923879532511287f, + -0.608761429008721f, + -0.793353340291235f, + -0.793353340291235f, + -0.608761429008721f, + -0.923879532511287f, + -0.38268343236509f, + -0.99144486137381f, + -0.130526192220052f, + -0.99144486137381f, + 0.130526192220051f, + -0.923879532511287f, + 0.38268343236509f, + -0.793353340291235f, + 0.608761429008721f, + -0.608761429008721f, + 0.793353340291235f, + -0.38268343236509f, + 0.923879532511287f, + -0.130526192220052f, + 0.99144486137381f, + 0.130526192220052f, + 0.99144486137381f, + 0.38268343236509f, + 0.923879532511287f, + 0.608761429008721f, + 0.793353340291235f, + 0.793353340291235f, + 0.608761429008721f, + 0.923879532511287f, + 0.38268343236509f, + 0.99144486137381f, + 0.130526192220051f, + 0.99144486137381f, + -0.130526192220051f, + 0.923879532511287f, + -0.38268343236509f, + 0.793353340291235f, + -0.60876142900872f, + 0.608761429008721f, + -0.793353340291235f, + 0.38268343236509f, + -0.923879532511287f, + 0.130526192220052f, + -0.99144486137381f, + -0.130526192220052f, + -0.99144486137381f, + -0.38268343236509f, + -0.923879532511287f, + -0.608761429008721f, + -0.793353340291235f, + -0.793353340291235f, + -0.608761429008721f, + -0.923879532511287f, + -0.38268343236509f, + -0.99144486137381f, + -0.130526192220052f, + -0.99144486137381f, + 0.130526192220051f, + -0.923879532511287f, + 0.38268343236509f, + -0.793353340291235f, + 0.608761429008721f, + -0.608761429008721f, + 0.793353340291235f, + -0.38268343236509f, + 0.923879532511287f, + -0.130526192220052f, + 0.99144486137381f, + 0.38268343236509f, + 0.923879532511287f, + 0.923879532511287f, + 0.38268343236509f, + 0.923879532511287f, + -0.38268343236509f, + 0.38268343236509f, + -0.923879532511287f, + -0.38268343236509f, + -0.923879532511287f, + -0.923879532511287f, + -0.38268343236509f, + -0.923879532511287f, + 0.38268343236509f, + -0.38268343236509f, + 0.923879532511287f, }; template -const T FastNoiseLite::Lookup::RandVecs2D[] = -{ - -0.2700222198f, -0.9628540911f, 0.3863092627f, -0.9223693152f, 0.04444859006f, -0.999011673f, -0.5992523158f, -0.8005602176f, -0.7819280288f, 0.6233687174f, 0.9464672271f, 0.3227999196f, -0.6514146797f, -0.7587218957f, 0.9378472289f, 0.347048376f, - -0.8497875957f, -0.5271252623f, -0.879042592f, 0.4767432447f, -0.892300288f, -0.4514423508f, -0.379844434f, -0.9250503802f, -0.9951650832f, 0.0982163789f, 0.7724397808f, -0.6350880136f, 0.7573283322f, -0.6530343002f, -0.9928004525f, -0.119780055f, - -0.0532665713f, 0.9985803285f, 0.9754253726f, -0.2203300762f, -0.7665018163f, 0.6422421394f, 0.991636706f, 0.1290606184f, -0.994696838f, 0.1028503788f, -0.5379205513f, -0.84299554f, 0.5022815471f, -0.8647041387f, 0.4559821461f, -0.8899889226f, - -0.8659131224f, -0.5001944266f, 0.0879458407f, -0.9961252577f, -0.5051684983f, 0.8630207346f, 0.7753185226f, -0.6315704146f, -0.6921944612f, 0.7217110418f, -0.5191659449f, -0.8546734591f, 0.8978622882f, -0.4402764035f, -0.1706774107f, 0.9853269617f, - -0.9353430106f, -0.3537420705f, -0.9992404798f, 0.03896746794f, -0.2882064021f, -0.9575683108f, -0.9663811329f, 0.2571137995f, -0.8759714238f, -0.4823630009f, -0.8303123018f, -0.5572983775f, 0.05110133755f, -0.9986934731f, -0.8558373281f, -0.5172450752f, - 0.09887025282f, 0.9951003332f, 0.9189016087f, 0.3944867976f, -0.2439375892f, -0.9697909324f, -0.8121409387f, -0.5834613061f, -0.9910431363f, 0.1335421355f, 0.8492423985f, -0.5280031709f, -0.9717838994f, -0.2358729591f, 0.9949457207f, 0.1004142068f, - 0.6241065508f, -0.7813392434f, 0.662910307f, 0.7486988212f, -0.7197418176f, 0.6942418282f, -0.8143370775f, -0.5803922158f, 0.104521054f, -0.9945226741f, -0.1065926113f, -0.9943027784f, 0.445799684f, -0.8951327509f, 0.105547406f, 0.9944142724f, - -0.992790267f, 0.1198644477f, -0.8334366408f, 0.552615025f, 0.9115561563f, -0.4111755999f, 0.8285544909f, -0.5599084351f, 0.7217097654f, -0.6921957921f, 0.4940492677f, -0.8694339084f, -0.3652321272f, -0.9309164803f, -0.9696606758f, 0.2444548501f, - 0.08925509731f, -0.996008799f, 0.5354071276f, -0.8445941083f, -0.1053576186f, 0.9944343981f, -0.9890284586f, 0.1477251101f, 0.004856104961f, 0.9999882091f, 0.9885598478f, 0.1508291331f, 0.9286129562f, -0.3710498316f, -0.5832393863f, -0.8123003252f, - 0.3015207509f, 0.9534596146f, -0.9575110528f, 0.2883965738f, 0.9715802154f, -0.2367105511f, 0.229981792f, 0.9731949318f, 0.955763816f, -0.2941352207f, 0.740956116f, 0.6715534485f, -0.9971513787f, -0.07542630764f, 0.6905710663f, -0.7232645452f, - -0.290713703f, -0.9568100872f, 0.5912777791f, -0.8064679708f, -0.9454592212f, -0.325740481f, 0.6664455681f, 0.74555369f, 0.6236134912f, 0.7817328275f, 0.9126993851f, -0.4086316587f, -0.8191762011f, 0.5735419353f, -0.8812745759f, -0.4726046147f, - 0.9953313627f, 0.09651672651f, 0.9855650846f, -0.1692969699f, -0.8495980887f, 0.5274306472f, 0.6174853946f, -0.7865823463f, 0.8508156371f, 0.52546432f, 0.9985032451f, -0.05469249926f, 0.1971371563f, -0.9803759185f, 0.6607855748f, -0.7505747292f, - -0.03097494063f, 0.9995201614f, -0.6731660801f, 0.739491331f, -0.7195018362f, -0.6944905383f, 0.9727511689f, 0.2318515979f, 0.9997059088f, -0.0242506907f, 0.4421787429f, -0.8969269532f, 0.9981350961f, -0.061043673f, -0.9173660799f, -0.3980445648f, - -0.8150056635f, -0.5794529907f, -0.8789331304f, 0.4769450202f, 0.0158605829f, 0.999874213f, -0.8095464474f, 0.5870558317f, -0.9165898907f, -0.3998286786f, -0.8023542565f, 0.5968480938f, -0.5176737917f, 0.8555780767f, -0.8154407307f, -0.5788405779f, - 0.4022010347f, -0.9155513791f, -0.9052556868f, -0.4248672045f, 0.7317445619f, 0.6815789728f, -0.5647632201f, -0.8252529947f, -0.8403276335f, -0.5420788397f, -0.9314281527f, 0.363925262f, 0.5238198472f, 0.8518290719f, 0.7432803869f, -0.6689800195f, - -0.985371561f, -0.1704197369f, 0.4601468731f, 0.88784281f, 0.825855404f, 0.5638819483f, 0.6182366099f, 0.7859920446f, 0.8331502863f, -0.553046653f, 0.1500307506f, 0.9886813308f, -0.662330369f, -0.7492119075f, -0.668598664f, 0.743623444f, - 0.7025606278f, 0.7116238924f, -0.5419389763f, -0.8404178401f, -0.3388616456f, 0.9408362159f, 0.8331530315f, 0.5530425174f, -0.2989720662f, -0.9542618632f, 0.2638522993f, 0.9645630949f, 0.124108739f, -0.9922686234f, -0.7282649308f, -0.6852956957f, - 0.6962500149f, 0.7177993569f, -0.9183535368f, 0.3957610156f, -0.6326102274f, -0.7744703352f, -0.9331891859f, -0.359385508f, -0.1153779357f, -0.9933216659f, 0.9514974788f, -0.3076565421f, -0.08987977445f, -0.9959526224f, 0.6678496916f, 0.7442961705f, - 0.7952400393f, -0.6062947138f, -0.6462007402f, -0.7631674805f, -0.2733598753f, 0.9619118351f, 0.9669590226f, -0.254931851f, -0.9792894595f, 0.2024651934f, -0.5369502995f, -0.8436138784f, -0.270036471f, -0.9628500944f, -0.6400277131f, 0.7683518247f, - -0.7854537493f, -0.6189203566f, 0.06005905383f, -0.9981948257f, -0.02455770378f, 0.9996984141f, -0.65983623f, 0.751409442f, -0.6253894466f, -0.7803127835f, -0.6210408851f, -0.7837781695f, 0.8348888491f, 0.5504185768f, -0.1592275245f, 0.9872419133f, - 0.8367622488f, 0.5475663786f, -0.8675753916f, -0.4973056806f, -0.2022662628f, -0.9793305667f, 0.9399189937f, 0.3413975472f, 0.9877404807f, -0.1561049093f, -0.9034455656f, 0.4287028224f, 0.1269804218f, -0.9919052235f, -0.3819600854f, 0.924178821f, - 0.9754625894f, 0.2201652486f, -0.3204015856f, -0.9472818081f, -0.9874760884f, 0.1577687387f, 0.02535348474f, -0.9996785487f, 0.4835130794f, -0.8753371362f, -0.2850799925f, -0.9585037287f, -0.06805516006f, -0.99768156f, -0.7885244045f, -0.6150034663f, - 0.3185392127f, -0.9479096845f, 0.8880043089f, 0.4598351306f, 0.6476921488f, -0.7619021462f, 0.9820241299f, 0.1887554194f, 0.9357275128f, -0.3527237187f, -0.8894895414f, 0.4569555293f, 0.7922791302f, 0.6101588153f, 0.7483818261f, 0.6632681526f, - -0.7288929755f, -0.6846276581f, 0.8729032783f, -0.4878932944f, 0.8288345784f, 0.5594937369f, 0.08074567077f, 0.9967347374f, 0.9799148216f, -0.1994165048f, -0.580730673f, -0.8140957471f, -0.4700049791f, -0.8826637636f, 0.2409492979f, 0.9705377045f, - 0.9437816757f, -0.3305694308f, -0.8927998638f, -0.4504535528f, -0.8069622304f, 0.5906030467f, 0.06258973166f, 0.9980393407f, -0.9312597469f, 0.3643559849f, 0.5777449785f, 0.8162173362f, -0.3360095855f, -0.941858566f, 0.697932075f, -0.7161639607f, - -0.002008157227f, -0.9999979837f, -0.1827294312f, -0.9831632392f, -0.6523911722f, 0.7578824173f, -0.4302626911f, -0.9027037258f, -0.9985126289f, -0.05452091251f, -0.01028102172f, -0.9999471489f, -0.4946071129f, 0.8691166802f, -0.2999350194f, 0.9539596344f, - 0.8165471961f, 0.5772786819f, 0.2697460475f, 0.962931498f, -0.7306287391f, -0.6827749597f, -0.7590952064f, -0.6509796216f, -0.907053853f, 0.4210146171f, -0.5104861064f, -0.8598860013f, 0.8613350597f, 0.5080373165f, 0.5007881595f, -0.8655698812f, - -0.654158152f, 0.7563577938f, -0.8382755311f, -0.545246856f, 0.6940070834f, 0.7199681717f, 0.06950936031f, 0.9975812994f, 0.1702942185f, -0.9853932612f, 0.2695973274f, 0.9629731466f, 0.5519612192f, -0.8338697815f, 0.225657487f, -0.9742067022f, - 0.4215262855f, -0.9068161835f, 0.4881873305f, -0.8727388672f, -0.3683854996f, -0.9296731273f, -0.9825390578f, 0.1860564427f, 0.81256471f, 0.5828709909f, 0.3196460933f, -0.9475370046f, 0.9570913859f, 0.2897862643f, -0.6876655497f, -0.7260276109f, - -0.9988770922f, -0.047376731f, -0.1250179027f, 0.992154486f, -0.8280133617f, 0.560708367f, 0.9324863769f, -0.3612051451f, 0.6394653183f, 0.7688199442f, -0.01623847064f, -0.9998681473f, -0.9955014666f, -0.09474613458f, -0.81453315f, 0.580117012f, - 0.4037327978f, -0.9148769469f, 0.9944263371f, 0.1054336766f, -0.1624711654f, 0.9867132919f, -0.9949487814f, -0.100383875f, -0.6995302564f, 0.7146029809f, 0.5263414922f, -0.85027327f, -0.5395221479f, 0.841971408f, 0.6579370318f, 0.7530729462f, - 0.01426758847f, -0.9998982128f, -0.6734383991f, 0.7392433447f, 0.639412098f, -0.7688642071f, 0.9211571421f, 0.3891908523f, -0.146637214f, -0.9891903394f, -0.782318098f, 0.6228791163f, -0.5039610839f, -0.8637263605f, -0.7743120191f, -0.6328039957f, +const T FastNoiseLite::Lookup::RandVecs2D[] = { + -0.2700222198f, + -0.9628540911f, + 0.3863092627f, + -0.9223693152f, + 0.04444859006f, + -0.999011673f, + -0.5992523158f, + -0.8005602176f, + -0.7819280288f, + 0.6233687174f, + 0.9464672271f, + 0.3227999196f, + -0.6514146797f, + -0.7587218957f, + 0.9378472289f, + 0.347048376f, + -0.8497875957f, + -0.5271252623f, + -0.879042592f, + 0.4767432447f, + -0.892300288f, + -0.4514423508f, + -0.379844434f, + -0.9250503802f, + -0.9951650832f, + 0.0982163789f, + 0.7724397808f, + -0.6350880136f, + 0.7573283322f, + -0.6530343002f, + -0.9928004525f, + -0.119780055f, + -0.0532665713f, + 0.9985803285f, + 0.9754253726f, + -0.2203300762f, + -0.7665018163f, + 0.6422421394f, + 0.991636706f, + 0.1290606184f, + -0.994696838f, + 0.1028503788f, + -0.5379205513f, + -0.84299554f, + 0.5022815471f, + -0.8647041387f, + 0.4559821461f, + -0.8899889226f, + -0.8659131224f, + -0.5001944266f, + 0.0879458407f, + -0.9961252577f, + -0.5051684983f, + 0.8630207346f, + 0.7753185226f, + -0.6315704146f, + -0.6921944612f, + 0.7217110418f, + -0.5191659449f, + -0.8546734591f, + 0.8978622882f, + -0.4402764035f, + -0.1706774107f, + 0.9853269617f, + -0.9353430106f, + -0.3537420705f, + -0.9992404798f, + 0.03896746794f, + -0.2882064021f, + -0.9575683108f, + -0.9663811329f, + 0.2571137995f, + -0.8759714238f, + -0.4823630009f, + -0.8303123018f, + -0.5572983775f, + 0.05110133755f, + -0.9986934731f, + -0.8558373281f, + -0.5172450752f, + 0.09887025282f, + 0.9951003332f, + 0.9189016087f, + 0.3944867976f, + -0.2439375892f, + -0.9697909324f, + -0.8121409387f, + -0.5834613061f, + -0.9910431363f, + 0.1335421355f, + 0.8492423985f, + -0.5280031709f, + -0.9717838994f, + -0.2358729591f, + 0.9949457207f, + 0.1004142068f, + 0.6241065508f, + -0.7813392434f, + 0.662910307f, + 0.7486988212f, + -0.7197418176f, + 0.6942418282f, + -0.8143370775f, + -0.5803922158f, + 0.104521054f, + -0.9945226741f, + -0.1065926113f, + -0.9943027784f, + 0.445799684f, + -0.8951327509f, + 0.105547406f, + 0.9944142724f, + -0.992790267f, + 0.1198644477f, + -0.8334366408f, + 0.552615025f, + 0.9115561563f, + -0.4111755999f, + 0.8285544909f, + -0.5599084351f, + 0.7217097654f, + -0.6921957921f, + 0.4940492677f, + -0.8694339084f, + -0.3652321272f, + -0.9309164803f, + -0.9696606758f, + 0.2444548501f, + 0.08925509731f, + -0.996008799f, + 0.5354071276f, + -0.8445941083f, + -0.1053576186f, + 0.9944343981f, + -0.9890284586f, + 0.1477251101f, + 0.004856104961f, + 0.9999882091f, + 0.9885598478f, + 0.1508291331f, + 0.9286129562f, + -0.3710498316f, + -0.5832393863f, + -0.8123003252f, + 0.3015207509f, + 0.9534596146f, + -0.9575110528f, + 0.2883965738f, + 0.9715802154f, + -0.2367105511f, + 0.229981792f, + 0.9731949318f, + 0.955763816f, + -0.2941352207f, + 0.740956116f, + 0.6715534485f, + -0.9971513787f, + -0.07542630764f, + 0.6905710663f, + -0.7232645452f, + -0.290713703f, + -0.9568100872f, + 0.5912777791f, + -0.8064679708f, + -0.9454592212f, + -0.325740481f, + 0.6664455681f, + 0.74555369f, + 0.6236134912f, + 0.7817328275f, + 0.9126993851f, + -0.4086316587f, + -0.8191762011f, + 0.5735419353f, + -0.8812745759f, + -0.4726046147f, + 0.9953313627f, + 0.09651672651f, + 0.9855650846f, + -0.1692969699f, + -0.8495980887f, + 0.5274306472f, + 0.6174853946f, + -0.7865823463f, + 0.8508156371f, + 0.52546432f, + 0.9985032451f, + -0.05469249926f, + 0.1971371563f, + -0.9803759185f, + 0.6607855748f, + -0.7505747292f, + -0.03097494063f, + 0.9995201614f, + -0.6731660801f, + 0.739491331f, + -0.7195018362f, + -0.6944905383f, + 0.9727511689f, + 0.2318515979f, + 0.9997059088f, + -0.0242506907f, + 0.4421787429f, + -0.8969269532f, + 0.9981350961f, + -0.061043673f, + -0.9173660799f, + -0.3980445648f, + -0.8150056635f, + -0.5794529907f, + -0.8789331304f, + 0.4769450202f, + 0.0158605829f, + 0.999874213f, + -0.8095464474f, + 0.5870558317f, + -0.9165898907f, + -0.3998286786f, + -0.8023542565f, + 0.5968480938f, + -0.5176737917f, + 0.8555780767f, + -0.8154407307f, + -0.5788405779f, + 0.4022010347f, + -0.9155513791f, + -0.9052556868f, + -0.4248672045f, + 0.7317445619f, + 0.6815789728f, + -0.5647632201f, + -0.8252529947f, + -0.8403276335f, + -0.5420788397f, + -0.9314281527f, + 0.363925262f, + 0.5238198472f, + 0.8518290719f, + 0.7432803869f, + -0.6689800195f, + -0.985371561f, + -0.1704197369f, + 0.4601468731f, + 0.88784281f, + 0.825855404f, + 0.5638819483f, + 0.6182366099f, + 0.7859920446f, + 0.8331502863f, + -0.553046653f, + 0.1500307506f, + 0.9886813308f, + -0.662330369f, + -0.7492119075f, + -0.668598664f, + 0.743623444f, + 0.7025606278f, + 0.7116238924f, + -0.5419389763f, + -0.8404178401f, + -0.3388616456f, + 0.9408362159f, + 0.8331530315f, + 0.5530425174f, + -0.2989720662f, + -0.9542618632f, + 0.2638522993f, + 0.9645630949f, + 0.124108739f, + -0.9922686234f, + -0.7282649308f, + -0.6852956957f, + 0.6962500149f, + 0.7177993569f, + -0.9183535368f, + 0.3957610156f, + -0.6326102274f, + -0.7744703352f, + -0.9331891859f, + -0.359385508f, + -0.1153779357f, + -0.9933216659f, + 0.9514974788f, + -0.3076565421f, + -0.08987977445f, + -0.9959526224f, + 0.6678496916f, + 0.7442961705f, + 0.7952400393f, + -0.6062947138f, + -0.6462007402f, + -0.7631674805f, + -0.2733598753f, + 0.9619118351f, + 0.9669590226f, + -0.254931851f, + -0.9792894595f, + 0.2024651934f, + -0.5369502995f, + -0.8436138784f, + -0.270036471f, + -0.9628500944f, + -0.6400277131f, + 0.7683518247f, + -0.7854537493f, + -0.6189203566f, + 0.06005905383f, + -0.9981948257f, + -0.02455770378f, + 0.9996984141f, + -0.65983623f, + 0.751409442f, + -0.6253894466f, + -0.7803127835f, + -0.6210408851f, + -0.7837781695f, + 0.8348888491f, + 0.5504185768f, + -0.1592275245f, + 0.9872419133f, + 0.8367622488f, + 0.5475663786f, + -0.8675753916f, + -0.4973056806f, + -0.2022662628f, + -0.9793305667f, + 0.9399189937f, + 0.3413975472f, + 0.9877404807f, + -0.1561049093f, + -0.9034455656f, + 0.4287028224f, + 0.1269804218f, + -0.9919052235f, + -0.3819600854f, + 0.924178821f, + 0.9754625894f, + 0.2201652486f, + -0.3204015856f, + -0.9472818081f, + -0.9874760884f, + 0.1577687387f, + 0.02535348474f, + -0.9996785487f, + 0.4835130794f, + -0.8753371362f, + -0.2850799925f, + -0.9585037287f, + -0.06805516006f, + -0.99768156f, + -0.7885244045f, + -0.6150034663f, + 0.3185392127f, + -0.9479096845f, + 0.8880043089f, + 0.4598351306f, + 0.6476921488f, + -0.7619021462f, + 0.9820241299f, + 0.1887554194f, + 0.9357275128f, + -0.3527237187f, + -0.8894895414f, + 0.4569555293f, + 0.7922791302f, + 0.6101588153f, + 0.7483818261f, + 0.6632681526f, + -0.7288929755f, + -0.6846276581f, + 0.8729032783f, + -0.4878932944f, + 0.8288345784f, + 0.5594937369f, + 0.08074567077f, + 0.9967347374f, + 0.9799148216f, + -0.1994165048f, + -0.580730673f, + -0.8140957471f, + -0.4700049791f, + -0.8826637636f, + 0.2409492979f, + 0.9705377045f, + 0.9437816757f, + -0.3305694308f, + -0.8927998638f, + -0.4504535528f, + -0.8069622304f, + 0.5906030467f, + 0.06258973166f, + 0.9980393407f, + -0.9312597469f, + 0.3643559849f, + 0.5777449785f, + 0.8162173362f, + -0.3360095855f, + -0.941858566f, + 0.697932075f, + -0.7161639607f, + -0.002008157227f, + -0.9999979837f, + -0.1827294312f, + -0.9831632392f, + -0.6523911722f, + 0.7578824173f, + -0.4302626911f, + -0.9027037258f, + -0.9985126289f, + -0.05452091251f, + -0.01028102172f, + -0.9999471489f, + -0.4946071129f, + 0.8691166802f, + -0.2999350194f, + 0.9539596344f, + 0.8165471961f, + 0.5772786819f, + 0.2697460475f, + 0.962931498f, + -0.7306287391f, + -0.6827749597f, + -0.7590952064f, + -0.6509796216f, + -0.907053853f, + 0.4210146171f, + -0.5104861064f, + -0.8598860013f, + 0.8613350597f, + 0.5080373165f, + 0.5007881595f, + -0.8655698812f, + -0.654158152f, + 0.7563577938f, + -0.8382755311f, + -0.545246856f, + 0.6940070834f, + 0.7199681717f, + 0.06950936031f, + 0.9975812994f, + 0.1702942185f, + -0.9853932612f, + 0.2695973274f, + 0.9629731466f, + 0.5519612192f, + -0.8338697815f, + 0.225657487f, + -0.9742067022f, + 0.4215262855f, + -0.9068161835f, + 0.4881873305f, + -0.8727388672f, + -0.3683854996f, + -0.9296731273f, + -0.9825390578f, + 0.1860564427f, + 0.81256471f, + 0.5828709909f, + 0.3196460933f, + -0.9475370046f, + 0.9570913859f, + 0.2897862643f, + -0.6876655497f, + -0.7260276109f, + -0.9988770922f, + -0.047376731f, + -0.1250179027f, + 0.992154486f, + -0.8280133617f, + 0.560708367f, + 0.9324863769f, + -0.3612051451f, + 0.6394653183f, + 0.7688199442f, + -0.01623847064f, + -0.9998681473f, + -0.9955014666f, + -0.09474613458f, + -0.81453315f, + 0.580117012f, + 0.4037327978f, + -0.9148769469f, + 0.9944263371f, + 0.1054336766f, + -0.1624711654f, + 0.9867132919f, + -0.9949487814f, + -0.100383875f, + -0.6995302564f, + 0.7146029809f, + 0.5263414922f, + -0.85027327f, + -0.5395221479f, + 0.841971408f, + 0.6579370318f, + 0.7530729462f, + 0.01426758847f, + -0.9998982128f, + -0.6734383991f, + 0.7392433447f, + 0.639412098f, + -0.7688642071f, + 0.9211571421f, + 0.3891908523f, + -0.146637214f, + -0.9891903394f, + -0.782318098f, + 0.6228791163f, + -0.5039610839f, + -0.8637263605f, + -0.7743120191f, + -0.6328039957f, }; template -const T FastNoiseLite::Lookup::Gradients3D[] = -{ - 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, - 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, - 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, - 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, - 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, - 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, - 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, - 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, - 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, - 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, - 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, - 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, - 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, - 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, - 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, - 1, 1, 0, 0, 0,-1, 1, 0, -1, 1, 0, 0, 0,-1,-1, 0 +const T FastNoiseLite::Lookup::Gradients3D[] = { + 0, 1, 1, 0, 0, -1, 1, 0, 0, 1, -1, 0, 0, -1, -1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0, -1, 0, -1, 0, -1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1, -1, 0, 0, -1, -1, 0, 0, + 0, 1, 1, 0, 0, -1, 1, 0, 0, 1, -1, 0, 0, -1, -1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0, -1, 0, -1, 0, -1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1, -1, 0, 0, -1, -1, 0, 0, + 0, 1, 1, 0, 0, -1, 1, 0, 0, 1, -1, 0, 0, -1, -1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0, -1, 0, -1, 0, -1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1, -1, 0, 0, -1, -1, 0, 0, + 0, 1, 1, 0, 0, -1, 1, 0, 0, 1, -1, 0, 0, -1, -1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0, -1, 0, -1, 0, -1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1, -1, 0, 0, -1, -1, 0, 0, + 0, 1, 1, 0, 0, -1, 1, 0, 0, 1, -1, 0, 0, -1, -1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0, -1, 0, -1, 0, -1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1, -1, 0, 0, -1, -1, 0, 0, + 1, 1, 0, 0, 0, -1, 1, 0, -1, 1, 0, 0, 0, -1, -1, 0 }; template -const T FastNoiseLite::Lookup::RandVecs3D[] = -{ - -0.7292736885f, -0.6618439697f, 0.1735581948f, 0, 0.790292081f, -0.5480887466f, -0.2739291014f, 0, 0.7217578935f, 0.6226212466f, -0.3023380997f, 0, 0.565683137f, -0.8208298145f, -0.0790000257f, 0, 0.760049034f, -0.5555979497f, -0.3370999617f, 0, 0.3713945616f, 0.5011264475f, 0.7816254623f, 0, -0.1277062463f, -0.4254438999f, -0.8959289049f, 0, -0.2881560924f, -0.5815838982f, 0.7607405838f, 0, - 0.5849561111f, -0.662820239f, -0.4674352136f, 0, 0.3307171178f, 0.0391653737f, 0.94291689f, 0, 0.8712121778f, -0.4113374369f, -0.2679381538f, 0, 0.580981015f, 0.7021915846f, 0.4115677815f, 0, 0.503756873f, 0.6330056931f, -0.5878203852f, 0, 0.4493712205f, 0.601390195f, 0.6606022552f, 0, -0.6878403724f, 0.09018890807f, -0.7202371714f, 0, -0.5958956522f, -0.6469350577f, 0.475797649f, 0, - -0.5127052122f, 0.1946921978f, -0.8361987284f, 0, -0.9911507142f, -0.05410276466f, -0.1212153153f, 0, -0.2149721042f, 0.9720882117f, -0.09397607749f, 0, -0.7518650936f, -0.5428057603f, 0.3742469607f, 0, 0.5237068895f, 0.8516377189f, -0.02107817834f, 0, 0.6333504779f, 0.1926167129f, -0.7495104896f, 0, -0.06788241606f, 0.3998305789f, 0.9140719259f, 0, -0.5538628599f, -0.4729896695f, -0.6852128902f, 0, - -0.7261455366f, -0.5911990757f, 0.3509933228f, 0, -0.9229274737f, -0.1782808786f, 0.3412049336f, 0, -0.6968815002f, 0.6511274338f, 0.3006480328f, 0, 0.9608044783f, -0.2098363234f, -0.1811724921f, 0, 0.06817146062f, -0.9743405129f, 0.2145069156f, 0, -0.3577285196f, -0.6697087264f, -0.6507845481f, 0, -0.1868621131f, 0.7648617052f, -0.6164974636f, 0, -0.6541697588f, 0.3967914832f, 0.6439087246f, 0, - 0.6993340405f, -0.6164538506f, 0.3618239211f, 0, -0.1546665739f, 0.6291283928f, 0.7617583057f, 0, -0.6841612949f, -0.2580482182f, -0.6821542638f, 0, 0.5383980957f, 0.4258654885f, 0.7271630328f, 0, -0.5026987823f, -0.7939832935f, -0.3418836993f, 0, 0.3202971715f, 0.2834415347f, 0.9039195862f, 0, 0.8683227101f, -0.0003762656404f, -0.4959995258f, 0, 0.791120031f, -0.08511045745f, 0.6057105799f, 0, - -0.04011016052f, -0.4397248749f, 0.8972364289f, 0, 0.9145119872f, 0.3579346169f, -0.1885487608f, 0, -0.9612039066f, -0.2756484276f, 0.01024666929f, 0, 0.6510361721f, -0.2877799159f, -0.7023778346f, 0, -0.2041786351f, 0.7365237271f, 0.644859585f, 0, -0.7718263711f, 0.3790626912f, 0.5104855816f, 0, -0.3060082741f, -0.7692987727f, 0.5608371729f, 0, 0.454007341f, -0.5024843065f, 0.7357899537f, 0, - 0.4816795475f, 0.6021208291f, -0.6367380315f, 0, 0.6961980369f, -0.3222197429f, 0.641469197f, 0, -0.6532160499f, -0.6781148932f, 0.3368515753f, 0, 0.5089301236f, -0.6154662304f, -0.6018234363f, 0, -0.1635919754f, -0.9133604627f, -0.372840892f, 0, 0.52408019f, -0.8437664109f, 0.1157505864f, 0, 0.5902587356f, 0.4983817807f, -0.6349883666f, 0, 0.5863227872f, 0.494764745f, 0.6414307729f, 0, - 0.6779335087f, 0.2341345225f, 0.6968408593f, 0, 0.7177054546f, -0.6858979348f, 0.120178631f, 0, -0.5328819713f, -0.5205125012f, 0.6671608058f, 0, -0.8654874251f, -0.0700727088f, -0.4960053754f, 0, -0.2861810166f, 0.7952089234f, 0.5345495242f, 0, -0.04849529634f, 0.9810836427f, -0.1874115585f, 0, -0.6358521667f, 0.6058348682f, 0.4781800233f, 0, 0.6254794696f, -0.2861619734f, 0.7258696564f, 0, - -0.2585259868f, 0.5061949264f, -0.8227581726f, 0, 0.02136306781f, 0.5064016808f, -0.8620330371f, 0, 0.200111773f, 0.8599263484f, 0.4695550591f, 0, 0.4743561372f, 0.6014985084f, -0.6427953014f, 0, 0.6622993731f, -0.5202474575f, -0.5391679918f, 0, 0.08084972818f, -0.6532720452f, 0.7527940996f, 0, -0.6893687501f, 0.0592860349f, 0.7219805347f, 0, -0.1121887082f, -0.9673185067f, 0.2273952515f, 0, - 0.7344116094f, 0.5979668656f, -0.3210532909f, 0, 0.5789393465f, -0.2488849713f, 0.7764570201f, 0, 0.6988182827f, 0.3557169806f, -0.6205791146f, 0, -0.8636845529f, -0.2748771249f, -0.4224826141f, 0, -0.4247027957f, -0.4640880967f, 0.777335046f, 0, 0.5257722489f, -0.8427017621f, 0.1158329937f, 0, 0.9343830603f, 0.316302472f, -0.1639543925f, 0, -0.1016836419f, -0.8057303073f, -0.5834887393f, 0, - -0.6529238969f, 0.50602126f, -0.5635892736f, 0, -0.2465286165f, -0.9668205684f, -0.06694497494f, 0, -0.9776897119f, -0.2099250524f, -0.007368825344f, 0, 0.7736893337f, 0.5734244712f, 0.2694238123f, 0, -0.6095087895f, 0.4995678998f, 0.6155736747f, 0, 0.5794535482f, 0.7434546771f, 0.3339292269f, 0, -0.8226211154f, 0.08142581855f, 0.5627293636f, 0, -0.510385483f, 0.4703667658f, 0.7199039967f, 0, - -0.5764971849f, -0.07231656274f, -0.8138926898f, 0, 0.7250628871f, 0.3949971505f, -0.5641463116f, 0, -0.1525424005f, 0.4860840828f, -0.8604958341f, 0, -0.5550976208f, -0.4957820792f, 0.667882296f, 0, -0.1883614327f, 0.9145869398f, 0.357841725f, 0, 0.7625556724f, -0.5414408243f, -0.3540489801f, 0, -0.5870231946f, -0.3226498013f, -0.7424963803f, 0, 0.3051124198f, 0.2262544068f, -0.9250488391f, 0, - 0.6379576059f, 0.577242424f, -0.5097070502f, 0, -0.5966775796f, 0.1454852398f, -0.7891830656f, 0, -0.658330573f, 0.6555487542f, -0.3699414651f, 0, 0.7434892426f, 0.2351084581f, 0.6260573129f, 0, 0.5562114096f, 0.8264360377f, -0.0873632843f, 0, -0.3028940016f, -0.8251527185f, 0.4768419182f, 0, 0.1129343818f, -0.985888439f, -0.1235710781f, 0, 0.5937652891f, -0.5896813806f, 0.5474656618f, 0, - 0.6757964092f, -0.5835758614f, -0.4502648413f, 0, 0.7242302609f, -0.1152719764f, 0.6798550586f, 0, -0.9511914166f, 0.0753623979f, -0.2992580792f, 0, 0.2539470961f, -0.1886339355f, 0.9486454084f, 0, 0.571433621f, -0.1679450851f, -0.8032795685f, 0, -0.06778234979f, 0.3978269256f, 0.9149531629f, 0, 0.6074972649f, 0.733060024f, -0.3058922593f, 0, -0.5435478392f, 0.1675822484f, 0.8224791405f, 0, - -0.5876678086f, -0.3380045064f, -0.7351186982f, 0, -0.7967562402f, 0.04097822706f, -0.6029098428f, 0, -0.1996350917f, 0.8706294745f, 0.4496111079f, 0, -0.02787660336f, -0.9106232682f, -0.4122962022f, 0, -0.7797625996f, -0.6257634692f, 0.01975775581f, 0, -0.5211232846f, 0.7401644346f, -0.4249554471f, 0, 0.8575424857f, 0.4053272873f, -0.3167501783f, 0, 0.1045223322f, 0.8390195772f, -0.5339674439f, 0, - 0.3501822831f, 0.9242524096f, -0.1520850155f, 0, 0.1987849858f, 0.07647613266f, 0.9770547224f, 0, 0.7845996363f, 0.6066256811f, -0.1280964233f, 0, 0.09006737436f, -0.9750989929f, -0.2026569073f, 0, -0.8274343547f, -0.542299559f, 0.1458203587f, 0, -0.3485797732f, -0.415802277f, 0.840000362f, 0, -0.2471778936f, -0.7304819962f, -0.6366310879f, 0, -0.3700154943f, 0.8577948156f, 0.3567584454f, 0, - 0.5913394901f, -0.548311967f, -0.5913303597f, 0, 0.1204873514f, -0.7626472379f, -0.6354935001f, 0, 0.616959265f, 0.03079647928f, 0.7863922953f, 0, 0.1258156836f, -0.6640829889f, -0.7369967419f, 0, -0.6477565124f, -0.1740147258f, -0.7417077429f, 0, 0.6217889313f, -0.7804430448f, -0.06547655076f, 0, 0.6589943422f, -0.6096987708f, 0.4404473475f, 0, -0.2689837504f, -0.6732403169f, -0.6887635427f, 0, - -0.3849775103f, 0.5676542638f, 0.7277093879f, 0, 0.5754444408f, 0.8110471154f, -0.1051963504f, 0, 0.9141593684f, 0.3832947817f, 0.131900567f, 0, -0.107925319f, 0.9245493968f, 0.3654593525f, 0, 0.377977089f, 0.3043148782f, 0.8743716458f, 0, -0.2142885215f, -0.8259286236f, 0.5214617324f, 0, 0.5802544474f, 0.4148098596f, -0.7008834116f, 0, -0.1982660881f, 0.8567161266f, -0.4761596756f, 0, - -0.03381553704f, 0.3773180787f, -0.9254661404f, 0, -0.6867922841f, -0.6656597827f, 0.2919133642f, 0, 0.7731742607f, -0.2875793547f, -0.5652430251f, 0, -0.09655941928f, 0.9193708367f, -0.3813575004f, 0, 0.2715702457f, -0.9577909544f, -0.09426605581f, 0, 0.2451015704f, -0.6917998565f, -0.6792188003f, 0, 0.977700782f, -0.1753855374f, 0.1155036542f, 0, -0.5224739938f, 0.8521606816f, 0.02903615945f, 0, - -0.7734880599f, -0.5261292347f, 0.3534179531f, 0, -0.7134492443f, -0.269547243f, 0.6467878011f, 0, 0.1644037271f, 0.5105846203f, -0.8439637196f, 0, 0.6494635788f, 0.05585611296f, 0.7583384168f, 0, -0.4711970882f, 0.5017280509f, -0.7254255765f, 0, -0.6335764307f, -0.2381686273f, -0.7361091029f, 0, -0.9021533097f, -0.270947803f, -0.3357181763f, 0, -0.3793711033f, 0.872258117f, 0.3086152025f, 0, - -0.6855598966f, -0.3250143309f, 0.6514394162f, 0, 0.2900942212f, -0.7799057743f, -0.5546100667f, 0, -0.2098319339f, 0.85037073f, 0.4825351604f, 0, -0.4592603758f, 0.6598504336f, -0.5947077538f, 0, 0.8715945488f, 0.09616365406f, -0.4807031248f, 0, -0.6776666319f, 0.7118504878f, -0.1844907016f, 0, 0.7044377633f, 0.312427597f, 0.637304036f, 0, -0.7052318886f, -0.2401093292f, -0.6670798253f, 0, - 0.081921007f, -0.7207336136f, -0.6883545647f, 0, -0.6993680906f, -0.5875763221f, -0.4069869034f, 0, -0.1281454481f, 0.6419895885f, 0.7559286424f, 0, -0.6337388239f, -0.6785471501f, -0.3714146849f, 0, 0.5565051903f, -0.2168887573f, -0.8020356851f, 0, -0.5791554484f, 0.7244372011f, -0.3738578718f, 0, 0.1175779076f, -0.7096451073f, 0.6946792478f, 0, -0.6134619607f, 0.1323631078f, 0.7785527795f, 0, - 0.6984635305f, -0.02980516237f, -0.715024719f, 0, 0.8318082963f, -0.3930171956f, 0.3919597455f, 0, 0.1469576422f, 0.05541651717f, -0.9875892167f, 0, 0.708868575f, -0.2690503865f, 0.6520101478f, 0, 0.2726053183f, 0.67369766f, -0.68688995f, 0, -0.6591295371f, 0.3035458599f, -0.6880466294f, 0, 0.4815131379f, -0.7528270071f, 0.4487723203f, 0, 0.9430009463f, 0.1675647412f, -0.2875261255f, 0, - 0.434802957f, 0.7695304522f, -0.4677277752f, 0, 0.3931996188f, 0.594473625f, 0.7014236729f, 0, 0.7254336655f, -0.603925654f, 0.3301814672f, 0, 0.7590235227f, -0.6506083235f, 0.02433313207f, 0, -0.8552768592f, -0.3430042733f, 0.3883935666f, 0, -0.6139746835f, 0.6981725247f, 0.3682257648f, 0, -0.7465905486f, -0.5752009504f, 0.3342849376f, 0, 0.5730065677f, 0.810555537f, -0.1210916791f, 0, - -0.9225877367f, -0.3475211012f, -0.167514036f, 0, -0.7105816789f, -0.4719692027f, -0.5218416899f, 0, -0.08564609717f, 0.3583001386f, 0.929669703f, 0, -0.8279697606f, -0.2043157126f, 0.5222271202f, 0, 0.427944023f, 0.278165994f, 0.8599346446f, 0, 0.5399079671f, -0.7857120652f, -0.3019204161f, 0, 0.5678404253f, -0.5495413974f, -0.6128307303f, 0, -0.9896071041f, 0.1365639107f, -0.04503418428f, 0, - -0.6154342638f, -0.6440875597f, 0.4543037336f, 0, 0.1074204368f, -0.7946340692f, 0.5975094525f, 0, -0.3595449969f, -0.8885529948f, 0.28495784f, 0, -0.2180405296f, 0.1529888965f, 0.9638738118f, 0, -0.7277432317f, -0.6164050508f, -0.3007234646f, 0, 0.7249729114f, -0.00669719484f, 0.6887448187f, 0, -0.5553659455f, -0.5336586252f, 0.6377908264f, 0, 0.5137558015f, 0.7976208196f, -0.3160000073f, 0, - -0.3794024848f, 0.9245608561f, -0.03522751494f, 0, 0.8229248658f, 0.2745365933f, -0.4974176556f, 0, -0.5404114394f, 0.6091141441f, 0.5804613989f, 0, 0.8036581901f, -0.2703029469f, 0.5301601931f, 0, 0.6044318879f, 0.6832968393f, 0.4095943388f, 0, 0.06389988817f, 0.9658208605f, -0.2512108074f, 0, 0.1087113286f, 0.7402471173f, -0.6634877936f, 0, -0.713427712f, -0.6926784018f, 0.1059128479f, 0, - 0.6458897819f, -0.5724548511f, -0.5050958653f, 0, -0.6553931414f, 0.7381471625f, 0.159995615f, 0, 0.3910961323f, 0.9188871375f, -0.05186755998f, 0, -0.4879022471f, -0.5904376907f, 0.6429111375f, 0, 0.6014790094f, 0.7707441366f, -0.2101820095f, 0, -0.5677173047f, 0.7511360995f, 0.3368851762f, 0, 0.7858573506f, 0.226674665f, 0.5753666838f, 0, -0.4520345543f, -0.604222686f, -0.6561857263f, 0, - 0.002272116345f, 0.4132844051f, -0.9105991643f, 0, -0.5815751419f, -0.5162925989f, 0.6286591339f, 0, -0.03703704785f, 0.8273785755f, 0.5604221175f, 0, -0.5119692504f, 0.7953543429f, -0.3244980058f, 0, -0.2682417366f, -0.9572290247f, -0.1084387619f, 0, -0.2322482736f, -0.9679131102f, -0.09594243324f, 0, 0.3554328906f, -0.8881505545f, 0.2913006227f, 0, 0.7346520519f, -0.4371373164f, 0.5188422971f, 0, - 0.9985120116f, 0.04659011161f, -0.02833944577f, 0, -0.3727687496f, -0.9082481361f, 0.1900757285f, 0, 0.91737377f, -0.3483642108f, 0.1925298489f, 0, 0.2714911074f, 0.4147529736f, -0.8684886582f, 0, 0.5131763485f, -0.7116334161f, 0.4798207128f, 0, -0.8737353606f, 0.18886992f, -0.4482350644f, 0, 0.8460043821f, -0.3725217914f, 0.3814499973f, 0, 0.8978727456f, -0.1780209141f, -0.4026575304f, 0, - 0.2178065647f, -0.9698322841f, -0.1094789531f, 0, -0.1518031304f, -0.7788918132f, -0.6085091231f, 0, -0.2600384876f, -0.4755398075f, -0.8403819825f, 0, 0.572313509f, -0.7474340931f, -0.3373418503f, 0, -0.7174141009f, 0.1699017182f, -0.6756111411f, 0, -0.684180784f, 0.02145707593f, -0.7289967412f, 0, -0.2007447902f, 0.06555605789f, -0.9774476623f, 0, -0.1148803697f, -0.8044887315f, 0.5827524187f, 0, - -0.7870349638f, 0.03447489231f, 0.6159443543f, 0, -0.2015596421f, 0.6859872284f, 0.6991389226f, 0, -0.08581082512f, -0.10920836f, -0.9903080513f, 0, 0.5532693395f, 0.7325250401f, -0.396610771f, 0, -0.1842489331f, -0.9777375055f, -0.1004076743f, 0, 0.0775473789f, -0.9111505856f, 0.4047110257f, 0, 0.1399838409f, 0.7601631212f, -0.6344734459f, 0, 0.4484419361f, -0.845289248f, 0.2904925424f, 0 +const T FastNoiseLite::Lookup::RandVecs3D[] = { + -0.7292736885f, -0.6618439697f, 0.1735581948f, 0, 0.790292081f, -0.5480887466f, -0.2739291014f, 0, 0.7217578935f, 0.6226212466f, -0.3023380997f, 0, 0.565683137f, -0.8208298145f, -0.0790000257f, 0, 0.760049034f, -0.5555979497f, -0.3370999617f, 0, 0.3713945616f, 0.5011264475f, 0.7816254623f, 0, -0.1277062463f, -0.4254438999f, -0.8959289049f, 0, -0.2881560924f, -0.5815838982f, 0.7607405838f, 0, + 0.5849561111f, -0.662820239f, -0.4674352136f, 0, 0.3307171178f, 0.0391653737f, 0.94291689f, 0, 0.8712121778f, -0.4113374369f, -0.2679381538f, 0, 0.580981015f, 0.7021915846f, 0.4115677815f, 0, 0.503756873f, 0.6330056931f, -0.5878203852f, 0, 0.4493712205f, 0.601390195f, 0.6606022552f, 0, -0.6878403724f, 0.09018890807f, -0.7202371714f, 0, -0.5958956522f, -0.6469350577f, 0.475797649f, 0, + -0.5127052122f, 0.1946921978f, -0.8361987284f, 0, -0.9911507142f, -0.05410276466f, -0.1212153153f, 0, -0.2149721042f, 0.9720882117f, -0.09397607749f, 0, -0.7518650936f, -0.5428057603f, 0.3742469607f, 0, 0.5237068895f, 0.8516377189f, -0.02107817834f, 0, 0.6333504779f, 0.1926167129f, -0.7495104896f, 0, -0.06788241606f, 0.3998305789f, 0.9140719259f, 0, -0.5538628599f, -0.4729896695f, -0.6852128902f, 0, + -0.7261455366f, -0.5911990757f, 0.3509933228f, 0, -0.9229274737f, -0.1782808786f, 0.3412049336f, 0, -0.6968815002f, 0.6511274338f, 0.3006480328f, 0, 0.9608044783f, -0.2098363234f, -0.1811724921f, 0, 0.06817146062f, -0.9743405129f, 0.2145069156f, 0, -0.3577285196f, -0.6697087264f, -0.6507845481f, 0, -0.1868621131f, 0.7648617052f, -0.6164974636f, 0, -0.6541697588f, 0.3967914832f, 0.6439087246f, 0, + 0.6993340405f, -0.6164538506f, 0.3618239211f, 0, -0.1546665739f, 0.6291283928f, 0.7617583057f, 0, -0.6841612949f, -0.2580482182f, -0.6821542638f, 0, 0.5383980957f, 0.4258654885f, 0.7271630328f, 0, -0.5026987823f, -0.7939832935f, -0.3418836993f, 0, 0.3202971715f, 0.2834415347f, 0.9039195862f, 0, 0.8683227101f, -0.0003762656404f, -0.4959995258f, 0, 0.791120031f, -0.08511045745f, 0.6057105799f, 0, + -0.04011016052f, -0.4397248749f, 0.8972364289f, 0, 0.9145119872f, 0.3579346169f, -0.1885487608f, 0, -0.9612039066f, -0.2756484276f, 0.01024666929f, 0, 0.6510361721f, -0.2877799159f, -0.7023778346f, 0, -0.2041786351f, 0.7365237271f, 0.644859585f, 0, -0.7718263711f, 0.3790626912f, 0.5104855816f, 0, -0.3060082741f, -0.7692987727f, 0.5608371729f, 0, 0.454007341f, -0.5024843065f, 0.7357899537f, 0, + 0.4816795475f, 0.6021208291f, -0.6367380315f, 0, 0.6961980369f, -0.3222197429f, 0.641469197f, 0, -0.6532160499f, -0.6781148932f, 0.3368515753f, 0, 0.5089301236f, -0.6154662304f, -0.6018234363f, 0, -0.1635919754f, -0.9133604627f, -0.372840892f, 0, 0.52408019f, -0.8437664109f, 0.1157505864f, 0, 0.5902587356f, 0.4983817807f, -0.6349883666f, 0, 0.5863227872f, 0.494764745f, 0.6414307729f, 0, + 0.6779335087f, 0.2341345225f, 0.6968408593f, 0, 0.7177054546f, -0.6858979348f, 0.120178631f, 0, -0.5328819713f, -0.5205125012f, 0.6671608058f, 0, -0.8654874251f, -0.0700727088f, -0.4960053754f, 0, -0.2861810166f, 0.7952089234f, 0.5345495242f, 0, -0.04849529634f, 0.9810836427f, -0.1874115585f, 0, -0.6358521667f, 0.6058348682f, 0.4781800233f, 0, 0.6254794696f, -0.2861619734f, 0.7258696564f, 0, + -0.2585259868f, 0.5061949264f, -0.8227581726f, 0, 0.02136306781f, 0.5064016808f, -0.8620330371f, 0, 0.200111773f, 0.8599263484f, 0.4695550591f, 0, 0.4743561372f, 0.6014985084f, -0.6427953014f, 0, 0.6622993731f, -0.5202474575f, -0.5391679918f, 0, 0.08084972818f, -0.6532720452f, 0.7527940996f, 0, -0.6893687501f, 0.0592860349f, 0.7219805347f, 0, -0.1121887082f, -0.9673185067f, 0.2273952515f, 0, + 0.7344116094f, 0.5979668656f, -0.3210532909f, 0, 0.5789393465f, -0.2488849713f, 0.7764570201f, 0, 0.6988182827f, 0.3557169806f, -0.6205791146f, 0, -0.8636845529f, -0.2748771249f, -0.4224826141f, 0, -0.4247027957f, -0.4640880967f, 0.777335046f, 0, 0.5257722489f, -0.8427017621f, 0.1158329937f, 0, 0.9343830603f, 0.316302472f, -0.1639543925f, 0, -0.1016836419f, -0.8057303073f, -0.5834887393f, 0, + -0.6529238969f, 0.50602126f, -0.5635892736f, 0, -0.2465286165f, -0.9668205684f, -0.06694497494f, 0, -0.9776897119f, -0.2099250524f, -0.007368825344f, 0, 0.7736893337f, 0.5734244712f, 0.2694238123f, 0, -0.6095087895f, 0.4995678998f, 0.6155736747f, 0, 0.5794535482f, 0.7434546771f, 0.3339292269f, 0, -0.8226211154f, 0.08142581855f, 0.5627293636f, 0, -0.510385483f, 0.4703667658f, 0.7199039967f, 0, + -0.5764971849f, -0.07231656274f, -0.8138926898f, 0, 0.7250628871f, 0.3949971505f, -0.5641463116f, 0, -0.1525424005f, 0.4860840828f, -0.8604958341f, 0, -0.5550976208f, -0.4957820792f, 0.667882296f, 0, -0.1883614327f, 0.9145869398f, 0.357841725f, 0, 0.7625556724f, -0.5414408243f, -0.3540489801f, 0, -0.5870231946f, -0.3226498013f, -0.7424963803f, 0, 0.3051124198f, 0.2262544068f, -0.9250488391f, 0, + 0.6379576059f, 0.577242424f, -0.5097070502f, 0, -0.5966775796f, 0.1454852398f, -0.7891830656f, 0, -0.658330573f, 0.6555487542f, -0.3699414651f, 0, 0.7434892426f, 0.2351084581f, 0.6260573129f, 0, 0.5562114096f, 0.8264360377f, -0.0873632843f, 0, -0.3028940016f, -0.8251527185f, 0.4768419182f, 0, 0.1129343818f, -0.985888439f, -0.1235710781f, 0, 0.5937652891f, -0.5896813806f, 0.5474656618f, 0, + 0.6757964092f, -0.5835758614f, -0.4502648413f, 0, 0.7242302609f, -0.1152719764f, 0.6798550586f, 0, -0.9511914166f, 0.0753623979f, -0.2992580792f, 0, 0.2539470961f, -0.1886339355f, 0.9486454084f, 0, 0.571433621f, -0.1679450851f, -0.8032795685f, 0, -0.06778234979f, 0.3978269256f, 0.9149531629f, 0, 0.6074972649f, 0.733060024f, -0.3058922593f, 0, -0.5435478392f, 0.1675822484f, 0.8224791405f, 0, + -0.5876678086f, -0.3380045064f, -0.7351186982f, 0, -0.7967562402f, 0.04097822706f, -0.6029098428f, 0, -0.1996350917f, 0.8706294745f, 0.4496111079f, 0, -0.02787660336f, -0.9106232682f, -0.4122962022f, 0, -0.7797625996f, -0.6257634692f, 0.01975775581f, 0, -0.5211232846f, 0.7401644346f, -0.4249554471f, 0, 0.8575424857f, 0.4053272873f, -0.3167501783f, 0, 0.1045223322f, 0.8390195772f, -0.5339674439f, 0, + 0.3501822831f, 0.9242524096f, -0.1520850155f, 0, 0.1987849858f, 0.07647613266f, 0.9770547224f, 0, 0.7845996363f, 0.6066256811f, -0.1280964233f, 0, 0.09006737436f, -0.9750989929f, -0.2026569073f, 0, -0.8274343547f, -0.542299559f, 0.1458203587f, 0, -0.3485797732f, -0.415802277f, 0.840000362f, 0, -0.2471778936f, -0.7304819962f, -0.6366310879f, 0, -0.3700154943f, 0.8577948156f, 0.3567584454f, 0, + 0.5913394901f, -0.548311967f, -0.5913303597f, 0, 0.1204873514f, -0.7626472379f, -0.6354935001f, 0, 0.616959265f, 0.03079647928f, 0.7863922953f, 0, 0.1258156836f, -0.6640829889f, -0.7369967419f, 0, -0.6477565124f, -0.1740147258f, -0.7417077429f, 0, 0.6217889313f, -0.7804430448f, -0.06547655076f, 0, 0.6589943422f, -0.6096987708f, 0.4404473475f, 0, -0.2689837504f, -0.6732403169f, -0.6887635427f, 0, + -0.3849775103f, 0.5676542638f, 0.7277093879f, 0, 0.5754444408f, 0.8110471154f, -0.1051963504f, 0, 0.9141593684f, 0.3832947817f, 0.131900567f, 0, -0.107925319f, 0.9245493968f, 0.3654593525f, 0, 0.377977089f, 0.3043148782f, 0.8743716458f, 0, -0.2142885215f, -0.8259286236f, 0.5214617324f, 0, 0.5802544474f, 0.4148098596f, -0.7008834116f, 0, -0.1982660881f, 0.8567161266f, -0.4761596756f, 0, + -0.03381553704f, 0.3773180787f, -0.9254661404f, 0, -0.6867922841f, -0.6656597827f, 0.2919133642f, 0, 0.7731742607f, -0.2875793547f, -0.5652430251f, 0, -0.09655941928f, 0.9193708367f, -0.3813575004f, 0, 0.2715702457f, -0.9577909544f, -0.09426605581f, 0, 0.2451015704f, -0.6917998565f, -0.6792188003f, 0, 0.977700782f, -0.1753855374f, 0.1155036542f, 0, -0.5224739938f, 0.8521606816f, 0.02903615945f, 0, + -0.7734880599f, -0.5261292347f, 0.3534179531f, 0, -0.7134492443f, -0.269547243f, 0.6467878011f, 0, 0.1644037271f, 0.5105846203f, -0.8439637196f, 0, 0.6494635788f, 0.05585611296f, 0.7583384168f, 0, -0.4711970882f, 0.5017280509f, -0.7254255765f, 0, -0.6335764307f, -0.2381686273f, -0.7361091029f, 0, -0.9021533097f, -0.270947803f, -0.3357181763f, 0, -0.3793711033f, 0.872258117f, 0.3086152025f, 0, + -0.6855598966f, -0.3250143309f, 0.6514394162f, 0, 0.2900942212f, -0.7799057743f, -0.5546100667f, 0, -0.2098319339f, 0.85037073f, 0.4825351604f, 0, -0.4592603758f, 0.6598504336f, -0.5947077538f, 0, 0.8715945488f, 0.09616365406f, -0.4807031248f, 0, -0.6776666319f, 0.7118504878f, -0.1844907016f, 0, 0.7044377633f, 0.312427597f, 0.637304036f, 0, -0.7052318886f, -0.2401093292f, -0.6670798253f, 0, + 0.081921007f, -0.7207336136f, -0.6883545647f, 0, -0.6993680906f, -0.5875763221f, -0.4069869034f, 0, -0.1281454481f, 0.6419895885f, 0.7559286424f, 0, -0.6337388239f, -0.6785471501f, -0.3714146849f, 0, 0.5565051903f, -0.2168887573f, -0.8020356851f, 0, -0.5791554484f, 0.7244372011f, -0.3738578718f, 0, 0.1175779076f, -0.7096451073f, 0.6946792478f, 0, -0.6134619607f, 0.1323631078f, 0.7785527795f, 0, + 0.6984635305f, -0.02980516237f, -0.715024719f, 0, 0.8318082963f, -0.3930171956f, 0.3919597455f, 0, 0.1469576422f, 0.05541651717f, -0.9875892167f, 0, 0.708868575f, -0.2690503865f, 0.6520101478f, 0, 0.2726053183f, 0.67369766f, -0.68688995f, 0, -0.6591295371f, 0.3035458599f, -0.6880466294f, 0, 0.4815131379f, -0.7528270071f, 0.4487723203f, 0, 0.9430009463f, 0.1675647412f, -0.2875261255f, 0, + 0.434802957f, 0.7695304522f, -0.4677277752f, 0, 0.3931996188f, 0.594473625f, 0.7014236729f, 0, 0.7254336655f, -0.603925654f, 0.3301814672f, 0, 0.7590235227f, -0.6506083235f, 0.02433313207f, 0, -0.8552768592f, -0.3430042733f, 0.3883935666f, 0, -0.6139746835f, 0.6981725247f, 0.3682257648f, 0, -0.7465905486f, -0.5752009504f, 0.3342849376f, 0, 0.5730065677f, 0.810555537f, -0.1210916791f, 0, + -0.9225877367f, -0.3475211012f, -0.167514036f, 0, -0.7105816789f, -0.4719692027f, -0.5218416899f, 0, -0.08564609717f, 0.3583001386f, 0.929669703f, 0, -0.8279697606f, -0.2043157126f, 0.5222271202f, 0, 0.427944023f, 0.278165994f, 0.8599346446f, 0, 0.5399079671f, -0.7857120652f, -0.3019204161f, 0, 0.5678404253f, -0.5495413974f, -0.6128307303f, 0, -0.9896071041f, 0.1365639107f, -0.04503418428f, 0, + -0.6154342638f, -0.6440875597f, 0.4543037336f, 0, 0.1074204368f, -0.7946340692f, 0.5975094525f, 0, -0.3595449969f, -0.8885529948f, 0.28495784f, 0, -0.2180405296f, 0.1529888965f, 0.9638738118f, 0, -0.7277432317f, -0.6164050508f, -0.3007234646f, 0, 0.7249729114f, -0.00669719484f, 0.6887448187f, 0, -0.5553659455f, -0.5336586252f, 0.6377908264f, 0, 0.5137558015f, 0.7976208196f, -0.3160000073f, 0, + -0.3794024848f, 0.9245608561f, -0.03522751494f, 0, 0.8229248658f, 0.2745365933f, -0.4974176556f, 0, -0.5404114394f, 0.6091141441f, 0.5804613989f, 0, 0.8036581901f, -0.2703029469f, 0.5301601931f, 0, 0.6044318879f, 0.6832968393f, 0.4095943388f, 0, 0.06389988817f, 0.9658208605f, -0.2512108074f, 0, 0.1087113286f, 0.7402471173f, -0.6634877936f, 0, -0.713427712f, -0.6926784018f, 0.1059128479f, 0, + 0.6458897819f, -0.5724548511f, -0.5050958653f, 0, -0.6553931414f, 0.7381471625f, 0.159995615f, 0, 0.3910961323f, 0.9188871375f, -0.05186755998f, 0, -0.4879022471f, -0.5904376907f, 0.6429111375f, 0, 0.6014790094f, 0.7707441366f, -0.2101820095f, 0, -0.5677173047f, 0.7511360995f, 0.3368851762f, 0, 0.7858573506f, 0.226674665f, 0.5753666838f, 0, -0.4520345543f, -0.604222686f, -0.6561857263f, 0, + 0.002272116345f, 0.4132844051f, -0.9105991643f, 0, -0.5815751419f, -0.5162925989f, 0.6286591339f, 0, -0.03703704785f, 0.8273785755f, 0.5604221175f, 0, -0.5119692504f, 0.7953543429f, -0.3244980058f, 0, -0.2682417366f, -0.9572290247f, -0.1084387619f, 0, -0.2322482736f, -0.9679131102f, -0.09594243324f, 0, 0.3554328906f, -0.8881505545f, 0.2913006227f, 0, 0.7346520519f, -0.4371373164f, 0.5188422971f, 0, + 0.9985120116f, 0.04659011161f, -0.02833944577f, 0, -0.3727687496f, -0.9082481361f, 0.1900757285f, 0, 0.91737377f, -0.3483642108f, 0.1925298489f, 0, 0.2714911074f, 0.4147529736f, -0.8684886582f, 0, 0.5131763485f, -0.7116334161f, 0.4798207128f, 0, -0.8737353606f, 0.18886992f, -0.4482350644f, 0, 0.8460043821f, -0.3725217914f, 0.3814499973f, 0, 0.8978727456f, -0.1780209141f, -0.4026575304f, 0, + 0.2178065647f, -0.9698322841f, -0.1094789531f, 0, -0.1518031304f, -0.7788918132f, -0.6085091231f, 0, -0.2600384876f, -0.4755398075f, -0.8403819825f, 0, 0.572313509f, -0.7474340931f, -0.3373418503f, 0, -0.7174141009f, 0.1699017182f, -0.6756111411f, 0, -0.684180784f, 0.02145707593f, -0.7289967412f, 0, -0.2007447902f, 0.06555605789f, -0.9774476623f, 0, -0.1148803697f, -0.8044887315f, 0.5827524187f, 0, + -0.7870349638f, 0.03447489231f, 0.6159443543f, 0, -0.2015596421f, 0.6859872284f, 0.6991389226f, 0, -0.08581082512f, -0.10920836f, -0.9903080513f, 0, 0.5532693395f, 0.7325250401f, -0.396610771f, 0, -0.1842489331f, -0.9777375055f, -0.1004076743f, 0, 0.0775473789f, -0.9111505856f, 0.4047110257f, 0, 0.1399838409f, 0.7601631212f, -0.6344734459f, 0, 0.4484419361f, -0.845289248f, 0.2904925424f, 0 }; #endif diff --git a/source/json/json_spirit.h b/source/json/json_spirit.h index ac1879d5b..dc8a8d50c 100644 --- a/source/json/json_spirit.h +++ b/source/json/json_spirit.h @@ -7,7 +7,7 @@ // json spirit version 4.03 #if defined(_MSC_VER) && (_MSC_VER >= 1020) -# pragma once + #pragma once #endif #include "json_spirit_value.h" diff --git a/source/json/json_spirit_error_position.h b/source/json/json_spirit_error_position.h index fe4af6ff1..49075ba5a 100644 --- a/source/json/json_spirit_error_position.h +++ b/source/json/json_spirit_error_position.h @@ -7,50 +7,40 @@ // json spirit version 4.03 #if defined(_MSC_VER) && (_MSC_VER >= 1020) -# pragma once + #pragma once #endif #include -namespace json_spirit -{ - // An Error_position exception is thrown by the "read_or_throw" functions below on finding an error. - // Note the "read_or_throw" functions are around 3 times slower than the standard functions "read" - // functions that return a bool. - // - struct Error_position : std::runtime_error - { - Error_position(); - Error_position( unsigned int line, unsigned int column, const std::string& reason ); - bool operator==( const Error_position& lhs ) const; - unsigned int line_; - unsigned int column_; - std::string reason_; - }; - - inline Error_position::Error_position() - : std::runtime_error("Error_position") - , line_( 0 ) - , column_( 0 ) - { - } - - inline Error_position::Error_position( unsigned int line, unsigned int column, const std::string& reason ) - : std::runtime_error("Error_position") - , line_( line ) - , column_( column ) - , reason_( reason ) - { - } - - inline bool Error_position::operator==( const Error_position& lhs ) const - { - if( this == &lhs ) return true; - - return ( reason_ == lhs.reason_ ) && - ( line_ == lhs.line_ ) && - ( column_ == lhs.column_ ); -} +namespace json_spirit { + // An Error_position exception is thrown by the "read_or_throw" functions below on finding an error. + // Note the "read_or_throw" functions are around 3 times slower than the standard functions "read" + // functions that return a bool. + // + struct Error_position : std::runtime_error { + Error_position(); + Error_position(unsigned int line, unsigned int column, const std::string& reason); + bool operator==(const Error_position& lhs) const; + unsigned int line_; + unsigned int column_; + std::string reason_; + }; + + inline Error_position::Error_position() : + std::runtime_error("Error_position"), line_(0), column_(0) { + } + + inline Error_position::Error_position(unsigned int line, unsigned int column, const std::string& reason) : + std::runtime_error("Error_position"), line_(line), column_(column), reason_(reason) { + } + + inline bool Error_position::operator==(const Error_position& lhs) const { + if (this == &lhs) { + return true; + } + + return (reason_ == lhs.reason_) && (line_ == lhs.line_) && (column_ == lhs.column_); + } } #endif diff --git a/source/json/json_spirit_reader.cpp b/source/json/json_spirit_reader.cpp index aa4f63722..892ea3796 100644 --- a/source/json/json_spirit_reader.cpp +++ b/source/json/json_spirit_reader.cpp @@ -8,130 +8,106 @@ using namespace json_spirit; -bool json_spirit::read( const std::string& s, Value& value ) -{ - return read_string( s, value ); +bool json_spirit::read(const std::string& s, Value& value) { + return read_string(s, value); } -void json_spirit::read_or_throw( const std::string& s, Value& value ) -{ - read_string_or_throw( s, value ); +void json_spirit::read_or_throw(const std::string& s, Value& value) { + read_string_or_throw(s, value); } -bool json_spirit::read( std::istream& is, Value& value ) -{ - return read_stream( is, value ); +bool json_spirit::read(std::istream& is, Value& value) { + return read_stream(is, value); } -void json_spirit::read_or_throw( std::istream& is, Value& value ) -{ - read_stream_or_throw( is, value ); +void json_spirit::read_or_throw(std::istream& is, Value& value) { + read_stream_or_throw(is, value); } -bool json_spirit::read( std::string::const_iterator& begin, std::string::const_iterator end, Value& value ) -{ - return read_range( begin, end, value ); +bool json_spirit::read(std::string::const_iterator& begin, std::string::const_iterator end, Value& value) { + return read_range(begin, end, value); } -void json_spirit::read_or_throw( std::string::const_iterator& begin, std::string::const_iterator end, Value& value ) -{ - begin = read_range_or_throw( begin, end, value ); +void json_spirit::read_or_throw(std::string::const_iterator& begin, std::string::const_iterator end, Value& value) { + begin = read_range_or_throw(begin, end, value); } #ifndef BOOST_NO_STD_WSTRING -bool json_spirit::read( const std::wstring& s, wValue& value ) -{ - return read_string( s, value ); +bool json_spirit::read(const std::wstring& s, wValue& value) { + return read_string(s, value); } -void json_spirit::read_or_throw( const std::wstring& s, wValue& value ) -{ - read_string_or_throw( s, value ); +void json_spirit::read_or_throw(const std::wstring& s, wValue& value) { + read_string_or_throw(s, value); } -bool json_spirit::read( std::wistream& is, wValue& value ) -{ - return read_stream( is, value ); +bool json_spirit::read(std::wistream& is, wValue& value) { + return read_stream(is, value); } -void json_spirit::read_or_throw( std::wistream& is, wValue& value ) -{ - read_stream_or_throw( is, value ); +void json_spirit::read_or_throw(std::wistream& is, wValue& value) { + read_stream_or_throw(is, value); } -bool json_spirit::read( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value ) -{ - return read_range( begin, end, value ); +bool json_spirit::read(std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value) { + return read_range(begin, end, value); } -void json_spirit::read_or_throw( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value ) -{ - begin = read_range_or_throw( begin, end, value ); +void json_spirit::read_or_throw(std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value) { + begin = read_range_or_throw(begin, end, value); } #endif -bool json_spirit::read( const std::string& s, mValue& value ) -{ - return read_string( s, value ); +bool json_spirit::read(const std::string& s, mValue& value) { + return read_string(s, value); } -void json_spirit::read_or_throw( const std::string& s, mValue& value ) -{ - read_string_or_throw( s, value ); +void json_spirit::read_or_throw(const std::string& s, mValue& value) { + read_string_or_throw(s, value); } -bool json_spirit::read( std::istream& is, mValue& value ) -{ - return read_stream( is, value ); +bool json_spirit::read(std::istream& is, mValue& value) { + return read_stream(is, value); } -void json_spirit::read_or_throw( std::istream& is, mValue& value ) -{ - read_stream_or_throw( is, value ); +void json_spirit::read_or_throw(std::istream& is, mValue& value) { + read_stream_or_throw(is, value); } -bool json_spirit::read( std::string::const_iterator& begin, std::string::const_iterator end, mValue& value ) -{ - return read_range( begin, end, value ); +bool json_spirit::read(std::string::const_iterator& begin, std::string::const_iterator end, mValue& value) { + return read_range(begin, end, value); } -void json_spirit::read_or_throw( std::string::const_iterator& begin, std::string::const_iterator end, mValue& value ) -{ - begin = read_range_or_throw( begin, end, value ); +void json_spirit::read_or_throw(std::string::const_iterator& begin, std::string::const_iterator end, mValue& value) { + begin = read_range_or_throw(begin, end, value); } #ifndef BOOST_NO_STD_WSTRING -bool json_spirit::read( const std::wstring& s, wmValue& value ) -{ - return read_string( s, value ); +bool json_spirit::read(const std::wstring& s, wmValue& value) { + return read_string(s, value); } -void json_spirit::read_or_throw( const std::wstring& s, wmValue& value ) -{ - read_string_or_throw( s, value ); +void json_spirit::read_or_throw(const std::wstring& s, wmValue& value) { + read_string_or_throw(s, value); } -bool json_spirit::read( std::wistream& is, wmValue& value ) -{ - return read_stream( is, value ); +bool json_spirit::read(std::wistream& is, wmValue& value) { + return read_stream(is, value); } -void json_spirit::read_or_throw( std::wistream& is, wmValue& value ) -{ - read_stream_or_throw( is, value ); +void json_spirit::read_or_throw(std::wistream& is, wmValue& value) { + read_stream_or_throw(is, value); } -bool json_spirit::read( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wmValue& value ) -{ - return read_range( begin, end, value ); +bool json_spirit::read(std::wstring::const_iterator& begin, std::wstring::const_iterator end, wmValue& value) { + return read_range(begin, end, value); } -void json_spirit::read_or_throw( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wmValue& value ) -{ - begin = read_range_or_throw( begin, end, value ); +void json_spirit::read_or_throw(std::wstring::const_iterator& begin, std::wstring::const_iterator end, wmValue& value) { + begin = read_range_or_throw(begin, end, value); } #endif diff --git a/source/json/json_spirit_reader.h b/source/json/json_spirit_reader.h index 19de2a517..55bba5080 100644 --- a/source/json/json_spirit_reader.h +++ b/source/json/json_spirit_reader.h @@ -7,54 +7,53 @@ // json spirit version 4.03 #if defined(_MSC_VER) && (_MSC_VER >= 1020) -# pragma once + #pragma once #endif #include "json_spirit_value.h" #include "json_spirit_error_position.h" #include -namespace json_spirit -{ - // functions to reads a JSON values +namespace json_spirit { + // functions to reads a JSON values - bool read( const std::string& s, Value& value ); - bool read( std::istream& is, Value& value ); - bool read( std::string::const_iterator& begin, std::string::const_iterator end, Value& value ); + bool read(const std::string& s, Value& value); + bool read(std::istream& is, Value& value); + bool read(std::string::const_iterator& begin, std::string::const_iterator end, Value& value); - void read_or_throw( const std::string& s, Value& value ); - void read_or_throw( std::istream& is, Value& value ); - void read_or_throw( std::string::const_iterator& begin, std::string::const_iterator end, Value& value ); + void read_or_throw(const std::string& s, Value& value); + void read_or_throw(std::istream& is, Value& value); + void read_or_throw(std::string::const_iterator& begin, std::string::const_iterator end, Value& value); #ifndef BOOST_NO_STD_WSTRING - bool read( const std::wstring& s, wValue& value ); - bool read( std::wistream& is, wValue& value ); - bool read( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value ); + bool read(const std::wstring& s, wValue& value); + bool read(std::wistream& is, wValue& value); + bool read(std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value); - void read_or_throw( const std::wstring& s, wValue& value ); - void read_or_throw( std::wistream& is, wValue& value ); - void read_or_throw( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value ); + void read_or_throw(const std::wstring& s, wValue& value); + void read_or_throw(std::wistream& is, wValue& value); + void read_or_throw(std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value); #endif - bool read( const std::string& s, mValue& value ); - bool read( std::istream& is, mValue& value ); - bool read( std::string::const_iterator& begin, std::string::const_iterator end, mValue& value ); + bool read(const std::string& s, mValue& value); + bool read(std::istream& is, mValue& value); + bool read(std::string::const_iterator& begin, std::string::const_iterator end, mValue& value); - void read_or_throw( const std::string& s, mValue& value ); - void read_or_throw( std::istream& is, mValue& value ); - void read_or_throw( std::string::const_iterator& begin, std::string::const_iterator end, mValue& value ); + void read_or_throw(const std::string& s, mValue& value); + void read_or_throw(std::istream& is, mValue& value); + void read_or_throw(std::string::const_iterator& begin, std::string::const_iterator end, mValue& value); #ifndef BOOST_NO_STD_WSTRING - bool read( const std::wstring& s, wmValue& value ); - bool read( std::wistream& is, wmValue& value ); - bool read( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wmValue& value ); + bool read(const std::wstring& s, wmValue& value); + bool read(std::wistream& is, wmValue& value); + bool read(std::wstring::const_iterator& begin, std::wstring::const_iterator end, wmValue& value); - void read_or_throw( const std::wstring& s, wmValue& value ); - void read_or_throw( std::wistream& is, wmValue& value ); - void read_or_throw( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wmValue& value ); + void read_or_throw(const std::wstring& s, wmValue& value); + void read_or_throw(std::wistream& is, wmValue& value); + void read_or_throw(std::wstring::const_iterator& begin, std::wstring::const_iterator end, wmValue& value); #endif } diff --git a/source/json/json_spirit_reader_template.h b/source/json/json_spirit_reader_template.h index a9cabc95d..3c97c8c5c 100644 --- a/source/json/json_spirit_reader_template.h +++ b/source/json/json_spirit_reader_template.h @@ -9,605 +9,539 @@ #include "json_spirit_value.h" #include "json_spirit_error_position.h" -#define BOOST_SPIRIT_THREADSAFE // uncomment for multithreaded use, requires linking to boost.thread +#define BOOST_SPIRIT_THREADSAFE // uncomment for multithreaded use, requires linking to boost.thread #include #include #include #if BOOST_VERSION >= 103800 - #include - #include - #include - #include - #include - #define spirit_namespace boost::spirit::classic + #include + #include + #include + #include + #include + #define spirit_namespace boost::spirit::classic #else - #include - #include - #include - #include - #include - #define spirit_namespace boost::spirit + #include + #include + #include + #include + #include + #define spirit_namespace boost::spirit #endif -namespace json_spirit -{ - const spirit_namespace::int_parser < boost::int64_t > int64_p = spirit_namespace::int_parser < boost::int64_t >(); - const spirit_namespace::uint_parser< boost::uint64_t > uint64_p = spirit_namespace::uint_parser< boost::uint64_t >(); - - template< class Iter_type > - bool is_eq( Iter_type first, Iter_type last, const char* c_str ) - { - for( Iter_type i = first; i != last; ++i, ++c_str ) - { - if( *c_str == 0 ) return false; - - if( *i != *c_str ) return false; - } - - return true; - } - - template< class Char_type > - Char_type hex_to_num( const Char_type c ) - { - if( ( c >= '0' ) && ( c <= '9' ) ) return c - '0'; - if( ( c >= 'a' ) && ( c <= 'f' ) ) return c - 'a' + 10; - if( ( c >= 'A' ) && ( c <= 'F' ) ) return c - 'A' + 10; - return 0; - } - - template< class Char_type, class Iter_type > - Char_type hex_str_to_char( Iter_type& begin ) - { - const Char_type c1( *( ++begin ) ); - const Char_type c2( *( ++begin ) ); - - return ( hex_to_num( c1 ) << 4 ) + hex_to_num( c2 ); - } - - template< class Char_type, class Iter_type > - Char_type unicode_str_to_char( Iter_type& begin ) - { - const Char_type c1( *( ++begin ) ); - const Char_type c2( *( ++begin ) ); - const Char_type c3( *( ++begin ) ); - const Char_type c4( *( ++begin ) ); - - return ( hex_to_num( c1 ) << 12 ) + - ( hex_to_num( c2 ) << 8 ) + - ( hex_to_num( c3 ) << 4 ) + - hex_to_num( c4 ); - } - - template< class String_type > - void append_esc_char_and_incr_iter( String_type& s, - typename String_type::const_iterator& begin, - typename String_type::const_iterator end ) - { - typedef typename String_type::value_type Char_type; - - const Char_type c2( *begin ); - - switch( c2 ) - { - case 't': s += '\t'; break; - case 'b': s += '\b'; break; - case 'f': s += '\f'; break; - case 'n': s += '\n'; break; - case 'r': s += '\r'; break; - case '\\': s += '\\'; break; - case '/': s += '/'; break; - case '"': s += '"'; break; - case 'x': - { - if( end - begin >= 3 ) // expecting "xHH..." - { - s += hex_str_to_char< Char_type >( begin ); - } - break; - } - case 'u': - { - if( end - begin >= 5 ) // expecting "uHHHH..." - { - s += unicode_str_to_char< Char_type >( begin ); - } - break; - } - } - } - - template< class String_type > - String_type substitute_esc_chars( typename String_type::const_iterator begin, - typename String_type::const_iterator end ) - { - typedef typename String_type::const_iterator Iter_type; - - if( end - begin < 2 ) return String_type( begin, end ); - - String_type result; - - result.reserve( end - begin ); - - const Iter_type end_minus_1( end - 1 ); - - Iter_type substr_start = begin; - Iter_type i = begin; - - for( ; i < end_minus_1; ++i ) - { - if( *i == '\\' ) - { - result.append( substr_start, i ); - - ++i; // skip the '\' - - append_esc_char_and_incr_iter( result, i, end ); - - substr_start = i + 1; - } - } - - result.append( substr_start, end ); - - return result; - } - - template< class String_type > - String_type get_str_( typename String_type::const_iterator begin, - typename String_type::const_iterator end ) - { - assert( end - begin >= 2 ); - - typedef typename String_type::const_iterator Iter_type; - - Iter_type str_without_quotes( ++begin ); - Iter_type end_without_quotes( --end ); - - return substitute_esc_chars< String_type >( str_without_quotes, end_without_quotes ); - } - - inline std::string get_str( std::string::const_iterator begin, std::string::const_iterator end ) - { - return get_str_< std::string >( begin, end ); - } - - inline std::wstring get_str( std::wstring::const_iterator begin, std::wstring::const_iterator end ) - { - return get_str_< std::wstring >( begin, end ); - } - - template< class String_type, class Iter_type > - String_type get_str( Iter_type begin, Iter_type end ) - { - const String_type tmp( begin, end ); // convert multipass iterators to string iterators - - return get_str( tmp.begin(), tmp.end() ); - } - - // this class's methods get called by the spirit parse resulting - // in the creation of a JSON object or array - // - // NB Iter_type could be a std::string iterator, wstring iterator, a position iterator or a multipass iterator - // - template< class Value_type, class Iter_type > - class Semantic_actions - { - public: - - typedef typename Value_type::Config_type Config_type; - typedef typename Config_type::String_type String_type; - typedef typename Config_type::Object_type Object_type; - typedef typename Config_type::Array_type Array_type; - typedef typename String_type::value_type Char_type; - - Semantic_actions( Value_type& value ) - : value_( value ) - , current_p_( 0 ) - { - } - - void begin_obj( Char_type c ) - { - assert( c == '{' ); +namespace json_spirit { + const spirit_namespace::int_parser int64_p = spirit_namespace::int_parser(); + const spirit_namespace::uint_parser uint64_p = spirit_namespace::uint_parser(); + + template + bool is_eq(Iter_type first, Iter_type last, const char* c_str) { + for (Iter_type i = first; i != last; ++i, ++c_str) { + if (*c_str == 0) { + return false; + } + + if (*i != *c_str) { + return false; + } + } + + return true; + } + + template + Char_type hex_to_num(const Char_type c) { + if ((c >= '0') && (c <= '9')) { + return c - '0'; + } + if ((c >= 'a') && (c <= 'f')) { + return c - 'a' + 10; + } + if ((c >= 'A') && (c <= 'F')) { + return c - 'A' + 10; + } + return 0; + } + + template + Char_type hex_str_to_char(Iter_type& begin) { + const Char_type c1(*(++begin)); + const Char_type c2(*(++begin)); + + return (hex_to_num(c1) << 4) + hex_to_num(c2); + } + + template + Char_type unicode_str_to_char(Iter_type& begin) { + const Char_type c1(*(++begin)); + const Char_type c2(*(++begin)); + const Char_type c3(*(++begin)); + const Char_type c4(*(++begin)); + + return (hex_to_num(c1) << 12) + (hex_to_num(c2) << 8) + (hex_to_num(c3) << 4) + hex_to_num(c4); + } + + template + void append_esc_char_and_incr_iter(String_type& s, typename String_type::const_iterator& begin, typename String_type::const_iterator end) { + typedef typename String_type::value_type Char_type; + + const Char_type c2(*begin); + + switch (c2) { + case 't': + s += '\t'; + break; + case 'b': + s += '\b'; + break; + case 'f': + s += '\f'; + break; + case 'n': + s += '\n'; + break; + case 'r': + s += '\r'; + break; + case '\\': + s += '\\'; + break; + case '/': + s += '/'; + break; + case '"': + s += '"'; + break; + case 'x': { + if (end - begin >= 3) // expecting "xHH..." + { + s += hex_str_to_char(begin); + } + break; + } + case 'u': { + if (end - begin >= 5) // expecting "uHHHH..." + { + s += unicode_str_to_char(begin); + } + break; + } + } + } + + template + String_type substitute_esc_chars(typename String_type::const_iterator begin, typename String_type::const_iterator end) { + typedef typename String_type::const_iterator Iter_type; + + if (end - begin < 2) { + return String_type(begin, end); + } + + String_type result; + + result.reserve(end - begin); + + const Iter_type end_minus_1(end - 1); + + Iter_type substr_start = begin; + Iter_type i = begin; + + for (; i < end_minus_1; ++i) { + if (*i == '\\') { + result.append(substr_start, i); + + ++i; // skip the '\' + + append_esc_char_and_incr_iter(result, i, end); + + substr_start = i + 1; + } + } + + result.append(substr_start, end); + + return result; + } + + template + String_type get_str_(typename String_type::const_iterator begin, typename String_type::const_iterator end) { + assert(end - begin >= 2); + + typedef typename String_type::const_iterator Iter_type; + + Iter_type str_without_quotes(++begin); + Iter_type end_without_quotes(--end); + + return substitute_esc_chars(str_without_quotes, end_without_quotes); + } + + inline std::string get_str(std::string::const_iterator begin, std::string::const_iterator end) { + return get_str_(begin, end); + } + + inline std::wstring get_str(std::wstring::const_iterator begin, std::wstring::const_iterator end) { + return get_str_(begin, end); + } + + template + String_type get_str(Iter_type begin, Iter_type end) { + const String_type tmp(begin, end); // convert multipass iterators to string iterators + + return get_str(tmp.begin(), tmp.end()); + } + + // this class's methods get called by the spirit parse resulting + // in the creation of a JSON object or array + // + // NB Iter_type could be a std::string iterator, wstring iterator, a position iterator or a multipass iterator + // + template + class Semantic_actions { + public: + typedef typename Value_type::Config_type Config_type; + typedef typename Config_type::String_type String_type; + typedef typename Config_type::Object_type Object_type; + typedef typename Config_type::Array_type Array_type; + typedef typename String_type::value_type Char_type; - begin_compound< Object_type >(); - } + Semantic_actions(Value_type& value) : + value_(value), current_p_(0) { + } - void end_obj( Char_type c ) - { - assert( c == '}' ); + void begin_obj(Char_type c) { + assert(c == '{'); - end_compound(); - } + begin_compound(); + } - void begin_array( Char_type c ) - { - assert( c == '[' ); + void end_obj(Char_type c) { + assert(c == '}'); - begin_compound< Array_type >(); - } - - void end_array( Char_type c ) - { - assert( c == ']' ); - - end_compound(); - } - - void new_name( Iter_type begin, Iter_type end ) - { - assert( current_p_->type() == obj_type ); - - name_ = get_str< String_type >( begin, end ); - } - - void new_str( Iter_type begin, Iter_type end ) - { - add_to_current( get_str< String_type >( begin, end ) ); - } - - void new_true( Iter_type begin, Iter_type end ) - { - assert( is_eq( begin, end, "true" ) ); - - add_to_current( true ); - } - - void new_false( Iter_type begin, Iter_type end ) - { - assert( is_eq( begin, end, "false" ) ); - - add_to_current( false ); - } - - void new_null( Iter_type begin, Iter_type end ) - { - assert( is_eq( begin, end, "null" ) ); - - add_to_current( Value_type() ); - } - - void new_int( boost::int64_t i ) - { - add_to_current( i ); - } - - void new_uint64( boost::uint64_t ui ) - { - add_to_current( ui ); - } - - void new_real( double d ) - { - add_to_current( d ); - } - - private: - - Semantic_actions& operator=( const Semantic_actions& ); - // to prevent "assignment operator could not be generated" warning - - Value_type* add_first( const Value_type& value ) - { - assert( current_p_ == 0 ); - - value_ = value; - current_p_ = &value_; - return current_p_; - } - - template< class Array_or_obj > - void begin_compound() - { - if( current_p_ == 0 ) - { - add_first( Array_or_obj() ); - } - else - { - stack_.push_back( current_p_ ); - - Array_or_obj new_array_or_obj; // avoid copy by building newd array or object in place - - current_p_ = add_to_current( new_array_or_obj ); - } - } - - void end_compound() - { - if( current_p_ != &value_ ) - { - current_p_ = stack_.back(); - - stack_.pop_back(); - } - } - - Value_type* add_to_current( const Value_type& value ) - { - if( current_p_ == 0 ) - { - return add_first( value ); - } - else if( current_p_->type() == array_type ) - { - current_p_->get_array().push_back( value ); - - return ¤t_p_->get_array().back(); - } - - assert( current_p_->type() == obj_type ); - - return &Config_type::add( current_p_->get_obj(), name_, value ); - } - - Value_type& value_; // this is the object or array that is being created - Value_type* current_p_; // the child object or array that is currently being constructed - - std::vector< Value_type* > stack_; // previous child objects and arrays - - String_type name_; // of current name/value pair - }; - - template< typename Iter_type > - void throw_error( spirit_namespace::position_iterator< Iter_type > i, const std::string& reason ) - { - throw Error_position( i.get_position().line, i.get_position().column, reason ); - } - - template< typename Iter_type > - void throw_error( Iter_type i, const std::string& reason ) - { - throw reason; - } - - // the spirit grammer - // - template< class Value_type, class Iter_type > - class Json_grammer : public spirit_namespace::grammar< Json_grammer< Value_type, Iter_type > > - { - public: - - typedef Semantic_actions< Value_type, Iter_type > Semantic_actions_t; - - Json_grammer( Semantic_actions_t& semantic_actions ) - : actions_( semantic_actions ) - { - } - - static void throw_not_value( Iter_type begin, Iter_type end ) - { - throw_error( begin, "not a value" ); - } - - static void throw_not_array( Iter_type begin, Iter_type end ) - { - throw_error( begin, "not an array" ); - } - - static void throw_not_object( Iter_type begin, Iter_type end ) - { - throw_error( begin, "not an object" ); - } - - static void throw_not_pair( Iter_type begin, Iter_type end ) - { - throw_error( begin, "not a pair" ); - } - - static void throw_not_colon( Iter_type begin, Iter_type end ) - { - throw_error( begin, "no colon in pair" ); - } - - static void throw_not_string( Iter_type begin, Iter_type end ) - { - throw_error( begin, "not a string" ); - } - - template< typename ScannerT > - class definition - { - public: - - definition( const Json_grammer& self ) - { - using namespace spirit_namespace; - - typedef typename Value_type::String_type::value_type Char_type; - - // first we convert the semantic action class methods to functors with the - // parameter signature expected by spirit - - typedef boost::function< void( Char_type ) > Char_action; - typedef boost::function< void( Iter_type, Iter_type ) > Str_action; - typedef boost::function< void( double ) > Real_action; - typedef boost::function< void( boost::int64_t ) > Int_action; - typedef boost::function< void( boost::uint64_t ) > Uint64_action; - - Char_action begin_obj ( boost::bind( &Semantic_actions_t::begin_obj, &self.actions_, _1 ) ); - Char_action end_obj ( boost::bind( &Semantic_actions_t::end_obj, &self.actions_, _1 ) ); - Char_action begin_array( boost::bind( &Semantic_actions_t::begin_array, &self.actions_, _1 ) ); - Char_action end_array ( boost::bind( &Semantic_actions_t::end_array, &self.actions_, _1 ) ); - Str_action new_name ( boost::bind( &Semantic_actions_t::new_name, &self.actions_, _1, _2 ) ); - Str_action new_str ( boost::bind( &Semantic_actions_t::new_str, &self.actions_, _1, _2 ) ); - Str_action new_true ( boost::bind( &Semantic_actions_t::new_true, &self.actions_, _1, _2 ) ); - Str_action new_false ( boost::bind( &Semantic_actions_t::new_false, &self.actions_, _1, _2 ) ); - Str_action new_null ( boost::bind( &Semantic_actions_t::new_null, &self.actions_, _1, _2 ) ); - Real_action new_real ( boost::bind( &Semantic_actions_t::new_real, &self.actions_, _1 ) ); - Int_action new_int ( boost::bind( &Semantic_actions_t::new_int, &self.actions_, _1 ) ); - Uint64_action new_uint64 ( boost::bind( &Semantic_actions_t::new_uint64, &self.actions_, _1 ) ); - - // actual grammer - - json_ - = value_ | eps_p[ &throw_not_value ] - ; - - value_ - = string_[ new_str ] - | number_ - | object_ - | array_ - | str_p( "true" ) [ new_true ] - | str_p( "false" )[ new_false ] - | str_p( "null" ) [ new_null ] - ; - - object_ - = ch_p('{')[ begin_obj ] - >> !members_ - >> ( ch_p('}')[ end_obj ] | eps_p[ &throw_not_object ] ) - ; - - members_ - = pair_ >> *( ',' >> pair_ ) - ; - - pair_ - = string_[ new_name ] - >> ( ':' | eps_p[ &throw_not_colon ] ) - >> ( value_ | eps_p[ &throw_not_value ] ) - ; - - array_ - = ch_p('[')[ begin_array ] - >> !elements_ - >> ( ch_p(']')[ end_array ] | eps_p[ &throw_not_array ] ) - ; - - elements_ - = value_ >> *( ',' >> value_ ) - ; - - string_ - = lexeme_d // this causes white space inside a string to be retained - [ - confix_p - ( - '"', - *lex_escape_ch_p, - '"' - ) - ] - ; - - number_ - = strict_real_p[ new_real ] - | int64_p [ new_int ] - | uint64_p [ new_uint64 ] - ; - } - - spirit_namespace::rule< ScannerT > json_, object_, members_, pair_, array_, elements_, value_, string_, number_; - - const spirit_namespace::rule< ScannerT >& start() const { return json_; } - }; - - private: - - Json_grammer& operator=( const Json_grammer& ); // to prevent "assignment operator could not be generated" warning - - Semantic_actions_t& actions_; - }; - - template< class Iter_type, class Value_type > - Iter_type read_range_or_throw( Iter_type begin, Iter_type end, Value_type& value ) - { - Semantic_actions< Value_type, Iter_type > semantic_actions( value ); - - const spirit_namespace::parse_info< Iter_type > info = - spirit_namespace::parse( begin, end, - Json_grammer< Value_type, Iter_type >( semantic_actions ), - spirit_namespace::space_p ); - - if( !info.hit ) - { - assert( false ); // in theory exception should already have been thrown - throw_error( info.stop, "error" ); - } - - return info.stop; - } - - template< class Iter_type, class Value_type > - void add_posn_iter_and_read_range_or_throw( Iter_type begin, Iter_type end, Value_type& value ) - { - typedef spirit_namespace::position_iterator< Iter_type > Posn_iter_t; - - const Posn_iter_t posn_begin( begin, end ); - const Posn_iter_t posn_end( end, end ); - - read_range_or_throw( posn_begin, posn_end, value ); - } - - template< class Iter_type, class Value_type > - bool read_range( Iter_type& begin, Iter_type end, Value_type& value ) - { - try - { - begin = read_range_or_throw( begin, end, value ); - - return true; - } - catch( ... ) - { - return false; - } - } - - template< class String_type, class Value_type > - void read_string_or_throw( const String_type& s, Value_type& value ) - { - add_posn_iter_and_read_range_or_throw( s.begin(), s.end(), value ); - } - - template< class String_type, class Value_type > - bool read_string( const String_type& s, Value_type& value ) - { - typename String_type::const_iterator begin = s.begin(); - - return read_range( begin, s.end(), value ); - } - - template< class Istream_type > - struct Multi_pass_iters - { - typedef typename Istream_type::char_type Char_type; - typedef std::istream_iterator< Char_type, Char_type > istream_iter; - typedef spirit_namespace::multi_pass< istream_iter > Mp_iter; - - Multi_pass_iters( Istream_type& is ) - { - is.unsetf( std::ios::skipws ); - - begin_ = spirit_namespace::make_multi_pass( istream_iter( is ) ); - end_ = spirit_namespace::make_multi_pass( istream_iter() ); - } - - Mp_iter begin_; - Mp_iter end_; - }; - - template< class Istream_type, class Value_type > - bool read_stream( Istream_type& is, Value_type& value ) - { - Multi_pass_iters< Istream_type > mp_iters( is ); - - return read_range( mp_iters.begin_, mp_iters.end_, value ); - } - - template< class Istream_type, class Value_type > - void read_stream_or_throw( Istream_type& is, Value_type& value ) - { - const Multi_pass_iters< Istream_type > mp_iters( is ); - - add_posn_iter_and_read_range_or_throw( mp_iters.begin_, mp_iters.end_, value ); - } + end_compound(); + } + + void begin_array(Char_type c) { + assert(c == '['); + + begin_compound(); + } + + void end_array(Char_type c) { + assert(c == ']'); + + end_compound(); + } + + void new_name(Iter_type begin, Iter_type end) { + assert(current_p_->type() == obj_type); + + name_ = get_str(begin, end); + } + + void new_str(Iter_type begin, Iter_type end) { + add_to_current(get_str(begin, end)); + } + + void new_true(Iter_type begin, Iter_type end) { + assert(is_eq(begin, end, "true")); + + add_to_current(true); + } + + void new_false(Iter_type begin, Iter_type end) { + assert(is_eq(begin, end, "false")); + + add_to_current(false); + } + + void new_null(Iter_type begin, Iter_type end) { + assert(is_eq(begin, end, "null")); + + add_to_current(Value_type()); + } + + void new_int(boost::int64_t i) { + add_to_current(i); + } + + void new_uint64(boost::uint64_t ui) { + add_to_current(ui); + } + + void new_real(double d) { + add_to_current(d); + } + + private: + Semantic_actions& operator=(const Semantic_actions&); + // to prevent "assignment operator could not be generated" warning + + Value_type* add_first(const Value_type& value) { + assert(current_p_ == 0); + + value_ = value; + current_p_ = &value_; + return current_p_; + } + + template + void begin_compound() { + if (current_p_ == 0) { + add_first(Array_or_obj()); + } else { + stack_.push_back(current_p_); + + Array_or_obj new_array_or_obj; // avoid copy by building newd array or object in place + + current_p_ = add_to_current(new_array_or_obj); + } + } + + void end_compound() { + if (current_p_ != &value_) { + current_p_ = stack_.back(); + + stack_.pop_back(); + } + } + + Value_type* add_to_current(const Value_type& value) { + if (current_p_ == 0) { + return add_first(value); + } else if (current_p_->type() == array_type) { + current_p_->get_array().push_back(value); + + return ¤t_p_->get_array().back(); + } + + assert(current_p_->type() == obj_type); + + return &Config_type::add(current_p_->get_obj(), name_, value); + } + + Value_type& value_; // this is the object or array that is being created + Value_type* current_p_; // the child object or array that is currently being constructed + + std::vector stack_; // previous child objects and arrays + + String_type name_; // of current name/value pair + }; + + template + void throw_error(spirit_namespace::position_iterator i, const std::string& reason) { + throw Error_position(i.get_position().line, i.get_position().column, reason); + } + + template + void throw_error(Iter_type i, const std::string& reason) { + throw reason; + } + + // the spirit grammer + // + template + class Json_grammer : public spirit_namespace::grammar> { + public: + typedef Semantic_actions Semantic_actions_t; + + Json_grammer(Semantic_actions_t& semantic_actions) : + actions_(semantic_actions) { + } + + static void throw_not_value(Iter_type begin, Iter_type end) { + throw_error(begin, "not a value"); + } + + static void throw_not_array(Iter_type begin, Iter_type end) { + throw_error(begin, "not an array"); + } + + static void throw_not_object(Iter_type begin, Iter_type end) { + throw_error(begin, "not an object"); + } + + static void throw_not_pair(Iter_type begin, Iter_type end) { + throw_error(begin, "not a pair"); + } + + static void throw_not_colon(Iter_type begin, Iter_type end) { + throw_error(begin, "no colon in pair"); + } + + static void throw_not_string(Iter_type begin, Iter_type end) { + throw_error(begin, "not a string"); + } + + template + class definition { + public: + definition(const Json_grammer& self) { + using namespace spirit_namespace; + + typedef typename Value_type::String_type::value_type Char_type; + + // first we convert the semantic action class methods to functors with the + // parameter signature expected by spirit + + typedef boost::function Char_action; + typedef boost::function Str_action; + typedef boost::function Real_action; + typedef boost::function Int_action; + typedef boost::function Uint64_action; + + Char_action begin_obj(boost::bind(&Semantic_actions_t::begin_obj, &self.actions_, _1)); + Char_action end_obj(boost::bind(&Semantic_actions_t::end_obj, &self.actions_, _1)); + Char_action begin_array(boost::bind(&Semantic_actions_t::begin_array, &self.actions_, _1)); + Char_action end_array(boost::bind(&Semantic_actions_t::end_array, &self.actions_, _1)); + Str_action new_name(boost::bind(&Semantic_actions_t::new_name, &self.actions_, _1, _2)); + Str_action new_str(boost::bind(&Semantic_actions_t::new_str, &self.actions_, _1, _2)); + Str_action new_true(boost::bind(&Semantic_actions_t::new_true, &self.actions_, _1, _2)); + Str_action new_false(boost::bind(&Semantic_actions_t::new_false, &self.actions_, _1, _2)); + Str_action new_null(boost::bind(&Semantic_actions_t::new_null, &self.actions_, _1, _2)); + Real_action new_real(boost::bind(&Semantic_actions_t::new_real, &self.actions_, _1)); + Int_action new_int(boost::bind(&Semantic_actions_t::new_int, &self.actions_, _1)); + Uint64_action new_uint64(boost::bind(&Semantic_actions_t::new_uint64, &self.actions_, _1)); + + // actual grammer + + json_ + = value_ | eps_p[&throw_not_value]; + + value_ + = string_[new_str] + | number_ + | object_ + | array_ + | str_p("true")[new_true] + | str_p("false")[new_false] + | str_p("null")[new_null]; + + object_ + = ch_p('{')[begin_obj] + >> !members_ + >> (ch_p('}')[end_obj] | eps_p[&throw_not_object]); + + members_ + = pair_ >> *(',' >> pair_); + + pair_ + = string_[new_name] + >> (':' | eps_p[&throw_not_colon]) + >> (value_ | eps_p[&throw_not_value]); + + array_ + = ch_p('[')[begin_array] + >> !elements_ + >> (ch_p(']')[end_array] | eps_p[&throw_not_array]); + + elements_ + = value_ >> *(',' >> value_); + + string_ + = lexeme_d // this causes white space inside a string to be retained + [confix_p( + '"', + *lex_escape_ch_p, + '"' + )]; + + number_ + = strict_real_p[new_real] + | int64_p[new_int] + | uint64_p[new_uint64]; + } + + spirit_namespace::rule json_, object_, members_, pair_, array_, elements_, value_, string_, number_; + + const spirit_namespace::rule& start() const { + return json_; + } + }; + + private: + Json_grammer& operator=(const Json_grammer&); // to prevent "assignment operator could not be generated" warning + + Semantic_actions_t& actions_; + }; + + template + Iter_type read_range_or_throw(Iter_type begin, Iter_type end, Value_type& value) { + Semantic_actions semantic_actions(value); + + const spirit_namespace::parse_info info = spirit_namespace::parse(begin, end, Json_grammer(semantic_actions), spirit_namespace::space_p); + + if (!info.hit) { + assert(false); // in theory exception should already have been thrown + throw_error(info.stop, "error"); + } + + return info.stop; + } + + template + void add_posn_iter_and_read_range_or_throw(Iter_type begin, Iter_type end, Value_type& value) { + typedef spirit_namespace::position_iterator Posn_iter_t; + + const Posn_iter_t posn_begin(begin, end); + const Posn_iter_t posn_end(end, end); + + read_range_or_throw(posn_begin, posn_end, value); + } + + template + bool read_range(Iter_type& begin, Iter_type end, Value_type& value) { + try { + begin = read_range_or_throw(begin, end, value); + + return true; + } catch (...) { + return false; + } + } + + template + void read_string_or_throw(const String_type& s, Value_type& value) { + add_posn_iter_and_read_range_or_throw(s.begin(), s.end(), value); + } + + template + bool read_string(const String_type& s, Value_type& value) { + typename String_type::const_iterator begin = s.begin(); + + return read_range(begin, s.end(), value); + } + + template + struct Multi_pass_iters { + typedef typename Istream_type::char_type Char_type; + typedef std::istream_iterator istream_iter; + typedef spirit_namespace::multi_pass Mp_iter; + + Multi_pass_iters(Istream_type& is) { + is.unsetf(std::ios::skipws); + + begin_ = spirit_namespace::make_multi_pass(istream_iter(is)); + end_ = spirit_namespace::make_multi_pass(istream_iter()); + } + + Mp_iter begin_; + Mp_iter end_; + }; + + template + bool read_stream(Istream_type& is, Value_type& value) { + Multi_pass_iters mp_iters(is); + + return read_range(mp_iters.begin_, mp_iters.end_, value); + } + + template + void read_stream_or_throw(Istream_type& is, Value_type& value) { + const Multi_pass_iters mp_iters(is); + + add_posn_iter_and_read_range_or_throw(mp_iters.begin_, mp_iters.end_, value); + } } #endif - diff --git a/source/json/json_spirit_stream_reader.h b/source/json/json_spirit_stream_reader.h index 1a94392a8..77ec0b20a 100644 --- a/source/json/json_spirit_stream_reader.h +++ b/source/json/json_spirit_stream_reader.h @@ -7,64 +7,51 @@ // json spirit version 4.03 #if defined(_MSC_VER) && (_MSC_VER >= 1020) -# pragma once + #pragma once #endif #include "json_spirit_reader_template.h" -namespace json_spirit -{ - // these classes allows you to read multiple top level contiguous values from a stream, - // the normal stream read functions have a bug that prevent multiple top level values - // from being read unless they are separated by spaces - - template< class Istream_type, class Value_type > - class Stream_reader - { - public: - - Stream_reader( Istream_type& is ) - : iters_( is ) - { - } - - bool read_next( Value_type& value ) - { - return read_range( iters_.begin_, iters_.end_, value ); - } - - private: - - typedef Multi_pass_iters< Istream_type > Mp_iters; - - Mp_iters iters_; - }; - - template< class Istream_type, class Value_type > - class Stream_reader_thrower - { - public: - - Stream_reader_thrower( Istream_type& is ) - : iters_( is ) - , posn_begin_( iters_.begin_, iters_.end_ ) - , posn_end_( iters_.end_, iters_.end_ ) - { - } - - void read_next( Value_type& value ) - { - posn_begin_ = read_range_or_throw( posn_begin_, posn_end_, value ); - } - - private: - - typedef Multi_pass_iters< Istream_type > Mp_iters; - typedef spirit_namespace::position_iterator< typename Mp_iters::Mp_iter > Posn_iter_t; - - Mp_iters iters_; - Posn_iter_t posn_begin_, posn_end_; - }; +namespace json_spirit { + // these classes allows you to read multiple top level contiguous values from a stream, + // the normal stream read functions have a bug that prevent multiple top level values + // from being read unless they are separated by spaces + + template + class Stream_reader { + public: + Stream_reader(Istream_type& is) : + iters_(is) { + } + + bool read_next(Value_type& value) { + return read_range(iters_.begin_, iters_.end_, value); + } + + private: + typedef Multi_pass_iters Mp_iters; + + Mp_iters iters_; + }; + + template + class Stream_reader_thrower { + public: + Stream_reader_thrower(Istream_type& is) : + iters_(is), posn_begin_(iters_.begin_, iters_.end_), posn_end_(iters_.end_, iters_.end_) { + } + + void read_next(Value_type& value) { + posn_begin_ = read_range_or_throw(posn_begin_, posn_end_, value); + } + + private: + typedef Multi_pass_iters Mp_iters; + typedef spirit_namespace::position_iterator Posn_iter_t; + + Mp_iters iters_; + Posn_iter_t posn_begin_, posn_end_; + }; } #endif diff --git a/source/json/json_spirit_utils.h b/source/json/json_spirit_utils.h index 0d3bc8a4a..c8457a565 100644 --- a/source/json/json_spirit_utils.h +++ b/source/json/json_spirit_utils.h @@ -7,55 +7,47 @@ // json spirit version 4.03 #if defined(_MSC_VER) && (_MSC_VER >= 1020) -# pragma once + #pragma once #endif #include "json_spirit_value.h" #include -namespace json_spirit -{ - template< class Obj_t, class Map_t > - void obj_to_map( const Obj_t& obj, Map_t& mp_obj ) - { - mp_obj.clear(); +namespace json_spirit { + template + void obj_to_map(const Obj_t& obj, Map_t& mp_obj) { + mp_obj.clear(); - for( typename Obj_t::const_iterator i = obj.begin(); i != obj.end(); ++i ) - { - mp_obj[ i->name_ ] = i->value_; - } - } + for (typename Obj_t::const_iterator i = obj.begin(); i != obj.end(); ++i) { + mp_obj[i->name_] = i->value_; + } + } - template< class Obj_t, class Map_t > - void map_to_obj( const Map_t& mp_obj, Obj_t& obj ) - { - obj.clear(); + template + void map_to_obj(const Map_t& mp_obj, Obj_t& obj) { + obj.clear(); - for( typename Map_t::const_iterator i = mp_obj.begin(); i != mp_obj.end(); ++i ) - { - obj.push_back( typename Obj_t::value_type( i->first, i->second ) ); - } - } + for (typename Map_t::const_iterator i = mp_obj.begin(); i != mp_obj.end(); ++i) { + obj.push_back(typename Obj_t::value_type(i->first, i->second)); + } + } - typedef std::map< std::string, Value > Mapped_obj; + typedef std::map Mapped_obj; #ifndef BOOST_NO_STD_WSTRING - typedef std::map< std::wstring, wValue > wMapped_obj; + typedef std::map wMapped_obj; #endif - template< class Object_type, class String_type > - const typename Object_type::value_type::Value_type& find_value( const Object_type& obj, const String_type& name ) - { - for( typename Object_type::const_iterator i = obj.begin(); i != obj.end(); ++i ) - { - if( i->name_ == name ) - { - return i->value_; - } - } - - return Object_type::value_type::Value_type::null; - } + template + const typename Object_type::value_type::Value_type& find_value(const Object_type& obj, const String_type& name) { + for (typename Object_type::const_iterator i = obj.begin(); i != obj.end(); ++i) { + if (i->name_ == name) { + return i->value_; + } + } + + return Object_type::value_type::Value_type::null; + } } #endif diff --git a/source/json/json_spirit_value.h b/source/json/json_spirit_value.h index 10281d8a0..9f5b3db0a 100644 --- a/source/json/json_spirit_value.h +++ b/source/json/json_spirit_value.h @@ -7,7 +7,7 @@ // json spirit version 4.03 #if defined(_MSC_VER) && (_MSC_VER >= 1020) -# pragma once + #pragma once #endif #include @@ -21,512 +21,444 @@ #include #include -namespace json_spirit -{ - enum Value_type{ obj_type, array_type, str_type, bool_type, int_type, real_type, null_type }; +namespace json_spirit { + enum Value_type { obj_type, + array_type, + str_type, + bool_type, + int_type, + real_type, + null_type }; - template< class Config > // Config determines whether the value uses std::string or std::wstring and - // whether JSON Objects are represented as vectors or maps - class Value_impl - { - public: + template // Config determines whether the value uses std::string or std::wstring and + // whether JSON Objects are represented as vectors or maps + class Value_impl { + public: + typedef Config Config_type; + typedef typename Config::String_type String_type; + typedef typename Config::Object_type Object; + typedef typename Config::Array_type Array; + typedef typename String_type::const_pointer Const_str_ptr; // eg const char* - typedef Config Config_type; - typedef typename Config::String_type String_type; - typedef typename Config::Object_type Object; - typedef typename Config::Array_type Array; - typedef typename String_type::const_pointer Const_str_ptr; // eg const char* + Value_impl(); // creates null value + Value_impl(Const_str_ptr value); + Value_impl(const String_type& value); + Value_impl(const Object& value); + Value_impl(const Array& value); + Value_impl(bool value); + Value_impl(int value); + Value_impl(boost::int64_t value); + Value_impl(boost::uint64_t value); + Value_impl(double value); - Value_impl(); // creates null value - Value_impl( Const_str_ptr value ); - Value_impl( const String_type& value ); - Value_impl( const Object& value ); - Value_impl( const Array& value ); - Value_impl( bool value ); - Value_impl( int value ); - Value_impl( boost::int64_t value ); - Value_impl( boost::uint64_t value ); - Value_impl( double value ); + Value_impl(const Value_impl& other); - Value_impl( const Value_impl& other ); + bool operator==(const Value_impl& lhs) const; - bool operator==( const Value_impl& lhs ) const; + Value_impl& operator=(const Value_impl& lhs); - Value_impl& operator=( const Value_impl& lhs ); + Value_type type() const; - Value_type type() const; + bool is_uint64() const; + bool is_null() const; - bool is_uint64() const; - bool is_null() const; + const String_type& get_str() const; + const Object& get_obj() const; + const Array& get_array() const; + bool get_bool() const; + int get_int() const; + boost::int64_t get_int64() const; + boost::uint64_t get_uint64() const; + double get_real() const; - const String_type& get_str() const; - const Object& get_obj() const; - const Array& get_array() const; - bool get_bool() const; - int get_int() const; - boost::int64_t get_int64() const; - boost::uint64_t get_uint64() const; - double get_real() const; + Object& get_obj(); + Array& get_array(); - Object& get_obj(); - Array& get_array(); + template + T get_value() const; // example usage: int i = value.get_value< int >(); + // or double d = value.get_value< double >(); - template< typename T > T get_value() const; // example usage: int i = value.get_value< int >(); - // or double d = value.get_value< double >(); + static const Value_impl null; - static const Value_impl null; + private: + void check_type(const Value_type vtype) const; - private: + typedef boost::variant, boost::recursive_wrapper, bool, boost::int64_t, double> Variant; - void check_type( const Value_type vtype ) const; + Value_type type_; + Variant v_; + bool is_uint64_; + }; - typedef boost::variant< String_type, - boost::recursive_wrapper< Object >, boost::recursive_wrapper< Array >, - bool, boost::int64_t, double > Variant; + // vector objects - Value_type type_; - Variant v_; - bool is_uint64_; - }; + template + struct Pair_impl { + typedef typename Config::String_type String_type; + typedef typename Config::Value_type Value_type; - // vector objects + Pair_impl(const String_type& name, const Value_type& value); - template< class Config > - struct Pair_impl - { - typedef typename Config::String_type String_type; - typedef typename Config::Value_type Value_type; + bool operator==(const Pair_impl& lhs) const; - Pair_impl( const String_type& name, const Value_type& value ); + String_type name_; + Value_type value_; + }; - bool operator==( const Pair_impl& lhs ) const; + template + struct Config_vector { + typedef String String_type; + typedef Value_impl Value_type; + typedef Pair_impl Pair_type; + typedef std::vector Array_type; + typedef std::vector Object_type; - String_type name_; - Value_type value_; - }; + static Value_type& add(Object_type& obj, const String_type& name, const Value_type& value) { + obj.push_back(Pair_type(name, value)); - template< class String > - struct Config_vector - { - typedef String String_type; - typedef Value_impl< Config_vector > Value_type; - typedef Pair_impl < Config_vector > Pair_type; - typedef std::vector< Value_type > Array_type; - typedef std::vector< Pair_type > Object_type; + return obj.back().value_; + } - static Value_type& add( Object_type& obj, const String_type& name, const Value_type& value ) - { - obj.push_back( Pair_type( name , value ) ); + static String_type get_name(const Pair_type& pair) { + return pair.name_; + } - return obj.back().value_; - } + static Value_type get_value(const Pair_type& pair) { + return pair.value_; + } + }; - static String_type get_name( const Pair_type& pair ) - { - return pair.name_; - } + // typedefs for ASCII - static Value_type get_value( const Pair_type& pair ) - { - return pair.value_; - } - }; + typedef Config_vector Config; - // typedefs for ASCII + typedef Config::Value_type Value; + typedef Config::Pair_type Pair; + typedef Config::Object_type Object; + typedef Config::Array_type Array; - typedef Config_vector< std::string > Config; - - typedef Config::Value_type Value; - typedef Config::Pair_type Pair; - typedef Config::Object_type Object; - typedef Config::Array_type Array; - - // typedefs for Unicode + // typedefs for Unicode #ifndef BOOST_NO_STD_WSTRING - typedef Config_vector< std::wstring > wConfig; + typedef Config_vector wConfig; - typedef wConfig::Value_type wValue; - typedef wConfig::Pair_type wPair; - typedef wConfig::Object_type wObject; - typedef wConfig::Array_type wArray; + typedef wConfig::Value_type wValue; + typedef wConfig::Pair_type wPair; + typedef wConfig::Object_type wObject; + typedef wConfig::Array_type wArray; #endif - // map objects + // map objects - template< class String > - struct Config_map - { - typedef String String_type; - typedef Value_impl< Config_map > Value_type; - typedef std::vector< Value_type > Array_type; - typedef std::map< String_type, Value_type > Object_type; - typedef typename Object_type::value_type Pair_type; + template + struct Config_map { + typedef String String_type; + typedef Value_impl Value_type; + typedef std::vector Array_type; + typedef std::map Object_type; + typedef typename Object_type::value_type Pair_type; - static Value_type& add( Object_type& obj, const String_type& name, const Value_type& value ) - { - return obj[ name ] = value; - } + static Value_type& add(Object_type& obj, const String_type& name, const Value_type& value) { + return obj[name] = value; + } - static String_type get_name( const Pair_type& pair ) - { - return pair.first; - } + static String_type get_name(const Pair_type& pair) { + return pair.first; + } - static Value_type get_value( const Pair_type& pair ) - { - return pair.second; - } - }; + static Value_type get_value(const Pair_type& pair) { + return pair.second; + } + }; - // typedefs for ASCII + // typedefs for ASCII - typedef Config_map< std::string > mConfig; + typedef Config_map mConfig; - typedef mConfig::Value_type mValue; - typedef mConfig::Object_type mObject; - typedef mConfig::Array_type mArray; + typedef mConfig::Value_type mValue; + typedef mConfig::Object_type mObject; + typedef mConfig::Array_type mArray; - // typedefs for Unicode + // typedefs for Unicode #ifndef BOOST_NO_STD_WSTRING - typedef Config_map< std::wstring > wmConfig; + typedef Config_map wmConfig; - typedef wmConfig::Value_type wmValue; - typedef wmConfig::Object_type wmObject; - typedef wmConfig::Array_type wmArray; + typedef wmConfig::Value_type wmValue; + typedef wmConfig::Object_type wmObject; + typedef wmConfig::Array_type wmArray; #endif - /////////////////////////////////////////////////////////////////////////////////////////////// - // - // implementation - - template< class Config > - const Value_impl< Config > Value_impl< Config >::null; - - template< class Config > - Value_impl< Config >::Value_impl() - : type_( null_type ) - , is_uint64_( false ) - { - } - - template< class Config > - Value_impl< Config >::Value_impl( const Const_str_ptr value ) - : type_( str_type ) - , v_( String_type( value ) ) - , is_uint64_( false ) - { - } - - template< class Config > - Value_impl< Config >::Value_impl( const String_type& value ) - : type_( str_type ) - , v_( value ) - , is_uint64_( false ) - { - } - - template< class Config > - Value_impl< Config >::Value_impl( const Object& value ) - : type_( obj_type ) - , v_( value ) - , is_uint64_( false ) - { - } - - template< class Config > - Value_impl< Config >::Value_impl( const Array& value ) - : type_( array_type ) - , v_( value ) - , is_uint64_( false ) - { - } - - template< class Config > - Value_impl< Config >::Value_impl( bool value ) - : type_( bool_type ) - , v_( value ) - , is_uint64_( false ) - { - } - - template< class Config > - Value_impl< Config >::Value_impl( int value ) - : type_( int_type ) - , v_( static_cast< boost::int64_t >( value ) ) - , is_uint64_( false ) - { - } - - template< class Config > - Value_impl< Config >::Value_impl( boost::int64_t value ) - : type_( int_type ) - , v_( value ) - , is_uint64_( false ) - { - } - - template< class Config > - Value_impl< Config >::Value_impl( boost::uint64_t value ) - : type_( int_type ) - , v_( static_cast< boost::int64_t >( value ) ) - , is_uint64_( true ) - { - } - - template< class Config > - Value_impl< Config >::Value_impl( double value ) - : type_( real_type ) - , v_( value ) - , is_uint64_( false ) - { - } - - template< class Config > - Value_impl< Config >::Value_impl( const Value_impl< Config >& other ) - : type_( other.type() ) - , v_( other.v_ ) - , is_uint64_( other.is_uint64_ ) - { - } - - template< class Config > - Value_impl< Config >& Value_impl< Config >::operator=( const Value_impl& lhs ) - { - Value_impl tmp( lhs ); - - std::swap( type_, tmp.type_ ); - std::swap( v_, tmp.v_ ); - std::swap( is_uint64_, tmp.is_uint64_ ); - - return *this; - } - - template< class Config > - bool Value_impl< Config >::operator==( const Value_impl& lhs ) const - { - if( this == &lhs ) return true; - - if( type() != lhs.type() ) return false; - - return v_ == lhs.v_; - } - - template< class Config > - Value_type Value_impl< Config >::type() const - { - return type_; - } - - template< class Config > - bool Value_impl< Config >::is_uint64() const - { - return is_uint64_; - } - - template< class Config > - bool Value_impl< Config >::is_null() const - { - return type() == null_type; - } - - template< class Config > - void Value_impl< Config >::check_type( const Value_type vtype ) const - { - if( type() != vtype ) - { - std::ostringstream os; - - os << "value type is " << type() << " not " << vtype; - - throw std::runtime_error( os.str() ); - } - } - - template< class Config > - const typename Config::String_type& Value_impl< Config >::get_str() const - { - check_type( str_type ); - - return *boost::get< String_type >( &v_ ); - } - - template< class Config > - const typename Value_impl< Config >::Object& Value_impl< Config >::get_obj() const - { - check_type( obj_type ); - - return *boost::get< Object >( &v_ ); - } - - template< class Config > - const typename Value_impl< Config >::Array& Value_impl< Config >::get_array() const - { - check_type( array_type ); - - return *boost::get< Array >( &v_ ); - } - - template< class Config > - bool Value_impl< Config >::get_bool() const - { - check_type( bool_type ); - - return boost::get< bool >( v_ ); - } - - template< class Config > - int Value_impl< Config >::get_int() const - { - check_type( int_type ); - - return static_cast< int >( get_int64() ); - } - - template< class Config > - boost::int64_t Value_impl< Config >::get_int64() const - { - check_type( int_type ); - - return boost::get< boost::int64_t >( v_ ); - } - - template< class Config > - boost::uint64_t Value_impl< Config >::get_uint64() const - { - check_type( int_type ); - - return static_cast< boost::uint64_t >( get_int64() ); - } - - template< class Config > - double Value_impl< Config >::get_real() const - { - if( type() == int_type ) - { - return is_uint64() ? static_cast< double >( get_uint64() ) - : static_cast< double >( get_int64() ); - } - - check_type( real_type ); - - return boost::get< double >( v_ ); - } - - template< class Config > - typename Value_impl< Config >::Object& Value_impl< Config >::get_obj() - { - check_type( obj_type ); - - return *boost::get< Object >( &v_ ); - } - - template< class Config > - typename Value_impl< Config >::Array& Value_impl< Config >::get_array() - { - check_type( array_type ); - - return *boost::get< Array >( &v_ ); - } - - template< class Config > - Pair_impl< Config >::Pair_impl( const String_type& name, const Value_type& value ) - : name_( name ) - , value_( value ) - { - } - - template< class Config > - bool Pair_impl< Config >::operator==( const Pair_impl< Config >& lhs ) const - { - if( this == &lhs ) return true; - - return ( name_ == lhs.name_ ) && ( value_ == lhs.value_ ); - } - - // converts a C string, ie. 8 bit char array, to a string object - // - template < class String_type > - String_type to_str( const char* c_str ) - { - String_type result; - - for( const char* p = c_str; *p != 0; ++p ) - { - result += *p; - } - - return result; - } - - // - - namespace internal_ - { - template< typename T > - struct Type_to_type - { - }; - - template< class Value > - int get_value( const Value& value, Type_to_type< int > ) - { - return value.get_int(); - } - - template< class Value > - boost::int64_t get_value( const Value& value, Type_to_type< boost::int64_t > ) - { - return value.get_int64(); - } - - template< class Value > - boost::uint64_t get_value( const Value& value, Type_to_type< boost::uint64_t > ) - { - return value.get_uint64(); - } - - template< class Value > - double get_value( const Value& value, Type_to_type< double > ) - { - return value.get_real(); - } - - template< class Value > - typename Value::String_type get_value( const Value& value, Type_to_type< typename Value::String_type > ) - { - return value.get_str(); - } - - template< class Value > - typename Value::Array get_value( const Value& value, Type_to_type< typename Value::Array > ) - { - return value.get_array(); - } - - template< class Value > - typename Value::Object get_value( const Value& value, Type_to_type< typename Value::Object > ) - { - return value.get_obj(); - } - - template< class Value > - bool get_value( const Value& value, Type_to_type< bool > ) - { - return value.get_bool(); - } - } - - template< class Config > - template< typename T > - T Value_impl< Config >::get_value() const - { - return internal_::get_value( *this, internal_::Type_to_type< T >() ); - } + /////////////////////////////////////////////////////////////////////////////////////////////// + // + // implementation + + template + const Value_impl Value_impl::null; + + template + Value_impl::Value_impl() : + type_(null_type), is_uint64_(false) { + } + + template + Value_impl::Value_impl(const Const_str_ptr value) : + type_(str_type), v_(String_type(value)), is_uint64_(false) { + } + + template + Value_impl::Value_impl(const String_type& value) : + type_(str_type), v_(value), is_uint64_(false) { + } + + template + Value_impl::Value_impl(const Object& value) : + type_(obj_type), v_(value), is_uint64_(false) { + } + + template + Value_impl::Value_impl(const Array& value) : + type_(array_type), v_(value), is_uint64_(false) { + } + + template + Value_impl::Value_impl(bool value) : + type_(bool_type), v_(value), is_uint64_(false) { + } + + template + Value_impl::Value_impl(int value) : + type_(int_type), v_(static_cast(value)), is_uint64_(false) { + } + + template + Value_impl::Value_impl(boost::int64_t value) : + type_(int_type), v_(value), is_uint64_(false) { + } + + template + Value_impl::Value_impl(boost::uint64_t value) : + type_(int_type), v_(static_cast(value)), is_uint64_(true) { + } + + template + Value_impl::Value_impl(double value) : + type_(real_type), v_(value), is_uint64_(false) { + } + + template + Value_impl::Value_impl(const Value_impl& other) : + type_(other.type()), v_(other.v_), is_uint64_(other.is_uint64_) { + } + + template + Value_impl& Value_impl::operator=(const Value_impl& lhs) { + Value_impl tmp(lhs); + + std::swap(type_, tmp.type_); + std::swap(v_, tmp.v_); + std::swap(is_uint64_, tmp.is_uint64_); + + return *this; + } + + template + bool Value_impl::operator==(const Value_impl& lhs) const { + if (this == &lhs) { + return true; + } + + if (type() != lhs.type()) { + return false; + } + + return v_ == lhs.v_; + } + + template + Value_type Value_impl::type() const { + return type_; + } + + template + bool Value_impl::is_uint64() const { + return is_uint64_; + } + + template + bool Value_impl::is_null() const { + return type() == null_type; + } + + template + void Value_impl::check_type(const Value_type vtype) const { + if (type() != vtype) { + std::ostringstream os; + + os << "value type is " << type() << " not " << vtype; + + throw std::runtime_error(os.str()); + } + } + + template + const typename Config::String_type& Value_impl::get_str() const { + check_type(str_type); + + return *boost::get(&v_); + } + + template + const typename Value_impl::Object& Value_impl::get_obj() const { + check_type(obj_type); + + return *boost::get(&v_); + } + + template + const typename Value_impl::Array& Value_impl::get_array() const { + check_type(array_type); + + return *boost::get(&v_); + } + + template + bool Value_impl::get_bool() const { + check_type(bool_type); + + return boost::get(v_); + } + + template + int Value_impl::get_int() const { + check_type(int_type); + + return static_cast(get_int64()); + } + + template + boost::int64_t Value_impl::get_int64() const { + check_type(int_type); + + return boost::get(v_); + } + + template + boost::uint64_t Value_impl::get_uint64() const { + check_type(int_type); + + return static_cast(get_int64()); + } + + template + double Value_impl::get_real() const { + if (type() == int_type) { + return is_uint64() ? static_cast(get_uint64()) + : static_cast(get_int64()); + } + + check_type(real_type); + + return boost::get(v_); + } + + template + typename Value_impl::Object& Value_impl::get_obj() { + check_type(obj_type); + + return *boost::get(&v_); + } + + template + typename Value_impl::Array& Value_impl::get_array() { + check_type(array_type); + + return *boost::get(&v_); + } + + template + Pair_impl::Pair_impl(const String_type& name, const Value_type& value) : + name_(name), value_(value) { + } + + template + bool Pair_impl::operator==(const Pair_impl& lhs) const { + if (this == &lhs) { + return true; + } + + return (name_ == lhs.name_) && (value_ == lhs.value_); + } + + // converts a C string, ie. 8 bit char array, to a string object + // + template + String_type to_str(const char* c_str) { + String_type result; + + for (const char* p = c_str; *p != 0; ++p) { + result += *p; + } + + return result; + } + + // + + namespace internal_ { + template + struct Type_to_type { + }; + + template + int get_value(const Value& value, Type_to_type) { + return value.get_int(); + } + + template + boost::int64_t get_value(const Value& value, Type_to_type) { + return value.get_int64(); + } + + template + boost::uint64_t get_value(const Value& value, Type_to_type) { + return value.get_uint64(); + } + + template + double get_value(const Value& value, Type_to_type) { + return value.get_real(); + } + + template + typename Value::String_type get_value(const Value& value, Type_to_type) { + return value.get_str(); + } + + template + typename Value::Array get_value(const Value& value, Type_to_type) { + return value.get_array(); + } + + template + typename Value::Object get_value(const Value& value, Type_to_type) { + return value.get_obj(); + } + + template + bool get_value(const Value& value, Type_to_type) { + return value.get_bool(); + } + } + + template + template + T Value_impl::get_value() const { + return internal_::get_value(*this, internal_::Type_to_type()); + } } #endif diff --git a/source/json/json_spirit_writer.cpp b/source/json/json_spirit_writer.cpp index d24a632cf..4f95bb6a1 100644 --- a/source/json/json_spirit_writer.cpp +++ b/source/json/json_spirit_writer.cpp @@ -6,90 +6,74 @@ #include "json_spirit_writer.h" #include "json_spirit_writer_template.h" -void json_spirit::write( const Value& value, std::ostream& os ) -{ - write_stream( value, os, false ); +void json_spirit::write(const Value& value, std::ostream& os) { + write_stream(value, os, false); } -void json_spirit::write_formatted( const Value& value, std::ostream& os ) -{ - write_stream( value, os, true ); +void json_spirit::write_formatted(const Value& value, std::ostream& os) { + write_stream(value, os, true); } -std::string json_spirit::write( const Value& value ) -{ - return write_string( value, false ); +std::string json_spirit::write(const Value& value) { + return write_string(value, false); } -std::string json_spirit::write_formatted( const Value& value ) -{ - return write_string( value, true ); +std::string json_spirit::write_formatted(const Value& value) { + return write_string(value, true); } #ifndef BOOST_NO_STD_WSTRING -void json_spirit::write( const wValue& value, std::wostream& os ) -{ - write_stream( value, os, false ); +void json_spirit::write(const wValue& value, std::wostream& os) { + write_stream(value, os, false); } -void json_spirit::write_formatted( const wValue& value, std::wostream& os ) -{ - write_stream( value, os, true ); +void json_spirit::write_formatted(const wValue& value, std::wostream& os) { + write_stream(value, os, true); } -std::wstring json_spirit::write( const wValue& value ) -{ - return write_string( value, false ); +std::wstring json_spirit::write(const wValue& value) { + return write_string(value, false); } -std::wstring json_spirit::write_formatted( const wValue& value ) -{ - return write_string( value, true ); +std::wstring json_spirit::write_formatted(const wValue& value) { + return write_string(value, true); } #endif -void json_spirit::write( const mValue& value, std::ostream& os ) -{ - write_stream( value, os, false ); +void json_spirit::write(const mValue& value, std::ostream& os) { + write_stream(value, os, false); } -void json_spirit::write_formatted( const mValue& value, std::ostream& os ) -{ - write_stream( value, os, true ); +void json_spirit::write_formatted(const mValue& value, std::ostream& os) { + write_stream(value, os, true); } -std::string json_spirit::write( const mValue& value ) -{ - return write_string( value, false ); +std::string json_spirit::write(const mValue& value) { + return write_string(value, false); } -std::string json_spirit::write_formatted( const mValue& value ) -{ - return write_string( value, true ); +std::string json_spirit::write_formatted(const mValue& value) { + return write_string(value, true); } #ifndef BOOST_NO_STD_WSTRING -void json_spirit::write( const wmValue& value, std::wostream& os ) -{ - write_stream( value, os, false ); +void json_spirit::write(const wmValue& value, std::wostream& os) { + write_stream(value, os, false); } -void json_spirit::write_formatted( const wmValue& value, std::wostream& os ) -{ - write_stream( value, os, true ); +void json_spirit::write_formatted(const wmValue& value, std::wostream& os) { + write_stream(value, os, true); } -std::wstring json_spirit::write( const wmValue& value ) -{ - return write_string( value, false ); +std::wstring json_spirit::write(const wmValue& value) { + return write_string(value, false); } -std::wstring json_spirit::write_formatted( const wmValue& value ) -{ - return write_string( value, true ); +std::wstring json_spirit::write_formatted(const wmValue& value) { + return write_string(value, true); } #endif diff --git a/source/json/json_spirit_writer.h b/source/json/json_spirit_writer.h index 108a07f4d..0d8a35fa9 100644 --- a/source/json/json_spirit_writer.h +++ b/source/json/json_spirit_writer.h @@ -7,42 +7,41 @@ // json spirit version 4.03 #if defined(_MSC_VER) && (_MSC_VER >= 1020) -# pragma once + #pragma once #endif #include "json_spirit_value.h" #include -namespace json_spirit -{ - // functions to convert JSON Values to text, - // the "formatted" versions add whitespace to format the output nicely +namespace json_spirit { + // functions to convert JSON Values to text, + // the "formatted" versions add whitespace to format the output nicely - void write ( const Value& value, std::ostream& os ); - void write_formatted( const Value& value, std::ostream& os ); - std::string write ( const Value& value ); - std::string write_formatted( const Value& value ); + void write(const Value& value, std::ostream& os); + void write_formatted(const Value& value, std::ostream& os); + std::string write(const Value& value); + std::string write_formatted(const Value& value); #ifndef BOOST_NO_STD_WSTRING - void write ( const wValue& value, std::wostream& os ); - void write_formatted( const wValue& value, std::wostream& os ); - std::wstring write ( const wValue& value ); - std::wstring write_formatted( const wValue& value ); + void write(const wValue& value, std::wostream& os); + void write_formatted(const wValue& value, std::wostream& os); + std::wstring write(const wValue& value); + std::wstring write_formatted(const wValue& value); #endif - void write ( const mValue& value, std::ostream& os ); - void write_formatted( const mValue& value, std::ostream& os ); - std::string write ( const mValue& value ); - std::string write_formatted( const mValue& value ); + void write(const mValue& value, std::ostream& os); + void write_formatted(const mValue& value, std::ostream& os); + std::string write(const mValue& value); + std::string write_formatted(const mValue& value); #ifndef BOOST_NO_STD_WSTRING - void write ( const wmValue& value, std::wostream& os ); - void write_formatted( const wmValue& value, std::wostream& os ); - std::wstring write ( const wmValue& value ); - std::wstring write_formatted( const wmValue& value ); + void write(const wmValue& value, std::wostream& os); + void write_formatted(const wmValue& value, std::wostream& os); + std::wstring write(const wmValue& value); + std::wstring write_formatted(const wmValue& value); #endif } diff --git a/source/json/json_spirit_writer_template.h b/source/json/json_spirit_writer_template.h index 3dc9228bd..5692165de 100644 --- a/source/json/json_spirit_writer_template.h +++ b/source/json/json_spirit_writer_template.h @@ -12,232 +12,243 @@ #include #include -namespace json_spirit -{ - inline char to_hex_char( unsigned int c ) - { - assert( c <= 0xF ); - - const char ch = static_cast< char >( c ); - - if( ch < 10 ) return '0' + ch; - - return 'A' - 10 + ch; - } - - template< class String_type > - String_type non_printable_to_string( unsigned int c ) - { - String_type result( 6, '\\' ); - - result[1] = 'u'; - - result[ 5 ] = to_hex_char( c & 0x000F ); c >>= 4; - result[ 4 ] = to_hex_char( c & 0x000F ); c >>= 4; - result[ 3 ] = to_hex_char( c & 0x000F ); c >>= 4; - result[ 2 ] = to_hex_char( c & 0x000F ); - - return result; - } - - template< typename Char_type, class String_type > - bool add_esc_char( Char_type c, String_type& s ) - { - switch( c ) - { - case '"': s += to_str< String_type >( "\\\"" ); return true; - case '\\': s += to_str< String_type >( "\\\\" ); return true; - case '\b': s += to_str< String_type >( "\\b" ); return true; - case '\f': s += to_str< String_type >( "\\f" ); return true; - case '\n': s += to_str< String_type >( "\\n" ); return true; - case '\r': s += to_str< String_type >( "\\r" ); return true; - case '\t': s += to_str< String_type >( "\\t" ); return true; - } - - return false; - } - - template< class String_type > - String_type add_esc_chars( const String_type& s ) - { - typedef typename String_type::const_iterator Iter_type; - typedef typename String_type::value_type Char_type; - - String_type result; - - const Iter_type end( s.end() ); - - for( Iter_type i = s.begin(); i != end; ++i ) - { - const Char_type c( *i ); - - if( add_esc_char( c, result ) ) continue; - - const wint_t unsigned_c( ( c >= 0 ) ? c : 256 + c ); - - if( iswprint( unsigned_c ) ) - { - result += c; - } - else - { - result += non_printable_to_string< String_type >( unsigned_c ); - } - } - - return result; - } - - // this class generates the JSON text, - // it keeps track of the indentation level etc. - // - template< class Value_type, class Ostream_type > - class Generator - { - typedef typename Value_type::Config_type Config_type; - typedef typename Config_type::String_type String_type; - typedef typename Config_type::Object_type Object_type; - typedef typename Config_type::Array_type Array_type; - typedef typename String_type::value_type Char_type; - typedef typename Object_type::value_type Obj_member_type; - - public: - - Generator( const Value_type& value, Ostream_type& os, bool pretty ) - : os_( os ) - , indentation_level_( 0 ) - , pretty_( pretty ) - { - output( value ); - } - - private: - - void output( const Value_type& value ) - { - switch( value.type() ) - { - case obj_type: output( value.get_obj() ); break; - case array_type: output( value.get_array() ); break; - case str_type: output( value.get_str() ); break; - case bool_type: output( value.get_bool() ); break; - case int_type: output_int( value ); break; - case real_type: os_ << std::showpoint << std::setprecision( 16 ) - << value.get_real(); break; - case null_type: os_ << "null"; break; - default: assert( false ); - } - } - - void output( const Object_type& obj ) - { - output_array_or_obj( obj, '{', '}' ); - } - - void output( const Array_type& arr ) - { - output_array_or_obj( arr, '[', ']' ); - } - - void output( const Obj_member_type& member ) - { - output( Config_type::get_name( member ) ); space(); - os_ << ':'; space(); - output( Config_type::get_value( member ) ); - } - - void output_int( const Value_type& value ) - { - if( value.is_uint64() ) - { - os_ << value.get_uint64(); - } - else - { - os_ << value.get_int64(); - } - } - - void output( const String_type& s ) - { - os_ << '"' << add_esc_chars( s ) << '"'; - } - - void output( bool b ) - { - os_ << to_str< String_type >( b ? "true" : "false" ); - } - - template< class T > - void output_array_or_obj( const T& t, Char_type start_char, Char_type end_char ) - { - os_ << start_char; new_line(); - - ++indentation_level_; - - for( typename T::const_iterator i = t.begin(); i != t.end(); ++i ) - { - indent(); output( *i ); - - typename T::const_iterator next = i; - - if( ++next != t.end()) - { - os_ << ','; - } - - new_line(); - } - - --indentation_level_; - - indent(); os_ << end_char; - } - - void indent() - { - if( !pretty_ ) return; - - for( int i = 0; i < indentation_level_; ++i ) - { - os_ << " "; - } - } - - void space() - { - if( pretty_ ) os_ << ' '; - } - - void new_line() - { - if( pretty_ ) os_ << '\n'; - } - - Generator& operator=( const Generator& ); // to prevent "assignment operator could not be generated" warning - - Ostream_type& os_; - int indentation_level_; - bool pretty_; - }; - - template< class Value_type, class Ostream_type > - void write_stream( const Value_type& value, Ostream_type& os, bool pretty ) - { - Generator< Value_type, Ostream_type >( value, os, pretty ); - } - - template< class Value_type > - typename Value_type::String_type write_string( const Value_type& value, bool pretty ) - { - typedef typename Value_type::String_type::value_type Char_type; - - std::basic_ostringstream< Char_type > os; - - write_stream( value, os, pretty ); - - return os.str(); - } +namespace json_spirit { + inline char to_hex_char(unsigned int c) { + assert(c <= 0xF); + + const char ch = static_cast(c); + + if (ch < 10) { + return '0' + ch; + } + + return 'A' - 10 + ch; + } + + template + String_type non_printable_to_string(unsigned int c) { + String_type result(6, '\\'); + + result[1] = 'u'; + + result[5] = to_hex_char(c & 0x000F); + c >>= 4; + result[4] = to_hex_char(c & 0x000F); + c >>= 4; + result[3] = to_hex_char(c & 0x000F); + c >>= 4; + result[2] = to_hex_char(c & 0x000F); + + return result; + } + + template + bool add_esc_char(Char_type c, String_type& s) { + switch (c) { + case '"': + s += to_str("\\\""); + return true; + case '\\': + s += to_str("\\\\"); + return true; + case '\b': + s += to_str("\\b"); + return true; + case '\f': + s += to_str("\\f"); + return true; + case '\n': + s += to_str("\\n"); + return true; + case '\r': + s += to_str("\\r"); + return true; + case '\t': + s += to_str("\\t"); + return true; + } + + return false; + } + + template + String_type add_esc_chars(const String_type& s) { + typedef typename String_type::const_iterator Iter_type; + typedef typename String_type::value_type Char_type; + + String_type result; + + const Iter_type end(s.end()); + + for (Iter_type i = s.begin(); i != end; ++i) { + const Char_type c(*i); + + if (add_esc_char(c, result)) { + continue; + } + + const wint_t unsigned_c((c >= 0) ? c : 256 + c); + + if (iswprint(unsigned_c)) { + result += c; + } else { + result += non_printable_to_string(unsigned_c); + } + } + + return result; + } + + // this class generates the JSON text, + // it keeps track of the indentation level etc. + // + template + class Generator { + typedef typename Value_type::Config_type Config_type; + typedef typename Config_type::String_type String_type; + typedef typename Config_type::Object_type Object_type; + typedef typename Config_type::Array_type Array_type; + typedef typename String_type::value_type Char_type; + typedef typename Object_type::value_type Obj_member_type; + + public: + Generator(const Value_type& value, Ostream_type& os, bool pretty) : + os_(os), indentation_level_(0), pretty_(pretty) { + output(value); + } + + private: + void output(const Value_type& value) { + switch (value.type()) { + case obj_type: + output(value.get_obj()); + break; + case array_type: + output(value.get_array()); + break; + case str_type: + output(value.get_str()); + break; + case bool_type: + output(value.get_bool()); + break; + case int_type: + output_int(value); + break; + case real_type: + os_ << std::showpoint << std::setprecision(16) + << value.get_real(); + break; + case null_type: + os_ << "null"; + break; + default: + assert(false); + } + } + + void output(const Object_type& obj) { + output_array_or_obj(obj, '{', '}'); + } + + void output(const Array_type& arr) { + output_array_or_obj(arr, '[', ']'); + } + + void output(const Obj_member_type& member) { + output(Config_type::get_name(member)); + space(); + os_ << ':'; + space(); + output(Config_type::get_value(member)); + } + + void output_int(const Value_type& value) { + if (value.is_uint64()) { + os_ << value.get_uint64(); + } else { + os_ << value.get_int64(); + } + } + + void output(const String_type& s) { + os_ << '"' << add_esc_chars(s) << '"'; + } + + void output(bool b) { + os_ << to_str(b ? "true" : "false"); + } + + template + void output_array_or_obj(const T& t, Char_type start_char, Char_type end_char) { + os_ << start_char; + new_line(); + + ++indentation_level_; + + for (typename T::const_iterator i = t.begin(); i != t.end(); ++i) { + indent(); + output(*i); + + typename T::const_iterator next = i; + + if (++next != t.end()) { + os_ << ','; + } + + new_line(); + } + + --indentation_level_; + + indent(); + os_ << end_char; + } + + void indent() { + if (!pretty_) { + return; + } + + for (int i = 0; i < indentation_level_; ++i) { + os_ << " "; + } + } + + void space() { + if (pretty_) { + os_ << ' '; + } + } + + void new_line() { + if (pretty_) { + os_ << '\n'; + } + } + + Generator& operator=(const Generator&); // to prevent "assignment operator could not be generated" warning + + Ostream_type& os_; + int indentation_level_; + bool pretty_; + }; + + template + void write_stream(const Value_type& value, Ostream_type& os, bool pretty) { + Generator(value, os, pretty); + } + + template + typename Value_type::String_type write_string(const Value_type& value, bool pretty) { + typedef typename Value_type::String_type::value_type Char_type; + + std::basic_ostringstream os; + + write_stream(value, os, pretty); + + return os.str(); + } } #endif diff --git a/source/lua/lua_api.cpp b/source/lua/lua_api.cpp index e96dc81ff..5a94f8f12 100644 --- a/source/lua/lua_api.cpp +++ b/source/lua/lua_api.cpp @@ -20,51 +20,51 @@ namespace LuaAPI { -void registerAll(sol::state& lua) { - // Register base types first (order matters for dependencies) + void registerAll(sol::state& lua) { + // Register base types first (order matters for dependencies) - // Position is used by Tile and Map - registerPosition(lua); + // Position is used by Tile and Map + registerPosition(lua); - // Item is used by Tile - registerItem(lua); + // Item is used by Tile + registerItem(lua); - // Color is a standalone type - registerColor(lua); + // Color is a standalone type + registerColor(lua); - // Creature/Spawn are used by Tile (must come before Tile) - registerCreature(lua); + // Creature/Spawn are used by Tile (must come before Tile) + registerCreature(lua); - // Tile is used by Map and Selection - registerTile(lua); + // Tile is used by Map and Selection + registerTile(lua); - // Map uses Tile and Position - registerMap(lua); + // Map uses Tile and Position + registerMap(lua); - // Selection uses Tile - registerSelection(lua); + // Selection uses Tile + registerSelection(lua); - // Must come after Map and Selection so app.map/app.selection work - registerApp(lua); + // Must come after Map and Selection so app.map/app.selection work + registerApp(lua); - // Register Dialog class - registerDialog(lua); + // Register Dialog class + registerDialog(lua); - registerBrush(lua); + registerBrush(lua); - // Register Image class - registerImage(lua); + // Register Image class + registerImage(lua); - // Register JSON helper - registerJson(lua); + // Register JSON helper + registerJson(lua); - // Register HTTP client - registerHttp(lua); + // Register HTTP client + registerHttp(lua); - // Register procedural generation APIs - registerNoise(lua); - registerAlgo(lua); - registerGeo(lua); -} + // Register procedural generation APIs + registerNoise(lua); + registerAlgo(lua); + registerGeo(lua); + } } diff --git a/source/lua/lua_api_algo.cpp b/source/lua/lua_api_algo.cpp index 901b900fe..c00d80774 100644 --- a/source/lua/lua_api_algo.cpp +++ b/source/lua/lua_api_algo.cpp @@ -27,823 +27,842 @@ namespace LuaAPI { -// Helper: convert Lua table to 2D grid -static std::vector> tableToGrid(const sol::table& tbl, int width, int height) { - std::vector> grid(height, std::vector(width, 0)); - - for (int y = 1; y <= height; ++y) { - if (tbl[y].valid() && tbl[y].get_type() == sol::type::table) { - sol::table row = tbl[y]; - for (int x = 1; x <= width; ++x) { - if (row[x].valid()) { - grid[y - 1][x - 1] = row[x].get(); + // Helper: convert Lua table to 2D grid + static std::vector> tableToGrid(const sol::table& tbl, int width, int height) { + std::vector> grid(height, std::vector(width, 0)); + + for (int y = 1; y <= height; ++y) { + if (tbl[y].valid() && tbl[y].get_type() == sol::type::table) { + sol::table row = tbl[y]; + for (int x = 1; x <= width; ++x) { + if (row[x].valid()) { + grid[y - 1][x - 1] = row[x].get(); + } } } } + return grid; } - return grid; -} - -// Helper: convert 2D grid to Lua table -static sol::table gridToTable(const std::vector>& grid, sol::state_view& lua) { - sol::table result = lua.create_table(); - for (size_t y = 0; y < grid.size(); ++y) { - sol::table row = lua.create_table(); - for (size_t x = 0; x < grid[y].size(); ++x) { - row[x + 1] = grid[y][x]; + + // Helper: convert 2D grid to Lua table + static sol::table gridToTable(const std::vector>& grid, sol::state_view& lua) { + sol::table result = lua.create_table(); + for (size_t y = 0; y < grid.size(); ++y) { + sol::table row = lua.create_table(); + for (size_t x = 0; x < grid[y].size(); ++x) { + row[x + 1] = grid[y][x]; + } + result[y + 1] = row; } - result[y + 1] = row; + return result; } - return result; -} - -// Helper: convert Lua table to 2D float grid -static std::vector> tableToFloatGrid(const sol::table& tbl, int width, int height) { - std::vector> grid(height, std::vector(width, 0.0f)); - - for (int y = 1; y <= height; ++y) { - if (tbl[y].valid() && tbl[y].get_type() == sol::type::table) { - sol::table row = tbl[y]; - for (int x = 1; x <= width; ++x) { - if (row[x].valid()) { - grid[y - 1][x - 1] = row[x].get(); + + // Helper: convert Lua table to 2D float grid + static std::vector> tableToFloatGrid(const sol::table& tbl, int width, int height) { + std::vector> grid(height, std::vector(width, 0.0f)); + + for (int y = 1; y <= height; ++y) { + if (tbl[y].valid() && tbl[y].get_type() == sol::type::table) { + sol::table row = tbl[y]; + for (int x = 1; x <= width; ++x) { + if (row[x].valid()) { + grid[y - 1][x - 1] = row[x].get(); + } } } } + return grid; } - return grid; -} - -// Helper: convert 2D float grid to Lua table -static sol::table floatGridToTable(const std::vector>& grid, sol::state_view& lua) { - sol::table result = lua.create_table(); - for (size_t y = 0; y < grid.size(); ++y) { - sol::table row = lua.create_table(); - for (size_t x = 0; x < grid[y].size(); ++x) { - row[x + 1] = grid[y][x]; - } - result[y + 1] = row; - } - return result; -} - -void registerAlgo(sol::state& lua) { - sol::table algoTable = lua.create_table(); - - // ======================================== - // CELLULAR AUTOMATA - // ======================================== - - // algo.cellularAutomata(grid, options) -> grid - // Run cellular automata simulation (useful for caves, organic shapes) - // grid: 2D table where 1 = wall, 0 = floor - // options: { iterations, birthLimit, deathLimit, width, height } - algoTable.set_function("cellularAutomata", [](sol::table inputGrid, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); - - int iterations = 4; - int birthLimit = 4; // Become wall if neighbors >= birthLimit - int deathLimit = 3; // Stay wall if neighbors >= deathLimit - int width = 0; - int height = 0; - - // Get dimensions from grid - if (inputGrid[1].valid()) { - height = static_cast(inputGrid.size()); - if (inputGrid[1].get_type() == sol::type::table) { - sol::table firstRow = inputGrid[1]; - width = static_cast(firstRow.size()); - } - } - if (options) { - sol::table opts = *options; - iterations = opts.get_or("iterations", 4); - birthLimit = opts.get_or("birthLimit", 4); - deathLimit = opts.get_or("deathLimit", 3); - width = opts.get_or("width", width); - height = opts.get_or("height", height); + // Helper: convert 2D float grid to Lua table + static sol::table floatGridToTable(const std::vector>& grid, sol::state_view& lua) { + sol::table result = lua.create_table(); + for (size_t y = 0; y < grid.size(); ++y) { + sol::table row = lua.create_table(); + for (size_t x = 0; x < grid[y].size(); ++x) { + row[x + 1] = grid[y][x]; + } + result[y + 1] = row; } + return result; + } - if (width <= 0 || height <= 0) { - return inputGrid; // Return unchanged if invalid dimensions - } + void registerAlgo(sol::state& lua) { + sol::table algoTable = lua.create_table(); + + // ======================================== + // CELLULAR AUTOMATA + // ======================================== + + // algo.cellularAutomata(grid, options) -> grid + // Run cellular automata simulation (useful for caves, organic shapes) + // grid: 2D table where 1 = wall, 0 = floor + // options: { iterations, birthLimit, deathLimit, width, height } + algoTable.set_function("cellularAutomata", [](sol::table inputGrid, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + int iterations = 4; + int birthLimit = 4; // Become wall if neighbors >= birthLimit + int deathLimit = 3; // Stay wall if neighbors >= deathLimit + int width = 0; + int height = 0; + + // Get dimensions from grid + if (inputGrid[1].valid()) { + height = static_cast(inputGrid.size()); + if (inputGrid[1].get_type() == sol::type::table) { + sol::table firstRow = inputGrid[1]; + width = static_cast(firstRow.size()); + } + } - auto grid = tableToGrid(inputGrid, width, height); + if (options) { + sol::table opts = *options; + iterations = opts.get_or("iterations", 4); + birthLimit = opts.get_or("birthLimit", 4); + deathLimit = opts.get_or("deathLimit", 3); + width = opts.get_or("width", width); + height = opts.get_or("height", height); + } - // Run iterations - for (int iter = 0; iter < iterations; ++iter) { - std::vector> newGrid = grid; + if (width <= 0 || height <= 0) { + return inputGrid; // Return unchanged if invalid dimensions + } - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - // Count neighbors (8-directional) - int neighbors = 0; - for (int dy = -1; dy <= 1; ++dy) { - for (int dx = -1; dx <= 1; ++dx) { - if (dx == 0 && dy == 0) continue; - int nx = x + dx; - int ny = y + dy; - // Treat edges as walls - if (nx < 0 || nx >= width || ny < 0 || ny >= height) { - neighbors++; - } else if (grid[ny][nx] == 1) { - neighbors++; + auto grid = tableToGrid(inputGrid, width, height); + + // Run iterations + for (int iter = 0; iter < iterations; ++iter) { + std::vector> newGrid = grid; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + // Count neighbors (8-directional) + int neighbors = 0; + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + if (dx == 0 && dy == 0) { + continue; + } + int nx = x + dx; + int ny = y + dy; + // Treat edges as walls + if (nx < 0 || nx >= width || ny < 0 || ny >= height) { + neighbors++; + } else if (grid[ny][nx] == 1) { + neighbors++; + } } } - } - // Apply rules - if (grid[y][x] == 1) { - // Wall survives if enough neighbors - newGrid[y][x] = (neighbors >= deathLimit) ? 1 : 0; - } else { - // Floor becomes wall if too many neighbors - newGrid[y][x] = (neighbors >= birthLimit) ? 1 : 0; + // Apply rules + if (grid[y][x] == 1) { + // Wall survives if enough neighbors + newGrid[y][x] = (neighbors >= deathLimit) ? 1 : 0; + } else { + // Floor becomes wall if too many neighbors + newGrid[y][x] = (neighbors >= birthLimit) ? 1 : 0; + } } } - } - grid = newGrid; - } + grid = newGrid; + } - return gridToTable(grid, lua); - }); - - // algo.generateCave(width, height, options) -> grid - // Generate a cave map using cellular automata - // options: { fillProbability, iterations, birthLimit, deathLimit, seed } - algoTable.set_function("generateCave", [](int width, int height, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); - - float fillProbability = 0.45f; - int iterations = 4; - int birthLimit = 4; - int deathLimit = 3; - int seed = static_cast(time(nullptr)); - - if (options) { - sol::table opts = *options; - fillProbability = opts.get_or("fillProbability", 0.45f); - iterations = opts.get_or("iterations", 4); - birthLimit = opts.get_or("birthLimit", 4); - deathLimit = opts.get_or("deathLimit", 3); - seed = opts.get_or("seed", seed); - } + return gridToTable(grid, lua); + }); + + // algo.generateCave(width, height, options) -> grid + // Generate a cave map using cellular automata + // options: { fillProbability, iterations, birthLimit, deathLimit, seed } + algoTable.set_function("generateCave", [](int width, int height, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + float fillProbability = 0.45f; + int iterations = 4; + int birthLimit = 4; + int deathLimit = 3; + int seed = static_cast(time(nullptr)); + + if (options) { + sol::table opts = *options; + fillProbability = opts.get_or("fillProbability", 0.45f); + iterations = opts.get_or("iterations", 4); + birthLimit = opts.get_or("birthLimit", 4); + deathLimit = opts.get_or("deathLimit", 3); + seed = opts.get_or("seed", seed); + } - std::mt19937 rng(seed); - std::uniform_real_distribution dist(0.0f, 1.0f); + std::mt19937 rng(seed); + std::uniform_real_distribution dist(0.0f, 1.0f); - // Initialize random grid - std::vector> grid(height, std::vector(width, 0)); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - // Edges are always walls - if (x == 0 || x == width - 1 || y == 0 || y == height - 1) { - grid[y][x] = 1; - } else { - grid[y][x] = (dist(rng) < fillProbability) ? 1 : 0; + // Initialize random grid + std::vector> grid(height, std::vector(width, 0)); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + // Edges are always walls + if (x == 0 || x == width - 1 || y == 0 || y == height - 1) { + grid[y][x] = 1; + } else { + grid[y][x] = (dist(rng) < fillProbability) ? 1 : 0; + } } } - } - // Run cellular automata - for (int iter = 0; iter < iterations; ++iter) { - std::vector> newGrid = grid; - - for (int y = 1; y < height - 1; ++y) { - for (int x = 1; x < width - 1; ++x) { - int neighbors = 0; - for (int dy = -1; dy <= 1; ++dy) { - for (int dx = -1; dx <= 1; ++dx) { - if (dx == 0 && dy == 0) continue; - if (grid[y + dy][x + dx] == 1) neighbors++; + // Run cellular automata + for (int iter = 0; iter < iterations; ++iter) { + std::vector> newGrid = grid; + + for (int y = 1; y < height - 1; ++y) { + for (int x = 1; x < width - 1; ++x) { + int neighbors = 0; + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + if (dx == 0 && dy == 0) { + continue; + } + if (grid[y + dy][x + dx] == 1) { + neighbors++; + } + } } - } - if (grid[y][x] == 1) { - newGrid[y][x] = (neighbors >= deathLimit) ? 1 : 0; - } else { - newGrid[y][x] = (neighbors >= birthLimit) ? 1 : 0; + if (grid[y][x] == 1) { + newGrid[y][x] = (neighbors >= deathLimit) ? 1 : 0; + } else { + newGrid[y][x] = (neighbors >= birthLimit) ? 1 : 0; + } } } - } - grid = newGrid; - } + grid = newGrid; + } - return gridToTable(grid, lua); - }); - - // ======================================== - // EROSION ALGORITHMS - // ======================================== - - // algo.erode(heightmap, options) -> heightmap - // Hydraulic erosion simulation for terrain - // heightmap: 2D table of float values [0, 1] - // options: { iterations, erosionRadius, inertia, sedimentCapacity, minSlope, erosionSpeed, depositSpeed, evaporateSpeed, gravity } - algoTable.set_function("erode", [](sol::table inputHeightmap, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); - - // Get dimensions - int height = 0; - int width = 0; - if (inputHeightmap[1].valid()) { - height = static_cast(inputHeightmap.size()); - if (inputHeightmap[1].get_type() == sol::type::table) { - sol::table firstRow = inputHeightmap[1]; - width = static_cast(firstRow.size()); + return gridToTable(grid, lua); + }); + + // ======================================== + // EROSION ALGORITHMS + // ======================================== + + // algo.erode(heightmap, options) -> heightmap + // Hydraulic erosion simulation for terrain + // heightmap: 2D table of float values [0, 1] + // options: { iterations, erosionRadius, inertia, sedimentCapacity, minSlope, erosionSpeed, depositSpeed, evaporateSpeed, gravity } + algoTable.set_function("erode", [](sol::table inputHeightmap, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + // Get dimensions + int height = 0; + int width = 0; + if (inputHeightmap[1].valid()) { + height = static_cast(inputHeightmap.size()); + if (inputHeightmap[1].get_type() == sol::type::table) { + sol::table firstRow = inputHeightmap[1]; + width = static_cast(firstRow.size()); + } } - } - if (width <= 2 || height <= 2) { - return inputHeightmap; - } + if (width <= 2 || height <= 2) { + return inputHeightmap; + } - // Erosion parameters - int iterations = 50000; - int erosionRadius = 3; - float inertia = 0.05f; - float sedimentCapacity = 4.0f; - float minSlope = 0.01f; - float erosionSpeed = 0.3f; - float depositSpeed = 0.3f; - float evaporateSpeed = 0.01f; - float gravity = 4.0f; - int seed = static_cast(time(nullptr)); - int maxDropletLifetime = 30; - - if (options) { - sol::table opts = *options; - iterations = opts.get_or("iterations", 50000); - erosionRadius = opts.get_or("erosionRadius", 3); - inertia = opts.get_or("inertia", 0.05f); - sedimentCapacity = opts.get_or("sedimentCapacity", 4.0f); - minSlope = opts.get_or("minSlope", 0.01f); - erosionSpeed = opts.get_or("erosionSpeed", 0.3f); - depositSpeed = opts.get_or("depositSpeed", 0.3f); - evaporateSpeed = opts.get_or("evaporateSpeed", 0.01f); - gravity = opts.get_or("gravity", 4.0f); - seed = opts.get_or("seed", seed); - maxDropletLifetime = opts.get_or("maxDropletLifetime", 30); - } + // Erosion parameters + int iterations = 50000; + int erosionRadius = 3; + float inertia = 0.05f; + float sedimentCapacity = 4.0f; + float minSlope = 0.01f; + float erosionSpeed = 0.3f; + float depositSpeed = 0.3f; + float evaporateSpeed = 0.01f; + float gravity = 4.0f; + int seed = static_cast(time(nullptr)); + int maxDropletLifetime = 30; + + if (options) { + sol::table opts = *options; + iterations = opts.get_or("iterations", 50000); + erosionRadius = opts.get_or("erosionRadius", 3); + inertia = opts.get_or("inertia", 0.05f); + sedimentCapacity = opts.get_or("sedimentCapacity", 4.0f); + minSlope = opts.get_or("minSlope", 0.01f); + erosionSpeed = opts.get_or("erosionSpeed", 0.3f); + depositSpeed = opts.get_or("depositSpeed", 0.3f); + evaporateSpeed = opts.get_or("evaporateSpeed", 0.01f); + gravity = opts.get_or("gravity", 4.0f); + seed = opts.get_or("seed", seed); + maxDropletLifetime = opts.get_or("maxDropletLifetime", 30); + } - auto heightmap = tableToFloatGrid(inputHeightmap, width, height); + auto heightmap = tableToFloatGrid(inputHeightmap, width, height); - std::mt19937 rng(seed); - std::uniform_real_distribution dist(0.0f, 1.0f); + std::mt19937 rng(seed); + std::uniform_real_distribution dist(0.0f, 1.0f); - // Precompute erosion brush weights - std::vector>> brushIndices(erosionRadius * 2 + 1); - std::vector> brushWeights(erosionRadius * 2 + 1); + // Precompute erosion brush weights + std::vector>> brushIndices(erosionRadius * 2 + 1); + std::vector> brushWeights(erosionRadius * 2 + 1); - for (int radius = 0; radius <= erosionRadius; ++radius) { - for (int y = -radius; y <= radius; ++y) { - for (int x = -radius; x <= radius; ++x) { - float sqrDst = (float)(x * x + y * y); - if (sqrDst <= radius * radius) { - brushIndices[radius].push_back({x, y}); - float weight = 1.0f - std::sqrt(sqrDst) / (float)radius; - brushWeights[radius].push_back(weight); + for (int radius = 0; radius <= erosionRadius; ++radius) { + for (int y = -radius; y <= radius; ++y) { + for (int x = -radius; x <= radius; ++x) { + float sqrDst = (float)(x * x + y * y); + if (sqrDst <= radius * radius) { + brushIndices[radius].push_back({ x, y }); + float weight = 1.0f - std::sqrt(sqrDst) / (float)radius; + brushWeights[radius].push_back(weight); + } } } } - } - // Helper to get interpolated height - auto getHeight = [&](float x, float y) -> float { - int xi = (int)x; - int yi = (int)y; - float fx = x - xi; - float fy = y - yi; - - xi = std::max(0, std::min(xi, width - 2)); - yi = std::max(0, std::min(yi, height - 2)); - - float h00 = heightmap[yi][xi]; - float h10 = heightmap[yi][xi + 1]; - float h01 = heightmap[yi + 1][xi]; - float h11 = heightmap[yi + 1][xi + 1]; - - return h00 * (1 - fx) * (1 - fy) + - h10 * fx * (1 - fy) + - h01 * (1 - fx) * fy + - h11 * fx * fy; - }; - - // Helper to get gradient - auto getGradient = [&](float x, float y) -> std::pair { - int xi = (int)x; - int yi = (int)y; - - xi = std::max(1, std::min(xi, width - 2)); - yi = std::max(1, std::min(yi, height - 2)); - - float gx = (heightmap[yi][xi + 1] - heightmap[yi][xi - 1]) * 0.5f; - float gy = (heightmap[yi + 1][xi] - heightmap[yi - 1][xi]) * 0.5f; - - return {gx, gy}; - }; - - // Simulate droplets - for (int i = 0; i < iterations; ++i) { - // Random starting position - float posX = dist(rng) * (width - 2) + 1; - float posY = dist(rng) * (height - 2) + 1; - float dirX = 0, dirY = 0; - float speed = 1; - float water = 1; - float sediment = 0; - - for (int lifetime = 0; lifetime < maxDropletLifetime; ++lifetime) { - int nodeX = (int)posX; - int nodeY = (int)posY; - - // Get gradient - auto [gx, gy] = getGradient(posX, posY); - - // Update direction with inertia - dirX = dirX * inertia - gx * (1 - inertia); - dirY = dirY * inertia - gy * (1 - inertia); - - // Normalize direction - float len = std::sqrt(dirX * dirX + dirY * dirY); - if (len > 0) { - dirX /= len; - dirY /= len; - } + // Helper to get interpolated height + auto getHeight = [&](float x, float y) -> float { + int xi = (int)x; + int yi = (int)y; + float fx = x - xi; + float fy = y - yi; + + xi = std::max(0, std::min(xi, width - 2)); + yi = std::max(0, std::min(yi, height - 2)); + + float h00 = heightmap[yi][xi]; + float h10 = heightmap[yi][xi + 1]; + float h01 = heightmap[yi + 1][xi]; + float h11 = heightmap[yi + 1][xi + 1]; + + return h00 * (1 - fx) * (1 - fy) + h10 * fx * (1 - fy) + h01 * (1 - fx) * fy + h11 * fx * fy; + }; + + // Helper to get gradient + auto getGradient = [&](float x, float y) -> std::pair { + int xi = (int)x; + int yi = (int)y; + + xi = std::max(1, std::min(xi, width - 2)); + yi = std::max(1, std::min(yi, height - 2)); + + float gx = (heightmap[yi][xi + 1] - heightmap[yi][xi - 1]) * 0.5f; + float gy = (heightmap[yi + 1][xi] - heightmap[yi - 1][xi]) * 0.5f; + + return { gx, gy }; + }; + + // Simulate droplets + for (int i = 0; i < iterations; ++i) { + // Random starting position + float posX = dist(rng) * (width - 2) + 1; + float posY = dist(rng) * (height - 2) + 1; + float dirX = 0, dirY = 0; + float speed = 1; + float water = 1; + float sediment = 0; + + for (int lifetime = 0; lifetime < maxDropletLifetime; ++lifetime) { + int nodeX = (int)posX; + int nodeY = (int)posY; + + // Get gradient + auto [gx, gy] = getGradient(posX, posY); + + // Update direction with inertia + dirX = dirX * inertia - gx * (1 - inertia); + dirY = dirY * inertia - gy * (1 - inertia); + + // Normalize direction + float len = std::sqrt(dirX * dirX + dirY * dirY); + if (len > 0) { + dirX /= len; + dirY /= len; + } - // New position - float newPosX = posX + dirX; - float newPosY = posY + dirY; + // New position + float newPosX = posX + dirX; + float newPosY = posY + dirY; - // Stop if out of bounds - if (newPosX < 1 || newPosX >= width - 1 || newPosY < 1 || newPosY >= height - 1) { - break; - } + // Stop if out of bounds + if (newPosX < 1 || newPosX >= width - 1 || newPosY < 1 || newPosY >= height - 1) { + break; + } - // Height difference - float newHeight = getHeight(newPosX, newPosY); - float oldHeight = getHeight(posX, posY); - float deltaHeight = newHeight - oldHeight; + // Height difference + float newHeight = getHeight(newPosX, newPosY); + float oldHeight = getHeight(posX, posY); + float deltaHeight = newHeight - oldHeight; - // Calculate sediment capacity - float capacity = std::max(-deltaHeight, minSlope) * speed * water * sedimentCapacity; + // Calculate sediment capacity + float capacity = std::max(-deltaHeight, minSlope) * speed * water * sedimentCapacity; - // Deposit or erode - if (sediment > capacity || deltaHeight > 0) { - // Deposit sediment - float amountToDeposit = (deltaHeight > 0) ? std::min(deltaHeight, sediment) : (sediment - capacity) * depositSpeed; - sediment -= amountToDeposit; + // Deposit or erode + if (sediment > capacity || deltaHeight > 0) { + // Deposit sediment + float amountToDeposit = (deltaHeight > 0) ? std::min(deltaHeight, sediment) : (sediment - capacity) * depositSpeed; + sediment -= amountToDeposit; - // Deposit at current position - int hx = std::min(std::max(nodeX, 0), width - 1); - int hy = std::min(std::max(nodeY, 0), height - 1); - heightmap[hy][hx] += amountToDeposit; - } else { - // Erode terrain - float amountToErode = std::min((capacity - sediment) * erosionSpeed, -deltaHeight); - - // Erode in radius - for (size_t j = 0; j < brushIndices[erosionRadius].size(); ++j) { - int ex = nodeX + brushIndices[erosionRadius][j].first; - int ey = nodeY + brushIndices[erosionRadius][j].second; - - if (ex >= 0 && ex < width && ey >= 0 && ey < height) { - float weightedErode = amountToErode * brushWeights[erosionRadius][j]; - heightmap[ey][ex] -= weightedErode; - sediment += weightedErode; + // Deposit at current position + int hx = std::min(std::max(nodeX, 0), width - 1); + int hy = std::min(std::max(nodeY, 0), height - 1); + heightmap[hy][hx] += amountToDeposit; + } else { + // Erode terrain + float amountToErode = std::min((capacity - sediment) * erosionSpeed, -deltaHeight); + + // Erode in radius + for (size_t j = 0; j < brushIndices[erosionRadius].size(); ++j) { + int ex = nodeX + brushIndices[erosionRadius][j].first; + int ey = nodeY + brushIndices[erosionRadius][j].second; + + if (ex >= 0 && ex < width && ey >= 0 && ey < height) { + float weightedErode = amountToErode * brushWeights[erosionRadius][j]; + heightmap[ey][ex] -= weightedErode; + sediment += weightedErode; + } } } - } - // Update position and speed - posX = newPosX; - posY = newPosY; - speed = std::sqrt(speed * speed + deltaHeight * gravity); - water *= (1 - evaporateSpeed); + // Update position and speed + posX = newPosX; + posY = newPosY; + speed = std::sqrt(speed * speed + deltaHeight * gravity); + water *= (1 - evaporateSpeed); + } } - } - return floatGridToTable(heightmap, lua); - }); - - // algo.thermalErode(heightmap, options) -> heightmap - // Thermal erosion (talus/slope erosion) - algoTable.set_function("thermalErode", [](sol::table inputHeightmap, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); - - int height = 0; - int width = 0; - if (inputHeightmap[1].valid()) { - height = static_cast(inputHeightmap.size()); - if (inputHeightmap[1].get_type() == sol::type::table) { - sol::table firstRow = inputHeightmap[1]; - width = static_cast(firstRow.size()); + return floatGridToTable(heightmap, lua); + }); + + // algo.thermalErode(heightmap, options) -> heightmap + // Thermal erosion (talus/slope erosion) + algoTable.set_function("thermalErode", [](sol::table inputHeightmap, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + int height = 0; + int width = 0; + if (inputHeightmap[1].valid()) { + height = static_cast(inputHeightmap.size()); + if (inputHeightmap[1].get_type() == sol::type::table) { + sol::table firstRow = inputHeightmap[1]; + width = static_cast(firstRow.size()); + } } - } - if (width <= 2 || height <= 2) return inputHeightmap; + if (width <= 2 || height <= 2) { + return inputHeightmap; + } - int iterations = 50; - float talusAngle = 0.5f; // Maximum slope before erosion - float erosionAmount = 0.5f; + int iterations = 50; + float talusAngle = 0.5f; // Maximum slope before erosion + float erosionAmount = 0.5f; - if (options) { - sol::table opts = *options; - iterations = opts.get_or("iterations", 50); - talusAngle = opts.get_or("talusAngle", 0.5f); - erosionAmount = opts.get_or("erosionAmount", 0.5f); - } + if (options) { + sol::table opts = *options; + iterations = opts.get_or("iterations", 50); + talusAngle = opts.get_or("talusAngle", 0.5f); + erosionAmount = opts.get_or("erosionAmount", 0.5f); + } - auto heightmap = tableToFloatGrid(inputHeightmap, width, height); + auto heightmap = tableToFloatGrid(inputHeightmap, width, height); - // 4-directional neighbors - const int dx[] = {0, 1, 0, -1}; - const int dy[] = {-1, 0, 1, 0}; + // 4-directional neighbors + const int dx[] = { 0, 1, 0, -1 }; + const int dy[] = { -1, 0, 1, 0 }; - for (int iter = 0; iter < iterations; ++iter) { - auto newHeightmap = heightmap; + for (int iter = 0; iter < iterations; ++iter) { + auto newHeightmap = heightmap; - for (int y = 1; y < height - 1; ++y) { - for (int x = 1; x < width - 1; ++x) { - float currentHeight = heightmap[y][x]; + for (int y = 1; y < height - 1; ++y) { + for (int x = 1; x < width - 1; ++x) { + float currentHeight = heightmap[y][x]; - // Find maximum difference - float maxDiff = 0; - int maxIdx = -1; + // Find maximum difference + float maxDiff = 0; + int maxIdx = -1; - for (int i = 0; i < 4; ++i) { - int nx = x + dx[i]; - int ny = y + dy[i]; - float diff = currentHeight - heightmap[ny][nx]; - if (diff > maxDiff) { - maxDiff = diff; - maxIdx = i; + for (int i = 0; i < 4; ++i) { + int nx = x + dx[i]; + int ny = y + dy[i]; + float diff = currentHeight - heightmap[ny][nx]; + if (diff > maxDiff) { + maxDiff = diff; + maxIdx = i; + } } - } - // Erode if slope exceeds talus angle - if (maxDiff > talusAngle && maxIdx >= 0) { - float transfer = (maxDiff - talusAngle) * erosionAmount * 0.5f; - newHeightmap[y][x] -= transfer; - newHeightmap[y + dy[maxIdx]][x + dx[maxIdx]] += transfer; + // Erode if slope exceeds talus angle + if (maxDiff > talusAngle && maxIdx >= 0) { + float transfer = (maxDiff - talusAngle) * erosionAmount * 0.5f; + newHeightmap[y][x] -= transfer; + newHeightmap[y + dy[maxIdx]][x + dx[maxIdx]] += transfer; + } } } - } - - heightmap = newHeightmap; - } - - return floatGridToTable(heightmap, lua); - }); - // ======================================== - // SMOOTHING ALGORITHMS - // ======================================== - - // algo.smooth(grid, options) -> grid - // Gaussian-like smoothing for grids - algoTable.set_function("smooth", [](sol::table inputGrid, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); + heightmap = newHeightmap; + } - int height = 0; - int width = 0; - if (inputGrid[1].valid()) { - height = static_cast(inputGrid.size()); - if (inputGrid[1].get_type() == sol::type::table) { - sol::table firstRow = inputGrid[1]; - width = static_cast(firstRow.size()); + return floatGridToTable(heightmap, lua); + }); + + // ======================================== + // SMOOTHING ALGORITHMS + // ======================================== + + // algo.smooth(grid, options) -> grid + // Gaussian-like smoothing for grids + algoTable.set_function("smooth", [](sol::table inputGrid, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + int height = 0; + int width = 0; + if (inputGrid[1].valid()) { + height = static_cast(inputGrid.size()); + if (inputGrid[1].get_type() == sol::type::table) { + sol::table firstRow = inputGrid[1]; + width = static_cast(firstRow.size()); + } } - } - if (width <= 2 || height <= 2) return inputGrid; + if (width <= 2 || height <= 2) { + return inputGrid; + } - int iterations = 1; - int kernelSize = 3; + int iterations = 1; + int kernelSize = 3; - if (options) { - sol::table opts = *options; - iterations = opts.get_or("iterations", 1); - kernelSize = opts.get_or("kernelSize", 3); - } + if (options) { + sol::table opts = *options; + iterations = opts.get_or("iterations", 1); + kernelSize = opts.get_or("kernelSize", 3); + } - auto grid = tableToFloatGrid(inputGrid, width, height); + auto grid = tableToFloatGrid(inputGrid, width, height); - int radius = kernelSize / 2; + int radius = kernelSize / 2; - for (int iter = 0; iter < iterations; ++iter) { - auto newGrid = grid; + for (int iter = 0; iter < iterations; ++iter) { + auto newGrid = grid; - for (int y = radius; y < height - radius; ++y) { - for (int x = radius; x < width - radius; ++x) { - float sum = 0; - int count = 0; + for (int y = radius; y < height - radius; ++y) { + for (int x = radius; x < width - radius; ++x) { + float sum = 0; + int count = 0; - for (int dy = -radius; dy <= radius; ++dy) { - for (int dx = -radius; dx <= radius; ++dx) { - sum += grid[y + dy][x + dx]; - count++; + for (int dy = -radius; dy <= radius; ++dy) { + for (int dx = -radius; dx <= radius; ++dx) { + sum += grid[y + dy][x + dx]; + count++; + } } - } - newGrid[y][x] = sum / count; + newGrid[y][x] = sum / count; + } } - } - - grid = newGrid; - } - - return floatGridToTable(grid, lua); - }); - // ======================================== - // VORONOI DIAGRAM - // ======================================== - - // algo.voronoi(width, height, points) -> grid of region indices - // Generate Voronoi diagram from seed points - algoTable.set_function("voronoi", [](int width, int height, sol::table points, sol::this_state s) -> sol::table { - sol::state_view lua(s); + grid = newGrid; + } - // Parse points - std::vector> seedPoints; - for (auto& kv : points) { - if (kv.second.get_type() == sol::type::table) { - sol::table pt = kv.second; - int x = pt.get_or("x", pt.get_or(1, 0)); - int y = pt.get_or("y", pt.get_or(2, 0)); - seedPoints.push_back({x, y}); + return floatGridToTable(grid, lua); + }); + + // ======================================== + // VORONOI DIAGRAM + // ======================================== + + // algo.voronoi(width, height, points) -> grid of region indices + // Generate Voronoi diagram from seed points + algoTable.set_function("voronoi", [](int width, int height, sol::table points, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + // Parse points + std::vector> seedPoints; + for (auto& kv : points) { + if (kv.second.get_type() == sol::type::table) { + sol::table pt = kv.second; + int x = pt.get_or("x", pt.get_or(1, 0)); + int y = pt.get_or("y", pt.get_or(2, 0)); + seedPoints.push_back({ x, y }); + } } - } - if (seedPoints.empty()) { - return lua.create_table(); - } + if (seedPoints.empty()) { + return lua.create_table(); + } - std::vector> grid(height, std::vector(width, 0)); + std::vector> grid(height, std::vector(width, 0)); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - float minDist = std::numeric_limits::max(); - int closestRegion = 0; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + float minDist = std::numeric_limits::max(); + int closestRegion = 0; - for (size_t i = 0; i < seedPoints.size(); ++i) { - float dx = (float)(x - seedPoints[i].first); - float dy = (float)(y - seedPoints[i].second); - float dist = dx * dx + dy * dy; // Squared distance for speed + for (size_t i = 0; i < seedPoints.size(); ++i) { + float dx = (float)(x - seedPoints[i].first); + float dy = (float)(y - seedPoints[i].second); + float dist = dx * dx + dy * dy; // Squared distance for speed - if (dist < minDist) { - minDist = dist; - closestRegion = static_cast(i + 1); // 1-indexed for Lua + if (dist < minDist) { + minDist = dist; + closestRegion = static_cast(i + 1); // 1-indexed for Lua + } } - } - grid[y][x] = closestRegion; + grid[y][x] = closestRegion; + } } - } - return gridToTable(grid, lua); - }); + return gridToTable(grid, lua); + }); - // algo.generateRandomPoints(width, height, count, seed) -> table of points - // Generate random points for Voronoi, etc. - algoTable.set_function("generateRandomPoints", [](int width, int height, int count, sol::optional seed, sol::this_state s) -> sol::table { - sol::state_view lua(s); + // algo.generateRandomPoints(width, height, count, seed) -> table of points + // Generate random points for Voronoi, etc. + algoTable.set_function("generateRandomPoints", [](int width, int height, int count, sol::optional seed, sol::this_state s) -> sol::table { + sol::state_view lua(s); - int sd = seed.value_or(static_cast(time(nullptr))); - std::mt19937 rng(sd); - std::uniform_int_distribution distX(0, width - 1); - std::uniform_int_distribution distY(0, height - 1); + int sd = seed.value_or(static_cast(time(nullptr))); + std::mt19937 rng(sd); + std::uniform_int_distribution distX(0, width - 1); + std::uniform_int_distribution distY(0, height - 1); - sol::table result = lua.create_table(); + sol::table result = lua.create_table(); - for (int i = 0; i < count; ++i) { - sol::table point = lua.create_table(); - point["x"] = distX(rng); - point["y"] = distY(rng); - result[i + 1] = point; - } + for (int i = 0; i < count; ++i) { + sol::table point = lua.create_table(); + point["x"] = distX(rng); + point["y"] = distY(rng); + result[i + 1] = point; + } - return result; - }); + return result; + }); - // ======================================== - // MAZE GENERATION - // ======================================== + // ======================================== + // MAZE GENERATION + // ======================================== - // algo.generateMaze(width, height, options) -> grid - // Generate a maze using recursive backtracking - algoTable.set_function("generateMaze", [](int width, int height, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); + // algo.generateMaze(width, height, options) -> grid + // Generate a maze using recursive backtracking + algoTable.set_function("generateMaze", [](int width, int height, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); - int seed = static_cast(time(nullptr)); + int seed = static_cast(time(nullptr)); - if (options) { - sol::table opts = *options; - seed = opts.get_or("seed", seed); - } + if (options) { + sol::table opts = *options; + seed = opts.get_or("seed", seed); + } - std::mt19937 rng(seed); + std::mt19937 rng(seed); - // Make dimensions odd for proper maze - if (width % 2 == 0) width++; - if (height % 2 == 0) height++; + // Make dimensions odd for proper maze + if (width % 2 == 0) { + width++; + } + if (height % 2 == 0) { + height++; + } - // Initialize grid with walls - std::vector> grid(height, std::vector(width, 1)); + // Initialize grid with walls + std::vector> grid(height, std::vector(width, 1)); - // Recursive backtracking - std::function carve = [&](int x, int y) { - grid[y][x] = 0; + // Recursive backtracking + std::function carve = [&](int x, int y) { + grid[y][x] = 0; - // Shuffle directions - std::vector dirs = {0, 1, 2, 3}; // N, E, S, W - std::shuffle(dirs.begin(), dirs.end(), rng); + // Shuffle directions + std::vector dirs = { 0, 1, 2, 3 }; // N, E, S, W + std::shuffle(dirs.begin(), dirs.end(), rng); - const int dx[] = {0, 1, 0, -1}; - const int dy[] = {-1, 0, 1, 0}; + const int dx[] = { 0, 1, 0, -1 }; + const int dy[] = { -1, 0, 1, 0 }; - for (int dir : dirs) { - int nx = x + dx[dir] * 2; - int ny = y + dy[dir] * 2; + for (int dir : dirs) { + int nx = x + dx[dir] * 2; + int ny = y + dy[dir] * 2; - if (nx > 0 && nx < width - 1 && ny > 0 && ny < height - 1 && grid[ny][nx] == 1) { - // Carve through wall - grid[y + dy[dir]][x + dx[dir]] = 0; - carve(nx, ny); + if (nx > 0 && nx < width - 1 && ny > 0 && ny < height - 1 && grid[ny][nx] == 1) { + // Carve through wall + grid[y + dy[dir]][x + dx[dir]] = 0; + carve(nx, ny); + } } + }; + + // Start from (1, 1) + carve(1, 1); + + return gridToTable(grid, lua); + }); + + // ======================================== + // BSP (Binary Space Partitioning) - Dungeon Generation + // ======================================== + + // algo.generateDungeon(width, height, options) -> { grid, rooms } + // Generate a dungeon using BSP + algoTable.set_function("generateDungeon", [](int width, int height, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + int minRoomSize = 5; + int maxRoomSize = 15; + int seed = static_cast(time(nullptr)); + int maxDepth = 4; + + if (options) { + sol::table opts = *options; + minRoomSize = opts.get_or("minRoomSize", 5); + maxRoomSize = opts.get_or("maxRoomSize", 15); + seed = opts.get_or("seed", seed); + maxDepth = opts.get_or("maxDepth", 4); } - }; - // Start from (1, 1) - carve(1, 1); + std::mt19937 rng(seed); - return gridToTable(grid, lua); - }); + // Initialize grid with walls + std::vector> grid(height, std::vector(width, 1)); - // ======================================== - // BSP (Binary Space Partitioning) - Dungeon Generation - // ======================================== + // Store rooms + std::vector> rooms; // x, y, w, h - // algo.generateDungeon(width, height, options) -> { grid, rooms } - // Generate a dungeon using BSP - algoTable.set_function("generateDungeon", [](int width, int height, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); + // BSP Node + struct BSPNode { + int x, y, w, h; + BSPNode* left = nullptr; + BSPNode* right = nullptr; + int roomX, roomY, roomW, roomH; + bool hasRoom = false; + }; - int minRoomSize = 5; - int maxRoomSize = 15; - int seed = static_cast(time(nullptr)); - int maxDepth = 4; + std::function split; + split = [&](int x, int y, int w, int h, int depth) -> BSPNode* { + BSPNode* node = new BSPNode { x, y, w, h }; - if (options) { - sol::table opts = *options; - minRoomSize = opts.get_or("minRoomSize", 5); - maxRoomSize = opts.get_or("maxRoomSize", 15); - seed = opts.get_or("seed", seed); - maxDepth = opts.get_or("maxDepth", 4); - } + if (depth >= maxDepth || w < minRoomSize * 2 || h < minRoomSize * 2) { + // Check if space is sufficient for a room + if (w < minRoomSize + 2 || h < minRoomSize + 2) { + return node; + } - std::mt19937 rng(seed); + // Create room + std::uniform_int_distribution roomW(minRoomSize, std::min(maxRoomSize, w - 2)); + std::uniform_int_distribution roomH(minRoomSize, std::min(maxRoomSize, h - 2)); - // Initialize grid with walls - std::vector> grid(height, std::vector(width, 1)); + int rw = roomW(rng); + int rh = roomH(rng); - // Store rooms - std::vector> rooms; // x, y, w, h + std::uniform_int_distribution roomX(x + 1, x + w - rw - 1); + std::uniform_int_distribution roomY(y + 1, y + h - rh - 1); - // BSP Node - struct BSPNode { - int x, y, w, h; - BSPNode* left = nullptr; - BSPNode* right = nullptr; - int roomX, roomY, roomW, roomH; - bool hasRoom = false; - }; + node->roomX = roomX(rng); + node->roomY = roomY(rng); + node->roomW = rw; + node->roomH = rh; + node->hasRoom = true; - std::function split; - split = [&](int x, int y, int w, int h, int depth) -> BSPNode* { - BSPNode* node = new BSPNode{x, y, w, h}; + rooms.push_back({ node->roomX, node->roomY, rw, rh }); - if (depth >= maxDepth || w < minRoomSize * 2 || h < minRoomSize * 2) { - // Check if space is sufficient for a room - if (w < minRoomSize + 2 || h < minRoomSize + 2) { return node; } - // Create room - std::uniform_int_distribution roomW(minRoomSize, std::min(maxRoomSize, w - 2)); - std::uniform_int_distribution roomH(minRoomSize, std::min(maxRoomSize, h - 2)); - - int rw = roomW(rng); - int rh = roomH(rng); - - std::uniform_int_distribution roomX(x + 1, x + w - rw - 1); - std::uniform_int_distribution roomY(y + 1, y + h - rh - 1); + // Split + std::uniform_real_distribution splitDist(0.3f, 0.7f); + float splitRatio = splitDist(rng); - node->roomX = roomX(rng); - node->roomY = roomY(rng); - node->roomW = rw; - node->roomH = rh; - node->hasRoom = true; + bool splitHorizontal = (w < h) || (w == h && rng() % 2 == 0); - rooms.push_back({node->roomX, node->roomY, rw, rh}); + if (splitHorizontal) { + int splitY = y + static_cast(h * splitRatio); + node->left = split(x, y, w, splitY - y, depth + 1); + node->right = split(x, splitY, w, y + h - splitY, depth + 1); + } else { + int splitX = x + static_cast(w * splitRatio); + node->left = split(x, y, splitX - x, h, depth + 1); + node->right = split(splitX, y, x + w - splitX, h, depth + 1); + } return node; - } - - // Split - std::uniform_real_distribution splitDist(0.3f, 0.7f); - float splitRatio = splitDist(rng); - - bool splitHorizontal = (w < h) || (w == h && rng() % 2 == 0); - - if (splitHorizontal) { - int splitY = y + static_cast(h * splitRatio); - node->left = split(x, y, w, splitY - y, depth + 1); - node->right = split(x, splitY, w, y + h - splitY, depth + 1); - } else { - int splitX = x + static_cast(w * splitRatio); - node->left = split(x, y, splitX - x, h, depth + 1); - node->right = split(splitX, y, x + w - splitX, h, depth + 1); - } + }; - return node; - }; + BSPNode* root = split(0, 0, width, height, 0); - BSPNode* root = split(0, 0, width, height, 0); + // Carve rooms + for (const auto& room : rooms) { + int rx = std::get<0>(room); + int ry = std::get<1>(room); + int rw = std::get<2>(room); + int rh = std::get<3>(room); - // Carve rooms - for (const auto& room : rooms) { - int rx = std::get<0>(room); - int ry = std::get<1>(room); - int rw = std::get<2>(room); - int rh = std::get<3>(room); - - for (int py = ry; py < ry + rh && py < height; ++py) { - for (int px = rx; px < rx + rw && px < width; ++px) { - grid[py][px] = 0; + for (int py = ry; py < ry + rh && py < height; ++py) { + for (int px = rx; px < rx + rw && px < width; ++px) { + grid[py][px] = 0; + } } } - } - // Connect rooms with corridors - for (size_t i = 1; i < rooms.size(); ++i) { - int x1 = std::get<0>(rooms[i - 1]) + std::get<2>(rooms[i - 1]) / 2; - int y1 = std::get<1>(rooms[i - 1]) + std::get<3>(rooms[i - 1]) / 2; - int x2 = std::get<0>(rooms[i]) + std::get<2>(rooms[i]) / 2; - int y2 = std::get<1>(rooms[i]) + std::get<3>(rooms[i]) / 2; - - // L-shaped corridor - if (rng() % 2 == 0) { - // Horizontal first - for (int x = std::min(x1, x2); x <= std::max(x1, x2); ++x) { - if (y1 >= 0 && y1 < height && x >= 0 && x < width) grid[y1][x] = 0; - } - for (int y = std::min(y1, y2); y <= std::max(y1, y2); ++y) { - if (y >= 0 && y < height && x2 >= 0 && x2 < width) grid[y][x2] = 0; - } - } else { - // Vertical first - for (int y = std::min(y1, y2); y <= std::max(y1, y2); ++y) { - if (y >= 0 && y < height && x1 >= 0 && x1 < width) grid[y][x1] = 0; - } - for (int x = std::min(x1, x2); x <= std::max(x1, x2); ++x) { - if (y2 >= 0 && y2 < height && x >= 0 && x < width) grid[y2][x] = 0; + // Connect rooms with corridors + for (size_t i = 1; i < rooms.size(); ++i) { + int x1 = std::get<0>(rooms[i - 1]) + std::get<2>(rooms[i - 1]) / 2; + int y1 = std::get<1>(rooms[i - 1]) + std::get<3>(rooms[i - 1]) / 2; + int x2 = std::get<0>(rooms[i]) + std::get<2>(rooms[i]) / 2; + int y2 = std::get<1>(rooms[i]) + std::get<3>(rooms[i]) / 2; + + // L-shaped corridor + if (rng() % 2 == 0) { + // Horizontal first + for (int x = std::min(x1, x2); x <= std::max(x1, x2); ++x) { + if (y1 >= 0 && y1 < height && x >= 0 && x < width) { + grid[y1][x] = 0; + } + } + for (int y = std::min(y1, y2); y <= std::max(y1, y2); ++y) { + if (y >= 0 && y < height && x2 >= 0 && x2 < width) { + grid[y][x2] = 0; + } + } + } else { + // Vertical first + for (int y = std::min(y1, y2); y <= std::max(y1, y2); ++y) { + if (y >= 0 && y < height && x1 >= 0 && x1 < width) { + grid[y][x1] = 0; + } + } + for (int x = std::min(x1, x2); x <= std::max(x1, x2); ++x) { + if (y2 >= 0 && y2 < height && x >= 0 && x < width) { + grid[y2][x] = 0; + } + } } } - } - // Cleanup BSP - std::function deleteBSP = [&](BSPNode* node) { - if (node) { - deleteBSP(node->left); - deleteBSP(node->right); - delete node; + // Cleanup BSP + std::function deleteBSP = [&](BSPNode* node) { + if (node) { + deleteBSP(node->left); + deleteBSP(node->right); + delete node; + } + }; + deleteBSP(root); + + // Return result + sol::table result = lua.create_table(); + result["grid"] = gridToTable(grid, lua); + + sol::table roomsTable = lua.create_table(); + for (size_t i = 0; i < rooms.size(); ++i) { + sol::table room = lua.create_table(); + room["x"] = std::get<0>(rooms[i]); + room["y"] = std::get<1>(rooms[i]); + room["width"] = std::get<2>(rooms[i]); + room["height"] = std::get<3>(rooms[i]); + roomsTable[i + 1] = room; } - }; - deleteBSP(root); + result["rooms"] = roomsTable; - // Return result - sol::table result = lua.create_table(); - result["grid"] = gridToTable(grid, lua); - - sol::table roomsTable = lua.create_table(); - for (size_t i = 0; i < rooms.size(); ++i) { - sol::table room = lua.create_table(); - room["x"] = std::get<0>(rooms[i]); - room["y"] = std::get<1>(rooms[i]); - room["width"] = std::get<2>(rooms[i]); - room["height"] = std::get<3>(rooms[i]); - roomsTable[i + 1] = room; - } - result["rooms"] = roomsTable; - - return result; - }); + return result; + }); - lua["algo"] = algoTable; -} + lua["algo"] = algoTable; + } } // namespace LuaAPI diff --git a/source/lua/lua_api_app.cpp b/source/lua/lua_api_app.cpp index cdf8161ab..1b1a2bffa 100644 --- a/source/lua/lua_api_app.cpp +++ b/source/lua/lua_api_app.cpp @@ -48,692 +48,718 @@ namespace LuaAPI { #include "../selection.h" #include "../house.h" -// Helper to sync map metadata when swapping tiles -static void updateTileMetadata(Editor* editor, Tile* tile, bool adding) { - if (!editor || !tile) return; - Map* map = editor->getMap(); - if (!map) return; - - if (adding) { - if (tile->spawn) map->addSpawn(tile); - if (tile->getHouseID()) { - House* h = map->houses.getHouse(tile->getHouseID()); - if (h) h->addTile(tile); + // Helper to sync map metadata when swapping tiles + static void updateTileMetadata(Editor* editor, Tile* tile, bool adding) { + if (!editor || !tile) { + return; } - } else { - if (tile->spawn) map->removeSpawn(tile); - if (tile->getHouseID()) { - House* h = map->houses.getHouse(tile->getHouseID()); - if (h) h->removeTile(tile); + Map* map = editor->getMap(); + if (!map) { + return; } - // Also clean up selection if removing - if (tile->isSelected()) { - // We use internal session to avoid creating undo actions for this cleanup - editor->selection.start(Selection::INTERNAL); - editor->selection.removeInternal(tile); - editor->selection.finish(Selection::INTERNAL); - } - } -} -class LuaTransaction { - bool active; - Editor* editor; - BatchAction* batch; - Action* action; - std::unordered_map originalTiles; - - uint64_t positionKey(const Position& pos) const { - return (static_cast(pos.x) << 32) | - (static_cast(pos.y) << 16) | - static_cast(pos.z); + if (adding) { + if (tile->spawn) { + map->addSpawn(tile); + } + if (tile->getHouseID()) { + House* h = map->houses.getHouse(tile->getHouseID()); + if (h) { + h->addTile(tile); + } + } + } else { + if (tile->spawn) { + map->removeSpawn(tile); + } + if (tile->getHouseID()) { + House* h = map->houses.getHouse(tile->getHouseID()); + if (h) { + h->removeTile(tile); + } + } + // Also clean up selection if removing + if (tile->isSelected()) { + // We use internal session to avoid creating undo actions for this cleanup + editor->selection.start(Selection::INTERNAL); + editor->selection.removeInternal(tile); + editor->selection.finish(Selection::INTERNAL); + } + } } -public: - static LuaTransaction& getInstance() { - static LuaTransaction instance; - return instance; - } + class LuaTransaction { + bool active; + Editor* editor; + BatchAction* batch; + Action* action; + std::unordered_map originalTiles; - LuaTransaction() : active(false), editor(nullptr), batch(nullptr), action(nullptr) {} + uint64_t positionKey(const Position& pos) const { + return (static_cast(pos.x) << 32) | (static_cast(pos.y) << 16) | static_cast(pos.z); + } - void begin(Editor* ed) { - if (active) { - throw sol::error("Transaction already in progress"); + public: + static LuaTransaction& getInstance() { + static LuaTransaction instance; + return instance; } - editor = ed; - if (!editor || !editor->actionQueue) { - throw sol::error("No editor or action queue available"); + LuaTransaction() : + active(false), editor(nullptr), batch(nullptr), action(nullptr) { } + + void begin(Editor* ed) { + if (active) { + throw sol::error("Transaction already in progress"); + } + + editor = ed; + if (!editor || !editor->actionQueue) { + throw sol::error("No editor or action queue available"); + } + + active = true; + batch = editor->actionQueue->createBatch(ACTION_LUA_SCRIPT); + action = editor->actionQueue->createAction(ACTION_LUA_SCRIPT); + originalTiles.clear(); } - active = true; - batch = editor->actionQueue->createBatch(ACTION_LUA_SCRIPT); - action = editor->actionQueue->createAction(ACTION_LUA_SCRIPT); - originalTiles.clear(); - } + void commit() { + if (!active) { + return; + } + + // Process each modified tile + for (auto& pair : originalTiles) { + Tile* originalTile = pair.second; + Position pos = originalTile->getPosition(); - void commit() { - if (!active) return; + // Get the current (modified) tile from the map + Tile* modifiedTile = editor->getMap()->getTile(pos); + if (modifiedTile) { + // Create a deep copy of the modified tile - this is what we want as the "new" state + Tile* modifiedCopy = modifiedTile->deepCopy(*editor->getMap()); - // Process each modified tile - for (auto& pair : originalTiles) { - Tile* originalTile = pair.second; - Position pos = originalTile->getPosition(); + // Swap the original back into the map + Tile* swappedOut = editor->getMap()->swapTile(pos, originalTile); - // Get the current (modified) tile from the map - Tile* modifiedTile = editor->getMap()->getTile(pos); - if (modifiedTile) { - // Create a deep copy of the modified tile - this is what we want as the "new" state - Tile* modifiedCopy = modifiedTile->deepCopy(*editor->getMap()); + // swappedOut should be the modifiedTile. We need to clean it up. + // Remove it from Map metadata (spawns, houses) and selection + updateTileMetadata(editor, swappedOut, false); - // Swap the original back into the map - Tile* swappedOut = editor->getMap()->swapTile(pos, originalTile); + // Add originalTile back to Map metadata + updateTileMetadata(editor, originalTile, true); - // swappedOut should be the modifiedTile. We need to clean it up. - // Remove it from Map metadata (spawns, houses) and selection - updateTileMetadata(editor, swappedOut, false); + delete swappedOut; - // Add originalTile back to Map metadata - updateTileMetadata(editor, originalTile, true); + // Create Change with the modified copy + // When actions commit, they will swap modifiedCopy in and originalTile out. + // The Action system handles metadata updates during its commit/undo. + Change* change = new Change(modifiedCopy); + action->addChange(change); + } else { + // No real change or tile was removed, cleanup + delete originalTile; + } + } - delete swappedOut; + // Clear - ownership has been transferred + originalTiles.clear(); - // Create Change with the modified copy - // When actions commit, they will swap modifiedCopy in and originalTile out. - // The Action system handles metadata updates during its commit/undo. - Change* change = new Change(modifiedCopy); - action->addChange(change); + if (action->size() > 0) { + batch->addAndCommitAction(action); + editor->addBatch(batch); + editor->getMap()->doChange(); + g_gui.RefreshView(); // Force redraw immediately } else { - // No real change or tile was removed, cleanup - delete originalTile; + // No changes, clean up + delete action; + delete batch; } - } - // Clear - ownership has been transferred - originalTiles.clear(); - - if (action->size() > 0) { - batch->addAndCommitAction(action); - editor->addBatch(batch); - editor->getMap()->doChange(); - g_gui.RefreshView(); // Force redraw immediately - } else { - // No changes, clean up - delete action; - delete batch; + cleanup(); } - cleanup(); - } + void rollback() { + if (!active) { + return; + } - void rollback() { - if (!active) return; + // Restore original tiles (discard any changes made) + for (auto& pair : originalTiles) { + Tile* originalTile = pair.second; + if (originalTile) { + Position pos = originalTile->getPosition(); + Tile* modifiedTile = editor->getMap()->swapTile(pos, originalTile); - // Restore original tiles (discard any changes made) - for (auto& pair : originalTiles) { - Tile* originalTile = pair.second; - if (originalTile) { - Position pos = originalTile->getPosition(); - Tile* modifiedTile = editor->getMap()->swapTile(pos, originalTile); + // Clean up modified tile + updateTileMetadata(editor, modifiedTile, false); - // Clean up modified tile - updateTileMetadata(editor, modifiedTile, false); + // Restore original tile metadata + updateTileMetadata(editor, originalTile, true); - // Restore original tile metadata - updateTileMetadata(editor, originalTile, true); - - delete modifiedTile; // Discard the modified version + delete modifiedTile; // Discard the modified version + } } - } - originalTiles.clear(); + originalTiles.clear(); - // Discard without committing - delete action; - delete batch; + // Discard without committing + delete action; + delete batch; - cleanup(); - } + cleanup(); + } - void markTileModified(Tile* tile) { - if (!active || !tile) return; + void markTileModified(Tile* tile) { + if (!active || !tile) { + return; + } - Position pos = tile->getPosition(); - uint64_t key = positionKey(pos); + Position pos = tile->getPosition(); + uint64_t key = positionKey(pos); - // Only snapshot the tile once per transaction (first time it's modified) - if (originalTiles.find(key) == originalTiles.end()) { - // Create a deep copy of the ORIGINAL tile BEFORE modification - Tile* originalCopy = tile->deepCopy(*editor->getMap()); - originalTiles[key] = originalCopy; + // Only snapshot the tile once per transaction (first time it's modified) + if (originalTiles.find(key) == originalTiles.end()) { + // Create a deep copy of the ORIGINAL tile BEFORE modification + Tile* originalCopy = tile->deepCopy(*editor->getMap()); + originalTiles[key] = originalCopy; + } } - } - bool isActive() const { return active; } - Editor* getEditor() const { return editor; } + bool isActive() const { + return active; + } + Editor* getEditor() const { + return editor; + } -private: - void cleanup() { - active = false; - editor = nullptr; - batch = nullptr; - action = nullptr; - // Don't clear originalTiles here - it should be empty or ownership transferred - } -}; + private: + void cleanup() { + active = false; + editor = nullptr; + batch = nullptr; + action = nullptr; + // Don't clear originalTiles here - it should be empty or ownership transferred + } + }; -// Global accessor for tile modification tracking (used by lua_api_tile.cpp) -void markTileForUndo(Tile* tile) { - if (LuaTransaction::getInstance().isActive()) { - LuaTransaction::getInstance().markTileModified(tile); + // Global accessor for tile modification tracking (used by lua_api_tile.cpp) + void markTileForUndo(Tile* tile) { + if (LuaTransaction::getInstance().isActive()) { + LuaTransaction::getInstance().markTileModified(tile); + } } -} -// ============================================================================ -// Helper Functions -// ============================================================================ + // ============================================================================ + // Helper Functions + // ============================================================================ -// Helper function to show alert dialog -static int showAlert(sol::this_state ts, sol::object arg) { - sol::state_view lua(ts); + // Helper function to show alert dialog + static int showAlert(sol::this_state ts, sol::object arg) { + sol::state_view lua(ts); - std::string title = "Script"; - std::string message; - std::vector buttons; + std::string title = "Script"; + std::string message; + std::vector buttons; - // Handle different argument types - if (arg.is()) { - message = arg.as(); - buttons.push_back("OK"); - } else if (arg.is()) { - sol::table opts = arg.as(); + // Handle different argument types + if (arg.is()) { + message = arg.as(); + buttons.push_back("OK"); + } else if (arg.is()) { + sol::table opts = arg.as(); - if (opts["title"].valid()) { - title = opts["title"].get(); - } - if (opts["text"].valid()) { - message = opts["text"].get(); - } - if (opts["buttons"].valid()) { - sol::table btns = opts["buttons"]; - for (size_t i = 1; i <= btns.size(); ++i) { - if (btns[i].valid()) { - buttons.push_back(btns[i].get()); + if (opts["title"].valid()) { + title = opts["title"].get(); + } + if (opts["text"].valid()) { + message = opts["text"].get(); + } + if (opts["buttons"].valid()) { + sol::table btns = opts["buttons"]; + for (size_t i = 1; i <= btns.size(); ++i) { + if (btns[i].valid()) { + buttons.push_back(btns[i].get()); + } } } - } - if (buttons.empty()) { + if (buttons.empty()) { + buttons.push_back("OK"); + } + } else { + sol::function tostring = lua["tostring"]; + message = tostring(arg); buttons.push_back("OK"); } - } else { - sol::function tostring = lua["tostring"]; - message = tostring(arg); - buttons.push_back("OK"); - } - // Determine dialog style based on buttons - long style = wxCENTRE; - if (buttons.size() == 1) { - style |= wxOK; - } else if (buttons.size() == 2) { - std::string btn1 = buttons[0]; - std::string btn2 = buttons[1]; - std::transform(btn1.begin(), btn1.end(), btn1.begin(), ::tolower); - std::transform(btn2.begin(), btn2.end(), btn2.begin(), ::tolower); - - if ((btn1 == "ok" && btn2 == "cancel") || (btn1 == "cancel" && btn2 == "ok")) { - style |= wxOK | wxCANCEL; - } else if ((btn1 == "yes" && btn2 == "no") || (btn1 == "no" && btn2 == "yes")) { - style |= wxYES_NO; - } else { - style |= wxOK | wxCANCEL; + // Determine dialog style based on buttons + long style = wxCENTRE; + if (buttons.size() == 1) { + style |= wxOK; + } else if (buttons.size() == 2) { + std::string btn1 = buttons[0]; + std::string btn2 = buttons[1]; + std::transform(btn1.begin(), btn1.end(), btn1.begin(), ::tolower); + std::transform(btn2.begin(), btn2.end(), btn2.begin(), ::tolower); + + if ((btn1 == "ok" && btn2 == "cancel") || (btn1 == "cancel" && btn2 == "ok")) { + style |= wxOK | wxCANCEL; + } else if ((btn1 == "yes" && btn2 == "no") || (btn1 == "no" && btn2 == "yes")) { + style |= wxYES_NO; + } else { + style |= wxOK | wxCANCEL; + } + } else if (buttons.size() >= 3) { + style |= wxYES_NO | wxCANCEL; } - } else if (buttons.size() >= 3) { - style |= wxYES_NO | wxCANCEL; - } - wxWindow* parent = g_gui.root; - wxMessageDialog dlg(parent, wxString(message), wxString(title), style); - - int result = dlg.ShowModal(); - - switch (result) { - case wxID_OK: - case wxID_YES: - return 1; - case wxID_NO: - return 2; - case wxID_CANCEL: - return buttons.size() >= 3 ? 3 : 2; - default: - return 0; - } -} - -// Check if a map is currently open -static bool hasMap() { - Editor* editor = g_gui.GetCurrentEditor(); - return editor != nullptr && editor->getMap() != nullptr; -} + wxWindow* parent = g_gui.root; + wxMessageDialog dlg(parent, wxString(message), wxString(title), style); -// Refresh the map view -static void refresh() { - g_gui.RefreshView(); -} + int result = dlg.ShowModal(); -// Get the current Map object -static Map* getMap() { - Editor* editor = g_gui.GetCurrentEditor(); - if (!editor) return nullptr; - return editor->getMap(); -} + switch (result) { + case wxID_OK: + case wxID_YES: + return 1; + case wxID_NO: + return 2; + case wxID_CANCEL: + return buttons.size() >= 3 ? 3 : 2; + default: + return 0; + } + } -// Get the current Selection object -static Selection* getSelection() { - Editor* editor = g_gui.GetCurrentEditor(); - if (!editor) return nullptr; - return &editor->selection; -} + // Check if a map is currently open + static bool hasMap() { + Editor* editor = g_gui.GetCurrentEditor(); + return editor != nullptr && editor->getMap() != nullptr; + } -static void setClipboard(const std::string& text) { - if (wxTheClipboard->Open()) { - wxTheClipboard->SetData(new wxTextDataObject(text)); - wxTheClipboard->Close(); + // Refresh the map view + static void refresh() { + g_gui.RefreshView(); } -} -// Transaction function with undo/redo support -static void transaction(const std::string& name, sol::function func) { - Editor* editor = g_gui.GetCurrentEditor(); - if (!editor) { - throw sol::error("No map open"); + // Get the current Map object + static Map* getMap() { + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) { + return nullptr; + } + return editor->getMap(); } - LuaTransaction& trans = LuaTransaction::getInstance(); + // Get the current Selection object + static Selection* getSelection() { + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) { + return nullptr; + } + return &editor->selection; + } - try { - trans.begin(editor); - func(); - trans.commit(); - g_gui.RefreshView(); - } catch (const sol::error&) { - trans.rollback(); - throw; // Re-throw to show error to user - } catch (const std::exception& e) { - trans.rollback(); - throw sol::error(std::string("Transaction failed: ") + e.what()); - } catch (...) { - trans.rollback(); - throw sol::error("Transaction failed with unknown error"); + static void setClipboard(const std::string& text) { + if (wxTheClipboard->Open()) { + wxTheClipboard->SetData(new wxTextDataObject(text)); + wxTheClipboard->Close(); + } } -} -static sol::object getBorders(sol::this_state ts) { - sol::state_view lua(ts); + // Transaction function with undo/redo support + static void transaction(const std::string& name, sol::function func) { + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) { + throw sol::error("No map open"); + } - sol::table bordersTable = lua.create_table(); + LuaTransaction& trans = LuaTransaction::getInstance(); - for (auto& pair : g_brushes.getBorders()) { - AutoBorder* border = pair.second; - if (!border) continue; + try { + trans.begin(editor); + func(); + trans.commit(); + g_gui.RefreshView(); + } catch (const sol::error&) { + trans.rollback(); + throw; // Re-throw to show error to user + } catch (const std::exception& e) { + trans.rollback(); + throw sol::error(std::string("Transaction failed: ") + e.what()); + } catch (...) { + trans.rollback(); + throw sol::error("Transaction failed with unknown error"); + } + } - sol::table b = lua.create_table(); - b["id"] = border->id; - b["group"] = border->group; - b["ground"] = border->ground; + static sol::object getBorders(sol::this_state ts) { + sol::state_view lua(ts); - sol::table tiles = lua.create_table(); - for(int i = 0; i < 13; ++i) { - tiles[i+1] = border->tiles[i]; - } - b["tiles"] = tiles; + sol::table bordersTable = lua.create_table(); - bordersTable[border->id] = b; - } + for (auto& pair : g_brushes.getBorders()) { + AutoBorder* border = pair.second; + if (!border) { + continue; + } - return bordersTable; -} + sol::table b = lua.create_table(); + b["id"] = border->id; + b["group"] = border->group; + b["ground"] = border->ground; -static std::string getDataDirectory() { - return GUI::GetDataDirectory().ToStdString(); -} + sol::table tiles = lua.create_table(); + for (int i = 0; i < 13; ++i) { + tiles[i + 1] = border->tiles[i]; + } + b["tiles"] = tiles; -static sol::table storageForScript(sol::this_state ts, const std::string& name) { - sol::state_view lua(ts); - sol::table storage = lua.create_table(); + bordersTable[border->id] = b; + } - std::string scriptDir = "."; - if (lua["SCRIPT_DIR"].valid()) { - scriptDir = lua["SCRIPT_DIR"]; + return bordersTable; } - std::string filename = name; - if (filename.find('.') == std::string::npos) { - filename += ".json"; + static std::string getDataDirectory() { + return GUI::GetDataDirectory().ToStdString(); } - std::string path = scriptDir + "/" + filename; - storage["path"] = path; - - storage["load"] = [path](sol::this_state ts2, sol::object) -> sol::object { - sol::state_view lua(ts2); - std::ifstream file(path); - if (!file.is_open()) { - return sol::make_object(lua, sol::nil); - } - std::stringstream buffer; - buffer << file.rdbuf(); - file.close(); + static sol::table storageForScript(sol::this_state ts, const std::string& name) { + sol::state_view lua(ts); + sol::table storage = lua.create_table(); - std::string content = buffer.str(); - if (content.empty()) { - return sol::make_object(lua, sol::nil); + std::string scriptDir = "."; + if (lua["SCRIPT_DIR"].valid()) { + scriptDir = lua["SCRIPT_DIR"]; } - sol::table json = lua["json"]; - if (!json.valid() || !json["decode"].valid()) { - return sol::make_object(lua, sol::nil); + std::string filename = name; + if (filename.find('.') == std::string::npos) { + filename += ".json"; } + std::string path = scriptDir + "/" + filename; + storage["path"] = path; - try { - sol::function decode = json["decode"]; - sol::protected_function_result result = decode(content); - if (!result.valid()) { + storage["load"] = [path](sol::this_state ts2, sol::object) -> sol::object { + sol::state_view lua(ts2); + std::ifstream file(path); + if (!file.is_open()) { return sol::make_object(lua, sol::nil); } - sol::object decoded = result; - return sol::make_object(lua, decoded); - } catch (const sol::error&) { - return sol::make_object(lua, sol::nil); - } - }; - storage["save"] = [path](sol::this_state ts2, sol::object first, sol::object second) -> bool { - sol::state_view lua(ts2); - std::string content; + std::stringstream buffer; + buffer << file.rdbuf(); + file.close(); - sol::object data = (second.valid() && !second.is()) ? second : first; - if (!data.valid() || data.is()) { - return false; - } + std::string content = buffer.str(); + if (content.empty()) { + return sol::make_object(lua, sol::nil); + } - if (data.is()) { - content = data.as(); - } else { sol::table json = lua["json"]; - if (!json.valid() || !json["encode_pretty"].valid()) { - return false; + if (!json.valid() || !json["decode"].valid()) { + return sol::make_object(lua, sol::nil); } + try { - sol::function encode = json["encode_pretty"]; - sol::protected_function_result result = encode(data); + sol::function decode = json["decode"]; + sol::protected_function_result result = decode(content); if (!result.valid()) { - return false; + return sol::make_object(lua, sol::nil); } - content = result.get(); + sol::object decoded = result; + return sol::make_object(lua, decoded); } catch (const sol::error&) { - return false; + return sol::make_object(lua, sol::nil); } - } + }; - std::ofstream file(path, std::ios::trunc); - if (!file.is_open()) { - return false; - } - file << content; - file.close(); - return true; - }; + storage["save"] = [path](sol::this_state ts2, sol::object first, sol::object second) -> bool { + sol::state_view lua(ts2); + std::string content; - storage["clear"] = [path](sol::object) -> bool { - return std::remove(path.c_str()) == 0; - }; - - return storage; -} - -// ============================================================================ -// Register App API -// ============================================================================ - -void registerApp(sol::state& lua) { - // Create the 'app' table - sol::table app = lua.create_named_table("app"); - - // Version info - app["version"] = __RME_VERSION__; - app["apiVersion"] = 1; - - // Functions - app["alert"] = showAlert; - app["hasMap"] = hasMap; - app["refresh"] = refresh; - app["transaction"] = transaction; - app["setClipboard"] = setClipboard; - app["getDataDirectory"] = getDataDirectory; - app["addContextMenu"] = [](const std::string& label, sol::function callback) { - g_luaScripts.registerContextMenuItem(label, callback); - }; - app["selectRaw"] = [](int itemId) { - if (g_items.typeExists(itemId)) { - ItemType& it = g_items[itemId]; - if (it.raw_brush) { - g_gui.SelectBrush(it.raw_brush, TILESET_RAW); + sol::object data = (second.valid() && !second.is()) ? second : first; + if (!data.valid() || data.is()) { + return false; } - } - }; - app["setCameraPosition"] = [](int x, int y, int z) { - g_gui.SetScreenCenterPosition(Position(x, y, z)); - }; - app["storage"] = storageForScript; + if (data.is()) { + content = data.as(); + } else { + sol::table json = lua["json"]; + if (!json.valid() || !json["encode_pretty"].valid()) { + return false; + } + try { + sol::function encode = json["encode_pretty"]; + sol::protected_function_result result = encode(data); + if (!result.valid()) { + return false; + } + content = result.get(); + } catch (const sol::error&) { + return false; + } + } - // Yield to process pending UI events (prevents UI freeze during long operations) - app["yield"] = []() { - if (wxTheApp) { - wxTheApp->Yield(true); - } - }; + std::ofstream file(path, std::ios::trunc); + if (!file.is_open()) { + return false; + } + file << content; + file.close(); + return true; + }; - // Sleep for a given number of milliseconds (use sparingly, blocks the UI) - app["sleep"] = [](int milliseconds) { - if (milliseconds > 0 && milliseconds <= 10000) { // Max 10 seconds - std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); - } - }; + storage["clear"] = [path](sol::object) -> bool { + return std::remove(path.c_str()) == 0; + }; - // Get elapsed time in milliseconds since application start (high precision timer) - app["getTime"] = []() -> long { - return g_gui.gfx.getElapsedTime(); - }; + return storage; + } - // Event system: app.events:on("eventName", callback) / app.events:off(id) - sol::table events = lua.create_table(); - events["on"] = [](sol::this_state ts, sol::table self, const std::string& eventName, sol::function callback) -> int { - return g_luaScripts.addEventListener(eventName, callback); - }; - events["off"] = [](sol::this_state ts, sol::table self, int listenerId) -> bool { - return g_luaScripts.removeEventListener(listenerId); - }; - app["events"] = events; + // ============================================================================ + // Register App API + // ============================================================================ + + void registerApp(sol::state& lua) { + // Create the 'app' table + sol::table app = lua.create_named_table("app"); + + // Version info + app["version"] = __RME_VERSION__; + app["apiVersion"] = 1; + + // Functions + app["alert"] = showAlert; + app["hasMap"] = hasMap; + app["refresh"] = refresh; + app["transaction"] = transaction; + app["setClipboard"] = setClipboard; + app["getDataDirectory"] = getDataDirectory; + app["addContextMenu"] = [](const std::string& label, sol::function callback) { + g_luaScripts.registerContextMenuItem(label, callback); + }; + app["selectRaw"] = [](int itemId) { + if (g_items.typeExists(itemId)) { + ItemType& it = g_items[itemId]; + if (it.raw_brush) { + g_gui.SelectBrush(it.raw_brush, TILESET_RAW); + } + } + }; - // Properties via metatable (for dynamic properties like 'map' and 'selection') - sol::table mt = lua.create_table(); - mt[sol::meta_function::index] = [](sol::this_state ts, sol::table self, std::string key) -> sol::object { - sol::state_view lua(ts); + app["setCameraPosition"] = [](int x, int y, int z) { + g_gui.SetScreenCenterPosition(Position(x, y, z)); + }; + app["storage"] = storageForScript; - if (key == "map") { - Map* map = getMap(); - if (map) { - return sol::make_object(lua, map); + // Yield to process pending UI events (prevents UI freeze during long operations) + app["yield"] = []() { + if (wxTheApp) { + wxTheApp->Yield(true); } - return sol::nil; - } - else if (key == "selection") { - Selection* sel = getSelection(); - if (sel) { - return sol::make_object(lua, sel); + }; + + // Sleep for a given number of milliseconds (use sparingly, blocks the UI) + app["sleep"] = [](int milliseconds) { + if (milliseconds > 0 && milliseconds <= 10000) { // Max 10 seconds + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); } - return sol::nil; - } - else if (key == "borders") { - return getBorders(ts); - } - else if (key == "editor") { - Editor* editor = g_gui.GetCurrentEditor(); - if (editor) { - return sol::make_object(lua, editor); + }; + + // Get elapsed time in milliseconds since application start (high precision timer) + app["getTime"] = []() -> long { + return g_gui.gfx.getElapsedTime(); + }; + + // Event system: app.events:on("eventName", callback) / app.events:off(id) + sol::table events = lua.create_table(); + events["on"] = [](sol::this_state ts, sol::table self, const std::string& eventName, sol::function callback) -> int { + return g_luaScripts.addEventListener(eventName, callback); + }; + events["off"] = [](sol::this_state ts, sol::table self, int listenerId) -> bool { + return g_luaScripts.removeEventListener(listenerId); + }; + app["events"] = events; + + // Properties via metatable (for dynamic properties like 'map' and 'selection') + sol::table mt = lua.create_table(); + mt[sol::meta_function::index] = [](sol::this_state ts, sol::table self, std::string key) -> sol::object { + sol::state_view lua(ts); + + if (key == "map") { + Map* map = getMap(); + if (map) { + return sol::make_object(lua, map); + } + return sol::nil; + } else if (key == "selection") { + Selection* sel = getSelection(); + if (sel) { + return sol::make_object(lua, sel); + } + return sol::nil; + } else if (key == "borders") { + return getBorders(ts); + } else if (key == "editor") { + Editor* editor = g_gui.GetCurrentEditor(); + if (editor) { + return sol::make_object(lua, editor); + } + return sol::nil; + } else if (key == "brush") { + Brush* b = g_gui.GetCurrentBrush(); + if (b) { + return sol::make_object(lua, b); + } + return sol::nil; + } else if (key == "brushSize") { + return sol::make_object(lua, g_gui.GetBrushSize()); + } else if (key == "brushShape") { + return sol::make_object(lua, g_gui.GetBrushShape() == BRUSHSHAPE_CIRCLE ? "circle" : "square"); + } else if (key == "brushVariation") { + return sol::make_object(lua, g_gui.GetBrushVariation()); + } else if (key == "spawnTime") { + return sol::make_object(lua, g_gui.GetSpawnTime()); } return sol::nil; - } - else if (key == "brush") { - Brush* b = g_gui.GetCurrentBrush(); - if (b) return sol::make_object(lua, b); - return sol::nil; - } - else if (key == "brushSize") { - return sol::make_object(lua, g_gui.GetBrushSize()); - } - else if (key == "brushShape") { - return sol::make_object(lua, g_gui.GetBrushShape() == BRUSHSHAPE_CIRCLE ? "circle" : "square"); - } - else if (key == "brushVariation") { - return sol::make_object(lua, g_gui.GetBrushVariation()); - } - else if (key == "spawnTime") { - return sol::make_object(lua, g_gui.GetSpawnTime()); - } - return sol::nil; - }; + }; - mt[sol::meta_function::new_index] = [](sol::this_state ts, sol::table self, std::string key, sol::object value) { - if (key == "brushSize") { - if (value.is()) g_gui.SetBrushSize(value.as()); - } - else if (key == "brushShape") { - if (value.is()) { - std::string s = value.as(); - if (s == "circle") g_gui.SetBrushShape(BRUSHSHAPE_CIRCLE); - else if (s == "square") g_gui.SetBrushShape(BRUSHSHAPE_SQUARE); + mt[sol::meta_function::new_index] = [](sol::this_state ts, sol::table self, std::string key, sol::object value) { + if (key == "brushSize") { + if (value.is()) { + g_gui.SetBrushSize(value.as()); + } + } else if (key == "brushShape") { + if (value.is()) { + std::string s = value.as(); + if (s == "circle") { + g_gui.SetBrushShape(BRUSHSHAPE_CIRCLE); + } else if (s == "square") { + g_gui.SetBrushShape(BRUSHSHAPE_SQUARE); + } + } + } else if (key == "brushVariation") { + if (value.is()) { + g_gui.SetBrushVariation(value.as()); + } + } else if (key == "spawnTime") { + if (value.is()) { + g_gui.SetSpawnTime(value.as()); + } } - } - else if (key == "brushVariation") { - if (value.is()) g_gui.SetBrushVariation(value.as()); - } - else if (key == "spawnTime") { - if (value.is()) g_gui.SetSpawnTime(value.as()); - } - }; + }; - // Map overlay system - sol::table mapView = lua.create_table(); - mapView["addOverlay"] = [](sol::variadic_args va) -> bool { - if (va.size() == 2 && va[0].is() && va[1].is()) { - return g_luaScripts.addMapOverlay(va[0].as(), va[1].as()); - } - if (va.size() == 3 && va[1].is() && va[2].is()) { - return g_luaScripts.addMapOverlay(va[1].as(), va[2].as()); - } - return false; - }; - mapView["removeOverlay"] = [](sol::variadic_args va) -> bool { - if (va.size() == 1 && va[0].is()) { - return g_luaScripts.removeMapOverlay(va[0].as()); - } - if (va.size() == 2 && va[1].is()) { - return g_luaScripts.removeMapOverlay(va[1].as()); - } - return false; - }; - mapView["setEnabled"] = [](sol::variadic_args va) -> bool { - if (va.size() == 2 && va[0].is() && va[1].is()) { - return g_luaScripts.setMapOverlayEnabled(va[0].as(), va[1].as()); - } - if (va.size() == 3 && va[1].is() && va[2].is()) { - return g_luaScripts.setMapOverlayEnabled(va[1].as(), va[2].as()); - } - return false; - }; - mapView["registerShow"] = [](sol::variadic_args va) -> bool { - std::string label; - std::string overlayId; - bool enabled = true; - sol::function ontoggle; - - if (va.size() == 2 && va[0].is() && va[1].is()) { - label = va[0].as(); - overlayId = va[1].as(); - } else if (va.size() >= 3) { - if (va[0].is()) { - if (va[1].is()) label = va[1].as(); - if (va[2].is()) overlayId = va[2].as(); - } else if (va[0].is() && va[1].is()) { + // Map overlay system + sol::table mapView = lua.create_table(); + mapView["addOverlay"] = [](sol::variadic_args va) -> bool { + if (va.size() == 2 && va[0].is() && va[1].is()) { + return g_luaScripts.addMapOverlay(va[0].as(), va[1].as()); + } + if (va.size() == 3 && va[1].is() && va[2].is()) { + return g_luaScripts.addMapOverlay(va[1].as(), va[2].as()); + } + return false; + }; + mapView["removeOverlay"] = [](sol::variadic_args va) -> bool { + if (va.size() == 1 && va[0].is()) { + return g_luaScripts.removeMapOverlay(va[0].as()); + } + if (va.size() == 2 && va[1].is()) { + return g_luaScripts.removeMapOverlay(va[1].as()); + } + return false; + }; + mapView["setEnabled"] = [](sol::variadic_args va) -> bool { + if (va.size() == 2 && va[0].is() && va[1].is()) { + return g_luaScripts.setMapOverlayEnabled(va[0].as(), va[1].as()); + } + if (va.size() == 3 && va[1].is() && va[2].is()) { + return g_luaScripts.setMapOverlayEnabled(va[1].as(), va[2].as()); + } + return false; + }; + mapView["registerShow"] = [](sol::variadic_args va) -> bool { + std::string label; + std::string overlayId; + bool enabled = true; + sol::function ontoggle; + + if (va.size() == 2 && va[0].is() && va[1].is()) { label = va[0].as(); overlayId = va[1].as(); + } else if (va.size() >= 3) { + if (va[0].is()) { + if (va[1].is()) { + label = va[1].as(); + } + if (va[2].is()) { + overlayId = va[2].as(); + } + } else if (va[0].is() && va[1].is()) { + label = va[0].as(); + overlayId = va[1].as(); + } } - } - if (va.size() >= 3 && va[va.size() - 1].is()) { - sol::table opts = va[va.size() - 1].as(); - enabled = opts.get_or("enabled", enabled); - if (opts["ontoggle"].valid()) { - ontoggle = opts["ontoggle"]; + if (va.size() >= 3 && va[va.size() - 1].is()) { + sol::table opts = va[va.size() - 1].as(); + enabled = opts.get_or("enabled", enabled); + if (opts["ontoggle"].valid()) { + ontoggle = opts["ontoggle"]; + } + } else if (va.size() >= 3 && va[va.size() - 1].is()) { + enabled = va[va.size() - 1].as(); } - } else if (va.size() >= 3 && va[va.size() - 1].is()) { - enabled = va[va.size() - 1].as(); - } - if (label.empty() || overlayId.empty()) { - return false; - } + if (label.empty() || overlayId.empty()) { + return false; + } - return g_luaScripts.registerMapOverlayShow(label, overlayId, enabled, ontoggle); - }; - app["mapView"] = mapView; + return g_luaScripts.registerMapOverlayShow(label, overlayId, enabled, ontoggle); + }; + app["mapView"] = mapView; - app[sol::metatable_key] = mt; + app[sol::metatable_key] = mt; - // Register Editor usertype for undo/redo functionality - lua.new_usertype("Editor", - sol::no_constructor, + // Register Editor usertype for undo/redo functionality + lua.new_usertype( + "Editor", + sol::no_constructor, - // Undo/Redo functions - "undo", [](Editor* editor) { + // Undo/Redo functions + "undo", [](Editor* editor) { if (editor && editor->actionQueue && editor->actionQueue->canUndo()) { editor->actionQueue->undo(); g_gui.RefreshView(); - } - }, - "redo", [](Editor* editor) { + } }, + "redo", [](Editor* editor) { if (editor && editor->actionQueue && editor->actionQueue->canRedo()) { editor->actionQueue->redo(); g_gui.RefreshView(); - } - }, - "canUndo", [](Editor* editor) -> bool { - return editor && editor->actionQueue && editor->actionQueue->canUndo(); - }, - "canRedo", [](Editor* editor) -> bool { - return editor && editor->actionQueue && editor->actionQueue->canRedo(); - }, - - // History info - "historyIndex", sol::property([](Editor* editor) -> int { - if (editor && editor->actionQueue) { - return (int)editor->actionQueue->getCurrentIndex(); - } - return 0; - }), - "historySize", sol::property([](Editor* editor) -> int { - if (editor && editor->actionQueue) { - return (int)editor->actionQueue->getSize(); - } - return 0; - }), + } }, + "canUndo", [](Editor* editor) -> bool { return editor && editor->actionQueue && editor->actionQueue->canUndo(); }, + "canRedo", [](Editor* editor) -> bool { return editor && editor->actionQueue && editor->actionQueue->canRedo(); }, + + // History info + "historyIndex", sol::property([](Editor* editor) -> int { + if (editor && editor->actionQueue) { + return (int)editor->actionQueue->getCurrentIndex(); + } + return 0; + }), + "historySize", sol::property([](Editor* editor) -> int { + if (editor && editor->actionQueue) { + return (int)editor->actionQueue->getSize(); + } + return 0; + }), - // Get history as a table - "getHistory", [](Editor* editor, sol::this_state ts) -> sol::table { + // Get history as a table + "getHistory", [](Editor* editor, sol::this_state ts) -> sol::table { sol::state_view lua(ts); sol::table history = lua.create_table(); @@ -746,17 +772,18 @@ void registerApp(sol::state& lua) { history[i + 1] = input; } } - return history; - }, + return history; }, - // Navigate to specific history index - "goToHistory", [](Editor* editor, int targetIndex) { - if (!editor || !editor->actionQueue) return; + // Navigate to specific history index + "goToHistory", [](Editor* editor, int targetIndex) { + if (!editor || !editor->actionQueue){ return; +} int current = (int)editor->actionQueue->getCurrentIndex(); int target = targetIndex; // Already 1-based from Lua - if (target < 0) target = 0; + if (target < 0){ target = 0; +} if (target > (int)editor->actionQueue->getSize()) { target = (int)editor->actionQueue->getSize(); } @@ -776,9 +803,8 @@ void registerApp(sol::state& lua) { } } } - g_gui.RefreshView(); - } - ); -} + g_gui.RefreshView(); } + ); + } } // namespace LuaAPI diff --git a/source/lua/lua_api_brush.cpp b/source/lua/lua_api_brush.cpp index ed4787dd3..e7b240841 100644 --- a/source/lua/lua_api_brush.cpp +++ b/source/lua/lua_api_brush.cpp @@ -22,136 +22,156 @@ namespace LuaAPI { -// Helper to get brush type as string -static std::string getBrushTypeName(Brush* brush) { - if (!brush) return "unknown"; - - if (brush->isRaw()) return "raw"; - if (brush->isDoodad()) return "doodad"; - if (brush->isGround()) return "ground"; - if (brush->isWall()) return "wall"; - if (brush->isWallDecoration()) return "wall_decoration"; - if (brush->isTable()) return "table"; - if (brush->isCarpet()) return "carpet"; - if (brush->isDoor()) return "door"; - if (brush->isOptionalBorder()) return "optional_border"; - if (brush->isCreature()) return "creature"; - if (brush->isSpawn()) return "spawn"; - if (brush->isHouse()) return "house"; - if (brush->isHouseExit()) return "house_exit"; - if (brush->isWaypoint()) return "waypoint"; - if (brush->isFlag()) return "flag"; - if (brush->isEraser()) return "eraser"; - if (brush->isTerrain()) return "terrain"; - - return "unknown"; -} - -void registerBrush(sol::state& lua) { - // Register Brush usertype (read-only) - lua.new_usertype("Brush", - sol::no_constructor, - - // Properties (read-only) - "id", sol::property([](Brush* b) -> uint32_t { - return b ? b->getID() : 0; - }), - "name", sol::property([](Brush* b) -> std::string { - return b ? b->getName() : ""; - }), - "lookId", sol::property([](Brush* b) -> int { - return b ? b->getLookID() : 0; - }), - "type", sol::property([](Brush* b) -> std::string { - return getBrushTypeName(b); - }), - - // Capabilities (read-only) - "canDrag", sol::property([](Brush* b) -> bool { - return b && b->canDrag(); - }), - "canSmear", sol::property([](Brush* b) -> bool { - return b && b->canSmear(); - }), - "needsBorders", sol::property([](Brush* b) -> bool { - return b && b->needBorders(); - }), - "oneSizeFitsAll", sol::property([](Brush* b) -> bool { - return b && b->oneSizeFitsAll(); - }), - "maxVariation", sol::property([](Brush* b) -> int { - return b ? b->getMaxVariation() : 0; - }), - "visibleInPalette", sol::property([](Brush* b) -> bool { - return b && b->visibleInPalette(); - }), - - // Type checks - "isRaw", sol::property([](Brush* b) { return b && b->isRaw(); }), - "isDoodad", sol::property([](Brush* b) { return b && b->isDoodad(); }), - "isTerrain", sol::property([](Brush* b) { return b && b->isTerrain(); }), - "isGround", sol::property([](Brush* b) { return b && b->isGround(); }), - "isWall", sol::property([](Brush* b) { return b && b->isWall(); }), - "isTable", sol::property([](Brush* b) { return b && b->isTable(); }), - "isCarpet", sol::property([](Brush* b) { return b && b->isCarpet(); }), - "isDoor", sol::property([](Brush* b) { return b && b->isDoor(); }), - "isCreature", sol::property([](Brush* b) { return b && b->isCreature(); }), - "isSpawn", sol::property([](Brush* b) { return b && b->isSpawn(); }), - "isHouse", sol::property([](Brush* b) { return b && b->isHouse(); }), - "isEraser", sol::property([](Brush* b) { return b && b->isEraser(); }), - - // String representation - sol::meta_function::to_string, [](Brush* b) -> std::string { - if (!b) return "Brush(invalid)"; - return "Brush(\"" + b->getName() + "\", type=" + getBrushTypeName(b) + ")"; - } - ); - - // Register Brushes namespace for brush lookup - sol::table brushes = lua.create_named_table("Brushes"); - - // Get brush by name - brushes["get"] = [](const std::string& name) -> Brush* { - return g_brushes.getBrush(name); - }; - - // Get all brush names (for iteration) - brushes["getNames"] = [](sol::this_state ts) -> sol::table { - sol::state_view lua(ts); - sol::table names = lua.create_table(); - - int idx = 1; - for (const auto& pair : g_brushes.getMap()) { - names[idx++] = pair.first; - } - return names; - }; - - // Extend the existing 'app' table with brush-related functions - sol::table app = lua["app"]; - - // Current brush - app.set_function("getBrush", []() -> Brush* { - return g_gui.GetCurrentBrush(); - }); - - // Set brush by name - app.set_function("setBrush", [](const std::string& name) -> bool { - Brush* brush = g_brushes.getBrush(name); - if (brush) { - g_gui.SelectBrush(brush); - return true; - } - return false; - }); - - - - // Register BrushShape enum - lua.new_enum("BrushShape", - "SQUARE", BRUSHSHAPE_SQUARE, - "CIRCLE", BRUSHSHAPE_CIRCLE - ); -} + // Helper to get brush type as string + static std::string getBrushTypeName(Brush* brush) { + if (!brush) { + return "unknown"; + } + + if (brush->isRaw()) { + return "raw"; + } + if (brush->isDoodad()) { + return "doodad"; + } + if (brush->isGround()) { + return "ground"; + } + if (brush->isWall()) { + return "wall"; + } + if (brush->isWallDecoration()) { + return "wall_decoration"; + } + if (brush->isTable()) { + return "table"; + } + if (brush->isCarpet()) { + return "carpet"; + } + if (brush->isDoor()) { + return "door"; + } + if (brush->isOptionalBorder()) { + return "optional_border"; + } + if (brush->isCreature()) { + return "creature"; + } + if (brush->isSpawn()) { + return "spawn"; + } + if (brush->isHouse()) { + return "house"; + } + if (brush->isHouseExit()) { + return "house_exit"; + } + if (brush->isWaypoint()) { + return "waypoint"; + } + if (brush->isFlag()) { + return "flag"; + } + if (brush->isEraser()) { + return "eraser"; + } + if (brush->isTerrain()) { + return "terrain"; + } + + return "unknown"; + } + + void registerBrush(sol::state& lua) { + // Register Brush usertype (read-only) + lua.new_usertype("Brush", sol::no_constructor, + + // Properties (read-only) + "id", sol::property([](Brush* b) -> uint32_t { + return b ? b->getID() : 0; + }), + "name", sol::property([](Brush* b) -> std::string { + return b ? b->getName() : ""; + }), + "lookId", sol::property([](Brush* b) -> int { + return b ? b->getLookID() : 0; + }), + "type", sol::property([](Brush* b) -> std::string { + return getBrushTypeName(b); + }), + + // Capabilities (read-only) + "canDrag", sol::property([](Brush* b) -> bool { + return b && b->canDrag(); + }), + "canSmear", sol::property([](Brush* b) -> bool { + return b && b->canSmear(); + }), + "needsBorders", sol::property([](Brush* b) -> bool { + return b && b->needBorders(); + }), + "oneSizeFitsAll", sol::property([](Brush* b) -> bool { + return b && b->oneSizeFitsAll(); + }), + "maxVariation", sol::property([](Brush* b) -> int { + return b ? b->getMaxVariation() : 0; + }), + "visibleInPalette", sol::property([](Brush* b) -> bool { + return b && b->visibleInPalette(); + }), + + // Type checks + "isRaw", sol::property([](Brush* b) { return b && b->isRaw(); }), "isDoodad", sol::property([](Brush* b) { return b && b->isDoodad(); }), "isTerrain", sol::property([](Brush* b) { return b && b->isTerrain(); }), "isGround", sol::property([](Brush* b) { return b && b->isGround(); }), "isWall", sol::property([](Brush* b) { return b && b->isWall(); }), "isTable", sol::property([](Brush* b) { return b && b->isTable(); }), "isCarpet", sol::property([](Brush* b) { return b && b->isCarpet(); }), "isDoor", sol::property([](Brush* b) { return b && b->isDoor(); }), "isCreature", sol::property([](Brush* b) { return b && b->isCreature(); }), "isSpawn", sol::property([](Brush* b) { return b && b->isSpawn(); }), "isHouse", sol::property([](Brush* b) { return b && b->isHouse(); }), "isEraser", sol::property([](Brush* b) { return b && b->isEraser(); }), + + // String representation + sol::meta_function::to_string, [](Brush* b) -> std::string { + if (!b) { + return "Brush(invalid)"; + } + return "Brush(\"" + b->getName() + "\", type=" + getBrushTypeName(b) + ")"; + }); + + // Register Brushes namespace for brush lookup + sol::table brushes = lua.create_named_table("Brushes"); + + // Get brush by name + brushes["get"] = [](const std::string& name) -> Brush* { + return g_brushes.getBrush(name); + }; + + // Get all brush names (for iteration) + brushes["getNames"] = [](sol::this_state ts) -> sol::table { + sol::state_view lua(ts); + sol::table names = lua.create_table(); + + int idx = 1; + for (const auto& pair : g_brushes.getMap()) { + names[idx++] = pair.first; + } + return names; + }; + + // Extend the existing 'app' table with brush-related functions + sol::table app = lua["app"]; + + // Current brush + app.set_function("getBrush", []() -> Brush* { + return g_gui.GetCurrentBrush(); + }); + + // Set brush by name + app.set_function("setBrush", [](const std::string& name) -> bool { + Brush* brush = g_brushes.getBrush(name); + if (brush) { + g_gui.SelectBrush(brush); + return true; + } + return false; + }); + + // Register BrushShape enum + lua.new_enum("BrushShape", "SQUARE", BRUSHSHAPE_SQUARE, "CIRCLE", BRUSHSHAPE_CIRCLE); + } } // namespace LuaAPI diff --git a/source/lua/lua_api_color.cpp b/source/lua/lua_api_color.cpp index 3899a2bfd..d48a7e298 100644 --- a/source/lua/lua_api_color.cpp +++ b/source/lua/lua_api_color.cpp @@ -20,72 +20,61 @@ namespace LuaAPI { -void registerColor(sol::state& lua) { - // Register LuaColor as "Color" usertype - lua.new_usertype("Color", - // Constructors - sol::constructors< - LuaColor(), // Color() - LuaColor(uint8_t, uint8_t, uint8_t), // Color(r, g, b) - LuaColor(uint8_t, uint8_t, uint8_t, uint8_t) // Color(r, g, b, a) - >(), + void registerColor(sol::state& lua) { + // Register LuaColor as "Color" usertype + lua.new_usertype("Color", + // Constructors + sol::constructors(), - // Alternative constructor from table: Color{red=255, green=0, blue=0} - sol::call_constructor, sol::factories( - // Default constructor - []() { return LuaColor(); }, - // RGB constructor - [](uint8_t r, uint8_t g, uint8_t b) { return LuaColor(r, g, b); }, - // RGBA constructor - [](uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return LuaColor(r, g, b, a); }, - // Table constructor - [](sol::table t) { - LuaColor c; - c.red = t.get_or("red", 0); - c.green = t.get_or("green", 0); - c.blue = t.get_or("blue", 0); - c.alpha = t.get_or("alpha", 255); - return c; - } - ), + // Alternative constructor from table: Color{red=255, green=0, blue=0} + sol::call_constructor, sol::factories( + // Default constructor + []() { return LuaColor(); }, + // RGB constructor + [](uint8_t r, uint8_t g, uint8_t b) { return LuaColor(r, g, b); }, + // RGBA constructor + [](uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return LuaColor(r, g, b, a); }, + // Table constructor + [](sol::table t) { + LuaColor c; + c.red = t.get_or("red", 0); + c.green = t.get_or("green", 0); + c.blue = t.get_or("blue", 0); + c.alpha = t.get_or("alpha", 255); + return c; + } + ), - // Properties (read/write) - "red", &LuaColor::red, - "green", &LuaColor::green, - "blue", &LuaColor::blue, - "alpha", &LuaColor::alpha, + // Properties (read/write) + "red", &LuaColor::red, "green", &LuaColor::green, "blue", &LuaColor::blue, "alpha", &LuaColor::alpha, - // Shorthand aliases - "r", &LuaColor::red, - "g", &LuaColor::green, - "b", &LuaColor::blue, - "a", &LuaColor::alpha, + // Shorthand aliases + "r", &LuaColor::red, "g", &LuaColor::green, "b", &LuaColor::blue, "a", &LuaColor::alpha, - // Equality comparison - sol::meta_function::equal_to, &LuaColor::operator==, + // Equality comparison + sol::meta_function::equal_to, &LuaColor::operator==, - // String representation - sol::meta_function::to_string, [](const LuaColor& c) { - return "Color(" + std::to_string(c.red) + ", " + - std::to_string(c.green) + ", " + - std::to_string(c.blue) + - (c.alpha != 255 ? ", " + std::to_string(c.alpha) : "") + ")"; - } - ); + // String representation + sol::meta_function::to_string, [](const LuaColor& c) { + return "Color(" + std::to_string(c.red) + ", " + std::to_string(c.green) + ", " + std::to_string(c.blue) + (c.alpha != 255 ? ", " + std::to_string(c.alpha) : "") + ")"; + }); - // Predefined colors as constants - sol::table colorConstants = lua.create_named_table("Colors"); - colorConstants["BLACK"] = LuaColor(0, 0, 0); - colorConstants["WHITE"] = LuaColor(255, 255, 255); - colorConstants["RED"] = LuaColor(255, 0, 0); - colorConstants["GREEN"] = LuaColor(0, 255, 0); - colorConstants["BLUE"] = LuaColor(0, 0, 255); - colorConstants["YELLOW"] = LuaColor(255, 255, 0); - colorConstants["CYAN"] = LuaColor(0, 255, 255); - colorConstants["MAGENTA"] = LuaColor(255, 0, 255); - colorConstants["ORANGE"] = LuaColor(255, 165, 0); - colorConstants["GRAY"] = LuaColor(128, 128, 128); - colorConstants["TRANSPARENT"] = LuaColor(0, 0, 0, 0); -} + // Predefined colors as constants + sol::table colorConstants = lua.create_named_table("Colors"); + colorConstants["BLACK"] = LuaColor(0, 0, 0); + colorConstants["WHITE"] = LuaColor(255, 255, 255); + colorConstants["RED"] = LuaColor(255, 0, 0); + colorConstants["GREEN"] = LuaColor(0, 255, 0); + colorConstants["BLUE"] = LuaColor(0, 0, 255); + colorConstants["YELLOW"] = LuaColor(255, 255, 0); + colorConstants["CYAN"] = LuaColor(0, 255, 255); + colorConstants["MAGENTA"] = LuaColor(255, 0, 255); + colorConstants["ORANGE"] = LuaColor(255, 165, 0); + colorConstants["GRAY"] = LuaColor(128, 128, 128); + colorConstants["TRANSPARENT"] = LuaColor(0, 0, 0, 0); + } } // namespace LuaAPI diff --git a/source/lua/lua_api_color.h b/source/lua/lua_api_color.h index 36fe8e67a..efc743089 100644 --- a/source/lua/lua_api_color.h +++ b/source/lua/lua_api_color.h @@ -23,36 +23,36 @@ namespace LuaAPI { -// Simple RGBA color class for Lua scripting -struct LuaColor { - uint8_t red; - uint8_t green; - uint8_t blue; - uint8_t alpha; - - LuaColor() : red(0), green(0), blue(0), alpha(255) {} - LuaColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) - : red(r), green(g), blue(b), alpha(a) {} - - // Convert to wxColour for use with wxWidgets - wxColour toWxColour() const { - return wxColour(red, green, blue, alpha); - } - - // Create from wxColour - static LuaColor fromWxColour(const wxColour& c) { - return LuaColor(c.Red(), c.Green(), c.Blue(), c.Alpha()); - } - - // Equality comparison - bool operator==(const LuaColor& other) const { - return red == other.red && green == other.green && - blue == other.blue && alpha == other.alpha; - } -}; - -// Register the Color usertype with Lua -void registerColor(sol::state& lua); + // Simple RGBA color class for Lua scripting + struct LuaColor { + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t alpha; + + LuaColor() : + red(0), green(0), blue(0), alpha(255) { } + LuaColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) : + red(r), green(g), blue(b), alpha(a) { } + + // Convert to wxColour for use with wxWidgets + wxColour toWxColour() const { + return wxColour(red, green, blue, alpha); + } + + // Create from wxColour + static LuaColor fromWxColour(const wxColour& c) { + return LuaColor(c.Red(), c.Green(), c.Blue(), c.Alpha()); + } + + // Equality comparison + bool operator==(const LuaColor& other) const { + return red == other.red && green == other.green && blue == other.blue && alpha == other.alpha; + } + }; + + // Register the Color usertype with Lua + void registerColor(sol::state& lua); } // namespace LuaAPI diff --git a/source/lua/lua_api_creature.cpp b/source/lua/lua_api_creature.cpp index f48410abf..a1220aaca 100644 --- a/source/lua/lua_api_creature.cpp +++ b/source/lua/lua_api_creature.cpp @@ -28,53 +28,44 @@ namespace LuaAPI { -void registerCreature(sol::state& lua) { - // Register Direction enum - lua.new_enum("Direction", - "NORTH", NORTH, - "EAST", EAST, - "SOUTH", SOUTH, - "WEST", WEST - ); + void registerCreature(sol::state& lua) { + // Register Direction enum + lua.new_enum("Direction", "NORTH", NORTH, "EAST", EAST, "SOUTH", SOUTH, "WEST", WEST); - // Register Creature usertype (expanded from basic in lua_api_tile.cpp) - lua.new_usertype("Creature", - sol::no_constructor, + // Register Creature usertype (expanded from basic in lua_api_tile.cpp) + lua.new_usertype( + "Creature", + sol::no_constructor, - // Properties (read-only) - "name", sol::property([](Creature* c) -> std::string { - return c ? c->getName() : ""; - }), - "isNpc", sol::property([](Creature* c) -> bool { - return c && c->isNpc(); - }), + // Properties (read-only) + "name", sol::property([](Creature* c) -> std::string { + return c ? c->getName() : ""; + }), + "isNpc", sol::property([](Creature* c) -> bool { + return c && c->isNpc(); + }), - // Properties (read/write) - "spawnTime", sol::property( - [](Creature* c) -> int { return c ? c->getSpawnTime() : 0; }, - [](Creature* c, int time) { + // Properties (read/write) + "spawnTime", sol::property([](Creature* c) -> int { return c ? c->getSpawnTime() : 0; }, [](Creature* c, int time) { if (c) { c->setSpawnTime(time); - } - } - ), - "direction", sol::property( - [](Creature* c) -> int { return c ? static_cast(c->getDirection()) : 0; }, - [](Creature* c, int dir) { + } }), + "direction", sol::property([](Creature* c) -> int { return c ? static_cast(c->getDirection()) : 0; }, [](Creature* c, int dir) { if (c && dir >= DIRECTION_FIRST && dir <= DIRECTION_LAST) { c->setDirection(static_cast(dir)); - } - } - ), + } }), - // Selection - "isSelected", sol::property([](Creature* c) { return c && c->isSelected(); }), - "select", [](Creature* c) { if (c) c->select(); }, - "deselect", [](Creature* c) { if (c) c->deselect(); }, + // Selection + "isSelected", sol::property([](Creature* c) { return c && c->isSelected(); }), + "select", [](Creature* c) { if (c){ c->select(); +} }, + "deselect", [](Creature* c) { if (c){ c->deselect(); +} }, - // String representation - sol::meta_function::to_string, [](Creature* c) -> std::string { - if (!c) return "Creature(invalid)"; + // String representation + sol::meta_function::to_string, [](Creature* c) -> std::string { + if (!c){ return "Creature(invalid)"; +} std::string dir; switch (c->getDirection()) { case NORTH: dir = "N"; break; @@ -84,55 +75,49 @@ void registerCreature(sol::state& lua) { default: dir = "?"; break; } return "Creature(\"" + c->getName() + "\", dir=" + dir + - ", spawn=" + std::to_string(c->getSpawnTime()) + "s)"; - } - ); + ", spawn=" + std::to_string(c->getSpawnTime()) + "s)"; } + ); - // Register Spawn usertype (expanded from basic in lua_api_tile.cpp) - lua.new_usertype("Spawn", - sol::no_constructor, + // Register Spawn usertype (expanded from basic in lua_api_tile.cpp) + lua.new_usertype( + "Spawn", + sol::no_constructor, - // Properties (read/write) - "size", sol::property( - [](Spawn* s) -> int { return s ? s->getSize() : 0; }, - [](Spawn* s, int size) { + // Properties (read/write) + "size", sol::property([](Spawn* s) -> int { return s ? s->getSize() : 0; }, [](Spawn* s, int size) { if (s && size > 0 && size < 100) { s->setSize(size); - } - } - ), - // Alias for size - "radius", sol::property( - [](Spawn* s) -> int { return s ? s->getSize() : 0; }, - [](Spawn* s, int size) { + } }), + // Alias for size + "radius", sol::property([](Spawn* s) -> int { return s ? s->getSize() : 0; }, [](Spawn* s, int size) { if (s && size > 0 && size < 100) { s->setSize(size); - } - } - ), + } }), - // Selection - "isSelected", sol::property([](Spawn* s) { return s && s->isSelected(); }), - "select", [](Spawn* s) { if (s) s->select(); }, - "deselect", [](Spawn* s) { if (s) s->deselect(); }, + // Selection + "isSelected", sol::property([](Spawn* s) { return s && s->isSelected(); }), + "select", [](Spawn* s) { if (s){ s->select(); +} }, + "deselect", [](Spawn* s) { if (s){ s->deselect(); +} }, - // String representation - sol::meta_function::to_string, [](Spawn* s) -> std::string { - if (!s) return "Spawn(invalid)"; - return "Spawn(radius=" + std::to_string(s->getSize()) + ")"; - } - ); + // String representation + sol::meta_function::to_string, [](Spawn* s) -> std::string { + if (!s){ return "Spawn(invalid)"; +} + return "Spawn(radius=" + std::to_string(s->getSize()) + ")"; } + ); - // Helper function to check if a creature type exists - lua["creatureExists"] = [](const std::string& name) -> bool { - return g_creatures[name] != nullptr; - }; + // Helper function to check if a creature type exists + lua["creatureExists"] = [](const std::string& name) -> bool { + return g_creatures[name] != nullptr; + }; - // Helper function to check if a creature is an NPC by name - lua["isNpcType"] = [](const std::string& name) -> bool { - CreatureType* type = g_creatures[name]; - return type && type->isNpc; - }; -} + // Helper function to check if a creature is an NPC by name + lua["isNpcType"] = [](const std::string& name) -> bool { + CreatureType* type = g_creatures[name]; + return type && type->isNpc; + }; + } } // namespace LuaAPI diff --git a/source/lua/lua_api_geo.cpp b/source/lua/lua_api_geo.cpp index 0cd30b19c..b1e7b5164 100644 --- a/source/lua/lua_api_geo.cpp +++ b/source/lua/lua_api_geo.cpp @@ -28,905 +28,967 @@ namespace LuaAPI { -void registerGeo(sol::state& lua) { - sol::table geoTable = lua.create_table(); + void registerGeo(sol::state& lua) { + sol::table geoTable = lua.create_table(); - // ======================================== - // BRESENHAM'S LINE ALGORITHM - // ======================================== + // ======================================== + // BRESENHAM'S LINE ALGORITHM + // ======================================== - // geo.bresenhamLine(x1, y1, x2, y2) -> table of points - // Returns all points on a line between two points - geoTable.set_function("bresenhamLine", [](int x1, int y1, int x2, int y2, sol::this_state s) -> sol::table { - sol::state_view lua(s); - sol::table result = lua.create_table(); + // geo.bresenhamLine(x1, y1, x2, y2) -> table of points + // Returns all points on a line between two points + geoTable.set_function("bresenhamLine", [](int x1, int y1, int x2, int y2, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); - int dx = std::abs(x2 - x1); - int dy = std::abs(y2 - y1); - int sx = (x1 < x2) ? 1 : -1; - int sy = (y1 < y2) ? 1 : -1; - int err = dx - dy; + int dx = std::abs(x2 - x1); + int dy = std::abs(y2 - y1); + int sx = (x1 < x2) ? 1 : -1; + int sy = (y1 < y2) ? 1 : -1; + int err = dx - dy; - int index = 1; - int x = x1, y = y1; + int index = 1; + int x = x1, y = y1; - while (true) { - sol::table point = lua.create_table(); - point["x"] = x; - point["y"] = y; - result[index++] = point; + while (true) { + sol::table point = lua.create_table(); + point["x"] = x; + point["y"] = y; + result[index++] = point; - if (x == x2 && y == y2) break; + if (x == x2 && y == y2) { + break; + } - int e2 = 2 * err; - if (e2 > -dy) { - err -= dy; - x += sx; - } - if (e2 < dx) { - err += dx; - y += sy; + int e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x += sx; + } + if (e2 < dx) { + err += dx; + y += sy; + } } - } - return result; - }); + return result; + }); - // geo.bresenhamLine3d(x1, y1, z1, x2, y2, z2) -> table of 3D points - geoTable.set_function("bresenhamLine3d", [](int x1, int y1, int z1, int x2, int y2, int z2, sol::this_state s) -> sol::table { - sol::state_view lua(s); - sol::table result = lua.create_table(); + // geo.bresenhamLine3d(x1, y1, z1, x2, y2, z2) -> table of 3D points + geoTable.set_function("bresenhamLine3d", [](int x1, int y1, int z1, int x2, int y2, int z2, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); - int dx = std::abs(x2 - x1); - int dy = std::abs(y2 - y1); - int dz = std::abs(z2 - z1); + int dx = std::abs(x2 - x1); + int dy = std::abs(y2 - y1); + int dz = std::abs(z2 - z1); - int sx = (x1 < x2) ? 1 : -1; - int sy = (y1 < y2) ? 1 : -1; - int sz = (z1 < z2) ? 1 : -1; + int sx = (x1 < x2) ? 1 : -1; + int sy = (y1 < y2) ? 1 : -1; + int sz = (z1 < z2) ? 1 : -1; - // Driving axis is the one with greatest delta - int dm = std::max({dx, dy, dz}); + // Driving axis is the one with greatest delta + int dm = std::max({ dx, dy, dz }); - int i = dm; - int x = x1, y = y1, z = z1; + int i = dm; + int x = x1, y = y1, z = z1; - int xErr = dm / 2; - int yErr = dm / 2; - int zErr = dm / 2; + int xErr = dm / 2; + int yErr = dm / 2; + int zErr = dm / 2; - int index = 1; + int index = 1; - for (int step = 0; step <= dm; ++step) { - sol::table point = lua.create_table(); - point["x"] = x; - point["y"] = y; - point["z"] = z; - result[index++] = point; + for (int step = 0; step <= dm; ++step) { + sol::table point = lua.create_table(); + point["x"] = x; + point["y"] = y; + point["z"] = z; + result[index++] = point; - xErr -= dx; - yErr -= dy; - zErr -= dz; + xErr -= dx; + yErr -= dy; + zErr -= dz; - if (xErr < 0) { - xErr += dm; - x += sx; + if (xErr < 0) { + xErr += dm; + x += sx; + } + if (yErr < 0) { + yErr += dm; + y += sy; + } + if (zErr < 0) { + zErr += dm; + z += sz; + } } - if (yErr < 0) { - yErr += dm; - y += sy; + + return result; + }); + + // ======================================== + // BEZIER CURVES + // ======================================== + + // geo.bezierCurve(points, steps) -> table of points + // Quadratic/Cubic Bezier curve through control points + geoTable.set_function("bezierCurve", [](sol::table controlPoints, sol::optional steps, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + int numSteps = steps.value_or(20); + + // Parse control points + std::vector> points; + for (auto& kv : controlPoints) { + if (kv.second.get_type() == sol::type::table) { + sol::table pt = kv.second; + float x = pt.get_or("x", pt.get_or(1, 0.0f)); + float y = pt.get_or("y", pt.get_or(2, 0.0f)); + points.push_back({ x, y }); + } } - if (zErr < 0) { - zErr += dm; - z += sz; + + if (points.size() < 2) { + return result; } - } - return result; - }); + int index = 1; - // ======================================== - // BEZIER CURVES - // ======================================== + // De Casteljau's algorithm for any number of control points + auto deCasteljau = [&](float t) -> std::pair { + std::vector> temp = points; - // geo.bezierCurve(points, steps) -> table of points - // Quadratic/Cubic Bezier curve through control points - geoTable.set_function("bezierCurve", [](sol::table controlPoints, sol::optional steps, sol::this_state s) -> sol::table { - sol::state_view lua(s); - sol::table result = lua.create_table(); + while (temp.size() > 1) { + std::vector> newTemp; + for (size_t i = 0; i < temp.size() - 1; ++i) { + float x = temp[i].first + t * (temp[i + 1].first - temp[i].first); + float y = temp[i].second + t * (temp[i + 1].second - temp[i].second); + newTemp.push_back({ x, y }); + } + temp = newTemp; + } - int numSteps = steps.value_or(20); + return temp[0]; + }; - // Parse control points - std::vector> points; - for (auto& kv : controlPoints) { - if (kv.second.get_type() == sol::type::table) { - sol::table pt = kv.second; - float x = pt.get_or("x", pt.get_or(1, 0.0f)); - float y = pt.get_or("y", pt.get_or(2, 0.0f)); - points.push_back({x, y}); + for (int i = 0; i <= numSteps; ++i) { + float t = (float)i / (float)numSteps; + auto [x, y] = deCasteljau(t); + + sol::table point = lua.create_table(); + point["x"] = std::round(x); + point["y"] = std::round(y); + result[index++] = point; } - } - if (points.size() < 2) { return result; - } - - int index = 1; - - // De Casteljau's algorithm for any number of control points - auto deCasteljau = [&](float t) -> std::pair { - std::vector> temp = points; - - while (temp.size() > 1) { - std::vector> newTemp; - for (size_t i = 0; i < temp.size() - 1; ++i) { - float x = temp[i].first + t * (temp[i + 1].first - temp[i].first); - float y = temp[i].second + t * (temp[i + 1].second - temp[i].second); - newTemp.push_back({x, y}); + }); + + // geo.bezierCurve3d(points, steps) -> table of 3D points + geoTable.set_function("bezierCurve3d", [](sol::table controlPoints, sol::optional steps, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + int numSteps = steps.value_or(20); + + // Parse control points + std::vector> points; + for (auto& kv : controlPoints) { + if (kv.second.get_type() == sol::type::table) { + sol::table pt = kv.second; + float x = pt.get_or("x", 0.0f); + float y = pt.get_or("y", 0.0f); + float z = pt.get_or("z", 0.0f); + points.push_back({ x, y, z }); } - temp = newTemp; } - return temp[0]; - }; + if (points.size() < 2) { + return result; + } - for (int i = 0; i <= numSteps; ++i) { - float t = (float)i / (float)numSteps; - auto [x, y] = deCasteljau(t); + int index = 1; - sol::table point = lua.create_table(); - point["x"] = std::round(x); - point["y"] = std::round(y); - result[index++] = point; - } + auto deCasteljau3d = [&](float t) -> std::tuple { + std::vector> temp = points; - return result; - }); + while (temp.size() > 1) { + std::vector> newTemp; + for (size_t i = 0; i < temp.size() - 1; ++i) { + float x = std::get<0>(temp[i]) + t * (std::get<0>(temp[i + 1]) - std::get<0>(temp[i])); + float y = std::get<1>(temp[i]) + t * (std::get<1>(temp[i + 1]) - std::get<1>(temp[i])); + float z = std::get<2>(temp[i]) + t * (std::get<2>(temp[i + 1]) - std::get<2>(temp[i])); + newTemp.push_back({ x, y, z }); + } + temp = newTemp; + } - // geo.bezierCurve3d(points, steps) -> table of 3D points - geoTable.set_function("bezierCurve3d", [](sol::table controlPoints, sol::optional steps, sol::this_state s) -> sol::table { - sol::state_view lua(s); - sol::table result = lua.create_table(); + return temp[0]; + }; - int numSteps = steps.value_or(20); + for (int i = 0; i <= numSteps; ++i) { + float t = (float)i / (float)numSteps; + auto [x, y, z] = deCasteljau3d(t); - // Parse control points - std::vector> points; - for (auto& kv : controlPoints) { - if (kv.second.get_type() == sol::type::table) { - sol::table pt = kv.second; - float x = pt.get_or("x", 0.0f); - float y = pt.get_or("y", 0.0f); - float z = pt.get_or("z", 0.0f); - points.push_back({x, y, z}); + sol::table point = lua.create_table(); + point["x"] = std::round(x); + point["y"] = std::round(y); + point["z"] = std::round(z); + result[index++] = point; } - } - if (points.size() < 2) { return result; - } - - int index = 1; - - auto deCasteljau3d = [&](float t) -> std::tuple { - std::vector> temp = points; - - while (temp.size() > 1) { - std::vector> newTemp; - for (size_t i = 0; i < temp.size() - 1; ++i) { - float x = std::get<0>(temp[i]) + t * (std::get<0>(temp[i + 1]) - std::get<0>(temp[i])); - float y = std::get<1>(temp[i]) + t * (std::get<1>(temp[i + 1]) - std::get<1>(temp[i])); - float z = std::get<2>(temp[i]) + t * (std::get<2>(temp[i + 1]) - std::get<2>(temp[i])); - newTemp.push_back({x, y, z}); - } - temp = newTemp; - } - - return temp[0]; - }; - - for (int i = 0; i <= numSteps; ++i) { - float t = (float)i / (float)numSteps; - auto [x, y, z] = deCasteljau3d(t); - - sol::table point = lua.create_table(); - point["x"] = std::round(x); - point["y"] = std::round(y); - point["z"] = std::round(z); - result[index++] = point; - } - - return result; - }); - - // ======================================== - // FLOOD FILL - // ======================================== - - // geo.floodFill(grid, startX, startY, newValue, options) -> grid - // Flood fill algorithm (4-connected or 8-connected) - geoTable.set_function("floodFill", [](sol::table inputGrid, int startX, int startY, int newValue, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); - - // Get dimensions - int height = static_cast(inputGrid.size()); - int width = 0; - if (inputGrid[1].valid() && inputGrid[1].get_type() == sol::type::table) { - sol::table firstRow = inputGrid[1]; - width = static_cast(firstRow.size()); - } - - if (width <= 0 || height <= 0) return inputGrid; - - bool eightConnected = false; - if (options) { - sol::table opts = *options; - eightConnected = opts.get_or("eightConnected", false); - } - - // Convert to grid - std::vector> grid(height, std::vector(width, 0)); - for (int y = 1; y <= height; ++y) { - if (inputGrid[y].valid() && inputGrid[y].get_type() == sol::type::table) { - sol::table row = inputGrid[y]; - for (int x = 1; x <= width; ++x) { - if (row[x].valid()) { - grid[y - 1][x - 1] = row[x].get(); + }); + + // ======================================== + // FLOOD FILL + // ======================================== + + // geo.floodFill(grid, startX, startY, newValue, options) -> grid + // Flood fill algorithm (4-connected or 8-connected) + geoTable.set_function("floodFill", [](sol::table inputGrid, int startX, int startY, int newValue, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + + // Get dimensions + int height = static_cast(inputGrid.size()); + int width = 0; + if (inputGrid[1].valid() && inputGrid[1].get_type() == sol::type::table) { + sol::table firstRow = inputGrid[1]; + width = static_cast(firstRow.size()); + } + + if (width <= 0 || height <= 0) { + return inputGrid; + } + + bool eightConnected = false; + if (options) { + sol::table opts = *options; + eightConnected = opts.get_or("eightConnected", false); + } + + // Convert to grid + std::vector> grid(height, std::vector(width, 0)); + for (int y = 1; y <= height; ++y) { + if (inputGrid[y].valid() && inputGrid[y].get_type() == sol::type::table) { + sol::table row = inputGrid[y]; + for (int x = 1; x <= width; ++x) { + if (row[x].valid()) { + grid[y - 1][x - 1] = row[x].get(); + } } } } - } - // Adjust for 1-indexed Lua - int sx = startX - 1; - int sy = startY - 1; + // Adjust for 1-indexed Lua + int sx = startX - 1; + int sy = startY - 1; - if (sx < 0 || sx >= width || sy < 0 || sy >= height) { - return inputGrid; - } + if (sx < 0 || sx >= width || sy < 0 || sy >= height) { + return inputGrid; + } - int oldValue = grid[sy][sx]; - if (oldValue == newValue) { - return inputGrid; - } + int oldValue = grid[sy][sx]; + if (oldValue == newValue) { + return inputGrid; + } - // BFS flood fill - std::queue> queue; - queue.push({sx, sy}); + // BFS flood fill + std::queue> queue; + queue.push({ sx, sy }); - // Direction arrays - const int dx4[] = {0, 1, 0, -1}; - const int dy4[] = {-1, 0, 1, 0}; - const int dx8[] = {0, 1, 1, 1, 0, -1, -1, -1}; - const int dy8[] = {-1, -1, 0, 1, 1, 1, 0, -1}; + // Direction arrays + const int dx4[] = { 0, 1, 0, -1 }; + const int dy4[] = { -1, 0, 1, 0 }; + const int dx8[] = { 0, 1, 1, 1, 0, -1, -1, -1 }; + const int dy8[] = { -1, -1, 0, 1, 1, 1, 0, -1 }; - const int* dx = eightConnected ? dx8 : dx4; - const int* dy = eightConnected ? dy8 : dy4; - int numDirs = eightConnected ? 8 : 4; + const int* dx = eightConnected ? dx8 : dx4; + const int* dy = eightConnected ? dy8 : dy4; + int numDirs = eightConnected ? 8 : 4; - while (!queue.empty()) { - auto [cx, cy] = queue.front(); - queue.pop(); + while (!queue.empty()) { + auto [cx, cy] = queue.front(); + queue.pop(); - if (cx < 0 || cx >= width || cy < 0 || cy >= height) continue; - if (grid[cy][cx] != oldValue) continue; + if (cx < 0 || cx >= width || cy < 0 || cy >= height) { + continue; + } + if (grid[cy][cx] != oldValue) { + continue; + } - grid[cy][cx] = newValue; + grid[cy][cx] = newValue; - for (int i = 0; i < numDirs; ++i) { - int nx = cx + dx[i]; - int ny = cy + dy[i]; - if (nx >= 0 && nx < width && ny >= 0 && ny < height && grid[ny][nx] == oldValue) { - queue.push({nx, ny}); + for (int i = 0; i < numDirs; ++i) { + int nx = cx + dx[i]; + int ny = cy + dy[i]; + if (nx >= 0 && nx < width && ny >= 0 && ny < height && grid[ny][nx] == oldValue) { + queue.push({ nx, ny }); + } } } - } - // Convert back to Lua table - sol::table result = lua.create_table(); - for (int y = 0; y < height; ++y) { - sol::table row = lua.create_table(); - for (int x = 0; x < width; ++x) { - row[x + 1] = grid[y][x]; + // Convert back to Lua table + sol::table result = lua.create_table(); + for (int y = 0; y < height; ++y) { + sol::table row = lua.create_table(); + for (int x = 0; x < width; ++x) { + row[x + 1] = grid[y][x]; + } + result[y + 1] = row; } - result[y + 1] = row; - } - return result; - }); - - // geo.getFloodFillPositions(grid, startX, startY, options) -> table of positions - // Returns all positions that would be filled without modifying the grid - geoTable.set_function("getFloodFillPositions", [](sol::table inputGrid, int startX, int startY, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); - sol::table result = lua.create_table(); - - int height = static_cast(inputGrid.size()); - int width = 0; - if (inputGrid[1].valid() && inputGrid[1].get_type() == sol::type::table) { - sol::table firstRow = inputGrid[1]; - width = static_cast(firstRow.size()); - } + return result; + }); + + // geo.getFloodFillPositions(grid, startX, startY, options) -> table of positions + // Returns all positions that would be filled without modifying the grid + geoTable.set_function("getFloodFillPositions", [](sol::table inputGrid, int startX, int startY, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + int height = static_cast(inputGrid.size()); + int width = 0; + if (inputGrid[1].valid() && inputGrid[1].get_type() == sol::type::table) { + sol::table firstRow = inputGrid[1]; + width = static_cast(firstRow.size()); + } - if (width <= 0 || height <= 0) return result; + if (width <= 0 || height <= 0) { + return result; + } - bool eightConnected = false; - if (options) { - sol::table opts = *options; - eightConnected = opts.get_or("eightConnected", false); - } + bool eightConnected = false; + if (options) { + sol::table opts = *options; + eightConnected = opts.get_or("eightConnected", false); + } - // Convert to grid - std::vector> grid(height, std::vector(width, 0)); - for (int y = 1; y <= height; ++y) { - if (inputGrid[y].valid() && inputGrid[y].get_type() == sol::type::table) { - sol::table row = inputGrid[y]; - for (int x = 1; x <= width; ++x) { - if (row[x].valid()) { - grid[y - 1][x - 1] = row[x].get(); + // Convert to grid + std::vector> grid(height, std::vector(width, 0)); + for (int y = 1; y <= height; ++y) { + if (inputGrid[y].valid() && inputGrid[y].get_type() == sol::type::table) { + sol::table row = inputGrid[y]; + for (int x = 1; x <= width; ++x) { + if (row[x].valid()) { + grid[y - 1][x - 1] = row[x].get(); + } } } } - } - int sx = startX - 1; - int sy = startY - 1; + int sx = startX - 1; + int sy = startY - 1; - if (sx < 0 || sx >= width || sy < 0 || sy >= height) { - return result; - } + if (sx < 0 || sx >= width || sy < 0 || sy >= height) { + return result; + } - int targetValue = grid[sy][sx]; + int targetValue = grid[sy][sx]; - std::vector> visited(height, std::vector(width, false)); - std::queue> queue; - queue.push({sx, sy}); - visited[sy][sx] = true; + std::vector> visited(height, std::vector(width, false)); + std::queue> queue; + queue.push({ sx, sy }); + visited[sy][sx] = true; - const int dx4[] = {0, 1, 0, -1}; - const int dy4[] = {-1, 0, 1, 0}; - const int dx8[] = {0, 1, 1, 1, 0, -1, -1, -1}; - const int dy8[] = {-1, -1, 0, 1, 1, 1, 0, -1}; + const int dx4[] = { 0, 1, 0, -1 }; + const int dy4[] = { -1, 0, 1, 0 }; + const int dx8[] = { 0, 1, 1, 1, 0, -1, -1, -1 }; + const int dy8[] = { -1, -1, 0, 1, 1, 1, 0, -1 }; - const int* dx = eightConnected ? dx8 : dx4; - const int* dy = eightConnected ? dy8 : dy4; - int numDirs = eightConnected ? 8 : 4; + const int* dx = eightConnected ? dx8 : dx4; + const int* dy = eightConnected ? dy8 : dy4; + int numDirs = eightConnected ? 8 : 4; - int index = 1; + int index = 1; - while (!queue.empty()) { - auto [cx, cy] = queue.front(); - queue.pop(); + while (!queue.empty()) { + auto [cx, cy] = queue.front(); + queue.pop(); - sol::table point = lua.create_table(); - point["x"] = cx + 1; // Back to 1-indexed - point["y"] = cy + 1; - result[index++] = point; + sol::table point = lua.create_table(); + point["x"] = cx + 1; // Back to 1-indexed + point["y"] = cy + 1; + result[index++] = point; - for (int i = 0; i < numDirs; ++i) { - int nx = cx + dx[i]; - int ny = cy + dy[i]; - if (nx >= 0 && nx < width && ny >= 0 && ny < height && - !visited[ny][nx] && grid[ny][nx] == targetValue) { - visited[ny][nx] = true; - queue.push({nx, ny}); + for (int i = 0; i < numDirs; ++i) { + int nx = cx + dx[i]; + int ny = cy + dy[i]; + if (nx >= 0 && nx < width && ny >= 0 && ny < height && !visited[ny][nx] && grid[ny][nx] == targetValue) { + visited[ny][nx] = true; + queue.push({ nx, ny }); + } } } - } - return result; - }); + return result; + }); + + // ======================================== + // CIRCLE / ELLIPSE + // ======================================== + + // geo.circle(centerX, centerY, radius, options) -> table of points + // Generate points on a circle (outline or filled) + geoTable.set_function("circle", [](int centerX, int centerY, int radius, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + bool filled = false; + if (options) { + sol::table opts = *options; + filled = opts.get_or("filled", false); + } - // ======================================== - // CIRCLE / ELLIPSE - // ======================================== + int index = 1; + + if (filled) { + // Filled circle + for (int y = -radius; y <= radius; ++y) { + for (int x = -radius; x <= radius; ++x) { + if (x * x + y * y <= radius * radius) { + sol::table point = lua.create_table(); + point["x"] = centerX + x; + point["y"] = centerY + y; + result[index++] = point; + } + } + } + } else { + // Midpoint circle algorithm (outline) + int x = radius; + int y = 0; + int err = 0; + + auto addPoints = [&](int cx, int cy, int px, int py) { + sol::table p1 = lua.create_table(); + p1["x"] = cx + px; + p1["y"] = cy + py; + result[index++] = p1; + sol::table p2 = lua.create_table(); + p2["x"] = cx + py; + p2["y"] = cy + px; + result[index++] = p2; + sol::table p3 = lua.create_table(); + p3["x"] = cx - py; + p3["y"] = cy + px; + result[index++] = p3; + sol::table p4 = lua.create_table(); + p4["x"] = cx - px; + p4["y"] = cy + py; + result[index++] = p4; + sol::table p5 = lua.create_table(); + p5["x"] = cx - px; + p5["y"] = cy - py; + result[index++] = p5; + sol::table p6 = lua.create_table(); + p6["x"] = cx - py; + p6["y"] = cy - px; + result[index++] = p6; + sol::table p7 = lua.create_table(); + p7["x"] = cx + py; + p7["y"] = cy - px; + result[index++] = p7; + sol::table p8 = lua.create_table(); + p8["x"] = cx + px; + p8["y"] = cy - py; + result[index++] = p8; + }; + + while (x >= y) { + addPoints(centerX, centerY, x, y); + y++; + + if (err <= 0) { + err += 2 * y + 1; + } else { + x--; + err += 2 * (y - x) + 1; + } + } + } - // geo.circle(centerX, centerY, radius, options) -> table of points - // Generate points on a circle (outline or filled) - geoTable.set_function("circle", [](int centerX, int centerY, int radius, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); - sol::table result = lua.create_table(); + return result; + }); - bool filled = false; - if (options) { - sol::table opts = *options; - filled = opts.get_or("filled", false); - } + // geo.ellipse(centerX, centerY, radiusX, radiusY, options) -> table of points + geoTable.set_function("ellipse", [](int centerX, int centerY, int radiusX, int radiusY, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); - int index = 1; + bool filled = false; + if (options) { + sol::table opts = *options; + filled = opts.get_or("filled", false); + } - if (filled) { - // Filled circle - for (int y = -radius; y <= radius; ++y) { - for (int x = -radius; x <= radius; ++x) { - if (x * x + y * y <= radius * radius) { - sol::table point = lua.create_table(); - point["x"] = centerX + x; - point["y"] = centerY + y; - result[index++] = point; + int index = 1; + + if (filled) { + for (int y = -radiusY; y <= radiusY; ++y) { + for (int x = -radiusX; x <= radiusX; ++x) { + float dx = (float)x / radiusX; + float dy = (float)y / radiusY; + if (dx * dx + dy * dy <= 1.0f) { + sol::table point = lua.create_table(); + point["x"] = centerX + x; + point["y"] = centerY + y; + result[index++] = point; + } + } + } + } else { + // Midpoint ellipse algorithm + int rx2 = radiusX * radiusX; + int ry2 = radiusY * radiusY; + int twoRx2 = 2 * rx2; + int twoRy2 = 2 * ry2; + + int x = 0; + int y = radiusY; + int px = 0; + int py = twoRx2 * y; + + auto addEllipsePoints = [&](int cx, int cy, int ex, int ey) { + sol::table p1 = lua.create_table(); + p1["x"] = cx + ex; + p1["y"] = cy + ey; + result[index++] = p1; + sol::table p2 = lua.create_table(); + p2["x"] = cx - ex; + p2["y"] = cy + ey; + result[index++] = p2; + sol::table p3 = lua.create_table(); + p3["x"] = cx + ex; + p3["y"] = cy - ey; + result[index++] = p3; + sol::table p4 = lua.create_table(); + p4["x"] = cx - ex; + p4["y"] = cy - ey; + result[index++] = p4; + }; + + // Region 1 + int p = (int)(ry2 - rx2 * radiusY + 0.25 * rx2); + while (px < py) { + addEllipsePoints(centerX, centerY, x, y); + x++; + px += twoRy2; + if (p < 0) { + p += ry2 + px; + } else { + y--; + py -= twoRx2; + p += ry2 + px - py; } } - } - } else { - // Midpoint circle algorithm (outline) - int x = radius; - int y = 0; - int err = 0; - - auto addPoints = [&](int cx, int cy, int px, int py) { - sol::table p1 = lua.create_table(); p1["x"] = cx + px; p1["y"] = cy + py; result[index++] = p1; - sol::table p2 = lua.create_table(); p2["x"] = cx + py; p2["y"] = cy + px; result[index++] = p2; - sol::table p3 = lua.create_table(); p3["x"] = cx - py; p3["y"] = cy + px; result[index++] = p3; - sol::table p4 = lua.create_table(); p4["x"] = cx - px; p4["y"] = cy + py; result[index++] = p4; - sol::table p5 = lua.create_table(); p5["x"] = cx - px; p5["y"] = cy - py; result[index++] = p5; - sol::table p6 = lua.create_table(); p6["x"] = cx - py; p6["y"] = cy - px; result[index++] = p6; - sol::table p7 = lua.create_table(); p7["x"] = cx + py; p7["y"] = cy - px; result[index++] = p7; - sol::table p8 = lua.create_table(); p8["x"] = cx + px; p8["y"] = cy - py; result[index++] = p8; - }; - - while (x >= y) { - addPoints(centerX, centerY, x, y); - y++; - if (err <= 0) { - err += 2 * y + 1; - } else { - x--; - err += 2 * (y - x) + 1; + // Region 2 + p = (int)(ry2 * (x + 0.5) * (x + 0.5) + rx2 * (y - 1) * (y - 1) - rx2 * ry2); + while (y >= 0) { + addEllipsePoints(centerX, centerY, x, y); + y--; + py -= twoRx2; + if (p > 0) { + p += rx2 - py; + } else { + x++; + px += twoRy2; + p += rx2 - py + px; + } } } - } - return result; - }); + return result; + }); + + // ======================================== + // RECTANGLE + // ======================================== + + // geo.rectangle(x1, y1, x2, y2, options) -> table of points + geoTable.set_function("rectangle", [](int x1, int y1, int x2, int y2, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); - // geo.ellipse(centerX, centerY, radiusX, radiusY, options) -> table of points - geoTable.set_function("ellipse", [](int centerX, int centerY, int radiusX, int radiusY, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); - sol::table result = lua.create_table(); + bool filled = false; + if (options) { + sol::table opts = *options; + filled = opts.get_or("filled", false); + } - bool filled = false; - if (options) { - sol::table opts = *options; - filled = opts.get_or("filled", false); - } + int minX = std::min(x1, x2); + int maxX = std::max(x1, x2); + int minY = std::min(y1, y2); + int maxY = std::max(y1, y2); - int index = 1; + int index = 1; - if (filled) { - for (int y = -radiusY; y <= radiusY; ++y) { - for (int x = -radiusX; x <= radiusX; ++x) { - float dx = (float)x / radiusX; - float dy = (float)y / radiusY; - if (dx * dx + dy * dy <= 1.0f) { + if (filled) { + for (int y = minY; y <= maxY; ++y) { + for (int x = minX; x <= maxX; ++x) { sol::table point = lua.create_table(); - point["x"] = centerX + x; - point["y"] = centerY + y; + point["x"] = x; + point["y"] = y; result[index++] = point; } } - } - } else { - // Midpoint ellipse algorithm - int rx2 = radiusX * radiusX; - int ry2 = radiusY * radiusY; - int twoRx2 = 2 * rx2; - int twoRy2 = 2 * ry2; - - int x = 0; - int y = radiusY; - int px = 0; - int py = twoRx2 * y; - - auto addEllipsePoints = [&](int cx, int cy, int ex, int ey) { - sol::table p1 = lua.create_table(); p1["x"] = cx + ex; p1["y"] = cy + ey; result[index++] = p1; - sol::table p2 = lua.create_table(); p2["x"] = cx - ex; p2["y"] = cy + ey; result[index++] = p2; - sol::table p3 = lua.create_table(); p3["x"] = cx + ex; p3["y"] = cy - ey; result[index++] = p3; - sol::table p4 = lua.create_table(); p4["x"] = cx - ex; p4["y"] = cy - ey; result[index++] = p4; - }; - - // Region 1 - int p = (int)(ry2 - rx2 * radiusY + 0.25 * rx2); - while (px < py) { - addEllipsePoints(centerX, centerY, x, y); - x++; - px += twoRy2; - if (p < 0) { - p += ry2 + px; - } else { - y--; - py -= twoRx2; - p += ry2 + px - py; + } else { + // Outline only + for (int x = minX; x <= maxX; ++x) { + sol::table p1 = lua.create_table(); + p1["x"] = x; + p1["y"] = minY; + result[index++] = p1; + sol::table p2 = lua.create_table(); + p2["x"] = x; + p2["y"] = maxY; + result[index++] = p2; + } + for (int y = minY + 1; y < maxY; ++y) { + sol::table p1 = lua.create_table(); + p1["x"] = minX; + p1["y"] = y; + result[index++] = p1; + sol::table p2 = lua.create_table(); + p2["x"] = maxX; + p2["y"] = y; + result[index++] = p2; } } - // Region 2 - p = (int)(ry2 * (x + 0.5) * (x + 0.5) + rx2 * (y - 1) * (y - 1) - rx2 * ry2); - while (y >= 0) { - addEllipsePoints(centerX, centerY, x, y); - y--; - py -= twoRx2; - if (p > 0) { - p += rx2 - py; - } else { - x++; - px += twoRy2; - p += rx2 - py + px; + return result; + }); + + // ======================================== + // POLYGON + // ======================================== + + // geo.polygon(vertices, options) -> table of points (outline using Bresenham) + geoTable.set_function("polygon", [](sol::table vertices, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + // Parse vertices + std::vector> verts; + for (auto& kv : vertices) { + if (kv.second.get_type() == sol::type::table) { + sol::table pt = kv.second; + int x = pt.get_or("x", pt.get_or(1, 0)); + int y = pt.get_or("y", pt.get_or(2, 0)); + verts.push_back({ x, y }); } } - } - - return result; - }); - // ======================================== - // RECTANGLE - // ======================================== + if (verts.size() < 3) { + return result; + } - // geo.rectangle(x1, y1, x2, y2, options) -> table of points - geoTable.set_function("rectangle", [](int x1, int y1, int x2, int y2, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); - sol::table result = lua.create_table(); + int index = 1; - bool filled = false; - if (options) { - sol::table opts = *options; - filled = opts.get_or("filled", false); - } + // Draw lines between consecutive vertices + for (size_t i = 0; i < verts.size(); ++i) { + int x1 = verts[i].first; + int y1 = verts[i].second; + int x2 = verts[(i + 1) % verts.size()].first; + int y2 = verts[(i + 1) % verts.size()].second; - int minX = std::min(x1, x2); - int maxX = std::max(x1, x2); - int minY = std::min(y1, y2); - int maxY = std::max(y1, y2); + // Bresenham + int dx = std::abs(x2 - x1); + int dy = std::abs(y2 - y1); + int sx = (x1 < x2) ? 1 : -1; + int sy = (y1 < y2) ? 1 : -1; + int err = dx - dy; - int index = 1; + int x = x1, y = y1; - if (filled) { - for (int y = minY; y <= maxY; ++y) { - for (int x = minX; x <= maxX; ++x) { + while (true) { sol::table point = lua.create_table(); point["x"] = x; point["y"] = y; result[index++] = point; + + if (x == x2 && y == y2) { + break; + } + + int e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x += sx; + } + if (e2 < dx) { + err += dx; + y += sy; + } } } - } else { - // Outline only - for (int x = minX; x <= maxX; ++x) { - sol::table p1 = lua.create_table(); p1["x"] = x; p1["y"] = minY; result[index++] = p1; - sol::table p2 = lua.create_table(); p2["x"] = x; p2["y"] = maxY; result[index++] = p2; + + return result; + }); + + // ======================================== + // DISTANCE FUNCTIONS + // ======================================== + + // geo.distance(x1, y1, x2, y2) -> number (Euclidean distance) + geoTable.set_function("distance", [](float x1, float y1, float x2, float y2) -> float { + float dx = x2 - x1; + float dy = y2 - y1; + return std::sqrt(dx * dx + dy * dy); + }); + + // geo.distanceSq(x1, y1, x2, y2) -> number (Squared distance, faster) + geoTable.set_function("distanceSq", [](float x1, float y1, float x2, float y2) -> float { + float dx = x2 - x1; + float dy = y2 - y1; + return dx * dx + dy * dy; + }); + + // geo.distanceManhattan(x1, y1, x2, y2) -> number + geoTable.set_function("distanceManhattan", [](int x1, int y1, int x2, int y2) -> int { + return std::abs(x2 - x1) + std::abs(y2 - y1); + }); + + // geo.distanceChebyshev(x1, y1, x2, y2) -> number (King's move distance) + geoTable.set_function("distanceChebyshev", [](int x1, int y1, int x2, int y2) -> int { + return std::max(std::abs(x2 - x1), std::abs(y2 - y1)); + }); + + // ======================================== + // POINT IN SHAPE TESTS + // ======================================== + + // geo.pointInCircle(px, py, cx, cy, radius) -> boolean + geoTable.set_function("pointInCircle", [](float px, float py, float cx, float cy, float radius) -> bool { + float dx = px - cx; + float dy = py - cy; + return dx * dx + dy * dy <= radius * radius; + }); + + // geo.pointInRectangle(px, py, x1, y1, x2, y2) -> boolean + geoTable.set_function("pointInRectangle", [](float px, float py, float x1, float y1, float x2, float y2) -> bool { + float minX = std::min(x1, x2); + float maxX = std::max(x1, x2); + float minY = std::min(y1, y2); + float maxY = std::max(y1, y2); + return px >= minX && px <= maxX && py >= minY && py <= maxY; + }); + + // geo.pointInPolygon(px, py, vertices) -> boolean (Ray casting algorithm) + geoTable.set_function("pointInPolygon", [](float px, float py, sol::table vertices) -> bool { + std::vector> verts; + for (auto& kv : vertices) { + if (kv.second.get_type() == sol::type::table) { + sol::table pt = kv.second; + float x = pt.get_or("x", pt.get_or(1, 0.0f)); + float y = pt.get_or("y", pt.get_or(2, 0.0f)); + verts.push_back({ x, y }); + } } - for (int y = minY + 1; y < maxY; ++y) { - sol::table p1 = lua.create_table(); p1["x"] = minX; p1["y"] = y; result[index++] = p1; - sol::table p2 = lua.create_table(); p2["x"] = maxX; p2["y"] = y; result[index++] = p2; + + if (verts.size() < 3) { + return false; } - } - return result; - }); + bool inside = false; + size_t n = verts.size(); + for (size_t i = 0, j = n - 1; i < n; j = i++) { + float xi = verts[i].first, yi = verts[i].second; + float xj = verts[j].first, yj = verts[j].second; - // ======================================== - // POLYGON - // ======================================== + if (((yi > py) != (yj > py)) && (px < (xj - xi) * (py - yi) / (yj - yi) + xi)) { + inside = !inside; + } + } - // geo.polygon(vertices, options) -> table of points (outline using Bresenham) - geoTable.set_function("polygon", [](sol::table vertices, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); - sol::table result = lua.create_table(); + return inside; + }); - // Parse vertices - std::vector> verts; - for (auto& kv : vertices) { - if (kv.second.get_type() == sol::type::table) { - sol::table pt = kv.second; - int x = pt.get_or("x", pt.get_or(1, 0)); - int y = pt.get_or("y", pt.get_or(2, 0)); - verts.push_back({x, y}); - } - } + // ======================================== + // RANDOM SCATTER + // ======================================== - if (verts.size() < 3) { - return result; - } + // geo.randomScatter(x1, y1, x2, y2, count, options) -> table of points + // Scatter random points in a region (useful for doodad placement) + geoTable.set_function("randomScatter", [](int x1, int y1, int x2, int y2, int count, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); - int index = 1; + int seed = static_cast(time(nullptr)); + int minDistance = 0; - // Draw lines between consecutive vertices - for (size_t i = 0; i < verts.size(); ++i) { - int x1 = verts[i].first; - int y1 = verts[i].second; - int x2 = verts[(i + 1) % verts.size()].first; - int y2 = verts[(i + 1) % verts.size()].second; + if (options) { + sol::table opts = *options; + seed = opts.get_or("seed", seed); + minDistance = opts.get_or("minDistance", 0); + } - // Bresenham - int dx = std::abs(x2 - x1); - int dy = std::abs(y2 - y1); - int sx = (x1 < x2) ? 1 : -1; - int sy = (y1 < y2) ? 1 : -1; - int err = dx - dy; + std::mt19937 rng(seed); + std::uniform_int_distribution distX(std::min(x1, x2), std::max(x1, x2)); + std::uniform_int_distribution distY(std::min(y1, y2), std::max(y1, y2)); - int x = x1, y = y1; + std::vector> points; - while (true) { - sol::table point = lua.create_table(); - point["x"] = x; - point["y"] = y; - result[index++] = point; + int attempts = 0; + int maxAttempts = count * 100; - if (x == x2 && y == y2) break; + while (points.size() < static_cast(count) && attempts < maxAttempts) { + attempts++; + int x = distX(rng); + int y = distY(rng); - int e2 = 2 * err; - if (e2 > -dy) { - err -= dy; - x += sx; - } - if (e2 < dx) { - err += dx; - y += sy; - } - } - } - - return result; - }); - - // ======================================== - // DISTANCE FUNCTIONS - // ======================================== - - // geo.distance(x1, y1, x2, y2) -> number (Euclidean distance) - geoTable.set_function("distance", [](float x1, float y1, float x2, float y2) -> float { - float dx = x2 - x1; - float dy = y2 - y1; - return std::sqrt(dx * dx + dy * dy); - }); - - // geo.distanceSq(x1, y1, x2, y2) -> number (Squared distance, faster) - geoTable.set_function("distanceSq", [](float x1, float y1, float x2, float y2) -> float { - float dx = x2 - x1; - float dy = y2 - y1; - return dx * dx + dy * dy; - }); - - // geo.distanceManhattan(x1, y1, x2, y2) -> number - geoTable.set_function("distanceManhattan", [](int x1, int y1, int x2, int y2) -> int { - return std::abs(x2 - x1) + std::abs(y2 - y1); - }); - - // geo.distanceChebyshev(x1, y1, x2, y2) -> number (King's move distance) - geoTable.set_function("distanceChebyshev", [](int x1, int y1, int x2, int y2) -> int { - return std::max(std::abs(x2 - x1), std::abs(y2 - y1)); - }); - - // ======================================== - // POINT IN SHAPE TESTS - // ======================================== - - // geo.pointInCircle(px, py, cx, cy, radius) -> boolean - geoTable.set_function("pointInCircle", [](float px, float py, float cx, float cy, float radius) -> bool { - float dx = px - cx; - float dy = py - cy; - return dx * dx + dy * dy <= radius * radius; - }); - - // geo.pointInRectangle(px, py, x1, y1, x2, y2) -> boolean - geoTable.set_function("pointInRectangle", [](float px, float py, float x1, float y1, float x2, float y2) -> bool { - float minX = std::min(x1, x2); - float maxX = std::max(x1, x2); - float minY = std::min(y1, y2); - float maxY = std::max(y1, y2); - return px >= minX && px <= maxX && py >= minY && py <= maxY; - }); - - // geo.pointInPolygon(px, py, vertices) -> boolean (Ray casting algorithm) - geoTable.set_function("pointInPolygon", [](float px, float py, sol::table vertices) -> bool { - std::vector> verts; - for (auto& kv : vertices) { - if (kv.second.get_type() == sol::type::table) { - sol::table pt = kv.second; - float x = pt.get_or("x", pt.get_or(1, 0.0f)); - float y = pt.get_or("y", pt.get_or(2, 0.0f)); - verts.push_back({x, y}); - } - } - - if (verts.size() < 3) return false; - - bool inside = false; - size_t n = verts.size(); - for (size_t i = 0, j = n - 1; i < n; j = i++) { - float xi = verts[i].first, yi = verts[i].second; - float xj = verts[j].first, yj = verts[j].second; - - if (((yi > py) != (yj > py)) && - (px < (xj - xi) * (py - yi) / (yj - yi) + xi)) { - inside = !inside; - } - } - - return inside; - }); - - // ======================================== - // RANDOM SCATTER - // ======================================== - - // geo.randomScatter(x1, y1, x2, y2, count, options) -> table of points - // Scatter random points in a region (useful for doodad placement) - geoTable.set_function("randomScatter", [](int x1, int y1, int x2, int y2, int count, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); - sol::table result = lua.create_table(); - - int seed = static_cast(time(nullptr)); - int minDistance = 0; - - if (options) { - sol::table opts = *options; - seed = opts.get_or("seed", seed); - minDistance = opts.get_or("minDistance", 0); - } - - std::mt19937 rng(seed); - std::uniform_int_distribution distX(std::min(x1, x2), std::max(x1, x2)); - std::uniform_int_distribution distY(std::min(y1, y2), std::max(y1, y2)); - - std::vector> points; - - int attempts = 0; - int maxAttempts = count * 100; - - while (points.size() < static_cast(count) && attempts < maxAttempts) { - attempts++; - int x = distX(rng); - int y = distY(rng); - - // Check minimum distance - bool valid = true; - if (minDistance > 0) { - for (const auto& p : points) { - int dx = x - p.first; - int dy = y - p.second; - if (dx * dx + dy * dy < minDistance * minDistance) { - valid = false; - break; + // Check minimum distance + bool valid = true; + if (minDistance > 0) { + for (const auto& p : points) { + int dx = x - p.first; + int dy = y - p.second; + if (dx * dx + dy * dy < minDistance * minDistance) { + valid = false; + break; + } } } - } - if (valid) { - points.push_back({x, y}); + if (valid) { + points.push_back({ x, y }); + } } - } - - for (size_t i = 0; i < points.size(); ++i) { - sol::table point = lua.create_table(); - point["x"] = points[i].first; - point["y"] = points[i].second; - result[i + 1] = point; - } - - return result; - }); - // geo.poissonDiskSampling(x1, y1, x2, y2, minDistance, options) -> table of points - // Blue noise distribution (evenly spaced random points) - geoTable.set_function("poissonDiskSampling", [](int x1, int y1, int x2, int y2, float minDistance, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); - sol::table result = lua.create_table(); + for (size_t i = 0; i < points.size(); ++i) { + sol::table point = lua.create_table(); + point["x"] = points[i].first; + point["y"] = points[i].second; + result[i + 1] = point; + } - int seed = static_cast(time(nullptr)); - int maxAttempts = 30; + return result; + }); - if (options) { - sol::table opts = *options; - seed = opts.get_or("seed", seed); - maxAttempts = opts.get_or("maxAttempts", 30); - } + // geo.poissonDiskSampling(x1, y1, x2, y2, minDistance, options) -> table of points + // Blue noise distribution (evenly spaced random points) + geoTable.set_function("poissonDiskSampling", [](int x1, int y1, int x2, int y2, float minDistance, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); - std::mt19937 rng(seed); + int seed = static_cast(time(nullptr)); + int maxAttempts = 30; - int minX = std::min(x1, x2); - int maxX = std::max(x1, x2); - int minY = std::min(y1, y2); - int maxY = std::max(y1, y2); + if (options) { + sol::table opts = *options; + seed = opts.get_or("seed", seed); + maxAttempts = opts.get_or("maxAttempts", 30); + } - float width = static_cast(maxX - minX); - float height = static_cast(maxY - minY); + std::mt19937 rng(seed); - float cellSize = minDistance / std::sqrt(2.0f); - int gridWidth = static_cast(std::ceil(width / cellSize)); - int gridHeight = static_cast(std::ceil(height / cellSize)); + int minX = std::min(x1, x2); + int maxX = std::max(x1, x2); + int minY = std::min(y1, y2); + int maxY = std::max(y1, y2); - std::vector> grid(gridHeight, std::vector(gridWidth, -1)); - std::vector> points; - std::vector activeList; + float width = static_cast(maxX - minX); + float height = static_cast(maxY - minY); - // Start with first point - std::uniform_real_distribution distW(0, width); - std::uniform_real_distribution distH(0, height); + float cellSize = minDistance / std::sqrt(2.0f); + int gridWidth = static_cast(std::ceil(width / cellSize)); + int gridHeight = static_cast(std::ceil(height / cellSize)); - float firstX = distW(rng); - float firstY = distH(rng); - points.push_back({firstX, firstY}); - activeList.push_back(0); + std::vector> grid(gridHeight, std::vector(gridWidth, -1)); + std::vector> points; + std::vector activeList; - int gx = static_cast(firstX / cellSize); - int gy = static_cast(firstY / cellSize); - if (gx >= 0 && gx < gridWidth && gy >= 0 && gy < gridHeight) { - grid[gy][gx] = 0; - } + // Start with first point + std::uniform_real_distribution distW(0, width); + std::uniform_real_distribution distH(0, height); - std::uniform_real_distribution dist01(0, 1); + float firstX = distW(rng); + float firstY = distH(rng); + points.push_back({ firstX, firstY }); + activeList.push_back(0); - while (!activeList.empty()) { - size_t randIndex = rng() % activeList.size(); - size_t pointIndex = activeList[randIndex]; - auto& point = points[pointIndex]; + int gx = static_cast(firstX / cellSize); + int gy = static_cast(firstY / cellSize); + if (gx >= 0 && gx < gridWidth && gy >= 0 && gy < gridHeight) { + grid[gy][gx] = 0; + } - bool found = false; + std::uniform_real_distribution dist01(0, 1); - for (int k = 0; k < maxAttempts; ++k) { - float angle = dist01(rng) * 2 * 3.14159265f; - float r = minDistance + dist01(rng) * minDistance; + while (!activeList.empty()) { + size_t randIndex = rng() % activeList.size(); + size_t pointIndex = activeList[randIndex]; + auto& point = points[pointIndex]; - float newX = point.first + r * std::cos(angle); - float newY = point.second + r * std::sin(angle); + bool found = false; - if (newX < 0 || newX >= width || newY < 0 || newY >= height) continue; + for (int k = 0; k < maxAttempts; ++k) { + float angle = dist01(rng) * 2 * 3.14159265f; + float r = minDistance + dist01(rng) * minDistance; - int ngx = static_cast(newX / cellSize); - int ngy = static_cast(newY / cellSize); + float newX = point.first + r * std::cos(angle); + float newY = point.second + r * std::sin(angle); - bool valid = true; + if (newX < 0 || newX >= width || newY < 0 || newY >= height) { + continue; + } - // Check neighbors - for (int dy = -2; dy <= 2 && valid; ++dy) { - for (int dx = -2; dx <= 2 && valid; ++dx) { - int cx = ngx + dx; - int cy = ngy + dy; - if (cx >= 0 && cx < gridWidth && cy >= 0 && cy < gridHeight) { - int neighborIdx = grid[cy][cx]; - if (neighborIdx >= 0) { - auto& neighbor = points[neighborIdx]; - float ddx = newX - neighbor.first; - float ddy = newY - neighbor.second; - if (ddx * ddx + ddy * ddy < minDistance * minDistance) { - valid = false; + int ngx = static_cast(newX / cellSize); + int ngy = static_cast(newY / cellSize); + + bool valid = true; + + // Check neighbors + for (int dy = -2; dy <= 2 && valid; ++dy) { + for (int dx = -2; dx <= 2 && valid; ++dx) { + int cx = ngx + dx; + int cy = ngy + dy; + if (cx >= 0 && cx < gridWidth && cy >= 0 && cy < gridHeight) { + int neighborIdx = grid[cy][cx]; + if (neighborIdx >= 0) { + auto& neighbor = points[neighborIdx]; + float ddx = newX - neighbor.first; + float ddy = newY - neighbor.second; + if (ddx * ddx + ddy * ddy < minDistance * minDistance) { + valid = false; + } } } } } - } - if (valid) { - size_t newIdx = points.size(); - points.push_back({newX, newY}); - activeList.push_back(newIdx); - if (ngx >= 0 && ngx < gridWidth && ngy >= 0 && ngy < gridHeight) { - grid[ngy][ngx] = static_cast(newIdx); + if (valid) { + size_t newIdx = points.size(); + points.push_back({ newX, newY }); + activeList.push_back(newIdx); + if (ngx >= 0 && ngx < gridWidth && ngy >= 0 && ngy < gridHeight) { + grid[ngy][ngx] = static_cast(newIdx); + } + found = true; + break; } - found = true; - break; } - } - if (!found) { - activeList.erase(activeList.begin() + randIndex); + if (!found) { + activeList.erase(activeList.begin() + randIndex); + } } - } - for (size_t i = 0; i < points.size(); ++i) { - sol::table point = lua.create_table(); - point["x"] = static_cast(points[i].first) + minX; - point["y"] = static_cast(points[i].second) + minY; - result[i + 1] = point; - } + for (size_t i = 0; i < points.size(); ++i) { + sol::table point = lua.create_table(); + point["x"] = static_cast(points[i].first) + minX; + point["y"] = static_cast(points[i].second) + minY; + result[i + 1] = point; + } - return result; - }); + return result; + }); - lua["geo"] = geoTable; -} + lua["geo"] = geoTable; + } } // namespace LuaAPI diff --git a/source/lua/lua_api_http.cpp b/source/lua/lua_api_http.cpp index 2ee0d656f..93e37e025 100644 --- a/source/lua/lua_api_http.cpp +++ b/source/lua/lua_api_http.cpp @@ -31,425 +31,424 @@ namespace LuaAPI { -// StreamSession class for managing streaming HTTP requests -class StreamSession { -public: - StreamSession() : finished_(false), hasError_(false), statusCode_(0) {} - - void appendChunk(const std::string& chunk) { - std::lock_guard lock(mutex_); - chunks_.push(chunk); - cv_.notify_one(); - } + // StreamSession class for managing streaming HTTP requests + class StreamSession { + public: + StreamSession() : + finished_(false), hasError_(false), statusCode_(0) { } + + void appendChunk(const std::string& chunk) { + std::lock_guard lock(mutex_); + chunks_.push(chunk); + cv_.notify_one(); + } - std::string getNextChunk() { - std::lock_guard lock(mutex_); - if (chunks_.empty()) { - return ""; + std::string getNextChunk() { + std::lock_guard lock(mutex_); + if (chunks_.empty()) { + return ""; + } + std::string chunk = chunks_.front(); + chunks_.pop(); + return chunk; } - std::string chunk = chunks_.front(); - chunks_.pop(); - return chunk; - } - bool hasChunks() { - std::lock_guard lock(mutex_); - return !chunks_.empty(); - } + bool hasChunks() { + std::lock_guard lock(mutex_); + return !chunks_.empty(); + } - void setFinished() { - std::lock_guard lock(mutex_); - finished_ = true; - cv_.notify_all(); - } + void setFinished() { + std::lock_guard lock(mutex_); + finished_ = true; + cv_.notify_all(); + } - bool isFinished() { - std::lock_guard lock(mutex_); - return finished_; - } + bool isFinished() { + std::lock_guard lock(mutex_); + return finished_; + } - void setError(const std::string& error) { - std::lock_guard lock(mutex_); - hasError_ = true; - errorMessage_ = error; - finished_ = true; - cv_.notify_all(); - } + void setError(const std::string& error) { + std::lock_guard lock(mutex_); + hasError_ = true; + errorMessage_ = error; + finished_ = true; + cv_.notify_all(); + } - bool hasError() { - std::lock_guard lock(mutex_); - return hasError_; - } + bool hasError() { + std::lock_guard lock(mutex_); + return hasError_; + } - std::string getError() { - std::lock_guard lock(mutex_); - return errorMessage_; - } + std::string getError() { + std::lock_guard lock(mutex_); + return errorMessage_; + } - void setStatusCode(int code) { - statusCode_ = code; - } + void setStatusCode(int code) { + statusCode_ = code; + } - int getStatusCode() { - return statusCode_; - } + int getStatusCode() { + return statusCode_; + } - void setHeaders(const cpr::Header& headers) { - std::lock_guard lock(mutex_); - responseHeaders_ = headers; - } + void setHeaders(const cpr::Header& headers) { + std::lock_guard lock(mutex_); + responseHeaders_ = headers; + } - cpr::Header getHeaders() { - std::lock_guard lock(mutex_); - return responseHeaders_; - } + cpr::Header getHeaders() { + std::lock_guard lock(mutex_); + return responseHeaders_; + } -private: - std::queue chunks_; - std::mutex mutex_; - std::condition_variable cv_; - std::atomic finished_; - std::atomic hasError_; - std::string errorMessage_; - std::atomic statusCode_; - cpr::Header responseHeaders_; -}; - -// Global map to store active stream sessions -static std::map> g_streamSessions; -static std::mutex g_sessionsMutex; -static int g_nextSessionId = 1; - -// HTTP GET request -static sol::table httpGet(sol::this_state ts, const std::string& url, sol::optional optHeaders) { - sol::state_view lua(ts); - sol::table result = lua.create_table(); - - cpr::Header headers; - if (optHeaders) { - sol::table headersTable = *optHeaders; - for (auto& pair : headersTable) { - if (pair.first.is() && pair.second.is()) { - headers[pair.first.as()] = pair.second.as(); + private: + std::queue chunks_; + std::mutex mutex_; + std::condition_variable cv_; + std::atomic finished_; + std::atomic hasError_; + std::string errorMessage_; + std::atomic statusCode_; + cpr::Header responseHeaders_; + }; + + // Global map to store active stream sessions + static std::map> g_streamSessions; + static std::mutex g_sessionsMutex; + static int g_nextSessionId = 1; + + // HTTP GET request + static sol::table httpGet(sol::this_state ts, const std::string& url, sol::optional optHeaders) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); + + cpr::Header headers; + if (optHeaders) { + sol::table headersTable = *optHeaders; + for (auto& pair : headersTable) { + if (pair.first.is() && pair.second.is()) { + headers[pair.first.as()] = pair.second.as(); + } } } - } - cpr::Response response = cpr::Get(cpr::Url{url}, headers); + cpr::Response response = cpr::Get(cpr::Url { url }, headers); - result["status"] = static_cast(response.status_code); - result["body"] = response.text; - result["error"] = response.error.message; - result["ok"] = response.status_code >= 200 && response.status_code < 300; + result["status"] = static_cast(response.status_code); + result["body"] = response.text; + result["error"] = response.error.message; + result["ok"] = response.status_code >= 200 && response.status_code < 300; - // Parse response headers - sol::table respHeaders = lua.create_table(); - for (const auto& h : response.header) { - respHeaders[h.first] = h.second; - } - result["headers"] = respHeaders; - - return result; -} - -// HTTP POST request -static sol::table httpPost(sol::this_state ts, const std::string& url, const std::string& body, sol::optional optHeaders) { - sol::state_view lua(ts); - sol::table result = lua.create_table(); - - cpr::Header headers; - if (optHeaders) { - sol::table headersTable = *optHeaders; - for (auto& pair : headersTable) { - if (pair.first.is() && pair.second.is()) { - headers[pair.first.as()] = pair.second.as(); - } + // Parse response headers + sol::table respHeaders = lua.create_table(); + for (const auto& h : response.header) { + respHeaders[h.first] = h.second; } - } + result["headers"] = respHeaders; - cpr::Response response = cpr::Post( - cpr::Url{url}, - cpr::Body{body}, - headers - ); - - result["status"] = static_cast(response.status_code); - result["body"] = response.text; - result["error"] = response.error.message; - result["ok"] = response.status_code >= 200 && response.status_code < 300; - - // Parse response headers - sol::table respHeaders = lua.create_table(); - for (const auto& h : response.header) { - respHeaders[h.first] = h.second; + return result; } - result["headers"] = respHeaders; - - return result; -} - -// Helper function to convert Lua table to JSON -static std::function getLuaToJsonConverter() { - std::function luaToJson; - luaToJson = [&luaToJson](sol::object obj) -> nlohmann::json { - if (obj.is()) { - return obj.as(); - } else if (obj.is()) { - return obj.as(); - } else if (obj.is()) { - return obj.as(); - } else if (obj.is()) { - return obj.as(); - } else if (obj.is()) { - sol::table tbl = obj.as(); - - // Check if it's an array (sequential integer keys starting at 1) - bool isArray = true; - size_t expectedKey = 1; - for (auto& pair : tbl) { - if (!pair.first.is() || pair.first.as() != expectedKey) { - isArray = false; - break; + + // HTTP POST request + static sol::table httpPost(sol::this_state ts, const std::string& url, const std::string& body, sol::optional optHeaders) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); + + cpr::Header headers; + if (optHeaders) { + sol::table headersTable = *optHeaders; + for (auto& pair : headersTable) { + if (pair.first.is() && pair.second.is()) { + headers[pair.first.as()] = pair.second.as(); } - expectedKey++; } + } - if (isArray && expectedKey > 1) { - nlohmann::json arr = nlohmann::json::array(); + cpr::Response response = cpr::Post( + cpr::Url { url }, + cpr::Body { body }, + headers + ); + + result["status"] = static_cast(response.status_code); + result["body"] = response.text; + result["error"] = response.error.message; + result["ok"] = response.status_code >= 200 && response.status_code < 300; + + // Parse response headers + sol::table respHeaders = lua.create_table(); + for (const auto& h : response.header) { + respHeaders[h.first] = h.second; + } + result["headers"] = respHeaders; + + return result; + } + + // Helper function to convert Lua table to JSON + static std::function getLuaToJsonConverter() { + std::function luaToJson; + luaToJson = [&luaToJson](sol::object obj) -> nlohmann::json { + if (obj.is()) { + return obj.as(); + } else if (obj.is()) { + return obj.as(); + } else if (obj.is()) { + return obj.as(); + } else if (obj.is()) { + return obj.as(); + } else if (obj.is()) { + sol::table tbl = obj.as(); + + // Check if it's an array (sequential integer keys starting at 1) + bool isArray = true; + size_t expectedKey = 1; for (auto& pair : tbl) { - arr.push_back(luaToJson(pair.second)); + if (!pair.first.is() || pair.first.as() != expectedKey) { + isArray = false; + break; + } + expectedKey++; } - return arr; - } else { - nlohmann::json jsonObj = nlohmann::json::object(); - for (auto& pair : tbl) { - std::string key; - if (pair.first.is()) { - key = pair.first.as(); - } else if (pair.first.is()) { - key = std::to_string(pair.first.as()); - } else { - continue; + + if (isArray && expectedKey > 1) { + nlohmann::json arr = nlohmann::json::array(); + for (auto& pair : tbl) { + arr.push_back(luaToJson(pair.second)); } - jsonObj[key] = luaToJson(pair.second); + return arr; + } else { + nlohmann::json jsonObj = nlohmann::json::object(); + for (auto& pair : tbl) { + std::string key; + if (pair.first.is()) { + key = pair.first.as(); + } else if (pair.first.is()) { + key = std::to_string(pair.first.as()); + } else { + continue; + } + jsonObj[key] = luaToJson(pair.second); + } + return jsonObj; } - return jsonObj; + } else if (obj.is()) { + return nullptr; } - } else if (obj.is()) { return nullptr; - } - return nullptr; - }; - return luaToJson; -} + }; + return luaToJson; + } -// HTTP POST with JSON body -static sol::table httpPostJson(sol::this_state ts, const std::string& url, sol::table jsonBody, sol::optional optHeaders) { - sol::state_view lua(ts); + // HTTP POST with JSON body + static sol::table httpPostJson(sol::this_state ts, const std::string& url, sol::table jsonBody, sol::optional optHeaders) { + sol::state_view lua(ts); - auto luaToJson = getLuaToJsonConverter(); - std::string jsonStr = luaToJson(jsonBody).dump(); + auto luaToJson = getLuaToJsonConverter(); + std::string jsonStr = luaToJson(jsonBody).dump(); - // Add Content-Type header if not present - sol::table headers = lua.create_table(); - headers["Content-Type"] = "application/json"; + // Add Content-Type header if not present + sol::table headers = lua.create_table(); + headers["Content-Type"] = "application/json"; - if (optHeaders) { - for (auto& pair : *optHeaders) { - if (pair.first.is() && pair.second.is()) { - headers[pair.first.as()] = pair.second.as(); + if (optHeaders) { + for (auto& pair : *optHeaders) { + if (pair.first.is() && pair.second.is()) { + headers[pair.first.as()] = pair.second.as(); + } } } - } - return httpPost(ts, url, jsonStr, headers); -} + return httpPost(ts, url, jsonStr, headers); + } -// Start a streaming POST request - returns session ID -static sol::table httpPostStream(sol::this_state ts, const std::string& url, const std::string& body, sol::optional optHeaders) { - sol::state_view lua(ts); - sol::table result = lua.create_table(); + // Start a streaming POST request - returns session ID + static sol::table httpPostStream(sol::this_state ts, const std::string& url, const std::string& body, sol::optional optHeaders) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); - auto session = std::make_shared(); - int sessionId; + auto session = std::make_shared(); + int sessionId; - { - std::lock_guard lock(g_sessionsMutex); - sessionId = g_nextSessionId++; - g_streamSessions[sessionId] = session; - } + { + std::lock_guard lock(g_sessionsMutex); + sessionId = g_nextSessionId++; + g_streamSessions[sessionId] = session; + } - cpr::Header headers; - if (optHeaders) { - sol::table headersTable = *optHeaders; - for (auto& pair : headersTable) { - if (pair.first.is() && pair.second.is()) { - headers[pair.first.as()] = pair.second.as(); + cpr::Header headers; + if (optHeaders) { + sol::table headersTable = *optHeaders; + for (auto& pair : headersTable) { + if (pair.first.is() && pair.second.is()) { + headers[pair.first.as()] = pair.second.as(); + } } } - } - // Start the streaming request in a separate thread - std::thread([session, url, body, headers]() { - std::function writeCallback = [session](std::string_view data, intptr_t /*userdata*/) -> bool { - session->appendChunk(std::string(data)); - return true; - }; - - cpr::Response response = cpr::Post( - cpr::Url{url}, - cpr::Body{body}, - headers, - cpr::WriteCallback{writeCallback, 0} - ); + // Start the streaming request in a separate thread + std::thread([session, url, body, headers]() { + std::function writeCallback = [session](std::string_view data, intptr_t /*userdata*/) -> bool { + session->appendChunk(std::string(data)); + return true; + }; + + cpr::Response response = cpr::Post( + cpr::Url { url }, + cpr::Body { body }, + headers, + cpr::WriteCallback { writeCallback, 0 } + ); + + session->setStatusCode(static_cast(response.status_code)); + session->setHeaders(response.header); + + if (response.error) { + session->setError(response.error.message); + } else { + session->setFinished(); + } + }).detach(); - session->setStatusCode(static_cast(response.status_code)); - session->setHeaders(response.header); + result["sessionId"] = sessionId; + result["ok"] = true; - if (response.error) { - session->setError(response.error.message); - } else { - session->setFinished(); - } - }).detach(); + return result; + } - result["sessionId"] = sessionId; - result["ok"] = true; + // Start a streaming POST request with JSON body - returns session ID + static sol::table httpPostJsonStream(sol::this_state ts, const std::string& url, sol::table jsonBody, sol::optional optHeaders) { + sol::state_view lua(ts); - return result; -} + auto luaToJson = getLuaToJsonConverter(); + std::string jsonStr = luaToJson(jsonBody).dump(); -// Start a streaming POST request with JSON body - returns session ID -static sol::table httpPostJsonStream(sol::this_state ts, const std::string& url, sol::table jsonBody, sol::optional optHeaders) { - sol::state_view lua(ts); + // Add Content-Type header if not present + sol::table headers = lua.create_table(); + headers["Content-Type"] = "application/json"; - auto luaToJson = getLuaToJsonConverter(); - std::string jsonStr = luaToJson(jsonBody).dump(); + if (optHeaders) { + for (auto& pair : *optHeaders) { + if (pair.first.is() && pair.second.is()) { + headers[pair.first.as()] = pair.second.as(); + } + } + } - // Add Content-Type header if not present - sol::table headers = lua.create_table(); - headers["Content-Type"] = "application/json"; + return httpPostStream(ts, url, jsonStr, headers); + } - if (optHeaders) { - for (auto& pair : *optHeaders) { - if (pair.first.is() && pair.second.is()) { - headers[pair.first.as()] = pair.second.as(); + // Read available chunks from a stream session + static sol::table httpStreamRead(sol::this_state ts, int sessionId) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); + + std::shared_ptr session; + { + std::lock_guard lock(g_sessionsMutex); + auto it = g_streamSessions.find(sessionId); + if (it == g_streamSessions.end()) { + result["ok"] = false; + result["error"] = "Invalid session ID"; + result["finished"] = true; + return result; } + session = it->second; } - } - return httpPostStream(ts, url, jsonStr, headers); -} + // Collect all available chunks + std::string data; + while (session->hasChunks()) { + data += session->getNextChunk(); + } -// Read available chunks from a stream session -static sol::table httpStreamRead(sol::this_state ts, int sessionId) { - sol::state_view lua(ts); - sol::table result = lua.create_table(); + result["data"] = data; + result["finished"] = session->isFinished(); + result["hasError"] = session->hasError(); - std::shared_ptr session; - { - std::lock_guard lock(g_sessionsMutex); - auto it = g_streamSessions.find(sessionId); - if (it == g_streamSessions.end()) { + if (session->hasError()) { + result["error"] = session->getError(); result["ok"] = false; - result["error"] = "Invalid session ID"; - result["finished"] = true; - return result; + } else { + result["ok"] = true; } - session = it->second; - } - - // Collect all available chunks - std::string data; - while (session->hasChunks()) { - data += session->getNextChunk(); - } - - result["data"] = data; - result["finished"] = session->isFinished(); - result["hasError"] = session->hasError(); - if (session->hasError()) { - result["error"] = session->getError(); - result["ok"] = false; - } else { - result["ok"] = true; - } + if (session->isFinished()) { + result["status"] = session->getStatusCode(); - if (session->isFinished()) { - result["status"] = session->getStatusCode(); - - // Return headers when finished - sol::table respHeaders = lua.create_table(); - for (const auto& h : session->getHeaders()) { - respHeaders[h.first] = h.second; + // Return headers when finished + sol::table respHeaders = lua.create_table(); + for (const auto& h : session->getHeaders()) { + respHeaders[h.first] = h.second; + } + result["headers"] = respHeaders; } - result["headers"] = respHeaders; - } - - return result; -} -// Close and cleanup a stream session -static bool httpStreamClose(int sessionId) { - std::lock_guard lock(g_sessionsMutex); - auto it = g_streamSessions.find(sessionId); - if (it != g_streamSessions.end()) { - g_streamSessions.erase(it); - return true; + return result; } - return false; -} -// Check if a stream session is finished -static sol::table httpStreamStatus(sol::this_state ts, int sessionId) { - sol::state_view lua(ts); - sol::table result = lua.create_table(); - - std::shared_ptr session; - { + // Close and cleanup a stream session + static bool httpStreamClose(int sessionId) { std::lock_guard lock(g_sessionsMutex); auto it = g_streamSessions.find(sessionId); - if (it == g_streamSessions.end()) { - result["valid"] = false; - result["finished"] = true; - return result; + if (it != g_streamSessions.end()) { + g_streamSessions.erase(it); + return true; } - session = it->second; + return false; } - result["valid"] = true; - result["finished"] = session->isFinished(); - result["hasError"] = session->hasError(); - result["hasData"] = session->hasChunks(); - - if (session->hasError()) { - result["error"] = session->getError(); - } + // Check if a stream session is finished + static sol::table httpStreamStatus(sol::this_state ts, int sessionId) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); + + std::shared_ptr session; + { + std::lock_guard lock(g_sessionsMutex); + auto it = g_streamSessions.find(sessionId); + if (it == g_streamSessions.end()) { + result["valid"] = false; + result["finished"] = true; + return result; + } + session = it->second; + } - if (session->isFinished()) { - result["status"] = session->getStatusCode(); - } + result["valid"] = true; + result["finished"] = session->isFinished(); + result["hasError"] = session->hasError(); + result["hasData"] = session->hasChunks(); - return result; -} + if (session->hasError()) { + result["error"] = session->getError(); + } + if (session->isFinished()) { + result["status"] = session->getStatusCode(); + } + return result; + } -void registerHttp(sol::state& lua) { - sol::table http = lua.create_named_table("http"); + void registerHttp(sol::state& lua) { + sol::table http = lua.create_named_table("http"); - // Basic HTTP methods - http["get"] = httpGet; - http["post"] = httpPost; - http["postJson"] = httpPostJson; + // Basic HTTP methods + http["get"] = httpGet; + http["post"] = httpPost; + http["postJson"] = httpPostJson; - // Streaming HTTP methods - http["postStream"] = httpPostStream; - http["postJsonStream"] = httpPostJsonStream; - http["streamRead"] = httpStreamRead; - http["streamClose"] = httpStreamClose; - http["streamStatus"] = httpStreamStatus; -} + // Streaming HTTP methods + http["postStream"] = httpPostStream; + http["postJsonStream"] = httpPostJsonStream; + http["streamRead"] = httpStreamRead; + http["streamClose"] = httpStreamClose; + http["streamStatus"] = httpStreamStatus; + } } // namespace LuaAPI diff --git a/source/lua/lua_api_http.h b/source/lua/lua_api_http.h index 7671c3e98..ac6b58bc2 100644 --- a/source/lua/lua_api_http.h +++ b/source/lua/lua_api_http.h @@ -23,7 +23,7 @@ namespace LuaAPI { -void registerHttp(sol::state& lua); + void registerHttp(sol::state& lua); } // namespace LuaAPI diff --git a/source/lua/lua_api_image.cpp b/source/lua/lua_api_image.cpp index e2ba9c3ef..3520cce82 100644 --- a/source/lua/lua_api_image.cpp +++ b/source/lua/lua_api_image.cpp @@ -23,107 +23,113 @@ namespace LuaAPI { -LuaImage::LuaImage() : spriteId(0), spriteSource(false) { - // Empty image -} + LuaImage::LuaImage() : + spriteId(0), spriteSource(false) { + // Empty image + } -LuaImage::LuaImage(const std::string& path) : filePath(path), spriteId(0), spriteSource(false) { - if (!path.empty()) { - image.LoadFile(wxString(path)); + LuaImage::LuaImage(const std::string& path) : + filePath(path), spriteId(0), spriteSource(false) { + if (!path.empty()) { + image.LoadFile(wxString(path)); + } } -} - -LuaImage::LuaImage(int id, bool isItemSprite) : spriteId(id), spriteSource(true) { - if (isItemSprite) { - // Get sprite ID from item type - if (g_items.typeExists(id)) { - ItemType& itemType = g_items.getItemType(id); - if (itemType.id != 0) { - loadFromSpriteId(itemType.clientID); + + LuaImage::LuaImage(int id, bool isItemSprite) : + spriteId(id), spriteSource(true) { + if (isItemSprite) { + // Get sprite ID from item type + if (g_items.typeExists(id)) { + ItemType& itemType = g_items.getItemType(id); + if (itemType.id != 0) { + loadFromSpriteId(itemType.clientID); + } } + } else { + loadFromSpriteId(id); } - } else { - loadFromSpriteId(id); } -} - -LuaImage::LuaImage(const LuaImage& other) : - image(other.image.IsOk() ? other.image.Copy() : wxImage()), - filePath(other.filePath), - spriteId(other.spriteId), - spriteSource(other.spriteSource) { -} - -LuaImage::~LuaImage() { - // wxImage handles its own cleanup -} - -LuaImage LuaImage::loadFromFile(const std::string& path) { - return LuaImage(path); -} - -LuaImage LuaImage::loadFromItemSprite(int itemId) { - return LuaImage(itemId, true); -} - -LuaImage LuaImage::loadFromSprite(int spriteId) { - return LuaImage(spriteId, false); -} - -// Helper to reliably get sprite ID from item ID, handling client ID mapping -int getItemSpriteId(int itemId) { - if (!g_items.typeExists(itemId)) return 0; - ItemType& itemType = g_items.getItemType(itemId); - return itemType.clientID; -} - -void LuaImage::loadFromSpriteId(int id) { - Sprite* sprite = g_gui.gfx.getSprite(id); - if (!sprite) { - return; + + LuaImage::LuaImage(const LuaImage& other) : + image(other.image.IsOk() ? other.image.Copy() : wxImage()), + filePath(other.filePath), + spriteId(other.spriteId), + spriteSource(other.spriteSource) { + } + + LuaImage::~LuaImage() { + // wxImage handles its own cleanup + } + + LuaImage LuaImage::loadFromFile(const std::string& path) { + return LuaImage(path); + } + + LuaImage LuaImage::loadFromItemSprite(int itemId) { + return LuaImage(itemId, true); } - GameSprite* gameSprite = dynamic_cast(sprite); - if (gameSprite && gameSprite->width > 0 && gameSprite->height > 0) { - // Calculate full sprite size (can be larger than 32x32 for multi-tile sprites) - int spriteWidth = gameSprite->width * 32; - int spriteHeight = gameSprite->height * 32; - - // Create image with alpha channel - image.Create(spriteWidth, spriteHeight); - image.InitAlpha(); - - // Fill with transparent background - unsigned char* imgData = image.GetData(); - unsigned char* alphaData = image.GetAlpha(); - memset(imgData, 0, spriteWidth * spriteHeight * 3); - memset(alphaData, 0, spriteWidth * spriteHeight); // Fully transparent - - // Get sprite data for each part - for (int y = 0; y < gameSprite->height; ++y) { - for (int x = 0; x < gameSprite->width; ++x) { - int spriteIndex = gameSprite->getIndex(x, y, 0, 0, 0, 0, 0); - if (spriteIndex >= 0 && spriteIndex < (int)gameSprite->spriteList.size()) { - auto* normalImage = gameSprite->spriteList[spriteIndex]; - if (normalImage) { - uint8_t* rgbaData = normalImage->getRGBAData(); - if (rgbaData) { - // Copy pixel data to the correct position - int destX = (gameSprite->width - 1 - x) * 32; - int destY = (gameSprite->height - 1 - y) * 32; - - for (int py = 0; py < 32; ++py) { - for (int px = 0; px < 32; ++px) { - int srcIdx = (py * 32 + px) * 4; - int destIdx = (destY + py) * spriteWidth + (destX + px); - - // Only copy non-transparent pixels - uint8_t alpha = rgbaData[srcIdx + 3]; - if (alpha > 0) { - imgData[destIdx * 3 + 0] = rgbaData[srcIdx + 0]; // R - imgData[destIdx * 3 + 1] = rgbaData[srcIdx + 1]; // G - imgData[destIdx * 3 + 2] = rgbaData[srcIdx + 2]; // B - alphaData[destIdx] = alpha; + LuaImage LuaImage::loadFromSprite(int spriteId) { + return LuaImage(spriteId, false); + } + + // Helper to reliably get sprite ID from item ID, handling client ID mapping + int getItemSpriteId(int itemId) { + if (!g_items.typeExists(itemId)) { + return 0; + } + ItemType& itemType = g_items.getItemType(itemId); + return itemType.clientID; + } + + void LuaImage::loadFromSpriteId(int id) { + Sprite* sprite = g_gui.gfx.getSprite(id); + if (!sprite) { + return; + } + + GameSprite* gameSprite = dynamic_cast(sprite); + if (gameSprite && gameSprite->width > 0 && gameSprite->height > 0) { + // Calculate full sprite size (can be larger than 32x32 for multi-tile sprites) + int spriteWidth = gameSprite->width * 32; + int spriteHeight = gameSprite->height * 32; + + // Create image with alpha channel + image.Create(spriteWidth, spriteHeight); + image.InitAlpha(); + + // Fill with transparent background + unsigned char* imgData = image.GetData(); + unsigned char* alphaData = image.GetAlpha(); + memset(imgData, 0, spriteWidth * spriteHeight * 3); + memset(alphaData, 0, spriteWidth * spriteHeight); // Fully transparent + + // Get sprite data for each part + for (int y = 0; y < gameSprite->height; ++y) { + for (int x = 0; x < gameSprite->width; ++x) { + int spriteIndex = gameSprite->getIndex(x, y, 0, 0, 0, 0, 0); + if (spriteIndex >= 0 && spriteIndex < (int)gameSprite->spriteList.size()) { + auto* normalImage = gameSprite->spriteList[spriteIndex]; + if (normalImage) { + uint8_t* rgbaData = normalImage->getRGBAData(); + if (rgbaData) { + // Copy pixel data to the correct position + int destX = (gameSprite->width - 1 - x) * 32; + int destY = (gameSprite->height - 1 - y) * 32; + + for (int py = 0; py < 32; ++py) { + for (int px = 0; px < 32; ++px) { + int srcIdx = (py * 32 + px) * 4; + int destIdx = (destY + py) * spriteWidth + (destX + px); + + // Only copy non-transparent pixels + uint8_t alpha = rgbaData[srcIdx + 3]; + if (alpha > 0) { + imgData[destIdx * 3 + 0] = rgbaData[srcIdx + 0]; // R + imgData[destIdx * 3 + 1] = rgbaData[srcIdx + 1]; // G + imgData[destIdx * 3 + 2] = rgbaData[srcIdx + 2]; // B + alphaData[destIdx] = alpha; + } } } } @@ -131,136 +137,131 @@ void LuaImage::loadFromSpriteId(int id) { } } } + return; } - return; + + // Fallback: use DC-based rendering + wxBitmap bmp(32, 32, 32); + wxMemoryDC dc(bmp); + dc.SetBackground(*wxWHITE_BRUSH); + dc.Clear(); + sprite->DrawTo(&dc, SPRITE_SIZE_32x32, 0, 0, 32, 32); + dc.SelectObject(wxNullBitmap); + image = bmp.ConvertToImage(); } - // Fallback: use DC-based rendering - wxBitmap bmp(32, 32, 32); - wxMemoryDC dc(bmp); - dc.SetBackground(*wxWHITE_BRUSH); - dc.Clear(); - sprite->DrawTo(&dc, SPRITE_SIZE_32x32, 0, 0, 32, 32); - dc.SelectObject(wxNullBitmap); - image = bmp.ConvertToImage(); -} - -int LuaImage::getWidth() const { - return image.IsOk() ? image.GetWidth() : 0; -} - -int LuaImage::getHeight() const { - return image.IsOk() ? image.GetHeight() : 0; -} - -bool LuaImage::isValid() const { - return image.IsOk(); -} - -LuaImage LuaImage::resize(int width, int height, bool smooth) const { - LuaImage result; - if (image.IsOk() && width > 0 && height > 0) { - wxImageResizeQuality quality = smooth ? wxIMAGE_QUALITY_HIGH : wxIMAGE_QUALITY_NEAREST; - result.image = image.Scale(width, height, quality); - result.filePath = filePath; - result.spriteId = spriteId; - result.spriteSource = spriteSource; + int LuaImage::getWidth() const { + return image.IsOk() ? image.GetWidth() : 0; } - return result; -} -LuaImage LuaImage::scale(double factor, bool smooth) const { - if (factor <= 0 || !image.IsOk()) { - return LuaImage(); + int LuaImage::getHeight() const { + return image.IsOk() ? image.GetHeight() : 0; } - int newWidth = static_cast(image.GetWidth() * factor); - int newHeight = static_cast(image.GetHeight() * factor); - return resize(newWidth, newHeight, smooth); -} - -wxBitmap LuaImage::getBitmap() const { - if (!image.IsOk()) { - return wxNullBitmap; + + bool LuaImage::isValid() const { + return image.IsOk(); } - return wxBitmap(image); -} -wxBitmap LuaImage::getBitmap(int width, int height, bool smooth) const { - if (!image.IsOk() || width <= 0 || height <= 0) { - return wxNullBitmap; + LuaImage LuaImage::resize(int width, int height, bool smooth) const { + LuaImage result; + if (image.IsOk() && width > 0 && height > 0) { + wxImageResizeQuality quality = smooth ? wxIMAGE_QUALITY_HIGH : wxIMAGE_QUALITY_NEAREST; + result.image = image.Scale(width, height, quality); + result.filePath = filePath; + result.spriteId = spriteId; + result.spriteSource = spriteSource; + } + return result; } - wxImageResizeQuality quality = smooth ? wxIMAGE_QUALITY_HIGH : wxIMAGE_QUALITY_NEAREST; - wxImage scaled = image.Scale(width, height, quality); - return wxBitmap(scaled); -} - -bool LuaImage::operator==(const LuaImage& other) const { - // Compare by path if file-based, or sprite ID if sprite-based - if (spriteSource && other.spriteSource) { - return spriteId == other.spriteId; + + LuaImage LuaImage::scale(double factor, bool smooth) const { + if (factor <= 0 || !image.IsOk()) { + return LuaImage(); + } + int newWidth = static_cast(image.GetWidth() * factor); + int newHeight = static_cast(image.GetHeight() * factor); + return resize(newWidth, newHeight, smooth); } - if (!spriteSource && !other.spriteSource) { - return filePath == other.filePath; + + wxBitmap LuaImage::getBitmap() const { + if (!image.IsOk()) { + return wxNullBitmap; + } + return wxBitmap(image); } - return false; -} - -void registerImage(sol::state& lua) { - // Register LuaImage as "Image" usertype - lua.new_usertype("Image", - // Constructors - sol::constructors< - LuaImage(), - LuaImage(const std::string&) - >(), - - // Alternative constructor from table: Image{path = "..."} or Image{itemid = 100} - sol::call_constructor, sol::factories( - // Default constructor - []() { return LuaImage(); }, - // Path constructor - [](const std::string& path) { return LuaImage(path); }, - // Table constructor - [](sol::table t) { - if (t["path"].valid()) { - return LuaImage::loadFromFile(t.get("path")); - } - if (t["itemid"].valid()) { - return LuaImage::loadFromItemSprite(t.get("itemid")); - } - if (t["spriteid"].valid()) { - return LuaImage::loadFromSprite(t.get("spriteid")); - } - return LuaImage(); - } - ), - - // Static factory methods - "fromFile", &LuaImage::loadFromFile, - "fromItemSprite", &LuaImage::loadFromItemSprite, - "fromSprite", &LuaImage::loadFromSprite, - - // Properties (read-only) - "width", sol::property(&LuaImage::getWidth), - "height", sol::property(&LuaImage::getHeight), - "valid", sol::property(&LuaImage::isValid), - "path", sol::property(&LuaImage::getPath), - "spriteId", sol::property(&LuaImage::getSpriteId), - "isFromSprite", sol::property(&LuaImage::isSpriteSource), - - // Methods - "resize", [](const LuaImage& img, int w, int h, sol::optional smooth) { - return img.resize(w, h, smooth.value_or(true)); - }, - "scale", [](const LuaImage& img, double factor, sol::optional smooth) { - return img.scale(factor, smooth.value_or(true)); - }, - - // Equality comparison - sol::meta_function::equal_to, &LuaImage::operator==, - - // String representation - sol::meta_function::to_string, [](const LuaImage& img) { + + wxBitmap LuaImage::getBitmap(int width, int height, bool smooth) const { + if (!image.IsOk() || width <= 0 || height <= 0) { + return wxNullBitmap; + } + wxImageResizeQuality quality = smooth ? wxIMAGE_QUALITY_HIGH : wxIMAGE_QUALITY_NEAREST; + wxImage scaled = image.Scale(width, height, quality); + return wxBitmap(scaled); + } + + bool LuaImage::operator==(const LuaImage& other) const { + // Compare by path if file-based, or sprite ID if sprite-based + if (spriteSource && other.spriteSource) { + return spriteId == other.spriteId; + } + if (!spriteSource && !other.spriteSource) { + return filePath == other.filePath; + } + return false; + } + + void registerImage(sol::state& lua) { + // Register LuaImage as "Image" usertype + lua.new_usertype( + "Image", + // Constructors + sol::constructors< + LuaImage(), + LuaImage(const std::string&)>(), + + // Alternative constructor from table: Image{path = "..."} or Image{itemid = 100} + sol::call_constructor, sol::factories( + // Default constructor + []() { return LuaImage(); }, + // Path constructor + [](const std::string& path) { return LuaImage(path); }, + // Table constructor + [](sol::table t) { + if (t["path"].valid()) { + return LuaImage::loadFromFile(t.get("path")); + } + if (t["itemid"].valid()) { + return LuaImage::loadFromItemSprite(t.get("itemid")); + } + if (t["spriteid"].valid()) { + return LuaImage::loadFromSprite(t.get("spriteid")); + } + return LuaImage(); + } + ), + + // Static factory methods + "fromFile", &LuaImage::loadFromFile, + "fromItemSprite", &LuaImage::loadFromItemSprite, + "fromSprite", &LuaImage::loadFromSprite, + + // Properties (read-only) + "width", sol::property(&LuaImage::getWidth), + "height", sol::property(&LuaImage::getHeight), + "valid", sol::property(&LuaImage::isValid), + "path", sol::property(&LuaImage::getPath), + "spriteId", sol::property(&LuaImage::getSpriteId), + "isFromSprite", sol::property(&LuaImage::isSpriteSource), + + // Methods + "resize", [](const LuaImage& img, int w, int h, sol::optional smooth) { return img.resize(w, h, smooth.value_or(true)); }, + "scale", [](const LuaImage& img, double factor, sol::optional smooth) { return img.scale(factor, smooth.value_or(true)); }, + + // Equality comparison + sol::meta_function::equal_to, &LuaImage::operator==, + + // String representation + sol::meta_function::to_string, [](const LuaImage& img) { if (!img.isValid()) { return std::string("Image(invalid)"); } @@ -269,9 +270,8 @@ void registerImage(sol::state& lua) { ", " + std::to_string(img.getWidth()) + "x" + std::to_string(img.getHeight()) + ")"; } return "Image(\"" + img.getPath() + "\", " + - std::to_string(img.getWidth()) + "x" + std::to_string(img.getHeight()) + ")"; - } - ); -} + std::to_string(img.getWidth()) + "x" + std::to_string(img.getHeight()) + ")"; } + ); + } } // namespace LuaAPI diff --git a/source/lua/lua_api_image.h b/source/lua/lua_api_image.h index f18400a8d..5213a4140 100644 --- a/source/lua/lua_api_image.h +++ b/source/lua/lua_api_image.h @@ -25,61 +25,69 @@ namespace LuaAPI { -// LuaImage class for Lua scripting -// Supports loading external images and game item sprites -class LuaImage { -public: - // Default constructor (empty image) - LuaImage(); - - // Constructor from file path - explicit LuaImage(const std::string& path); - - // Constructor from item sprite ID - LuaImage(int spriteId, bool isItemSprite); - - // Copy constructor - LuaImage(const LuaImage& other); - - ~LuaImage(); - - // Static factory methods for Lua - static LuaImage loadFromFile(const std::string& path); - static LuaImage loadFromItemSprite(int itemId); - static LuaImage loadFromSprite(int spriteId); - - // Properties - int getWidth() const; - int getHeight() const; - bool isValid() const; - std::string getPath() const { return filePath; } - int getSpriteId() const { return spriteId; } - bool isSpriteSource() const { return spriteSource; } - - // Operations - LuaImage resize(int width, int height, bool smooth = true) const; - LuaImage scale(double factor, bool smooth = true) const; - - // Get the underlying wxImage (for internal use) - const wxImage& getWxImage() const { return image; } - wxBitmap getBitmap() const; - wxBitmap getBitmap(int width, int height, bool smooth = true) const; - - // Equality comparison - bool operator==(const LuaImage& other) const; - -private: - wxImage image; - std::string filePath; - int spriteId = 0; - bool spriteSource = false; - - // Load image from game sprite - void loadFromSpriteId(int id); -}; - -// Register the Image usertype with Lua -void registerImage(sol::state& lua); + // LuaImage class for Lua scripting + // Supports loading external images and game item sprites + class LuaImage { + public: + // Default constructor (empty image) + LuaImage(); + + // Constructor from file path + explicit LuaImage(const std::string& path); + + // Constructor from item sprite ID + LuaImage(int spriteId, bool isItemSprite); + + // Copy constructor + LuaImage(const LuaImage& other); + + ~LuaImage(); + + // Static factory methods for Lua + static LuaImage loadFromFile(const std::string& path); + static LuaImage loadFromItemSprite(int itemId); + static LuaImage loadFromSprite(int spriteId); + + // Properties + int getWidth() const; + int getHeight() const; + bool isValid() const; + std::string getPath() const { + return filePath; + } + int getSpriteId() const { + return spriteId; + } + bool isSpriteSource() const { + return spriteSource; + } + + // Operations + LuaImage resize(int width, int height, bool smooth = true) const; + LuaImage scale(double factor, bool smooth = true) const; + + // Get the underlying wxImage (for internal use) + const wxImage& getWxImage() const { + return image; + } + wxBitmap getBitmap() const; + wxBitmap getBitmap(int width, int height, bool smooth = true) const; + + // Equality comparison + bool operator==(const LuaImage& other) const; + + private: + wxImage image; + std::string filePath; + int spriteId = 0; + bool spriteSource = false; + + // Load image from game sprite + void loadFromSpriteId(int id); + }; + + // Register the Image usertype with Lua + void registerImage(sol::state& lua); } // namespace LuaAPI diff --git a/source/lua/lua_api_item.cpp b/source/lua/lua_api_item.cpp index 3c2c20d66..8237e632c 100644 --- a/source/lua/lua_api_item.cpp +++ b/source/lua/lua_api_item.cpp @@ -25,229 +25,194 @@ namespace LuaAPI { -// Helper: convert string to lowercase -static std::string toLower(const std::string& str) { - std::string result = str; - std::transform(result.begin(), result.end(), result.begin(), - [](unsigned char c) { return std::tolower(c); }); - return result; -} - -// Helper: check if string contains another (case-insensitive) -static bool containsIgnoreCase(const std::string& haystack, const std::string& needle) { - std::string lowerHaystack = toLower(haystack); - std::string lowerNeedle = toLower(needle); - return lowerHaystack.find(lowerNeedle) != std::string::npos; -} - -void registerItem(sol::state& lua) { - // Register Item usertype - lua.new_usertype("Item", - // No public constructor - items are created via Tile:addItem() or obtained from tiles - sol::no_constructor, - - // Read-only properties - "id", sol::property(&Item::getID), - "clientId", sol::property(&Item::getClientID), - "name", sol::property(&Item::getName), - "fullName", sol::property(&Item::getFullName), - - // Read/write properties - "count", sol::property( - &Item::getCount, - [](Item& item, int count) { + // Helper: convert string to lowercase + static std::string toLower(const std::string& str) { + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return std::tolower(c); }); + return result; + } + + // Helper: check if string contains another (case-insensitive) + static bool containsIgnoreCase(const std::string& haystack, const std::string& needle) { + std::string lowerHaystack = toLower(haystack); + std::string lowerNeedle = toLower(needle); + return lowerHaystack.find(lowerNeedle) != std::string::npos; + } + + void registerItem(sol::state& lua) { + // Register Item usertype + lua.new_usertype( + "Item", + // No public constructor - items are created via Tile:addItem() or obtained from tiles + sol::no_constructor, + + // Read-only properties + "id", sol::property(&Item::getID), + "clientId", sol::property(&Item::getClientID), + "name", sol::property(&Item::getName), + "fullName", sol::property(&Item::getFullName), + + // Read/write properties + "count", sol::property(&Item::getCount, [](Item& item, int count) { item.setSubtype(static_cast(count)); - } - ), - "subtype", sol::property( - [](const Item& item) -> int { return item.getSubtype(); }, - [](Item& item, int subtype) { item.setSubtype(static_cast(subtype)); } - ), - "actionId", sol::property( - [](const Item& item) -> int { return item.getActionID(); }, - [](Item& item, int aid) { item.setActionID(static_cast(aid)); } - ), - "uniqueId", sol::property( - [](const Item& item) -> int { return item.getUniqueID(); }, - [](Item& item, int uid) { item.setUniqueID(static_cast(uid)); } - ), - "tier", sol::property( - [](const Item& item) -> int { return item.getTier(); }, - [](Item& item, int tier) { item.setTier(static_cast(tier)); } - ), - "text", sol::property(&Item::getText, &Item::setText), - "description", sol::property(&Item::getDescription, &Item::setDescription), - - // Selection - "isSelected", sol::property(&Item::isSelected), - "select", &Item::select, - "deselect", &Item::deselect, - - // Type checks (read-only) - "isStackable", sol::property(&Item::isStackable), - "isMoveable", sol::property(&Item::isMoveable), - "isPickupable", sol::property(&Item::isPickupable), - "isBlocking", sol::property(&Item::isBlocking), - "isGroundTile", sol::property(&Item::isGroundTile), - "isBorder", sol::property(&Item::isBorder), - "isWall", sol::property(&Item::isWall), - "isDoor", sol::property(&Item::isDoor), - "isTable", sol::property(&Item::isTable), - "isCarpet", sol::property(&Item::isCarpet), - "isHangable", sol::property(&Item::isHangable), - "isRoteable", sol::property(&Item::isRoteable), - "isFluidContainer", sol::property(&Item::isFluidContainer), - "isSplash", sol::property(&Item::isSplash), - "hasCharges", sol::property(&Item::hasCharges), - "hasElevation", sol::property([](const Item& item) { - return g_items[item.getID()].hasElevation; - }), - "zOrder", sol::property(&Item::getTopOrder), - - // Methods - "clone", [](const Item& item) -> Item* { - return item.deepCopy(); - }, - "rotate", &Item::doRotate, - - - "getName", [](int id) -> std::string { + }), + "subtype", sol::property([](const Item& item) -> int { return item.getSubtype(); }, [](Item& item, int subtype) { item.setSubtype(static_cast(subtype)); }), "actionId", sol::property([](const Item& item) -> int { return item.getActionID(); }, [](Item& item, int aid) { item.setActionID(static_cast(aid)); }), "uniqueId", sol::property([](const Item& item) -> int { return item.getUniqueID(); }, [](Item& item, int uid) { item.setUniqueID(static_cast(uid)); }), "tier", sol::property([](const Item& item) -> int { return item.getTier(); }, [](Item& item, int tier) { item.setTier(static_cast(tier)); }), "text", sol::property(&Item::getText, &Item::setText), "description", sol::property(&Item::getDescription, &Item::setDescription), + + // Selection + "isSelected", sol::property(&Item::isSelected), "select", &Item::select, "deselect", &Item::deselect, + + // Type checks (read-only) + "isStackable", sol::property(&Item::isStackable), "isMoveable", sol::property(&Item::isMoveable), "isPickupable", sol::property(&Item::isPickupable), "isBlocking", sol::property(&Item::isBlocking), "isGroundTile", sol::property(&Item::isGroundTile), "isBorder", sol::property(&Item::isBorder), "isWall", sol::property(&Item::isWall), "isDoor", sol::property(&Item::isDoor), "isTable", sol::property(&Item::isTable), "isCarpet", sol::property(&Item::isCarpet), "isHangable", sol::property(&Item::isHangable), "isRoteable", sol::property(&Item::isRoteable), "isFluidContainer", sol::property(&Item::isFluidContainer), "isSplash", sol::property(&Item::isSplash), "hasCharges", sol::property(&Item::hasCharges), "hasElevation", sol::property([](const Item& item) { + return g_items[item.getID()].hasElevation; + }), + "zOrder", sol::property(&Item::getTopOrder), + + // Methods + "clone", [](const Item& item) -> Item* { return item.deepCopy(); }, "rotate", &Item::doRotate, + + "getName", [](int id) -> std::string { if (g_items.typeExists(id)) { return g_items.getItemType(id).name; } - return ""; - }, - "getDescription", [](int id) -> std::string { + return ""; }, "getDescription", [](int id) -> std::string { if (g_items.typeExists(id)) { return g_items.getItemType(id).description; } - return ""; - }, + return ""; }, - // String representation - sol::meta_function::to_string, [](const Item& item) { - return "Item(id=" + std::to_string(item.getID()) + - ", name=\"" + item.getName() + "\")"; - } - ); + // String representation + sol::meta_function::to_string, [](const Item& item) { return "Item(id=" + std::to_string(item.getID()) + ", name=\"" + item.getName() + "\")"; } + ); - // Register Items namespace for item lookup - sol::table items = lua.create_named_table("Items"); - - // Get item type info by ID - items["get"] = [](int id) -> sol::optional { - if (!g_items.typeExists(id)) { - return sol::nullopt; - } - // Return item info as table (not the actual ItemType pointer) - return sol::nullopt; // We'll use getInfo instead - }; - - // Get item info by ID - returns a table with item properties - items["getInfo"] = [](sol::this_state ts, int id) -> sol::object { - sol::state_view lua(ts); - if (!g_items.typeExists(id)) { - return sol::nil; - } - ItemType& it = g_items.getItemType(id); - sol::table info = lua.create_table(); - info["id"] = it.id; - info["clientId"] = it.clientID; - info["name"] = it.name; - info["description"] = it.description; - info["isStackable"] = it.stackable; - info["isMoveable"] = it.moveable; - info["isPickupable"] = it.pickupable; - info["isGroundTile"] = it.isGroundTile(); - info["isBorder"] = it.isBorder; - info["isWall"] = it.isWall; - info["isDoor"] = it.isDoor(); - info["isTable"] = it.isTable; - info["isCarpet"] = it.isCarpet; - info["hasElevation"] = it.hasElevation; - return info; - }; - - // Check if item ID exists - items["exists"] = [](int id) -> bool { - return g_items.typeExists(id); - }; - - // Get max item ID - items["getMaxId"] = []() -> int { - return g_items.getMaxID(); - }; - - // Find items by name (returns array of {id, name} tables) - // Searches for items whose name contains the search string (case-insensitive) - items["findByName"] = [](sol::this_state ts, const std::string& searchName, sol::optional maxResults) -> sol::table { - sol::state_view lua(ts); - sol::table results = lua.create_table(); - - int limit = maxResults.value_or(50); // Default max 50 results - int count = 0; - int maxId = g_items.getMaxID(); - - std::string searchLower = toLower(searchName); - - for (int id = 1; id <= maxId && count < limit; ++id) { - if (!g_items.typeExists(id)) continue; + // Register Items namespace for item lookup + sol::table items = lua.create_named_table("Items"); + // Get item type info by ID + items["get"] = [](int id) -> sol::optional { + if (!g_items.typeExists(id)) { + return sol::nullopt; + } + // Return item info as table (not the actual ItemType pointer) + return sol::nullopt; // We'll use getInfo instead + }; + + // Get item info by ID - returns a table with item properties + items["getInfo"] = [](sol::this_state ts, int id) -> sol::object { + sol::state_view lua(ts); + if (!g_items.typeExists(id)) { + return sol::nil; + } ItemType& it = g_items.getItemType(id); - if (it.name.empty()) continue; - - // Check if name contains search string - if (containsIgnoreCase(it.name, searchName)) { - sol::table item = lua.create_table(); - item["id"] = it.id; - item["name"] = it.name; - results[++count] = item; + sol::table info = lua.create_table(); + info["id"] = it.id; + info["clientId"] = it.clientID; + info["name"] = it.name; + info["description"] = it.description; + info["isStackable"] = it.stackable; + info["isMoveable"] = it.moveable; + info["isPickupable"] = it.pickupable; + info["isGroundTile"] = it.isGroundTile(); + info["isBorder"] = it.isBorder; + info["isWall"] = it.isWall; + info["isDoor"] = it.isDoor(); + info["isTable"] = it.isTable; + info["isCarpet"] = it.isCarpet; + info["hasElevation"] = it.hasElevation; + return info; + }; + + // Check if item ID exists + items["exists"] = [](int id) -> bool { + return g_items.typeExists(id); + }; + + // Get max item ID + items["getMaxId"] = []() -> int { + return g_items.getMaxID(); + }; + + // Find items by name (returns array of {id, name} tables) + // Searches for items whose name contains the search string (case-insensitive) + items["findByName"] = [](sol::this_state ts, const std::string& searchName, sol::optional maxResults) -> sol::table { + sol::state_view lua(ts); + sol::table results = lua.create_table(); + + int limit = maxResults.value_or(50); // Default max 50 results + int count = 0; + int maxId = g_items.getMaxID(); + + std::string searchLower = toLower(searchName); + + for (int id = 1; id <= maxId && count < limit; ++id) { + if (!g_items.typeExists(id)) { + continue; + } + + ItemType& it = g_items.getItemType(id); + if (it.name.empty()) { + continue; + } + + // Check if name contains search string + if (containsIgnoreCase(it.name, searchName)) { + sol::table item = lua.create_table(); + item["id"] = it.id; + item["name"] = it.name; + results[++count] = item; + } } - } - return results; - }; + return results; + }; + + // Find first item matching name exactly (case-insensitive) + // Returns item ID or nil + items["findIdByName"] = [](const std::string& searchName) -> sol::optional { + std::string searchLower = toLower(searchName); + int maxId = g_items.getMaxID(); + + // First pass: exact match + for (int id = 1; id <= maxId; ++id) { + if (!g_items.typeExists(id)) { + continue; + } + + ItemType& it = g_items.getItemType(id); + if (it.name.empty()) { + continue; + } + + if (toLower(it.name) == searchLower) { + return id; + } + } - // Find first item matching name exactly (case-insensitive) - // Returns item ID or nil - items["findIdByName"] = [](const std::string& searchName) -> sol::optional { - std::string searchLower = toLower(searchName); - int maxId = g_items.getMaxID(); + // Second pass: contains match (return first) + for (int id = 1; id <= maxId; ++id) { + if (!g_items.typeExists(id)) { + continue; + } - // First pass: exact match - for (int id = 1; id <= maxId; ++id) { - if (!g_items.typeExists(id)) continue; + ItemType& it = g_items.getItemType(id); + if (it.name.empty()) { + continue; + } - ItemType& it = g_items.getItemType(id); - if (it.name.empty()) continue; - - if (toLower(it.name) == searchLower) { - return id; + if (containsIgnoreCase(it.name, searchName)) { + return id; + } } - } - - // Second pass: contains match (return first) - for (int id = 1; id <= maxId; ++id) { - if (!g_items.typeExists(id)) continue; - ItemType& it = g_items.getItemType(id); - if (it.name.empty()) continue; + return sol::nullopt; + }; - if (containsIgnoreCase(it.name, searchName)) { - return id; + // Get item name by ID + items["getName"] = [](int id) -> std::string { + if (g_items.typeExists(id)) { + return g_items.getItemType(id).name; } - } - - return sol::nullopt; - }; - - // Get item name by ID - items["getName"] = [](int id) -> std::string { - if (g_items.typeExists(id)) { - return g_items.getItemType(id).name; - } - return ""; - }; -} + return ""; + }; + } } // namespace LuaAPI diff --git a/source/lua/lua_api_json.cpp b/source/lua/lua_api_json.cpp index 81e0a1076..c7ed4adba 100644 --- a/source/lua/lua_api_json.cpp +++ b/source/lua/lua_api_json.cpp @@ -8,165 +8,170 @@ namespace LuaAPI { -// Forward declarations -sol::object valueToLua(const json_spirit::mValue& val, sol::state_view& lua); -json_spirit::mValue luaToValue(const sol::object& obj); - -// Convert JSON Spirit Value to Lua Object -sol::object valueToLua(const json_spirit::mValue& val, sol::state_view& lua) { - switch (val.type()) { - case json_spirit::null_type: - return sol::nil; - case json_spirit::bool_type: - return sol::make_object(lua, val.get_bool()); - case json_spirit::int_type: - return sol::make_object(lua, val.get_int()); - case json_spirit::real_type: - return sol::make_object(lua, val.get_real()); - case json_spirit::str_type: - return sol::make_object(lua, val.get_str()); - case json_spirit::array_type: { - sol::table t = lua.create_table(); - const json_spirit::mArray& arr = val.get_array(); - for (size_t i = 0; i < arr.size(); ++i) { - t[i + 1] = valueToLua(arr[i], lua); // Lua 1-based indexing + // Forward declarations + sol::object valueToLua(const json_spirit::mValue& val, sol::state_view& lua); + json_spirit::mValue luaToValue(const sol::object& obj); + + // Convert JSON Spirit Value to Lua Object + sol::object valueToLua(const json_spirit::mValue& val, sol::state_view& lua) { + switch (val.type()) { + case json_spirit::null_type: + return sol::nil; + case json_spirit::bool_type: + return sol::make_object(lua, val.get_bool()); + case json_spirit::int_type: + return sol::make_object(lua, val.get_int()); + case json_spirit::real_type: + return sol::make_object(lua, val.get_real()); + case json_spirit::str_type: + return sol::make_object(lua, val.get_str()); + case json_spirit::array_type: { + sol::table t = lua.create_table(); + const json_spirit::mArray& arr = val.get_array(); + for (size_t i = 0; i < arr.size(); ++i) { + t[i + 1] = valueToLua(arr[i], lua); // Lua 1-based indexing + } + return t; } - return t; - } - case json_spirit::obj_type: { - sol::table t = lua.create_table(); - const json_spirit::mObject& obj = val.get_obj(); - for (const auto& pair : obj) { - t[pair.first] = valueToLua(pair.second, lua); + case json_spirit::obj_type: { + sol::table t = lua.create_table(); + const json_spirit::mObject& obj = val.get_obj(); + for (const auto& pair : obj) { + t[pair.first] = valueToLua(pair.second, lua); + } + return t; } - return t; } + return sol::nil; } - return sol::nil; -} - -// Convert Lua Object to JSON Spirit Value -json_spirit::mValue luaToValue(const sol::object& obj) { - switch (obj.get_type()) { - case sol::type::nil: - return json_spirit::mValue(); // null - case sol::type::boolean: - return json_spirit::mValue(obj.as()); - case sol::type::number: - // check if int or double, but Lua 5.1/JIT uses double mostly. - // json_spirit distinguishes. - // Let's just use double if floating point, int otherwise? - // Simplification: use double for safety or check floor - { - double d = obj.as(); - if (d == (int64_t)d) { - return json_spirit::mValue((int64_t)d); + + // Convert Lua Object to JSON Spirit Value + json_spirit::mValue luaToValue(const sol::object& obj) { + switch (obj.get_type()) { + case sol::type::nil: + return json_spirit::mValue(); // null + case sol::type::boolean: + return json_spirit::mValue(obj.as()); + case sol::type::number: + // check if int or double, but Lua 5.1/JIT uses double mostly. + // json_spirit distinguishes. + // Let's just use double if floating point, int otherwise? + // Simplification: use double for safety or check floor + { + double d = obj.as(); + if (d == (int64_t)d) { + return json_spirit::mValue((int64_t)d); + } + return json_spirit::mValue(d); } - return json_spirit::mValue(d); - } - case sol::type::string: - return json_spirit::mValue(obj.as()); - case sol::type::table: { - sol::table t = obj.as(); - // Determine if it's an array or object - // Algorithm: allow explicit "is_array"? or check keys. - // For now, check if it has key 1. If so, treat as array? - // Or check length #t > 0. - // Robust check: iterate keys. If all are integers 1..N, it's an array. - - bool isArray = true; - size_t maxKey = 0; - size_t count = 0; - - for (auto& pair : t) { - count++; - if (pair.first.get_type() == sol::type::number) { - double k = pair.first.as(); - if (k >= 1 && k == (size_t)k) { - size_t idx = (size_t)k; - if (idx > maxKey) maxKey = idx; + case sol::type::string: + return json_spirit::mValue(obj.as()); + case sol::type::table: { + sol::table t = obj.as(); + // Determine if it's an array or object + // Algorithm: allow explicit "is_array"? or check keys. + // For now, check if it has key 1. If so, treat as array? + // Or check length #t > 0. + // Robust check: iterate keys. If all are integers 1..N, it's an array. + + bool isArray = true; + size_t maxKey = 0; + size_t count = 0; + + for (auto& pair : t) { + count++; + if (pair.first.get_type() == sol::type::number) { + double k = pair.first.as(); + if (k >= 1 && k == (size_t)k) { + size_t idx = (size_t)k; + if (idx > maxKey) { + maxKey = idx; + } + } else { + isArray = false; + break; + } } else { isArray = false; break; } - } else { - isArray = false; - break; } - } - if (isArray && count > 0) { - // We also check sparse arrays? - // If maxKey == count, it's a dense array. - if (maxKey != count) { - // Sparse array or holes... treat as object with string keys? - // Or just treat as sparse array? json_spirit doesn't support sparse arrays directly - // standard JSON doesn't either (uses nulls). - // Let's assume non-sparse for array. - // If holes, better fail to object string keys. - isArray = false; + if (isArray && count > 0) { + // We also check sparse arrays? + // If maxKey == count, it's a dense array. + if (maxKey != count) { + // Sparse array or holes... treat as object with string keys? + // Or just treat as sparse array? json_spirit doesn't support sparse arrays directly + // standard JSON doesn't either (uses nulls). + // Let's assume non-sparse for array. + // If holes, better fail to object string keys. + isArray = false; + } } - } - // Empty table {} -> Object by default in many JSON libs, or Array? - // Lua {} is ambiguous. Let's default to Object (empty dict) as it's safer. - if (count == 0) isArray = false; - - if (isArray) { - json_spirit::mArray arr; - arr.reserve(count); - for (size_t i = 1; i <= count; ++i) { - if (t[i].valid()) - arr.push_back(luaToValue(t[i])); - else - arr.push_back(json_spirit::mValue()); // null + // Empty table {} -> Object by default in many JSON libs, or Array? + // Lua {} is ambiguous. Let's default to Object (empty dict) as it's safer. + if (count == 0) { + isArray = false; } - return json_spirit::mValue(arr); - } else { - json_spirit::mObject objVal; - for (auto& pair : t) { - std::string key; - if (pair.first.get_type() == sol::type::string) { - key = pair.first.as(); - } else if (pair.first.get_type() == sol::type::number) { - key = std::to_string(pair.first.as()); // Convert number keys to string for JSON object - } else { - continue; // Skip function keys etc + + if (isArray) { + json_spirit::mArray arr; + arr.reserve(count); + for (size_t i = 1; i <= count; ++i) { + if (t[i].valid()) { + arr.push_back(luaToValue(t[i])); + } else { + arr.push_back(json_spirit::mValue()); // null + } + } + return json_spirit::mValue(arr); + } else { + json_spirit::mObject objVal; + for (auto& pair : t) { + std::string key; + if (pair.first.get_type() == sol::type::string) { + key = pair.first.as(); + } else if (pair.first.get_type() == sol::type::number) { + key = std::to_string(pair.first.as()); // Convert number keys to string for JSON object + } else { + continue; // Skip function keys etc + } + objVal[key] = luaToValue(pair.second); } - objVal[key] = luaToValue(pair.second); + return json_spirit::mValue(objVal); } - return json_spirit::mValue(objVal); } + default: + return json_spirit::mValue(); // null/ignore userdata/functions } - default: - return json_spirit::mValue(); // null/ignore userdata/functions } -} - -void registerJson(sol::state& lua) { - // Create "json" table - sol::table jsonTable = lua.create_table(); - - jsonTable.set_function("encode", [](sol::object obj, sol::this_state s) -> std::string { - json_spirit::mValue val = luaToValue(obj); - return json_spirit::write(val); // minified output - }); - - jsonTable.set_function("encode_pretty", [](sol::object obj, sol::this_state s) -> std::string { - json_spirit::mValue val = luaToValue(obj); - return json_spirit::write_formatted(val); // pretty-printed output - }); - - jsonTable.set_function("decode", [](std::string jsonStr, sol::this_state s) -> sol::object { - json_spirit::mValue val; - if (json_spirit::read(jsonStr, val)) { - sol::state_view lua(s); - return valueToLua(val, lua); - } - return sol::nil; - }); - lua["json"] = jsonTable; -} + void registerJson(sol::state& lua) { + // Create "json" table + sol::table jsonTable = lua.create_table(); + + jsonTable.set_function("encode", [](sol::object obj, sol::this_state s) -> std::string { + json_spirit::mValue val = luaToValue(obj); + return json_spirit::write(val); // minified output + }); + + jsonTable.set_function("encode_pretty", [](sol::object obj, sol::this_state s) -> std::string { + json_spirit::mValue val = luaToValue(obj); + return json_spirit::write_formatted(val); // pretty-printed output + }); + + jsonTable.set_function("decode", [](std::string jsonStr, sol::this_state s) -> sol::object { + json_spirit::mValue val; + if (json_spirit::read(jsonStr, val)) { + sol::state_view lua(s); + return valueToLua(val, lua); + } + return sol::nil; + }); + + lua["json"] = jsonTable; + } } // namespace LuaAPI diff --git a/source/lua/lua_api_json.h b/source/lua/lua_api_json.h index a8ab4b6d2..70e67696d 100644 --- a/source/lua/lua_api_json.h +++ b/source/lua/lua_api_json.h @@ -9,7 +9,7 @@ namespace LuaAPI { -void registerJson(sol::state& lua); + void registerJson(sol::state& lua); } diff --git a/source/lua/lua_api_map.cpp b/source/lua/lua_api_map.cpp index 856fa4032..11f76174f 100644 --- a/source/lua/lua_api_map.cpp +++ b/source/lua/lua_api_map.cpp @@ -26,120 +26,112 @@ namespace LuaAPI { -// Custom iterator for Map tiles to use in Lua for-loops -class LuaMapTileIterator { -public: - LuaMapTileIterator(Map* map) : map(map), started(false) { - if (map) { - iter = map->begin(); - endIter = map->end(); + // Custom iterator for Map tiles to use in Lua for-loops + class LuaMapTileIterator { + public: + LuaMapTileIterator(Map* map) : + map(map), started(false) { + if (map) { + iter = map->begin(); + endIter = map->end(); + } } - } - - std::tuple next(sol::this_state ts) { - sol::state_view lua(ts); - if (!map) { - return std::make_tuple(sol::nil, sol::nil); - } + std::tuple next(sol::this_state ts) { + sol::state_view lua(ts); - // Find next valid tile - while (iter != endIter) { - TileLocation* loc = *iter; - ++iter; - - if (loc && loc->get()) { - Tile* tile = loc->get(); - return std::make_tuple( - sol::make_object(lua, tile), - sol::make_object(lua, tile) - ); + if (!map) { + return std::make_tuple(sol::nil, sol::nil); } - } - return std::make_tuple(sol::nil, sol::nil); - } + // Find next valid tile + while (iter != endIter) { + TileLocation* loc = *iter; + ++iter; + + if (loc && loc->get()) { + Tile* tile = loc->get(); + return std::make_tuple( + sol::make_object(lua, tile), + sol::make_object(lua, tile) + ); + } + } -private: - Map* map; - MapIterator iter; - MapIterator endIter; - bool started; -}; - -// Iterator for Spawns -class LuaMapSpawnIterator { -public: - LuaMapSpawnIterator(Map* map) : map(map) { - if (map) { - iter = map->spawns.begin(); - endIter = map->spawns.end(); + return std::make_tuple(sol::nil, sol::nil); } - } - - Tile* next() { - if (!map) return nullptr; - if (iter != endIter) { - Position pos = *iter; - ++iter; - return map->getTile(pos); + private: + Map* map; + MapIterator iter; + MapIterator endIter; + bool started; + }; + + // Iterator for Spawns + class LuaMapSpawnIterator { + public: + LuaMapSpawnIterator(Map* map) : + map(map) { + if (map) { + iter = map->spawns.begin(); + endIter = map->spawns.end(); + } } - return nullptr; - } -private: - Map* map; - SpawnPositionList::const_iterator iter; - SpawnPositionList::const_iterator endIter; -}; - -void registerMap(sol::state& lua) { - // Register the iterator type - lua.new_usertype("MapTileIterator", - sol::no_constructor, - "next", &LuaMapTileIterator::next - ); - - // Register Spawn iterator - lua.new_usertype("MapSpawnIterator", - sol::no_constructor, - "next", &LuaMapSpawnIterator::next - ); - - // Register Map usertype - lua.new_usertype("Map", - // No public constructor - maps are obtained from app.map - sol::no_constructor, - - // Properties (read-only) - "name", sol::property(&Map::getName), - "filename", sol::property(&Map::getFilename), - "description", sol::property(&Map::getMapDescription), - "width", sol::property(&Map::getWidth), - "height", sol::property(&Map::getHeight), - "houseFilename", sol::property(&Map::getHouseFilename), - "spawnFilename", sol::property(&Map::getSpawnFilename), - "hasFile", sol::property(&Map::hasFile), - "hasChanged", sol::property(&Map::hasChanged), - "tileCount", sol::property([](Map* map) -> uint64_t { - return map ? map->getTileCount() : 0; - }), - - - // Get tile methods - "getTile", sol::overload( - [](Map* map, int x, int y, int z) -> Tile* { - return map ? map->getTile(x, y, z) : nullptr; - }, - [](Map* map, const Position& pos) -> Tile* { - return map ? map->getTile(pos) : nullptr; + Tile* next() { + if (!map) { + return nullptr; + } + + if (iter != endIter) { + Position pos = *iter; + ++iter; + return map->getTile(pos); } - ), + return nullptr; + } - // Get or create tile (for adding content to empty positions) - "getOrCreateTile", [](Map* map, sol::variadic_args va) -> Tile* { - if (!map) return nullptr; + private: + Map* map; + SpawnPositionList::const_iterator iter; + SpawnPositionList::const_iterator endIter; + }; + + void registerMap(sol::state& lua) { + // Register the iterator type + lua.new_usertype("MapTileIterator", sol::no_constructor, "next", &LuaMapTileIterator::next); + + // Register Spawn iterator + lua.new_usertype("MapSpawnIterator", sol::no_constructor, "next", &LuaMapSpawnIterator::next); + + // Register Map usertype + lua.new_usertype( + "Map", + // No public constructor - maps are obtained from app.map + sol::no_constructor, + + // Properties (read-only) + "name", sol::property(&Map::getName), + "filename", sol::property(&Map::getFilename), + "description", sol::property(&Map::getMapDescription), + "width", sol::property(&Map::getWidth), + "height", sol::property(&Map::getHeight), + "houseFilename", sol::property(&Map::getHouseFilename), + "spawnFilename", sol::property(&Map::getSpawnFilename), + "hasFile", sol::property(&Map::hasFile), + "hasChanged", sol::property(&Map::hasChanged), + "tileCount", sol::property([](Map* map) -> uint64_t { + return map ? map->getTileCount() : 0; + }), + + // Get tile methods + "getTile", sol::overload([](Map* map, int x, int y, int z) -> Tile* { return map ? map->getTile(x, y, z) : nullptr; }, [](Map* map, const Position& pos) -> Tile* { return map ? map->getTile(pos) : nullptr; }), + + // Get or create tile (for adding content to empty positions) + "getOrCreateTile", [](Map* map, sol::variadic_args va) -> Tile* { + if (!map){ return nullptr; +} Position pos; if (va.size() == 1 && va[0].is()) { @@ -152,41 +144,40 @@ void registerMap(sol::state& lua) { throw sol::error("getOrCreateTile expects (x, y, z) or (Position)"); } - return map->getOrCreateTile(pos); - }, + return map->getOrCreateTile(pos); }, - // Tiles iterator - allows: for tile in map.tiles do ... end - "tiles", sol::property([](Map* map, sol::this_state ts) { - sol::state_view lua(ts); + // Tiles iterator - allows: for tile in map.tiles do ... end + "tiles", sol::property([](Map* map, sol::this_state ts) { + sol::state_view lua(ts); - // Return an iterator function - auto iterator = std::make_shared(map); + // Return an iterator function + auto iterator = std::make_shared(map); - // Return the iterator function that Lua will call repeatedly - return sol::make_object(lua, [iterator](sol::this_state ts) { - return iterator->next(ts); - }); - }), + // Return the iterator function that Lua will call repeatedly + return sol::make_object(lua, [iterator](sol::this_state ts) { + return iterator->next(ts); + }); + }), - // Spawns iterator - allows: for tile in map.spawns do ... end - "spawns", sol::property([](Map* map, sol::this_state ts) { - sol::state_view lua(ts); + // Spawns iterator - allows: for tile in map.spawns do ... end + "spawns", sol::property([](Map* map, sol::this_state ts) { + sol::state_view lua(ts); - auto iterator = std::make_shared(map); + auto iterator = std::make_shared(map); - return sol::make_object(lua, [iterator]() -> Tile* { - return iterator->next(); - }); - }), + return sol::make_object(lua, [iterator]() -> Tile* { + return iterator->next(); + }); + }), - // String representation - sol::meta_function::to_string, [](Map* map) { - if (!map) return std::string("Map(invalid)"); + // String representation + sol::meta_function::to_string, [](Map* map) { + if (!map){ return std::string("Map(invalid)"); +} return "Map(\"" + map->getName() + "\", " + std::to_string(map->getWidth()) + "x" + - std::to_string(map->getHeight()) + ")"; - } - ); -} + std::to_string(map->getHeight()) + ")"; } + ); + } } // namespace LuaAPI diff --git a/source/lua/lua_api_noise.cpp b/source/lua/lua_api_noise.cpp index 72dfeef1f..53644e8ed 100644 --- a/source/lua/lua_api_noise.cpp +++ b/source/lua/lua_api_noise.cpp @@ -24,446 +24,452 @@ namespace LuaAPI { -// Thread-local noise generator cache for better performance -// Each seed gets its own generator instance -class NoiseGeneratorCache { -public: - FastNoiseLite& getGenerator(int seed) { - std::lock_guard lock(mutex_); - auto it = generators_.find(seed); - if (it == generators_.end()) { - auto& gen = generators_[seed]; - gen.SetSeed(seed); - return gen; - } - return it->second; - } - - void clear() { - std::lock_guard lock(mutex_); - generators_.clear(); - } - -private: - std::unordered_map generators_; - std::mutex mutex_; -}; - -static NoiseGeneratorCache g_noiseCache; - -// Helper to create configured noise generator -static FastNoiseLite createNoiseGenerator(int seed, FastNoiseLite::NoiseType type, float frequency = 0.01f) { - FastNoiseLite noise; - noise.SetSeed(seed); - noise.SetNoiseType(type); - noise.SetFrequency(frequency); - return noise; -} - -void registerNoise(sol::state& lua) { - sol::table noiseTable = lua.create_table(); - - // ======================================== - // PERLIN NOISE - // ======================================== - - // noise.perlin(x, y, seed, frequency) -> number [-1, 1] - // Simple 2D perlin noise - noiseTable.set_function("perlin", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { - int s = seed.value_or(1337); - float freq = frequency.value_or(0.01f); - - FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Perlin, freq); - return noise.GetNoise(x, y); - }); - - // noise.perlin3d(x, y, z, seed, frequency) -> number [-1, 1] - // 3D perlin noise - noiseTable.set_function("perlin3d", [](float x, float y, float z, sol::optional seed, sol::optional frequency) -> float { - int s = seed.value_or(1337); - float freq = frequency.value_or(0.01f); - - FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Perlin, freq); - return noise.GetNoise(x, y, z); - }); - - // ======================================== - // SIMPLEX / OPENSIMPLEX2 NOISE - // ======================================== - - // noise.simplex(x, y, seed, frequency) -> number [-1, 1] - // OpenSimplex2 noise - better quality than perlin - noiseTable.set_function("simplex", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { - int s = seed.value_or(1337); - float freq = frequency.value_or(0.01f); - - FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_OpenSimplex2, freq); - return noise.GetNoise(x, y); - }); - - // noise.simplex3d(x, y, z, seed, frequency) -> number [-1, 1] - noiseTable.set_function("simplex3d", [](float x, float y, float z, sol::optional seed, sol::optional frequency) -> float { - int s = seed.value_or(1337); - float freq = frequency.value_or(0.01f); - - FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_OpenSimplex2, freq); - return noise.GetNoise(x, y, z); - }); - - // noise.simplexSmooth(x, y, seed, frequency) -> number [-1, 1] - // OpenSimplex2S - smoother variant - noiseTable.set_function("simplexSmooth", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { - int s = seed.value_or(1337); - float freq = frequency.value_or(0.01f); - - FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_OpenSimplex2S, freq); - return noise.GetNoise(x, y); - }); - - // ======================================== - // CELLULAR / VORONOI NOISE - // ======================================== - - // noise.cellular(x, y, seed, frequency, distanceFunc, returnType) -> number - // Cellular/Voronoi noise for cave-like structures, organic patterns - noiseTable.set_function("cellular", [](float x, float y, sol::optional seed, sol::optional frequency, - sol::optional distanceFunc, sol::optional returnType) -> float { - - int s = seed.value_or(1337); - float freq = frequency.value_or(0.01f); - std::string distFn = distanceFunc.value_or("euclidean"); - std::string retType = returnType.value_or("distance"); - - FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Cellular, freq); - - // Set distance function - if (distFn == "euclidean" || distFn == "euclideanSq") { - noise.SetCellularDistanceFunction(FastNoiseLite::CellularDistanceFunction_EuclideanSq); - } else if (distFn == "manhattan") { - noise.SetCellularDistanceFunction(FastNoiseLite::CellularDistanceFunction_Manhattan); - } else if (distFn == "hybrid") { - noise.SetCellularDistanceFunction(FastNoiseLite::CellularDistanceFunction_Hybrid); + // Thread-local noise generator cache for better performance + // Each seed gets its own generator instance + class NoiseGeneratorCache { + public: + FastNoiseLite& getGenerator(int seed) { + std::lock_guard lock(mutex_); + auto it = generators_.find(seed); + if (it == generators_.end()) { + auto& gen = generators_[seed]; + gen.SetSeed(seed); + return gen; + } + return it->second; } - // Set return type - if (retType == "cellValue") { - noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_CellValue); - } else if (retType == "distance") { - noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance); - } else if (retType == "distance2") { - noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2); - } else if (retType == "distance2Add") { - noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2Add); - } else if (retType == "distance2Sub") { - noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2Sub); - } else if (retType == "distance2Mul") { - noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2Mul); - } else if (retType == "distance2Div") { - noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2Div); + void clear() { + std::lock_guard lock(mutex_); + generators_.clear(); } - return noise.GetNoise(x, y); - }); - - // noise.cellular3d(x, y, z, seed, frequency) -> number - noiseTable.set_function("cellular3d", [](float x, float y, float z, sol::optional seed, sol::optional frequency) -> float { - int s = seed.value_or(1337); - float freq = frequency.value_or(0.01f); - - FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Cellular, freq); - return noise.GetNoise(x, y, z); - }); + private: + std::unordered_map generators_; + std::mutex mutex_; + }; - // ======================================== - // VALUE NOISE - // ======================================== - - // noise.value(x, y, seed, frequency) -> number [-1, 1] - // Value noise - blocky, good for heightmaps - noiseTable.set_function("value", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { - int s = seed.value_or(1337); - float freq = frequency.value_or(0.01f); - - FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Value, freq); - return noise.GetNoise(x, y); - }); - - // noise.valueCubic(x, y, seed, frequency) -> number [-1, 1] - // Cubic interpolated value noise - smoother than value - noiseTable.set_function("valueCubic", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { - int s = seed.value_or(1337); - float freq = frequency.value_or(0.01f); - - FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_ValueCubic, freq); - return noise.GetNoise(x, y); - }); - - // ======================================== - // FBM (Fractional Brownian Motion) - // ======================================== - - // noise.fbm(x, y, seed, options) -> number - // Fractal Brownian Motion - layered noise for natural terrain - // options: { frequency, octaves, lacunarity, gain, noiseType } - noiseTable.set_function("fbm", [](float x, float y, sol::optional seed, sol::optional options) -> float { - int s = seed.value_or(1337); + static NoiseGeneratorCache g_noiseCache; + // Helper to create configured noise generator + static FastNoiseLite createNoiseGenerator(int seed, FastNoiseLite::NoiseType type, float frequency = 0.01f) { FastNoiseLite noise; - noise.SetSeed(s); - noise.SetFractalType(FastNoiseLite::FractalType_FBm); - - if (options) { - sol::table opts = *options; - - // Frequency - float freq = opts.get_or("frequency", 0.01f); - noise.SetFrequency(freq); - - // Octaves (layers of noise) - int octaves = opts.get_or("octaves", 4); - noise.SetFractalOctaves(octaves); - - // Lacunarity (frequency multiplier per octave) - float lacunarity = opts.get_or("lacunarity", 2.0f); - noise.SetFractalLacunarity(lacunarity); - - // Gain (amplitude multiplier per octave) - float gain = opts.get_or("gain", 0.5f); - noise.SetFractalGain(gain); + noise.SetSeed(seed); + noise.SetNoiseType(type); + noise.SetFrequency(frequency); + return noise; + } - // Noise type for fractal - std::string noiseType = opts.get_or("noiseType", "simplex"); - if (noiseType == "perlin") { - noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); - } else if (noiseType == "simplex" || noiseType == "opensimplex") { - noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); - } else if (noiseType == "value") { - noise.SetNoiseType(FastNoiseLite::NoiseType_Value); - } else if (noiseType == "cellular") { - noise.SetNoiseType(FastNoiseLite::NoiseType_Cellular); + void registerNoise(sol::state& lua) { + sol::table noiseTable = lua.create_table(); + + // ======================================== + // PERLIN NOISE + // ======================================== + + // noise.perlin(x, y, seed, frequency) -> number [-1, 1] + // Simple 2D perlin noise + noiseTable.set_function("perlin", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Perlin, freq); + return noise.GetNoise(x, y); + }); + + // noise.perlin3d(x, y, z, seed, frequency) -> number [-1, 1] + // 3D perlin noise + noiseTable.set_function("perlin3d", [](float x, float y, float z, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Perlin, freq); + return noise.GetNoise(x, y, z); + }); + + // ======================================== + // SIMPLEX / OPENSIMPLEX2 NOISE + // ======================================== + + // noise.simplex(x, y, seed, frequency) -> number [-1, 1] + // OpenSimplex2 noise - better quality than perlin + noiseTable.set_function("simplex", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_OpenSimplex2, freq); + return noise.GetNoise(x, y); + }); + + // noise.simplex3d(x, y, z, seed, frequency) -> number [-1, 1] + noiseTable.set_function("simplex3d", [](float x, float y, float z, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_OpenSimplex2, freq); + return noise.GetNoise(x, y, z); + }); + + // noise.simplexSmooth(x, y, seed, frequency) -> number [-1, 1] + // OpenSimplex2S - smoother variant + noiseTable.set_function("simplexSmooth", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_OpenSimplex2S, freq); + return noise.GetNoise(x, y); + }); + + // ======================================== + // CELLULAR / VORONOI NOISE + // ======================================== + + // noise.cellular(x, y, seed, frequency, distanceFunc, returnType) -> number + // Cellular/Voronoi noise for cave-like structures, organic patterns + noiseTable.set_function("cellular", [](float x, float y, sol::optional seed, sol::optional frequency, sol::optional distanceFunc, sol::optional returnType) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + std::string distFn = distanceFunc.value_or("euclidean"); + std::string retType = returnType.value_or("distance"); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Cellular, freq); + + // Set distance function + if (distFn == "euclidean" || distFn == "euclideanSq") { + noise.SetCellularDistanceFunction(FastNoiseLite::CellularDistanceFunction_EuclideanSq); + } else if (distFn == "manhattan") { + noise.SetCellularDistanceFunction(FastNoiseLite::CellularDistanceFunction_Manhattan); + } else if (distFn == "hybrid") { + noise.SetCellularDistanceFunction(FastNoiseLite::CellularDistanceFunction_Hybrid); } - } else { - noise.SetFrequency(0.01f); - noise.SetFractalOctaves(4); - noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); - } - return noise.GetNoise(x, y); - }); - - // noise.fbm3d(x, y, z, seed, options) -> number - noiseTable.set_function("fbm3d", [](float x, float y, float z, sol::optional seed, sol::optional options) -> float { - int s = seed.value_or(1337); + // Set return type + if (retType == "cellValue") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_CellValue); + } else if (retType == "distance") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance); + } else if (retType == "distance2") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2); + } else if (retType == "distance2Add") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2Add); + } else if (retType == "distance2Sub") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2Sub); + } else if (retType == "distance2Mul") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2Mul); + } else if (retType == "distance2Div") { + noise.SetCellularReturnType(FastNoiseLite::CellularReturnType_Distance2Div); + } - FastNoiseLite noise; - noise.SetSeed(s); - noise.SetFractalType(FastNoiseLite::FractalType_FBm); - - if (options) { - sol::table opts = *options; - noise.SetFrequency(opts.get_or("frequency", 0.01f)); - noise.SetFractalOctaves(opts.get_or("octaves", 4)); - noise.SetFractalLacunarity(opts.get_or("lacunarity", 2.0f)); - noise.SetFractalGain(opts.get_or("gain", 0.5f)); - - std::string noiseType = opts.get_or("noiseType", "simplex"); - if (noiseType == "perlin") { - noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); + return noise.GetNoise(x, y); + }); + + // noise.cellular3d(x, y, z, seed, frequency) -> number + noiseTable.set_function("cellular3d", [](float x, float y, float z, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Cellular, freq); + return noise.GetNoise(x, y, z); + }); + + // ======================================== + // VALUE NOISE + // ======================================== + + // noise.value(x, y, seed, frequency) -> number [-1, 1] + // Value noise - blocky, good for heightmaps + noiseTable.set_function("value", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_Value, freq); + return noise.GetNoise(x, y); + }); + + // noise.valueCubic(x, y, seed, frequency) -> number [-1, 1] + // Cubic interpolated value noise - smoother than value + noiseTable.set_function("valueCubic", [](float x, float y, sol::optional seed, sol::optional frequency) -> float { + int s = seed.value_or(1337); + float freq = frequency.value_or(0.01f); + + FastNoiseLite noise = createNoiseGenerator(s, FastNoiseLite::NoiseType_ValueCubic, freq); + return noise.GetNoise(x, y); + }); + + // ======================================== + // FBM (Fractional Brownian Motion) + // ======================================== + + // noise.fbm(x, y, seed, options) -> number + // Fractal Brownian Motion - layered noise for natural terrain + // options: { frequency, octaves, lacunarity, gain, noiseType } + noiseTable.set_function("fbm", [](float x, float y, sol::optional seed, sol::optional options) -> float { + int s = seed.value_or(1337); + + FastNoiseLite noise; + noise.SetSeed(s); + noise.SetFractalType(FastNoiseLite::FractalType_FBm); + + if (options) { + sol::table opts = *options; + + // Frequency + float freq = opts.get_or("frequency", 0.01f); + noise.SetFrequency(freq); + + // Octaves (layers of noise) + int octaves = opts.get_or("octaves", 4); + noise.SetFractalOctaves(octaves); + + // Lacunarity (frequency multiplier per octave) + float lacunarity = opts.get_or("lacunarity", 2.0f); + noise.SetFractalLacunarity(lacunarity); + + // Gain (amplitude multiplier per octave) + float gain = opts.get_or("gain", 0.5f); + noise.SetFractalGain(gain); + + // Noise type for fractal + std::string noiseType = opts.get_or("noiseType", "simplex"); + if (noiseType == "perlin") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); + } else if (noiseType == "simplex" || noiseType == "opensimplex") { + noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); + } else if (noiseType == "value") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Value); + } else if (noiseType == "cellular") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Cellular); + } } else { + noise.SetFrequency(0.01f); + noise.SetFractalOctaves(4); noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); } - } else { - noise.SetFrequency(0.01f); - noise.SetFractalOctaves(4); - noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); - } - - return noise.GetNoise(x, y, z); - }); - // ======================================== - // RIDGED NOISE - // ======================================== + return noise.GetNoise(x, y); + }); - // noise.ridged(x, y, seed, options) -> number - // Ridged fractal noise - good for mountains, veins - noiseTable.set_function("ridged", [](float x, float y, sol::optional seed, sol::optional options) -> float { - int s = seed.value_or(1337); + // noise.fbm3d(x, y, z, seed, options) -> number + noiseTable.set_function("fbm3d", [](float x, float y, float z, sol::optional seed, sol::optional options) -> float { + int s = seed.value_or(1337); - FastNoiseLite noise; - noise.SetSeed(s); - noise.SetFractalType(FastNoiseLite::FractalType_Ridged); - noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); - - if (options) { - sol::table opts = *options; - noise.SetFrequency(opts.get_or("frequency", 0.01f)); - noise.SetFractalOctaves(opts.get_or("octaves", 4)); - noise.SetFractalLacunarity(opts.get_or("lacunarity", 2.0f)); - noise.SetFractalGain(opts.get_or("gain", 0.5f)); - } else { - noise.SetFrequency(0.01f); - noise.SetFractalOctaves(4); - } + FastNoiseLite noise; + noise.SetSeed(s); + noise.SetFractalType(FastNoiseLite::FractalType_FBm); - return noise.GetNoise(x, y); - }); - - // ======================================== - // DOMAIN WARP - // ======================================== - - // noise.warp(x, y, seed, options) -> x, y (warped coordinates) - // Domain warping - distorts input coordinates for organic effects - noiseTable.set_function("warp", [](float x, float y, sol::optional seed, sol::optional options, sol::this_state s) -> sol::object { - int sd = seed.value_or(1337); + if (options) { + sol::table opts = *options; + noise.SetFrequency(opts.get_or("frequency", 0.01f)); + noise.SetFractalOctaves(opts.get_or("octaves", 4)); + noise.SetFractalLacunarity(opts.get_or("lacunarity", 2.0f)); + noise.SetFractalGain(opts.get_or("gain", 0.5f)); - FastNoiseLite noise; - noise.SetSeed(sd); - noise.SetDomainWarpType(FastNoiseLite::DomainWarpType_OpenSimplex2); - - float amplitude = 30.0f; - float frequency = 0.01f; - - if (options) { - sol::table opts = *options; - amplitude = opts.get_or("amplitude", 30.0f); - frequency = opts.get_or("frequency", 0.01f); - - std::string warpType = opts.get_or("type", "simplex"); - if (warpType == "simplex" || warpType == "opensimplex") { - noise.SetDomainWarpType(FastNoiseLite::DomainWarpType_OpenSimplex2); - } else if (warpType == "simplexReduced") { - noise.SetDomainWarpType(FastNoiseLite::DomainWarpType_OpenSimplex2Reduced); - } else if (warpType == "basic") { - noise.SetDomainWarpType(FastNoiseLite::DomainWarpType_BasicGrid); + std::string noiseType = opts.get_or("noiseType", "simplex"); + if (noiseType == "perlin") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); + } else { + noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); + } + } else { + noise.SetFrequency(0.01f); + noise.SetFractalOctaves(4); + noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); } - } - - noise.SetDomainWarpAmp(amplitude); - noise.SetFrequency(frequency); - noise.DomainWarp(x, y); - - sol::state_view lua(s); - sol::table result = lua.create_table(); - result["x"] = x; - result["y"] = y; - return result; - }); - - // ======================================== - // UTILITY FUNCTIONS - // ======================================== - - // noise.normalize(value, min, max) -> number [0, 1] - // Normalize noise value from [-1,1] to [min, max] - noiseTable.set_function("normalize", [](float value, sol::optional minVal, sol::optional maxVal) -> float { - float min = minVal.value_or(0.0f); - float max = maxVal.value_or(1.0f); - // value is in [-1, 1], normalize to [0, 1] first, then scale - float normalized = (value + 1.0f) * 0.5f; - return min + normalized * (max - min); - }); - - // noise.threshold(value, threshold) -> boolean - // Returns true if value is above threshold - noiseTable.set_function("threshold", [](float value, float threshold) -> bool { - return value >= threshold; - }); - - // noise.map(value, inMin, inMax, outMin, outMax) -> number - // Map value from one range to another - noiseTable.set_function("map", [](float value, float inMin, float inMax, float outMin, float outMax) -> float { - float t = (value - inMin) / (inMax - inMin); - return outMin + t * (outMax - outMin); - }); - - // noise.clamp(value, min, max) -> number - noiseTable.set_function("clamp", [](float value, float min, float max) -> float { - if (value < min) return min; - if (value > max) return max; - return value; - }); - - // noise.lerp(a, b, t) -> number - // Linear interpolation - noiseTable.set_function("lerp", [](float a, float b, float t) -> float { - return a + t * (b - a); - }); - - // noise.smoothstep(edge0, edge1, x) -> number - // Smooth interpolation - noiseTable.set_function("smoothstep", [](float edge0, float edge1, float x) -> float { - float t = (x - edge0) / (edge1 - edge0); - if (t < 0.0f) t = 0.0f; - if (t > 1.0f) t = 1.0f; - return t * t * (3.0f - 2.0f * t); - }); - - // noise.clearCache() - clear noise generator cache - noiseTable.set_function("clearCache", []() { - g_noiseCache.clear(); - }); - - // ======================================== - // BATCH GENERATION (for performance) - // ======================================== - - // noise.generateGrid(x1, y1, x2, y2, options) -> table of values - // Generate noise values for a grid area (faster than individual calls) - noiseTable.set_function("generateGrid", [](int x1, int y1, int x2, int y2, sol::optional options, sol::this_state s) -> sol::table { - sol::state_view lua(s); - sol::table result = lua.create_table(); + return noise.GetNoise(x, y, z); + }); - FastNoiseLite noise; + // ======================================== + // RIDGED NOISE + // ======================================== - if (options) { - sol::table opts = *options; - noise.SetSeed(opts.get_or("seed", 1337)); - noise.SetFrequency(opts.get_or("frequency", 0.01f)); + // noise.ridged(x, y, seed, options) -> number + // Ridged fractal noise - good for mountains, veins + noiseTable.set_function("ridged", [](float x, float y, sol::optional seed, sol::optional options) -> float { + int s = seed.value_or(1337); - std::string noiseType = opts.get_or("noiseType", "simplex"); - if (noiseType == "perlin") { - noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); - } else if (noiseType == "simplex") { - noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); - } else if (noiseType == "cellular") { - noise.SetNoiseType(FastNoiseLite::NoiseType_Cellular); - } else if (noiseType == "value") { - noise.SetNoiseType(FastNoiseLite::NoiseType_Value); - } + FastNoiseLite noise; + noise.SetSeed(s); + noise.SetFractalType(FastNoiseLite::FractalType_Ridged); + noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); - // Fractal settings - std::string fractal = opts.get_or("fractal", "none"); - if (fractal == "fbm") { - noise.SetFractalType(FastNoiseLite::FractalType_FBm); + if (options) { + sol::table opts = *options; + noise.SetFrequency(opts.get_or("frequency", 0.01f)); noise.SetFractalOctaves(opts.get_or("octaves", 4)); noise.SetFractalLacunarity(opts.get_or("lacunarity", 2.0f)); noise.SetFractalGain(opts.get_or("gain", 0.5f)); - } else if (fractal == "ridged") { - noise.SetFractalType(FastNoiseLite::FractalType_Ridged); - noise.SetFractalOctaves(opts.get_or("octaves", 4)); + } else { + noise.SetFrequency(0.01f); + noise.SetFractalOctaves(4); } - } else { - noise.SetSeed(1337); - noise.SetFrequency(0.01f); - noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); - } - // Generate values - for (int y = y1; y <= y2; ++y) { - sol::table row = lua.create_table(); - for (int x = x1; x <= x2; ++x) { - row[x - x1 + 1] = noise.GetNoise((float)x, (float)y); + return noise.GetNoise(x, y); + }); + + // ======================================== + // DOMAIN WARP + // ======================================== + + // noise.warp(x, y, seed, options) -> x, y (warped coordinates) + // Domain warping - distorts input coordinates for organic effects + noiseTable.set_function("warp", [](float x, float y, sol::optional seed, sol::optional options, sol::this_state s) -> sol::object { + int sd = seed.value_or(1337); + + FastNoiseLite noise; + noise.SetSeed(sd); + noise.SetDomainWarpType(FastNoiseLite::DomainWarpType_OpenSimplex2); + + float amplitude = 30.0f; + float frequency = 0.01f; + + if (options) { + sol::table opts = *options; + amplitude = opts.get_or("amplitude", 30.0f); + frequency = opts.get_or("frequency", 0.01f); + + std::string warpType = opts.get_or("type", "simplex"); + if (warpType == "simplex" || warpType == "opensimplex") { + noise.SetDomainWarpType(FastNoiseLite::DomainWarpType_OpenSimplex2); + } else if (warpType == "simplexReduced") { + noise.SetDomainWarpType(FastNoiseLite::DomainWarpType_OpenSimplex2Reduced); + } else if (warpType == "basic") { + noise.SetDomainWarpType(FastNoiseLite::DomainWarpType_BasicGrid); + } } - result[y - y1 + 1] = row; - } - return result; - }); + noise.SetDomainWarpAmp(amplitude); + noise.SetFrequency(frequency); + + noise.DomainWarp(x, y); + + sol::state_view lua(s); + sol::table result = lua.create_table(); + result["x"] = x; + result["y"] = y; + return result; + }); + + // ======================================== + // UTILITY FUNCTIONS + // ======================================== + + // noise.normalize(value, min, max) -> number [0, 1] + // Normalize noise value from [-1,1] to [min, max] + noiseTable.set_function("normalize", [](float value, sol::optional minVal, sol::optional maxVal) -> float { + float min = minVal.value_or(0.0f); + float max = maxVal.value_or(1.0f); + // value is in [-1, 1], normalize to [0, 1] first, then scale + float normalized = (value + 1.0f) * 0.5f; + return min + normalized * (max - min); + }); + + // noise.threshold(value, threshold) -> boolean + // Returns true if value is above threshold + noiseTable.set_function("threshold", [](float value, float threshold) -> bool { + return value >= threshold; + }); + + // noise.map(value, inMin, inMax, outMin, outMax) -> number + // Map value from one range to another + noiseTable.set_function("map", [](float value, float inMin, float inMax, float outMin, float outMax) -> float { + float t = (value - inMin) / (inMax - inMin); + return outMin + t * (outMax - outMin); + }); + + // noise.clamp(value, min, max) -> number + noiseTable.set_function("clamp", [](float value, float min, float max) -> float { + if (value < min) { + return min; + } + if (value > max) { + return max; + } + return value; + }); + + // noise.lerp(a, b, t) -> number + // Linear interpolation + noiseTable.set_function("lerp", [](float a, float b, float t) -> float { + return a + t * (b - a); + }); + + // noise.smoothstep(edge0, edge1, x) -> number + // Smooth interpolation + noiseTable.set_function("smoothstep", [](float edge0, float edge1, float x) -> float { + float t = (x - edge0) / (edge1 - edge0); + if (t < 0.0f) { + t = 0.0f; + } + if (t > 1.0f) { + t = 1.0f; + } + return t * t * (3.0f - 2.0f * t); + }); + + // noise.clearCache() - clear noise generator cache + noiseTable.set_function("clearCache", []() { + g_noiseCache.clear(); + }); + + // ======================================== + // BATCH GENERATION (for performance) + // ======================================== + + // noise.generateGrid(x1, y1, x2, y2, options) -> table of values + // Generate noise values for a grid area (faster than individual calls) + noiseTable.set_function("generateGrid", [](int x1, int y1, int x2, int y2, sol::optional options, sol::this_state s) -> sol::table { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + FastNoiseLite noise; + + if (options) { + sol::table opts = *options; + noise.SetSeed(opts.get_or("seed", 1337)); + noise.SetFrequency(opts.get_or("frequency", 0.01f)); + + std::string noiseType = opts.get_or("noiseType", "simplex"); + if (noiseType == "perlin") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); + } else if (noiseType == "simplex") { + noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); + } else if (noiseType == "cellular") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Cellular); + } else if (noiseType == "value") { + noise.SetNoiseType(FastNoiseLite::NoiseType_Value); + } + + // Fractal settings + std::string fractal = opts.get_or("fractal", "none"); + if (fractal == "fbm") { + noise.SetFractalType(FastNoiseLite::FractalType_FBm); + noise.SetFractalOctaves(opts.get_or("octaves", 4)); + noise.SetFractalLacunarity(opts.get_or("lacunarity", 2.0f)); + noise.SetFractalGain(opts.get_or("gain", 0.5f)); + } else if (fractal == "ridged") { + noise.SetFractalType(FastNoiseLite::FractalType_Ridged); + noise.SetFractalOctaves(opts.get_or("octaves", 4)); + } + } else { + noise.SetSeed(1337); + noise.SetFrequency(0.01f); + noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2); + } - lua["noise"] = noiseTable; -} + // Generate values + for (int y = y1; y <= y2; ++y) { + sol::table row = lua.create_table(); + for (int x = x1; x <= x2; ++x) { + row[x - x1 + 1] = noise.GetNoise((float)x, (float)y); + } + result[y - y1 + 1] = row; + } + + return result; + }); + + lua["noise"] = noiseTable; + } } // namespace LuaAPI diff --git a/source/lua/lua_api_position.cpp b/source/lua/lua_api_position.cpp index 5ef0866dd..c8c2e76bc 100644 --- a/source/lua/lua_api_position.cpp +++ b/source/lua/lua_api_position.cpp @@ -21,51 +21,36 @@ namespace LuaAPI { -void registerPosition(sol::state& lua) { - // Register Position usertype - lua.new_usertype("Position", - // Constructors - sol::constructors< - Position(), - Position(int, int, int) - >(), - - // Also allow construction from table: Position{x=100, y=200, z=7} - sol::call_constructor, sol::factories( - [](int x, int y, int z) { - return Position(x, y, z); - }, - [](sol::table t) { + void registerPosition(sol::state& lua) { + // Register Position usertype + lua.new_usertype( + "Position", + // Constructors + sol::constructors< + Position(), + Position(int, int, int)>(), + + // Also allow construction from table: Position{x=100, y=200, z=7} + sol::call_constructor, sol::factories([](int x, int y, int z) { return Position(x, y, z); }, [](sol::table t) { int x = t.get_or("x", 0); int y = t.get_or("y", 0); int z = t.get_or("z", 0); - return Position(x, y, z); - } - ), - - // Properties - "x", &Position::x, - "y", &Position::y, - "z", &Position::z, - - // Methods - "isValid", &Position::isValid, - - // Operators - sol::meta_function::equal_to, [](const Position& a, const Position& b) { - return a == b; - }, - sol::meta_function::addition, [](const Position& a, const Position& b) { - return a + b; - }, - sol::meta_function::subtraction, [](const Position& a, const Position& b) { - return a - b; - }, - sol::meta_function::to_string, [](const Position& pos) { - return "Position(" + std::to_string(pos.x) + ", " + - std::to_string(pos.y) + ", " + std::to_string(pos.z) + ")"; - } - ); -} + return Position(x, y, z); }), + + // Properties + "x", &Position::x, + "y", &Position::y, + "z", &Position::z, + + // Methods + "isValid", &Position::isValid, + + // Operators + sol::meta_function::equal_to, [](const Position& a, const Position& b) { return a == b; }, + sol::meta_function::addition, [](const Position& a, const Position& b) { return a + b; }, + sol::meta_function::subtraction, [](const Position& a, const Position& b) { return a - b; }, + sol::meta_function::to_string, [](const Position& pos) { return "Position(" + std::to_string(pos.x) + ", " + std::to_string(pos.y) + ", " + std::to_string(pos.z) + ")"; } + ); + } } // namespace LuaAPI diff --git a/source/lua/lua_api_selection.cpp b/source/lua/lua_api_selection.cpp index 4edb47e05..6b5bb8915 100644 --- a/source/lua/lua_api_selection.cpp +++ b/source/lua/lua_api_selection.cpp @@ -25,127 +25,121 @@ namespace LuaAPI { -// Helper to get the current selection -static Selection* getCurrentSelection() { - Editor* editor = g_gui.GetCurrentEditor(); - if (!editor) return nullptr; - return &editor->selection; -} + // Helper to get the current selection + static Selection* getCurrentSelection() { + Editor* editor = g_gui.GetCurrentEditor(); + if (!editor) { + return nullptr; + } + return &editor->selection; + } -// Get tiles as a Lua table -static sol::table getSelectionTiles(sol::this_state ts) { - sol::state_view lua(ts); - sol::table result = lua.create_table(); + // Get tiles as a Lua table + static sol::table getSelectionTiles(sol::this_state ts) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); - Selection* sel = getCurrentSelection(); - if (!sel) return result; + Selection* sel = getCurrentSelection(); + if (!sel) { + return result; + } - int idx = 1; - for (Tile* tile : sel->getTiles()) { - if (tile) { - result[idx++] = tile; + int idx = 1; + for (Tile* tile : sel->getTiles()) { + if (tile) { + result[idx++] = tile; + } } + return result; } - return result; -} -// Get bounds as a table with min/max positions -static sol::table getSelectionBounds(sol::this_state ts) { - sol::state_view lua(ts); - sol::table result = lua.create_table(); + // Get bounds as a table with min/max positions + static sol::table getSelectionBounds(sol::this_state ts) { + sol::state_view lua(ts); + sol::table result = lua.create_table(); + + Selection* sel = getCurrentSelection(); + if (!sel || sel->size() == 0) { + return result; + } - Selection* sel = getCurrentSelection(); - if (!sel || sel->size() == 0) { + result["min"] = sel->minPosition(); + result["max"] = sel->maxPosition(); return result; } - result["min"] = sel->minPosition(); - result["max"] = sel->maxPosition(); - return result; -} - -void registerSelection(sol::state& lua) { - // Register Selection usertype - lua.new_usertype("Selection", - // No public constructor - selection is obtained from app.selection - sol::no_constructor, - - // Properties - "isEmpty", sol::property([](Selection* sel) { - return sel == nullptr || sel->size() == 0; - }), - "size", sol::property([](Selection* sel) -> size_t { - return sel ? sel->size() : 0; - }), - "isBusy", sol::property([](Selection* sel) { - return sel && sel->isBusy(); - }), - - // Tiles collection (as a table) - "tiles", sol::property([](Selection* sel, sol::this_state ts) { - return getSelectionTiles(ts); - }), - - // Bounds - "bounds", sol::property([](Selection* sel, sol::this_state ts) { - return getSelectionBounds(ts); - }), - "minPosition", sol::property([](Selection* sel) -> Position { - return sel ? sel->minPosition() : Position(); - }), - "maxPosition", sol::property([](Selection* sel) -> Position { - return sel ? sel->maxPosition() : Position(); - }), - - // Methods - "clear", [](Selection* sel) { + void registerSelection(sol::state& lua) { + // Register Selection usertype + lua.new_usertype( + "Selection", + // No public constructor - selection is obtained from app.selection + sol::no_constructor, + + // Properties + "isEmpty", sol::property([](Selection* sel) { + return sel == nullptr || sel->size() == 0; + }), + "size", sol::property([](Selection* sel) -> size_t { + return sel ? sel->size() : 0; + }), + "isBusy", sol::property([](Selection* sel) { + return sel && sel->isBusy(); + }), + + // Tiles collection (as a table) + "tiles", sol::property([](Selection* sel, sol::this_state ts) { + return getSelectionTiles(ts); + }), + + // Bounds + "bounds", sol::property([](Selection* sel, sol::this_state ts) { + return getSelectionBounds(ts); + }), + "minPosition", sol::property([](Selection* sel) -> Position { + return sel ? sel->minPosition() : Position(); + }), + "maxPosition", sol::property([](Selection* sel) -> Position { + return sel ? sel->maxPosition() : Position(); + }), + + // Methods + "clear", [](Selection* sel) { if (sel) { sel->start(Selection::INTERNAL); sel->clear(); sel->finish(Selection::INTERNAL); - } - }, + } }, - "add", sol::overload( - [](Selection* sel, Tile* tile) { + "add", sol::overload([](Selection* sel, Tile* tile) { if (sel && tile) { sel->start(Selection::INTERNAL); sel->add(tile); sel->finish(Selection::INTERNAL); - } - }, - [](Selection* sel, Tile* tile, Item* item) { + } }, [](Selection* sel, Tile* tile, Item* item) { if (sel && tile && item) { sel->start(Selection::INTERNAL); sel->add(tile, item); sel->finish(Selection::INTERNAL); - } - } - ), + } }), - "remove", sol::overload( - [](Selection* sel, Tile* tile) { + "remove", sol::overload([](Selection* sel, Tile* tile) { if (sel && tile) { sel->start(Selection::INTERNAL); sel->remove(tile); sel->finish(Selection::INTERNAL); - } - }, - [](Selection* sel, Tile* tile, Item* item) { + } }, [](Selection* sel, Tile* tile, Item* item) { if (sel && tile && item) { sel->start(Selection::INTERNAL); sel->remove(tile, item); sel->finish(Selection::INTERNAL); - } - } - ), + } }), - // String representation - sol::meta_function::to_string, [](Selection* sel) { - if (!sel) return std::string("Selection(invalid)"); - return "Selection(size=" + std::to_string(sel->size()) + ")"; - } - ); + // String representation + sol::meta_function::to_string, [](Selection* sel) { + if (!sel){ return std::string("Selection(invalid)"); } + return "Selection(size=" + std::to_string(sel->size()) + ")"; } + ); + } } // namespace LuaAPI diff --git a/source/lua/lua_api_tile.cpp b/source/lua/lua_api_tile.cpp index 582846d16..ebfe72a32 100644 --- a/source/lua/lua_api_tile.cpp +++ b/source/lua/lua_api_tile.cpp @@ -39,7 +39,9 @@ namespace LuaAPI { sol::state_view lua(ts); sol::table items = lua.create_table(); - if (!tile) return items; + if (!tile) { + return items; + } int idx = 1; for (Item* item : tile->items) { @@ -105,9 +107,7 @@ namespace LuaAPI { } // Set creature on tile - static Creature* setTileCreature(Tile* tile, const std::string& creatureName, - sol::optional spawnTimeOpt, - sol::optional directionOpt) { + static Creature* setTileCreature(Tile* tile, const std::string& creatureName, sol::optional spawnTimeOpt, sol::optional directionOpt) { if (!tile) { throw sol::error("Invalid tile"); } @@ -184,8 +184,12 @@ namespace LuaAPI { // Create new spawn with given size (default 3) int size = sizeOpt.value_or(3); - if (size < 1) size = 1; - if (size > 50) size = 50; + if (size < 1) { + size = 1; + } + if (size > 50) { + size = 50; + } tile->spawn = newd Spawn(size); @@ -255,7 +259,9 @@ namespace LuaAPI { // Set house ID static void setTileHouseId(Tile* tile, uint32_t houseId) { - if (!tile) return; + if (!tile) { + return; + } // Mark tile for undo before modification markTileForUndo(tile); @@ -266,13 +272,19 @@ namespace LuaAPI { // Apply a brush to a tile (with optional auto-bordering) static bool applyBrushToTile(Tile* tile, const std::string& brushName, sol::optional autoBorder) { - if (!tile) return false; + if (!tile) { + return false; + } Brush* brush = g_brushes.getBrush(brushName); - if (!brush) return false; + if (!brush) { + return false; + } Editor* editor = g_gui.GetCurrentEditor(); - if (!editor) return false; + if (!editor) { + return false; + } markTileForUndo(tile); @@ -319,7 +331,8 @@ namespace LuaAPI { void registerTile(sol::state& lua) { // Register Tile usertype - lua.new_usertype("Tile", + lua.new_usertype( + "Tile", // No public constructor - tiles are obtained from the map sol::no_constructor, @@ -330,10 +343,7 @@ namespace LuaAPI { "z", sol::property([](Tile* tile) { return tile ? tile->getZ() : 0; }), // Ground (read/write) - "ground", sol::property( - [](Tile* tile) -> Item* { return tile ? tile->ground : nullptr; }, - setTileGround - ), + "ground", sol::property([](Tile* tile) -> Item* { return tile ? tile->ground : nullptr; }, setTileGround), "hasGround", sol::property([](Tile* tile) { return tile && tile->hasGround(); }), // Items collection (read-only - use addItem/removeItem to modify) @@ -341,10 +351,7 @@ namespace LuaAPI { "itemCount", sol::property([](Tile* tile) { return tile ? tile->size() : 0; }), // House - "houseId", sol::property( - [](Tile* tile) -> uint32_t { return tile ? tile->getHouseID() : 0; }, - setTileHouseId - ), + "houseId", sol::property([](Tile* tile) -> uint32_t { return tile ? tile->getHouseID() : 0; }, setTileHouseId), "isHouseTile", sol::property([](Tile* tile) { return tile && tile->isHouseTile(); }), "isHouseExit", sol::property([](Tile* tile) { return tile && tile->isHouseExit(); }), @@ -356,7 +363,9 @@ namespace LuaAPI { "hasTable", sol::property([](Tile* tile) { return tile && tile->hasTable(); }), "hasCarpet", sol::property([](Tile* tile) { return tile && tile->hasCarpet(); }), "groundZOrder", sol::property([](Tile* tile) -> int { - if (!tile) return 0; + if (!tile) { + return 0; + } GroundBrush* brush = tile->getGroundBrush(); return brush ? brush->getZ() : 0; }), @@ -365,21 +374,19 @@ namespace LuaAPI { "isPvpZone", sol::property([](Tile* tile) { return tile && (tile->getMapFlags() & TILESTATE_PVPZONE); }), // Map flags - "mapFlags", sol::property( - [](Tile* tile) -> uint16_t { return tile ? tile->getMapFlags() : 0; }, - [](Tile* tile, uint16_t flags) { + "mapFlags", sol::property([](Tile* tile) -> uint16_t { return tile ? tile->getMapFlags() : 0; }, [](Tile* tile, uint16_t flags) { if (tile) { markTileForUndo(tile); tile->setMapFlags(flags); tile->modify(); - } - } - ), + } }), // Selection "isSelected", sol::property([](Tile* tile) { return tile && tile->isSelected(); }), - "select", [](Tile* tile) { if (tile) tile->select(); }, - "deselect", [](Tile* tile) { if (tile) tile->deselect(); }, + "select", [](Tile* tile) { if (tile){ tile->select(); +} }, + "deselect", [](Tile* tile) { if (tile){ tile->deselect(); +} }, // Creature and Spawn (read-only access, use methods to modify) "creature", sol::property([](Tile* tile) -> Creature* { return tile ? tile->creature : nullptr; }), @@ -388,120 +395,110 @@ namespace LuaAPI { "hasSpawn", sol::property([](Tile* tile) { return tile && tile->spawn != nullptr; }), // Creature methods - "setCreature", sol::overload( - [](Tile* tile, const std::string& name) -> Creature* { - return setTileCreature(tile, name, sol::nullopt, sol::nullopt); - }, - [](Tile* tile, const std::string& name, int spawnTime) -> Creature* { - return setTileCreature(tile, name, spawnTime, sol::nullopt); - }, - [](Tile* tile, const std::string& name, int spawnTime, int direction) -> Creature* { - return setTileCreature(tile, name, spawnTime, direction); - } - ), + "setCreature", sol::overload([](Tile* tile, const std::string& name) -> Creature* { return setTileCreature(tile, name, sol::nullopt, sol::nullopt); }, [](Tile* tile, const std::string& name, int spawnTime) -> Creature* { return setTileCreature(tile, name, spawnTime, sol::nullopt); }, [](Tile* tile, const std::string& name, int spawnTime, int direction) -> Creature* { return setTileCreature(tile, name, spawnTime, direction); }), "removeCreature", removeTileCreature, // Spawn methods - "setSpawn", sol::overload( - [](Tile* tile) -> Spawn* { - return setTileSpawn(tile, sol::nullopt); - }, - [](Tile* tile, int size) -> Spawn* { - return setTileSpawn(tile, size); - } - ), + "setSpawn", sol::overload([](Tile* tile) -> Spawn* { return setTileSpawn(tile, sol::nullopt); }, [](Tile* tile, int size) -> Spawn* { return setTileSpawn(tile, size); }), "removeSpawn", removeTileSpawn, // Methods - "addItem", sol::overload( - [](Tile* tile, int itemId) -> Item* { - return addItemToTile(tile, itemId, sol::nullopt); - }, - [](Tile* tile, int itemId, int count) -> Item* { - return addItemToTile(tile, itemId, count); - } - ), + "addItem", sol::overload([](Tile* tile, int itemId) -> Item* { return addItemToTile(tile, itemId, sol::nullopt); }, [](Tile* tile, int itemId, int count) -> Item* { return addItemToTile(tile, itemId, count); }), "removeItem", removeItemFromTile, "applyBrush", applyBrushToTile, "borderize", [](Tile* tile) { - if (!tile) return; + if (!tile){ return; +} Editor* editor = g_gui.GetCurrentEditor(); - if (!editor) return; + if (!editor){ return; +} markTileForUndo(tile); tile->borderize(&editor->map); - tile->modify(); - }, + tile->modify(); }, "wallize", [](Tile* tile) { - if (!tile) return; + if (!tile){ return; +} Editor* editor = g_gui.GetCurrentEditor(); - if (!editor) return; + if (!editor){ return; +} markTileForUndo(tile); tile->wallize(&editor->map); - tile->modify(); - }, + tile->modify(); }, "moveItem", sol::overload( - // Index-based move within same tile - // Semantics: Move item at fromIdx so it ends up at toIdx position - [](Tile* tile, int fromIdx, int toIdx) { - if (!tile) return; - - int from = fromIdx - 1; - int to = toIdx - 1; - int size = (int)tile->items.size(); - - if (from < 0 || from >= size || to < 0 || to >= size || from == to) { - return; - } - - markTileForUndo(tile); - - Item* item = tile->items[from]; - tile->items.erase(tile->items.begin() + from); - - // After erasing from position 'from', indices shift: - // - If to > from: the target position shifted down by 1, so use 'to' directly - // (because we want the item to end up at that visual slot) - // - If to < from: no shift needed, use 'to' directly - // In both cases, clamp to valid range after erase - int insertPos = std::clamp(to, 0, (int)tile->items.size()); - - tile->items.insert(tile->items.begin() + insertPos, item); - tile->modify(); - }, - // Move specific item object to new index in same tile - [](Tile* tile, Item* item, int toIdx) { - if (!tile || !item) return; - auto it = std::find(tile->items.begin(), tile->items.end(), item); - if (it == tile->items.end()) return; - - int from = (int)std::distance(tile->items.begin(), it); - int to = std::clamp(toIdx - 1, 0, (int)tile->items.size() - 1); - if (from == to) return; - - markTileForUndo(tile); - tile->items.erase(it); - - int insertPos = std::clamp(to, 0, (int)tile->items.size()); - tile->items.insert(tile->items.begin() + insertPos, item); - tile->modify(); - }, - // Move item to DIFFERENT tile - [](Tile* sourceTile, Item* item, Tile* destTile, sol::optional toIdx) { - if (!sourceTile || !item || !destTile) return; - auto it = std::find(sourceTile->items.begin(), sourceTile->items.end(), item); - if (it == sourceTile->items.end()) return; - - markTileForUndo(sourceTile); - markTileForUndo(destTile); - - sourceTile->items.erase(it); - int to = toIdx ? std::clamp(*toIdx - 1, 0, (int)destTile->items.size()) : (int)destTile->items.size(); - destTile->items.insert(destTile->items.begin() + to, item); - - sourceTile->modify(); - destTile->modify(); - } - ), + // Index-based move within same tile + // Semantics: Move item at fromIdx so it ends up at toIdx position + [](Tile* tile, int fromIdx, int toIdx) { + if (!tile) { + return; + } + + int from = fromIdx - 1; + int to = toIdx - 1; + int size = (int)tile->items.size(); + + if (from < 0 || from >= size || to < 0 || to >= size || from == to) { + return; + } + + markTileForUndo(tile); + + Item* item = tile->items[from]; + tile->items.erase(tile->items.begin() + from); + + // After erasing from position 'from', indices shift: + // - If to > from: the target position shifted down by 1, so use 'to' directly + // (because we want the item to end up at that visual slot) + // - If to < from: no shift needed, use 'to' directly + // In both cases, clamp to valid range after erase + int insertPos = std::clamp(to, 0, (int)tile->items.size()); + + tile->items.insert(tile->items.begin() + insertPos, item); + tile->modify(); + }, + // Move specific item object to new index in same tile + [](Tile* tile, Item* item, int toIdx) { + if (!tile || !item) { + return; + } + auto it = std::find(tile->items.begin(), tile->items.end(), item); + if (it == tile->items.end()) { + return; + } + + int from = (int)std::distance(tile->items.begin(), it); + int to = std::clamp(toIdx - 1, 0, (int)tile->items.size() - 1); + if (from == to) { + return; + } + + markTileForUndo(tile); + tile->items.erase(it); + + int insertPos = std::clamp(to, 0, (int)tile->items.size()); + tile->items.insert(tile->items.begin() + insertPos, item); + tile->modify(); + }, + // Move item to DIFFERENT tile + [](Tile* sourceTile, Item* item, Tile* destTile, sol::optional toIdx) { + if (!sourceTile || !item || !destTile) { + return; + } + auto it = std::find(sourceTile->items.begin(), sourceTile->items.end(), item); + if (it == sourceTile->items.end()) { + return; + } + + markTileForUndo(sourceTile); + markTileForUndo(destTile); + + sourceTile->items.erase(it); + int to = toIdx ? std::clamp(*toIdx - 1, 0, (int)destTile->items.size()) : (int)destTile->items.size(); + destTile->items.insert(destTile->items.begin() + to, item); + + sourceTile->modify(); + destTile->modify(); + } + ), "getPosition", [](Tile* tile, sol::this_state ts) { sol::state_view lua(ts); @@ -512,38 +509,29 @@ namespace LuaAPI { t["y"] = p.y; t["z"] = p.z; } - return t; - }, + return t; }, "getItemAt", [](Tile* tile, int index) -> Item* { - if (!tile) return nullptr; + if (!tile){ return nullptr; +} // Lua uses 1-based indexing - return tile->getItemAt(index - 1); - }, + return tile->getItemAt(index - 1); }, - "getTopItem", [](Tile* tile) -> Item* { - return tile ? tile->getTopItem() : nullptr; - }, + "getTopItem", [](Tile* tile) -> Item* { return tile ? tile->getTopItem() : nullptr; }, - "getWall", [](Tile* tile) -> Item* { - return tile ? tile->getWall() : nullptr; - }, + "getWall", [](Tile* tile) -> Item* { return tile ? tile->getWall() : nullptr; }, - "getTable", [](Tile* tile) -> Item* { - return tile ? tile->getTable() : nullptr; - }, + "getTable", [](Tile* tile) -> Item* { return tile ? tile->getTable() : nullptr; }, - "getCarpet", [](Tile* tile) -> Item* { - return tile ? tile->getCarpet() : nullptr; - }, + "getCarpet", [](Tile* tile) -> Item* { return tile ? tile->getCarpet() : nullptr; }, // String representation sol::meta_function::to_string, [](Tile* tile) { - if (!tile) return std::string("Tile(invalid)"); + if (!tile){ return std::string("Tile(invalid)"); +} Position pos = tile->getPosition(); return "Tile(" + std::to_string(pos.x) + ", " + - std::to_string(pos.y) + ", " + std::to_string(pos.z) + ")"; - } + std::to_string(pos.y) + ", " + std::to_string(pos.z) + ")"; } ); } } // namespace LuaAPI diff --git a/source/lua/lua_dialog.cpp b/source/lua/lua_dialog.cpp index c2275bc27..c2fb2ef2c 100644 --- a/source/lua/lua_dialog.cpp +++ b/source/lua/lua_dialog.cpp @@ -36,7 +36,7 @@ #include #include #ifdef __WXMSW__ -#include + #include #endif using namespace std::string_literals; @@ -63,8 +63,12 @@ class MapPreviewCanvas : public MapCanvas { // Overrides to decouple from MapWindow void SetZoom(double value) override { - if (value < 0.125) value = 0.125; - if (value > 25.00) value = 25.0; + if (value < 0.125) { + value = 0.125; + } + if (value > 25.00) { + value = 25.0; + } zoom = value; wxGLCanvas::Refresh(); } @@ -110,8 +114,12 @@ class MapPreviewCanvas : public MapCanvas { int height = size.GetHeight(); // If not yet laid out, use a default size to avoid bad coordinates - if (width <= 0) width = 400; - if (height <= 0) height = 300; + if (width <= 0) { + width = 400; + } + if (height <= 0) { + height = 300; + } view_x = (x * 32) - (width * zoom) / 2; view_y = (y * 32) - (height * zoom) / 2; @@ -120,8 +128,8 @@ class MapPreviewCanvas : public MapCanvas { } // Disable status bar updates for preview canvases - void UpdatePositionStatus(int x = -1, int y = -1) {} - void UpdateZoomStatus() {} + void UpdatePositionStatus(int x = -1, int y = -1) { } + void UpdateZoomStatus() { } void Refresh() { wxGLCanvas::Refresh(); } @@ -135,14 +143,20 @@ class MapPreviewCanvas : public MapCanvas { // Do nothing - disabled to prevent confusion and potential issues } - void OnGainMouse(wxMouseEvent& event) { Refresh(); } - void OnLoseMouse(wxMouseEvent& event) { Refresh(); } - void OnMouseLeftClick(wxMouseEvent& event) { SetFocus(); } - void OnMouseLeftRelease(wxMouseEvent& event) {} - void OnMouseRightClick(wxMouseEvent& event) {} - void OnMouseRightRelease(wxMouseEvent& event) {} - void OnMouseCenterClick(wxMouseEvent& event) {} - void OnMouseCenterRelease(wxMouseEvent& event) {} + void OnGainMouse(wxMouseEvent& event) { + Refresh(); + } + void OnLoseMouse(wxMouseEvent& event) { + Refresh(); + } + void OnMouseLeftClick(wxMouseEvent& event) { + SetFocus(); + } + void OnMouseLeftRelease(wxMouseEvent& event) { } + void OnMouseRightClick(wxMouseEvent& event) { } + void OnMouseRightRelease(wxMouseEvent& event) { } + void OnMouseCenterClick(wxMouseEvent& event) { } + void OnMouseCenterRelease(wxMouseEvent& event) { } void SetViewSize(int w, int h) { client_w = w; @@ -173,7 +187,9 @@ class MapPreviewCanvas : public MapCanvas { void SyncView() { Editor* current_editor = g_gui.GetCurrentEditor(); - if (!current_editor) return; + if (!current_editor) { + return; + } MapTab* tab = g_gui.GetCurrentMapTab(); if (tab) { @@ -196,8 +212,12 @@ class MapPreviewCanvas : public MapCanvas { return (view_y + (height * (double)zoom) / 2.0) / 32; } - int GetClientWidth() const override { return client_w; } - int GetClientHeight() const override { return client_h; } + int GetClientWidth() const override { + return client_w; + } + int GetClientHeight() const override { + return client_h; + } private: int view_x; @@ -210,37 +230,32 @@ class MapPreviewCanvas : public MapCanvas { }; BEGIN_EVENT_TABLE(MapPreviewCanvas, MapCanvas) - EVT_MOTION(MapPreviewCanvas::OnMouseMove) - EVT_MOUSEWHEEL(MapPreviewCanvas::OnWheel) - EVT_ENTER_WINDOW(MapPreviewCanvas::OnGainMouse) - EVT_LEAVE_WINDOW(MapPreviewCanvas::OnLoseMouse) - EVT_LEFT_DOWN(MapPreviewCanvas::OnMouseLeftClick) - EVT_LEFT_UP(MapPreviewCanvas::OnMouseLeftRelease) - EVT_RIGHT_DOWN(MapPreviewCanvas::OnMouseRightClick) - EVT_RIGHT_UP(MapPreviewCanvas::OnMouseRightRelease) - EVT_MIDDLE_DOWN(MapPreviewCanvas::OnMouseCenterClick) - EVT_MIDDLE_UP(MapPreviewCanvas::OnMouseCenterRelease) - EVT_PAINT(MapCanvas::OnPaint) +EVT_MOTION(MapPreviewCanvas::OnMouseMove) +EVT_MOUSEWHEEL(MapPreviewCanvas::OnWheel) +EVT_ENTER_WINDOW(MapPreviewCanvas::OnGainMouse) +EVT_LEAVE_WINDOW(MapPreviewCanvas::OnLoseMouse) +EVT_LEFT_DOWN(MapPreviewCanvas::OnMouseLeftClick) +EVT_LEFT_UP(MapPreviewCanvas::OnMouseLeftRelease) +EVT_RIGHT_DOWN(MapPreviewCanvas::OnMouseRightClick) +EVT_RIGHT_UP(MapPreviewCanvas::OnMouseRightRelease) +EVT_MIDDLE_DOWN(MapPreviewCanvas::OnMouseCenterClick) +EVT_MIDDLE_UP(MapPreviewCanvas::OnMouseCenterRelease) +EVT_PAINT(MapCanvas::OnPaint) END_EVENT_TABLE() BEGIN_EVENT_TABLE(LuaDialog, wxDialog) - EVT_CLOSE(LuaDialog::OnClose) +EVT_CLOSE(LuaDialog::OnClose) END_EVENT_TABLE() LuaDialog::LuaDialog(const std::string& title, sol::this_state ts) : - wxDialog(g_gui.root, wxID_ANY, wxString(title), wxDefaultPosition, wxDefaultSize, - wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + wxDialog(g_gui.root, wxID_ANY, wxString(title), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), lua(ts) { createLayout(); } // Overload constructor to handle options LuaDialog::LuaDialog(sol::table options, sol::this_state ts) : - wxDialog(g_gui.root, wxID_ANY, wxString(options.get_or("title", "Script Dialog"s)), - wxDefaultPosition, wxDefaultSize, - wxDEFAULT_DIALOG_STYLE | - (options.get_or("resizable", true) ? wxRESIZE_BORDER : 0) | - (options.get_or("topmost", false) ? wxSTAY_ON_TOP : 0)), + wxDialog(g_gui.root, wxID_ANY, wxString(options.get_or("title", "Script Dialog"s)), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | (options.get_or("resizable", true) ? wxRESIZE_BORDER : 0) | (options.get_or("topmost", false) ? wxSTAY_ON_TOP : 0)), lua(ts) { reqWidth = options.get_or("width", -1); @@ -481,8 +496,7 @@ LuaDialog* LuaDialog::input(sol::table options) { currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); } - wxTextCtrl* input = new wxTextCtrl(getParentForWidget(), wxID_ANY, wxString(text), - wxDefaultPosition, wxSize(150, -1)); + wxTextCtrl* input = new wxTextCtrl(getParentForWidget(), wxID_ANY, wxString(text), wxDefaultPosition, wxSize(150, -1)); currentRowSizer->Add(input, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); input->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent& event) { @@ -534,8 +548,7 @@ LuaDialog* LuaDialog::number(sol::table options) { currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); } - wxSpinCtrlDouble* spin = new wxSpinCtrlDouble(getParentForWidget(), wxID_ANY, "", - wxDefaultPosition, wxSize(100, -1), wxSP_ARROW_KEYS, minVal, maxVal, value, decimals == 0 ? 1 : std::pow(0.1, decimals)); + wxSpinCtrlDouble* spin = new wxSpinCtrlDouble(getParentForWidget(), wxID_ANY, "", wxDefaultPosition, wxSize(100, -1), wxSP_ARROW_KEYS, minVal, maxVal, value, decimals == 0 ? 1 : std::pow(0.1, decimals)); spin->SetDigits(decimals); currentRowSizer->Add(spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); @@ -572,8 +585,7 @@ LuaDialog* LuaDialog::slider(sol::table options) { currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); } - wxSlider* slider = new wxSlider(getParentForWidget(), wxID_ANY, value, minVal, maxVal, - wxDefaultPosition, wxSize(150, -1)); + wxSlider* slider = new wxSlider(getParentForWidget(), wxID_ANY, value, minVal, maxVal, wxDefaultPosition, wxSize(150, -1)); currentRowSizer->Add(slider, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); LuaDialogWidget widget; @@ -680,8 +692,7 @@ LuaDialog* LuaDialog::combobox(sol::table options) { currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); } - wxChoice* choice = new wxChoice(getParentForWidget(), wxID_ANY, wxDefaultPosition, - wxSize(150, -1), choices); + wxChoice* choice = new wxChoice(getParentForWidget(), wxID_ANY, wxDefaultPosition, wxSize(150, -1), choices); if (!selected.empty()) { int idx = choice->FindString(wxString(selected)); @@ -875,7 +886,6 @@ class LuaDialogListBox : public wxVListBox { event.Skip(); } - void OnLeftDouble(wxMouseEvent& event) { int n = HitTest(event.GetPosition()); if (n != wxNOT_FOUND) { @@ -917,7 +927,9 @@ class LuaDialogListBox : public wxVListBox { } virtual void OnDrawItem(wxDC& dc, const wxRect& rect, size_t n) const override { - if (n >= items.size()) return; + if (n >= items.size()) { + return; + } const LuaListBoxItem& item = items[n]; bool isSelected = IsSelected(n); @@ -1027,8 +1039,6 @@ class LuaGridCtrl : public wxListCtrl { } }; - - LuaDialog* LuaDialog::list(sol::table options) { finishCurrentRow(); @@ -1238,13 +1248,9 @@ LuaDialog* LuaDialog::file(sol::table options) { wxFilePickerCtrl* picker; if (save) { - picker = new wxFilePickerCtrl(getParentForWidget(), wxID_ANY, wxString(filename), - "Select a file", wxString(filetypes), wxDefaultPosition, wxSize(200, -1), - wxFLP_SAVE | wxFLP_USE_TEXTCTRL); + picker = new wxFilePickerCtrl(getParentForWidget(), wxID_ANY, wxString(filename), "Select a file", wxString(filetypes), wxDefaultPosition, wxSize(200, -1), wxFLP_SAVE | wxFLP_USE_TEXTCTRL); } else { - picker = new wxFilePickerCtrl(getParentForWidget(), wxID_ANY, wxString(filename), - "Select a file", wxString(filetypes), wxDefaultPosition, wxSize(200, -1), - wxFLP_OPEN | wxFLP_FILE_MUST_EXIST | wxFLP_USE_TEXTCTRL); + picker = new wxFilePickerCtrl(getParentForWidget(), wxID_ANY, wxString(filename), "Select a file", wxString(filetypes), wxDefaultPosition, wxSize(200, -1), wxFLP_OPEN | wxFLP_FILE_MUST_EXIST | wxFLP_USE_TEXTCTRL); } currentRowSizer->Add(picker, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); @@ -1413,8 +1419,7 @@ LuaDialog* LuaDialog::item(sol::table options) { LuaDialog* LuaDialog::separator() { finishCurrentRow(); - wxStaticLine* line = new wxStaticLine(getParentForWidget(), wxID_ANY, - wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); + wxStaticLine* line = new wxStaticLine(getParentForWidget(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); getSizerForWidget()->Add(line, 0, wxEXPAND | wxALL, 5); // Separator doesn't support many options but we can try @@ -1730,8 +1735,12 @@ LuaDialog* LuaDialog::show(sol::optional options) { // Re-apply requested size from constructor if it's larger than fitted size if (reqWidth != -1 || reqHeight != -1) { wxSize sz = GetSize(); - if (reqWidth != -1) sz.x = std::max(sz.x, reqWidth); - if (reqHeight != -1) sz.y = std::max(sz.y, reqHeight); + if (reqWidth != -1) { + sz.x = std::max(sz.x, reqWidth); + } + if (reqHeight != -1) { + sz.y = std::max(sz.y, reqHeight); + } SetSize(sz); } @@ -1951,85 +1960,85 @@ LuaDialog* LuaDialog::modify(sol::table options) { } else { ctrl->SetSelection(wxNOT_FOUND); } - } + } - } else if (widget.type == "grid") { - LuaGridCtrl* ctrl = static_cast(widget.widget); - int iconWidth = ctrl->iconWidth; - int iconHeight = ctrl->iconHeight; - int cellWidth = ctrl->cellWidth; - int cellHeight = ctrl->cellHeight; - bool updateIconSize = false; - bool updateCellSize = false; - - int iconSize = props.get_or("icon_size", -1); - int itemSize = props.get_or("item_size", -1); - int itemWidth = props.get_or("item_width", -1); - int itemHeight = props.get_or("item_height", -1); - int iconWidthOpt = props.get_or("icon_width", -1); - int iconHeightOpt = props.get_or("icon_height", -1); - int cellSize = props.get_or("cell_size", -1); - int cellWidthOpt = props.get_or("cell_width", -1); - int cellHeightOpt = props.get_or("cell_height", -1); - - if (iconSize > 0) { - iconWidth = iconSize; - iconHeight = iconSize; - updateIconSize = true; - } - if (itemSize > 0) { - iconWidth = itemSize; - iconHeight = itemSize; - updateIconSize = true; - } - if (itemWidth > 0) { - iconWidth = itemWidth; - updateIconSize = true; - } - if (itemHeight > 0) { - iconHeight = itemHeight; - updateIconSize = true; - } - if (iconWidthOpt > 0) { - iconWidth = iconWidthOpt; - updateIconSize = true; - } - if (iconHeightOpt > 0) { - iconHeight = iconHeightOpt; - updateIconSize = true; - } + } else if (widget.type == "grid") { + LuaGridCtrl* ctrl = static_cast(widget.widget); + int iconWidth = ctrl->iconWidth; + int iconHeight = ctrl->iconHeight; + int cellWidth = ctrl->cellWidth; + int cellHeight = ctrl->cellHeight; + bool updateIconSize = false; + bool updateCellSize = false; + + int iconSize = props.get_or("icon_size", -1); + int itemSize = props.get_or("item_size", -1); + int itemWidth = props.get_or("item_width", -1); + int itemHeight = props.get_or("item_height", -1); + int iconWidthOpt = props.get_or("icon_width", -1); + int iconHeightOpt = props.get_or("icon_height", -1); + int cellSize = props.get_or("cell_size", -1); + int cellWidthOpt = props.get_or("cell_width", -1); + int cellHeightOpt = props.get_or("cell_height", -1); + + if (iconSize > 0) { + iconWidth = iconSize; + iconHeight = iconSize; + updateIconSize = true; + } + if (itemSize > 0) { + iconWidth = itemSize; + iconHeight = itemSize; + updateIconSize = true; + } + if (itemWidth > 0) { + iconWidth = itemWidth; + updateIconSize = true; + } + if (itemHeight > 0) { + iconHeight = itemHeight; + updateIconSize = true; + } + if (iconWidthOpt > 0) { + iconWidth = iconWidthOpt; + updateIconSize = true; + } + if (iconHeightOpt > 0) { + iconHeight = iconHeightOpt; + updateIconSize = true; + } - if (cellSize > 0) { - cellWidth = cellSize; - cellHeight = cellSize; - updateCellSize = true; - } - if (cellWidthOpt > 0) { - cellWidth = cellWidthOpt; - updateCellSize = true; - } - if (cellHeightOpt > 0) { - cellHeight = cellHeightOpt; - updateCellSize = true; - } + if (cellSize > 0) { + cellWidth = cellSize; + cellHeight = cellSize; + updateCellSize = true; + } + if (cellWidthOpt > 0) { + cellWidth = cellWidthOpt; + updateCellSize = true; + } + if (cellHeightOpt > 0) { + cellHeight = cellHeightOpt; + updateCellSize = true; + } - if (updateCellSize) { - if (cellWidth <= 0) { - cellWidth = iconWidth + 8; - } - if (cellHeight <= 0) { - cellHeight = iconHeight + 8; - } + if (updateCellSize) { + if (cellWidth <= 0) { + cellWidth = iconWidth + 8; + } + if (cellHeight <= 0) { + cellHeight = iconHeight + 8; + } #ifdef __WXMSW__ - ListView_SetIconSpacing(static_cast(ctrl->GetHandle()), cellWidth, cellHeight); + ListView_SetIconSpacing(static_cast(ctrl->GetHandle()), cellWidth, cellHeight); #endif - ctrl->cellWidth = cellWidth; - ctrl->cellHeight = cellHeight; - } - if (props["selection"].valid()) { - int selection = props.get_or("selection", 0); - long item = -1; - while ((item = ctrl->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) { + ctrl->cellWidth = cellWidth; + ctrl->cellHeight = cellHeight; + } + if (props["selection"].valid()) { + int selection = props.get_or("selection", 0); + long item = -1; + while ((item = ctrl->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) { ctrl->SetItemState(item, 0, wxLIST_STATE_SELECTED); } if (selection > 0 && selection <= ctrl->GetItemCount()) { @@ -2037,25 +2046,24 @@ LuaDialog* LuaDialog::modify(sol::table options) { } } + if (props["items"].valid()) { + ctrl->Freeze(); + ctrl->DeleteAllItems(); + ctrl->ClearTooltips(); + wxImageList* imageList = ctrl->GetImageList(wxIMAGE_LIST_NORMAL); + if (!imageList || updateIconSize) { + imageList = new wxImageList(iconWidth, iconHeight, true); + ctrl->AssignImageList(imageList, wxIMAGE_LIST_NORMAL); + ctrl->iconWidth = iconWidth; + ctrl->iconHeight = iconHeight; + } else { + imageList->RemoveAll(); + } - if (props["items"].valid()) { - ctrl->Freeze(); - ctrl->DeleteAllItems(); - ctrl->ClearTooltips(); - wxImageList* imageList = ctrl->GetImageList(wxIMAGE_LIST_NORMAL); - if (!imageList || updateIconSize) { - imageList = new wxImageList(iconWidth, iconHeight, true); - ctrl->AssignImageList(imageList, wxIMAGE_LIST_NORMAL); - ctrl->iconWidth = iconWidth; - ctrl->iconHeight = iconHeight; - } else { - imageList->RemoveAll(); - } - - sol::table itemsTable = props["items"]; - for (auto& pair : itemsTable) { - if (pair.second.is()) { - sol::table itemTable = pair.second; + sol::table itemsTable = props["items"]; + for (auto& pair : itemsTable) { + if (pair.second.is()) { + sol::table itemTable = pair.second; std::string text = itemTable.get_or("text", ""s); LuaAPI::LuaImage img; @@ -2066,7 +2074,7 @@ LuaDialog* LuaDialog::modify(sol::table options) { img = itemTable["image"].get(); } else if (itemTable["image"].is()) { img = LuaAPI::LuaImage::loadFromFile(itemTable["image"].get()); - } + } } if (img.isValid()) { @@ -2203,8 +2211,7 @@ LuaDialog* LuaDialog::grid(sol::table options) { LONG_PTR style = ::GetWindowLongPtr(hwnd, GWL_STYLE); style |= LVS_NOLABELWRAP; ::SetWindowLongPtr(hwnd, GWL_STYLE, style); - ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, - SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); + ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); } if (cellWidth > 0 || cellHeight > 0) { @@ -2391,8 +2398,6 @@ LuaDialog* LuaDialog::grid(sol::table options) { return this; } - - void LuaDialog::repaint() { Refresh(); Update(); @@ -2652,58 +2657,20 @@ void LuaDialog::onWidgetDoubleClick(const std::string& id) { namespace LuaAPI { -void registerDialog(sol::state& lua) { - // Register Dialog class with constructor and method chaining - lua.new_usertype("Dialog", - // Constructor: Dialog{title = "...", topmost = true} - sol::call_constructor, sol::factories( - [](sol::table options, sol::this_state ts) { - return new LuaDialog(options, ts); - }, - [](const std::string& title, sol::this_state ts) { - return new LuaDialog(title, ts); - } - ), - - // Widget methods (return self for chaining) - "label", &LuaDialog::label, - "input", &LuaDialog::input, - "number", &LuaDialog::number, - "slider", &LuaDialog::slider, - "check", &LuaDialog::check, - "radio", &LuaDialog::radio, - "combobox", &LuaDialog::combobox, - "button", &LuaDialog::button, - "color", &LuaDialog::color, - "item", &LuaDialog::item, - "file", &LuaDialog::file, - "image", &LuaDialog::image, - "separator", &LuaDialog::separator, - "newrow", &LuaDialog::newrow, - "tab", &LuaDialog::tab, - "endtabs", &LuaDialog::endtabs, - "wrap", &LuaDialog::wrap, - "endwrap", &LuaDialog::endwrap, - "box", &LuaDialog::box, - "endbox", &LuaDialog::endbox, - "mapCanvas", &LuaDialog::mapCanvas, - "list", &LuaDialog::list, - "grid", &LuaDialog::grid, - - // Dialog control - "show", &LuaDialog::show, - "close", &LuaDialog::close, - "modify", &LuaDialog::modify, - "repaint", &LuaDialog::repaint, - "clear", &LuaDialog::clear, - "layout", &LuaDialog::layout, - - // Properties - "data", sol::property(&LuaDialog::getData, &LuaDialog::setData), - "bounds", sol::property(&LuaDialog::getBounds, &LuaDialog::setBounds), - "dockable", sol::property(&LuaDialog::isDockable), - "activeTab", sol::property(&LuaDialog::getActiveTab) - ); -} + void registerDialog(sol::state& lua) { + // Register Dialog class with constructor and method chaining + lua.new_usertype("Dialog", + // Constructor: Dialog{title = "...", topmost = true} + sol::call_constructor, sol::factories([](sol::table options, sol::this_state ts) { return new LuaDialog(options, ts); }, [](const std::string& title, sol::this_state ts) { return new LuaDialog(title, ts); }), + + // Widget methods (return self for chaining) + "label", &LuaDialog::label, "input", &LuaDialog::input, "number", &LuaDialog::number, "slider", &LuaDialog::slider, "check", &LuaDialog::check, "radio", &LuaDialog::radio, "combobox", &LuaDialog::combobox, "button", &LuaDialog::button, "color", &LuaDialog::color, "item", &LuaDialog::item, "file", &LuaDialog::file, "image", &LuaDialog::image, "separator", &LuaDialog::separator, "newrow", &LuaDialog::newrow, "tab", &LuaDialog::tab, "endtabs", &LuaDialog::endtabs, "wrap", &LuaDialog::wrap, "endwrap", &LuaDialog::endwrap, "box", &LuaDialog::box, "endbox", &LuaDialog::endbox, "mapCanvas", &LuaDialog::mapCanvas, "list", &LuaDialog::list, "grid", &LuaDialog::grid, + + // Dialog control + "show", &LuaDialog::show, "close", &LuaDialog::close, "modify", &LuaDialog::modify, "repaint", &LuaDialog::repaint, "clear", &LuaDialog::clear, "layout", &LuaDialog::layout, + + // Properties + "data", sol::property(&LuaDialog::getData, &LuaDialog::setData), "bounds", sol::property(&LuaDialog::getBounds, &LuaDialog::setBounds), "dockable", sol::property(&LuaDialog::isDockable), "activeTab", sol::property(&LuaDialog::getActiveTab)); + } } // namespace LuaAPI diff --git a/source/lua/lua_dialog.h b/source/lua/lua_dialog.h index 54b614626..99218133d 100644 --- a/source/lua/lua_dialog.h +++ b/source/lua/lua_dialog.h @@ -124,7 +124,9 @@ class LuaDialog : public wxDialog { void setBounds(sol::table bounds); sol::object getActiveTab(); - bool isDockable() const { return dockPanel != nullptr; } + bool isDockable() const { + return dockPanel != nullptr; + } private: sol::state_view lua; diff --git a/source/lua/lua_engine.cpp b/source/lua/lua_engine.cpp index 48ff5543a..a7c9a8bcb 100644 --- a/source/lua/lua_engine.cpp +++ b/source/lua/lua_engine.cpp @@ -21,7 +21,8 @@ #include #include -LuaEngine::LuaEngine() : initialized(false) { +LuaEngine::LuaEngine() : + initialized(false) { } LuaEngine::~LuaEngine() { @@ -121,11 +122,7 @@ void LuaEngine::registerBaseLibraries() { }; // Register MouseButton enum - lua.new_enum("MouseButton", - "LEFT", 1, - "RIGHT", 2, - "MIDDLE", 3 - ); + lua.new_enum("MouseButton", "LEFT", 1, "RIGHT", 2, "MIDDLE", 3); } void LuaEngine::setPrintCallback(PrintCallback callback) { diff --git a/source/lua/lua_engine.h b/source/lua/lua_engine.h index 33be324dd..9503ab658 100644 --- a/source/lua/lua_engine.h +++ b/source/lua/lua_engine.h @@ -36,22 +36,32 @@ class LuaEngine { void shutdown(); // Check if engine is ready - bool isInitialized() const { return initialized; } + bool isInitialized() const { + return initialized; + } // Script execution bool executeFile(const std::string& filepath); bool executeString(const std::string& code, const std::string& chunkName = "chunk"); // Get underlying sol state for API registration - sol::state& getState() { return lua; } - const sol::state& getState() const { return lua; } + sol::state& getState() { + return lua; + } + const sol::state& getState() const { + return lua; + } // Error handling - std::string getLastError() const { return lastError; } - void clearError() { lastError.clear(); } + std::string getLastError() const { + return lastError; + } + void clearError() { + lastError.clear(); + } // Safe call wrapper with error handling - template + template bool safeCall(Func&& func) { try { auto result = func(); @@ -71,7 +81,7 @@ class LuaEngine { } // Safe call that doesn't return a result - template + template bool safeCallVoid(Func&& func) { try { func(); diff --git a/source/lua/lua_script.cpp b/source/lua/lua_script.cpp index fe0cfd7b3..fc48b2811 100644 --- a/source/lua/lua_script.cpp +++ b/source/lua/lua_script.cpp @@ -133,11 +133,15 @@ void LuaScript::parseMetadataFromManifest() { quote = '\''; } - if (pos == std::string::npos) return ""; + if (pos == std::string::npos) { + return ""; + } size_t valueStart = content.find(quote, pos) + 1; size_t valueEnd = content.find(quote, valueStart); - if (valueEnd == std::string::npos) return ""; + if (valueEnd == std::string::npos) { + return ""; + } return content.substr(valueStart, valueEnd - valueStart); }; @@ -145,23 +149,35 @@ void LuaScript::parseMetadataFromManifest() { std::string val; val = getValue("name"); - if (!val.empty()) displayName = val; + if (!val.empty()) { + displayName = val; + } val = getValue("description"); - if (!val.empty()) description = val; + if (!val.empty()) { + description = val; + } val = getValue("author"); - if (!val.empty()) author = val; + if (!val.empty()) { + author = val; + } val = getValue("version"); - if (!val.empty()) version = val; + if (!val.empty()) { + version = val; + } val = getValue("shortcut"); - if (!val.empty()) shortcut = val; + if (!val.empty()) { + shortcut = val; + } val = getValue("autorun"); if (!val.empty()) { - if (val == "true") autorun = true; + if (val == "true") { + autorun = true; + } } else { // Also check boolean literal if (content.find("autorun = true") != std::string::npos || content.find("autorun=true") != std::string::npos) { @@ -238,35 +254,49 @@ void LuaScript::parseMetadataFromComments() { if (comment.size() > 7 && comment.substr(0, 7) == "@Title:") { displayName = comment.substr(7); size_t s = displayName.find_first_not_of(" \t"); - if (s != std::string::npos) displayName = displayName.substr(s); + if (s != std::string::npos) { + displayName = displayName.substr(s); + } foundName = true; continue; } else if (comment.size() > 13 && comment.substr(0, 13) == "@Description:") { std::string descPart = comment.substr(13); size_t s = descPart.find_first_not_of(" \t"); - if (s != std::string::npos) descPart = descPart.substr(s); + if (s != std::string::npos) { + descPart = descPart.substr(s); + } - if (descBuilder.tellp() > 0) descBuilder << " "; + if (descBuilder.tellp() > 0) { + descBuilder << " "; + } descBuilder << descPart; continue; } else if (comment.size() > 8 && comment.substr(0, 8) == "@Author:") { author = comment.substr(8); size_t s = author.find_first_not_of(" \t"); - if (s != std::string::npos) author = author.substr(s); + if (s != std::string::npos) { + author = author.substr(s); + } continue; } else if (comment.size() > 9 && comment.substr(0, 9) == "@Version:") { version = comment.substr(9); size_t s = version.find_first_not_of(" \t"); - if (s != std::string::npos) version = version.substr(s); + if (s != std::string::npos) { + version = version.substr(s); + } continue; } else if (comment.size() > 10 && comment.substr(0, 10) == "@Shortcut:") { shortcut = comment.substr(10); size_t s = shortcut.find_first_not_of(" \t"); - if (s != std::string::npos) shortcut = shortcut.substr(s); + if (s != std::string::npos) { + shortcut = shortcut.substr(s); + } continue; } else if (comment.size() > 9 && (comment.substr(0, 9) == "@AutoRun:" || comment.substr(0, 9) == "@Autorun:")) { std::string ar = comment.substr(9); - if (ar.find("true") != std::string::npos) autorun = true; + if (ar.find("true") != std::string::npos) { + autorun = true; + } continue; } diff --git a/source/lua/lua_script.h b/source/lua/lua_script.h index 48c2a0f1f..9198db095 100644 --- a/source/lua/lua_script.h +++ b/source/lua/lua_script.h @@ -32,22 +32,46 @@ class LuaScript { ~LuaScript() = default; // Script info - const std::string& getFilePath() const { return filepath; } - const std::string& getDirectory() const { return directory; } - const std::string& getFileName() const { return filename; } - const std::string& getDisplayName() const { return displayName; } - const std::string& getDescription() const { return description; } - const std::string& getAuthor() const { return author; } - const std::string& getVersion() const { return version; } - const std::string& getShortcut() const { return shortcut; } - bool shouldAutoRun() const { return autorun; } + const std::string& getFilePath() const { + return filepath; + } + const std::string& getDirectory() const { + return directory; + } + const std::string& getFileName() const { + return filename; + } + const std::string& getDisplayName() const { + return displayName; + } + const std::string& getDescription() const { + return description; + } + const std::string& getAuthor() const { + return author; + } + const std::string& getVersion() const { + return version; + } + const std::string& getShortcut() const { + return shortcut; + } + bool shouldAutoRun() const { + return autorun; + } // Is this a directory-based script with manifest? - bool isPackage() const { return isPackageScript; } + bool isPackage() const { + return isPackageScript; + } // State - bool isEnabled() const { return enabled; } - void setEnabled(bool value) { enabled = value; } + bool isEnabled() const { + return enabled; + } + void setEnabled(bool value) { + enabled = value; + } // Parse metadata from comments or manifest.json void parseMetadata(); @@ -56,17 +80,17 @@ class LuaScript { void parseMetadataFromComments(); void parseMetadataFromManifest(); - std::string filepath; // Full path to the main .lua file - std::string directory; // Directory containing the script (for packages) - std::string filename; // Just the filename - std::string displayName; // Display name (from metadata or filename) - std::string description; // Description - std::string author; // Author - std::string version; // Version - std::string shortcut; // Keyboard shortcut + std::string filepath; // Full path to the main .lua file + std::string directory; // Directory containing the script (for packages) + std::string filename; // Just the filename + std::string displayName; // Display name (from metadata or filename) + std::string description; // Description + std::string author; // Author + std::string version; // Version + std::string shortcut; // Keyboard shortcut bool enabled; bool autorun; - bool isPackageScript; // True if loaded from directory with manifest.json + bool isPackageScript; // True if loaded from directory with manifest.json }; #endif // RME_LUA_SCRIPT_H diff --git a/source/lua/lua_script_manager.cpp b/source/lua/lua_script_manager.cpp index 44e65a6f6..5c3a2c794 100644 --- a/source/lua/lua_script_manager.cpp +++ b/source/lua/lua_script_manager.cpp @@ -27,8 +27,8 @@ #include #ifdef _WIN32 -#include -#include + #include + #include #endif LuaScriptManager& LuaScriptManager::getInstance() { @@ -102,15 +102,13 @@ bool LuaScriptManager::removeEventListener(int listenerId) { return false; } - - void LuaScriptManager::clearAllCallbacks() { contextMenuItems.clear(); eventListeners.clear(); nextListenerId = 1; mapOverlays.clear(); mapOverlayShows.clear(); - mapOverlayHover = MapOverlayHoverState{}; + mapOverlayHover = MapOverlayHoverState {}; } void LuaScriptManager::registerAPIs() { @@ -129,10 +127,18 @@ static wxColor parseColor(const sol::object& obj, const wxColor& fallback) { int g = tbl.get_or("g", tbl.get_or("green", 255)); int b = tbl.get_or("b", tbl.get_or("blue", 255)); int a = tbl.get_or("a", tbl.get_or("alpha", 255)); - if (tbl[1].valid()) r = tbl.get_or(1, r); - if (tbl[2].valid()) g = tbl.get_or(2, g); - if (tbl[3].valid()) b = tbl.get_or(3, b); - if (tbl[4].valid()) a = tbl.get_or(4, a); + if (tbl[1].valid()) { + r = tbl.get_or(1, r); + } + if (tbl[2].valid()) { + g = tbl.get_or(2, g); + } + if (tbl[3].valid()) { + b = tbl.get_or(3, b); + } + if (tbl[4].valid()) { + a = tbl.get_or(4, a); + } return wxColor(r, g, b, a); } @@ -298,7 +304,9 @@ void LuaScriptManager::collectMapOverlayCommands(const MapViewInfo& view, std::v ctx["rect"] = [&, getOptsTable](sol::variadic_args va) { sol::table opts = getOptsTable(va); - if (!opts.valid()) return; + if (!opts.valid()) { + return; + } MapOverlayCommand cmd; cmd.type = MapOverlayCommand::Type::Rect; cmd.screen_space = opts.get_or("screen", false); @@ -315,7 +323,9 @@ void LuaScriptManager::collectMapOverlayCommands(const MapViewInfo& view, std::v ctx["line"] = [&, getOptsTable](sol::variadic_args va) { sol::table opts = getOptsTable(va); - if (!opts.valid()) return; + if (!opts.valid()) { + return; + } MapOverlayCommand cmd; cmd.type = MapOverlayCommand::Type::Line; cmd.screen_space = opts.get_or("screen", false); @@ -332,7 +342,9 @@ void LuaScriptManager::collectMapOverlayCommands(const MapViewInfo& view, std::v ctx["text"] = [&, getOptsTable](sol::variadic_args va) { sol::table opts = getOptsTable(va); - if (!opts.valid()) return; + if (!opts.valid()) { + return; + } MapOverlayCommand cmd; cmd.type = MapOverlayCommand::Type::Text; cmd.screen_space = opts.get_or("screen", false); @@ -367,7 +379,7 @@ void LuaScriptManager::collectMapOverlayCommands(const MapViewInfo& view, std::v } void LuaScriptManager::updateMapOverlayHover(int map_x, int map_y, int map_z, int screen_x, int screen_y, Tile* tile, Item* topItem) { - mapOverlayHover = MapOverlayHoverState{}; + mapOverlayHover = MapOverlayHoverState {}; if (!initialized) { return; } @@ -484,10 +496,9 @@ void LuaScriptManager::discoverScripts() { scanDirectory(scriptsDir); // Sort scripts by display name - std::sort(scripts.begin(), scripts.end(), - [](const std::unique_ptr& a, const std::unique_ptr& b) { - return a->getDisplayName() < b->getDisplayName(); - }); + std::sort(scripts.begin(), scripts.end(), [](const std::unique_ptr& a, const std::unique_ptr& b) { + return a->getDisplayName() < b->getDisplayName(); + }); runAutoScripts(); } diff --git a/source/lua/lua_script_manager.h b/source/lua/lua_script_manager.h index 1b69df6b8..56583a097 100644 --- a/source/lua/lua_script_manager.h +++ b/source/lua/lua_script_manager.h @@ -45,7 +45,9 @@ class LuaScriptManager { // Lifecycle bool initialize(); void shutdown(); - bool isInitialized() const { return initialized; } + bool isInitialized() const { + return initialized; + } // Script discovery and management void discoverScripts(); @@ -57,7 +59,9 @@ class LuaScriptManager { bool executeScript(size_t index, std::string& errorOut); // Access scripts - const std::vector>& getScripts() const { return scripts; } + const std::vector>& getScripts() const { + return scripts; + } LuaScript* getScript(const std::string& filepath); // Enable/disable scripts @@ -65,10 +69,14 @@ class LuaScriptManager { bool isScriptEnabled(size_t index) const; // Engine access - LuaEngine& getEngine() { return engine; } + LuaEngine& getEngine() { + return engine; + } // Error handling - std::string getLastError() const { return lastError; } + std::string getLastError() const { + return lastError; + } // Scripts directory std::string getScriptsDirectory() const; @@ -84,7 +92,9 @@ class LuaScriptManager { sol::function callback; }; void registerContextMenuItem(const std::string& label, sol::function callback); - const std::vector& getContextMenuItems() const { return contextMenuItems; } + const std::vector& getContextMenuItems() const { + return contextMenuItems; + } // Event System struct EventListener { @@ -97,7 +107,9 @@ class LuaScriptManager { template void emit(const std::string& eventName, Args&&... args) { - if (!initialized) return; + if (!initialized) { + return; + } for (auto& listener : eventListeners) { if (listener.eventName == eventName && listener.callback.valid()) { @@ -133,10 +145,14 @@ class LuaScriptManager { bool registerMapOverlayShow(const std::string& label, const std::string& overlayId, bool enabled, sol::function ontoggle); bool setMapOverlayShowEnabled(const std::string& overlayId, bool enabled); bool isMapOverlayEnabled(const std::string& id) const; - const std::vector& getMapOverlayShows() const { return mapOverlayShows; } + const std::vector& getMapOverlayShows() const { + return mapOverlayShows; + } void collectMapOverlayCommands(const MapViewInfo& view, std::vector& out); void updateMapOverlayHover(int map_x, int map_y, int map_z, int screen_x, int screen_y, Tile* tile, Item* topItem); - const MapOverlayHoverState& getMapOverlayHover() const { return mapOverlayHover; } + const MapOverlayHoverState& getMapOverlayHover() const { + return mapOverlayHover; + } private: LuaScriptManager() = default; diff --git a/source/lua/lua_scripts_window.cpp b/source/lua/lua_scripts_window.cpp index 92eb3ae0e..7db8071b8 100644 --- a/source/lua/lua_scripts_window.cpp +++ b/source/lua/lua_scripts_window.cpp @@ -27,12 +27,12 @@ LuaScriptsWindow* LuaScriptsWindow::instance = nullptr; BEGIN_EVENT_TABLE(LuaScriptsWindow, wxPanel) - EVT_LIST_ITEM_ACTIVATED(SCRIPT_MANAGER_LIST, LuaScriptsWindow::OnScriptActivated) - EVT_LIST_ITEM_SELECTED(SCRIPT_MANAGER_LIST, LuaScriptsWindow::OnScriptSelected) - EVT_BUTTON(SCRIPT_MANAGER_RELOAD, LuaScriptsWindow::OnReloadScripts) - EVT_BUTTON(SCRIPT_MANAGER_OPEN_FOLDER, LuaScriptsWindow::OnOpenFolder) - EVT_BUTTON(SCRIPT_MANAGER_CLEAR_CONSOLE, LuaScriptsWindow::OnClearConsole) - EVT_BUTTON(SCRIPT_MANAGER_RUN_SCRIPT, LuaScriptsWindow::OnRunScript) +EVT_LIST_ITEM_ACTIVATED(SCRIPT_MANAGER_LIST, LuaScriptsWindow::OnScriptActivated) +EVT_LIST_ITEM_SELECTED(SCRIPT_MANAGER_LIST, LuaScriptsWindow::OnScriptSelected) +EVT_BUTTON(SCRIPT_MANAGER_RELOAD, LuaScriptsWindow::OnReloadScripts) +EVT_BUTTON(SCRIPT_MANAGER_OPEN_FOLDER, LuaScriptsWindow::OnOpenFolder) +EVT_BUTTON(SCRIPT_MANAGER_CLEAR_CONSOLE, LuaScriptsWindow::OnClearConsole) +EVT_BUTTON(SCRIPT_MANAGER_RUN_SCRIPT, LuaScriptsWindow::OnRunScript) END_EVENT_TABLE() LuaScriptsWindow::LuaScriptsWindow(wxWindow* parent) : @@ -97,9 +97,7 @@ void LuaScriptsWindow::BuildUI() { mainSizer->Add(buttonSizer, 0, wxEXPAND | wxALL, 2); // Script list - script_list = newd wxListCtrl(this, SCRIPT_MANAGER_LIST, - wxDefaultPosition, wxSize(-1, 150), - wxLC_REPORT | wxLC_SINGLE_SEL); + script_list = newd wxListCtrl(this, SCRIPT_MANAGER_LIST, wxDefaultPosition, wxSize(-1, 150), wxLC_REPORT | wxLC_SINGLE_SEL); script_list->InsertColumn(0, "Status", wxLIST_FORMAT_CENTER, 40); script_list->InsertColumn(1, "Title", wxLIST_FORMAT_LEFT, 70); @@ -115,9 +113,7 @@ void LuaScriptsWindow::BuildUI() { mainSizer->Add(consoleLabel, 0, wxLEFT | wxTOP, 4); // Console output - console_output = newd wxTextCtrl(this, wxID_ANY, wxEmptyString, - wxDefaultPosition, wxSize(-1, 100), - wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2 | wxHSCROLL); + console_output = newd wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(-1, 100), wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2 | wxHSCROLL); // Set monospace font for console wxFont consoleFont(9, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); @@ -131,7 +127,9 @@ void LuaScriptsWindow::BuildUI() { } void LuaScriptsWindow::RefreshScriptList() { - if (!script_list) return; + if (!script_list) { + return; + } script_list->DeleteAllItems(); @@ -141,7 +139,6 @@ void LuaScriptsWindow::RefreshScriptList() { long index = script_list->InsertItem(i, script->isEnabled() ? "On" : "Off"); - script_list->SetItem(index, 1, wxString::FromUTF8(script->getDisplayName())); script_list->SetItem(index, 2, wxString::FromUTF8(script->getDescription())); script_list->SetItem(index, 3, wxString::FromUTF8(script->getAuthor())); @@ -168,14 +165,16 @@ void LuaScriptsWindow::RefreshScriptList() { } void LuaScriptsWindow::LogMessage(const wxString& message, bool isError) { - if (!console_output) return; + if (!console_output) { + return; + } // Set color based on message type wxTextAttr attr; if (isError) { - attr.SetTextColour(wxColour(255, 100, 100)); // Red for errors + attr.SetTextColour(wxColour(255, 100, 100)); // Red for errors } else { - attr.SetTextColour(wxColour(200, 200, 200)); // Light gray for normal + attr.SetTextColour(wxColour(200, 200, 200)); // Light gray for normal } console_output->SetDefaultStyle(attr); @@ -200,7 +199,9 @@ void LuaScriptsWindow::ClearConsole() { } void LuaScriptsWindow::UpdateScriptState(long index) { - if (!script_list || index < 0) return; + if (!script_list || index < 0) { + return; + } size_t scriptIndex = static_cast(script_list->GetItemData(index)); const auto& scripts = g_luaScripts.getScripts(); @@ -220,7 +221,9 @@ void LuaScriptsWindow::UpdateScriptState(long index) { void LuaScriptsWindow::OnScriptActivated(wxListEvent& event) { // Double-click to run the script long index = event.GetIndex(); - if (index < 0) return; + if (index < 0) { + return; + } size_t scriptIndex = static_cast(script_list->GetItemData(index)); const auto& scripts = g_luaScripts.getScripts(); @@ -274,7 +277,9 @@ void LuaScriptsWindow::OnClearConsole(wxCommandEvent& event) { void LuaScriptsWindow::OnRunScript(wxCommandEvent& event) { long selected = script_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - if (selected < 0) return; + if (selected < 0) { + return; + } size_t scriptIndex = static_cast(script_list->GetItemData(selected)); const auto& scripts = g_luaScripts.getScripts(); @@ -292,7 +297,9 @@ void LuaScriptsWindow::OnRunScript(wxCommandEvent& event) { void LuaScriptsWindow::OnScriptCheckToggle(wxListEvent& event) { // Toggle script enabled state (would need checkbox implementation) long index = event.GetIndex(); - if (index < 0) return; + if (index < 0) { + return; + } size_t scriptIndex = static_cast(script_list->GetItemData(index)); g_luaScripts.setScriptEnabled(scriptIndex, !g_luaScripts.isScriptEnabled(scriptIndex)); diff --git a/source/lua/lua_scripts_window.h b/source/lua/lua_scripts_window.h index 000eda824..dc6a9360b 100644 --- a/source/lua/lua_scripts_window.h +++ b/source/lua/lua_scripts_window.h @@ -36,8 +36,12 @@ class LuaScriptsWindow : public wxPanel { void ClearConsole(); // Get singleton instance (created by application) - static LuaScriptsWindow* Get() { return instance; } - static void SetInstance(LuaScriptsWindow* win) { instance = win; } + static LuaScriptsWindow* Get() { + return instance; + } + static void SetInstance(LuaScriptsWindow* win) { + instance = win; + } protected: // Event handlers diff --git a/source/main_menubar.cpp b/source/main_menubar.cpp index 9a7cb72da..6a4423be7 100644 --- a/source/main_menubar.cpp +++ b/source/main_menubar.cpp @@ -852,8 +852,6 @@ void MainMenuBar::OnImportMonsterData(wxCommandEvent& WXUNUSED(event)) { } } - - void MainMenuBar::OnImportMinimap(wxCommandEvent& WXUNUSED(event)) { ASSERT(g_gui.IsEditorOpen()); // wxDialog* importmap = newd ImportMapWindow(); @@ -2260,7 +2258,7 @@ void MainMenuBar::LoadScriptsMenu() { int scriptIndex = 0; for (const auto& script : scripts) { if (scriptIndex >= (SCRIPTS_LAST - SCRIPTS_FIRST)) { - break; // Maximum scripts reached + break; // Maximum scripts reached } if (!script->isEnabled()) { diff --git a/source/main_menubar.h b/source/main_menubar.h index 7c2d03ece..1a437d35f 100644 --- a/source/main_menubar.h +++ b/source/main_menubar.h @@ -160,13 +160,13 @@ namespace MenuBar { // Scripts menu SCRIPTS_OPEN_FOLDER, SCRIPTS_RELOAD, - SCRIPTS_MANAGER, // Show/hide Script Manager window - SCRIPTS_FIRST, // Dynamic script IDs start here - SCRIPTS_LAST = SCRIPTS_FIRST + 100, // Allow up to 100 scripts + SCRIPTS_MANAGER, // Show/hide Script Manager window + SCRIPTS_FIRST, // Dynamic script IDs start here + SCRIPTS_LAST = SCRIPTS_FIRST + 100, // Allow up to 100 scripts // Show menu (custom overlays) SHOW_CUSTOM_FIRST, - SHOW_CUSTOM_LAST = SHOW_CUSTOM_FIRST + 200, // Allow up to 200 custom show entries + SHOW_CUSTOM_LAST = SHOW_CUSTOM_FIRST + 200, // Allow up to 200 custom show entries }; } diff --git a/source/map_display.cpp b/source/map_display.cpp index 697c46f96..e7f8dd45c 100644 --- a/source/map_display.cpp +++ b/source/map_display.cpp @@ -180,8 +180,9 @@ void MapCanvas::SetZoom(double value) { zoom = value; // Unsafe cast if parent isn't MapWindow, but this is the base implementation - if (GetParent()) + if (GetParent()) { static_cast(GetParent())->SetScreenCenterPosition(Position(center_x, center_y, floor)); + } UpdatePositionStatus(); UpdateZoomStatus(); @@ -2596,7 +2597,9 @@ void MapPopupMenu::Update() { for (size_t i = 0; i < menuItems.size(); ++i) { int id = MAP_POPUP_MENU_SCRIPT_FIRST + i; - if (id > MAP_POPUP_MENU_SCRIPT_LAST) break; + if (id > MAP_POPUP_MENU_SCRIPT_LAST) { + break; + } Append(id, wxString::FromUTF8(menuItems[i].label)); } diff --git a/source/map_display.h b/source/map_display.h index 18dd6f075..ae72964d2 100644 --- a/source/map_display.h +++ b/source/map_display.h @@ -101,8 +101,12 @@ class MapCanvas : public wxGLCanvas { } virtual void GetScreenCenter(int* map_x, int* map_y); - virtual int GetClientWidth() const { return ClientMapWidth; } - virtual int GetClientHeight() const { return ClientMapHeight; } + virtual int GetClientWidth() const { + return ClientMapWidth; + } + virtual int GetClientHeight() const { + return ClientMapHeight; + } void StartPasting(); void EndPasting(); @@ -113,7 +117,9 @@ class MapCanvas : public wxGLCanvas { void UpdateZoomStatus(); void ChangeFloor(int new_floor); - void SetFloor(int new_floor) { floor = new_floor; } + void SetFloor(int new_floor) { + floor = new_floor; + } int GetFloor() const { return floor; } diff --git a/source/map_drawer.cpp b/source/map_drawer.cpp index 8005caa91..4e0b431ce 100644 --- a/source/map_drawer.cpp +++ b/source/map_drawer.cpp @@ -272,14 +272,18 @@ static void DrawDirectText(int x, int y, const std::string& text, const wxColor& glColor4ub(0, 0, 0, 255); glRasterPos2i(x + 1, y + 13); for (const char* c = text.c_str(); *c != '\0'; c++) { - if (*c != '\n') glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, *c); + if (*c != '\n') { + glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, *c); + } } // Text glColor4ub(color.Red(), color.Green(), color.Blue(), color.Alpha()); glRasterPos2i(x, y + 12); for (const char* c = text.c_str(); *c != '\0'; c++) { - if (*c != '\n') glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, *c); + if (*c != '\n') { + glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, *c); + } } } @@ -349,8 +353,7 @@ bool MapDrawer::drawOverlayCommands(const std::vector& comman y1 = static_cast(cmd.y); x2 = static_cast(cmd.x2); y2 = static_cast(cmd.y2); - } else if (mapToScreen(this, cmd.x, cmd.y, cmd.z, x1, y1) && - mapToScreen(this, cmd.x2, cmd.y2, cmd.z2, x2, y2)) { + } else if (mapToScreen(this, cmd.x, cmd.y, cmd.z, x1, y1) && mapToScreen(this, cmd.x2, cmd.y2, cmd.z2, x2, y2)) { // use map coords as-is } else { if (isScreenSpace) { diff --git a/source/mt_rand.cpp b/source/mt_rand.cpp index ccd6480c9..3b9f79ca6 100644 --- a/source/mt_rand.cpp +++ b/source/mt_rand.cpp @@ -43,7 +43,7 @@ mt_get(void* vstate) { unsigned long k; unsigned long int* const mt = state->mt; -#define MAGIC(y) (((y) & 0x1) ? 0x9908b0dfUL : 0) +#define MAGIC(y) (((y)&0x1) ? 0x9908b0dfUL : 0) if (state->mti >= N) { /* generate N words at one time */ int kk; From 52096e537d6195f0a64d70876e223981de723adf Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 21:46:26 +0100 Subject: [PATCH 12/41] ci: fix required files in cmakelist and build flow --- .github/workflows/build.yml | 5 ++++- CMakeLists.txt | 12 ++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f324db4d3..742b43a66 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,8 +33,11 @@ jobs: freeglut3-dev \ libarchive-dev \ zlib1g-dev \ + libarchive-dev \ + zlib1g-dev \ libxmu-dev \ - libxi-dev + libxi-dev \ + liblua5.3-dev - name: Setup ccache run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 401b1cacb..de2dd7041 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,15 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.12) project(rme) +if(POLICY CMP0072) + cmake_policy(SET CMP0072 NEW) # Prefer GLVND +endif() + +if(POLICY CMP0167) + cmake_policy(SET CMP0167 NEW) # Use BoostConfig +endif() + if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo) endif() @@ -41,4 +49,4 @@ set_target_properties(rme PROPERTIES CXX_STANDARD 17) set_target_properties(rme PROPERTIES CXX_STANDARD_REQUIRED ON) include_directories(${Boost_INCLUDE_DIRS} ${LibArchive_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR} ${LUA_INCLUDE_DIR} ${SOL2_INCLUDE_DIRS}) -target_link_libraries(rme ${wxWidgets_LIBRARIES} ${Boost_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${ZLIB_LIBRARIES} ${LUA_LIBRARIES}) \ No newline at end of file +target_link_libraries(rme ${wxWidgets_LIBRARIES} ${Boost_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${ZLIB_LIBRARIES} ${LUA_LIBRARIES}) From 06a5533a8f60d88346fb0eb8d56bfa7a36eaefab Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 21:49:44 +0100 Subject: [PATCH 13/41] ci: typo in the FastNoiseLite -> fast_noise_lite, unhandled libsol --- .github/workflows/build.yml | 3 ++- source/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 742b43a66..f80fb08c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,8 @@ jobs: zlib1g-dev \ libxmu-dev \ libxi-dev \ - liblua5.3-dev + liblua5.3-dev \ + libsol2-dev - name: Setup ccache run: | diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index c32dd553c..262fc9a96 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -126,7 +126,7 @@ ${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_http.h ${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_noise.h ${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_algo.h ${CMAKE_CURRENT_LIST_DIR}/lua/lua_api_geo.h -${CMAKE_CURRENT_LIST_DIR}/FastNoiseLite.h +${CMAKE_CURRENT_LIST_DIR}/fast_noise_lite.h ) set(rme_SRC From 6ccc864637a2b77c64d8a572ea0c66d96c4ceac7 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 21:52:38 +0100 Subject: [PATCH 14/41] ci: solve libsol reaching in linux --- .github/workflows/build.yml | 3 +-- CMakeLists.txt | 9 ++++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f80fb08c8..742b43a66 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,8 +37,7 @@ jobs: zlib1g-dev \ libxmu-dev \ libxi-dev \ - liblua5.3-dev \ - libsol2-dev + liblua5.3-dev - name: Setup ccache run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index de2dd7041..17f1b9514 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,14 @@ find_package(ZLIB REQUIRED) # Lua scripting support find_package(Lua REQUIRED) -find_path(SOL2_INCLUDE_DIRS "sol/sol.hpp") +include(FetchContent) +FetchContent_Declare( + sol2 + GIT_REPOSITORY https://github.com/ThePhD/sol2.git + GIT_TAG v3.3.0 +) +FetchContent_MakeAvailable(sol2) +set(SOL2_INCLUDE_DIRS ${sol2_SOURCE_DIR}/include) include(${wxWidgets_USE_FILE}) include(source/CMakeLists.txt) From c1ef38b496d3f2b3453a679842652d6a6c3c454c Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 21:56:20 +0100 Subject: [PATCH 15/41] fix: remove autoborder redefinition issue --- source/brush.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/brush.h b/source/brush.h index c2df4d2e2..9062223aa 100644 --- a/source/brush.h +++ b/source/brush.h @@ -82,7 +82,7 @@ class Brushes { } protected: - typedef std::map BorderMap; + // typedef std::map BorderMap; BrushMap brushes; BorderMap borders; From 6e12201365405e0af0c3425681378f0ef7d85d9d Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 22:26:40 +0100 Subject: [PATCH 16/41] fix: remove unnecesary const --- source/complexitem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/complexitem.h b/source/complexitem.h index 7f1a8f6eb..0addfb25f 100644 --- a/source/complexitem.h +++ b/source/complexitem.h @@ -156,7 +156,7 @@ class Podium : public Item { outfit = newOutfit; } - const uint8_t getDirection() { + uint8_t getDirection() { return direction; } void setDirection(uint8_t newDirection) { From cd54816afe6724c42702243a94f8d70d247d8835 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 22:34:58 +0100 Subject: [PATCH 17/41] fix: solve issue related to source header discover --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 17f1b9514..0df8b45d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,5 +55,5 @@ add_executable(rme ${rme_H} ${rme_SRC}) set_target_properties(rme PROPERTIES CXX_STANDARD 17) set_target_properties(rme PROPERTIES CXX_STANDARD_REQUIRED ON) -include_directories(${Boost_INCLUDE_DIRS} ${LibArchive_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR} ${LUA_INCLUDE_DIR} ${SOL2_INCLUDE_DIRS}) +include_directories(${CMAKE_SOURCE_DIR}/source ${Boost_INCLUDE_DIRS} ${LibArchive_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR} ${LUA_INCLUDE_DIR} ${SOL2_INCLUDE_DIRS}) target_link_libraries(rme ${wxWidgets_LIBRARIES} ${Boost_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${ZLIB_LIBRARIES} ${LUA_LIBRARIES}) From 6edcc8d23c320f6028d8dbad6ee51bfbb09fbab5 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 22:50:53 +0100 Subject: [PATCH 18/41] feat: add sandbox security, prevent use of malicious paths or unknown calls --- source/lua/lua_api_app.cpp | 7 +++++ source/lua/lua_api_http.cpp | 38 ++++++++++++++++++++-- source/lua/lua_api_image.cpp | 39 +++++++++++++++++++++++ source/lua/lua_engine.cpp | 61 +++++++++++++++++++++++++++++------- 4 files changed, 131 insertions(+), 14 deletions(-) diff --git a/source/lua/lua_api_app.cpp b/source/lua/lua_api_app.cpp index 1b1a2bffa..8f036f3f2 100644 --- a/source/lua/lua_api_app.cpp +++ b/source/lua/lua_api_app.cpp @@ -436,6 +436,13 @@ namespace LuaAPI { } std::string filename = name; + + // Security check: Prevent path traversal and absolute paths + if (filename.find("..") != std::string::npos || std::filesystem::path(filename).is_absolute()) { + printf("[Lua Security] Blocked unsafe path in app.storage: %s\n", filename.c_str()); + return lua.create_table(); + } + if (filename.find('.') == std::string::npos) { filename += ".json"; } diff --git a/source/lua/lua_api_http.cpp b/source/lua/lua_api_http.cpp index 93e37e025..972d5f481 100644 --- a/source/lua/lua_api_http.cpp +++ b/source/lua/lua_api_http.cpp @@ -121,11 +121,29 @@ namespace LuaAPI { static std::mutex g_sessionsMutex; static int g_nextSessionId = 1; + // Security helper: Block localhost and loopback + static bool isUrlSafe(const std::string& url_str) { + std::string low = url_str; + std::transform(low.begin(), low.end(), low.begin(), ::tolower); + if (low.find("localhost") != std::string::npos || + low.find("127.0.0.1") != std::string::npos || + low.find("::1") != std::string::npos) { + return false; + } + return true; + } + // HTTP GET request static sol::table httpGet(sol::this_state ts, const std::string& url, sol::optional optHeaders) { sol::state_view lua(ts); sol::table result = lua.create_table(); + if (!isUrlSafe(url)) { + result["error"] = "Security: URL blocked (Localhost access denied)"; + result["ok"] = false; + return result; + } + cpr::Header headers; if (optHeaders) { sol::table headersTable = *optHeaders; @@ -136,7 +154,7 @@ namespace LuaAPI { } } - cpr::Response response = cpr::Get(cpr::Url { url }, headers); + cpr::Response response = cpr::Get(cpr::Url { url }, headers, cpr::Timeout{10000}); result["status"] = static_cast(response.status_code); result["body"] = response.text; @@ -158,6 +176,12 @@ namespace LuaAPI { sol::state_view lua(ts); sol::table result = lua.create_table(); + if (!isUrlSafe(url)) { + result["error"] = "Security: URL blocked (Localhost access denied)"; + result["ok"] = false; + return result; + } + cpr::Header headers; if (optHeaders) { sol::table headersTable = *optHeaders; @@ -171,7 +195,8 @@ namespace LuaAPI { cpr::Response response = cpr::Post( cpr::Url { url }, cpr::Body { body }, - headers + headers, + cpr::Timeout{10000} ); result["status"] = static_cast(response.status_code); @@ -271,6 +296,12 @@ namespace LuaAPI { sol::state_view lua(ts); sol::table result = lua.create_table(); + if (!isUrlSafe(url)) { + result["error"] = "Security: URL blocked (Localhost access denied)"; + result["ok"] = false; + return result; + } + auto session = std::make_shared(); int sessionId; @@ -301,7 +332,8 @@ namespace LuaAPI { cpr::Url { url }, cpr::Body { body }, headers, - cpr::WriteCallback { writeCallback, 0 } + cpr::WriteCallback { writeCallback, 0 }, + cpr::Timeout{30000} ); session->setStatusCode(static_cast(response.status_code)); diff --git a/source/lua/lua_api_image.cpp b/source/lua/lua_api_image.cpp index 3520cce82..b882fece9 100644 --- a/source/lua/lua_api_image.cpp +++ b/source/lua/lua_api_image.cpp @@ -20,6 +20,8 @@ #include "../gui.h" #include "../graphics.h" #include "../items.h" +#include +#include "lua_script_manager.h" namespace LuaAPI { @@ -28,8 +30,45 @@ namespace LuaAPI { // Empty image } + + LuaImage::LuaImage(const std::string& path) : filePath(path), spriteId(0), spriteSource(false) { + + if (path.empty()) return; + + // Security Check + if (path.find("..") != std::string::npos) { + printf("[Lua Security] Blocked image path with traversal: %s\n", path.c_str()); + return; + } + + namespace fs = std::filesystem; + try { + fs::path p(path); + if (p.is_absolute()) { + // Normalize directories for comparison + fs::path scriptsPath = fs::absolute(LuaScriptManager::getInstance().getScriptsDirectory()); + fs::path dataPath = fs::absolute(GUI::GetDataDirectory().ToStdString()); + fs::path absPath = fs::absolute(p); + + std::string absStr = absPath.string(); + std::string scriptsStr = scriptsPath.string(); + std::string dataStr = dataPath.string(); + + bool allowed = false; + if (absStr.find(scriptsStr) == 0) allowed = true; + if (absStr.find(dataStr) == 0) allowed = true; + + if (!allowed) { + printf("[Lua Security] Blocked absolute image path outside allowed directories: %s\n", path.c_str()); + return; + } + } + } catch (...) { + return; + } + if (!path.empty()) { image.LoadFile(wxString(path)); } diff --git a/source/lua/lua_engine.cpp b/source/lua/lua_engine.cpp index a7c9a8bcb..848eff053 100644 --- a/source/lua/lua_engine.cpp +++ b/source/lua/lua_engine.cpp @@ -79,17 +79,56 @@ void LuaEngine::setupSandbox() { // Remove dangerous functions for security // We don't want scripts to be able to execute system commands or access arbitrary files - // Sandbox restrictions conditioned or removed for development flexibility - // if (lua["os"].valid()) { - // lua["os"]["execute"] = sol::nil; - // lua["os"]["exit"] = sol::nil; - // } - - // Enable standard libraries for versatile scripting - // lua["io"] = sol::nil; - // lua["loadfile"] = sol::nil; - // lua["dofile"] = sol::nil; - // lua["package"]["loadlib"] = sol::nil; + // Sandbox OS library + if (lua["os"].valid()) { + lua["os"]["execute"] = sol::nil; + lua["os"]["exit"] = sol::nil; + lua["os"]["remove"] = sol::nil; + lua["os"]["rename"] = sol::nil; + lua["os"]["tmpname"] = sol::nil; + lua["os"]["getenv"] = sol::nil; + lua["os"]["setlocale"] = sol::nil; + } + + // Disable IO library completely - scripts must use app.storage + lua["io"] = sol::nil; + + // Disable dynamic loading of C libraries + if (lua["package"].valid()) { + lua["package"]["loadlib"] = sol::nil; + + // Also remove C loaders from package.searchers (Lua 5.2+) to prevent 'require' from loading DLLs + if (lua["package"]["searchers"].valid()) { + sol::table searchers = lua["package"]["searchers"]; + // 1: preload, 2: lua loader, 3: c loader, 4: all-in-one loader + // Keep 1 and 2, remove 3 and 4 + searchers[3] = sol::nil; + searchers[4] = sol::nil; + } + } + + // Disable accessing arbitrary files + lua["dofile"] = sol::nil; + lua["loadfile"] = sol::nil; + + // Secure 'load' to prevent bytecode execution (only allow mode "t") + // If the chunk starts with the bytecode signature (ESC Lua), load() normally detects it. + // By enforcing mode "t", we prevent loading precompiled bytecode. + try { + lua.script(R"( + local old_load = load + _G.load = function(chunk, chunkname, mode, env) + -- If mode is provided, ensure it is 't' + if mode and mode ~= "t" then + error("Secure Mode: Binary chunks are disabled.") + end + -- Force 't' mode + return old_load(chunk, chunkname, "t", env) + end + )"); + } catch (...) { + // Ignore if load is already nil or something + } } void LuaEngine::registerBaseLibraries() { From 140bca36b472bbcce6f6bea0923d42a5faf3d042 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 22:51:15 +0100 Subject: [PATCH 19/41] fix: force to correct overload resolution in fast_noise_lite lib --- source/fast_noise_lite.h | 24 ++++++++--------- source/lua/lua_api_noise.cpp | 50 ++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/source/fast_noise_lite.h b/source/fast_noise_lite.h index 2b66999aa..a4c9464a8 100644 --- a/source/fast_noise_lite.h +++ b/source/fast_noise_lite.h @@ -1358,10 +1358,10 @@ class FastNoiseLite { case CellularDistanceFunction_Euclidean: case CellularDistanceFunction_EuclideanSq: for (int xi = xr - 1; xi <= xr + 1; xi++) { - int yPrimed = yPrimedBase; + unsigned int yPrimed = yPrimedBase; for (int yi = yr - 1; yi <= yr + 1; yi++) { - int hash = Hash(seed, xPrimed, yPrimed); + int hash = Hash(seed, xPrimed, (int)yPrimed); int idx = hash & (255 << 1); float vecX = (float)(xi - x) + Lookup::RandVecs2D[idx] * cellularJitter; @@ -1381,10 +1381,10 @@ class FastNoiseLite { break; case CellularDistanceFunction_Manhattan: for (int xi = xr - 1; xi <= xr + 1; xi++) { - int yPrimed = yPrimedBase; + unsigned int yPrimed = yPrimedBase; for (int yi = yr - 1; yi <= yr + 1; yi++) { - int hash = Hash(seed, xPrimed, yPrimed); + int hash = Hash(seed, xPrimed, (int)yPrimed); int idx = hash & (255 << 1); float vecX = (float)(xi - x) + Lookup::RandVecs2D[idx] * cellularJitter; @@ -1404,10 +1404,10 @@ class FastNoiseLite { break; case CellularDistanceFunction_Hybrid: for (int xi = xr - 1; xi <= xr + 1; xi++) { - int yPrimed = yPrimedBase; + unsigned int yPrimed = yPrimedBase; for (int yi = yr - 1; yi <= yr + 1; yi++) { - int hash = Hash(seed, xPrimed, yPrimed); + int hash = Hash(seed, xPrimed, (int)yPrimed); int idx = hash & (255 << 1); float vecX = (float)(xi - x) + Lookup::RandVecs2D[idx] * cellularJitter; @@ -1478,10 +1478,10 @@ class FastNoiseLite { int yPrimed = yPrimedBase; for (int yi = yr - 1; yi <= yr + 1; yi++) { - int zPrimed = zPrimedBase; + unsigned int zPrimed = zPrimedBase; for (int zi = zr - 1; zi <= zr + 1; zi++) { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int hash = Hash(seed, xPrimed, yPrimed, (int)zPrimed); int idx = hash & (255 << 2); float vecX = (float)(xi - x) + Lookup::RandVecs3D[idx] * cellularJitter; @@ -1507,10 +1507,10 @@ class FastNoiseLite { int yPrimed = yPrimedBase; for (int yi = yr - 1; yi <= yr + 1; yi++) { - int zPrimed = zPrimedBase; + unsigned int zPrimed = zPrimedBase; for (int zi = zr - 1; zi <= zr + 1; zi++) { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int hash = Hash(seed, xPrimed, yPrimed, (int)zPrimed); int idx = hash & (255 << 2); float vecX = (float)(xi - x) + Lookup::RandVecs3D[idx] * cellularJitter; @@ -1536,10 +1536,10 @@ class FastNoiseLite { int yPrimed = yPrimedBase; for (int yi = yr - 1; yi <= yr + 1; yi++) { - int zPrimed = zPrimedBase; + unsigned int zPrimed = zPrimedBase; for (int zi = zr - 1; zi <= zr + 1; zi++) { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int hash = Hash(seed, xPrimed, yPrimed, (int)zPrimed); int idx = hash & (255 << 2); float vecX = (float)(xi - x) + Lookup::RandVecs3D[idx] * cellularJitter; diff --git a/source/lua/lua_api_noise.cpp b/source/lua/lua_api_noise.cpp index 53644e8ed..2b95717ab 100644 --- a/source/lua/lua_api_noise.cpp +++ b/source/lua/lua_api_noise.cpp @@ -214,23 +214,23 @@ namespace LuaAPI { sol::table opts = *options; // Frequency - float freq = opts.get_or("frequency", 0.01f); + float freq = opts.get_or(std::string("frequency"), 0.01f); noise.SetFrequency(freq); // Octaves (layers of noise) - int octaves = opts.get_or("octaves", 4); + int octaves = opts.get_or(std::string("octaves"), 4); noise.SetFractalOctaves(octaves); // Lacunarity (frequency multiplier per octave) - float lacunarity = opts.get_or("lacunarity", 2.0f); + float lacunarity = opts.get_or(std::string("lacunarity"), 2.0f); noise.SetFractalLacunarity(lacunarity); // Gain (amplitude multiplier per octave) - float gain = opts.get_or("gain", 0.5f); + float gain = opts.get_or(std::string("gain"), 0.5f); noise.SetFractalGain(gain); // Noise type for fractal - std::string noiseType = opts.get_or("noiseType", "simplex"); + std::string noiseType = opts.get_or(std::string("noiseType"), "simplex"); if (noiseType == "perlin") { noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); } else if (noiseType == "simplex" || noiseType == "opensimplex") { @@ -259,12 +259,12 @@ namespace LuaAPI { if (options) { sol::table opts = *options; - noise.SetFrequency(opts.get_or("frequency", 0.01f)); - noise.SetFractalOctaves(opts.get_or("octaves", 4)); - noise.SetFractalLacunarity(opts.get_or("lacunarity", 2.0f)); - noise.SetFractalGain(opts.get_or("gain", 0.5f)); + noise.SetFrequency(opts.get_or(std::string("frequency"), 0.01f)); + noise.SetFractalOctaves(opts.get_or(std::string("octaves"), 4)); + noise.SetFractalLacunarity(opts.get_or(std::string("lacunarity"), 2.0f)); + noise.SetFractalGain(opts.get_or(std::string("gain"), 0.5f)); - std::string noiseType = opts.get_or("noiseType", "simplex"); + std::string noiseType = opts.get_or(std::string("noiseType"), "simplex"); if (noiseType == "perlin") { noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); } else { @@ -295,10 +295,10 @@ namespace LuaAPI { if (options) { sol::table opts = *options; - noise.SetFrequency(opts.get_or("frequency", 0.01f)); - noise.SetFractalOctaves(opts.get_or("octaves", 4)); - noise.SetFractalLacunarity(opts.get_or("lacunarity", 2.0f)); - noise.SetFractalGain(opts.get_or("gain", 0.5f)); + noise.SetFrequency(opts.get_or(std::string("frequency"), 0.01f)); + noise.SetFractalOctaves(opts.get_or(std::string("octaves"), 4)); + noise.SetFractalLacunarity(opts.get_or(std::string("lacunarity"), 2.0f)); + noise.SetFractalGain(opts.get_or(std::string("gain"), 0.5f)); } else { noise.SetFrequency(0.01f); noise.SetFractalOctaves(4); @@ -325,10 +325,10 @@ namespace LuaAPI { if (options) { sol::table opts = *options; - amplitude = opts.get_or("amplitude", 30.0f); - frequency = opts.get_or("frequency", 0.01f); + amplitude = opts.get_or(std::string("amplitude"), 30.0f); + frequency = opts.get_or(std::string("frequency"), 0.01f); - std::string warpType = opts.get_or("type", "simplex"); + std::string warpType = opts.get_or(std::string("type"), "simplex"); if (warpType == "simplex" || warpType == "opensimplex") { noise.SetDomainWarpType(FastNoiseLite::DomainWarpType_OpenSimplex2); } else if (warpType == "simplexReduced") { @@ -426,10 +426,10 @@ namespace LuaAPI { if (options) { sol::table opts = *options; - noise.SetSeed(opts.get_or("seed", 1337)); - noise.SetFrequency(opts.get_or("frequency", 0.01f)); + noise.SetSeed(opts.get_or(std::string("seed"), 1337)); + noise.SetFrequency(opts.get_or(std::string("frequency"), 0.01f)); - std::string noiseType = opts.get_or("noiseType", "simplex"); + std::string noiseType = opts.get_or(std::string("noiseType"), "simplex"); if (noiseType == "perlin") { noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); } else if (noiseType == "simplex") { @@ -441,15 +441,15 @@ namespace LuaAPI { } // Fractal settings - std::string fractal = opts.get_or("fractal", "none"); + std::string fractal = opts.get_or(std::string("fractal"), "none"); if (fractal == "fbm") { noise.SetFractalType(FastNoiseLite::FractalType_FBm); - noise.SetFractalOctaves(opts.get_or("octaves", 4)); - noise.SetFractalLacunarity(opts.get_or("lacunarity", 2.0f)); - noise.SetFractalGain(opts.get_or("gain", 0.5f)); + noise.SetFractalOctaves(opts.get_or(std::string("octaves"), 4)); + noise.SetFractalLacunarity(opts.get_or(std::string("lacunarity"), 2.0f)); + noise.SetFractalGain(opts.get_or(std::string("gain"), 0.5f)); } else if (fractal == "ridged") { noise.SetFractalType(FastNoiseLite::FractalType_Ridged); - noise.SetFractalOctaves(opts.get_or("octaves", 4)); + noise.SetFractalOctaves(opts.get_or(std::string("octaves"), 4)); } } else { noise.SetSeed(1337); From 309db7eeb7308e7a936ac37720fc181506970937 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 23 Jan 2026 21:51:50 +0000 Subject: [PATCH 20/41] Code format - (Clang-format) --- source/lua/lua_api_http.cpp | 10 ++++------ source/lua/lua_api_image.cpp | 14 +++++++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/source/lua/lua_api_http.cpp b/source/lua/lua_api_http.cpp index 972d5f481..3878c565e 100644 --- a/source/lua/lua_api_http.cpp +++ b/source/lua/lua_api_http.cpp @@ -125,9 +125,7 @@ namespace LuaAPI { static bool isUrlSafe(const std::string& url_str) { std::string low = url_str; std::transform(low.begin(), low.end(), low.begin(), ::tolower); - if (low.find("localhost") != std::string::npos || - low.find("127.0.0.1") != std::string::npos || - low.find("::1") != std::string::npos) { + if (low.find("localhost") != std::string::npos || low.find("127.0.0.1") != std::string::npos || low.find("::1") != std::string::npos) { return false; } return true; @@ -154,7 +152,7 @@ namespace LuaAPI { } } - cpr::Response response = cpr::Get(cpr::Url { url }, headers, cpr::Timeout{10000}); + cpr::Response response = cpr::Get(cpr::Url { url }, headers, cpr::Timeout { 10000 }); result["status"] = static_cast(response.status_code); result["body"] = response.text; @@ -196,7 +194,7 @@ namespace LuaAPI { cpr::Url { url }, cpr::Body { body }, headers, - cpr::Timeout{10000} + cpr::Timeout { 10000 } ); result["status"] = static_cast(response.status_code); @@ -333,7 +331,7 @@ namespace LuaAPI { cpr::Body { body }, headers, cpr::WriteCallback { writeCallback, 0 }, - cpr::Timeout{30000} + cpr::Timeout { 30000 } ); session->setStatusCode(static_cast(response.status_code)); diff --git a/source/lua/lua_api_image.cpp b/source/lua/lua_api_image.cpp index b882fece9..e3a2da28c 100644 --- a/source/lua/lua_api_image.cpp +++ b/source/lua/lua_api_image.cpp @@ -30,12 +30,12 @@ namespace LuaAPI { // Empty image } - - LuaImage::LuaImage(const std::string& path) : filePath(path), spriteId(0), spriteSource(false) { - if (path.empty()) return; + if (path.empty()) { + return; + } // Security Check if (path.find("..") != std::string::npos) { @@ -57,8 +57,12 @@ namespace LuaAPI { std::string dataStr = dataPath.string(); bool allowed = false; - if (absStr.find(scriptsStr) == 0) allowed = true; - if (absStr.find(dataStr) == 0) allowed = true; + if (absStr.find(scriptsStr) == 0) { + allowed = true; + } + if (absStr.find(dataStr) == 0) { + allowed = true; + } if (!allowed) { printf("[Lua Security] Blocked absolute image path outside allowed directories: %s\n", path.c_str()); From d81907430063e5f8c4ede06c6223b74170161096 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 23:00:36 +0100 Subject: [PATCH 21/41] ci: fix strings in lua_dialog --- source/lua/lua_dialog.cpp | 66 +++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/source/lua/lua_dialog.cpp b/source/lua/lua_dialog.cpp index c2fb2ef2c..661fc42a2 100644 --- a/source/lua/lua_dialog.cpp +++ b/source/lua/lua_dialog.cpp @@ -263,19 +263,19 @@ LuaDialog::LuaDialog(sol::table options, sol::this_state ts) : reqX = options.get_or("x", -1); reqY = options.get_or("y", -1); - if (reqWidth != -1 || reqHeight != -1) { + if (reqWidth != -1 || reqHeight != -1) { SetMinSize(wxSize(reqWidth != -1 ? reqWidth : 150, reqHeight != -1 ? reqHeight : 100)); SetSize(reqX != -1 ? reqX : -1, reqY != -1 ? reqY : -1, reqWidth, reqHeight); } else if (reqX != -1 || reqY != -1) { SetPosition(wxPoint(reqX != -1 ? reqX : GetPosition().x, reqY != -1 ? reqY : GetPosition().y)); } - if (options.get_or("dockable", false)) { + if (options.get_or(std::string("dockable"), false)) { dockPanel = new wxPanel(g_gui.root, wxID_ANY); wxAuiPaneInfo info; - std::string title = options.get_or("title", "Script Dialog"s); - std::string id = options.get_or("id", title); + std::string title = options.get_or(std::string("title"), "Script Dialog"s); + std::string id = options.get_or(std::string("id"), title); info.Name(id); info.Caption(title); @@ -536,12 +536,12 @@ LuaDialog* LuaDialog::input(sol::table options) { LuaDialog* LuaDialog::number(sol::table options) { ensureRowSizer(); - std::string id = options.get_or("id", "number_"s + std::to_string(widgets.size())); - std::string labelText = options.get_or("label", ""s); - double value = options.get_or("text", options.get_or("value", 0.0)); - int decimals = options.get_or("decimals", 0); - double minVal = options.get_or("min", -999999.0); - double maxVal = options.get_or("max", 999999.0); + std::string id = options.get_or(std::string("id"), "number_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or(std::string("label"), ""s); + double value = options.get_or(std::string("text"), options.get_or(std::string("value"), 0.0)); + int decimals = options.get_or(std::string("decimals"), 0); + double minVal = options.get_or(std::string("min"), -999999.0); + double maxVal = options.get_or(std::string("max"), 999999.0); if (!labelText.empty()) { wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); @@ -1042,15 +1042,15 @@ class LuaGridCtrl : public wxListCtrl { LuaDialog* LuaDialog::list(sol::table options) { finishCurrentRow(); - std::string id = options.get_or("id", "list_"s + std::to_string(widgets.size())); - int width = options.get_or("width", 200); - int height = options.get_or("height", 150); - int iconWidth = options.get_or("icon_width", 16); - int iconHeight = options.get_or("icon_height", 16); - int iconSize = options.get_or("icon_size", -1); - int itemHeight = options.get_or("item_height", 24); - bool showText = options.get_or("show_text", true); - bool smooth = options.get_or("smooth", true); + std::string id = options.get_or(std::string("id"), "list_"s + std::to_string(widgets.size())); + int width = options.get_or(std::string("width"), 200); + int height = options.get_or(std::string("height"), 150); + int iconWidth = options.get_or(std::string("icon_width"), 16); + int iconHeight = options.get_or(std::string("icon_height"), 16); + int iconSize = options.get_or(std::string("icon_size"), -1); + int itemHeight = options.get_or(std::string("item_height"), 24); + bool showText = options.get_or(std::string("show_text"), true); + bool smooth = options.get_or(std::string("smooth"), true); if (iconSize > 0) { iconWidth = iconSize; @@ -1235,11 +1235,11 @@ LuaDialog* LuaDialog::list(sol::table options) { LuaDialog* LuaDialog::file(sol::table options) { ensureRowSizer(); - std::string id = options.get_or("id", "file_"s + std::to_string(widgets.size())); - std::string labelText = options.get_or("label", ""s); - std::string filename = options.get_or("filename", ""s); - std::string filetypes = options.get_or("filetypes", "*.*"s); - bool save = options.get_or("save", false); + std::string id = options.get_or(std::string("id"), "file_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or(std::string("label"), ""s); + std::string filename = options.get_or(std::string("filename"), ""s); + std::string filetypes = options.get_or(std::string("filetypes"), "*.*"s); + bool save = options.get_or(std::string("save"), false); if (!labelText.empty()) { wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); @@ -1668,8 +1668,8 @@ void LuaDialog::popupContextMenu(const sol::function& callback, sol::table info, continue; } sol::table item = pair.second.as(); - bool separator = item.get_or("separator", false); - std::string text = item.get_or("text", ""s); + bool separator = item.get_or(std::string("separator"), false); + std::string text = item.get_or(std::string("text"), ""s); if (separator || text.empty()) { menu.AppendSeparator(); @@ -1678,7 +1678,7 @@ void LuaDialog::popupContextMenu(const sol::function& callback, sol::table info, int id = nextId++; wxMenuItem* menuItem = menu.Append(id, wxString(text)); - bool enabled = item.get_or("enabled", true); + bool enabled = item.get_or(std::string("enabled"), true); menuItem->Enable(enabled); if (item["onclick"].valid()) { @@ -1863,23 +1863,23 @@ LuaDialog* LuaDialog::modify(sol::table options) { if (widget.type == "mapCanvas") { MapPreviewCanvas* canvas = static_cast(widget.widget); if (props["x"].valid() && props["y"].valid()) { - canvas->SetPosition(props.get_or("x", 1000), props.get_or("y", 1000), props.get_or("z", (int)canvas->GetFloor())); + canvas->SetPosition(props.get_or(std::string("x"), 1000), props.get_or(std::string("y"), 1000), props.get_or(std::string("z"), (int)canvas->GetFloor())); } if (props["zoom"].valid()) { - canvas->SetZoom(props.get_or("zoom", 1.0)); + canvas->SetZoom(props.get_or(std::string("zoom"), 1.0)); } if (props["floor"].valid()) { - canvas->SetFloor(props.get_or("floor", 7)); + canvas->SetFloor(props.get_or(std::string("floor"), 7)); canvas->Refresh(); } - if (props["sync"].valid() && props.get_or("sync", false)) { + if (props["sync"].valid() && props.get_or(std::string("sync"), false)) { canvas->SyncView(); } if (props["client_w"].valid() && props["client_h"].valid()) { - canvas->SetViewSize(props.get_or("client_w", ClientMapWidth), props.get_or("client_h", ClientMapHeight)); + canvas->SetViewSize(props.get_or(std::string("client_w"), ClientMapWidth), props.get_or(std::string("client_h"), ClientMapHeight)); } if (props["light"].valid()) { - canvas->SetLight(props.get_or("light", false)); + canvas->SetLight(props.get_or(std::string("light"), false)); } } else if (widget.type == "input") { wxTextCtrl* ctrl = static_cast(widget.widget); From 65e41cc3989ed9c350cc3bff5bbe6548da3070b4 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 23 Jan 2026 22:01:13 +0000 Subject: [PATCH 22/41] Code format - (Clang-format) --- source/lua/lua_dialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/lua/lua_dialog.cpp b/source/lua/lua_dialog.cpp index 661fc42a2..410548d9b 100644 --- a/source/lua/lua_dialog.cpp +++ b/source/lua/lua_dialog.cpp @@ -263,7 +263,7 @@ LuaDialog::LuaDialog(sol::table options, sol::this_state ts) : reqX = options.get_or("x", -1); reqY = options.get_or("y", -1); - if (reqWidth != -1 || reqHeight != -1) { + if (reqWidth != -1 || reqHeight != -1) { SetMinSize(wxSize(reqWidth != -1 ? reqWidth : 150, reqHeight != -1 ? reqHeight : 100)); SetSize(reqX != -1 ? reqX : -1, reqY != -1 ? reqY : -1, reqWidth, reqHeight); } else if (reqX != -1 || reqY != -1) { From ebd372b0cb6aaa9bbd62d1782fd52d1cfe4f8db6 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 23:15:14 +0100 Subject: [PATCH 23/41] ci: Fix sol2 warnings, LuaImage deprecation, and add cpr/json dependencies --- CMakeLists.txt | 16 ++- source/lua/lua_api_algo.cpp | 66 +++++----- source/lua/lua_api_app.cpp | 2 +- source/lua/lua_api_geo.cpp | 28 ++-- source/lua/lua_api_image.cpp | 10 ++ source/lua/lua_api_image.h | 3 + source/lua/lua_api_position.cpp | 6 +- source/lua/lua_dialog.cpp | 206 +++++++++++++++--------------- source/lua/lua_script_manager.cpp | 70 +++++----- 9 files changed, 217 insertions(+), 190 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0df8b45d2..7091a4b51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,20 @@ FetchContent_Declare( FetchContent_MakeAvailable(sol2) set(SOL2_INCLUDE_DIRS ${sol2_SOURCE_DIR}/include) +FetchContent_Declare( + cpr + GIT_REPOSITORY https://github.com/libcpr/cpr.git + GIT_TAG 1.10.5 +) +FetchContent_MakeAvailable(cpr) + +FetchContent_Declare( + json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.11.3 +) +FetchContent_MakeAvailable(json) + include(${wxWidgets_USE_FILE}) include(source/CMakeLists.txt) add_executable(rme ${rme_H} ${rme_SRC}) @@ -56,4 +70,4 @@ set_target_properties(rme PROPERTIES CXX_STANDARD 17) set_target_properties(rme PROPERTIES CXX_STANDARD_REQUIRED ON) include_directories(${CMAKE_SOURCE_DIR}/source ${Boost_INCLUDE_DIRS} ${LibArchive_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR} ${LUA_INCLUDE_DIR} ${SOL2_INCLUDE_DIRS}) -target_link_libraries(rme ${wxWidgets_LIBRARIES} ${Boost_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${ZLIB_LIBRARIES} ${LUA_LIBRARIES}) +target_link_libraries(rme ${wxWidgets_LIBRARIES} ${Boost_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${ZLIB_LIBRARIES} ${LUA_LIBRARIES} cpr::cpr nlohmann_json::nlohmann_json) diff --git a/source/lua/lua_api_algo.cpp b/source/lua/lua_api_algo.cpp index c00d80774..866b6fe46 100644 --- a/source/lua/lua_api_algo.cpp +++ b/source/lua/lua_api_algo.cpp @@ -118,11 +118,11 @@ namespace LuaAPI { if (options) { sol::table opts = *options; - iterations = opts.get_or("iterations", 4); - birthLimit = opts.get_or("birthLimit", 4); - deathLimit = opts.get_or("deathLimit", 3); - width = opts.get_or("width", width); - height = opts.get_or("height", height); + iterations = opts.get_or(std::string("iterations"), 4); + birthLimit = opts.get_or(std::string("birthLimit"), 4); + deathLimit = opts.get_or(std::string("deathLimit"), 3); + width = opts.get_or(std::string("width"), width); + height = opts.get_or(std::string("height"), height); } if (width <= 0 || height <= 0) { @@ -186,11 +186,11 @@ namespace LuaAPI { if (options) { sol::table opts = *options; - fillProbability = opts.get_or("fillProbability", 0.45f); - iterations = opts.get_or("iterations", 4); - birthLimit = opts.get_or("birthLimit", 4); - deathLimit = opts.get_or("deathLimit", 3); - seed = opts.get_or("seed", seed); + fillProbability = opts.get_or(std::string("fillProbability"), 0.45f); + iterations = opts.get_or(std::string("iterations"), 4); + birthLimit = opts.get_or(std::string("birthLimit"), 4); + deathLimit = opts.get_or(std::string("deathLimit"), 3); + seed = opts.get_or(std::string("seed"), seed); } std::mt19937 rng(seed); @@ -282,17 +282,17 @@ namespace LuaAPI { if (options) { sol::table opts = *options; - iterations = opts.get_or("iterations", 50000); - erosionRadius = opts.get_or("erosionRadius", 3); - inertia = opts.get_or("inertia", 0.05f); - sedimentCapacity = opts.get_or("sedimentCapacity", 4.0f); - minSlope = opts.get_or("minSlope", 0.01f); - erosionSpeed = opts.get_or("erosionSpeed", 0.3f); - depositSpeed = opts.get_or("depositSpeed", 0.3f); - evaporateSpeed = opts.get_or("evaporateSpeed", 0.01f); - gravity = opts.get_or("gravity", 4.0f); - seed = opts.get_or("seed", seed); - maxDropletLifetime = opts.get_or("maxDropletLifetime", 30); + iterations = opts.get_or(std::string("iterations"), 50000); + erosionRadius = opts.get_or(std::string("erosionRadius"), 3); + inertia = opts.get_or(std::string("inertia"), 0.05f); + sedimentCapacity = opts.get_or(std::string("sedimentCapacity"), 4.0f); + minSlope = opts.get_or(std::string("minSlope"), 0.01f); + erosionSpeed = opts.get_or(std::string("erosionSpeed"), 0.3f); + depositSpeed = opts.get_or(std::string("depositSpeed"), 0.3f); + evaporateSpeed = opts.get_or(std::string("evaporateSpeed"), 0.01f); + gravity = opts.get_or(std::string("gravity"), 4.0f); + seed = opts.get_or(std::string("seed"), seed); + maxDropletLifetime = opts.get_or(std::string("maxDropletLifetime"), 30); } auto heightmap = tableToFloatGrid(inputHeightmap, width, height); @@ -457,9 +457,9 @@ namespace LuaAPI { if (options) { sol::table opts = *options; - iterations = opts.get_or("iterations", 50); - talusAngle = opts.get_or("talusAngle", 0.5f); - erosionAmount = opts.get_or("erosionAmount", 0.5f); + iterations = opts.get_or(std::string("iterations"), 50); + talusAngle = opts.get_or(std::string("talusAngle"), 0.5f); + erosionAmount = opts.get_or(std::string("erosionAmount"), 0.5f); } auto heightmap = tableToFloatGrid(inputHeightmap, width, height); @@ -532,8 +532,8 @@ namespace LuaAPI { if (options) { sol::table opts = *options; - iterations = opts.get_or("iterations", 1); - kernelSize = opts.get_or("kernelSize", 3); + iterations = opts.get_or(std::string("iterations"), 1); + kernelSize = opts.get_or(std::string("kernelSize"), 3); } auto grid = tableToFloatGrid(inputGrid, width, height); @@ -579,8 +579,8 @@ namespace LuaAPI { for (auto& kv : points) { if (kv.second.get_type() == sol::type::table) { sol::table pt = kv.second; - int x = pt.get_or("x", pt.get_or(1, 0)); - int y = pt.get_or("y", pt.get_or(2, 0)); + int x = pt.get_or(std::string("x"), pt.get_or(1, 0)); + int y = pt.get_or(std::string("y"), pt.get_or(2, 0)); seedPoints.push_back({ x, y }); } } @@ -649,7 +649,7 @@ namespace LuaAPI { if (options) { sol::table opts = *options; - seed = opts.get_or("seed", seed); + seed = opts.get_or(std::string("seed"), seed); } std::mt19937 rng(seed); @@ -710,10 +710,10 @@ namespace LuaAPI { if (options) { sol::table opts = *options; - minRoomSize = opts.get_or("minRoomSize", 5); - maxRoomSize = opts.get_or("maxRoomSize", 15); - seed = opts.get_or("seed", seed); - maxDepth = opts.get_or("maxDepth", 4); + minRoomSize = opts.get_or(std::string("minRoomSize"), 5); + maxRoomSize = opts.get_or(std::string("maxRoomSize"), 15); + seed = opts.get_or(std::string("seed"), seed); + maxDepth = opts.get_or(std::string("maxDepth"), 4); } std::mt19937 rng(seed); diff --git a/source/lua/lua_api_app.cpp b/source/lua/lua_api_app.cpp index 8f036f3f2..96e98830c 100644 --- a/source/lua/lua_api_app.cpp +++ b/source/lua/lua_api_app.cpp @@ -714,7 +714,7 @@ namespace LuaAPI { if (va.size() >= 3 && va[va.size() - 1].is()) { sol::table opts = va[va.size() - 1].as(); - enabled = opts.get_or("enabled", enabled); + enabled = opts.get_or(std::string("enabled"), enabled); if (opts["ontoggle"].valid()) { ontoggle = opts["ontoggle"]; } diff --git a/source/lua/lua_api_geo.cpp b/source/lua/lua_api_geo.cpp index b1e7b5164..9aeae27b2 100644 --- a/source/lua/lua_api_geo.cpp +++ b/source/lua/lua_api_geo.cpp @@ -144,8 +144,8 @@ namespace LuaAPI { for (auto& kv : controlPoints) { if (kv.second.get_type() == sol::type::table) { sol::table pt = kv.second; - float x = pt.get_or("x", pt.get_or(1, 0.0f)); - float y = pt.get_or("y", pt.get_or(2, 0.0f)); + float x = pt.get_or(std::string("x"), pt.get_or(1, 0.0f)); + float y = pt.get_or(std::string("y"), pt.get_or(2, 0.0f)); points.push_back({ x, y }); } } @@ -198,9 +198,9 @@ namespace LuaAPI { for (auto& kv : controlPoints) { if (kv.second.get_type() == sol::type::table) { sol::table pt = kv.second; - float x = pt.get_or("x", 0.0f); - float y = pt.get_or("y", 0.0f); - float z = pt.get_or("z", 0.0f); + float x = pt.get_or(std::string("x"), 0.0f); + float y = pt.get_or(std::string("y"), 0.0f); + float z = pt.get_or(std::string("z"), 0.0f); points.push_back({ x, y, z }); } } @@ -266,7 +266,7 @@ namespace LuaAPI { bool eightConnected = false; if (options) { sol::table opts = *options; - eightConnected = opts.get_or("eightConnected", false); + eightConnected = opts.get_or(std::string("eightConnected"), false); } // Convert to grid @@ -364,7 +364,7 @@ namespace LuaAPI { bool eightConnected = false; if (options) { sol::table opts = *options; - eightConnected = opts.get_or("eightConnected", false); + eightConnected = opts.get_or(std::string("eightConnected"), false); } // Convert to grid @@ -440,7 +440,7 @@ namespace LuaAPI { bool filled = false; if (options) { sol::table opts = *options; - filled = opts.get_or("filled", false); + filled = opts.get_or(std::string("filled"), false); } int index = 1; @@ -522,7 +522,7 @@ namespace LuaAPI { bool filled = false; if (options) { sol::table opts = *options; - filled = opts.get_or("filled", false); + filled = opts.get_or(std::string("filled"), false); } int index = 1; @@ -617,7 +617,7 @@ namespace LuaAPI { bool filled = false; if (options) { sol::table opts = *options; - filled = opts.get_or("filled", false); + filled = opts.get_or(std::string("filled"), false); } int minX = std::min(x1, x2); @@ -677,8 +677,8 @@ namespace LuaAPI { for (auto& kv : vertices) { if (kv.second.get_type() == sol::type::table) { sol::table pt = kv.second; - int x = pt.get_or("x", pt.get_or(1, 0)); - int y = pt.get_or("y", pt.get_or(2, 0)); + int x = pt.get_or(std::string("x"), pt.get_or(1, 0)); + int y = pt.get_or(std::string("y"), pt.get_or(2, 0)); verts.push_back({ x, y }); } } @@ -784,8 +784,8 @@ namespace LuaAPI { for (auto& kv : vertices) { if (kv.second.get_type() == sol::type::table) { sol::table pt = kv.second; - float x = pt.get_or("x", pt.get_or(1, 0.0f)); - float y = pt.get_or("y", pt.get_or(2, 0.0f)); + float x = pt.get_or(std::string("x"), pt.get_or(1, 0.0f)); + float y = pt.get_or(std::string("y"), pt.get_or(2, 0.0f)); verts.push_back({ x, y }); } } diff --git a/source/lua/lua_api_image.cpp b/source/lua/lua_api_image.cpp index e3a2da28c..ec66a901c 100644 --- a/source/lua/lua_api_image.cpp +++ b/source/lua/lua_api_image.cpp @@ -100,6 +100,16 @@ namespace LuaAPI { spriteSource(other.spriteSource) { } + LuaImage& LuaImage::operator=(const LuaImage& other) { + if (this != &other) { + image = other.image.IsOk() ? other.image.Copy() : wxImage(); + filePath = other.filePath; + spriteId = other.spriteId; + spriteSource = other.spriteSource; + } + return *this; + } + LuaImage::~LuaImage() { // wxImage handles its own cleanup } diff --git a/source/lua/lua_api_image.h b/source/lua/lua_api_image.h index 5213a4140..d8153376d 100644 --- a/source/lua/lua_api_image.h +++ b/source/lua/lua_api_image.h @@ -41,6 +41,9 @@ namespace LuaAPI { // Copy constructor LuaImage(const LuaImage& other); + // Copy assignment operator + LuaImage& operator=(const LuaImage& other); + ~LuaImage(); // Static factory methods for Lua diff --git a/source/lua/lua_api_position.cpp b/source/lua/lua_api_position.cpp index c8c2e76bc..99fbaac05 100644 --- a/source/lua/lua_api_position.cpp +++ b/source/lua/lua_api_position.cpp @@ -32,9 +32,9 @@ namespace LuaAPI { // Also allow construction from table: Position{x=100, y=200, z=7} sol::call_constructor, sol::factories([](int x, int y, int z) { return Position(x, y, z); }, [](sol::table t) { - int x = t.get_or("x", 0); - int y = t.get_or("y", 0); - int z = t.get_or("z", 0); + int x = t.get_or(std::string("x"), 0); + int y = t.get_or(std::string("y"), 0); + int z = t.get_or(std::string("z"), 0); return Position(x, y, z); }), // Properties diff --git a/source/lua/lua_dialog.cpp b/source/lua/lua_dialog.cpp index 661fc42a2..79912ee6e 100644 --- a/source/lua/lua_dialog.cpp +++ b/source/lua/lua_dialog.cpp @@ -255,13 +255,13 @@ LuaDialog::LuaDialog(const std::string& title, sol::this_state ts) : // Overload constructor to handle options LuaDialog::LuaDialog(sol::table options, sol::this_state ts) : - wxDialog(g_gui.root, wxID_ANY, wxString(options.get_or("title", "Script Dialog"s)), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | (options.get_or("resizable", true) ? wxRESIZE_BORDER : 0) | (options.get_or("topmost", false) ? wxSTAY_ON_TOP : 0)), + wxDialog(g_gui.root, wxID_ANY, wxString(options.get_or(std::string("title"), "Script Dialog"s)), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | (options.get_or(std::string("resizable"), true) ? wxRESIZE_BORDER : 0) | (options.get_or(std::string("topmost"), false) ? wxSTAY_ON_TOP : 0)), lua(ts) { - reqWidth = options.get_or("width", -1); - reqHeight = options.get_or("height", -1); - reqX = options.get_or("x", -1); - reqY = options.get_or("y", -1); + reqWidth = options.get_or(std::string("width"), -1); + reqHeight = options.get_or(std::string("height"), -1); + reqX = options.get_or(std::string("x"), -1); + reqY = options.get_or(std::string("y"), -1); if (reqWidth != -1 || reqHeight != -1) { SetMinSize(wxSize(reqWidth != -1 ? reqWidth : 150, reqHeight != -1 ? reqHeight : 100)); @@ -281,8 +281,8 @@ LuaDialog::LuaDialog(sol::table options, sol::this_state ts) : info.Caption(title); info.Right().Layer(1).Position(1).CloseButton(true).MaximizeButton(true); - int minW = options.get_or("min_width", reqWidth != -1 ? reqWidth : 200); - int minH = options.get_or("min_height", reqHeight != -1 ? reqHeight : 150); + int minW = options.get_or(std::string("min_width"), reqWidth != -1 ? reqWidth : 200); + int minH = options.get_or(std::string("min_height"), reqHeight != -1 ? reqHeight : 150); info.MinSize(wxSize(minW, minH)); if (reqWidth != -1 || reqHeight != -1) { @@ -383,8 +383,8 @@ LuaDialog* LuaDialog::endwrap() { LuaDialog* LuaDialog::box(sol::table options) { finishCurrentRow(); - std::string orient = options.get_or("orient", "vertical"s); - std::string label = options.get_or("label", ""s); + std::string orient = options.get_or(std::string("orient"), "vertical"s); + std::string label = options.get_or(std::string("label"), ""s); wxSizer* sizer; if (!label.empty()) { @@ -424,8 +424,8 @@ LuaDialog* LuaDialog::endbox() { LuaDialog* LuaDialog::label(sol::table options) { ensureRowSizer(); - std::string text = options.get_or("text", "label"s); - std::string id = options.get_or("id", ""s); + std::string text = options.get_or(std::string("text"), "label"s); + std::string id = options.get_or(std::string("id"), ""s); wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(text)); currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); @@ -452,8 +452,8 @@ LuaDialog* LuaDialog::mapCanvas(sol::table options) { return this; } - std::string id = options.get_or("id", "map_c_"s + std::to_string(widgets.size())); - std::string labelText = options.get_or("label", ""s); + std::string id = options.get_or(std::string("id"), "map_c_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or(std::string("label"), ""s); if (!labelText.empty()) { wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); @@ -486,10 +486,10 @@ LuaDialog* LuaDialog::mapCanvas(sol::table options) { LuaDialog* LuaDialog::input(sol::table options) { ensureRowSizer(); - std::string id = options.get_or("id", "input_"s + std::to_string(widgets.size())); - std::string labelText = options.get_or("label", ""s); - std::string text = options.get_or("text", ""s); - bool focus = options.get_or("focus", false); + std::string id = options.get_or(std::string("id"), "input_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or(std::string("label"), ""s); + std::string text = options.get_or(std::string("text"), ""s); + bool focus = options.get_or(std::string("focus"), false); if (!labelText.empty()) { wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); @@ -574,11 +574,11 @@ LuaDialog* LuaDialog::number(sol::table options) { LuaDialog* LuaDialog::slider(sol::table options) { ensureRowSizer(); - std::string id = options.get_or("id", "slider_"s + std::to_string(widgets.size())); - std::string labelText = options.get_or("label", ""s); - int value = options.get_or("value", 0); - int minVal = options.get_or("min", 0); - int maxVal = options.get_or("max", 100); + std::string id = options.get_or(std::string("id"), "slider_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or(std::string("label"), ""s); + int value = options.get_or(std::string("value"), 0); + int minVal = options.get_or(std::string("min"), 0); + int maxVal = options.get_or(std::string("max"), 100); if (!labelText.empty()) { wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); @@ -610,9 +610,9 @@ LuaDialog* LuaDialog::slider(sol::table options) { LuaDialog* LuaDialog::check(sol::table options) { ensureRowSizer(); - std::string id = options.get_or("id", "check_"s + std::to_string(widgets.size())); - std::string text = options.get_or("text", ""s); - bool selected = options.get_or("selected", false); + std::string id = options.get_or(std::string("id"), "check_"s + std::to_string(widgets.size())); + std::string text = options.get_or(std::string("text"), ""s); + bool selected = options.get_or(std::string("selected"), false); wxCheckBox* checkbox = new wxCheckBox(getParentForWidget(), wxID_ANY, wxString(text)); checkbox->SetValue(selected); @@ -643,9 +643,9 @@ LuaDialog* LuaDialog::check(sol::table options) { LuaDialog* LuaDialog::radio(sol::table options) { ensureRowSizer(); - std::string id = options.get_or("id", "radio_"s + std::to_string(widgets.size())); - std::string text = options.get_or("text", ""s); - bool selected = options.get_or("selected", false); + std::string id = options.get_or(std::string("id"), "radio_"s + std::to_string(widgets.size())); + std::string text = options.get_or(std::string("text"), ""s); + bool selected = options.get_or(std::string("selected"), false); wxRadioButton* radio = new wxRadioButton(getParentForWidget(), wxID_ANY, wxString(text)); radio->SetValue(selected); @@ -673,9 +673,9 @@ LuaDialog* LuaDialog::radio(sol::table options) { LuaDialog* LuaDialog::combobox(sol::table options) { ensureRowSizer(); - std::string id = options.get_or("id", "combobox_"s + std::to_string(widgets.size())); - std::string labelText = options.get_or("label", ""s); - std::string selected = options.get_or("option", ""s); + std::string id = options.get_or(std::string("id"), "combobox_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or(std::string("label"), ""s); + std::string selected = options.get_or(std::string("option"), ""s); wxArrayString choices; if (options["options"].valid()) { @@ -728,9 +728,9 @@ LuaDialog* LuaDialog::combobox(sol::table options) { LuaDialog* LuaDialog::button(sol::table options) { ensureRowSizer(); - std::string id = options.get_or("id", "button_"s + std::to_string(widgets.size())); - std::string text = options.get_or("text", "Button"s); - bool focus = options.get_or("focus", false); + std::string id = options.get_or(std::string("id"), "button_"s + std::to_string(widgets.size())); + std::string text = options.get_or(std::string("text"), "Button"s); + bool focus = options.get_or(std::string("focus"), false); wxButton* btn = new wxButton(getParentForWidget(), wxID_ANY, wxString(text)); currentRowSizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); @@ -761,15 +761,15 @@ LuaDialog* LuaDialog::button(sol::table options) { LuaDialog* LuaDialog::color(sol::table options) { ensureRowSizer(); - std::string id = options.get_or("id", "color_"s + std::to_string(widgets.size())); - std::string labelText = options.get_or("label", ""s); + std::string id = options.get_or(std::string("id"), "color_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or(std::string("label"), ""s); wxColour defaultColor = *wxBLACK; if (options["color"].valid()) { sol::table c = options["color"]; - int r = c.get_or("red", c.get_or(1, 0)); - int g = c.get_or("green", c.get_or(2, 0)); - int b = c.get_or("blue", c.get_or(3, 0)); + int r = c.get_or(std::string("red"), c.get_or(1, 0)); + int g = c.get_or(std::string("green"), c.get_or(2, 0)); + int b = c.get_or(std::string("blue"), c.get_or(3, 0)); defaultColor = wxColour(r, g, b); } @@ -1072,9 +1072,9 @@ LuaDialog* LuaDialog::list(sol::table options) { for (auto& pair : itemsTable) { if (pair.second.is()) { sol::table itemTable = pair.second; - std::string text = itemTable.get_or("text", ""s); - int icon = itemTable.get_or("icon", 0); - std::string tooltip = itemTable.get_or("tooltip", ""s); + std::string text = itemTable.get_or(std::string("text"), ""s); + int icon = itemTable.get_or(std::string("icon"), 0); + std::string tooltip = itemTable.get_or(std::string("tooltip"), ""s); LuaAPI::LuaImage img; if (itemTable["image"].valid()) { if (itemTable["image"].is()) { @@ -1089,7 +1089,7 @@ LuaDialog* LuaDialog::list(sol::table options) { } // Selection - int selection = options.get_or("selection", 0); + int selection = options.get_or(std::string("selection"), 0); if (selection > 0 && selection <= (int)listbox->items.size()) { listbox->SetSelection(selection - 1); } @@ -1276,11 +1276,11 @@ LuaDialog* LuaDialog::file(sol::table options) { LuaDialog* LuaDialog::image(sol::table options) { ensureRowSizer(); - std::string id = options.get_or("id", "image_"s + std::to_string(widgets.size())); - std::string labelText = options.get_or("label", ""s); - int width = options.get_or("width", -1); - int height = options.get_or("height", -1); - bool smooth = options.get_or("smooth", true); // Default to smooth scaling + std::string id = options.get_or(std::string("id"), "image_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or(std::string("label"), ""s); + int width = options.get_or(std::string("width"), -1); + int height = options.get_or(std::string("height"), -1); + bool smooth = options.get_or(std::string("smooth"), true); // Default to smooth scaling if (!labelText.empty()) { wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); @@ -1358,9 +1358,9 @@ LuaDialog* LuaDialog::image(sol::table options) { LuaDialog* LuaDialog::item(sol::table options) { ensureRowSizer(); - std::string id = options.get_or("id", "item_"s + std::to_string(widgets.size())); - std::string labelText = options.get_or("label", ""s); - int itemId = options.get_or("itemid", 0); + std::string id = options.get_or(std::string("id"), "item_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or(std::string("label"), ""s); + int itemId = options.get_or(std::string("itemid"), 0); if (!labelText.empty()) { wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText)); @@ -1385,7 +1385,7 @@ LuaDialog* LuaDialog::item(sol::table options) { values[id] = sol::make_object(lua, itemId); - bool readonly = options.get_or("readonly", false); + bool readonly = options.get_or(std::string("readonly"), false); // Bind click event btn->Bind(wxEVT_BUTTON, [this, id, btn, readonly](wxCommandEvent&) { @@ -1437,10 +1437,10 @@ LuaDialog* LuaDialog::newrow() { LuaDialog* LuaDialog::tab(sol::table options) { finishCurrentRow(); - std::string id = options.get_or("id", "tab_"s + std::to_string(widgets.size())); - std::string text = options.get_or("text", "Tab"s); - bool isButton = options.get_or("button", false) || options.get_or("is_button", false); - int insertIndex = options.get_or("index", -1); + std::string id = options.get_or(std::string("id"), "tab_"s + std::to_string(widgets.size())); + std::string text = options.get_or(std::string("text"), "Tab"s); + bool isButton = options.get_or(std::string("button"), false) || options.get_or(std::string("is_button"), false); + int insertIndex = options.get_or(std::string("index"), -1); sol::function onclick; sol::function oncontextmenu; if (options["onclick"].valid()) { @@ -1747,7 +1747,7 @@ LuaDialog* LuaDialog::show(sol::optional options) { // Process options if (options) { sol::table opts = *options; - waitMode = opts.get_or("wait", true); + waitMode = opts.get_or(std::string("wait"), true); if (opts["bounds"].valid()) { setBounds(opts["bounds"]); @@ -1884,36 +1884,36 @@ LuaDialog* LuaDialog::modify(sol::table options) { } else if (widget.type == "input") { wxTextCtrl* ctrl = static_cast(widget.widget); if (props["text"].valid()) { - ctrl->SetValue(wxString(props.get_or("text", ""s))); + ctrl->SetValue(wxString(props.get_or(std::string("text"), ""s))); } } else if (widget.type == "number") { wxSpinCtrlDouble* ctrl = static_cast(widget.widget); if (props["value"].valid()) { - ctrl->SetValue(props.get_or("value", 0.0)); + ctrl->SetValue(props.get_or(std::string("value"), 0.0)); } } else if (widget.type == "check") { wxCheckBox* ctrl = static_cast(widget.widget); if (props["selected"].valid()) { - ctrl->SetValue(props.get_or("selected", false)); + ctrl->SetValue(props.get_or(std::string("selected"), false)); } } else if (widget.type == "item") { ItemButton* ctrl = static_cast(widget.widget); if (props["itemid"].valid()) { - int newItemId = props.get_or("itemid", 0); + int newItemId = props.get_or(std::string("itemid"), 0); ctrl->SetSprite(newItemId); ctrl->Refresh(); } } else if (widget.type == "label") { wxStaticText* ctrl = static_cast(widget.widget); if (props["text"].valid()) { - ctrl->SetLabel(wxString(props.get_or("text", ""s))); + ctrl->SetLabel(wxString(props.get_or(std::string("text"), ""s))); } } else if (widget.type == "list") { LuaDialogListBox* ctrl = static_cast(widget.widget); if (props["icon_width"].valid() || props["icon_height"].valid() || props["icon_size"].valid()) { - int iconWidth = props.get_or("icon_width", ctrl->iconWidth); - int iconHeight = props.get_or("icon_height", ctrl->iconHeight); - int iconSize = props.get_or("icon_size", -1); + int iconWidth = props.get_or(std::string("icon_width"), ctrl->iconWidth); + int iconHeight = props.get_or(std::string("icon_height"), ctrl->iconHeight); + int iconSize = props.get_or(std::string("icon_size"), -1); if (iconSize > 0) { iconWidth = iconSize; iconHeight = iconSize; @@ -1921,13 +1921,13 @@ LuaDialog* LuaDialog::modify(sol::table options) { ctrl->SetIconSize(iconWidth, iconHeight); } if (props["item_height"].valid()) { - ctrl->SetItemHeight(props.get_or("item_height", ctrl->itemHeight)); + ctrl->SetItemHeight(props.get_or(std::string("item_height"), ctrl->itemHeight)); } if (props["show_text"].valid()) { - ctrl->SetShowText(props.get_or("show_text", true)); + ctrl->SetShowText(props.get_or(std::string("show_text"), true)); } if (props["smooth"].valid()) { - ctrl->SetSmooth(props.get_or("smooth", true)); + ctrl->SetSmooth(props.get_or(std::string("smooth"), true)); } if (props["items"].valid()) { ctrl->Freeze(); @@ -1936,9 +1936,9 @@ LuaDialog* LuaDialog::modify(sol::table options) { for (auto& pair : itemsTable) { if (pair.second.is()) { sol::table itemTable = pair.second; - std::string text = itemTable.get_or("text", ""s); - int icon = itemTable.get_or("icon", 0); - std::string tooltip = itemTable.get_or("tooltip", ""s); + std::string text = itemTable.get_or(std::string("text"), ""s); + int icon = itemTable.get_or(std::string("icon"), 0); + std::string tooltip = itemTable.get_or(std::string("tooltip"), ""s); LuaAPI::LuaImage img; if (itemTable["image"].valid()) { if (itemTable["image"].is()) { @@ -1954,7 +1954,7 @@ LuaDialog* LuaDialog::modify(sol::table options) { ctrl->Thaw(); } if (props["selection"].valid()) { - int selection = props.get_or("selection", 0); + int selection = props.get_or(std::string("selection"), 0); if (selection > 0 && selection <= (int)ctrl->items.size()) { ctrl->SetSelection(selection - 1); } else { @@ -1971,15 +1971,15 @@ LuaDialog* LuaDialog::modify(sol::table options) { bool updateIconSize = false; bool updateCellSize = false; - int iconSize = props.get_or("icon_size", -1); - int itemSize = props.get_or("item_size", -1); - int itemWidth = props.get_or("item_width", -1); - int itemHeight = props.get_or("item_height", -1); - int iconWidthOpt = props.get_or("icon_width", -1); - int iconHeightOpt = props.get_or("icon_height", -1); - int cellSize = props.get_or("cell_size", -1); - int cellWidthOpt = props.get_or("cell_width", -1); - int cellHeightOpt = props.get_or("cell_height", -1); + int iconSize = props.get_or(std::string("icon_size"), -1); + int itemSize = props.get_or(std::string("item_size"), -1); + int itemWidth = props.get_or(std::string("item_width"), -1); + int itemHeight = props.get_or(std::string("item_height"), -1); + int iconWidthOpt = props.get_or(std::string("icon_width"), -1); + int iconHeightOpt = props.get_or(std::string("icon_height"), -1); + int cellSize = props.get_or(std::string("cell_size"), -1); + int cellWidthOpt = props.get_or(std::string("cell_width"), -1); + int cellHeightOpt = props.get_or(std::string("cell_height"), -1); if (iconSize > 0) { iconWidth = iconSize; @@ -2098,9 +2098,9 @@ LuaDialog* LuaDialog::modify(sol::table options) { wxStaticBitmap* ctrl = static_cast(widget.widget); LuaAPI::LuaImage luaImage; - int width = props.get_or("width", -1); - int height = props.get_or("height", -1); - bool smooth = props.get_or("smooth", true); + int width = props.get_or(std::string("width"), -1); + int height = props.get_or(std::string("height"), -1); + bool smooth = props.get_or(std::string("smooth"), true); if (props["image"].valid()) { luaImage = props["image"].get(); @@ -2148,19 +2148,19 @@ LuaDialog* LuaDialog::modify(sol::table options) { LuaDialog* LuaDialog::grid(sol::table options) { ensureRowSizer(); - std::string id = options.get_or("id", "grid_"s + std::to_string(widgets.size())); - std::string labelText = options.get_or("label", ""s); - int iconWidth = options.get_or("icon_width", 32); - int iconHeight = options.get_or("icon_height", 32); - int iconSize = options.get_or("icon_size", -1); - int itemSize = options.get_or("item_size", -1); - int itemWidth = options.get_or("item_width", -1); - int itemHeight = options.get_or("item_height", -1); - int cellSize = options.get_or("cell_size", -1); - int cellWidth = options.get_or("cell_width", -1); - int cellHeight = options.get_or("cell_height", -1); - bool labelWrap = options.get_or("label_wrap", true); - bool showText = options.get_or("show_text", true); + std::string id = options.get_or(std::string("id"), "grid_"s + std::to_string(widgets.size())); + std::string labelText = options.get_or(std::string("label"), ""s); + int iconWidth = options.get_or(std::string("icon_width"), 32); + int iconHeight = options.get_or(std::string("icon_height"), 32); + int iconSize = options.get_or(std::string("icon_size"), -1); + int itemSize = options.get_or(std::string("item_size"), -1); + int itemWidth = options.get_or(std::string("item_width"), -1); + int itemHeight = options.get_or(std::string("item_height"), -1); + int cellSize = options.get_or(std::string("cell_size"), -1); + int cellWidth = options.get_or(std::string("cell_width"), -1); + int cellHeight = options.get_or(std::string("cell_height"), -1); + bool labelWrap = options.get_or(std::string("label_wrap"), true); + bool showText = options.get_or(std::string("show_text"), true); if (iconSize > 0) { iconWidth = iconSize; @@ -2234,7 +2234,7 @@ LuaDialog* LuaDialog::grid(sol::table options) { for (auto& pair : itemsTable) { if (pair.second.is()) { sol::table itemTable = pair.second; - std::string text = itemTable.get_or("text", ""s); + std::string text = itemTable.get_or(std::string("text"), ""s); if (!showText) { text.clear(); } @@ -2258,7 +2258,7 @@ LuaDialog* LuaDialog::grid(sol::table options) { // Store original item index/id in data if needed, or rely on position grid->SetItemData(index, index + 1); // 1-based index - std::string tooltip = itemTable.get_or("tooltip", ""s); + std::string tooltip = itemTable.get_or(std::string("tooltip"), ""s); if (!tooltip.empty()) { grid->AddTooltip(index, tooltip); } @@ -2477,10 +2477,10 @@ sol::object LuaDialog::getActiveTab() { } void LuaDialog::setBounds(sol::table bounds) { - int x = bounds.get_or("x", GetPosition().x); - int y = bounds.get_or("y", GetPosition().y); - int w = bounds.get_or("width", GetSize().GetWidth()); - int h = bounds.get_or("height", GetSize().GetHeight()); + int x = bounds.get_or(std::string("x"), GetPosition().x); + int y = bounds.get_or(std::string("y"), GetPosition().y); + int w = bounds.get_or(std::string("width"), GetSize().GetWidth()); + int h = bounds.get_or(std::string("height"), GetSize().GetHeight()); SetSize(x, y, w, h); } diff --git a/source/lua/lua_script_manager.cpp b/source/lua/lua_script_manager.cpp index 5c3a2c794..3665ed31f 100644 --- a/source/lua/lua_script_manager.cpp +++ b/source/lua/lua_script_manager.cpp @@ -123,10 +123,10 @@ static wxColor parseColor(const sol::object& obj, const wxColor& fallback) { if (obj.is()) { sol::table tbl = obj.as(); - int r = tbl.get_or("r", tbl.get_or("red", 255)); - int g = tbl.get_or("g", tbl.get_or("green", 255)); - int b = tbl.get_or("b", tbl.get_or("blue", 255)); - int a = tbl.get_or("a", tbl.get_or("alpha", 255)); + int r = tbl.get_or(std::string("r"), tbl.get_or(std::string("red"), 255)); + int g = tbl.get_or(std::string("g"), tbl.get_or(std::string("green"), 255)); + int b = tbl.get_or(std::string("b"), tbl.get_or(std::string("blue"), 255)); + int a = tbl.get_or(std::string("a"), tbl.get_or(std::string("alpha"), 255)); if (tbl[1].valid()) { r = tbl.get_or(1, r); } @@ -152,8 +152,8 @@ bool LuaScriptManager::addMapOverlay(const std::string& id, sol::table options) MapOverlay overlay; overlay.id = id; - overlay.enabled = options.get_or("enabled", true); - overlay.order = options.get_or("order", 0); + overlay.enabled = options.get_or(std::string("enabled"), true); + overlay.order = options.get_or(std::string("order"), 0); if (options["ondraw"].valid()) { overlay.ondraw = options["ondraw"]; } @@ -309,14 +309,14 @@ void LuaScriptManager::collectMapOverlayCommands(const MapViewInfo& view, std::v } MapOverlayCommand cmd; cmd.type = MapOverlayCommand::Type::Rect; - cmd.screen_space = opts.get_or("screen", false); - cmd.filled = opts.get_or("filled", true); - cmd.width = opts.get_or("width", 1); - cmd.x = opts.get_or("x", 0); - cmd.y = opts.get_or("y", 0); - cmd.z = opts.get_or("z", view.floor); - cmd.w = opts.get_or("w", 1); - cmd.h = opts.get_or("h", 1); + cmd.screen_space = opts.get_or(std::string("screen"), false); + cmd.filled = opts.get_or(std::string("filled"), true); + cmd.width = opts.get_or(std::string("width"), 1); + cmd.x = opts.get_or(std::string("x"), 0); + cmd.y = opts.get_or(std::string("y"), 0); + cmd.z = opts.get_or(std::string("z"), view.floor); + cmd.w = opts.get_or(std::string("w"), 1); + cmd.h = opts.get_or(std::string("h"), 1); cmd.color = parseColor(opts["color"], wxColor(255, 255, 255, 128)); out.push_back(cmd); }; @@ -328,14 +328,14 @@ void LuaScriptManager::collectMapOverlayCommands(const MapViewInfo& view, std::v } MapOverlayCommand cmd; cmd.type = MapOverlayCommand::Type::Line; - cmd.screen_space = opts.get_or("screen", false); - cmd.width = opts.get_or("width", 1); - cmd.x = opts.get_or("x1", 0); - cmd.y = opts.get_or("y1", 0); - cmd.z = opts.get_or("z1", view.floor); - cmd.x2 = opts.get_or("x2", 0); - cmd.y2 = opts.get_or("y2", 0); - cmd.z2 = opts.get_or("z2", view.floor); + cmd.screen_space = opts.get_or(std::string("screen"), false); + cmd.width = opts.get_or(std::string("width"), 1); + cmd.x = opts.get_or(std::string("x1"), 0); + cmd.y = opts.get_or(std::string("y1"), 0); + cmd.z = opts.get_or(std::string("z1"), view.floor); + cmd.x2 = opts.get_or(std::string("x2"), 0); + cmd.y2 = opts.get_or(std::string("y2"), 0); + cmd.z2 = opts.get_or(std::string("z2"), view.floor); cmd.color = parseColor(opts["color"], wxColor(255, 255, 255, 200)); out.push_back(cmd); }; @@ -347,11 +347,11 @@ void LuaScriptManager::collectMapOverlayCommands(const MapViewInfo& view, std::v } MapOverlayCommand cmd; cmd.type = MapOverlayCommand::Type::Text; - cmd.screen_space = opts.get_or("screen", false); - cmd.x = opts.get_or("x", 0); - cmd.y = opts.get_or("y", 0); - cmd.z = opts.get_or("z", view.floor); - cmd.text = opts.get_or("text", std::string()); + cmd.screen_space = opts.get_or(std::string("screen"), false); + cmd.x = opts.get_or(std::string("x"), 0); + cmd.y = opts.get_or(std::string("y"), 0); + cmd.z = opts.get_or(std::string("z"), view.floor); + cmd.text = opts.get_or(std::string("text"), std::string()); cmd.color = parseColor(opts["color"], wxColor(255, 255, 255, 255)); if (!cmd.text.empty()) { out.push_back(cmd); @@ -431,13 +431,13 @@ void LuaScriptManager::updateMapOverlayHover(int map_x, int map_y, int map_z, in if (table["highlight"].valid()) { sol::table h = table["highlight"]; highlight.type = MapOverlayCommand::Type::Rect; - highlight.x = h.get_or("x", map_x); - highlight.y = h.get_or("y", map_y); - highlight.z = h.get_or("z", map_z); - highlight.w = h.get_or("w", 1); - highlight.h = h.get_or("h", 1); - highlight.filled = h.get_or("filled", false); - highlight.width = h.get_or("width", 1); + highlight.x = h.get_or(std::string("x"), map_x); + highlight.y = h.get_or(std::string("y"), map_y); + highlight.z = h.get_or(std::string("z"), map_z); + highlight.w = h.get_or(std::string("w"), 1); + highlight.h = h.get_or(std::string("h"), 1); + highlight.filled = h.get_or(std::string("filled"), false); + highlight.width = h.get_or(std::string("width"), 1); highlight.color = parseColor(h["color"], wxColor(255, 255, 0, 128)); hasHighlight = true; } @@ -448,7 +448,7 @@ void LuaScriptManager::updateMapOverlayHover(int map_x, int map_y, int map_z, in tooltip.color = wxColor(255, 255, 255, 255); } else if (table["tooltip"].is()) { sol::table t = table["tooltip"]; - tooltip.text = t.get_or("text", std::string()); + tooltip.text = t.get_or(std::string("text"), std::string()); tooltip.color = parseColor(t["color"], wxColor(255, 255, 255, 255)); } From c2a216dd99ec108f3c11b29b11853d5269efa18e Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 23:28:49 +0100 Subject: [PATCH 24/41] docs: update documentation related to init new script project --- scripts/README.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/scripts/README.md b/scripts/README.md index 413acdad5..678b9777e 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -47,7 +47,13 @@ Scripts are executed sequentially when the editor loads. You can define global f ### Script Metadata -Scripts can define metadata in their header to be displayed in the editor's menu and logs. + +There are two ways to create scripts in RME: + +#### Option 1: Single File Script (The Easy Way) +This is best for simple scripts, tools, or quick experiments. You just create a single `.lua` file in the `scripts/` directory. + +You can define metadata in the file header using comments. The editor parses these to display information in the menus. ```lua -- My Script Name v1.0 @@ -76,6 +82,32 @@ You can also use special tags for specific fields: -- @Shortcut: Ctrl+Shift+G ``` +#### Option 2: Package Script (The Advanced Way) + +This method is recommended for complex tools or extensions that require multiple files, custom modules, or external resources (like images or data files). + +1. Create a subdirectory in `scripts/` (e.g., `scripts/my_cool_tool/`). +2. Create a `manifest.lua` file inside that directory. +3. Place your main script and resources in that directory. + +**manifest.lua Example:** +```lua +return { + name = "My Cool Tool", + description = "A tool that does amazing things.", + author = "Jane Doe", + version = "1.0", + main = "main.lua", -- Entry point script (defaults to main.lua) + autorun = true, -- Run automatically on startup (optional) + shortcut = "Ctrl+Alt+T" -- Keyboard shortcut (optional) +} +``` + +When using packages, the `SCRIPT_DIR` global variable in your main script will point to your package directory, allowing you to easily load resources: +```lua +local img = Image.fromFile(SCRIPT_DIR .. "/assets/icon.png") +``` + ### Best Practices * Wrap your functionality in local functions. * Use `app.transaction` for any operations that modify the map to ensure Undo/Redo support works correctly. From 13e5196bdfef5a19990fc77f71dd4b38aeadcf44 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 23:34:36 +0100 Subject: [PATCH 25/41] ci: include windows CI --- .github/workflows/build-windows.yml | 79 +++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/workflows/build-windows.yml diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml new file mode 100644 index 000000000..1acdec731 --- /dev/null +++ b/.github/workflows/build-windows.yml @@ -0,0 +1,79 @@ +name: Build - Windows + +# Trigger on manual dispatch, and push/PR to master +on: + workflow_dispatch: + push: + branches: + - master + pull_request: + branches: + - master + +permissions: + contents: read + +jobs: + build-windows: + name: Windows-Release + runs-on: windows-2022 + + env: + CMAKE_BUILD_PARALLEL_LEVEL: 2 + LUKKA_RUN_VCPKG_SHA: '734f8130ffe2f02cf855a3a42a2958f01b3fb005' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Get latest CMake and Ninja + uses: lukka/get-cmake@v3.31.6 + + - name: Setup vcpkg + uses: lukka/run-vcpkg@v11 + with: + vcpkgGitCommitId: ${{ env.LUKKA_RUN_VCPKG_SHA }} + + - name: Configure CMake + shell: pwsh + run: | + cmake -S . -B build -G "Visual Studio 17 2022" -A x64 ` + -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" ` + -DVCPKG_TARGET_TRIPLET=x64-windows-static ` + -DCMAKE_BUILD_TYPE=Release + + - name: Install Dependencies via vcpkg + shell: pwsh + run: | + $deps = @("wxwidgets", "boost-thread", "boost-system", "libarchive", "freeglut", "zlib", "lua", "cpr", "nlohmann-json") + & "$env:VCPKG_ROOT/vcpkg" install $deps --triplet x64-windows-static + + - name: Build + shell: pwsh + run: | + cmake --build build --config Release + + - name: Prepare Artifacts + shell: pwsh + run: | + $artifactDir = "$env:GITHUB_WORKSPACE\artifacts" + New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null + + # Find the executable + $exePath = Get-ChildItem -Recurse -Path "$env:GITHUB_WORKSPACE\build" -Filter "rme.exe" | Select-Object -First 1 + + if (-not $exePath) { + Write-Error "Not found rme.exe!" + exit 1 + } + + Copy-Item $exePath.FullName -Destination $artifactDir + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: rme-windows + path: artifacts/ From 97d26be1c85572232062dc5d4ccf27033fb6b6f6 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 23:45:15 +0100 Subject: [PATCH 26/41] ci: label correctly linux build, add release CI --- .../workflows/{build.yml => build-linux.yml} | 2 +- .github/workflows/release.yml | 144 ++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) rename .github/workflows/{build.yml => build-linux.yml} (98%) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build-linux.yml similarity index 98% rename from .github/workflows/build.yml rename to .github/workflows/build-linux.yml index 742b43a66..b98bd033d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build-linux.yml @@ -1,4 +1,4 @@ -name: Build RME +name: Build - Linux on: push: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..6db37b91b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,144 @@ +name: Release + +on: + push: + tags: + - v* + +permissions: + contents: write + +jobs: + release: + name: Release ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + asset_prefix: rme-linux + - os: windows-2022 + asset_prefix: rme-windows + + env: + CMAKE_BUILD_PARALLEL_LEVEL: 2 + LUKKA_RUN_VCPKG_SHA: '734f8130ffe2f02cf855a3a42a2958f01b3fb005' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + # --- Linux Dependencies --- + - name: Install Dependencies (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential cmake ccache libasio-dev nlohmann-json3-dev \ + libfmt-dev libboost-system-dev libboost-thread-dev libwxgtk3.2-dev \ + libglu1-mesa-dev freeglut3-dev libarchive-dev zlib1g-dev \ + libxmu-dev libxi-dev liblua5.3-dev + + # --- Windows Dependencies --- + - name: Get latest CMake (Windows) + if: runner.os == 'Windows' + uses: lukka/get-cmake@v3.31.6 + + - name: Setup vcpkg (Windows) + if: runner.os == 'Windows' + uses: lukka/run-vcpkg@v11 + with: + vcpkgGitCommitId: ${{ env.LUKKA_RUN_VCPKG_SHA }} + + - name: Install Dependencies via vcpkg (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $deps = @("wxwidgets", "boost-thread", "boost-system", "libarchive", "freeglut", "zlib", "lua", "cpr", "nlohmann-json") + & "$env:VCPKG_ROOT/vcpkg" install $deps --triplet x64-windows-static + + # --- Configure --- + - name: Configure CMake (Linux) + if: runner.os == 'Linux' + run: | + cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + + - name: Configure CMake (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + cmake -S . -B build -G "Visual Studio 17 2022" -A x64 ` + -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" ` + -DVCPKG_TARGET_TRIPLET=x64-windows-static ` + -DCMAKE_BUILD_TYPE=Release + + # --- Build --- + - name: Build + run: cmake --build build --config Release --parallel 2 + + # --- Package --- + - name: Prepare Release Bundle + shell: bash + run: | + mkdir release_package + + # Copy Executable + if [ "${{ runner.os }}" == "Windows" ]; then + find build -name "rme.exe" -exec cp {} release_package/ \; + else + find build -name "rme" -exec cp {} release_package/ \; + chmod +x release_package/rme + fi + + # Copy Resources + cp -r data release_package/ + [ -d "brushes" ] && cp -r brushes release_package/ + [ -d "icons" ] && cp -r icons release_package/ + [ -d "extensions" ] && cp -r extensions release_package/ + [ -d "modules" ] && cp -r modules release_package/ + [ -d "scripts" ] && cp -r scripts release_package/ + + # Copy License/Readme + [ -f "LICENSE" ] && cp LICENSE release_package/ + [ -f "LICENSE.rtf" ] && cp LICENSE.rtf release_package/ + [ -f "README.md" ] && cp README.md release_package/ + + - name: Prepare Source Bundle + if: runner.os == 'Windows' + shell: bash + run: | + mkdir source_package + [ -d "source" ] && cp -r source source_package/ + [ -d "vcproj" ] && cp -r vcproj source_package/ + [ -f "CMakeLists.txt" ] && cp CMakeLists.txt source_package/ + [ -f "vcpkg.json" ] && cp vcpkg.json source_package/ + [ -f "LICENSE.rtf" ] && cp LICENSE.rtf source_package/ + [ -f "README.md" ] && cp README.md source_package/ + + - name: Zip Source + if: runner.os == 'Windows' + uses: thedoctor0/zip-release@0.7.6 + with: + type: zip + filename: rme-source-${{ github.ref_name }}.zip + path: source_package/ + + - name: Zip Release + uses: thedoctor0/zip-release@0.7.6 + with: + type: zip + filename: ${{ matrix.asset_prefix }}-${{ github.ref_name }}.zip + path: release_package/ + + # --- Upload --- + - name: Upload Release Artifact + uses: ncipollo/release-action@v1 + with: + artifacts: "*.zip" + allowUpdates: true + replacesArtifacts: true + token: ${{ secrets.GITHUB_TOKEN }} From 0eeed5acd33c1c0894fbec32b1e1c28eb3cc18b3 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Fri, 23 Jan 2026 23:52:09 +0100 Subject: [PATCH 27/41] ci: update release GA tags to handle OTA release convention correctly --- .github/workflows/release.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6db37b91b..f4dc852f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,11 @@ name: Release on: push: tags: - - v* + - 'v*' + - 'Hotfix*' + - 'Release*' + - 'Hotfix *' + - 'Release *' permissions: contents: write From a541a185b6908bdbb03f00eecab15457c940b198 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Sat, 24 Jan 2026 03:44:42 +0100 Subject: [PATCH 28/41] feat: expose keyboard methods, mouse methods and internal selection methods --- source/action.cpp | 4 --- source/lua/lua_api_app.cpp | 18 +++++++++++++ source/lua/lua_api_selection.cpp | 42 +++++++++++++++++++++---------- source/lua/lua_script_manager.cpp | 4 +++ source/lua/lua_script_manager.h | 23 +++++++++++++++++ source/map_display.cpp | 24 ++++++++++++++++++ source/map_drawer.cpp | 9 +++++++ source/map_overlay.h | 1 + source/selection.cpp | 19 +++++++++++--- 9 files changed, 123 insertions(+), 21 deletions(-) diff --git a/source/action.cpp b/source/action.cpp index a0e524f80..6d728e138 100644 --- a/source/action.cpp +++ b/source/action.cpp @@ -142,7 +142,6 @@ size_t Action::memsize() const { } void Action::commit(DirtyList* dirty_list) { - editor.selection.start(Selection::INTERNAL); ChangeList::const_iterator it = changes.begin(); while (it != changes.end()) { Change* c = *it; @@ -281,7 +280,6 @@ void Action::commit(DirtyList* dirty_list) { } ++it; } - editor.selection.finish(Selection::INTERNAL); commited = true; } @@ -290,7 +288,6 @@ void Action::undo(DirtyList* dirty_list) { return; } - editor.selection.start(Selection::INTERNAL); ChangeList::reverse_iterator it = changes.rbegin(); while (it != changes.rend()) { @@ -410,7 +407,6 @@ void Action::undo(DirtyList* dirty_list) { } ++it; } - editor.selection.finish(Selection::INTERNAL); commited = false; } diff --git a/source/lua/lua_api_app.cpp b/source/lua/lua_api_app.cpp index 96e98830c..2a3ca407a 100644 --- a/source/lua/lua_api_app.cpp +++ b/source/lua/lua_api_app.cpp @@ -660,6 +660,24 @@ namespace LuaAPI { } }; + // Keyboard modification state + sol::table keyboard = lua.create_table(); + keyboard["isCtrlDown"] = []() -> bool { + return wxGetKeyState(WXK_CONTROL); + }; + keyboard["isShiftDown"] = []() -> bool { + return wxGetKeyState(WXK_SHIFT); + }; + keyboard["isAltDown"] = []() -> bool { + return wxGetKeyState(WXK_ALT); + }; + app["keyboard"] = keyboard; + + // Clipboard / Edit operations + app["copy"] = []() { g_gui.DoCopy(); }; + app["cut"] = []() { g_gui.DoCut(); }; + app["paste"] = []() { g_gui.DoPaste(); }; + // Map overlay system sol::table mapView = lua.create_table(); mapView["addOverlay"] = [](sol::variadic_args va) -> bool { diff --git a/source/lua/lua_api_selection.cpp b/source/lua/lua_api_selection.cpp index 6b5bb8915..ae5adad0c 100644 --- a/source/lua/lua_api_selection.cpp +++ b/source/lua/lua_api_selection.cpp @@ -103,35 +103,51 @@ namespace LuaAPI { }), // Methods + "start", [](Selection* sel) { + if (sel && !sel->isBusy()) { + sel->start(Selection::INTERNAL); + } + }, + "finish", [](Selection* sel) { + if (sel && sel->isBusy()) { + sel->finish(Selection::INTERNAL); + } + }, "clear", [](Selection* sel) { - if (sel) { - sel->start(Selection::INTERNAL); - sel->clear(); - sel->finish(Selection::INTERNAL); - } }, + if (sel) { + bool managed = !sel->isBusy(); + if (managed) sel->start(Selection::INTERNAL); + sel->clear(); + if (managed) sel->finish(Selection::INTERNAL); + } + }, "add", sol::overload([](Selection* sel, Tile* tile) { if (sel && tile) { - sel->start(Selection::INTERNAL); + bool managed = !sel->isBusy(); + if (managed) sel->start(Selection::INTERNAL); sel->add(tile); - sel->finish(Selection::INTERNAL); + if (managed) sel->finish(Selection::INTERNAL); } }, [](Selection* sel, Tile* tile, Item* item) { if (sel && tile && item) { - sel->start(Selection::INTERNAL); + bool managed = !sel->isBusy(); + if (managed) sel->start(Selection::INTERNAL); sel->add(tile, item); - sel->finish(Selection::INTERNAL); + if (managed) sel->finish(Selection::INTERNAL); } }), "remove", sol::overload([](Selection* sel, Tile* tile) { if (sel && tile) { - sel->start(Selection::INTERNAL); + bool managed = !sel->isBusy(); + if (managed) sel->start(Selection::INTERNAL); sel->remove(tile); - sel->finish(Selection::INTERNAL); + if (managed) sel->finish(Selection::INTERNAL); } }, [](Selection* sel, Tile* tile, Item* item) { if (sel && tile && item) { - sel->start(Selection::INTERNAL); + bool managed = !sel->isBusy(); + if (managed) sel->start(Selection::INTERNAL); sel->remove(tile, item); - sel->finish(Selection::INTERNAL); + if (managed) sel->finish(Selection::INTERNAL); } }), // String representation diff --git a/source/lua/lua_script_manager.cpp b/source/lua/lua_script_manager.cpp index 3665ed31f..7ae90d3b0 100644 --- a/source/lua/lua_script_manager.cpp +++ b/source/lua/lua_script_manager.cpp @@ -312,6 +312,8 @@ void LuaScriptManager::collectMapOverlayCommands(const MapViewInfo& view, std::v cmd.screen_space = opts.get_or(std::string("screen"), false); cmd.filled = opts.get_or(std::string("filled"), true); cmd.width = opts.get_or(std::string("width"), 1); + std::string style = opts.get_or(std::string("style"), std::string("solid")); + cmd.dashed = (style == "dotted" || style == "dashed"); cmd.x = opts.get_or(std::string("x"), 0); cmd.y = opts.get_or(std::string("y"), 0); cmd.z = opts.get_or(std::string("z"), view.floor); @@ -330,6 +332,8 @@ void LuaScriptManager::collectMapOverlayCommands(const MapViewInfo& view, std::v cmd.type = MapOverlayCommand::Type::Line; cmd.screen_space = opts.get_or(std::string("screen"), false); cmd.width = opts.get_or(std::string("width"), 1); + std::string style = opts.get_or(std::string("style"), std::string("solid")); + cmd.dashed = (style == "dotted" || style == "dashed"); cmd.x = opts.get_or(std::string("x1"), 0); cmd.y = opts.get_or(std::string("y1"), 0); cmd.z = opts.get_or(std::string("z1"), view.floor); diff --git a/source/lua/lua_script_manager.h b/source/lua/lua_script_manager.h index 56583a097..41b12ba1e 100644 --- a/source/lua/lua_script_manager.h +++ b/source/lua/lua_script_manager.h @@ -122,6 +122,29 @@ class LuaScriptManager { } } + template + bool emitCancellable(const std::string& eventName, Args&&... args) { + if (!initialized) { + return false; + } + + bool consumed = false; + for (auto& listener : eventListeners) { + if (listener.eventName == eventName && listener.callback.valid()) { + try { + sol::object result = listener.callback(std::forward(args)...); + if (result.valid() && result.is() && result.as()) { + consumed = true; + break; // Stop propagation + } + } catch (const sol::error& e) { + logOutput("Event '" + eventName + "' error: " + std::string(e.what()), true); + } + } + } + return consumed; + } + // Clear all registered callbacks (called before script reload) void clearAllCallbacks(); diff --git a/source/map_display.cpp b/source/map_display.cpp index e7f8dd45c..bdcf542b1 100644 --- a/source/map_display.cpp +++ b/source/map_display.cpp @@ -474,6 +474,10 @@ void MapCanvas::OnMouseMove(wxMouseEvent& event) { } } + if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("onMouseMove", mouse_map_x, mouse_map_y, floor)) { + return; + } + if (g_gui.IsSelectionMode()) { if (map_update && isPasting()) { Refresh(); @@ -585,10 +589,20 @@ void MapCanvas::OnMouseMove(wxMouseEvent& event) { } void MapCanvas::OnMouseLeftRelease(wxMouseEvent& event) { + int map_x, map_y; + ScreenToMap(event.GetX(), event.GetY(), &map_x, &map_y); + if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("onMouseRelease", map_x, map_y, floor, std::string("left"))) { + return; + } OnMouseActionRelease(event); } void MapCanvas::OnMouseLeftClick(wxMouseEvent& event) { + int map_x, map_y; + ScreenToMap(event.GetX(), event.GetY(), &map_x, &map_y); + if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("onMousePress", map_x, map_y, floor, std::string("left"))) { + return; + } OnMouseActionClick(event); } @@ -646,6 +660,11 @@ void MapCanvas::OnMouseCenterRelease(wxMouseEvent& event) { } void MapCanvas::OnMouseRightClick(wxMouseEvent& event) { + int map_x, map_y; + ScreenToMap(event.GetX(), event.GetY(), &map_x, &map_y); + if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("onMousePress", map_x, map_y, floor, std::string("right"))) { + return; + } if (g_settings.getInteger(Config::SWITCH_MOUSEBUTTONS)) { OnMouseCameraClick(event); } else { @@ -654,6 +673,11 @@ void MapCanvas::OnMouseRightClick(wxMouseEvent& event) { } void MapCanvas::OnMouseRightRelease(wxMouseEvent& event) { + int map_x, map_y; + ScreenToMap(event.GetX(), event.GetY(), &map_x, &map_y); + if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("onMouseRelease", map_x, map_y, floor, std::string("right"))) { + return; + } if (g_settings.getInteger(Config::SWITCH_MOUSEBUTTONS)) { OnMouseCameraRelease(event); } else { diff --git a/source/map_drawer.cpp b/source/map_drawer.cpp index 4e0b431ce..81cefe737 100644 --- a/source/map_drawer.cpp +++ b/source/map_drawer.cpp @@ -365,12 +365,21 @@ bool MapDrawer::drawOverlayCommands(const std::vector& comman continue; } + if (cmd.dashed) { + glEnable(GL_LINE_STIPPLE); + glLineStipple(1, 0x00FF); + } + glLineWidth(cmd.width); glColor4ub(cmd.color.Red(), cmd.color.Green(), cmd.color.Blue(), cmd.color.Alpha()); glBegin(GL_LINES); glVertex2f(x1, y1); glVertex2f(x2, y2); glEnd(); + + if (cmd.dashed) { + glDisable(GL_LINE_STIPPLE); + } } else if (cmd.type == MapOverlayCommand::Type::Text) { if (!cmd.text.empty()) { if (isScreenSpace) { diff --git a/source/map_overlay.h b/source/map_overlay.h index 40be36228..36510731e 100644 --- a/source/map_overlay.h +++ b/source/map_overlay.h @@ -46,6 +46,7 @@ struct MapOverlayCommand { Type type = Type::Rect; bool screen_space = false; bool filled = true; + bool dashed = false; int width = 1; int x = 0; diff --git a/source/selection.cpp b/source/selection.cpp index 89833ac68..13c1a4bd7 100644 --- a/source/selection.cpp +++ b/source/selection.cpp @@ -223,7 +223,9 @@ void Selection::clear() { } void Selection::start(SessionFlags flags) { - if (!(flags & INTERNAL)) { + if (flags & INTERNAL) { + subsession = editor.actionQueue->createAction(ACTION_SELECT); + } else if (!(flags & INTERNAL)) { if (flags & SUBTHREAD) { ; } else { @@ -251,7 +253,13 @@ void Selection::commit() { } void Selection::finish(SessionFlags flags) { - if (!(flags & INTERNAL)) { + if (flags & INTERNAL) { + if (subsession) { + subsession->commit(nullptr); + delete subsession; + subsession = nullptr; + } + } else if (!(flags & INTERNAL)) { if (flags & SUBTHREAD) { ASSERT(subsession); subsession = nullptr; @@ -271,8 +279,11 @@ void Selection::finish(SessionFlags flags) { } busy = false; - // Notify Lua scripts only if we're on the main thread and it's a "real" selection change - if (!(flags & (INTERNAL | SUBTHREAD))) { + // Update status bar + updateSelectionCount(); + + // Notify Lua scripts + if (!(flags & SUBTHREAD)) { if (g_luaScripts.isInitialized()) { g_luaScripts.emit("selectionChange"); } From 6aeed4a0e724790fd7eeb9c7e7c6a528c6d836c2 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Sat, 24 Jan 2026 03:44:51 +0100 Subject: [PATCH 29/41] docs: improve documentation --- scripts/README.md | 13 +++++++++++++ scripts/linter.lua | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/scripts/README.md b/scripts/README.md index 678b9777e..59fda686e 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -127,6 +127,7 @@ The `app` table provides access to global editor state and utility functions. | `app.apiVersion` | number | The API version number (currently 2). | | `app.map` | [Map](#map) | Returns the currently active Map object (or nil). | | `app.selection` | [Selection](#selection) | Returns the current Selection object. | +| `app.keyboard` | [Keyboard](#keyboard) | State of modifier keys. | | `app.borders` | table | Table of all available borders. | | `app.brush` | [Brush](#brushes) | The currently selected brush. | | `app.brushSize` | number | Current brush size (radius). | @@ -135,6 +136,9 @@ The `app` table provides access to global editor state and utility functions. | `app.getDataDirectory()` | function | Returns the absolute path to the data directory. | | `app.hasMap()` | function | Returns `true` if a map is currently open. | | `app.refresh()` | function | Refreshes the map view. | +| `app.copy()` | function | Copies current selection to internal clipboard. | +| `app.cut()` | function | Cuts current selection to internal clipboard. | +| `app.paste()` | function | Pastes from internal clipboard to current map position. | | `app.setClipboard(text)` | function | Sets the system clipboard text. | | `app.setCameraPosition(x, y, z)` | function | Moves the camera to the specified map coordinates. | | `app.storage(name)` | function | Returns a per-script storage helper (`load/save/clear`) backed by JSON. | @@ -143,6 +147,15 @@ The `app` table provides access to global editor state and utility functions. | `app.yield()` | function | Yields to process pending UI events. Use in long-running loops to prevent UI freeze. | | `app.sleep(ms)` | function | Sleeps for the given milliseconds (max 10000). Blocks the UI thread. | +#### Keyboard +Access via `app.keyboard`. + +| Method | Description | +| :--- | :--- | +| `isCtrlDown()` | Returns true if Control key is pressed. | +| `isShiftDown()` | Returns true if Shift key is pressed. | +| `isAltDown()` | Returns true if Alt key is pressed. | + #### Events The `app.events` object allows scripts to listen for global editor events. diff --git a/scripts/linter.lua b/scripts/linter.lua index 8d93c2476..8987cc551 100644 --- a/scripts/linter.lua +++ b/scripts/linter.lua @@ -124,6 +124,11 @@ function TileClass:getPosition() return {x=0,y=0,z=0} end ---@field bounds table ---@field minPosition Position ---@field maxPosition Position +local SelectionClass = {} +--- Starts a batched selection operation (improves performance). +function SelectionClass:start() end +--- Finishes a batched selection operation and commits changes. +function SelectionClass:finish() end ---@class Map ---@field name string @@ -143,8 +148,12 @@ function MapClass:getTile(x, y, z) return nil end ---@field editor Editor ---@field events Events ---@field mapView MapView +---@field keyboard Keyboard ---@field alert fun(message: string) ---@field refresh fun() +---@field copy fun() Copies current selection to clipboard +---@field cut fun() Cuts current selection to clipboard +---@field paste fun() Pastes from clipboard ---@field transaction fun(name: string, callback: fun()) ---@field addContextMenu fun(label: string, callback: fun()) ---@field selectRaw fun(id: number) @@ -154,6 +163,12 @@ function MapClass:getTile(x, y, z) return nil end ---@field sleep fun(milliseconds: number) Sleeps for the given milliseconds (max 10000). Blocks UI. app = {} +---@class Keyboard +---@field isCtrlDown fun(): boolean +---@field isShiftDown fun(): boolean +---@field isAltDown fun(): boolean +local Keyboard = {} + ---@class Events ---@field on fun(self: Events, eventName: string, callback: fun()): number ---@field off fun(self: Events, listenerId: number): boolean @@ -313,6 +328,8 @@ function DialogClass:layout() end ---@field filled? boolean ---@field width? number ---@field screen? boolean +---@field style? "solid"|"dotted"|"dashed" +---@field dashed? boolean ---@class MapOverlayLineOptions ---@field x1 number @@ -324,6 +341,8 @@ function DialogClass:layout() end ---@field color? {r?: number, g?: number, b?: number, a?: number} ---@field width? number ---@field screen? boolean +---@field style? "solid"|"dotted"|"dashed" +---@field dashed? boolean ---@class MapOverlayTextOptions ---@field x number From 26137dcaddc82ffc160004f900c795603cc87fd2 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sat, 24 Jan 2026 02:45:26 +0000 Subject: [PATCH 30/41] Code format - (Clang-format) --- source/lua/lua_api_selection.cpp | 39 +++++++++++++++++++------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/source/lua/lua_api_selection.cpp b/source/lua/lua_api_selection.cpp index ae5adad0c..759eae6dc 100644 --- a/source/lua/lua_api_selection.cpp +++ b/source/lua/lua_api_selection.cpp @@ -106,48 +106,55 @@ namespace LuaAPI { "start", [](Selection* sel) { if (sel && !sel->isBusy()) { sel->start(Selection::INTERNAL); - } - }, + } }, "finish", [](Selection* sel) { if (sel && sel->isBusy()) { sel->finish(Selection::INTERNAL); - } - }, + } }, "clear", [](Selection* sel) { if (sel) { bool managed = !sel->isBusy(); - if (managed) sel->start(Selection::INTERNAL); + if (managed){ sel->start(Selection::INTERNAL); +} sel->clear(); - if (managed) sel->finish(Selection::INTERNAL); - } - }, + if (managed){ sel->finish(Selection::INTERNAL); +} + } }, "add", sol::overload([](Selection* sel, Tile* tile) { if (sel && tile) { bool managed = !sel->isBusy(); - if (managed) sel->start(Selection::INTERNAL); + if (managed){ sel->start(Selection::INTERNAL); +} sel->add(tile); - if (managed) sel->finish(Selection::INTERNAL); + if (managed){ sel->finish(Selection::INTERNAL); +} } }, [](Selection* sel, Tile* tile, Item* item) { if (sel && tile && item) { bool managed = !sel->isBusy(); - if (managed) sel->start(Selection::INTERNAL); + if (managed){ sel->start(Selection::INTERNAL); +} sel->add(tile, item); - if (managed) sel->finish(Selection::INTERNAL); + if (managed){ sel->finish(Selection::INTERNAL); +} } }), "remove", sol::overload([](Selection* sel, Tile* tile) { if (sel && tile) { bool managed = !sel->isBusy(); - if (managed) sel->start(Selection::INTERNAL); + if (managed){ sel->start(Selection::INTERNAL); +} sel->remove(tile); - if (managed) sel->finish(Selection::INTERNAL); + if (managed){ sel->finish(Selection::INTERNAL); +} } }, [](Selection* sel, Tile* tile, Item* item) { if (sel && tile && item) { bool managed = !sel->isBusy(); - if (managed) sel->start(Selection::INTERNAL); + if (managed){ sel->start(Selection::INTERNAL); +} sel->remove(tile, item); - if (managed) sel->finish(Selection::INTERNAL); + if (managed){ sel->finish(Selection::INTERNAL); +} } }), // String representation From d9f37e910b67bd2d3037c5419b62a90f62f5c949 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Sat, 24 Jan 2026 03:48:12 +0100 Subject: [PATCH 31/41] ci: fix windows build --- .github/workflows/build-windows.yml | 5 ----- vcpkg.json | 5 ++++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 1acdec731..606e2be96 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -45,11 +45,6 @@ jobs: -DVCPKG_TARGET_TRIPLET=x64-windows-static ` -DCMAKE_BUILD_TYPE=Release - - name: Install Dependencies via vcpkg - shell: pwsh - run: | - $deps = @("wxwidgets", "boost-thread", "boost-system", "libarchive", "freeglut", "zlib", "lua", "cpr", "nlohmann-json") - & "$env:VCPKG_ROOT/vcpkg" install $deps --triplet x64-windows-static - name: Build shell: pwsh diff --git a/vcpkg.json b/vcpkg.json index 958180751..ce14052e9 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -15,7 +15,10 @@ "boost-asio", "lua", "sol2", - "cpr" + "cpr", + "boost-system", + "boost-thread", + "zlib" ], "builtin-baseline": "734f8130ffe2f02cf855a3a42a2958f01b3fb005" } From 6832ecd9829f7433d311c32e35195b188e9ed289 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Sat, 24 Jan 2026 14:43:38 +0100 Subject: [PATCH 32/41] typo: remove on~ in mouse events --- source/map_display.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/map_display.cpp b/source/map_display.cpp index bdcf542b1..66ba45473 100644 --- a/source/map_display.cpp +++ b/source/map_display.cpp @@ -474,7 +474,7 @@ void MapCanvas::OnMouseMove(wxMouseEvent& event) { } } - if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("onMouseMove", mouse_map_x, mouse_map_y, floor)) { + if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("mouseMove", mouse_map_x, mouse_map_y, floor)) { return; } @@ -591,7 +591,7 @@ void MapCanvas::OnMouseMove(wxMouseEvent& event) { void MapCanvas::OnMouseLeftRelease(wxMouseEvent& event) { int map_x, map_y; ScreenToMap(event.GetX(), event.GetY(), &map_x, &map_y); - if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("onMouseRelease", map_x, map_y, floor, std::string("left"))) { + if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("mouseRelease", map_x, map_y, floor, std::string("left"))) { return; } OnMouseActionRelease(event); @@ -600,7 +600,7 @@ void MapCanvas::OnMouseLeftRelease(wxMouseEvent& event) { void MapCanvas::OnMouseLeftClick(wxMouseEvent& event) { int map_x, map_y; ScreenToMap(event.GetX(), event.GetY(), &map_x, &map_y); - if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("onMousePress", map_x, map_y, floor, std::string("left"))) { + if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("mousePress", map_x, map_y, floor, std::string("left"))) { return; } OnMouseActionClick(event); @@ -662,7 +662,7 @@ void MapCanvas::OnMouseCenterRelease(wxMouseEvent& event) { void MapCanvas::OnMouseRightClick(wxMouseEvent& event) { int map_x, map_y; ScreenToMap(event.GetX(), event.GetY(), &map_x, &map_y); - if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("onMousePress", map_x, map_y, floor, std::string("right"))) { + if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("mousePress", map_x, map_y, floor, std::string("right"))) { return; } if (g_settings.getInteger(Config::SWITCH_MOUSEBUTTONS)) { @@ -675,7 +675,7 @@ void MapCanvas::OnMouseRightClick(wxMouseEvent& event) { void MapCanvas::OnMouseRightRelease(wxMouseEvent& event) { int map_x, map_y; ScreenToMap(event.GetX(), event.GetY(), &map_x, &map_y); - if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("onMouseRelease", map_x, map_y, floor, std::string("right"))) { + if (g_luaScripts.isInitialized() && g_luaScripts.emitCancellable("mouseRelease", map_x, map_y, floor, std::string("right"))) { return; } if (g_settings.getInteger(Config::SWITCH_MOUSEBUTTONS)) { From 52a2483d89f4d812fe9e4fe90a91a107d26add7b Mon Sep 17 00:00:00 2001 From: michyaraque Date: Sat, 24 Jan 2026 14:44:01 +0100 Subject: [PATCH 33/41] build: update MSVC flags --- CMakeLists.txt | 47 ++++++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7091a4b51..3940b166a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,11 +14,19 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo) endif() -set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-deprecated-declarations -Wno-overloaded-virtual -Wno-strict-aliasing -Wno-sign-compare -Wno-unused-function -Wunused-result") -set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -D__DEBUG__") -set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") -set(CMAKE_CXX_FLAGS_RELEASE "-O4 -DNDEBUG") -set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") +if(MSVC) + set(CMAKE_CXX_FLAGS "/W3 /D_CRT_SECURE_NO_WARNINGS") + set(CMAKE_CXX_FLAGS_DEBUG "/Od /Zi /D__DEBUG__") + set(CMAKE_CXX_FLAGS_MINSIZEREL "/O1 /DNDEBUG") + set(CMAKE_CXX_FLAGS_RELEASE "/O2 /DNDEBUG") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/O2 /Zi") +else() + set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-deprecated-declarations -Wno-overloaded-virtual -Wno-strict-aliasing -Wno-sign-compare -Wno-unused-function -Wunused-result") + set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -D__DEBUG__") + set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") + set(CMAKE_CXX_FLAGS_RELEASE "-O4 -DNDEBUG") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") +endif() find_package(OpenGL REQUIRED) @@ -39,28 +47,9 @@ find_package(ZLIB REQUIRED) # Lua scripting support find_package(Lua REQUIRED) -include(FetchContent) -FetchContent_Declare( - sol2 - GIT_REPOSITORY https://github.com/ThePhD/sol2.git - GIT_TAG v3.3.0 -) -FetchContent_MakeAvailable(sol2) -set(SOL2_INCLUDE_DIRS ${sol2_SOURCE_DIR}/include) - -FetchContent_Declare( - cpr - GIT_REPOSITORY https://github.com/libcpr/cpr.git - GIT_TAG 1.10.5 -) -FetchContent_MakeAvailable(cpr) - -FetchContent_Declare( - json - GIT_REPOSITORY https://github.com/nlohmann/json.git - GIT_TAG v3.11.3 -) -FetchContent_MakeAvailable(json) +find_package(sol2 CONFIG REQUIRED) +find_package(cpr CONFIG REQUIRED) +find_package(nlohmann_json CONFIG REQUIRED) include(${wxWidgets_USE_FILE}) include(source/CMakeLists.txt) @@ -69,5 +58,5 @@ add_executable(rme ${rme_H} ${rme_SRC}) set_target_properties(rme PROPERTIES CXX_STANDARD 17) set_target_properties(rme PROPERTIES CXX_STANDARD_REQUIRED ON) -include_directories(${CMAKE_SOURCE_DIR}/source ${Boost_INCLUDE_DIRS} ${LibArchive_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR} ${LUA_INCLUDE_DIR} ${SOL2_INCLUDE_DIRS}) -target_link_libraries(rme ${wxWidgets_LIBRARIES} ${Boost_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${ZLIB_LIBRARIES} ${LUA_LIBRARIES} cpr::cpr nlohmann_json::nlohmann_json) +include_directories(${CMAKE_SOURCE_DIR}/source ${Boost_INCLUDE_DIRS} ${LibArchive_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR} ${LUA_INCLUDE_DIR}) +target_link_libraries(rme ${wxWidgets_LIBRARIES} ${Boost_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${ZLIB_LIBRARIES} ${LUA_LIBRARIES} cpr::cpr nlohmann_json::nlohmann_json sol2::sol2) From dc825bc15d5489e5b1915fb045dfc84ece1e97d9 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Sat, 24 Jan 2026 14:47:51 +0100 Subject: [PATCH 34/41] build: fix linux unhandled packages --- CMakeLists.txt | 56 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3940b166a..17a9ccbed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,11 +45,61 @@ find_package(wxWidgets COMPONENTS html aui gl adv core net base REQUIRED) find_package(GLUT REQUIRED) find_package(ZLIB REQUIRED) +# Lua scripting support # Lua scripting support find_package(Lua REQUIRED) -find_package(sol2 CONFIG REQUIRED) -find_package(cpr CONFIG REQUIRED) -find_package(nlohmann_json CONFIG REQUIRED) + +include(FetchContent) + +# sol2 +find_package(sol2 CONFIG QUIET) +if(NOT sol2_FOUND) + message(STATUS "sol2 not found via find_package, using FetchContent...") + FetchContent_Declare( + sol2 + GIT_REPOSITORY https://github.com/ThePhD/sol2.git + GIT_TAG v3.3.0 + ) + FetchContent_MakeAvailable(sol2) + + # Ensure usable target exists + if(NOT TARGET sol2::sol2) + if(TARGET sol2) + add_library(sol2::sol2 ALIAS sol2) + else() + # If no target, setting include directory manually + set(SOL2_INCLUDE_DIRS ${sol2_SOURCE_DIR}/include) + include_directories(${SOL2_INCLUDE_DIRS}) + # Create a dummy interface target to link against if needed to satisfy linker + add_library(sol2::sol2 INTERFACE IMPORTED) + set_target_properties(sol2::sol2 PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${SOL2_INCLUDE_DIRS}") + endif() + endif() +endif() + +# cpr +find_package(cpr CONFIG QUIET) +if(NOT cpr_FOUND) + message(STATUS "cpr not found via find_package, using FetchContent...") + FetchContent_Declare( + cpr + GIT_REPOSITORY https://github.com/libcpr/cpr.git + GIT_TAG 1.10.5 + ) + FetchContent_MakeAvailable(cpr) +endif() + +# nlohmann_json +find_package(nlohmann_json CONFIG QUIET) +if(NOT nlohmann_json_FOUND) + message(STATUS "nlohmann_json not found via find_package, using FetchContent...") + FetchContent_Declare( + json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.11.3 + ) + FetchContent_MakeAvailable(json) +endif() include(${wxWidgets_USE_FILE}) include(source/CMakeLists.txt) From 77e5ac1dd8134ae764be2d3787a5eec2f937b4bc Mon Sep 17 00:00:00 2001 From: michyaraque Date: Sat, 24 Jan 2026 15:29:59 +0100 Subject: [PATCH 35/41] build: solve lua libraries --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 17a9ccbed..9bc85396c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ if(NOT CMAKE_BUILD_TYPE) endif() if(MSVC) - set(CMAKE_CXX_FLAGS "/W3 /D_CRT_SECURE_NO_WARNINGS") + set(CMAKE_CXX_FLAGS "/W3 /EHsc /D_CRT_SECURE_NO_WARNINGS") set(CMAKE_CXX_FLAGS_DEBUG "/Od /Zi /D__DEBUG__") set(CMAKE_CXX_FLAGS_MINSIZEREL "/O1 /DNDEBUG") set(CMAKE_CXX_FLAGS_RELEASE "/O2 /DNDEBUG") @@ -108,5 +108,5 @@ add_executable(rme ${rme_H} ${rme_SRC}) set_target_properties(rme PROPERTIES CXX_STANDARD 17) set_target_properties(rme PROPERTIES CXX_STANDARD_REQUIRED ON) -include_directories(${CMAKE_SOURCE_DIR}/source ${Boost_INCLUDE_DIRS} ${LibArchive_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR} ${LUA_INCLUDE_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/source ${Boost_INCLUDE_DIRS} ${LibArchive_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR} ${LUA_INCLUDE_DIR} ${LUA_INCLUDE_DIRS}) target_link_libraries(rme ${wxWidgets_LIBRARIES} ${Boost_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${ZLIB_LIBRARIES} ${LUA_LIBRARIES} cpr::cpr nlohmann_json::nlohmann_json sol2::sol2) From 4a60d6f34476fb56fb3bc686d9df45f8a979c41a Mon Sep 17 00:00:00 2001 From: michyaraque Date: Sat, 24 Jan 2026 15:32:44 +0100 Subject: [PATCH 36/41] ci: support vcpkg cache --- .github/workflows/build-windows.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 606e2be96..4b0fd330f 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -32,10 +32,21 @@ jobs: - name: Get latest CMake and Ninja uses: lukka/get-cmake@v3.31.6 + - name: Restore vcpkg artifacts + uses: actions/cache@v4 + with: + path: | + ${{ env.LOCALAPPDATA }}\vcpkg\archives + ${{ env.APPDATA }}\vcpkg\archives + key: ${{ runner.os }}-vcpkg-${{ hashFiles('vcpkg.json') }} + restore-keys: | + ${{ runner.os }}-vcpkg- + - name: Setup vcpkg uses: lukka/run-vcpkg@v11 with: vcpkgGitCommitId: ${{ env.LUKKA_RUN_VCPKG_SHA }} + vcpkgJsonGlob: 'vcpkg.json' - name: Configure CMake shell: pwsh From 4b96efe45f6481f8d7fa3fb2b6620749d53c4dd8 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Sat, 24 Jan 2026 16:09:50 +0100 Subject: [PATCH 37/41] build: remove sol2 ambiguity --- CMakeLists.txt | 124 +++++++++++++++++++++++++++++-------------------- 1 file changed, 74 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bc85396c..0682f1f37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,59 +46,83 @@ find_package(GLUT REQUIRED) find_package(ZLIB REQUIRED) # Lua scripting support -# Lua scripting support -find_package(Lua REQUIRED) - -include(FetchContent) - -# sol2 -find_package(sol2 CONFIG QUIET) -if(NOT sol2_FOUND) - message(STATUS "sol2 not found via find_package, using FetchContent...") - FetchContent_Declare( - sol2 - GIT_REPOSITORY https://github.com/ThePhD/sol2.git - GIT_TAG v3.3.0 - ) - FetchContent_MakeAvailable(sol2) - - # Ensure usable target exists - if(NOT TARGET sol2::sol2) - if(TARGET sol2) - add_library(sol2::sol2 ALIAS sol2) - else() - # If no target, setting include directory manually - set(SOL2_INCLUDE_DIRS ${sol2_SOURCE_DIR}/include) - include_directories(${SOL2_INCLUDE_DIRS}) - # Create a dummy interface target to link against if needed to satisfy linker - add_library(sol2::sol2 INTERFACE IMPORTED) - set_target_properties(sol2::sol2 PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${SOL2_INCLUDE_DIRS}") +if(MSVC) + # Windows/vcpkg specific configuration + find_package(lua CONFIG REQUIRED) + find_package(sol2 CONFIG REQUIRED) + find_package(cpr CONFIG REQUIRED) + find_package(nlohmann_json CONFIG REQUIRED) + + # Validate Lua target from vcpkg + if(TARGET lua::lua) + set(LUA_LIB_TARGET lua::lua) + elseif(TARGET lua) + set(LUA_LIB_TARGET lua) + else() + message(FATAL_ERROR "Lua target not found in vcpkg config!") + endif() + + # Explicitly add Lua includes to global path to ensure sol2 finds 'lua.hpp' + get_target_property(VCPKG_LUA_INCLUDES ${LUA_LIB_TARGET} INTERFACE_INCLUDE_DIRECTORIES) + if(VCPKG_LUA_INCLUDES) + include_directories(${VCPKG_LUA_INCLUDES}) + endif() + + set(LIBS_TO_LINK ${LUA_LIB_TARGET} cpr::cpr nlohmann_json::nlohmann_json sol2::sol2) + +else() + # Linux / Standard CMake Module configuration + find_package(Lua REQUIRED) + + include(FetchContent) + + # sol2 + find_package(sol2 CONFIG QUIET) + if(NOT sol2_FOUND) + message(STATUS "sol2 not found via find_package, using FetchContent...") + FetchContent_Declare( + sol2 + GIT_REPOSITORY https://github.com/ThePhD/sol2.git + GIT_TAG v3.3.0 + ) + FetchContent_MakeAvailable(sol2) + if(NOT TARGET sol2::sol2) + if(TARGET sol2) + add_library(sol2::sol2 ALIAS sol2) + else() + set(SOL2_INCLUDE_DIRS ${sol2_SOURCE_DIR}/include) + include_directories(${SOL2_INCLUDE_DIRS}) + add_library(sol2::sol2 INTERFACE IMPORTED) + set_target_properties(sol2::sol2 PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${SOL2_INCLUDE_DIRS}") + endif() endif() endif() -endif() -# cpr -find_package(cpr CONFIG QUIET) -if(NOT cpr_FOUND) - message(STATUS "cpr not found via find_package, using FetchContent...") - FetchContent_Declare( - cpr - GIT_REPOSITORY https://github.com/libcpr/cpr.git - GIT_TAG 1.10.5 - ) - FetchContent_MakeAvailable(cpr) -endif() + # cpr + find_package(cpr CONFIG QUIET) + if(NOT cpr_FOUND) + message(STATUS "cpr not found via find_package, using FetchContent...") + FetchContent_Declare( + cpr + GIT_REPOSITORY https://github.com/libcpr/cpr.git + GIT_TAG 1.10.5 + ) + FetchContent_MakeAvailable(cpr) + endif() + + # nlohmann_json + find_package(nlohmann_json CONFIG QUIET) + if(NOT nlohmann_json_FOUND) + message(STATUS "nlohmann_json not found via find_package, using FetchContent...") + FetchContent_Declare( + json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.11.3 + ) + FetchContent_MakeAvailable(json) + endif() -# nlohmann_json -find_package(nlohmann_json CONFIG QUIET) -if(NOT nlohmann_json_FOUND) - message(STATUS "nlohmann_json not found via find_package, using FetchContent...") - FetchContent_Declare( - json - GIT_REPOSITORY https://github.com/nlohmann/json.git - GIT_TAG v3.11.3 - ) - FetchContent_MakeAvailable(json) + set(LIBS_TO_LINK ${LUA_LIBRARIES} cpr::cpr nlohmann_json::nlohmann_json sol2::sol2) endif() include(${wxWidgets_USE_FILE}) @@ -109,4 +133,4 @@ set_target_properties(rme PROPERTIES CXX_STANDARD 17) set_target_properties(rme PROPERTIES CXX_STANDARD_REQUIRED ON) include_directories(${CMAKE_SOURCE_DIR}/source ${Boost_INCLUDE_DIRS} ${LibArchive_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR} ${LUA_INCLUDE_DIR} ${LUA_INCLUDE_DIRS}) -target_link_libraries(rme ${wxWidgets_LIBRARIES} ${Boost_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${ZLIB_LIBRARIES} ${LUA_LIBRARIES} cpr::cpr nlohmann_json::nlohmann_json sol2::sol2) +target_link_libraries(rme ${wxWidgets_LIBRARIES} ${Boost_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ${ZLIB_LIBRARIES} ${LIBS_TO_LINK}) From 88be3bf866d61debd0ba433d67f19a62e30a1851 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Sat, 24 Jan 2026 16:44:52 +0100 Subject: [PATCH 38/41] ci: include lua.hpp header --- source/lua/lua_engine.h | 1 + 1 file changed, 1 insertion(+) diff --git a/source/lua/lua_engine.h b/source/lua/lua_engine.h index 9503ab658..980f50dbd 100644 --- a/source/lua/lua_engine.h +++ b/source/lua/lua_engine.h @@ -20,6 +20,7 @@ #define SOL_ALL_SAFETIES_ON 1 +#include #include #include From 60d2c2f11704bfd48af9949f3e3226740f3567c4 Mon Sep 17 00:00:00 2001 From: michyaraque Date: Sat, 24 Jan 2026 17:26:52 +0100 Subject: [PATCH 39/41] build: fix lua version link --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0682f1f37..415828eb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,8 @@ if(MSVC) include_directories(${VCPKG_LUA_INCLUDES}) endif() + add_compile_definitions(SOL_USING_CXX_LUA_HPP=1) + set(LIBS_TO_LINK ${LUA_LIB_TARGET} cpr::cpr nlohmann_json::nlohmann_json sol2::sol2) else() From 5a0e2a64e596579ea60120487aab82312402398c Mon Sep 17 00:00:00 2001 From: michyaraque Date: Sat, 24 Jan 2026 17:33:17 +0100 Subject: [PATCH 40/41] ci: improve vcpkg cache --- .github/workflows/build-windows.yml | 38 ++++++++++++++++++----------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 4b0fd330f..991e37f1f 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -1,6 +1,5 @@ name: Build - Windows -# Trigger on manual dispatch, and push/PR to master on: workflow_dispatch: push: @@ -19,8 +18,9 @@ jobs: runs-on: windows-2022 env: - CMAKE_BUILD_PARALLEL_LEVEL: 2 - LUKKA_RUN_VCPKG_SHA: '734f8130ffe2f02cf855a3a42a2958f01b3fb005' + CMAKE_BUILD_PARALLEL_LEVEL: 4 + VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}\vcpkg_cache + VCPKG_COMMIT: '734f8130ffe2f02cf855a3a42a2958f01b3fb005' steps: - name: Checkout repository @@ -32,20 +32,32 @@ jobs: - name: Get latest CMake and Ninja uses: lukka/get-cmake@v3.31.6 - - name: Restore vcpkg artifacts + - name: Create vcpkg cache directory + run: mkdir -p "${{ env.VCPKG_DEFAULT_BINARY_CACHE }}" + shell: bash + + - name: Restore vcpkg binary cache + id: vcpkg-cache uses: actions/cache@v4 with: - path: | - ${{ env.LOCALAPPDATA }}\vcpkg\archives - ${{ env.APPDATA }}\vcpkg\archives - key: ${{ runner.os }}-vcpkg-${{ hashFiles('vcpkg.json') }} + path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} + key: vcpkg-bin-${{ runner.os }}-${{ env.VCPKG_COMMIT }}-${{ hashFiles('vcpkg.json') }} restore-keys: | - ${{ runner.os }}-vcpkg- + vcpkg-bin-${{ runner.os }}-${{ env.VCPKG_COMMIT }}- + + - name: Restore vcpkg installed packages + id: vcpkg-installed + uses: actions/cache@v4 + with: + path: build/vcpkg_installed + key: vcpkg-installed-${{ runner.os }}-${{ env.VCPKG_COMMIT }}-${{ hashFiles('vcpkg.json') }} + restore-keys: | + vcpkg-installed-${{ runner.os }}-${{ env.VCPKG_COMMIT }}- - name: Setup vcpkg uses: lukka/run-vcpkg@v11 with: - vcpkgGitCommitId: ${{ env.LUKKA_RUN_VCPKG_SHA }} + vcpkgGitCommitId: ${{ env.VCPKG_COMMIT }} vcpkgJsonGlob: 'vcpkg.json' - name: Configure CMake @@ -54,13 +66,12 @@ jobs: cmake -S . -B build -G "Visual Studio 17 2022" -A x64 ` -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" ` -DVCPKG_TARGET_TRIPLET=x64-windows-static ` + -DVCPKG_INSTALL_OPTIONS="--clean-after-build" ` -DCMAKE_BUILD_TYPE=Release - - name: Build shell: pwsh - run: | - cmake --build build --config Release + run: cmake --build build --config Release --parallel - name: Prepare Artifacts shell: pwsh @@ -68,7 +79,6 @@ jobs: $artifactDir = "$env:GITHUB_WORKSPACE\artifacts" New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null - # Find the executable $exePath = Get-ChildItem -Recurse -Path "$env:GITHUB_WORKSPACE\build" -Filter "rme.exe" | Select-Object -First 1 if (-not $exePath) { From e102d16e701d445671c684e4b78f613c1c22741e Mon Sep 17 00:00:00 2001 From: michyaraque Date: Sat, 24 Jan 2026 18:44:49 +0100 Subject: [PATCH 41/41] ci: drop windows CI for now --- .github/workflows/build-windows.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 991e37f1f..f40add636 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -2,12 +2,6 @@ name: Build - Windows on: workflow_dispatch: - push: - branches: - - master - pull_request: - branches: - - master permissions: contents: read