mirror of
https://github.com/opentibiabr/remeres-map-editor
synced 2026-08-15 18:26:04 -04:00
This commit adds the full Lua scripting API and integrates the Script Manager into the application. It builds on top of the scripting engine introduced in #158, exposing all the API modules that Lua scripts can use and making everything accessible from the UI. Changes: - Added all Lua API modules: map, tile, item, brush, image, position, selection, creature, color, app, dialog, noise, algo, geo, json, http. - Added map overlay system for scripts to draw on the map view. - Added event system, context menu registration, persistent storage, and transaction support with undo/redo. - Added support for package scripts (directory with manifest.lua) and script metadata tags. - Added Scripts menu to the menubar with per-script execution and reload. - Connected the Script Manager window to the UI. - Added missing build dependencies and fixed MSVC compilation errors. Notes: - Build dependencies were added for HTTP and noise generation support. - Some code was adjusted to compile correctly on MSVC.
325 lines
9.7 KiB
C++
325 lines
9.7 KiB
C++
//////////////////////////////////////////////////////////////////////
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#include "main.h"
|
|
#include "lua_api_image.h"
|
|
#include "../gui.h"
|
|
#include "../graphics.h"
|
|
#include "../items.h"
|
|
#include <filesystem>
|
|
#include <print>
|
|
#include "lua_script_manager.h"
|
|
|
|
namespace LuaAPI {
|
|
|
|
LuaImage::LuaImage() = default;
|
|
|
|
LuaImage::LuaImage(const std::string &path) :
|
|
filePath(path) {
|
|
|
|
if (path.empty()) {
|
|
return;
|
|
}
|
|
|
|
// Security Check
|
|
if (path.contains("..")) {
|
|
std::print("[Lua Security] Blocked image path with traversal: {}\n", path);
|
|
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) {
|
|
std::print("[Lua Security] Blocked absolute image path outside allowed directories: {}\n", path);
|
|
return;
|
|
}
|
|
}
|
|
} catch (const std::filesystem::filesystem_error &) {
|
|
return;
|
|
}
|
|
|
|
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[id].id != 0) {
|
|
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::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() = default;
|
|
|
|
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);
|
|
}
|
|
|
|
static void copySpriteTilePixels(const uint8_t* rgbaData, unsigned char* imgData, unsigned char* alphaData, int destX, int destY, int spriteWidth) {
|
|
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);
|
|
uint8_t alpha = rgbaData[srcIdx + 3];
|
|
if (alpha > 0) {
|
|
imgData[destIdx * 3 + 0] = rgbaData[srcIdx + 0];
|
|
imgData[destIdx * 3 + 1] = rgbaData[srcIdx + 1];
|
|
imgData[destIdx * 3 + 2] = rgbaData[srcIdx + 2];
|
|
alphaData[destIdx] = alpha;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper to reliably get sprite ID from item ID, handling client ID mapping
|
|
int getItemSpriteId(int itemId) {
|
|
if (g_items[itemId].id == 0) {
|
|
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;
|
|
}
|
|
|
|
auto* gameSprite = dynamic_cast<GameSprite*>(sprite);
|
|
if (gameSprite && gameSprite->width > 0 && gameSprite->height > 0) {
|
|
// 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();
|
|
return;
|
|
}
|
|
|
|
int spriteWidth = gameSprite->width * 32;
|
|
int spriteHeight = gameSprite->height * 32;
|
|
|
|
image.Create(spriteWidth, spriteHeight);
|
|
image.InitAlpha();
|
|
|
|
unsigned char* imgData = image.GetData();
|
|
unsigned char* alphaData = image.GetAlpha();
|
|
memset(imgData, 0, spriteWidth * spriteHeight * 3);
|
|
memset(alphaData, 0, spriteWidth * spriteHeight);
|
|
|
|
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()) {
|
|
continue;
|
|
}
|
|
auto* normalImage = gameSprite->spriteList[spriteIndex];
|
|
if (!normalImage) {
|
|
continue;
|
|
}
|
|
uint8_t* rgbaData = normalImage->getRGBAData();
|
|
if (!rgbaData) {
|
|
continue;
|
|
}
|
|
int destX = (gameSprite->width - 1 - x) * 32;
|
|
int destY = (gameSprite->height - 1 - y) * 32;
|
|
copySpriteTilePixels(rgbaData, imgData, alphaData, destX, destY, spriteWidth);
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
auto newWidth = static_cast<int>(image.GetWidth() * factor);
|
|
auto newHeight = static_cast<int>(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<LuaImage>(
|
|
"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<std::string>("path"));
|
|
}
|
|
if (t["itemid"].valid()) {
|
|
return LuaImage::loadFromItemSprite(t.get<int>("itemid"));
|
|
}
|
|
if (t["spriteid"].valid()) {
|
|
return LuaImage::loadFromSprite(t.get<int>("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<bool> smooth) { return img.resize(w, h, smooth.value_or(true)); },
|
|
"scale", [](const LuaImage &img, double factor, sol::optional<bool> 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
|