diff --git a/library/Core.cpp b/library/Core.cpp index ec64e6231..fd5dc7f65 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2217,9 +2217,6 @@ void Core::onStateChange(color_ostream &out, state_change_event event) } } break; - case SC_VIEWSCREEN_CHANGED: - Textures::init(out); - break; default: break; } diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index f470e69c0..a1821d2fb 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -161,6 +161,26 @@ static bool get_int_field(lua_State *L, T *pf, int idx, const char *name, int de return !nil; } +template +static bool get_int_or_closure_field(lua_State *L, T *pf, int idx, const char *name, int defval) +{ + lua_getfield(L, idx, name); + bool nil = lua_isnil(L, -1); + if (nil) { + *pf = T(defval); + } else if (lua_isnumber(L, -1)) { + *pf = T(lua_tointeger(L, -1)); + } else if (lua_isfunction(L, -1)) { + lua_call(L, 0, 1); + *pf = T(lua_tointeger(L, -1)); + lua_pop(L, 1); + } else { + luaL_error(L, "Field %s is not a number or closure function.", name); + } + lua_pop(L, 1); + return !nil; +} + static bool get_char_field(lua_State *L, char *pf, int idx, const char *name, char defval) { lua_getfield(L, idx, name); @@ -207,7 +227,7 @@ static void decode_pen(lua_State *L, Pen &pen, int idx) else pen.bold = lua_toboolean(L, -1); lua_pop(L, 1); - get_int_field(L, &pen.tile, idx, "tile", 0); + get_int_or_closure_field(L, &pen.tile, idx, "tile", 0); bool tcolor = get_int_field(L, &pen.tile_fg, idx, "tile_fg", 7); tcolor = get_int_field(L, &pen.tile_bg, idx, "tile_bg", 0) || tcolor; @@ -1377,6 +1397,13 @@ static void OpenModule(lua_State *state, const char *mname, lua_pop(state, 1); } +static void OpenModule(lua_State *state, const char *mname, const luaL_Reg *reg2) +{ + luaL_getsubtable(state, lua_gettop(state), mname); + luaL_setfuncs(state, reg2, 0); + lua_pop(state, 1); +} + #define WRAPM(module, function) { #function, df::wrap_function(module::function,true) } #define WRAP(function) { #function, df::wrap_function(function,true) } #define WRAPN(name, function) { #name, df::wrap_function(function,true) } @@ -1726,19 +1753,31 @@ static const luaL_Reg dfhack_job_funcs[] = { /***** Textures module *****/ -static const LuaWrapper::FunctionReg dfhack_textures_module[] = { - WRAPM(Textures, getDfhackLogoTexposStart), - WRAPM(Textures, getGreenPinTexposStart), - WRAPM(Textures, getRedPinTexposStart), - WRAPM(Textures, getIconsTexposStart), - WRAPM(Textures, getOnOffTexposStart), - WRAPM(Textures, getMapUnsuspendTexposStart), - WRAPM(Textures, getControlPanelTexposStart), - WRAPM(Textures, getThinBordersTexposStart), - WRAPM(Textures, getMediumBordersTexposStart), - WRAPM(Textures, getBoldBordersTexposStart), - WRAPM(Textures, getPanelBordersTexposStart), - WRAPM(Textures, getWindowBordersTexposStart), +static int textures_loadTileset(lua_State *state) +{ + std::string file = luaL_checkstring(state, 1); + auto tile_w = luaL_checkint(state, 2); + auto tile_h = luaL_checkint(state, 3); + auto handles = Textures::loadTileset(file, tile_w, tile_h); + Lua::PushVector(state, handles); + return 1; +} + +static int textures_getTexposByHandle(lua_State *state) +{ + auto handle = luaL_checkunsigned(state, 1); + auto texpos = Textures::getTexposByHandle(handle); + if (texpos == -1) { + lua_pushnil(state); + } else { + Lua::Push(state, texpos); + } + return 1; +} + +static const luaL_Reg dfhack_textures_funcs[] = { + { "loadTileset", textures_loadTileset }, + { "getTexposByHandle", textures_getTexposByHandle }, { NULL, NULL } }; @@ -3713,7 +3752,7 @@ void OpenDFHackApi(lua_State *state) luaL_setfuncs(state, dfhack_funcs, 0); OpenModule(state, "gui", dfhack_gui_module, dfhack_gui_funcs); OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); - OpenModule(state, "textures", dfhack_textures_module); + OpenModule(state, "textures", dfhack_textures_funcs); OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs); OpenModule(state, "military", dfhack_military_module); OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs); diff --git a/library/include/modules/Textures.h b/library/include/modules/Textures.h index 3b9d32541..fa1f743be 100644 --- a/library/include/modules/Textures.h +++ b/library/include/modules/Textures.h @@ -1,7 +1,14 @@ #pragma once -#include "Export.h" +#include +#include + #include "ColorText.h" +#include "Export.h" + +struct SDL_Surface; + +typedef uintptr_t TexposHandle; namespace DFHack { @@ -12,63 +19,41 @@ namespace DFHack { */ namespace Textures { -/** - * Call this on DFHack init and on every viewscreen change so we can reload - * and reindex textures as needed. - */ -void init(DFHack::color_ostream &out); - -/** - * Call this when DFHack is being unloaded. - * - */ -void cleanup(); +const uint32_t TILE_WIDTH_PX = 8; +const uint32_t TILE_HEIGHT_PX = 12; /** - * Get first texpos for the DFHack logo. This texpos and the next 11 make up the - * 4x3 grid of logo textures that can be displayed on the UI layer. + * Load texture and get handle. + * Keep it to obtain valid texpos. */ -DFHACK_EXPORT long getDfhackLogoTexposStart(); +DFHACK_EXPORT TexposHandle loadTexture(SDL_Surface* surface); /** - * Get the first texpos for the UI pin tiles. Each are 2x2 grids. + * Load tileset from image file. + * Return vector of handles to obtain valid texposes. */ -DFHACK_EXPORT long getGreenPinTexposStart(); -DFHACK_EXPORT long getRedPinTexposStart(); +DFHACK_EXPORT std::vector loadTileset(const std::string& file, + int tile_px_w = TILE_WIDTH_PX, + int tile_px_h = TILE_HEIGHT_PX); /** - * Get the first texpos for the DFHack icons. It's a 5x2 grid. + * Get texpos by handle. + * Always use this function, if you need to get valid texpos for your texture. + * Texpos can change on game textures reset, but handle will be the same. */ -DFHACK_EXPORT long getIconsTexposStart(); +DFHACK_EXPORT long getTexposByHandle(TexposHandle handle); /** - * Get the first texpos for the on and off icons. It's a 2x1 grid. + * Call this on DFHack init just once to setup interposed handlers and + * init static assets. */ -DFHACK_EXPORT long getOnOffTexposStart(); +void init(DFHack::color_ostream& out); /** - * Get the first texpos for the pathable 32x32 sprites. It's a 2x1 grid. - */ -DFHACK_EXPORT long getMapPathableTexposStart(); - -/** - * Get the first texpos for the unsuspend 32x32 sprites. It's a 5x1 grid. - */ -DFHACK_EXPORT long getMapUnsuspendTexposStart(); - -/** - * Get the first texpos for the control panel icons. 10x2 grid. - */ -DFHACK_EXPORT long getControlPanelTexposStart(); - -/** - * Get the first texpos for the DFHack borders. Each is a 7x3 grid. + * Call this when DFHack is being unloaded. + * */ -DFHACK_EXPORT long getThinBordersTexposStart(); -DFHACK_EXPORT long getMediumBordersTexposStart(); -DFHACK_EXPORT long getBoldBordersTexposStart(); -DFHACK_EXPORT long getPanelBordersTexposStart(); -DFHACK_EXPORT long getWindowBordersTexposStart(); +void cleanup(); -} -} +} // namespace Textures +} // namespace DFHack diff --git a/library/lua/gui.lua b/library/lua/gui.lua index ca3b212f0..ac25381ca 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -2,6 +2,7 @@ local _ENV = mkmodule('gui') +local textures = require('gui.textures') local utils = require('utils') local dscreen = dfhack.screen @@ -912,33 +913,46 @@ local BASE_FRAME = { paused_pen = to_pen{fg=COLOR_RED, bg=COLOR_BLACK}, } -local function make_frame(name, double_line) - local texpos = dfhack.textures['get'..name..'BordersTexposStart']() - local tp = function(offset) - if texpos == -1 then return nil end - return texpos + offset - end +local function make_frame(tp, double_line) local frame = copyall(BASE_FRAME) - frame.t_frame_pen = to_pen{ tile=tp(1), ch=double_line and 205 or 196, fg=COLOR_GREY, bg=COLOR_BLACK } - frame.l_frame_pen = to_pen{ tile=tp(7), ch=double_line and 186 or 179, fg=COLOR_GREY, bg=COLOR_BLACK } - frame.b_frame_pen = to_pen{ tile=tp(15), ch=double_line and 205 or 196, fg=COLOR_GREY, bg=COLOR_BLACK } - frame.r_frame_pen = to_pen{ tile=tp(9), ch=double_line and 186 or 179, fg=COLOR_GREY, bg=COLOR_BLACK } - frame.lt_frame_pen = to_pen{ tile=tp(0), ch=double_line and 201 or 218, fg=COLOR_GREY, bg=COLOR_BLACK } - frame.lb_frame_pen = to_pen{ tile=tp(14), ch=double_line and 200 or 192, fg=COLOR_GREY, bg=COLOR_BLACK } - frame.rt_frame_pen = to_pen{ tile=tp(2), ch=double_line and 187 or 191, fg=COLOR_GREY, bg=COLOR_BLACK } - frame.rb_frame_pen = to_pen{ tile=tp(16), ch=double_line and 188 or 217, fg=COLOR_GREY, bg=COLOR_BLACK } + frame.t_frame_pen = to_pen{ tile=curry(tp, 2), ch=double_line and 205 or 196, fg=COLOR_GREY, bg=COLOR_BLACK } + frame.l_frame_pen = to_pen{ tile=curry(tp, 8), ch=double_line and 186 or 179, fg=COLOR_GREY, bg=COLOR_BLACK } + frame.b_frame_pen = to_pen{ tile=curry(tp, 16), ch=double_line and 205 or 196, fg=COLOR_GREY, bg=COLOR_BLACK } + frame.r_frame_pen = to_pen{ tile=curry(tp, 10), ch=double_line and 186 or 179, fg=COLOR_GREY, bg=COLOR_BLACK } + frame.lt_frame_pen = to_pen{ tile=curry(tp, 1), ch=double_line and 201 or 218, fg=COLOR_GREY, bg=COLOR_BLACK } + frame.lb_frame_pen = to_pen{ tile=curry(tp, 15), ch=double_line and 200 or 192, fg=COLOR_GREY, bg=COLOR_BLACK } + frame.rt_frame_pen = to_pen{ tile=curry(tp, 3), ch=double_line and 187 or 191, fg=COLOR_GREY, bg=COLOR_BLACK } + frame.rb_frame_pen = to_pen{ tile=curry(tp, 17), ch=double_line and 188 or 217, fg=COLOR_GREY, bg=COLOR_BLACK } return frame end -FRAME_WINDOW = make_frame('Window', true) -FRAME_PANEL = make_frame('Panel', false) -FRAME_MEDIUM = make_frame('Medium', false) -FRAME_BOLD = make_frame('Bold', true) -FRAME_INTERIOR = make_frame('Thin', false) -FRAME_INTERIOR.signature_pen = false -FRAME_INTERIOR_MEDIUM = copyall(FRAME_MEDIUM) -FRAME_INTERIOR_MEDIUM.signature_pen = false +function FRAME_WINDOW(resizable) + local frame = make_frame(textures.tp_border_window, true) + if not resizable then + frame.rb_frame_pen = to_pen{ tile=curry(textures.tp_border_panel, 17), ch=double_line and 188 or 217, fg=COLOR_GREY, bg=COLOR_BLACK } + end + return frame +end +function FRAME_PANEL(resizable) + return make_frame(textures.tp_border_panel, false) +end +function FRAME_MEDIUM(resizable) + return make_frame(textures.tp_border_medium, false) +end +function FRAME_BOLD(resizable) + return make_frame(textures.tp_border_bold, true) +end +function FRAME_INTERIOR(resizable) + local frame = make_frame(textures.tp_border_thin, false) + frame.signature_pen = false + return frame +end +function FRAME_INTERIOR_MEDIUM(resizable) + local frame = make_frame(textures.tp_border_medium, false) + frame.signature_pen = false + return frame +end -- for compatibility with pre-steam code GREY_LINE_FRAME = FRAME_PANEL @@ -951,18 +965,16 @@ BOLD_FRAME = FRAME_BOLD INTERIOR_FRAME = FRAME_INTERIOR INTERIOR_MEDIUM_FRAME = FRAME_INTERIOR_MEDIUM - -function paint_frame(dc,rect,style,title,inactive,pause_forced,resizable) +function paint_frame(dc, rect, style, title, inactive, pause_forced, resizable) + if type(style) == 'function' then + style = style(resizable) + end local pen = style.frame_pen local x1,y1,x2,y2 = dc.x1+rect.x1, dc.y1+rect.y1, dc.x1+rect.x2, dc.y1+rect.y2 dscreen.paintTile(style.lt_frame_pen or pen, x1, y1) dscreen.paintTile(style.rt_frame_pen or pen, x2, y1) dscreen.paintTile(style.lb_frame_pen or pen, x1, y2) - local rb_frame_pen = style.rb_frame_pen - if rb_frame_pen == FRAME_WINDOW.rb_frame_pen and not resizable then - rb_frame_pen = FRAME_PANEL.rb_frame_pen - end - dscreen.paintTile(rb_frame_pen or pen, x2, y2) + dscreen.paintTile(style.rb_frame_pen or pen, x2, y2) dscreen.fillRect(style.t_frame_pen or style.h_frame_pen or pen,x1+1,y1,x2-1,y1) dscreen.fillRect(style.b_frame_pen or style.h_frame_pen or pen,x1+1,y2,x2-1,y2) dscreen.fillRect(style.l_frame_pen or style.v_frame_pen or pen,x1,y1+1,x1,y2-1) diff --git a/library/lua/gui/textures.lua b/library/lua/gui/textures.lua new file mode 100644 index 000000000..6557b6e02 --- /dev/null +++ b/library/lua/gui/textures.lua @@ -0,0 +1,93 @@ +-- DFHack textures + +local _ENV = mkmodule('gui.textures') + +---@alias TexposHandle integer + +-- Preloaded DFHack Assets. +-- Use this handles if you need to get dfhack standard textures. +---@type table +local texpos_handles = { + green_pin = dfhack.textures.loadTileset('hack/data/art/green-pin.png', 8, 12), + red_pin = dfhack.textures.loadTileset('hack/data/art/red-pin.png', 8, 12), + icons = dfhack.textures.loadTileset('hack/data/art/icons.png', 8, 12), + on_off = dfhack.textures.loadTileset('hack/data/art/on-off.png', 8, 12), + control_panel = dfhack.textures.loadTileset('hack/data/art/control-panel.png', 8, 12), + border_thin = dfhack.textures.loadTileset('hack/data/art/border-thin.png', 8, 12), + border_medium = dfhack.textures.loadTileset('hack/data/art/border-medium.png', 8, 12), + border_bold = dfhack.textures.loadTileset('hack/data/art/border-bold.png', 8, 12), + border_panel = dfhack.textures.loadTileset('hack/data/art/border-panel.png', 8, 12), + border_window = dfhack.textures.loadTileset('hack/data/art/border-window.png', 8, 12), +} + +-- Get valid texpos for preloaded texture in tileset +---@param offset integer +---@return integer +function tp_green_pin(offset) + return dfhack.textures.getTexposByHandle(texpos_handles.green_pin[offset]) +end + +-- Get valid texpos for preloaded texture in tileset +---@param offset integer +---@return integer +function tp_red_pin(offset) + return dfhack.textures.getTexposByHandle(texpos_handles.red_pin[offset]) +end + +-- Get valid texpos for preloaded texture in tileset +---@param offset integer +---@return integer +function tp_icons(offset) + return dfhack.textures.getTexposByHandle(texpos_handles.icons[offset]) +end + +-- Get valid texpos for preloaded texture in tileset +---@param offset integer +---@return integer +function tp_on_off(offset) + return dfhack.textures.getTexposByHandle(texpos_handles.on_off[offset]) +end + +-- Get valid texpos for preloaded texture in tileset +---@param offset integer +---@return integer +function tp_control_panel(offset) + return dfhack.textures.getTexposByHandle(texpos_handles.control_panel[offset]) +end + +-- Get valid texpos for preloaded texture in tileset +---@param offset integer +---@return integer +function tp_border_thin(offset) + return dfhack.textures.getTexposByHandle(texpos_handles.border_thin[offset]) +end + +-- Get valid texpos for preloaded texture in tileset +---@param offset integer +---@return integer +function tp_border_medium(offset) + return dfhack.textures.getTexposByHandle(texpos_handles.border_medium[offset]) +end + +-- Get valid texpos for preloaded texture in tileset +---@param offset integer +---@return TexposHandle +function tp_border_bold(offset) + return dfhack.textures.getTexposByHandle(texpos_handles.border_bold[offset]) +end + +-- Get valid texpos for preloaded texture in tileset +---@param offset integer +---@return integer +function tp_border_panel(offset) + return dfhack.textures.getTexposByHandle(texpos_handles.border_panel[offset]) +end + +-- Get valid texpos for preloaded texture in tileset +---@param offset integer +---@return integer +function tp_border_window(offset) + return dfhack.textures.getTexposByHandle(texpos_handles.border_window[offset]) +end + +return _ENV diff --git a/library/modules/Textures.cpp b/library/modules/Textures.cpp index 9976a3c4f..48bc71e3c 100644 --- a/library/modules/Textures.cpp +++ b/library/modules/Textures.cpp @@ -1,3 +1,6 @@ +#include +#include + #include "Internal.h" #include "modules/DFSDL.h" @@ -5,8 +8,13 @@ #include "Debug.h" #include "PluginManager.h" +#include "VTableInterpose.h" #include "df/enabler.h" +#include "df/viewscreen_adopt_regionst.h" +#include "df/viewscreen_loadgamest.h" +#include "df/viewscreen_new_arenast.h" +#include "df/viewscreen_new_regionst.h" #include @@ -15,24 +23,12 @@ using namespace DFHack; using namespace DFHack::DFSDL; namespace DFHack { - DBG_DECLARE(core, textures, DebugCategory::LINFO); +DBG_DECLARE(core, textures, DebugCategory::LINFO); } -static bool g_loaded = false; -static long g_num_dfhack_textures = 0; -static long g_dfhack_logo_texpos_start = -1; -static long g_green_pin_texpos_start = -1; -static long g_red_pin_texpos_start = -1; -static long g_icons_texpos_start = -1; -static long g_on_off_texpos_start = -1; -static long g_pathable_texpos_start = -1; -static long g_unsuspend_texpos_start = -1; -static long g_control_panel_texpos_start = -1; -static long g_thin_borders_texpos_start = -1; -static long g_medium_borders_texpos_start = -1; -static long g_bold_borders_texpos_start = -1; -static long g_panel_borders_texpos_start = -1; -static long g_window_borders_texpos_start = -1; +static std::unordered_map g_handle_to_texpos; +static std::unordered_map g_handle_to_surface; +static std::mutex g_adding_mutex; // Converts an arbitrary Surface to something like the display format // (32-bit RGBA), and converts magenta to transparency if convert_magenta is set @@ -41,205 +37,215 @@ static long g_window_borders_texpos_start = -1; // // It uses the same pixel format (RGBA, R at lowest address) regardless of // hardware. -SDL_Surface * canonicalize_format(SDL_Surface *src) { - SDL_PixelFormat fmt; - fmt.palette = NULL; - fmt.BitsPerPixel = 32; - fmt.BytesPerPixel = 4; - fmt.Rloss = fmt.Gloss = fmt.Bloss = fmt.Aloss = 0; +SDL_Surface* canonicalize_format(SDL_Surface* src) { + SDL_PixelFormat fmt; + fmt.palette = NULL; + fmt.BitsPerPixel = 32; + fmt.BytesPerPixel = 4; + fmt.Rloss = fmt.Gloss = fmt.Bloss = fmt.Aloss = 0; #if SDL_BYTEORDER == SDL_BIG_ENDIAN - fmt.Rshift = 24; fmt.Gshift = 16; fmt.Bshift = 8; fmt.Ashift = 0; + fmt.Rshift = 24; + fmt.Gshift = 16; + fmt.Bshift = 8; + fmt.Ashift = 0; #else - fmt.Rshift = 0; fmt.Gshift = 8; fmt.Bshift = 16; fmt.Ashift = 24; + fmt.Rshift = 0; + fmt.Gshift = 8; + fmt.Bshift = 16; + fmt.Ashift = 24; #endif - fmt.Rmask = 255 << fmt.Rshift; - fmt.Gmask = 255 << fmt.Gshift; - fmt.Bmask = 255 << fmt.Bshift; - fmt.Amask = 255 << fmt.Ashift; - - SDL_Surface *tgt = DFSDL_ConvertSurface(src, &fmt, SDL_SWSURFACE); - DFSDL_FreeSurface(src); - for (int x = 0; x < tgt->w; ++x) { - for (int y = 0; y < tgt->h; ++y) { - Uint8* p = (Uint8*)tgt->pixels + y * tgt->pitch + x * 4; - if (p[3] == 0) { - for (int c = 0; c < 3; c++) { - p[c] = 0; - } - } - } - } - return tgt; + fmt.Rmask = 255 << fmt.Rshift; + fmt.Gmask = 255 << fmt.Gshift; + fmt.Bmask = 255 << fmt.Bshift; + fmt.Amask = 255 << fmt.Ashift; + + SDL_Surface* tgt = DFSDL_ConvertSurface(src, &fmt, SDL_SWSURFACE); + DFSDL_FreeSurface(src); + for (int x = 0; x < tgt->w; ++x) { + for (int y = 0; y < tgt->h; ++y) { + Uint8* p = (Uint8*)tgt->pixels + y * tgt->pitch + x * 4; + if (p[3] == 0) { + for (int c = 0; c < 3; c++) { + p[c] = 0; + } + } + } + } + return tgt; +} + +// register surface in texture raws, get a texpos +static long add_texture(SDL_Surface* surface) { + std::lock_guard lg_add_texture(g_adding_mutex); + auto texpos = enabler->textures.raws.size(); + enabler->textures.raws.push_back(surface); + return texpos; } -const uint32_t TILE_WIDTH_PX = 8; -const uint32_t TILE_HEIGHT_PX = 12; - -static size_t load_textures(color_ostream & out, const char * fname, - long *texpos_start, - int tile_w = TILE_WIDTH_PX, - int tile_h = TILE_HEIGHT_PX) { - SDL_Surface *s = DFIMG_Load(fname); - if (!s) { - out.printerr("unable to load textures from '%s'\n", fname); - return 0; +TexposHandle Textures::loadTexture(SDL_Surface* surface) { + if (!surface) + return 0; // should be some error, i guess + + auto handle = reinterpret_cast(surface); + g_handle_to_surface.emplace(handle, surface); + surface->refcount++; // prevent destruct on next FreeSurface by game + auto texpos = add_texture(surface); + g_handle_to_texpos.emplace(handle, texpos); + return handle; +} + +std::vector Textures::loadTileset(const std::string& file, int tile_px_w, + int tile_px_h) { + std::vector handles{}; + + SDL_Surface* surface = DFIMG_Load(file.c_str()); + if (!surface) { + ERR(textures).printerr("unable to load textures from '%s'\n", file.c_str()); + return handles; } - s = canonicalize_format(s); - int dimx = s->w / tile_w; - int dimy = s->h / tile_h; - long count = 0; + surface = canonicalize_format(surface); + int dimx = surface->w / tile_px_w; + int dimy = surface->h / tile_px_h; for (int y = 0; y < dimy; y++) { for (int x = 0; x < dimx; x++) { - SDL_Surface *tile = DFSDL_CreateRGBSurface(0, // SDL_SWSURFACE - tile_w, tile_h, 32, - s->format->Rmask, s->format->Gmask, s->format->Bmask, - s->format->Amask); - SDL_Rect vp; - vp.x = tile_w * x; - vp.y = tile_h * y; - vp.w = tile_w; - vp.h = tile_h; - DFSDL_UpperBlit(s, &vp, tile, NULL); - if (!count++) - *texpos_start = enabler->textures.raws.size(); - enabler->textures.raws.push_back(tile); + SDL_Surface* tile = DFSDL_CreateRGBSurface( + 0, tile_px_w, tile_px_h, 32, surface->format->Rmask, surface->format->Gmask, + surface->format->Bmask, surface->format->Amask); + SDL_Rect vp{tile_px_w * x, tile_px_h * y, tile_px_w, tile_px_h}; + DFSDL_UpperBlit(surface, &vp, tile, NULL); + auto handle = Textures::loadTexture(tile); + handles.push_back(handle); } } - DFSDL_FreeSurface(s); - DEBUG(textures,out).print("loaded %ld textures from '%s'\n", count, fname); - return count; -} + DFSDL_FreeSurface(surface); + DEBUG(textures).print("loaded %zd textures from '%s'\n", handles.size(), file.c_str()); -// DFHack could conceivably be loaded at any time, so we need to be able to -// handle loading textures before or after a world is loaded. -// If a world is already loaded, then append our textures to the raws. they'll -// be freed when the world is unloaded and we'll reload when we get to the title -// screen. If it's pre-world, append our textures and then adjust the "init" -// texture count so our textures will no longer be freed when worlds are -// unloaded. -// -void Textures::init(color_ostream &out) { - if (!enabler) - return; - - auto & textures = enabler->textures; - long num_textures = textures.raws.size(); - if (num_textures <= g_dfhack_logo_texpos_start) - g_loaded = false; - - if (g_loaded) - return; - - bool is_pre_world = num_textures == textures.init_texture_size; - - g_num_dfhack_textures = load_textures(out, "hack/data/art/dfhack.png", - &g_dfhack_logo_texpos_start); - g_num_dfhack_textures += load_textures(out, "hack/data/art/green-pin.png", - &g_green_pin_texpos_start); - g_num_dfhack_textures += load_textures(out, "hack/data/art/red-pin.png", - &g_red_pin_texpos_start); - g_num_dfhack_textures += load_textures(out, "hack/data/art/icons.png", - &g_icons_texpos_start); - g_num_dfhack_textures += load_textures(out, "hack/data/art/on-off.png", - &g_on_off_texpos_start); - g_num_dfhack_textures += load_textures(out, "hack/data/art/pathable.png", - &g_pathable_texpos_start, 32, 32); - g_num_dfhack_textures += load_textures(out, "hack/data/art/unsuspend.png", - &g_unsuspend_texpos_start, 32, 32); - g_num_dfhack_textures += load_textures(out, "hack/data/art/control-panel.png", - &g_control_panel_texpos_start); - g_num_dfhack_textures += load_textures(out, "hack/data/art/border-thin.png", - &g_thin_borders_texpos_start); - g_num_dfhack_textures += load_textures(out, "hack/data/art/border-medium.png", - &g_medium_borders_texpos_start); - g_num_dfhack_textures += load_textures(out, "hack/data/art/border-bold.png", - &g_bold_borders_texpos_start); - g_num_dfhack_textures += load_textures(out, "hack/data/art/border-panel.png", - &g_panel_borders_texpos_start); - g_num_dfhack_textures += load_textures(out, "hack/data/art/border-window.png", - &g_window_borders_texpos_start); - - DEBUG(textures,out).print("loaded %ld textures\n", g_num_dfhack_textures); - - if (is_pre_world) - textures.init_texture_size += g_num_dfhack_textures; - - // NOTE: when GL modes are supported, we'll have to re-upload textures here - - g_loaded = true; + return handles; } -// It's ok to leave NULLs in the raws list (according to usage in g_src) -void Textures::cleanup() { - if (!g_loaded) - return; - - auto & textures = enabler->textures; - auto &raws = textures.raws; - size_t texpos_end = g_dfhack_logo_texpos_start + g_num_dfhack_textures - 1; - for (size_t idx = g_dfhack_logo_texpos_start; idx <= texpos_end; ++idx) { - DFSDL_FreeSurface((SDL_Surface *)raws[idx]); - raws[idx] = NULL; - } +long Textures::getTexposByHandle(TexposHandle handle) { + if (!handle) + return -1; - if (g_dfhack_logo_texpos_start == textures.init_texture_size - g_num_dfhack_textures) - textures.init_texture_size -= g_num_dfhack_textures; + if (g_handle_to_texpos.contains(handle)) + return g_handle_to_texpos[handle]; - g_loaded = false; - g_num_dfhack_textures = 0; - g_dfhack_logo_texpos_start = -1; -} + if (g_handle_to_surface.contains(handle)) { + g_handle_to_surface[handle]->refcount++; // prevent destruct on next FreeSurface by game + auto texpos = add_texture(g_handle_to_surface[handle]); + g_handle_to_texpos.emplace(handle, texpos); + return texpos; + } -long Textures::getDfhackLogoTexposStart() { - return g_dfhack_logo_texpos_start; + return -1; } -long Textures::getGreenPinTexposStart() { - return g_green_pin_texpos_start; +static void reset_texpos() { + g_handle_to_texpos.clear(); } -long Textures::getRedPinTexposStart() { - return g_red_pin_texpos_start; +static void reset_surface() { + for (auto& entry : g_handle_to_surface) { + DFSDL_FreeSurface(entry.second); + } + g_handle_to_surface.clear(); } -long Textures::getIconsTexposStart() { - return g_icons_texpos_start; -} +// reset point on New Game +struct tracking_stage_new_region : df::viewscreen_new_regionst { + typedef df::viewscreen_new_regionst interpose_base; -long Textures::getOnOffTexposStart() { - return g_on_off_texpos_start; -} + DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { + if (this->m_raw_load_stage != this->raw_load_stage) { + this->m_raw_load_stage = this->raw_load_stage; + if (this->m_raw_load_stage == 1) + reset_texpos(); + } + INTERPOSE_NEXT(logic)(); + } -long Textures::getMapPathableTexposStart() { - return g_pathable_texpos_start; -} + private: + inline static int m_raw_load_stage = -2; // not valid state at the start +}; +IMPLEMENT_VMETHOD_INTERPOSE(tracking_stage_new_region, logic); -long Textures::getMapUnsuspendTexposStart() { - return g_unsuspend_texpos_start; -} +// reset point on New Game in Existing World +struct tracking_stage_adopt_region : df::viewscreen_adopt_regionst { + typedef df::viewscreen_adopt_regionst interpose_base; -long Textures::getControlPanelTexposStart() { - return g_control_panel_texpos_start; -} + DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { + if (this->m_cur_step != this->cur_step) { + this->m_cur_step = this->cur_step; + if (this->m_cur_step == 1) + reset_texpos(); + } + INTERPOSE_NEXT(logic)(); + } -long Textures::getThinBordersTexposStart() { - return g_thin_borders_texpos_start; -} + private: + inline static int m_cur_step = -2; // not valid state at the start +}; +IMPLEMENT_VMETHOD_INTERPOSE(tracking_stage_adopt_region, logic); + +// reset point on Load Game +struct tracking_stage_load_region : df::viewscreen_loadgamest { + typedef df::viewscreen_loadgamest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { + if (this->m_cur_step != this->cur_step) { + this->m_cur_step = this->cur_step; + if (this->m_cur_step == 1) + reset_texpos(); + } + INTERPOSE_NEXT(logic)(); + } + + private: + inline static int m_cur_step = -2; // not valid state at the start +}; +IMPLEMENT_VMETHOD_INTERPOSE(tracking_stage_load_region, logic); -long Textures::getMediumBordersTexposStart() { - return g_medium_borders_texpos_start; +// reset point on New Arena +struct tracking_stage_new_arena : df::viewscreen_new_arenast { + typedef df::viewscreen_new_arenast interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { + if (this->m_cur_step != this->cur_step) { + this->m_cur_step = this->cur_step; + if (this->m_cur_step == 0) + reset_texpos(); + } + INTERPOSE_NEXT(logic)(); + } + + private: + inline static int m_cur_step = -2; // not valid state at the start +}; +IMPLEMENT_VMETHOD_INTERPOSE(tracking_stage_new_arena, logic); + +static void install_reset_point() { + INTERPOSE_HOOK(tracking_stage_new_region, logic).apply(); + INTERPOSE_HOOK(tracking_stage_adopt_region, logic).apply(); + INTERPOSE_HOOK(tracking_stage_load_region, logic).apply(); + INTERPOSE_HOOK(tracking_stage_new_arena, logic).apply(); } -long Textures::getBoldBordersTexposStart() { - return g_bold_borders_texpos_start; +static void uninstall_reset_point() { + INTERPOSE_HOOK(tracking_stage_new_region, logic).remove(); + INTERPOSE_HOOK(tracking_stage_adopt_region, logic).remove(); + INTERPOSE_HOOK(tracking_stage_load_region, logic).remove(); + INTERPOSE_HOOK(tracking_stage_new_arena, logic).remove(); } -long Textures::getPanelBordersTexposStart() { - return g_panel_borders_texpos_start; +void Textures::init(color_ostream& out) { + install_reset_point(); + DEBUG(textures, out).print("dynamic texture loading ready"); } -long Textures::getWindowBordersTexposStart() { - return g_window_borders_texpos_start; +void Textures::cleanup() { + reset_texpos(); + reset_surface(); + uninstall_reset_point(); } diff --git a/plugins/lua/buildingplan/pens.lua b/plugins/lua/buildingplan/pens.lua index e69a4c210..2ab76da06 100644 --- a/plugins/lua/buildingplan/pens.lua +++ b/plugins/lua/buildingplan/pens.lua @@ -1,5 +1,8 @@ local _ENV = mkmodule('plugins.buildingplan.pens') +local gui = require('gui') +local textures = require('gui.textures') + GOOD_TILE_PEN, BAD_TILE_PEN = nil, nil VERT_TOP_PEN, VERT_MID_PEN, VERT_BOT_PEN = nil, nil, nil HORI_LEFT_PEN, HORI_MID_PEN, HORI_RIGHT_PEN = nil, nil, nil @@ -9,29 +12,21 @@ MINI_TEXT_PEN, MINI_TEXT_HPEN, MINI_BUTT_PEN, MINI_BUTT_HPEN = nil, nil, nil, ni local to_pen = dfhack.pen.parse -local tp = function(base, offset) - if base == -1 then return nil end - return base + offset -end - function reload_pens() GOOD_TILE_PEN = to_pen{ch='o', fg=COLOR_GREEN, tile=dfhack.screen.findGraphicsTile('CURSORS', 1, 2)} BAD_TILE_PEN = to_pen{ch='X', fg=COLOR_RED, tile=dfhack.screen.findGraphicsTile('CURSORS', 3, 0)} - local tb_texpos = dfhack.textures.getThinBordersTexposStart() - VERT_TOP_PEN = to_pen{tile=tp(tb_texpos, 10), ch=194, fg=COLOR_GREY, bg=COLOR_BLACK} - VERT_MID_PEN = to_pen{tile=tp(tb_texpos, 4), ch=179, fg=COLOR_GREY, bg=COLOR_BLACK} - VERT_BOT_PEN = to_pen{tile=tp(tb_texpos, 11), ch=193, fg=COLOR_GREY, bg=COLOR_BLACK} + VERT_TOP_PEN = to_pen { tile = curry(textures.tp_border_thin, 11), ch = 194, fg = COLOR_GREY, bg = COLOR_BLACK } + VERT_MID_PEN = to_pen { tile = curry(textures.tp_border_thin, 5), ch = 179, fg = COLOR_GREY, bg = COLOR_BLACK } + VERT_BOT_PEN = to_pen { tile = curry(textures.tp_border_thin, 12), ch = 193, fg = COLOR_GREY, bg = COLOR_BLACK } - local mb_texpos = dfhack.textures.getMediumBordersTexposStart() - HORI_LEFT_PEN = to_pen{tile=tp(mb_texpos, 12), ch=195, fg=COLOR_GREY, bg=COLOR_BLACK} - HORI_MID_PEN = to_pen{tile=tp(mb_texpos, 5), ch=196, fg=COLOR_GREY, bg=COLOR_BLACK} - HORI_RIGHT_PEN = to_pen{tile=tp(mb_texpos, 13), ch=180, fg=COLOR_GREY, bg=COLOR_BLACK} + HORI_LEFT_PEN = to_pen { tile = curry(textures.tp_border_medium, 13), ch = 195, fg = COLOR_GREY, bg = COLOR_BLACK } + HORI_MID_PEN = to_pen { tile = curry(textures.tp_border_medium, 6), ch = 196, fg = COLOR_GREY, bg = COLOR_BLACK } + HORI_RIGHT_PEN = to_pen { tile = curry(textures.tp_border_medium, 14), ch = 180, fg = COLOR_GREY, bg = COLOR_BLACK } - local cp_texpos = dfhack.textures.getControlPanelTexposStart() - BUTTON_START_PEN = to_pen{tile=tp(cp_texpos, 13), ch='[', fg=COLOR_YELLOW} - BUTTON_END_PEN = to_pen{tile=tp(cp_texpos, 15), ch=']', fg=COLOR_YELLOW} - SELECTED_ITEM_PEN = to_pen{tile=tp(cp_texpos, 9), ch=string.char(251), fg=COLOR_YELLOW} + BUTTON_START_PEN = to_pen { tile = curry(textures.tp_control_panel, 14), ch = '[', fg = COLOR_YELLOW } + BUTTON_END_PEN = to_pen { tile = curry(textures.tp_control_panel, 16), ch = ']', fg = COLOR_YELLOW } + SELECTED_ITEM_PEN = to_pen { tile = curry(textures.tp_control_panel, 10), ch = string.char(251), fg = COLOR_YELLOW } MINI_TEXT_PEN = to_pen{fg=COLOR_BLACK, bg=COLOR_GREY} MINI_TEXT_HPEN = to_pen{fg=COLOR_BLACK, bg=COLOR_WHITE} diff --git a/plugins/lua/hotkeys.lua b/plugins/lua/hotkeys.lua index 629f743cc..d380eba84 100644 --- a/plugins/lua/hotkeys.lua +++ b/plugins/lua/hotkeys.lua @@ -5,6 +5,8 @@ local helpdb = require('helpdb') local overlay = require('plugins.overlay') local widgets = require('gui.widgets') +local textures = dfhack.textures.loadTileset('hack/data/art/dfhack.png', 8, 12) + local function get_command(cmdline) local first_word = cmdline:trim():split(' +')[1] if first_word:startswith(':') then first_word = first_word:sub(2) end @@ -69,28 +71,30 @@ end local dscreen = dfhack.screen function HotspotMenuWidget:onRenderBody(dc) - local tpos = dfhack.textures.getDfhackLogoTexposStart() local x, y = dc.x, dc.y + local tp = function(offset) + return dfhack.textures.getTexposByHandle(textures[offset]) + end - if tpos == -1 then - dscreen.paintString(COLOR_WHITE, x, y+0, '!DF!') - dscreen.paintString(COLOR_WHITE, x, y+1, '!Ha!') - dscreen.paintString(COLOR_WHITE, x, y+2, '!ck!') + if tp(1) == nil then + dscreen.paintString(COLOR_WHITE, x, y + 0, '!DF!') + dscreen.paintString(COLOR_WHITE, x, y + 1, '!Ha!') + dscreen.paintString(COLOR_WHITE, x, y + 2, '!ck!') else - dscreen.paintTile(COLOR_WHITE, x+0, y+0, '!', tpos+0) - dscreen.paintTile(COLOR_WHITE, x+1, y+0, 'D', tpos+1) - dscreen.paintTile(COLOR_WHITE, x+2, y+0, 'F', tpos+2) - dscreen.paintTile(COLOR_WHITE, x+3, y+0, '!', tpos+3) - - dscreen.paintTile(COLOR_WHITE, x+0, y+1, '!', tpos+4) - dscreen.paintTile(COLOR_WHITE, x+1, y+1, 'H', tpos+5) - dscreen.paintTile(COLOR_WHITE, x+2, y+1, 'a', tpos+6) - dscreen.paintTile(COLOR_WHITE, x+3, y+1, '!', tpos+7) - - dscreen.paintTile(COLOR_WHITE, x+0, y+2, '!', tpos+8) - dscreen.paintTile(COLOR_WHITE, x+1, y+2, 'c', tpos+9) - dscreen.paintTile(COLOR_WHITE, x+2, y+2, 'k', tpos+10) - dscreen.paintTile(COLOR_WHITE, x+3, y+2, '!', tpos+11) + dscreen.paintTile(COLOR_WHITE, x + 0, y + 0, '!', tp(1)) + dscreen.paintTile(COLOR_WHITE, x + 1, y + 0, 'D', tp(2)) + dscreen.paintTile(COLOR_WHITE, x + 2, y + 0, 'F', tp(3)) + dscreen.paintTile(COLOR_WHITE, x + 3, y + 0, '!', tp(4)) + + dscreen.paintTile(COLOR_WHITE, x + 0, y + 1, '!', tp(5)) + dscreen.paintTile(COLOR_WHITE, x + 1, y + 1, 'H', tp(6)) + dscreen.paintTile(COLOR_WHITE, x + 2, y + 1, 'a', tp(7)) + dscreen.paintTile(COLOR_WHITE, x + 3, y + 1, '!', tp(8)) + + dscreen.paintTile(COLOR_WHITE, x + 0, y + 2, '!', tp(9)) + dscreen.paintTile(COLOR_WHITE, x + 1, y + 2, 'c', tp(10)) + dscreen.paintTile(COLOR_WHITE, x + 2, y + 2, 'k', tp(11)) + dscreen.paintTile(COLOR_WHITE, x + 3, y + 2, '!', tp(12)) end end diff --git a/plugins/pathable.cpp b/plugins/pathable.cpp index f60469489..12a69dd31 100644 --- a/plugins/pathable.cpp +++ b/plugins/pathable.cpp @@ -26,7 +26,10 @@ namespace DFHack { DBG_DECLARE(pathable, log, DebugCategory::LINFO); } +static std::vector textures; + DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + textures = Textures::loadTileset("hack/data/art/pathable.png", 32, 32); return CR_OK; } @@ -44,10 +47,10 @@ static void paintScreenPathable(df::coord target, bool show_hidden = false) { long pathable_tile_texpos = init->load_bar_texpos[1]; long unpathable_tile_texpos = init->load_bar_texpos[4]; - long on_off_texpos = Textures::getMapPathableTexposStart(); + long on_off_texpos = Textures::getTexposByHandle(textures[0]); if (on_off_texpos > 0) { - pathable_tile_texpos = on_off_texpos + 0; - unpathable_tile_texpos = on_off_texpos + 1; + pathable_tile_texpos = on_off_texpos; + unpathable_tile_texpos = Textures::getTexposByHandle(textures[1]); } auto dims = Gui::getDwarfmodeViewDims().map();