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;
case SC_VIEWSCREEN_CHANGED:
Textures::init(out);
break;
default:
break;
}

@ -161,6 +161,26 @@ static bool get_int_field(lua_State *L, T *pf, int idx, const char *name, int de
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)
{
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);

@ -1,7 +1,14 @@
#pragma once
#include "Export.h"
#include <string>
#include <vector>
#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<TexposHandle> 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

@ -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)

@ -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 "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 <SDL_surface.h>
@ -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<TexposHandle, long> g_handle_to_texpos;
static std::unordered_map<TexposHandle, SDL_Surface*> 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<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;
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<uintptr_t>(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<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);
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();
}

@ -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}

@ -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

@ -26,7 +26,10 @@ namespace DFHack {
DBG_DECLARE(pathable, log, DebugCategory::LINFO);
}
static std::vector<TexposHandle> textures;
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;
}
@ -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();