Merge branch 'develop' into work_orders_manager_enhancement

develop
Myk 2023-09-29 09:43:19 -07:00 committed by GitHub
commit 424f242823
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 849 additions and 456 deletions

@ -67,19 +67,25 @@ jobs:
compiler: msvc
plugins: "default"
config: "empty"
# TODO: uncomment once we have a linux build we can download from bay12
# - os: ubuntu
# compiler: gcc-10
# plugins: "default"
# config: "default"
# - os: ubuntu
# compiler: gcc-12
# plugins: "all"
# config: "default"
- os: ubuntu
compiler: gcc-10
plugins: "default"
config: "default"
- os: ubuntu
compiler: gcc-12
plugins: "all"
config: "default"
steps:
- name: Set env
shell: bash
run: echo "DF_FOLDER=DF" >> $GITHUB_ENV
- name: Install dependencies
if: matrix.os == 'ubuntu'
run: |
sudo apt-get update
sudo apt-get install \
libsdl2-2.0-0 \
libsdl2-image-2.0-0
- name: Clone DFHack
uses: actions/checkout@v3
with:
@ -117,8 +123,14 @@ jobs:
- name: Install DFHack
shell: bash
run: tar xjf test-${{ matrix.compiler }}.tar.bz2 -C ${{ env.DF_FOLDER }}
- name: Start X server
if: matrix.os == 'ubuntu'
run: Xvfb :0 -screen 0 1600x1200x24 &
- name: Run lua tests
timeout-minutes: 10
env:
DISPLAY: :0
TERM: xterm-256color
run: python ci/run-tests.py --keep-status "${{ env.DF_FOLDER }}"
- name: Check RPC interface
run: python ci/check-rpc.py "${{ env.DF_FOLDER }}/dfhack-rpc.txt"

@ -18,7 +18,7 @@ elif test "$OS_TARGET" = "ubuntu"; then
WGET=wget
df_url="${df_url}_linux.tar.bz2"
df_archive_name="df.tar.bz2"
df_extract_cmd="tar -x -j --strip-components=1 -f"
df_extract_cmd="tar -x -j -C ${DF_FOLDER} -f"
else
echo "Unhandled OS target: ${OS_TARGET}"
exit 1
@ -29,22 +29,25 @@ if ! $WGET -v "$df_url" -O "$df_archive_name"; then
exit 1
fi
md5sum "$df_archive_name"
save_url="https://dffd.bay12games.com/download.php?id=15434&f=dreamfort.7z"
save_archive_name="test_save.7z"
save_extract_cmd="7z x -oDF/save"
save_extract_cmd="7z x -o${DF_FOLDER}/save"
if ! $WGET -v "$save_url" -O "$save_archive_name"; then
echo "Failed to download test save from $save_url"
exit 1
fi
md5sum "$save_archive_name"
echo Extracting
mkdir -p ${DF_FOLDER}
$df_extract_cmd "$df_archive_name"
$save_extract_cmd "$save_archive_name"
mv DF/save/* DF/save/region1
mv ${DF_FOLDER}/save/* ${DF_FOLDER}/save/region1
echo Done
ls -l
md5sum "$df_archive_name" "$save_archive_name"

@ -65,14 +65,12 @@ if not os.path.exists(init_txt_path):
shutil.copyfile(init_txt_path, init_txt_path + '.orig')
with open(init_txt_path) as f:
init_contents = f.read()
init_contents = change_setting(init_contents, 'INTRO', 'NO')
init_contents = change_setting(init_contents, 'SOUND', 'NO')
init_contents = change_setting(init_contents, 'WINDOWED', 'YES')
init_contents = change_setting(init_contents, 'WINDOWEDX', '80')
init_contents = change_setting(init_contents, 'WINDOWEDY', '25')
init_contents = change_setting(init_contents, 'FPS', 'YES')
if args.headless:
init_contents = change_setting(init_contents, 'PRINT_MODE', 'TEXT')
init_contents = change_setting(init_contents, 'WINDOWEDX', '1200')
init_contents = change_setting(init_contents, 'WINDOWEDY', '800')
#if args.headless:
# init_contents = change_setting(init_contents, 'PRINT_MODE', 'TEXT')
init_path = 'dfhack-config/init'
if not os.path.isdir('hack/init'):

@ -2,6 +2,7 @@
--@ module = true
local expect = require('test_util.expect')
local gui = require('gui')
local helpdb = require('helpdb')
local json = require('json')
local mock = require('test_util.mock')
@ -151,33 +152,37 @@ end
test_envvars.require = clean_require
test_envvars.reqscript = clean_reqscript
local function is_title_screen(scr)
scr = scr or dfhack.gui.getCurViewscreen()
return df.viewscreen_titlest:is_instance(scr)
local function is_title_screen()
return dfhack.gui.matchFocusString('title/Default')
end
-- This only handles pre-fortress-load screens. It will time out if the player
-- has already loaded a fortress or is in any screen that can't get to the title
-- screen by sending ESC keys.
local function ensure_title_screen()
local function wait_for(ms, desc, predicate)
local start_ms = dfhack.getTickCount()
local prev_ms = start_ms
while df.viewscreen_initial_prepst:is_instance(dfhack.gui.getCurViewscreen()) do
while not predicate() do
delay(10)
-- wait up to 1 minute for the game to load and show the title screen
local now_ms = dfhack.getTickCount()
if now_ms - start_ms > 60000 then
qerror(('Could not find title screen (timed out at %s)'):format(
dfhack.gui.getCurFocus(true)[1]))
if now_ms - start_ms > ms then
qerror(('%s took too long (timed out at %s)'):format(
desc, dfhack.gui.getCurFocus(true)[1]))
end
if now_ms - prev_ms > 1000 then
print('Waiting for game to load and show title screen...')
print(('Waiting for %s...'):format(desc))
prev_ms = now_ms
end
end
end
-- This only handles pre-fortress-load screens. It will time out if the player
-- has already loaded a fortress or is in any screen that can't get to the title
-- screen by sending ESC keys.
local function ensure_title_screen()
if df.viewscreen_dwarfmodest:is_instance(dfhack.gui.getDFViewscreen(true)) then
qerror('Cannot reach title screen from loaded fort')
end
for i = 1, 100 do
local scr = dfhack.gui.getCurViewscreen()
if is_title_screen(scr) then
if is_title_screen() then
print('Found title screen')
return
end
@ -189,54 +194,94 @@ local function ensure_title_screen()
dfhack.gui.getCurFocus(true)[1]))
end
local function is_fortress(focus_string)
focus_string = focus_string or dfhack.gui.getCurFocus(true)
return focus_string == 'dwarfmode/Default'
local function is_fortress()
return dfhack.gui.matchFocusString('dwarfmode/Default')
end
-- error out if we're not running in a CI environment
-- the tests may corrupt saves, and we don't want to unexpectedly ruin a real player save
-- this heuristic is not perfect, but it should be able to detect most cases
local function ensure_ci_save(scr)
if #scr.savegame_header ~= 1
or #scr.savegame_header_world ~= 1
or not string.find(scr.savegame_header[0].fort_name, 'Dream')
then
qerror('Unexpected test save in slot 0; please manually load a fort for ' ..
'running fortress mode tests. note that tests may alter or corrupt the ' ..
'fort! Do not save after running tests.')
end
end
local function click_top_title_button(scr)
local sw, sh = dfhack.screen.getWindowSize()
df.global.gps.mouse_x = sw // 2
df.global.gps.precise_mouse_x = df.global.gps.mouse_x * df.global.gps.tile_pixel_x
if sh < 60 then
df.global.gps.mouse_y = 25
else
df.global.gps.mouse_y = (sh // 2) + 3
end
df.global.gps.precise_mouse_y = df.global.gps.mouse_y * df.global.gps.tile_pixel_y
df.global.enabler.tracking_on = 1
df.global.enabler.mouse_lbut = 1
df.global.enabler.mouse_lbut_down = 1
gui.simulateInput(scr, '_MOUSE_L')
end
local function load_first_save(scr)
if #scr.savegame_header == 0 then
qerror('no savegames available to load')
end
click_top_title_button(scr)
wait_for(1000, 'world list', function()
return scr.mode == 2
end)
click_top_title_button(scr)
wait_for(1000, 'savegame list', function()
return scr.mode == 3
end)
click_top_title_button(scr)
wait_for(1000, 'loadgame progress bar', function()
return dfhack.gui.matchFocusString('loadgame')
end)
end
-- Requires that a fortress game is already loaded or is ready to be loaded via
-- the "Continue Playing" option in the title screen. Otherwise the function
-- the "Continue active game" option in the title screen. Otherwise the function
-- will time out and/or exit with error.
local function ensure_fortress(config)
local focus_string = dfhack.gui.getCurFocus(true)
for screen_timeout = 1,10 do
if is_fortress(focus_string) then
print('Loaded fortress map')
if is_fortress() then
print('Fortress map is loaded')
-- pause the game (if it's not already paused)
dfhack.gui.resetDwarfmodeView(true)
return
end
local scr = dfhack.gui.getCurViewscreen(true)
if focus_string == 'title' or
focus_string == 'dfhack/lua/load_screen' then
local scr = dfhack.gui.getCurViewscreen()
if dfhack.gui.matchFocusString('title/Default', scr) then
print('Attempting to load the test fortress')
-- TODO: reinstate loading of a specified save dir; for now
-- just load the first possible save, which will at least let us
-- run fortress tests in CI
-- qerror()'s on falure
dfhack.run_script('load-save', config.save_dir)
elseif focus_string ~= 'loadgame' then
-- dfhack.run_script('load-save', config.save_dir)
ensure_ci_save(scr)
load_first_save(scr)
elseif not dfhack.gui.matchFocusString('loadgame', scr) then
-- if we're not actively loading a game, hope we're in
-- a screen where hitting ESC will get us to the game map
-- or the title screen
scr:feed_key(df.interface_key.LEAVESCREEN)
end
-- wait for current screen to change
local prev_focus_string = focus_string
for frame_timeout = 1,100 do
delay(10)
focus_string = dfhack.gui.getCurFocus(true)
if focus_string ~= prev_focus_string then
goto next_screen
end
if frame_timeout % 10 == 0 then
print(string.format(
'Loading fortress (currently at screen: %s)',
focus_string))
end
end
print('Timed out waiting for screen to change')
break
::next_screen::
local prev_focus_string = dfhack.gui.getCurFocus()[1]
wait_for(60000, 'screen change', function()
return dfhack.gui.getCurFocus()[1] ~= prev_focus_string
end)
end
qerror(string.format('Could not load fortress (timed out at %s)',
focus_string))
table.concat(dfhack.gui.getCurFocus(), ' ')))
end
local MODES = {
@ -578,6 +623,10 @@ local function filter_tests(tests, config)
end
local function run_tests(tests, status, counts, config)
wait_for(60000, 'game load', function()
local scr = dfhack.gui.getDFViewscreen()
return not df.viewscreen_initial_prepst:is_instance(scr)
end)
print(('Running %d tests'):format(#tests))
local start_ms = dfhack.getTickCount()
local num_skipped = 0
@ -596,12 +645,13 @@ local function run_tests(tests, status, counts, config)
goto skip
end
end
-- pre-emptively mark the test as failed in case we induce a crash
status[test.full_name] = TestStatus.FAILED
save_test_status(status)
if run_test(test, status, counts) then
status[test.full_name] = TestStatus.PASSED
else
status[test.full_name] = TestStatus.FAILED
save_test_status(status)
end
save_test_status(status)
::skip::
end
local elapsed_ms = dfhack.getTickCount() - start_ms

@ -139,6 +139,7 @@ moversti moversti
mrrho mrrho
Murad Beybalaev Erquint
Myk Taylor myk002
Najeeb Al-Shabibi master-spike
napagokc napagokc
Neil Little nmlittle
Nick Rart nickrart comestible

@ -56,14 +56,23 @@ Template for new versions:
## New Features
## Fixes
- `autolabor`: now unconditionally re-enables vanilla work details when the fort or the plugin is unloaded
- ``dfhack.TranslateName()``: fixed crash on certain invalid names, which affected `warn-starving`
- EventManager: Unit death event no longer misfires on units leaving the map
## Misc Improvements
- `dig`: `digtype` command now has options to choose between designating only visible tiles or hidden tiles, as well as "auto" dig mode. Z-level options adjusted to allow choosing z-levels above, below, or the same as the cursor.
- `hotkeys`: make the DFHack logo brighten on hover in ascii mode to indicate that it is clickable
- `hotkeys`: use vertical bars instead of "!" symbols for the DFHack logo to make it easier to read
- EventManager: guarded against potential iterator invalidation if one of the event listeners modified the global data structure being iterated over
- EventManager: changed firing order of building created and building destroyed events to improve performance in the building location cache.
## Documentation
## API
## Lua
- mouse key events are now aligned with internal DF semantics: ``_MOUSE_L`` indicates that the left mouse button has just been pressed and ``_MOUSE_L_DOWN`` indicates that the left mouse button is being held down. similar for ``_MOUSE_R`` and ``_MOUSE_M``. 3rd party scripts may have to adjust.
## Removed
@ -73,6 +82,9 @@ Template for new versions:
- Linux launcher: allow Steam Overlay and game streaming to function
- `autobutcher`: don't ignore semi-wild units when marking units for slaughter
## Misc Improvements
- 'sort': Improve combat skill scale thresholds
# 50.09-r4
## New Features

@ -2528,10 +2528,10 @@ Supported callbacks and fields are:
Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience.
``_MOUSE_L, _MOUSE_R, _MOUSE_M``
If the left, right, and/or middle mouse button is being pressed.
If the left, right, and/or middle mouse button was just pressed.
``_MOUSE_L_DOWN, _MOUSE_R_DOWN, _MOUSE_M_DOWN``
If the left, right, and/or middle mouse button was just pressed.
If the left, right, and/or middle mouse button is being held down.
If this method is omitted, the screen is dismissed on reception of the ``LEAVESCREEN`` key.
@ -2594,11 +2594,18 @@ invalidates the ``texpos`` value that used to point to that texture.
The ``textures`` module solves this problem by providing a stable handle instead of a
raw ``texpos``. When we need to draw a particular tile, we can look up the current
``texpos`` value via the handle.
Texture module can register textures in two ways: to reserved and dynamic ranges.
Reserved range is a limit buffer in a game texture vector, that will never be wiped.
It is good for static assets, which need to be loaded at the very beginning and will be used during the process running.
In other cases, it is better to use dynamic range.
If reserved range buffer limit has been reached, dynamic range will be used by default.
* ``loadTileset(file, tile_px_w, tile_px_h)``
* ``loadTileset(file, tile_px_w, tile_px_h[, reserved])``
Loads a tileset from the image ``file`` with give tile dimensions in pixels. The
image will be sliced in row major order. Returns an array of ``TexposHandle``.
``reserved`` is optional boolean argument, which indicates texpos range.
``true`` - reserved, ``false`` - dynamic (default).
Example usage::
@ -2611,18 +2618,22 @@ raw ``texpos``. When we need to draw a particular tile, we can look up the curre
get the ``texpos`` for your texture. ``texpos`` can change when game textures are
reset, but the handle will be the same.
* ``createTile(pixels, tile_px_w, tile_px_h)``
* ``createTile(pixels, tile_px_w, tile_px_h[, reserved])``
Create and register a new texture with the given tile dimensions and an array of
``pixels`` in row major order. Each pixel is an integer representing color in packed
RBGA format (for example, #0022FF11). Returns a ``TexposHandle``.
``reserved`` is optional boolean argument, which indicates texpos range.
``true`` - reserved, ``false`` - dynamic (default).
* ``createTileset(pixels, texture_px_w, texture_px_h, tile_px_w, tile_px_h)``
* ``createTileset(pixels, texture_px_w, texture_px_h, tile_px_w, tile_px_h[, reserved])``
Create and register a new texture with the given texture dimensions and an array of
``pixels`` in row major order. Then slice it into tiles with the given tile
dimensions. Each pixel is an integer representing color in packed RBGA format (for
example #0022FF11). Returns an array of ``TexposHandle``.
``reserved`` is optional boolean argument, which indicates texpos range.
``true`` - reserved, ``false`` - dynamic (default).
* ``deleteHandle(handle)``

@ -50,7 +50,7 @@ Usage
Designate circles. The diameter is the number of tiles across the center of
the circle that you want to dig. See the `digcircle`_ section below for
options.
``digtype [<designation>] [-p<number>] [-z]``
``digtype [<designation>] [-p<number>] [--zup|-u] [--zdown|-zu] [--cur-zlevel|-z] [--hidden|-h] [--no-auto|-a]``
Designate all vein tiles of the same type as the selected tile. See the
`digtype`_ section below for options.
``digexp [<pattern>] [<filter>] [-p<number>]``
@ -119,9 +119,11 @@ the last selected parameters.
digtype
-------
For every tile on the map of the same vein type as the selected tile, this
command designates it to have the same designation as the selected tile. If the
selected tile has no designation, they will be dig designated.
For every tile on the map of the same vein type as the selected tile, this command
designates it to have the same designation as the selected tile. If the selected
tile has no designation, they will be dig designated. By default, only designates
visible tiles, and in the case of dig designation, applies automatic mining to them
(designates uncovered neighbouring tiles of the same type to be dug).
If an argument is given, the designation of the selected tile is ignored, and
all appropriate tiles are set to the specified designation.
@ -143,9 +145,18 @@ Designation options:
``clear``
Clear any designations.
You can also pass a ``-z`` option, which restricts designations to the current
z-level and down. This is useful when you don't want to designate tiles on the
same z-levels as your carefully dug fort above.
Other options:
``--zdown``, ``-d``
Only designates tiles on the cursor's z-level and below.
``--zup``, ``-u``
Only designates tiles on the cursor's z-level and above.
``--cur-zlevel``, ``-z``
Only designates tiles on the same z-level as the cursor.
``--hidden``, ``-h``
Allows designation of hidden tiles, and picking a hidden tile as the target type.
``--no-auto``, ``-a``
No automatic mining mode designation - useful if you want to avoid dwarves digging where you don't want them.
digexp
------

@ -1459,12 +1459,9 @@ void Core::fatal (std::string output)
con.print("\n");
}
fprintf(stderr, "%s\n", out.str().c_str());
#ifndef LINUX_BUILD
out << "Check file stderr.log for details\n";
MessageBox(0,out.str().c_str(),"DFHack error!", MB_OK | MB_ICONERROR);
#else
out << "Check file stderr.log for details.\n";
std::cout << "DFHack fatal error: " << out.str() << std::endl;
#endif
DFSDL::DFSDL_ShowSimpleMessageBox(0x10 /* SDL_MESSAGEBOX_ERROR */, "DFHack error!", out.str().c_str(), NULL);
bool is_headless = bool(getenv("DFHACK_HEADLESS"));
if (is_headless)
@ -1506,6 +1503,12 @@ bool Core::InitMainThread() {
std::cerr << "DFHack build: " << Version::git_description() << "\n"
<< "Starting with working directory: " << Filesystem::getcwd() << std::endl;
std::cerr << "Binding to SDL.\n";
if (!DFSDL::init(con)) {
fatal("cannot bind SDL libraries");
return false;
}
// find out what we are...
#ifdef LINUX_BUILD
const char * path = "hack/symbols.xml";
@ -1692,11 +1695,6 @@ bool Core::InitSimulationThread()
return false;
}
std::cerr << "Binding to SDL.\n";
if (!DFSDL::init(con)) {
fatal("cannot bind SDL libraries");
return false;
}
if (DFSteam::init(con)) {
std::cerr << "Found Steam.\n";
DFSteam::launchSteamDFHackIfNecessary(con);

@ -1760,7 +1760,8 @@ 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);
bool reserved = lua_isboolean(state, 4) ? lua_toboolean(state, 4) : false;
auto handles = Textures::loadTileset(file, tile_w, tile_h, reserved);
Lua::PushVector(state, handles);
return 1;
}
@ -1798,7 +1799,8 @@ static int textures_createTile(lua_State *state)
Lua::GetVector(state, pixels);
auto tile_w = luaL_checkint(state, 2);
auto tile_h = luaL_checkint(state, 3);
auto handle = Textures::createTile(pixels, tile_w, tile_h);
bool reserved = lua_isboolean(state, 4) ? lua_toboolean(state, 4) : false;
auto handle = Textures::createTile(pixels, tile_w, tile_h, reserved);
Lua::Push(state, handle);
return 1;
}
@ -1811,7 +1813,8 @@ static int textures_createTileset(lua_State *state)
auto texture_h = luaL_checkint(state, 3);
auto tile_w = luaL_checkint(state, 4);
auto tile_h = luaL_checkint(state, 5);
auto handles = Textures::createTileset(pixels, texture_w, texture_h, tile_w, tile_h);
bool reserved = lua_isboolean(state, 6) ? lua_toboolean(state, 6) : false;
auto handles = Textures::createTileset(pixels, texture_w, texture_h, tile_w, tile_h, reserved);
Lua::PushVector(state, handles);
return 1;
}

@ -131,12 +131,12 @@ void DFHack::Lua::GetVector(lua_State *state, std::vector<std::string> &pvec, in
}
}
static bool trigger_inhibit_l_down = false;
static bool trigger_inhibit_r_down = false;
static bool trigger_inhibit_m_down = false;
static bool inhibit_l_down = false;
static bool inhibit_r_down = false;
static bool inhibit_m_down = false;
static bool trigger_inhibit_l = false;
static bool trigger_inhibit_r = false;
static bool trigger_inhibit_m = false;
static bool inhibit_l = false;
static bool inhibit_r = false;
static bool inhibit_m = false;
void DFHack::Lua::PushInterfaceKeys(lua_State *L,
const std::set<df::interface_key> &keys) {
@ -161,32 +161,32 @@ void DFHack::Lua::PushInterfaceKeys(lua_State *L,
}
if (df::global::enabler) {
if (!inhibit_l_down && df::global::enabler->mouse_lbut_down) {
if (!inhibit_l && df::global::enabler->mouse_lbut) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_L_DOWN");
trigger_inhibit_l_down = true;
lua_setfield(L, -2, "_MOUSE_L");
trigger_inhibit_l = true;
}
if (!inhibit_r_down && df::global::enabler->mouse_rbut_down) {
if (!inhibit_r && df::global::enabler->mouse_rbut) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_R_DOWN");
trigger_inhibit_r_down = true;
lua_setfield(L, -2, "_MOUSE_R");
trigger_inhibit_r = true;
}
if (!inhibit_m_down && df::global::enabler->mouse_mbut_down) {
if (!inhibit_m && df::global::enabler->mouse_mbut) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_M_DOWN");
trigger_inhibit_m_down = true;
lua_setfield(L, -2, "_MOUSE_M");
trigger_inhibit_m = true;
}
if (df::global::enabler->mouse_lbut) {
if (df::global::enabler->mouse_lbut_down) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_L");
lua_setfield(L, -2, "_MOUSE_L_DOWN");
}
if (df::global::enabler->mouse_rbut) {
if (df::global::enabler->mouse_rbut_down) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_R");
lua_setfield(L, -2, "_MOUSE_R_DOWN");
}
if (df::global::enabler->mouse_mbut) {
if (df::global::enabler->mouse_mbut_down) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_M");
lua_setfield(L, -2, "_MOUSE_M_DOWN");
}
}
}
@ -2159,25 +2159,25 @@ void DFHack::Lua::Core::Reset(color_ostream &out, const char *where)
lua_settop(State, 0);
}
if (trigger_inhibit_l_down) {
trigger_inhibit_l_down = false;
inhibit_l_down = true;
if (trigger_inhibit_l) {
trigger_inhibit_l = false;
inhibit_l = true;
}
if (trigger_inhibit_r_down) {
trigger_inhibit_r_down = false;
inhibit_r_down = true;
if (trigger_inhibit_r) {
trigger_inhibit_r = false;
inhibit_r = true;
}
if (trigger_inhibit_m_down) {
trigger_inhibit_m_down = false;
inhibit_m_down = true;
if (trigger_inhibit_m) {
trigger_inhibit_m = false;
inhibit_m = true;
}
if (df::global::enabler) {
if (!df::global::enabler->mouse_lbut)
inhibit_l_down = false;
if (!df::global::enabler->mouse_rbut)
inhibit_r_down = false;
if (!df::global::enabler->mouse_mbut)
inhibit_m_down = false;
if (!df::global::enabler->mouse_lbut_down)
inhibit_l = false;
if (!df::global::enabler->mouse_rbut_down)
inhibit_r = false;
if (!df::global::enabler->mouse_mbut_down)
inhibit_m = false;
}
}

@ -6,6 +6,7 @@
struct SDL_Surface;
struct SDL_Rect;
struct SDL_PixelFormat;
struct SDL_Window;
union SDL_Event;
namespace DFHack
@ -50,6 +51,7 @@ DFHACK_EXPORT int DFSDL_PushEvent(SDL_Event *event);
DFHACK_EXPORT void DFSDL_free(void *ptr);
DFHACK_EXPORT SDL_PixelFormat* DFSDL_AllocFormat(uint32_t pixel_format);
DFHACK_EXPORT SDL_Surface* DFSDL_CreateRGBSurfaceWithFormat(uint32_t flags, int width, int height, int depth, uint32_t format);
DFHACK_EXPORT int DFSDL_ShowSimpleMessageBox(uint32_t flags, const char *title, const char *message, SDL_Window *window);
// submitted and returned text is UTF-8
// see wrapper functions below for cp-437 variants

@ -26,7 +26,7 @@ const uint32_t TILE_HEIGHT_PX = 12;
* Load texture and get handle.
* Keep it to obtain valid texpos.
*/
DFHACK_EXPORT TexposHandle loadTexture(SDL_Surface* surface);
DFHACK_EXPORT TexposHandle loadTexture(SDL_Surface* surface, bool reserved = false);
/**
* Load tileset from image file.
@ -34,7 +34,8 @@ DFHACK_EXPORT TexposHandle loadTexture(SDL_Surface* surface);
*/
DFHACK_EXPORT std::vector<TexposHandle> loadTileset(const std::string& file,
int tile_px_w = TILE_WIDTH_PX,
int tile_px_h = TILE_HEIGHT_PX);
int tile_px_h = TILE_HEIGHT_PX,
bool reserved = false);
/**
* Get texpos by handle.
@ -53,7 +54,7 @@ DFHACK_EXPORT void deleteHandle(TexposHandle handle);
* Register this texture and return TexposHandle.
*/
DFHACK_EXPORT TexposHandle createTile(std::vector<uint32_t>& pixels, int tile_px_w = TILE_WIDTH_PX,
int tile_px_h = TILE_HEIGHT_PX);
int tile_px_h = TILE_HEIGHT_PX, bool reserved = false);
/**
* Create new textures as tileset with RGBA32 format and pixels as data in row major order.
@ -62,7 +63,8 @@ DFHACK_EXPORT TexposHandle createTile(std::vector<uint32_t>& pixels, int tile_px
DFHACK_EXPORT std::vector<TexposHandle> createTileset(std::vector<uint32_t>& pixels,
int texture_px_w, int texture_px_h,
int tile_px_w = TILE_WIDTH_PX,
int tile_px_h = TILE_HEIGHT_PX);
int tile_px_h = TILE_HEIGHT_PX,
bool reserved = false);
/**
* Call this on DFHack init just once to setup interposed handlers and

@ -696,16 +696,12 @@ end
DEFAULT_INITIAL_PAUSE = true
local zscreen_inhibit_mouse_l = false
-- ensure underlying DF screens don't also react to handled clicks
function markMouseClicksHandled(keys)
if keys._MOUSE_L_DOWN then
-- note we can't clear mouse_lbut here. otherwise we break dragging,
df.global.enabler.mouse_lbut_down = 0
zscreen_inhibit_mouse_l = true
if keys._MOUSE_L then
df.global.enabler.mouse_lbut = 0
end
if keys._MOUSE_R_DOWN then
if keys._MOUSE_R then
df.global.enabler.mouse_rbut_down = 0
df.global.enabler.mouse_rbut = 0
end
@ -789,7 +785,7 @@ function ZScreen:onInput(keys)
local has_mouse = self:isMouseOver()
if not self:hasFocus() then
if has_mouse and
(keys._MOUSE_L_DOWN or keys._MOUSE_R_DOWN or
(keys._MOUSE_L or keys._MOUSE_R or
keys.CONTEXT_SCROLL_UP or keys.CONTEXT_SCROLL_DOWN or
keys.CONTEXT_SCROLL_PAGEUP or keys.CONTEXT_SCROLL_PAGEDOWN) then
self:raise()
@ -804,22 +800,15 @@ function ZScreen:onInput(keys)
return
end
if self.pass_mouse_clicks and keys._MOUSE_L_DOWN and not has_mouse then
if self.pass_mouse_clicks and keys._MOUSE_L and not has_mouse then
self.defocused = self.defocusable
self:sendInputToParent(keys)
return
elseif keys.LEAVESCREEN or keys._MOUSE_R_DOWN then
elseif keys.LEAVESCREEN or keys._MOUSE_R then
self:dismiss()
markMouseClicksHandled(keys)
return
else
if zscreen_inhibit_mouse_l then
if keys._MOUSE_L then
return
else
zscreen_inhibit_mouse_l = false
end
end
local passit = self.pass_pause and keys.D_PAUSE
if not passit and self.pass_mouse_clicks then
if keys.CONTEXT_SCROLL_UP or keys.CONTEXT_SCROLL_DOWN or

@ -57,11 +57,11 @@ function MessageBox:onDestroy()
end
function MessageBox:onInput(keys)
if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R_DOWN then
if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R then
self:dismiss()
if keys.SELECT and self.on_accept then
self.on_accept()
elseif (keys.LEAVESCREEN or keys._MOUSE_R_DOWN) and self.on_cancel then
elseif (keys.LEAVESCREEN or keys._MOUSE_R) and self.on_cancel then
self.on_cancel()
end
gui.markMouseClicksHandled(keys)
@ -129,7 +129,7 @@ function InputBox:onInput(keys)
self.on_input(self.subviews.edit.text)
end
return true
elseif keys.LEAVESCREEN or keys._MOUSE_R_DOWN then
elseif keys.LEAVESCREEN or keys._MOUSE_R then
self:dismiss()
if self.on_cancel then
self.on_cancel()
@ -231,7 +231,7 @@ function ListBox:getWantedFrameSize()
end
function ListBox:onInput(keys)
if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then
if keys.LEAVESCREEN or keys._MOUSE_R then
self:dismiss()
if self.on_cancel then
self.on_cancel()

@ -6,18 +6,18 @@ local _ENV = mkmodule('gui.textures')
-- Preloaded DFHack Assets.
-- Use this handles if you need to get dfhack standard textures.
---@type table<string, TexposHandle>
---@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),
green_pin = dfhack.textures.loadTileset('hack/data/art/green-pin.png', 8, 12, true),
red_pin = dfhack.textures.loadTileset('hack/data/art/red-pin.png', 8, 12, true),
icons = dfhack.textures.loadTileset('hack/data/art/icons.png', 8, 12, true),
on_off = dfhack.textures.loadTileset('hack/data/art/on-off.png', 8, 12, true),
control_panel = dfhack.textures.loadTileset('hack/data/art/control-panel.png', 8, 12, true),
border_thin = dfhack.textures.loadTileset('hack/data/art/border-thin.png', 8, 12, true),
border_medium = dfhack.textures.loadTileset('hack/data/art/border-medium.png', 8, 12, true),
border_bold = dfhack.textures.loadTileset('hack/data/art/border-bold.png', 8, 12, true),
border_panel = dfhack.textures.loadTileset('hack/data/art/border-panel.png', 8, 12, true),
border_window = dfhack.textures.loadTileset('hack/data/art/border-window.png', 8, 12, true),
}
-- Get valid texpos for preloaded texture in tileset

@ -273,7 +273,7 @@ end
function Panel:onInput(keys)
if self.kbd_get_pos then
if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R_DOWN then
if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R then
Panel_end_drag(self, not keys.SELECT and self.saved_frame or nil,
not not keys.SELECT)
return true
@ -281,7 +281,6 @@ function Panel:onInput(keys)
for code in pairs(keys) do
local dx, dy = guidm.get_movement_delta(code, 1, 10)
if dx then
local frame_rect = self.frame_rect
local kbd_pos = self.kbd_get_pos()
kbd_pos.x = kbd_pos.x + dx
kbd_pos.y = kbd_pos.y + dy
@ -292,9 +291,9 @@ function Panel:onInput(keys)
return
end
if self.drag_offset then
if keys._MOUSE_R_DOWN then
if keys._MOUSE_R then
Panel_end_drag(self, self.saved_frame)
elseif keys._MOUSE_L then
elseif keys._MOUSE_L_DOWN then
Panel_update_frame(self, Panel_make_frame(self))
end
return true
@ -302,7 +301,7 @@ function Panel:onInput(keys)
if Panel.super.onInput(self, keys) then
return true
end
if not keys._MOUSE_L_DOWN then return end
if not keys._MOUSE_L then return end
local x,y = self:getMouseFramePos()
if not x then return end
@ -489,7 +488,7 @@ function Panel:onRenderFrame(dc, rect)
dc:seek(pos.x, pos.y):pen(pen):char(string.char(0xDB))
end
if self.drag_offset and not self.kbd_get_pos
and df.global.enabler.mouse_lbut == 0 then
and df.global.enabler.mouse_lbut_down == 0 then
Panel_end_drag(self, nil, true)
end
end
@ -718,7 +717,7 @@ function EditField:onInput(keys)
end
end
if self.key and (keys.LEAVESCREEN or keys._MOUSE_R_DOWN) then
if self.key and (keys.LEAVESCREEN or keys._MOUSE_R) then
self:setText(self.saved_text)
self:setFocus(false)
return true
@ -740,8 +739,8 @@ function EditField:onInput(keys)
end
end
return not not self.key
elseif keys._MOUSE_L then
local mouse_x, mouse_y = self:getMousePos()
elseif keys._MOUSE_L_DOWN then
local mouse_x = self:getMousePos()
if mouse_x then
self:setCursor(self.start_pos + mouse_x - (self.text_offset or 0))
return true
@ -986,7 +985,7 @@ function Scrollbar:onRenderBody(dc)
if self.is_dragging then
scrollbar_do_drag(self)
end
if df.global.enabler.mouse_lbut == 0 then
if df.global.enabler.mouse_lbut_down == 0 then
self.last_scroll_ms = 0
self.is_dragging = false
self.scroll_spec = nil
@ -1023,7 +1022,7 @@ function Scrollbar:onInput(keys)
return true
end
end
if not keys._MOUSE_L_DOWN then return false end
if not keys._MOUSE_L then return false end
local _,y = self:getMousePos()
if not y then return false end
local scroll_spec = nil
@ -1386,11 +1385,11 @@ function Label:onInput(keys)
if self:inputToSubviews(keys) then
return true
end
if keys._MOUSE_L_DOWN and self:getMousePos() and self.on_click then
if keys._MOUSE_L and self:getMousePos() and self.on_click then
self.on_click()
return true
end
if keys._MOUSE_R_DOWN and self:getMousePos() and self.on_rclick then
if keys._MOUSE_R and self:getMousePos() and self.on_rclick then
self.on_rclick()
return true
end
@ -1498,7 +1497,7 @@ end
function HotkeyLabel:onInput(keys)
if HotkeyLabel.super.onInput(self, keys) then
return true
elseif keys._MOUSE_L_DOWN and self:getMousePos() and self.on_activate
elseif keys._MOUSE_L and self:getMousePos() and self.on_activate
and not is_disabled(self) then
self.on_activate()
return true
@ -1658,7 +1657,7 @@ end
function CycleHotkeyLabel:onInput(keys)
if CycleHotkeyLabel.super.onInput(self, keys) then
return true
elseif keys._MOUSE_L_DOWN and self:getMousePos() and not is_disabled(self) then
elseif keys._MOUSE_L and self:getMousePos() and not is_disabled(self) then
self:cycle()
return true
end
@ -1962,7 +1961,7 @@ function List:onInput(keys)
return self:submit()
elseif keys.CUSTOM_SHIFT_ENTER then
return self:submit2()
elseif keys._MOUSE_L_DOWN then
elseif keys._MOUSE_L then
local idx = self:getIdxUnderMouse()
if idx then
local now_ms = dfhack.getTickCount()
@ -2317,7 +2316,7 @@ end
function Tab:onInput(keys)
if Tab.super.onInput(self, keys) then return true end
if keys._MOUSE_L_DOWN and self:getMousePos() then
if keys._MOUSE_L and self:getMousePos() then
self.on_select(self.id)
return true
end
@ -2419,7 +2418,7 @@ local function rangeslider_get_width_per_idx(self)
end
function RangeSlider:onInput(keys)
if not keys._MOUSE_L_DOWN then return false end
if not keys._MOUSE_L then return false end
local x = self:getMousePos()
if not x then return false end
local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn()
@ -2527,7 +2526,7 @@ function RangeSlider:onRenderBody(dc, rect)
if self.is_dragging_target then
rangeslider_do_drag(self, width_per_idx)
end
if df.global.enabler.mouse_lbut == 0 then
if df.global.enabler.mouse_lbut_down == 0 then
self.is_dragging_target = nil
self.is_dragging_idx = nil
end

@ -43,6 +43,7 @@ char * (*g_SDL_GetClipboardText)();
void (*g_SDL_free)(void *);
SDL_PixelFormat* (*g_SDL_AllocFormat)(uint32_t pixel_format) = nullptr;
SDL_Surface* (*g_SDL_CreateRGBSurfaceWithFormat)(uint32_t flags, int width, int height, int depth, uint32_t format) = nullptr;
int (*g_SDL_ShowSimpleMessageBox)(uint32_t flags, const char *title, const char *message, SDL_Window *window) = nullptr;
bool DFSDL::init(color_ostream &out) {
for (auto &lib_str : SDL_LIBS) {
@ -85,6 +86,7 @@ bool DFSDL::init(color_ostream &out) {
bind(g_sdl_handle, SDL_free);
bind(g_sdl_handle, SDL_AllocFormat);
bind(g_sdl_handle, SDL_CreateRGBSurfaceWithFormat);
bind(g_sdl_handle, SDL_ShowSimpleMessageBox);
#undef bind
DEBUG(dfsdl,out).print("sdl successfully loaded\n");
@ -159,6 +161,11 @@ SDL_Surface* DFSDL::DFSDL_CreateRGBSurfaceWithFormat(uint32_t flags, int width,
return g_SDL_CreateRGBSurfaceWithFormat(flags, width, height, depth, format);
}
int DFSDL::DFSDL_ShowSimpleMessageBox(uint32_t flags, const char *title, const char *message, SDL_Window *window) {
if (!g_SDL_ShowSimpleMessageBox)
return -1;
return g_SDL_ShowSimpleMessageBox(flags, title, message, window);
}
DFHACK_EXPORT std::string DFHack::getClipboardTextCp437() {
if (!g_sdl_handle || g_SDL_HasClipboardText() != SDL_TRUE)

@ -43,6 +43,7 @@
#include <unordered_map>
#include <unordered_set>
#include <array>
#include <utility>
namespace DFHack {
DBG_DECLARE(eventmanager, log, DebugCategory::LINFO);
@ -246,6 +247,15 @@ static int32_t reportToRelevantUnitsTime = -1;
//interaction
static int32_t lastReportInteraction;
struct hash_pair {
template<typename A, typename B>
size_t operator()(const std::pair<A,B>& p) const {
auto h1 = std::hash<A>{}(p.first);
auto h2 = std::hash<B>{}(p.second);
return h1 ^ (h2 << 1);
}
};
void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) {
static bool doOnce = false;
// const string eventNames[] = {"world loaded", "world unloaded", "map loaded", "map unloaded", "viewscreen changed", "core initialized", "begin unload", "paused", "unpaused"};
@ -276,9 +286,9 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event
gameLoaded = false;
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNLOAD].begin(), handlers[EventType::UNLOAD].end());
for (auto &key_value : copy) {
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for map unloaded state change event\n");
key_value.second.eventHandler(out, nullptr);
handle.eventHandler(out, nullptr);
}
} else if ( event == DFHack::SC_MAP_LOADED ) {
/*
@ -375,8 +385,7 @@ void DFHack::EventManager::manageEvents(color_ostream& out) {
continue;
int32_t eventFrequency = -100;
if ( a != EventType::TICK )
for (auto &key_value : handlers[a]) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : handlers[a]) {
if (handle.freq < eventFrequency || eventFrequency == -100 )
eventFrequency = handle.freq;
}
@ -439,8 +448,7 @@ static void manageJobInitiatedEvent(color_ostream& out) {
continue;
if ( link->item->id <= lastJobId )
continue;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for job initiated event\n");
handle.eventHandler(out, (void*)link->item);
}
@ -455,17 +463,24 @@ static void manageJobStartedEvent(color_ostream& out) {
static unordered_set<int32_t> startedJobs;
vector<df::job*> new_started_jobs;
// iterate event handler callbacks
multimap<Plugin*, EventHandler> copy(handlers[EventType::JOB_STARTED].begin(), handlers[EventType::JOB_STARTED].end());
for (df::job_list_link* link = df::global::world->jobs.list.next; link != nullptr; link = link->next) {
df::job* job = link->item;
for (df::job_list_link* link = &df::global::world->jobs.list; link->next != nullptr; link = link->next) {
df::job* job = link->next->item;
int32_t j_id = job->id;
if (job && Job::getWorker(job) && !startedJobs.count(job->id)) {
startedJobs.emplace(job->id);
for (auto &key_value : copy) {
auto &handler = key_value.second;
for (auto &[_,handle] : copy) {
// the jobs must have a worker to start
DEBUG(log,out).print("calling handler for job started event\n");
handler.eventHandler(out, job);
handle.eventHandler(out, job);
}
}
if (link->next == nullptr || link->next->item->id != j_id) {
if ( Once::doOnce("EventManager jobstarted job removed") ) {
out.print("%s,%d: job %u removed from jobs linked list\n", __FILE__, __LINE__, j_id);
}
}
}
@ -487,11 +502,11 @@ static void manageJobCompletedEvent(color_ostream& out) {
int32_t tick1 = df::global::world->frame_counter;
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end());
map<int32_t, df::job*> nowJobs;
unordered_map<int32_t, df::job*> nowJobs;
for ( df::job_list_link* link = &df::global::world->jobs.list; link != nullptr; link = link->next ) {
if ( link->item == nullptr )
continue;
nowJobs[link->item->id] = link->item;
nowJobs.emplace(link->item->id, link->item);
}
#if 0
@ -573,10 +588,9 @@ static void manageJobCompletedEvent(color_ostream& out) {
continue;
//still false positive if cancelled at EXACTLY the right time, but experiments show this doesn't happen
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for repeated job completed event\n");
handle.eventHandler(out, (void*)&job0);
handle.eventHandler(out, (void*) &job0);
}
continue;
}
@ -586,28 +600,27 @@ static void manageJobCompletedEvent(color_ostream& out) {
if ( job0.flags.bits.repeat || job0.completion_timer != 0 )
continue;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for job completed event\n");
handle.eventHandler(out, (void*)&job0);
handle.eventHandler(out, (void*) &job0);
}
}
//erase old jobs, copy over possibly altered jobs
for (auto &prevJob : prevJobs) {
Job::deleteJobStruct(prevJob.second, true);
for (auto &[_,prev_job] : prevJobs) {
Job::deleteJobStruct(prev_job, true);
}
prevJobs.clear();
//create new jobs
for (auto &nowJob : nowJobs) {
for (auto &[_,now_job] : nowJobs) {
/*map<int32_t, df::job*>::iterator i = prevJobs.find((*j).first);
if ( i != prevJobs.end() ) {
continue;
}*/
df::job* newJob = Job::cloneJobStruct(nowJob.second, true);
prevJobs[newJob->id] = newJob;
df::job* newJob = Job::cloneJobStruct(now_job, true);
prevJobs.emplace(newJob->id, newJob);
}
}
@ -617,15 +630,17 @@ static void manageNewUnitActiveEvent(color_ostream& out) {
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_NEW_ACTIVE].begin(), handlers[EventType::UNIT_NEW_ACTIVE].end());
// iterate event handler callbacks
for (auto &key_value : copy) {
auto &handler = key_value.second;
for (df::unit* unit : df::global::world->units.active) {
int32_t id = unit->id;
if (!activeUnits.count(id)) {
activeUnits.emplace(id);
DEBUG(log,out).print("calling handler for new unit event\n");
handler.eventHandler(out, (void*) intptr_t(id)); // intptr_t() avoids cast from smaller type warning
}
vector<int32_t> new_active_unit_ids;
for (df::unit* unit : df::global::world->units.active) {
if (!activeUnits.count(unit->id)) {
activeUnits.emplace(unit->id);
new_active_unit_ids.emplace_back(unit->id);
}
}
for (int32_t unit_id : new_active_unit_ids) {
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for new unit event\n");
handle.eventHandler(out, (void*) intptr_t(unit_id)); // intptr_t() avoids cast from smaller type warning
}
}
}
@ -635,22 +650,26 @@ static void manageUnitDeathEvent(color_ostream& out) {
if (!df::global::world)
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end());
vector<int32_t> dead_unit_ids;
for (auto unit : df::global::world->units.all) {
//if ( unit->counters.death_id == -1 ) {
if ( Units::isActive(unit) ) {
livingUnits.insert(unit->id);
continue;
}
if (!Units::isDead(unit)) continue; // for units that have left the map but aren't dead
//dead: if dead since last check, trigger events
if ( livingUnits.find(unit->id) == livingUnits.end() )
continue;
livingUnits.erase(unit->id);
dead_unit_ids.emplace_back(unit->id);
}
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (int32_t unit_id : dead_unit_ids) {
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit death event\n");
handle.eventHandler(out, (void*)intptr_t(unit->id));
handle.eventHandler(out, (void*)intptr_t(unit_id));
}
livingUnits.erase(unit->id);
}
}
@ -666,6 +685,8 @@ static void manageItemCreationEvent(color_ostream& out) {
multimap<Plugin*,EventHandler> copy(handlers[EventType::ITEM_CREATED].begin(), handlers[EventType::ITEM_CREATED].end());
size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false);
if ( index != 0 ) index--;
std::vector<int32_t> created_items;
for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) {
df::item* item = df::global::world->items.all[a];
//already processed
@ -683,12 +704,17 @@ static void manageItemCreationEvent(color_ostream& out) {
//spider webs don't count
if ( item->flags.bits.spider_web )
continue;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
created_items.push_back(item->id);
}
// handle all created items
for (int32_t item_id : created_items) {
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for item created event\n");
handle.eventHandler(out, (void*)intptr_t(item->id));
handle.eventHandler(out, (void*)intptr_t(item_id));
}
}
nextItem = *df::global::item_next_id;
}
@ -703,6 +729,7 @@ static void manageBuildingEvent(color_ostream& out) {
**/
multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end());
//first alert people about new buildings
vector<int32_t> new_buildings;
for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) {
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a);
if ( index == -1 ) {
@ -711,30 +738,34 @@ static void manageBuildingEvent(color_ostream& out) {
continue;
}
buildings.insert(a);
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for created building event\n");
handle.eventHandler(out, (void*)intptr_t(a));
}
new_buildings.emplace_back(a);
}
nextBuilding = *df::global::building_next_id;
//now alert people about destroyed buildings
for ( auto a = buildings.begin(); a != buildings.end(); ) {
int32_t id = *a;
for ( auto it = buildings.begin(); it != buildings.end(); ) {
int32_t id = *it;
int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id);
if ( index != -1 ) {
a++;
++it;
continue;
}
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for destroyed building event\n");
handle.eventHandler(out, (void*)intptr_t(id));
}
a = buildings.erase(a);
it = buildings.erase(it);
}
//alert people about newly created buildings
std::for_each(new_buildings.begin(), new_buildings.end(), [&](int32_t building){
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for created building event\n");
handle.eventHandler(out, (void*)intptr_t(building));
}
});
}
static void manageConstructionEvent(color_ostream& out) {
@ -743,35 +774,41 @@ static void manageConstructionEvent(color_ostream& out) {
//unordered_set<df::construction*> constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end());
multimap<Plugin*, EventHandler> copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end());
// find & send construction removals
for (auto iter = constructions.begin(); iter != constructions.end();) {
auto &construction = *iter;
// if we can't find it, it was removed
if (df::construction::find(construction.pos) != nullptr) {
++iter;
continue;
unordered_set<df::construction> next_construction_set; // will be swapped with constructions
next_construction_set.reserve(constructions.bucket_count());
vector<df::construction> new_constructions;
// find new constructions - swapping found constructions over from constructions to next_construction_set
for (auto c : df::global::world->constructions) {
auto &construction = *c;
auto it = constructions.find(construction);
if (it == constructions.end()) {
// handle new construction event later
new_constructions.emplace_back(construction);
}
else {
constructions.erase(it);
}
// send construction to handlers, because it was removed
for (const auto &key_value: copy) {
EventHandler handle = key_value.second;
next_construction_set.emplace(construction);
}
constructions.swap(next_construction_set);
// now next_construction_set contains all the constructions that were removed (not found in df::global::world->constructions)
for (auto& construction : next_construction_set) {
// handle construction removed event
for (const auto &[_,handle]: copy) {
DEBUG(log,out).print("calling handler for destroyed construction event\n");
handle.eventHandler(out, (void*) &construction);
}
// erase from existent constructions
iter = constructions.erase(iter);
}
// find & send construction additions
for (auto c: df::global::world->constructions) {
auto &construction = *c;
// add construction to constructions, if it isn't already present
if (constructions.emplace(construction).second) {
// send construction to handlers, because it is new
for (const auto &key_value: copy) {
EventHandler handle = key_value.second;
DEBUG(log,out).print("calling handler for created construction event\n");
handle.eventHandler(out, (void*) &construction);
}
// now handle all the new constructions
for (auto& construction : new_constructions) {
for (const auto &[_,handle]: copy) {
DEBUG(log,out).print("calling handler for created construction event\n");
handle.eventHandler(out, (void*) &construction);
}
}
}
@ -781,6 +818,8 @@ static void manageSyndromeEvent(color_ostream& out) {
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end());
int32_t highestTime = -1;
std::vector<SyndromeData> new_syndrome_data;
for (auto unit : df::global::world->units.all) {
/*
@ -795,14 +834,16 @@ static void manageSyndromeEvent(color_ostream& out) {
if ( startTime <= lastSyndromeTime )
continue;
SyndromeData data(unit->id, b);
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for syndrome event\n");
handle.eventHandler(out, (void*)&data);
}
new_syndrome_data.emplace_back(unit->id, b);
}
}
for (auto& data : new_syndrome_data) {
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for syndrome event\n");
handle.eventHandler(out, (void*)&data);
}
}
lastSyndromeTime = highestTime;
}
@ -815,8 +856,7 @@ static void manageInvasionEvent(color_ostream& out) {
return;
nextInvasion = df::global::plotinfo->invasions.next_id;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for invasion event\n");
handle.eventHandler(out, (void*)intptr_t(nextInvasion-1));
}
@ -829,6 +869,18 @@ static void manageEquipmentEvent(color_ostream& out) {
unordered_map<int32_t, InventoryItem> itemIdToInventoryItem;
unordered_set<int32_t> currentlyEquipped;
vector<InventoryChangeData> equipment_pickups;
vector<InventoryChangeData> equipment_drops;
vector<InventoryChangeData> equipment_changes;
// This vector stores the pointers to newly created changed items
// needed as the stack allocated temporary (in the loop) is lost when we go to
// handle the event calls, so we move that data to the heap if its needed,
// and then once we are done we delete everything.
vector<InventoryItem*> changed_items;
for (auto unit : df::global::world->units.all) {
itemIdToInventoryItem.clear();
currentlyEquipped.clear();
@ -856,40 +908,30 @@ static void manageEquipmentEvent(color_ostream& out) {
auto c = itemIdToInventoryItem.find(dfitem_new->item->id);
if ( c == itemIdToInventoryItem.end() ) {
//new item equipped (probably just picked up)
InventoryChangeData data(unit->id, nullptr, &item_new);
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for new item equipped inventory change event\n");
handle.eventHandler(out, (void*)&data);
}
changed_items.emplace_back(new InventoryItem(item_new));
equipment_pickups.emplace_back(unit->id, nullptr, changed_items.back());
continue;
}
InventoryItem item_old = (*c).second;
InventoryItem item_old = c->second;
df::unit_inventory_item& item0 = item_old.item;
df::unit_inventory_item& item1 = item_new.item;
if ( item0.mode == item1.mode && item0.body_part_id == item1.body_part_id && item0.wound_id == item1.wound_id )
continue;
//some sort of change in how it's equipped
InventoryChangeData data(unit->id, &item_old, &item_new);
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for inventory change event\n");
handle.eventHandler(out, (void*)&data);
}
changed_items.emplace_back(new InventoryItem(item_new));
InventoryItem* item_new_ptr = changed_items.back();
changed_items.emplace_back(new InventoryItem(item_old));
InventoryItem* item_old_ptr = changed_items.back();
equipment_changes.emplace_back(unit->id, item_old_ptr, item_new_ptr);
}
//check for dropped items
for (auto i : v) {
if ( currentlyEquipped.find(i.itemId) != currentlyEquipped.end() )
continue;
//TODO: delete ptr if invalid
InventoryChangeData data(unit->id, &i, nullptr);
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for dropped item inventory change event\n");
handle.eventHandler(out, (void*)&data);
}
changed_items.emplace_back(new InventoryItem(i));
equipment_drops.emplace_back(unit->id, changed_items.back(), nullptr);
}
if ( !hadEquipment )
delete temp;
@ -902,6 +944,31 @@ static void manageEquipmentEvent(color_ostream& out) {
equipment.push_back(item);
}
}
// now handle events
std::for_each(equipment_pickups.begin(), equipment_pickups.end(), [&](InventoryChangeData& data) {
for (auto &[_, handle] : copy) {
DEBUG(log,out).print("calling handler for new item equipped inventory change event\n");
handle.eventHandler(out, (void*) &data);
}
});
std::for_each(equipment_drops.begin(), equipment_drops.end(), [&](InventoryChangeData& data) {
for (auto &[_, handle] : copy) {
DEBUG(log,out).print("calling handler for dropped item inventory change event\n");
handle.eventHandler(out, (void*) &data);
}
});
std::for_each(equipment_changes.begin(), equipment_changes.end(), [&](InventoryChangeData& data) {
for (auto &[_, handle] : copy) {
DEBUG(log,out).print("calling handler for inventory change event\n");
handle.eventHandler(out, (void*) &data);
}
});
// clean up changed items list
std::for_each(changed_items.begin(), changed_items.end(), [](InventoryItem* p){
delete p;
});
}
static void updateReportToRelevantUnits() {
@ -939,8 +1006,7 @@ static void manageReportEvent(color_ostream& out) {
for ( ; idx < reports.size(); idx++ ) {
df::report* report = reports[idx];
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for report event\n");
handle.eventHandler(out, (void*)intptr_t(report->id));
}
@ -981,7 +1047,7 @@ static void manageUnitAttackEvent(color_ostream& out) {
if ( strikeReports.empty() )
return;
updateReportToRelevantUnits();
map<int32_t, map<int32_t, int32_t> > alreadyDone;
unordered_set<std::pair<int32_t, int32_t>, hash_pair> already_done;
for (int reportId : strikeReports) {
df::report* report = df::report::find(reportId);
if ( !report )
@ -1011,27 +1077,25 @@ static void manageUnitAttackEvent(color_ostream& out) {
UnitAttackData data{};
data.report_id = report->id;
if ( wound1 && !alreadyDone[unit1->id][unit2->id] ) {
if ( wound1 && already_done.find(std::make_pair(unit1->id,unit2->id)) == already_done.end() ) {
data.attacker = unit1->id;
data.defender = unit2->id;
data.wound = wound1->id;
alreadyDone[data.attacker][data.defender] = 1;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
already_done.emplace(unit1->id, unit2->id);
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit1 attack unit attack event\n");
handle.eventHandler(out, (void*)&data);
}
}
if ( wound2 && !alreadyDone[unit1->id][unit2->id] ) {
if ( wound2 && already_done.find(std::make_pair(unit1->id,unit2->id)) == already_done.end() ) {
data.attacker = unit2->id;
data.defender = unit1->id;
data.wound = wound2->id;
alreadyDone[data.attacker][data.defender] = 1;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
already_done.emplace(unit1->id, unit2->id);
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit2 attack unit attack event\n");
handle.eventHandler(out, (void*)&data);
}
@ -1041,9 +1105,9 @@ static void manageUnitAttackEvent(color_ostream& out) {
data.attacker = unit2->id;
data.defender = unit1->id;
data.wound = -1;
alreadyDone[data.attacker][data.defender] = 1;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
already_done.emplace(unit1->id, unit2->id);
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit1 killed unit attack event\n");
handle.eventHandler(out, (void*)&data);
}
@ -1053,9 +1117,9 @@ static void manageUnitAttackEvent(color_ostream& out) {
data.attacker = unit1->id;
data.defender = unit2->id;
data.wound = -1;
alreadyDone[data.attacker][data.defender] = 1;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
already_done.emplace(unit1->id, unit2->id);
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit2 killed unit attack event\n");
handle.eventHandler(out, (void*)&data);
}
@ -1313,8 +1377,7 @@ static void manageInteractionEvent(color_ostream& out) {
lastAttacker = df::unit::find(data.attacker);
//lastDefender = df::unit::find(data.defender);
//fire event
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for interaction event\n");
handle.eventHandler(out, (void*)&data);
}

@ -87,6 +87,7 @@ using namespace DFHack;
#include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_legendsst.h"
#include "df/viewscreen_new_regionst.h"
#include "df/viewscreen_setupdwarfgamest.h"
#include "df/viewscreen_titlest.h"
#include "df/world.h"
@ -174,6 +175,45 @@ DEFINE_GET_FOCUS_STRING_HANDLER(new_region)
focusStrings.push_back(baseFocus);
}
DEFINE_GET_FOCUS_STRING_HANDLER(setupdwarfgame)
{
if (screen->doing_custom_settings)
focusStrings.push_back(baseFocus + "/CustomSettings");
else if (game->main_interface.options.open)
focusStrings.push_back(baseFocus + "/Abort");
else if (screen->initial_selection == 1)
focusStrings.push_back(baseFocus + "/Default");
else if (game->main_interface.name_creator.open) {
switch (game->main_interface.name_creator.context) {
case df::name_creator_context_type::EMBARK_FORT_NAME:
focusStrings.push_back(baseFocus + "/FortName");
break;
case df::name_creator_context_type::EMBARK_GROUP_NAME:
focusStrings.push_back(baseFocus + "/GroupName");
break;
default:
break;
}
}
else if (game->main_interface.image_creator.open) {
focusStrings.push_back(baseFocus + "/GroupSymbol");
}
else if (screen->viewing_objections != 0)
focusStrings.push_back(baseFocus + "/Objections");
else {
switch (screen->mode) {
case 0: focusStrings.push_back(baseFocus + "/Dwarves"); break;
case 1: focusStrings.push_back(baseFocus + "/Items"); break;
case 2: focusStrings.push_back(baseFocus + "/Animals"); break;
default:
break;
}
}
if (focusStrings.empty())
focusStrings.push_back(baseFocus + "/Default");
}
DEFINE_GET_FOCUS_STRING_HANDLER(legends)
{
if (screen->init_stage != -1)

@ -1004,6 +1004,8 @@ dfhack_lua_viewscreen::~dfhack_lua_viewscreen()
void dfhack_lua_viewscreen::render()
{
using df::global::enabler;
if (Screen::isDismissed(this))
{
if (parent)
@ -1011,6 +1013,14 @@ void dfhack_lua_viewscreen::render()
return;
}
if (enabler &&
(enabler->mouse_lbut_down || enabler->mouse_rbut_down || enabler->mouse_mbut_down))
{
// synthesize feed events for held mouse buttons
std::set<df::interface_key> keys;
feed(&keys);
}
dfhack_viewscreen::render();
safe_call_lua(do_render, 0, 0);

@ -1,3 +1,5 @@
#include <algorithm>
#include <atomic>
#include <mutex>
#include <numeric>
#include <unordered_map>
@ -28,9 +30,35 @@ namespace DFHack {
DBG_DECLARE(core, textures, DebugCategory::LINFO);
}
struct ReservedRange {
void init(int32_t start) {
this->start = start;
this->end = start + ReservedRange::size;
this->current = start;
this->is_installed = true;
}
long get_new_texpos() {
if (this->current == this->end)
return -1;
return this->current++;
}
static const int32_t size = 10000; // size of reserved texpos buffer
int32_t start = -1;
int32_t end = -1;
long current = -1;
bool is_installed = false;
};
static ReservedRange reserved_range{};
static std::unordered_map<TexposHandle, long> g_handle_to_texpos;
static std::unordered_map<TexposHandle, long> g_handle_to_reserved_texpos;
static std::unordered_map<TexposHandle, SDL_Surface*> g_handle_to_surface;
static std::unordered_map<std::string, std::vector<TexposHandle>> g_tileset_to_handles;
static std::vector<TexposHandle> g_delayed_regs;
static std::mutex g_adding_mutex;
static std::atomic<bool> loading_state = false;
static SDL_Surface* dummy_surface = NULL;
// Converts an arbitrary Surface to something like the display format
// (32-bit RGBA), and converts magenta to transparency if convert_magenta is set
@ -71,6 +99,12 @@ static long add_texture(SDL_Surface* surface) {
return texpos;
}
// register surface in texture raws to specific texpos
static void insert_texture(SDL_Surface* surface, long texpos) {
std::lock_guard<std::mutex> lg_add_texture(g_adding_mutex);
enabler->textures.raws[texpos] = surface;
}
// delete surface from texture raws
static void delete_texture(long texpos) {
std::lock_guard<std::mutex> lg_add_texture(g_adding_mutex);
@ -94,7 +128,8 @@ SDL_Surface* create_texture(std::vector<uint32_t>& pixels, int texture_px_w, int
// convert single surface into tiles according w/h
// register tiles in texture raws and return handles
std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int tile_px_h) {
std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int tile_px_h,
bool reserved) {
std::vector<TexposHandle> handles{};
if (!surface)
return handles;
@ -102,6 +137,12 @@ std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int
int dimx = surface->w / tile_px_w;
int dimy = surface->h / tile_px_h;
if (reserved && (dimx * dimy > reserved_range.end - reserved_range.current)) {
WARN(textures).print(
"there is not enough space in reserved range for whole tileset, using dynamic range\n");
reserved = false;
}
for (int y = 0; y < dimy; y++) {
for (int x = 0; x < dimx; x++) {
SDL_Surface* tile = DFSDL_CreateRGBSurface(
@ -109,7 +150,7 @@ std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int
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);
auto handle = Textures::loadTexture(tile, reserved);
handles.push_back(handle);
}
}
@ -118,20 +159,46 @@ std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int
return handles;
}
TexposHandle Textures::loadTexture(SDL_Surface* surface) {
TexposHandle Textures::loadTexture(SDL_Surface* surface, bool reserved) {
if (!surface || !enabler)
return 0; // should be some error, i guess
if (loading_state)
reserved = true; // use reserved range during loading for all textures
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);
if (reserved && reserved_range.is_installed) {
auto texpos = reserved_range.get_new_texpos();
if (texpos != -1) {
insert_texture(surface, texpos);
g_handle_to_reserved_texpos.emplace(handle, texpos);
dummy_surface->refcount--;
return handle;
}
if (loading_state) { // if we in loading state and reserved range is full -> error
ERR(textures).printerr("reserved range limit has been reached, use dynamic range\n");
return 0;
}
}
// if we here in loading state = true, then it should be dynamic range -> delay reg
if (loading_state) {
g_delayed_regs.push_back(handle);
} else {
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) {
int tile_px_h, bool reserved) {
if (g_tileset_to_handles.contains(file))
return g_tileset_to_handles[file];
if (!enabler)
return std::vector<TexposHandle>{};
@ -142,9 +209,10 @@ std::vector<TexposHandle> Textures::loadTileset(const std::string& file, int til
}
surface = canonicalize_format(surface);
auto handles = slice_tileset(surface, tile_px_w, tile_px_h);
auto handles = slice_tileset(surface, tile_px_w, tile_px_h, reserved);
DEBUG(textures).print("loaded %zd textures from '%s'\n", handles.size(), file.c_str());
g_tileset_to_handles[file] = handles;
return handles;
}
@ -153,11 +221,18 @@ long Textures::getTexposByHandle(TexposHandle handle) {
if (!handle || !enabler)
return -1;
if (g_handle_to_reserved_texpos.contains(handle))
return g_handle_to_reserved_texpos[handle];
if (g_handle_to_texpos.contains(handle))
return g_handle_to_texpos[handle];
if (std::find(g_delayed_regs.begin(), g_delayed_regs.end(), handle) != g_delayed_regs.end())
return 0;
if (g_handle_to_surface.contains(handle)) {
g_handle_to_surface[handle]->refcount++; // prevent destruct on next FreeSurface by game
if (loading_state) { // reinit dor dynamic range during loading -> delayed
g_delayed_regs.push_back(handle);
return 0;
}
auto texpos = add_texture(g_handle_to_surface[handle]);
g_handle_to_texpos.emplace(handle, texpos);
return texpos;
@ -166,22 +241,24 @@ long Textures::getTexposByHandle(TexposHandle handle) {
return -1;
}
TexposHandle Textures::createTile(std::vector<uint32_t>& pixels, int tile_px_w, int tile_px_h) {
TexposHandle Textures::createTile(std::vector<uint32_t>& pixels, int tile_px_w, int tile_px_h,
bool reserved) {
if (!enabler)
return 0;
auto texture = create_texture(pixels, tile_px_w, tile_px_h);
auto handle = Textures::loadTexture(texture);
auto handle = Textures::loadTexture(texture, reserved);
return handle;
}
std::vector<TexposHandle> Textures::createTileset(std::vector<uint32_t>& pixels, int texture_px_w,
int texture_px_h, int tile_px_w, int tile_px_h) {
int texture_px_h, int tile_px_w, int tile_px_h,
bool reserved) {
if (!enabler)
return std::vector<TexposHandle>{};
auto texture = create_texture(pixels, texture_px_w, texture_px_h);
auto handles = slice_tileset(texture, tile_px_w, tile_px_h);
auto handles = slice_tileset(texture, tile_px_w, tile_px_h, reserved);
return handles;
}
@ -192,8 +269,13 @@ void Textures::deleteHandle(TexposHandle handle) {
auto texpos = Textures::getTexposByHandle(handle);
if (texpos > 0)
delete_texture(texpos);
if (g_handle_to_reserved_texpos.contains(handle))
g_handle_to_reserved_texpos.erase(handle);
if (g_handle_to_texpos.contains(handle))
g_handle_to_texpos.erase(handle);
if (auto it = std::find(g_delayed_regs.begin(), g_delayed_regs.end(), handle);
it != g_delayed_regs.end())
g_delayed_regs.erase(it);
if (g_handle_to_surface.contains(handle)) {
auto surface = g_handle_to_surface[handle];
while (surface->refcount)
@ -207,20 +289,45 @@ static void reset_texpos() {
g_handle_to_texpos.clear();
}
static void reset_reserved_texpos() {
DEBUG(textures).print("resetting reserved texture mappings\n");
g_handle_to_reserved_texpos.clear();
}
static void reset_tilesets() {
DEBUG(textures).print("resetting tileset to handle mappings\n");
g_tileset_to_handles.clear();
}
static void reset_surface() {
DEBUG(textures).print("deleting cached surfaces\n");
for (auto& entry : g_handle_to_surface) {
DFSDL_FreeSurface(entry.second);
}
g_handle_to_surface.clear();
}
static void register_delayed_handles() {
DEBUG(textures).print("register delayed handles, size %zd\n", g_delayed_regs.size());
for (auto& handle : g_delayed_regs) {
auto texpos = add_texture(g_handle_to_surface[handle]);
g_handle_to_texpos.emplace(handle, texpos);
}
g_delayed_regs.clear();
}
// reset point on New Game
struct tracking_stage_new_region : df::viewscreen_new_regionst {
typedef df::viewscreen_new_regionst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
if (this->m_raw_load_stage != this->raw_load_stage) {
TRACE(textures).print("raw_load_stage %d -> %d\n", this->m_raw_load_stage, this->raw_load_stage);
TRACE(textures).print("raw_load_stage %d -> %d\n", this->m_raw_load_stage,
this->raw_load_stage);
bool tmp_state = loading_state;
loading_state = this->raw_load_stage >= 0 && this->raw_load_stage < 3 ? true : false;
if (tmp_state != loading_state && !loading_state)
register_delayed_handles();
this->m_raw_load_stage = this->raw_load_stage;
if (this->m_raw_load_stage == 1)
reset_texpos();
@ -240,6 +347,10 @@ struct tracking_stage_adopt_region : df::viewscreen_adopt_regionst {
DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
if (this->m_cur_step != this->cur_step) {
TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step);
bool tmp_state = loading_state;
loading_state = this->cur_step >= 0 && this->cur_step < 3 ? true : false;
if (tmp_state != loading_state && !loading_state)
register_delayed_handles();
this->m_cur_step = this->cur_step;
if (this->m_cur_step == 1)
reset_texpos();
@ -259,6 +370,10 @@ struct tracking_stage_load_region : df::viewscreen_loadgamest {
DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
if (this->m_cur_step != this->cur_step) {
TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step);
bool tmp_state = loading_state;
loading_state = this->cur_step >= 0 && this->cur_step < 3 ? true : false;
if (tmp_state != loading_state && !loading_state)
register_delayed_handles();
this->m_cur_step = this->cur_step;
if (this->m_cur_step == 1)
reset_texpos();
@ -278,6 +393,10 @@ struct tracking_stage_new_arena : df::viewscreen_new_arenast {
DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
if (this->m_cur_step != this->cur_step) {
TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step);
bool tmp_state = loading_state;
loading_state = this->cur_step >= 0 && this->cur_step < 3 ? true : false;
if (tmp_state != loading_state && !loading_state)
register_delayed_handles();
this->m_cur_step = this->cur_step;
if (this->m_cur_step == 0)
reset_texpos();
@ -304,12 +423,31 @@ static void uninstall_reset_point() {
INTERPOSE_HOOK(tracking_stage_new_arena, logic).remove();
}
static void reserve_static_range() {
if (static_cast<size_t>(enabler->textures.init_texture_size) != enabler->textures.raws.size()) {
WARN(textures).print(
"reserved range can't be installed! all textures will be loaded to dynamic range!");
return;
}
reserved_range.init(enabler->textures.init_texture_size);
dummy_surface =
DFSDL_CreateRGBSurfaceWithFormat(0, 0, 0, 32, SDL_PixelFormatEnum::SDL_PIXELFORMAT_RGBA32);
dummy_surface->refcount += ReservedRange::size;
for (int32_t i = 0; i < ReservedRange::size; i++) {
add_texture(dummy_surface);
}
enabler->textures.init_texture_size += ReservedRange::size;
}
void Textures::init(color_ostream& out) {
if (!enabler)
return;
reserve_static_range();
install_reset_point();
DEBUG(textures, out).print("dynamic texture loading ready");
DEBUG(textures, out)
.print("dynamic texture loading ready, reserved range %d-%d\n", reserved_range.start,
reserved_range.end);
}
void Textures::cleanup() {
@ -317,6 +455,8 @@ void Textures::cleanup() {
return;
reset_texpos();
reset_reserved_texpos();
reset_tilesets();
reset_surface();
uninstall_reset_point();
}

@ -131,6 +131,37 @@ void Translation::setNickname(df::language_name *name, std::string nick)
}
}
static string translate_word(const df::language_name * name, size_t word_idx) {
CHECK_NULL_POINTER(name);
auto translation = vector_get(world->raws.language.translations, name->language);
if (!translation)
return "";
auto word = vector_get(translation->words, word_idx);
if (!word)
return "";
return *word;
}
static string translate_english_word(const df::language_name * name, size_t part_idx) {
CHECK_NULL_POINTER(name);
if (part_idx >= 7)
return "";
auto words = vector_get(world->raws.language.words, name->words[part_idx]);
if (!words)
return "";
df::part_of_speech part = name->parts_of_speech[part_idx];
if (part < df::part_of_speech::Noun || part > df::part_of_speech::VerbGerund)
return "";
return words->forms[part];
}
string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart)
{
CHECK_NULL_POINTER(name);
@ -166,20 +197,20 @@ string Translation::TranslateName(const df::language_name * name, bool inEnglish
{
word.clear();
if (name->words[0] >= 0)
word.append(*world->raws.language.translations[name->language]->words[name->words[0]]);
word.append(translate_word(name, name->words[0]));
if (name->words[1] >= 0)
word.append(*world->raws.language.translations[name->language]->words[name->words[1]]);
word.append(translate_word(name, name->words[1]));
addNameWord(out, word);
}
word.clear();
for (int i = 2; i <= 5; i++)
if (name->words[i] >= 0)
word.append(*world->raws.language.translations[name->language]->words[name->words[i]]);
word.append(translate_word(name, name->words[i]));
addNameWord(out, word);
if (name->words[6] >= 0)
{
word.clear();
word.append(*world->raws.language.translations[name->language]->words[name->words[6]]);
word.append(translate_word(name, name->words[6]));
addNameWord(out, word);
}
}
@ -189,9 +220,9 @@ string Translation::TranslateName(const df::language_name * name, bool inEnglish
{
word.clear();
if (name->words[0] >= 0)
word.append(world->raws.language.words[name->words[0]]->forms[name->parts_of_speech[0]]);
word.append(translate_english_word(name, 0));
if (name->words[1] >= 0)
word.append(world->raws.language.words[name->words[1]]->forms[name->parts_of_speech[1]]);
word.append(translate_english_word(name, 1));
addNameWord(out, word);
}
if (name->words[2] >= 0 || name->words[3] >= 0 || name->words[4] >= 0 || name->words[5] >= 0)
@ -201,10 +232,10 @@ string Translation::TranslateName(const df::language_name * name, bool inEnglish
else
out.append("The");
}
for (int i = 2; i <= 5; i++)
for (size_t i = 2; i <= 5; i++)
{
if (name->words[i] >= 0)
addNameWord(out, world->raws.language.words[name->words[i]]->forms[name->parts_of_speech[i]]);
addNameWord(out, translate_english_word(name, i));
}
if (name->words[6] >= 0)
{
@ -213,7 +244,7 @@ string Translation::TranslateName(const df::language_name * name, bool inEnglish
else
out.append("Of");
addNameWord(out, world->raws.language.words[name->words[6]]->forms[name->parts_of_speech[6]]);
addNameWord(out, translate_english_word(name, 6));
}
}

@ -1 +1 @@
Subproject commit 041493b221e0799c106abeac1f86df4535ab80d3
Subproject commit 9312906c5a33feea50c0c32cd683ad22fb87822c

@ -305,6 +305,7 @@ static void cleanup_state()
{
enable_autolabor = false;
labor_infos.clear();
game->external_flag &= ~1; // reinstate DF's work detail system
}
static void reset_labor(df::unit_labor labor)
@ -326,6 +327,8 @@ static void init_state()
if (!enable_autolabor)
return;
game->external_flag |= 1; // bypass DF's work detail system
auto cfg_haulpct = World::GetPersistentData("autolabor/haulpct");
if (cfg_haulpct.isValid())
{
@ -413,8 +416,17 @@ static void enable_plugin(color_ostream &out)
cleanup_state();
init_state();
}
static void disable_plugin(color_ostream& out)
{
if (config.isValid())
setOptionEnabled(CF_ENABLED, false);
game->external_flag |= 1; // shut down DF's work detail system
enable_autolabor = false;
out << "Disabling autolabor." << std::endl;
cleanup_state();
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
@ -1081,12 +1093,7 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable )
}
else if(!enable && enable_autolabor)
{
enable_autolabor = false;
setOptionEnabled(CF_ENABLED, false);
game->external_flag &= ~1; // reenable DF's work detail system
out << "Autolabor is disabled." << std::endl;
disable_plugin(out);
}
return CR_OK;

@ -151,24 +151,6 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
return CR_OK;
}
// Name functions taken from manipulator.cpp
static std::string get_first_name(df::unit *unit)
{
return Translation::capitalize(unit->name.first_name);
}
static std::string get_last_name(df::unit *unit)
{
df::language_name name = unit->name;
std::string ret = "";
for (int i = 0; i < 2; i++)
{
if (name.words[i] >= 0)
ret += *world->raws.language.translations[name.language]->words[name.words[i]];
}
return Translation::capitalize(ret);
}
// Queue up a single order to engrave the slab for the given unit
static void createSlabJob(df::unit *unit)
{
@ -212,7 +194,7 @@ static void checkslabs(color_ostream &out)
)
{
createSlabJob(ghost);
auto fullName = get_first_name(ghost) + " " + get_last_name(ghost);
auto fullName = Translation::TranslateName(&ghost->name, false);
out.print("Added slab order for ghost %s\n", fullName.c_str());
}
}

@ -4,6 +4,7 @@
#include <stack>
#include <string>
#include <cmath>
#include <memory>
#include "Core.h"
#include "Console.h"
@ -1072,14 +1073,13 @@ command_result digv (color_ostream &out, vector <string> & parameters)
con.printerr("I won't dig the borders. That would be cheating!\n");
return CR_FAILURE;
}
MapExtras::MapCache * MCache = new MapExtras::MapCache;
std::unique_ptr<MapExtras::MapCache> MCache = std::make_unique<MapExtras::MapCache>();
df::tile_designation des = MCache->designationAt(xy);
df::tiletype tt = MCache->tiletypeAt(xy);
int16_t veinmat = MCache->veinMaterialAt(xy);
if( veinmat == -1 )
{
con.printerr("This tile is not a vein.\n");
delete MCache;
return CR_FAILURE;
}
con.print("%d/%d/%d tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, des.whole);
@ -1192,7 +1192,6 @@ command_result digv (color_ostream &out, vector <string> & parameters)
}
}
MCache->WriteAll();
delete MCache;
return CR_OK;
}
@ -1259,7 +1258,7 @@ command_result digl (color_ostream &out, vector <string> & parameters)
con.printerr("I won't dig the borders. That would be cheating!\n");
return CR_FAILURE;
}
MapExtras::MapCache * MCache = new MapExtras::MapCache;
std::unique_ptr<MapExtras::MapCache> MCache = std::make_unique<MapExtras::MapCache>();
df::tile_designation des = MCache->designationAt(xy);
df::tiletype tt = MCache->tiletypeAt(xy);
int16_t veinmat = MCache->veinMaterialAt(xy);
@ -1267,7 +1266,6 @@ command_result digl (color_ostream &out, vector <string> & parameters)
if( veinmat != -1 )
{
con.printerr("This is a vein. Use digv instead!\n");
delete MCache;
return CR_FAILURE;
}
con.print("%d/%d/%d tiletype: %d, basemat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, basemat, des.whole);
@ -1408,7 +1406,6 @@ command_result digl (color_ostream &out, vector <string> & parameters)
}
}
MCache->WriteAll();
delete MCache;
return CR_OK;
}
@ -1424,9 +1421,13 @@ command_result digtype (color_ostream &out, vector <string> & parameters)
return CR_FAILURE;
}
uint32_t zMin = 0;
uint32_t xMax,yMax,zMax;
Maps::getSize(xMax,yMax,zMax);
bool hidden = false;
bool automine = true;
int32_t targetDigType = -1;
for (string parameter : parameters) {
if ( parameter == "clear" )
@ -1443,8 +1444,16 @@ command_result digtype (color_ostream &out, vector <string> & parameters)
targetDigType = tile_dig_designation::DownStair;
else if ( parameter == "up" )
targetDigType = tile_dig_designation::UpStair;
else if ( parameter == "-z" )
else if ( parameter == "-z" || parameter == "--cur-zlevel" )
{zMax = *window_z + 1; zMin = *window_z;}
else if ( parameter == "--zdown" || parameter == "-d")
zMax = *window_z + 1;
else if ( parameter == "--zup" || parameter == "-u")
zMin = *window_z;
else if ( parameter == "--hidden" || parameter == "-h")
hidden = true;
else if ( parameter == "--no-auto" || parameter == "-a" )
automine = false;
else
{
out.printerr("Invalid parameter: '%s'.\n", parameter.c_str());
@ -1462,14 +1471,21 @@ command_result digtype (color_ostream &out, vector <string> & parameters)
return CR_FAILURE;
}
DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz);
MapExtras::MapCache * mCache = new MapExtras::MapCache;
std::unique_ptr<MapExtras::MapCache> mCache = std::make_unique<MapExtras::MapCache>();
df::tile_designation baseDes = mCache->designationAt(xy);
if (baseDes.bits.hidden && !hidden) {
out.printerr("Cursor is pointing at a hidden tile. Point the cursor at a visible tile when using the --hidden option.\n");
return CR_FAILURE;
}
df::tile_occupancy baseOcc = mCache->occupancyAt(xy);
df::tiletype tt = mCache->tiletypeAt(xy);
int16_t veinmat = mCache->veinMaterialAt(xy);
if( veinmat == -1 )
{
out.printerr("This tile is not a vein.\n");
delete mCache;
return CR_FAILURE;
}
out.print("(%d,%d,%d) tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, baseDes.whole);
@ -1485,8 +1501,12 @@ command_result digtype (color_ostream &out, vector <string> & parameters)
baseDes.bits.dig = tile_dig_designation::Default;
}
}
// Auto dig only works on default dig designation. Setting dig_auto for any other designation
// prevents dwarves from digging that tile at all.
if (baseDes.bits.dig == tile_dig_designation::Default && automine) baseOcc.bits.dig_auto = true;
else baseOcc.bits.dig_auto = false;
for( uint32_t z = 0; z < zMax; z++ )
for( uint32_t z = zMin; z < zMax; z++ )
{
for( uint32_t x = 1; x < tileXMax-1; x++ )
{
@ -1506,18 +1526,22 @@ command_result digtype (color_ostream &out, vector <string> & parameters)
if ( !mCache->testCoord(current) )
{
out.printerr("testCoord failed at (%d,%d,%d)\n", x, y, z);
delete mCache;
return CR_FAILURE;
}
df::tile_designation designation = mCache->designationAt(current);
if (designation.bits.hidden && !hidden) continue;
df::tile_occupancy occupancy = mCache->occupancyAt(current);
designation.bits.dig = baseDes.bits.dig;
mCache->setDesignationAt(current, designation,priority);
occupancy.bits.dig_auto = baseOcc.bits.dig_auto;
mCache->setDesignationAt(current, designation, priority);
mCache->setOccupancyAt(current, occupancy);
}
}
}
mCache->WriteAll();
delete mCache;
return CR_OK;
}

@ -127,9 +127,9 @@ function InspectorOverlay:onInput(keys)
if not require('plugins.buildingplan').isPlannedBuilding(dfhack.gui.getSelectedBuilding(true)) then
return false
end
if keys._MOUSE_L_DOWN and mouse_is_over_resume_button(self.frame_parent_rect) then
if keys._MOUSE_L and mouse_is_over_resume_button(self.frame_parent_rect) then
return true
elseif keys._MOUSE_L_DOWN or keys._MOUSE_R_DOWN or keys.LEAVESCREEN then
elseif keys._MOUSE_L or keys._MOUSE_R or keys.LEAVESCREEN then
self:reset()
end
return InspectorOverlay.super.onInput(self, keys)

@ -366,10 +366,10 @@ function ItemSelection:submit(choices)
end
function ItemSelection:onInput(keys)
if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then
if keys.LEAVESCREEN or keys._MOUSE_R then
self.on_cancel()
return true
elseif keys._MOUSE_L_DOWN then
elseif keys._MOUSE_L then
local list = self.subviews.flist.list
local idx = list:getIdxUnderMouse()
if idx then

@ -272,7 +272,7 @@ function ItemLine:reset()
end
function ItemLine:onInput(keys)
if keys._MOUSE_L_DOWN and self:getMousePos() then
if keys._MOUSE_L and self:getMousePos() then
self.on_select(self.idx)
end
return ItemLine.super.onInput(self, keys)
@ -739,7 +739,7 @@ end
function PlannerOverlay:onInput(keys)
if not is_plannable() then return false end
if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then
if keys.LEAVESCREEN or keys._MOUSE_R then
if uibs.selection_pos:isValid() then
uibs.selection_pos:clear()
return true
@ -758,7 +758,7 @@ function PlannerOverlay:onInput(keys)
return true
end
if self:is_minimized() then return false end
if keys._MOUSE_L_DOWN then
if keys._MOUSE_L then
if is_over_options_panel() then return false end
local detect_rect = copyall(self.frame_rect)
detect_rect.height = self.subviews.main.frame_rect.height +
@ -828,7 +828,7 @@ function PlannerOverlay:onInput(keys)
end
end
end
return keys._MOUSE_L or keys.SELECT
return keys._MOUSE_L_DOWN or keys.SELECT
end
function PlannerOverlay:render(dc)

@ -5,8 +5,8 @@ local helpdb = require('helpdb')
local overlay = require('plugins.overlay')
local widgets = require('gui.widgets')
local logo_textures = dfhack.textures.loadTileset('hack/data/art/logo.png', 8, 12)
local logo_hovered_textures = dfhack.textures.loadTileset('hack/data/art/logo_hovered.png', 8, 12)
local logo_textures = dfhack.textures.loadTileset('hack/data/art/logo.png', 8, 12, true)
local logo_hovered_textures = dfhack.textures.loadTileset('hack/data/art/logo_hovered.png', 8, 12, true)
local function get_command(cmdline)
local first_word = cmdline:trim():split(' +')[1]
@ -65,6 +65,7 @@ function HotspotMenuWidget:init()
tile=function() return dfhack.textures.getTexposByHandle(logo_hovered_textures[idx]) end,
ch=ch,
fg=COLOR_WHITE,
bold=true,
}
end
local function get_tile_token(idx, ch)
@ -78,9 +79,9 @@ function HotspotMenuWidget:init()
self:addviews{
widgets.Label{
text={
get_tile_token(1, '!'), get_tile_token(2, 'D'), get_tile_token(3, 'F'), get_tile_token(4, '!'), NEWLINE,
get_tile_token(5, '!'), get_tile_token(6, 'H'), get_tile_token(7, 'a'), get_tile_token(8, '!'), NEWLINE,
get_tile_token(9, '!'), get_tile_token(10, 'c'), get_tile_token(11, 'k'), get_tile_token(12, '!'),
get_tile_token(1, 179), get_tile_token(2, 'D'), get_tile_token(3, 'F'), get_tile_token(4, 179), NEWLINE,
get_tile_token(5, 179), get_tile_token(6, 'H'), get_tile_token(7, 'a'), get_tile_token(8, 179), NEWLINE,
get_tile_token(9, 179), get_tile_token(10, 'c'), get_tile_token(11, 'k'), get_tile_token(12, 179),
},
on_click=function() dfhack.run_command('hotkeys') end,
},
@ -269,24 +270,24 @@ function Menu:onSubmit2(_, choice)
end
function Menu:onInput(keys)
if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then
if keys.LEAVESCREEN or keys._MOUSE_R then
return false
elseif keys.KEYBOARD_CURSOR_RIGHT then
self:onSubmit2(self.subviews.list:getSelected())
return true
elseif keys._MOUSE_L_DOWN then
elseif keys._MOUSE_L then
local list = self.subviews.list
local x = list:getMousePos()
if x == 0 then -- clicked on icon
self:onSubmit2(list:getSelected())
df.global.enabler.mouse_lbut = 0
gui.markMouseClicksHandled(keys)
return true
end
if not self:getMouseFramePos() then
self.parent_view:dismiss()
return true
end
df.global.enabler.mouse_lbut = 0
gui.markMouseClicksHandled(keys)
end
self:inputToSubviews(keys)
return true -- we're modal

@ -507,9 +507,6 @@ function feed_viewscreen_widgets(vs_name, vs, keys)
return false
end
gui.markMouseClicksHandled(keys)
if keys._MOUSE_L_DOWN then
df.global.enabler.mouse_lbut = 0
end
return true
end

@ -212,7 +212,7 @@ local function melee_skill_effectiveness(unit)
end
local function get_melee_skill_effectiveness_rating(unit)
return get_rating(melee_skill_effectiveness(unit), 350000, 2350000, 78, 64, 49, 35)
return get_rating(melee_skill_effectiveness(unit), 350000, 2750000, 64, 52, 40, 28)
end
local function make_sort_by_melee_skill_effectiveness_desc()
@ -272,7 +272,7 @@ local function ranged_skill_effectiveness(unit)
end
local function get_ranged_skill_effectiveness_rating(unit)
return get_rating(ranged_skill_effectiveness(unit), 0, 500000, 90, 62, 44, 27)
return get_rating(ranged_skill_effectiveness(unit), 0, 800000, 72, 52, 31, 11)
end
local function make_sort_by_ranged_skill_effectiveness_desc(list)
@ -345,41 +345,41 @@ end
-- Statistical rating that is higher for dwarves that are mentally stable
local function get_mental_stability(unit)
local ALTRUISM = unit.status.current_soul.personality.traits.ALTRUISM
local ANXIETY_PROPENSITY = unit.status.current_soul.personality.traits.ANXIETY_PROPENSITY
local BRAVERY = unit.status.current_soul.personality.traits.BRAVERY
local CHEER_PROPENSITY = unit.status.current_soul.personality.traits.CHEER_PROPENSITY
local CURIOUS = unit.status.current_soul.personality.traits.CURIOUS
local DISCORD = unit.status.current_soul.personality.traits.DISCORD
local DUTIFULNESS = unit.status.current_soul.personality.traits.DUTIFULNESS
local EMOTIONALLY_OBSESSIVE = unit.status.current_soul.personality.traits.EMOTIONALLY_OBSESSIVE
local HUMOR = unit.status.current_soul.personality.traits.HUMOR
local LOVE_PROPENSITY = unit.status.current_soul.personality.traits.LOVE_PROPENSITY
local PERSEVERENCE = unit.status.current_soul.personality.traits.PERSEVERENCE
local POLITENESS = unit.status.current_soul.personality.traits.POLITENESS
local PRIVACY = unit.status.current_soul.personality.traits.PRIVACY
local STRESS_VULNERABILITY = unit.status.current_soul.personality.traits.STRESS_VULNERABILITY
local TOLERANT = unit.status.current_soul.personality.traits.TOLERANT
local CRAFTSMANSHIP = setbelief.getUnitBelief(unit, df.value_type['CRAFTSMANSHIP'])
local FAMILY = setbelief.getUnitBelief(unit, df.value_type['FAMILY'])
local HARMONY = setbelief.getUnitBelief(unit, df.value_type['HARMONY'])
local INDEPENDENCE = setbelief.getUnitBelief(unit, df.value_type['INDEPENDENCE'])
local KNOWLEDGE = setbelief.getUnitBelief(unit, df.value_type['KNOWLEDGE'])
local LEISURE_TIME = setbelief.getUnitBelief(unit, df.value_type['LEISURE_TIME'])
local NATURE = setbelief.getUnitBelief(unit, df.value_type['NATURE'])
local SKILL = setbelief.getUnitBelief(unit, df.value_type['SKILL'])
-- Calculate the rating using the defined variables
local rating = (CRAFTSMANSHIP * -0.01) + (FAMILY * -0.09) + (HARMONY * 0.05)
+ (INDEPENDENCE * 0.06) + (KNOWLEDGE * -0.30) + (LEISURE_TIME * 0.24)
+ (NATURE * 0.27) + (SKILL * -0.21) + (ALTRUISM * 0.13)
+ (ANXIETY_PROPENSITY * -0.06) + (BRAVERY * 0.06)
+ (CHEER_PROPENSITY * 0.41) + (CURIOUS * -0.06) + (DISCORD * 0.14)
+ (DUTIFULNESS * -0.03) + (EMOTIONALLY_OBSESSIVE * -0.13)
+ (HUMOR * -0.05) + (LOVE_PROPENSITY * 0.15) + (PERSEVERENCE * -0.07)
+ (POLITENESS * -0.14) + (PRIVACY * 0.03) + (STRESS_VULNERABILITY * -0.20)
+ (TOLERANT * -0.11)
local altruism = unit.status.current_soul.personality.traits.ALTRUISM
local anxiety_propensity = unit.status.current_soul.personality.traits.ANXIETY_PROPENSITY
local bravery = unit.status.current_soul.personality.traits.BRAVERY
local cheer_propensity = unit.status.current_soul.personality.traits.CHEER_PROPENSITY
local curious = unit.status.current_soul.personality.traits.CURIOUS
local discord = unit.status.current_soul.personality.traits.DISCORD
local dutifulness = unit.status.current_soul.personality.traits.DUTIFULNESS
local emotionally_obsessive = unit.status.current_soul.personality.traits.EMOTIONALLY_OBSESSIVE
local humor = unit.status.current_soul.personality.traits.HUMOR
local love_propensity = unit.status.current_soul.personality.traits.LOVE_PROPENSITY
local perseverence = unit.status.current_soul.personality.traits.PERSEVERENCE
local politeness = unit.status.current_soul.personality.traits.POLITENESS
local privacy = unit.status.current_soul.personality.traits.PRIVACY
local stress_vulnerability = unit.status.current_soul.personality.traits.STRESS_VULNERABILITY
local tolerant = unit.status.current_soul.personality.traits.TOLERANT
local craftsmanship = setbelief.getUnitBelief(unit, df.value_type['CRAFTSMANSHIP'])
local family = setbelief.getUnitBelief(unit, df.value_type['FAMILY'])
local harmony = setbelief.getUnitBelief(unit, df.value_type['HARMONY'])
local independence = setbelief.getUnitBelief(unit, df.value_type['INDEPENDENCE'])
local knowledge = setbelief.getUnitBelief(unit, df.value_type['KNOWLEDGE'])
local leisure_time = setbelief.getUnitBelief(unit, df.value_type['LEISURE_TIME'])
local nature = setbelief.getUnitBelief(unit, df.value_type['NATURE'])
local skill = setbelief.getUnitBelief(unit, df.value_type['SKILL'])
-- calculate the rating using the defined variables
local rating = (craftsmanship * -0.01) + (family * -0.09) + (harmony * 0.05)
+ (independence * 0.06) + (knowledge * -0.30) + (leisure_time * 0.24)
+ (nature * 0.27) + (skill * -0.21) + (altruism * 0.13)
+ (anxiety_propensity * -0.06) + (bravery * 0.06)
+ (cheer_propensity * 0.41) + (curious * -0.06) + (discord * 0.14)
+ (dutifulness * -0.03) + (emotionally_obsessive * -0.13)
+ (humor * -0.05) + (love_propensity * 0.15) + (perseverence * -0.07)
+ (politeness * -0.14) + (privacy * 0.03) + (stress_vulnerability * -0.20)
+ (tolerant * -0.11)
return rating
end
@ -1148,9 +1148,7 @@ function SquadAssignmentOverlay:refresh_list(sort_widget, sort_fn)
end
function SquadAssignmentOverlay:onInput(keys)
if keys._MOUSE_R_DOWN or
keys._MOUSE_L_DOWN and not self:getMouseFramePos()
then
if keys._MOUSE_R or (keys._MOUSE_L and not self:getMouseFramePos()) then
-- if any click is made outside of our window, we may need to refresh our list
self.dirty = true
end

@ -801,10 +801,12 @@ end
function AssignAnimalScreen:onInput(keys)
local handled = AssignAnimalScreen.super.onInput(self, keys)
if not self.is_valid_ui_state() then
view:dismiss()
if view then
view:dismiss()
end
return
end
if keys._MOUSE_L_DOWN then
if keys._MOUSE_L then
-- if any click is made outside of our window, we need to recheck unit properties
local window = self.subviews[1]
if not window:getMouseFramePos() then
@ -818,7 +820,7 @@ function AssignAnimalScreen:onInput(keys)
end
function AssignAnimalScreen:onRenderFrame()
if not self.is_valid_ui_state() then
if view and not self.is_valid_ui_state() then
view:dismiss()
end
end
@ -1072,6 +1074,7 @@ function CageChainOverlay:init()
frame={t=0, l=0, r=0, h=1},
label='DFHack assign',
key='CUSTOM_CTRL_T',
visible=is_valid_building,
on_activate=function() view = view and view:raise() or show_cage_chain_screen() end,
},
}

@ -36,7 +36,7 @@ namespace DFHack {
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);
textures = Textures::loadTileset("hack/data/art/pathable.png", 32, 32, true);
return CR_OK;
}

@ -130,7 +130,6 @@
using namespace DFHack;
using namespace df::enums;
using namespace RemoteFortressReader;
using namespace std;
DFHACK_PLUGIN("RemoteFortressReader");
@ -193,7 +192,7 @@ const char* growth_locations[] = {
#include "df/art_image.h"
#include "df/art_image_chunk.h"
#include "df/art_image_ref.h"
command_result loadArtImageChunk(color_ostream &out, vector <string> & parameters)
command_result loadArtImageChunk(color_ostream &out, std::vector<std::string> & parameters)
{
if (parameters.size() != 1)
return CR_WRONG_USAGE;
@ -214,7 +213,7 @@ command_result loadArtImageChunk(color_ostream &out, vector <string> & parameter
return CR_OK;
}
command_result RemoteFortressReader_version(color_ostream &out, vector<string> &parameters)
command_result RemoteFortressReader_version(color_ostream &out, std::vector<std::string> &parameters)
{
out.print(RFR_VERSION);
return CR_OK;
@ -645,7 +644,7 @@ void CopyMat(RemoteFortressReader::MatPair * mat, int type, int index)
}
map<DFCoord, uint16_t> hashes;
std::map<DFCoord, uint16_t> hashes;
bool IsTiletypeChanged(DFCoord pos)
{
@ -663,7 +662,7 @@ bool IsTiletypeChanged(DFCoord pos)
return false;
}
map<DFCoord, uint16_t> waterHashes;
std::map<DFCoord, uint16_t> waterHashes;
bool IsDesignationChanged(DFCoord pos)
{
@ -681,7 +680,7 @@ bool IsDesignationChanged(DFCoord pos)
return false;
}
map<DFCoord, uint8_t> buildingHashes;
std::map<DFCoord, uint8_t> buildingHashes;
bool IsBuildingChanged(DFCoord pos)
{
@ -700,7 +699,7 @@ bool IsBuildingChanged(DFCoord pos)
return changed;
}
map<DFCoord, uint16_t> spatterHashes;
std::map<DFCoord, uint16_t> spatterHashes;
bool IsspatterChanged(DFCoord pos)
{
@ -737,7 +736,7 @@ bool IsspatterChanged(DFCoord pos)
return false;
}
map<int, uint16_t> itemHashes;
std::map<int, uint16_t> itemHashes;
bool isItemChanged(int i)
{
@ -755,7 +754,7 @@ bool isItemChanged(int i)
return false;
}
bool areItemsChanged(vector<int> * items)
bool areItemsChanged(std::vector<int> * items)
{
bool result = false;
for (size_t i = 0; i < items->size(); i++)
@ -766,7 +765,7 @@ bool areItemsChanged(vector<int> * items)
return result;
}
map<int, int> engravingHashes;
std::map<int, int> engravingHashes;
bool isEngravingNew(int index)
{

@ -1 +1 @@
Subproject commit e0591830b72cdfaec5c9bdb1bf713a74fe744788
Subproject commit 73eac04134b3e7d89b7be1bc355dddab38c5656d

@ -42,17 +42,17 @@ function test.editfield_click()
expect.eq(5, e.cursor)
mock.patch(e, 'getMousePos', mock.func(0), function()
e:onInput{_MOUSE_L=true}
e:onInput{_MOUSE_L_DOWN=true}
expect.eq(1, e.cursor)
end)
mock.patch(e, 'getMousePos', mock.func(20), function()
e:onInput{_MOUSE_L=true}
e:onInput{_MOUSE_L_DOWN=true}
expect.eq(5, e.cursor, 'should only seek to end of text')
end)
mock.patch(e, 'getMousePos', mock.func(2), function()
e:onInput{_MOUSE_L=true}
e:onInput{_MOUSE_L_DOWN=true}
expect.eq(3, e.cursor)
end)
end

@ -59,37 +59,37 @@ function test.onInput()
s:update(23, 10, 50)
expect.false_(s:onInput{}, 'no mouse down')
expect.false_(s:onInput{_MOUSE_L_DOWN=true}, 'no y coord')
expect.false_(s:onInput{_MOUSE_L=true}, 'no y coord')
spec, y = nil, 0
expect.true_(s:onInput{_MOUSE_L_DOWN=true})
expect.true_(s:onInput{_MOUSE_L=true})
expect.eq('up_small', spec, 'on up arrow')
spec, y = nil, 1
expect.true_(s:onInput{_MOUSE_L_DOWN=true})
expect.true_(s:onInput{_MOUSE_L=true})
expect.eq('up_large', spec, 'on body above bar')
spec, y = nil, 44
expect.true_(s:onInput{_MOUSE_L_DOWN=true})
expect.true_(s:onInput{_MOUSE_L=true})
expect.eq('up_large', spec, 'on body just above bar')
spec, y = nil, 45
expect.true_(s:onInput{_MOUSE_L_DOWN=true})
expect.true_(s:onInput{_MOUSE_L=true})
expect.nil_(spec, 'on top of bar')
spec, y = nil, 63
expect.true_(s:onInput{_MOUSE_L_DOWN=true})
expect.true_(s:onInput{_MOUSE_L=true})
expect.nil_(spec, 'on bottom of bar')
spec, y = nil, 64
expect.true_(s:onInput{_MOUSE_L_DOWN=true})
expect.true_(s:onInput{_MOUSE_L=true})
expect.eq('down_large', spec, 'on body just below bar')
spec, y = nil, 98
expect.true_(s:onInput{_MOUSE_L_DOWN=true})
expect.true_(s:onInput{_MOUSE_L=true})
expect.eq('down_large', spec, 'on body below bar')
spec, y = nil, 99
expect.true_(s:onInput{_MOUSE_L_DOWN=true})
expect.true_(s:onInput{_MOUSE_L=true})
expect.eq('down_small', spec, 'on down arrow')
end

@ -7,7 +7,7 @@ function test.hotkeylabel_click()
local l = widgets.HotkeyLabel{key='SELECT', on_activate=func}
mock.patch(l, 'getMousePos', mock.func(0), function()
l:onInput{_MOUSE_L_DOWN=true}
l:onInput{_MOUSE_L=true}
expect.eq(1, func.call_count)
end)
end
@ -33,7 +33,7 @@ function test.togglehotkeylabel_click()
local l = widgets.ToggleHotkeyLabel{}
expect.true_(l:getOptionValue())
mock.patch(l, 'getMousePos', mock.func(0), function()
l:onInput{_MOUSE_L_DOWN=true}
l:onInput{_MOUSE_L=true}
expect.false_(l:getOptionValue())
end)
end

@ -1,5 +1,5 @@
config.mode = 'fortress'
--config.target = 'orders'
config.target = 'orders'
local FILE_PATH_PATTERN = 'dfhack-config/orders/%s.json'