Merge pull request #3663 from shevernitskiy/dynamic-texture-loading

feat: dynamic texture loading
develop
Myk 2023-08-27 22:45:52 -07:00 committed by GitHub
commit a740c6628e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 446 additions and 312 deletions

@ -2217,9 +2217,6 @@ void Core::onStateChange(color_ostream &out, state_change_event event)
} }
} }
break; break;
case SC_VIEWSCREEN_CHANGED:
Textures::init(out);
break;
default: default:
break; break;
} }

@ -161,6 +161,26 @@ static bool get_int_field(lua_State *L, T *pf, int idx, const char *name, int de
return !nil; return !nil;
} }
template<class T>
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) static bool get_char_field(lua_State *L, char *pf, int idx, const char *name, char defval)
{ {
lua_getfield(L, idx, name); 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); else pen.bold = lua_toboolean(L, -1);
lua_pop(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); 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; 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); 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 WRAPM(module, function) { #function, df::wrap_function(module::function,true) }
#define WRAP(function) { #function, df::wrap_function(function,true) } #define WRAP(function) { #function, df::wrap_function(function,true) }
#define WRAPN(name, function) { #name, 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 *****/ /***** Textures module *****/
static const LuaWrapper::FunctionReg dfhack_textures_module[] = { static int textures_loadTileset(lua_State *state)
WRAPM(Textures, getDfhackLogoTexposStart), {
WRAPM(Textures, getGreenPinTexposStart), std::string file = luaL_checkstring(state, 1);
WRAPM(Textures, getRedPinTexposStart), auto tile_w = luaL_checkint(state, 2);
WRAPM(Textures, getIconsTexposStart), auto tile_h = luaL_checkint(state, 3);
WRAPM(Textures, getOnOffTexposStart), auto handles = Textures::loadTileset(file, tile_w, tile_h);
WRAPM(Textures, getMapUnsuspendTexposStart), Lua::PushVector(state, handles);
WRAPM(Textures, getControlPanelTexposStart), return 1;
WRAPM(Textures, getThinBordersTexposStart), }
WRAPM(Textures, getMediumBordersTexposStart),
WRAPM(Textures, getBoldBordersTexposStart), static int textures_getTexposByHandle(lua_State *state)
WRAPM(Textures, getPanelBordersTexposStart), {
WRAPM(Textures, getWindowBordersTexposStart), 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 } { NULL, NULL }
}; };
@ -3713,7 +3752,7 @@ void OpenDFHackApi(lua_State *state)
luaL_setfuncs(state, dfhack_funcs, 0); luaL_setfuncs(state, dfhack_funcs, 0);
OpenModule(state, "gui", dfhack_gui_module, dfhack_gui_funcs); OpenModule(state, "gui", dfhack_gui_module, dfhack_gui_funcs);
OpenModule(state, "job", dfhack_job_module, dfhack_job_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, "units", dfhack_units_module, dfhack_units_funcs);
OpenModule(state, "military", dfhack_military_module); OpenModule(state, "military", dfhack_military_module);
OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs); OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs);

@ -1,7 +1,14 @@
#pragma once #pragma once
#include "Export.h" #include <string>
#include <vector>
#include "ColorText.h" #include "ColorText.h"
#include "Export.h"
struct SDL_Surface;
typedef uintptr_t TexposHandle;
namespace DFHack { namespace DFHack {
@ -12,63 +19,41 @@ namespace DFHack {
*/ */
namespace Textures { namespace Textures {
/** const uint32_t TILE_WIDTH_PX = 8;
* Call this on DFHack init and on every viewscreen change so we can reload const uint32_t TILE_HEIGHT_PX = 12;
* and reindex textures as needed.
*/
void init(DFHack::color_ostream &out);
/**
* Call this when DFHack is being unloaded.
*
*/
void cleanup();
/** /**
* Get first texpos for the DFHack logo. This texpos and the next 11 make up the * Load texture and get handle.
* 4x3 grid of logo textures that can be displayed on the UI layer. * 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 std::vector<TexposHandle> loadTileset(const std::string& file,
DFHACK_EXPORT long getRedPinTexposStart(); 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. * Call this when DFHack is being unloaded.
*/ *
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.
*/ */
DFHACK_EXPORT long getThinBordersTexposStart(); void cleanup();
DFHACK_EXPORT long getMediumBordersTexposStart();
DFHACK_EXPORT long getBoldBordersTexposStart();
DFHACK_EXPORT long getPanelBordersTexposStart();
DFHACK_EXPORT long getWindowBordersTexposStart();
} } // namespace Textures
} } // namespace DFHack

@ -2,6 +2,7 @@
local _ENV = mkmodule('gui') local _ENV = mkmodule('gui')
local textures = require('gui.textures')
local utils = require('utils') local utils = require('utils')
local dscreen = dfhack.screen local dscreen = dfhack.screen
@ -912,33 +913,46 @@ local BASE_FRAME = {
paused_pen = to_pen{fg=COLOR_RED, bg=COLOR_BLACK}, 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) 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.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=tp(7), ch=double_line and 186 or 179, 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=tp(15), ch=double_line and 205 or 196, 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=tp(9), ch=double_line and 186 or 179, 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=tp(0), ch=double_line and 201 or 218, 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=tp(14), ch=double_line and 200 or 192, 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=tp(2), ch=double_line and 187 or 191, 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=tp(16), ch=double_line and 188 or 217, 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 return frame
end end
FRAME_WINDOW = make_frame('Window', true) function FRAME_WINDOW(resizable)
FRAME_PANEL = make_frame('Panel', false) local frame = make_frame(textures.tp_border_window, true)
FRAME_MEDIUM = make_frame('Medium', false) if not resizable then
FRAME_BOLD = make_frame('Bold', true) 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 }
FRAME_INTERIOR = make_frame('Thin', false) end
FRAME_INTERIOR.signature_pen = false return frame
FRAME_INTERIOR_MEDIUM = copyall(FRAME_MEDIUM) end
FRAME_INTERIOR_MEDIUM.signature_pen = false 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 -- for compatibility with pre-steam code
GREY_LINE_FRAME = FRAME_PANEL GREY_LINE_FRAME = FRAME_PANEL
@ -951,18 +965,16 @@ BOLD_FRAME = FRAME_BOLD
INTERIOR_FRAME = FRAME_INTERIOR INTERIOR_FRAME = FRAME_INTERIOR
INTERIOR_MEDIUM_FRAME = FRAME_INTERIOR_MEDIUM 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 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 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.lt_frame_pen or pen, x1, y1)
dscreen.paintTile(style.rt_frame_pen or pen, x2, y1) dscreen.paintTile(style.rt_frame_pen or pen, x2, y1)
dscreen.paintTile(style.lb_frame_pen or pen, x1, y2) dscreen.paintTile(style.lb_frame_pen or pen, x1, y2)
local rb_frame_pen = style.rb_frame_pen dscreen.paintTile(style.rb_frame_pen or pen, x2, y2)
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.fillRect(style.t_frame_pen or style.h_frame_pen or pen,x1+1,y1,x2-1,y1) 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.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) dscreen.fillRect(style.l_frame_pen or style.v_frame_pen or pen,x1,y1+1,x1,y2-1)

@ -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<string, TexposHandle>
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

@ -1,3 +1,6 @@
#include <mutex>
#include <unordered_map>
#include "Internal.h" #include "Internal.h"
#include "modules/DFSDL.h" #include "modules/DFSDL.h"
@ -5,8 +8,13 @@
#include "Debug.h" #include "Debug.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "VTableInterpose.h"
#include "df/enabler.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 <SDL_surface.h> #include <SDL_surface.h>
@ -15,24 +23,12 @@ using namespace DFHack;
using namespace DFHack::DFSDL; using namespace DFHack::DFSDL;
namespace DFHack { namespace DFHack {
DBG_DECLARE(core, textures, DebugCategory::LINFO); DBG_DECLARE(core, textures, DebugCategory::LINFO);
} }
static bool g_loaded = false; static std::unordered_map<TexposHandle, long> g_handle_to_texpos;
static long g_num_dfhack_textures = 0; static std::unordered_map<TexposHandle, SDL_Surface*> g_handle_to_surface;
static long g_dfhack_logo_texpos_start = -1; static std::mutex g_adding_mutex;
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;
// Converts an arbitrary Surface to something like the display format // Converts an arbitrary Surface to something like the display format
// (32-bit RGBA), and converts magenta to transparency if convert_magenta is set // (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 // It uses the same pixel format (RGBA, R at lowest address) regardless of
// hardware. // hardware.
SDL_Surface * canonicalize_format(SDL_Surface *src) { SDL_Surface* canonicalize_format(SDL_Surface* src) {
SDL_PixelFormat fmt; SDL_PixelFormat fmt;
fmt.palette = NULL; fmt.palette = NULL;
fmt.BitsPerPixel = 32; fmt.BitsPerPixel = 32;
fmt.BytesPerPixel = 4; fmt.BytesPerPixel = 4;
fmt.Rloss = fmt.Gloss = fmt.Bloss = fmt.Aloss = 0; fmt.Rloss = fmt.Gloss = fmt.Bloss = fmt.Aloss = 0;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN #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 #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 #endif
fmt.Rmask = 255 << fmt.Rshift; fmt.Rmask = 255 << fmt.Rshift;
fmt.Gmask = 255 << fmt.Gshift; fmt.Gmask = 255 << fmt.Gshift;
fmt.Bmask = 255 << fmt.Bshift; fmt.Bmask = 255 << fmt.Bshift;
fmt.Amask = 255 << fmt.Ashift; fmt.Amask = 255 << fmt.Ashift;
SDL_Surface *tgt = DFSDL_ConvertSurface(src, &fmt, SDL_SWSURFACE); SDL_Surface* tgt = DFSDL_ConvertSurface(src, &fmt, SDL_SWSURFACE);
DFSDL_FreeSurface(src); DFSDL_FreeSurface(src);
for (int x = 0; x < tgt->w; ++x) { for (int x = 0; x < tgt->w; ++x) {
for (int y = 0; y < tgt->h; ++y) { for (int y = 0; y < tgt->h; ++y) {
Uint8* p = (Uint8*)tgt->pixels + y * tgt->pitch + x * 4; Uint8* p = (Uint8*)tgt->pixels + y * tgt->pitch + x * 4;
if (p[3] == 0) { if (p[3] == 0) {
for (int c = 0; c < 3; c++) { for (int c = 0; c < 3; c++) {
p[c] = 0; p[c] = 0;
} }
} }
} }
} }
return tgt; return tgt;
}
// register surface in texture raws, get a texpos
static long add_texture(SDL_Surface* surface) {
std::lock_guard<std::mutex> 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; TexposHandle Textures::loadTexture(SDL_Surface* surface) {
const uint32_t TILE_HEIGHT_PX = 12; if (!surface)
return 0; // should be some error, i guess
static size_t load_textures(color_ostream & out, const char * fname,
long *texpos_start, auto handle = reinterpret_cast<uintptr_t>(surface);
int tile_w = TILE_WIDTH_PX, g_handle_to_surface.emplace(handle, surface);
int tile_h = TILE_HEIGHT_PX) { surface->refcount++; // prevent destruct on next FreeSurface by game
SDL_Surface *s = DFIMG_Load(fname); auto texpos = add_texture(surface);
if (!s) { g_handle_to_texpos.emplace(handle, texpos);
out.printerr("unable to load textures from '%s'\n", fname); return handle;
return 0; }
std::vector<TexposHandle> Textures::loadTileset(const std::string& file, int tile_px_w,
int tile_px_h) {
std::vector<TexposHandle> 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); surface = canonicalize_format(surface);
int dimx = s->w / tile_w; int dimx = surface->w / tile_px_w;
int dimy = s->h / tile_h; int dimy = surface->h / tile_px_h;
long count = 0;
for (int y = 0; y < dimy; y++) { for (int y = 0; y < dimy; y++) {
for (int x = 0; x < dimx; x++) { for (int x = 0; x < dimx; x++) {
SDL_Surface *tile = DFSDL_CreateRGBSurface(0, // SDL_SWSURFACE SDL_Surface* tile = DFSDL_CreateRGBSurface(
tile_w, tile_h, 32, 0, tile_px_w, tile_px_h, 32, surface->format->Rmask, surface->format->Gmask,
s->format->Rmask, s->format->Gmask, s->format->Bmask, surface->format->Bmask, surface->format->Amask);
s->format->Amask); SDL_Rect vp{tile_px_w * x, tile_px_h * y, tile_px_w, tile_px_h};
SDL_Rect vp; DFSDL_UpperBlit(surface, &vp, tile, NULL);
vp.x = tile_w * x; auto handle = Textures::loadTexture(tile);
vp.y = tile_h * y; handles.push_back(handle);
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);
} }
} }
DFSDL_FreeSurface(s);
DEBUG(textures,out).print("loaded %ld textures from '%s'\n", count, fname); DFSDL_FreeSurface(surface);
return count; 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 return handles;
// 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;
} }
// It's ok to leave NULLs in the raws list (according to usage in g_src) long Textures::getTexposByHandle(TexposHandle handle) {
void Textures::cleanup() { if (!handle)
if (!g_loaded) return -1;
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;
}
if (g_dfhack_logo_texpos_start == textures.init_texture_size - g_num_dfhack_textures) if (g_handle_to_texpos.contains(handle))
textures.init_texture_size -= g_num_dfhack_textures; return g_handle_to_texpos[handle];
g_loaded = false; if (g_handle_to_surface.contains(handle)) {
g_num_dfhack_textures = 0; g_handle_to_surface[handle]->refcount++; // prevent destruct on next FreeSurface by game
g_dfhack_logo_texpos_start = -1; auto texpos = add_texture(g_handle_to_surface[handle]);
} g_handle_to_texpos.emplace(handle, texpos);
return texpos;
}
long Textures::getDfhackLogoTexposStart() { return -1;
return g_dfhack_logo_texpos_start;
} }
long Textures::getGreenPinTexposStart() { static void reset_texpos() {
return g_green_pin_texpos_start; g_handle_to_texpos.clear();
} }
long Textures::getRedPinTexposStart() { static void reset_surface() {
return g_red_pin_texpos_start; for (auto& entry : g_handle_to_surface) {
DFSDL_FreeSurface(entry.second);
}
g_handle_to_surface.clear();
} }
long Textures::getIconsTexposStart() { // reset point on New Game
return g_icons_texpos_start; struct tracking_stage_new_region : df::viewscreen_new_regionst {
} typedef df::viewscreen_new_regionst interpose_base;
long Textures::getOnOffTexposStart() { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
return g_on_off_texpos_start; 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() { private:
return g_pathable_texpos_start; 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() { // reset point on New Game in Existing World
return g_unsuspend_texpos_start; struct tracking_stage_adopt_region : df::viewscreen_adopt_regionst {
} typedef df::viewscreen_adopt_regionst interpose_base;
long Textures::getControlPanelTexposStart() { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
return g_control_panel_texpos_start; 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() { private:
return g_thin_borders_texpos_start; 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() { // reset point on New Arena
return g_medium_borders_texpos_start; 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() { static void uninstall_reset_point() {
return g_bold_borders_texpos_start; 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() { void Textures::init(color_ostream& out) {
return g_panel_borders_texpos_start; install_reset_point();
DEBUG(textures, out).print("dynamic texture loading ready");
} }
long Textures::getWindowBordersTexposStart() { void Textures::cleanup() {
return g_window_borders_texpos_start; reset_texpos();
reset_surface();
uninstall_reset_point();
} }

@ -1,5 +1,8 @@
local _ENV = mkmodule('plugins.buildingplan.pens') local _ENV = mkmodule('plugins.buildingplan.pens')
local gui = require('gui')
local textures = require('gui.textures')
GOOD_TILE_PEN, BAD_TILE_PEN = nil, nil GOOD_TILE_PEN, BAD_TILE_PEN = nil, nil
VERT_TOP_PEN, VERT_MID_PEN, VERT_BOT_PEN = nil, 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 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 to_pen = dfhack.pen.parse
local tp = function(base, offset)
if base == -1 then return nil end
return base + offset
end
function reload_pens() function reload_pens()
GOOD_TILE_PEN = to_pen{ch='o', fg=COLOR_GREEN, tile=dfhack.screen.findGraphicsTile('CURSORS', 1, 2)} 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)} 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 = curry(textures.tp_border_thin, 11), ch = 194, fg = COLOR_GREY, bg = COLOR_BLACK }
VERT_TOP_PEN = to_pen{tile=tp(tb_texpos, 10), 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_MID_PEN = to_pen{tile=tp(tb_texpos, 4), 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 }
VERT_BOT_PEN = to_pen{tile=tp(tb_texpos, 11), ch=193, fg=COLOR_GREY, bg=COLOR_BLACK}
local mb_texpos = dfhack.textures.getMediumBordersTexposStart() HORI_LEFT_PEN = to_pen { tile = curry(textures.tp_border_medium, 13), ch = 195, fg = COLOR_GREY, bg = COLOR_BLACK }
HORI_LEFT_PEN = to_pen{tile=tp(mb_texpos, 12), 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_MID_PEN = to_pen{tile=tp(mb_texpos, 5), 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 }
HORI_RIGHT_PEN = to_pen{tile=tp(mb_texpos, 13), ch=180, fg=COLOR_GREY, bg=COLOR_BLACK}
local cp_texpos = dfhack.textures.getControlPanelTexposStart() BUTTON_START_PEN = to_pen { tile = curry(textures.tp_control_panel, 14), ch = '[', fg = COLOR_YELLOW }
BUTTON_START_PEN = to_pen{tile=tp(cp_texpos, 13), ch='[', fg=COLOR_YELLOW} BUTTON_END_PEN = to_pen { tile = curry(textures.tp_control_panel, 16), ch = ']', fg = COLOR_YELLOW }
BUTTON_END_PEN = to_pen{tile=tp(cp_texpos, 15), ch=']', fg=COLOR_YELLOW} SELECTED_ITEM_PEN = to_pen { tile = curry(textures.tp_control_panel, 10), ch = string.char(251), fg = COLOR_YELLOW }
SELECTED_ITEM_PEN = to_pen{tile=tp(cp_texpos, 9), ch=string.char(251), fg=COLOR_YELLOW}
MINI_TEXT_PEN = to_pen{fg=COLOR_BLACK, bg=COLOR_GREY} MINI_TEXT_PEN = to_pen{fg=COLOR_BLACK, bg=COLOR_GREY}
MINI_TEXT_HPEN = to_pen{fg=COLOR_BLACK, bg=COLOR_WHITE} MINI_TEXT_HPEN = to_pen{fg=COLOR_BLACK, bg=COLOR_WHITE}

@ -5,6 +5,8 @@ local helpdb = require('helpdb')
local overlay = require('plugins.overlay') local overlay = require('plugins.overlay')
local widgets = require('gui.widgets') local widgets = require('gui.widgets')
local textures = dfhack.textures.loadTileset('hack/data/art/dfhack.png', 8, 12)
local function get_command(cmdline) local function get_command(cmdline)
local first_word = cmdline:trim():split(' +')[1] local first_word = cmdline:trim():split(' +')[1]
if first_word:startswith(':') then first_word = first_word:sub(2) end if first_word:startswith(':') then first_word = first_word:sub(2) end
@ -69,28 +71,30 @@ end
local dscreen = dfhack.screen local dscreen = dfhack.screen
function HotspotMenuWidget:onRenderBody(dc) function HotspotMenuWidget:onRenderBody(dc)
local tpos = dfhack.textures.getDfhackLogoTexposStart()
local x, y = dc.x, dc.y local x, y = dc.x, dc.y
local tp = function(offset)
return dfhack.textures.getTexposByHandle(textures[offset])
end
if tpos == -1 then if tp(1) == nil then
dscreen.paintString(COLOR_WHITE, x, y+0, '!DF!') dscreen.paintString(COLOR_WHITE, x, y + 0, '!DF!')
dscreen.paintString(COLOR_WHITE, x, y+1, '!Ha!') dscreen.paintString(COLOR_WHITE, x, y + 1, '!Ha!')
dscreen.paintString(COLOR_WHITE, x, y+2, '!ck!') dscreen.paintString(COLOR_WHITE, x, y + 2, '!ck!')
else else
dscreen.paintTile(COLOR_WHITE, x+0, y+0, '!', tpos+0) dscreen.paintTile(COLOR_WHITE, x + 0, y + 0, '!', tp(1))
dscreen.paintTile(COLOR_WHITE, x+1, y+0, 'D', tpos+1) dscreen.paintTile(COLOR_WHITE, x + 1, y + 0, 'D', tp(2))
dscreen.paintTile(COLOR_WHITE, x+2, y+0, 'F', tpos+2) dscreen.paintTile(COLOR_WHITE, x + 2, y + 0, 'F', tp(3))
dscreen.paintTile(COLOR_WHITE, x+3, y+0, '!', tpos+3) dscreen.paintTile(COLOR_WHITE, x + 3, y + 0, '!', tp(4))
dscreen.paintTile(COLOR_WHITE, x+0, y+1, '!', tpos+4) dscreen.paintTile(COLOR_WHITE, x + 0, y + 1, '!', tp(5))
dscreen.paintTile(COLOR_WHITE, x+1, y+1, 'H', tpos+5) dscreen.paintTile(COLOR_WHITE, x + 1, y + 1, 'H', tp(6))
dscreen.paintTile(COLOR_WHITE, x+2, y+1, 'a', tpos+6) dscreen.paintTile(COLOR_WHITE, x + 2, y + 1, 'a', tp(7))
dscreen.paintTile(COLOR_WHITE, x+3, y+1, '!', tpos+7) dscreen.paintTile(COLOR_WHITE, x + 3, y + 1, '!', tp(8))
dscreen.paintTile(COLOR_WHITE, x+0, y+2, '!', tpos+8) dscreen.paintTile(COLOR_WHITE, x + 0, y + 2, '!', tp(9))
dscreen.paintTile(COLOR_WHITE, x+1, y+2, 'c', tpos+9) dscreen.paintTile(COLOR_WHITE, x + 1, y + 2, 'c', tp(10))
dscreen.paintTile(COLOR_WHITE, x+2, y+2, 'k', tpos+10) dscreen.paintTile(COLOR_WHITE, x + 2, y + 2, 'k', tp(11))
dscreen.paintTile(COLOR_WHITE, x+3, y+2, '!', tpos+11) dscreen.paintTile(COLOR_WHITE, x + 3, y + 2, '!', tp(12))
end end
end end

@ -26,7 +26,10 @@ namespace DFHack {
DBG_DECLARE(pathable, log, DebugCategory::LINFO); DBG_DECLARE(pathable, log, DebugCategory::LINFO);
} }
static std::vector<TexposHandle> textures;
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) { DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) {
textures = Textures::loadTileset("hack/data/art/pathable.png", 32, 32);
return CR_OK; 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 pathable_tile_texpos = init->load_bar_texpos[1];
long unpathable_tile_texpos = init->load_bar_texpos[4]; 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) { if (on_off_texpos > 0) {
pathable_tile_texpos = on_off_texpos + 0; pathable_tile_texpos = on_off_texpos;
unpathable_tile_texpos = on_off_texpos + 1; unpathable_tile_texpos = Textures::getTexposByHandle(textures[1]);
} }
auto dims = Gui::getDwarfmodeViewDims().map(); auto dims = Gui::getDwarfmodeViewDims().map();