diff --git a/docs/changelog.txt b/docs/changelog.txt index 924adbd31..f6524dc58 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,6 +41,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - Terminal console no longer appears in front of the game window on startup +- `gui/design`: Improved performance for drawing shapes ## Documentation diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 85d759802..1967c370a 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -78,6 +78,7 @@ dfhack_plugin(add-spatter add-spatter.cpp) dfhack_plugin(autobutcher autobutcher.cpp LINK_LIBRARIES lua) dfhack_plugin(autochop autochop.cpp LINK_LIBRARIES lua) dfhack_plugin(autoclothing autoclothing.cpp LINK_LIBRARIES lua) +dfhack_plugin(design design.cpp LINK_LIBRARIES lua) dfhack_plugin(autodump autodump.cpp) dfhack_plugin(autofarm autofarm.cpp) #dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_static) diff --git a/plugins/design.cpp b/plugins/design.cpp new file mode 100644 index 000000000..37ee32cbd --- /dev/null +++ b/plugins/design.cpp @@ -0,0 +1,270 @@ +#include +#include +#include +#include +#include +#include + +#include "ColorText.h" +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" +#include "df/graphic_viewportst.h" +#include "df/world.h" +#include "modules/Gui.h" +#include "modules/Persistence.h" +#include "modules/Screen.h" +#include "modules/World.h" + +DFHACK_PLUGIN("design"); +using DFHack::color_value; + +REQUIRE_GLOBAL(window_x); +REQUIRE_GLOBAL(window_y); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(plotinfo); +using namespace DFHack; +using namespace df::enums; + +namespace DFHack { +DBG_DECLARE(design, log, DebugCategory::LINFO); +} + +DFhackCExport command_result plugin_init(color_ostream &out, + std::vector &commands) { + return CR_OK; +} + +std::map PENS; + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (event == DFHack::SC_WORLD_UNLOADED) { + DEBUG(log,out).print("clearing PENS\n"); + PENS.clear(); + } + return CR_OK; +} + +struct DrawingPoint { + uint32_t penKey = 0; + std::pair cursor_coords; + + DrawingPoint() : penKey(0), cursor_coords({-1, -1}) {} +}; + +typedef std::map> ShapeMap; +ShapeMap arr; + +bool has_point(int x, int y) { + return arr.count(x) != 0 && arr.at(x).count(y) != 0; +}; + +// Key tuple is N, W, E, S +typedef std::tuple DirectionKey; +std::map> CURSORS_MAP = { + {{false, false, false, false}, {1, 2}}, // INSIDE + {{true, true, false, false}, {0, 1}}, // NW + {{true, false, false, false}, {1, 1}}, // NORTH + {{true, false, true, false}, {2, 1}}, // NE + {{false, true, false, false}, {0, 2}}, // WEST + {{false, false, true, false}, {2, 2}}, // EAST + {{false, true, false, true}, {0, 3}}, // SW + {{false, false, false, true}, {1, 3}}, // SOUTH + {{false, false, true, true}, {2, 3}}, // SE + {{true, true, true, false}, {3, 2}}, // N_NUB + {{true, false, true, true}, {5, 1}}, // E_NUB + {{true, true, false, true}, {3, 1}}, // W_NUB + {{false, true, true, true}, {4, 2}}, // S_NUB + {{false, true, true, false}, {3, 3}}, // VERT_NS + {{true, false, false, true}, {4, 1}}, // VERT_EW + {{true, true, true, true}, {4, 3}}, // POINT +}; + +enum PenMask { + North = 0, + South, + East, + West, + DragPoint, + MouseOver, + InShape, + ExtraPoint, + NumFlags +}; + +uint32_t gen_pen_key(bool n, bool s, bool e, bool w, bool is_drag_point, + bool is_mouse_over, bool inshape, bool extra_point) { + std::bitset(PenMask::NumFlags)> ret; + ret[PenMask::North] = n; + ret[PenMask::South] = s; + ret[PenMask::East] = e; + ret[PenMask::West] = w; + ret[PenMask::DragPoint] = is_drag_point; + ret[PenMask::MouseOver] = is_mouse_over; + ret[PenMask::InShape] = inshape; + ret[PenMask::ExtraPoint] = extra_point; + + return ret.to_ulong(); +} + +Screen::Pen make_pen(const std::pair &direction, bool is_drag_point, + bool is_mouse_over, bool inshape, bool extra_point) { + color_value color = COLOR_GREEN; + int ycursor_mod = 0; + + if (!extra_point) { + if (is_drag_point) { + color = COLOR_CYAN; + ycursor_mod += 6; + if (is_mouse_over) { + color = COLOR_MAGENTA; + ycursor_mod += 3; + } + } + } else { + ycursor_mod += 15; + color = COLOR_LIGHTRED; + + if (is_mouse_over) { + color = COLOR_RED; + ycursor_mod += 3; + } + } + + Screen::Pen pen; + pen.ch = inshape ? 'X' : 'o'; + pen.fg = color; + int selected_tile_texpos = 0; + Screen::findGraphicsTile("CURSORS", direction.first, + direction.second + ycursor_mod, + &selected_tile_texpos); + pen.tile = selected_tile_texpos; + + return pen; +} + +Screen::Pen get_pen(int x, int y, ShapeMap &arr, const std::string &type = "") { + bool n = false, w = false, e = false, s = false; + if (has_point(x, y)) { + if (y == 0 || !has_point(x, y - 1)) n = true; + if (x == 0 || !has_point(x - 1, y)) w = true; + if (!has_point(x + 1, y)) e = true; // TODO check map size + if (!has_point(x, y + 1)) s = true; // TODO check map size + } + + bool is_drag_point = type == "drag_point"; + bool is_extra = type == "extra_point"; + bool is_in_shape = has_point(x, y); + auto mouse_pos = Gui::getMousePos(); + bool mouse_over = mouse_pos.x == x && mouse_pos.y == y; + + uint32_t pen_key = gen_pen_key(n, s, e, w, is_drag_point, mouse_over, + is_in_shape, is_extra); + + if (CURSORS_MAP.count({n, w, e, s}) > 0 && has_point(x, y)) { + arr[x][y].cursor_coords = CURSORS_MAP.at({n, w, e, s}); + } + + if (PENS.find(pen_key) == PENS.end()) { + std::pair cursor{-1, -1}; + + if (type != "") { + return make_pen(CURSORS_MAP.at({n, w, e, s}), is_drag_point, + mouse_over, is_in_shape, is_extra); + } + + if (CURSORS_MAP.count({n, w, e, s}) > 0) { + PENS.emplace(pen_key, + make_pen(CURSORS_MAP.at({n, w, e, s}), is_drag_point, + mouse_over, is_in_shape, is_extra)); + if (type == "" && has_point(x, y)) { + arr[x][y].penKey = pen_key; + } + } + } + + return PENS.at(pen_key); +} + +static int design_load_shape(lua_State *L) { + if (lua_istable(L, -1)) { + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + int x = lua_tointeger(L, -2); + + if (lua_istable(L, -1)) { + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + int y = lua_tointeger(L, -2); + bool value = lua_toboolean(L, -1); + + if (value) { + arr[x][y] = DrawingPoint(); + } + lua_pop(L, 1); + } + } + lua_pop(L, 1); + } + } + + return 0; +} + +static int design_clear_shape(lua_State *L) { + arr.clear(); + + return 0; +} + +static int design_draw_shape(lua_State *L) { + if (arr.size() == 0) { + design_load_shape(L); + } + + for (auto x : arr) { + for (auto y : x.second) { + Screen::Pen cur_tile = Screen::readTile(x.first, y.first, true); + Screen::Pen pen = get_pen(x.first, y.first, arr); + cur_tile.tile = pen.tile; + Screen::paintTile(cur_tile, x.first - *window_x, + y.first - *window_y, true); + } + } + + return 0; +} + +static int design_draw_points(lua_State *L) { + if (lua_istable(L, -1)) { + const char *str; + lua_rawgeti(L, -1, 2); + str = lua_tostring(L, -1); + lua_pop(L, 1); + + lua_rawgeti(L, -1, 1); + int n = luaL_len(L, -1); + for (int i = 1; i <= n; i++) { + lua_rawgeti(L, -1, i); + int x, y; + lua_getfield(L, -1, "y"); + y = lua_tointeger(L, -1); + lua_getfield(L, -2, "x"); + x = lua_tointeger(L, -1); + lua_pop(L, 3); + + Screen::Pen cur_tile = Screen::readTile(x, y, true); + Screen::Pen pen = get_pen(x, y, arr, str); + cur_tile.tile = pen.tile; + Screen::paintTile(cur_tile, x - *window_x, y - *window_y, true); + } + lua_pop(L, 1); + } + + return 0; +} + +DFHACK_PLUGIN_LUA_COMMANDS{DFHACK_LUA_COMMAND(design_draw_shape), + DFHACK_LUA_COMMAND(design_draw_points), + DFHACK_LUA_COMMAND(design_clear_shape), + DFHACK_LUA_END}; diff --git a/plugins/lua/design.lua b/plugins/lua/design.lua new file mode 100644 index 000000000..ec3b4c79b --- /dev/null +++ b/plugins/lua/design.lua @@ -0,0 +1,3 @@ +local _ENV = mkmodule('plugins.design') + +return _ENV