Merge branch 'develop' into placesort

develop
Ryan Dwyer 2023-11-07 00:00:33 -08:00 committed by GitHub
commit 5df373c3c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 336 additions and 443 deletions

@ -4,7 +4,7 @@ ci:
repos:
# shared across repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
@ -20,7 +20,7 @@ repos:
args: ['--fix=lf']
- id: trailing-whitespace
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.27.0
rev: 0.27.1
hooks:
- id: check-github-workflows
- repo: https://github.com/Lucas-C/pre-commit-hooks

@ -98,7 +98,6 @@ enable overlay
# dwarfmonitor \
# mousequery \
# autogems \
# trackstop \
# zone \
# stocks \
#

@ -58,11 +58,13 @@ Template for new versions:
- `prospect`: can now give you an estimate of resources from the embark screen. hover the mouse over a potential embark area and run `prospect`.
- `burrow`: integrated 3d box fill and 2d/3d flood fill extensions for burrow painting mode
- `buildingplan`: allow specific mechanisms to be selected when linking levers
- `sort`: military and burrow membership filters for the burrow assignment screen
## Fixes
- `stockpiles`: hide configure and help buttons when the overlay panel is minimized
- `caravan`: price of vermin swarms correctly adjusted down. a stack of 10000 bees is worth 10, not 10000
- `sort`: when filtering out already-established temples in the location assignment screen, also filter out the "No specific deity" option if a non-denominational temple has already been established
- RemoteServer: continue to accept connections as long as the listening socket is valid instead of closing the socket after the first disconnect
## Misc Improvements
- `buildingplan`: display how many items are available on the planner panel
@ -80,11 +82,15 @@ Template for new versions:
- ``Maps::getWalkableGroup``: get the walkability group of a tile
- ``Units::getReadableName``: now returns the *untranslated* name
- ``Burrows::setAssignedUnit``: now properly handles inactive burrows
- ``Gui::getMousePos``: now takes an optional ``allow_out_of_bounds`` parameter so coordinates can be returned for mouse positions outside of the game map (i.e. in the blank space around the map)
- ``Buildings::completebuild``: used to link a newly created building into the world
## Lua
- ``dfhack.gui.revealInDwarfmodeMap``: gained ``highlight`` parameter to control setting the tile highlight on the zoom target
- ``dfhack.maps.getWalkableGroup``: get the walkability group of a tile
- ``dfhack.gui.getMousePos``: support new optional ``allow_out_of_bounds`` parameter
- ``gui.FRAME_THIN``: a panel frame suitable for floating tooltips
- ``dfhack.buildings.completebuild``: expose new module API
## Removed

@ -1140,10 +1140,12 @@ Announcements
If you want a guaranteed announcement without parsing, use ``dfhack.gui.showAutoAnnouncement`` instead.
* ``dfhack.gui.getMousePos()``
* ``dfhack.gui.getMousePos([allow_out_of_bounds])``
Returns the map coordinates of the map tile the mouse is over as a table of
``{x, y, z}``. If the cursor is not over the map, returns ``nil``.
``{x, y, z}``. If the cursor is not over a valid tile, returns ``nil``. To
allow the function to return coordinates outside of the map, set
``allow_out_of_bounds`` to ``true``.
Other
~~~~~

@ -1,19 +0,0 @@
trackstop
=========
.. dfhack-tool::
:summary: Add dynamic configuration options for track stops.
:tags: unavailable
:no-command:
When enabled, this plugin adds a :kbd:`q` menu for track stops, which is
completely blank in vanilla DF. This allows you to view and/or change the track
stop's friction and dump direction settings, using the keybindings from the
track stop building interface.
Usage
-----
::
enable trackstop

@ -1505,8 +1505,9 @@ static int gui_getDwarfmodeViewDims(lua_State *state)
static int gui_getMousePos(lua_State *L)
{
auto pos = Gui::getMousePos();
if (pos.isValid())
bool allow_out_of_bounds = lua_toboolean(L, 1);
df::coord pos = Gui::getMousePos(allow_out_of_bounds);
if ((allow_out_of_bounds && pos.z >= 0) || pos.isValid())
Lua::Push(L, pos);
else
lua_pushnil(L);
@ -2439,6 +2440,7 @@ static const LuaWrapper::FunctionReg dfhack_buildings_module[] = {
WRAPM(Buildings, isPenPasture),
WRAPM(Buildings, isPitPond),
WRAPM(Buildings, isActive),
WRAPM(Buildings, completebuild),
{ NULL, NULL }
};

@ -51,6 +51,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "PassiveSocket.h"
#include "PluginManager.h"
#include "MiscUtils.h"
#include "Debug.h"
#include <cstdio>
#include <cstdlib>
@ -85,6 +86,7 @@ namespace {
}
namespace DFHack {
DBG_DECLARE(core, socket, DebugCategory::LINFO);
struct BlockGuard {
std::lock_guard<std::mutex> lock;
@ -476,17 +478,33 @@ void ServerMainImpl::threadFn(std::promise<bool> promise, int port)
CActiveSocket *client = nullptr;
try {
while ((client = server.socket.Accept()) != NULL)
for (int acceptFail = 0 ; server.socket.IsSocketValid() && acceptFail < 5 ; acceptFail++)
{
BlockGuard lock;
ServerConnection::Accepted(client);
client = nullptr;
if ((client = server.socket.Accept()) != NULL)
{
BlockGuard lock;
ServerConnection::Accepted(client);
client = nullptr;
}
else
{
WARN(socket).print("Connection failure: %s (%d of %d)\n", server.socket.DescribeError(), acceptFail + 1, 5);
}
}
} catch(BlockedException &) {
if (client)
client->Close();
delete client;
}
if (server.socket.IsSocketValid())
{
WARN(socket).print("Too many failed accepts, shutting down RemoteServer\n");
}
else
{
WARN(socket).print("Listening socket invalid, shutting down RemoteServer\n");
}
}
void ServerMain::block()

@ -289,5 +289,10 @@ DFHACK_EXPORT df::building* findPenPitAt(df::coord coord);
* Returns the units currently in the given cage
*/
DFHACK_EXPORT bool getCageOccupants(df::building_cagest *cage, std::vector<df::unit*> &units);
/**
* Finalizes a new building into the world
*/
DFHACK_EXPORT void completebuild(df::building* bld, char in_play);
}
}

@ -150,7 +150,7 @@ namespace DFHack
*/
DFHACK_EXPORT df::coord getViewportPos();
DFHACK_EXPORT df::coord getCursorPos();
DFHACK_EXPORT df::coord getMousePos();
DFHACK_EXPORT df::coord getMousePos(bool allow_out_of_bounds = false);
static const int AREA_MAP_WIDTH = 23;
static const int MENU_WIDTH = 30;

@ -1707,3 +1707,15 @@ bool Buildings::getCageOccupants(df::building_cagest *cage, vector<df::unit*> &u
return true;
}
void Buildings::completebuild(df::building* bld, char in_play)
{
CHECK_NULL_POINTER(bld);
auto fp = df::global::buildingst_completebuild;
CHECK_NULL_POINTER(fp);
using FT = std::function<void(df::building* bld, char)>;
auto f = reinterpret_cast<FT*>(fp);
(*f)(bld, in_play);
}

@ -380,8 +380,13 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
case df::view_sheet_type::BUILDING:
if (game->main_interface.view_sheets.linking_lever)
newFocusString = baseFocus + "/LinkingLever";
else if (auto bld = df::building::find(game->main_interface.view_sheets.viewing_bldid))
else if (auto bld = df::building::find(game->main_interface.view_sheets.viewing_bldid)) {
newFocusString += '/' + enum_item_key(bld->getType());
if (bld->getType() == df::enums::building_type::Trap) {
df::building_trapst* trap = strict_virtual_cast<df::building_trapst>(bld);
newFocusString += '/' + enum_item_key(trap->trap_type);
}
}
break;
default:
break;
@ -2257,7 +2262,7 @@ bool Gui::setDesignationCoords (const int32_t x, const int32_t y, const int32_t
}
// returns the map coordinates that the mouse cursor is over
df::coord Gui::getMousePos()
df::coord Gui::getMousePos(bool allow_out_of_bounds)
{
df::coord pos;
if (gps && gps->precise_mouse_x > -1) {
@ -2271,7 +2276,7 @@ df::coord Gui::getMousePos()
pos.y += gps->mouse_y;
}
}
if (!Maps::isValidTilePos(pos.x, pos.y, pos.z))
if (!allow_out_of_bounds && !Maps::isValidTilePos(pos.x, pos.y, pos.z))
return df::coord();
return pos;
}

@ -1 +1 @@
Subproject commit a23ec834bbbd2a9d47defd0fa1add717c837632a
Subproject commit 6375044bc504cf5bf4579659755966bb30704b7f

@ -166,7 +166,6 @@ if(BUILD_SUPPORTED)
dfhack_plugin(tailor tailor.cpp LINK_LIBRARIES lua)
dfhack_plugin(tiletypes tiletypes.cpp Brushes.h LINK_LIBRARIES lua)
#dfhack_plugin(title-folder title-folder.cpp)
#dfhack_plugin(trackstop trackstop.cpp)
dfhack_plugin(tubefill tubefill.cpp)
#add_subdirectory(tweak)
dfhack_plugin(workflow workflow.cpp LINK_LIBRARIES lua)

@ -82,6 +82,7 @@ public:
bool failfast;
bool noprogress;
bool maybepointer;
uint8_t perturb_byte;
Checker(color_ostream & out);
bool queue_item(const QueueItem & item, CheckedStructure cs);

@ -296,7 +296,7 @@ void Checker::dispatch_primitive(const QueueItem & item, const CheckedStructure
else if (cs.identity == df::identity_traits<bool>::get())
{
auto val = *reinterpret_cast<const uint8_t *>(item.ptr);
if (val > 1 && val != 0xd2)
if (val > 1 && perturb_byte && val != perturb_byte && val != (perturb_byte ^ 0xff))
{
FAIL("invalid value for bool: " << int(val));
}

@ -33,28 +33,33 @@ DFhackCExport command_result plugin_init(color_ostream &, std::vector<PluginComm
return CR_OK;
}
bool check_malloc_perturb()
// returns 0 if MALLOC_PERTURB_ is unset, or if set to 0, because 0 is not useful
uint8_t check_malloc_perturb()
{
struct T_test {
uint32_t data[1024];
};
auto test = new T_test;
bool ret = (test->data[0] == 0xd2d2d2d2);
delete test;
return ret;
const size_t TEST_DATA_LEN = 5000; // >1 4kb page
std::unique_ptr<uint8_t[]> test_data{new uint8_t[TEST_DATA_LEN]};
uint8_t expected_perturb = test_data[0];
if (getenv("MALLOC_PERTURB_"))
expected_perturb = 0xff ^ static_cast<uint8_t>(atoi(getenv("MALLOC_PERTURB_")));
for (size_t i = 0; i < TEST_DATA_LEN; i++)
if (expected_perturb != test_data[i])
return 0;
return expected_perturb;
}
static command_result command(color_ostream & out, std::vector<std::string> & parameters)
{
if (!check_malloc_perturb())
{
out.printerr("check-structures-sanity: MALLOC_PERTURB_ not set, cannot continue\n");
return CR_FAILURE;
}
uint8_t perturb_byte = check_malloc_perturb();
if (!perturb_byte)
out.printerr("check-structures-sanity: MALLOC_PERTURB_ not set. Some checks may be bypassed or fail.\n");
CoreSuspender suspend;
Checker checker(out);
checker.perturb_byte = perturb_byte;
// check parameters with values first
#define VAL_PARAM(name, expr_using_value) \

@ -13,7 +13,7 @@ local if_burrow = df.global.game.main_interface.burrow
local function is_choosing_area(pos)
return if_burrow.doing_rectangle and
selection_rect.start_x >= 0 and
selection_rect.start_z >= 0 and
(pos or dfhack.gui.getMousePos())
end
@ -23,10 +23,17 @@ local function reset_selection_rect()
selection_rect.start_z = -30000
end
local function clamp(pos)
return xyz2pos(
math.max(0, math.min(df.global.world.map.x_count-1, pos.x)),
math.max(0, math.min(df.global.world.map.y_count-1, pos.y)),
math.max(0, math.min(df.global.world.map.z_count-1, pos.z)))
end
local function get_bounds(pos1, pos2)
pos1 = pos1 or dfhack.gui.getMousePos()
pos2 = pos2 or xyz2pos(selection_rect.start_x, selection_rect.start_y, selection_rect.start_z)
local bounds = {
pos1 = clamp(pos1 or dfhack.gui.getMousePos(true))
pos2 = clamp(pos2 or xyz2pos(selection_rect.start_x, selection_rect.start_y, selection_rect.start_z))
return {
x1=math.min(pos1.x, pos2.x),
x2=math.max(pos1.x, pos2.x),
y1=math.min(pos1.y, pos2.y),
@ -34,18 +41,6 @@ local function get_bounds(pos1, pos2)
z1=math.min(pos1.z, pos2.z),
z2=math.max(pos1.z, pos2.z),
}
-- clamp to map edges
bounds = {
x1=math.max(0, bounds.x1),
x2=math.min(df.global.world.map.x_count-1, bounds.x2),
y1=math.max(0, bounds.y1),
y2=math.min(df.global.world.map.y_count-1, bounds.y2),
z1=math.max(0, bounds.z1),
z2=math.min(df.global.world.map.z_count-1, bounds.z2),
}
return bounds
end
local function get_cur_area_dims()
@ -114,7 +109,7 @@ function BurrowDesignationOverlay:onInput(keys)
-- have been initialized. instead, allow clicks to go through so that vanilla
-- behavior is triggered before we modify the burrow further
elseif keys._MOUSE_L then
local pos = dfhack.gui.getMousePos()
local pos = dfhack.gui.getMousePos(true)
if pos then
local now_ms = dfhack.getTickCount()
if not same_xyz(pos, self.saved_pos) then

@ -1295,6 +1295,7 @@ OVERLAY_WIDGETS = {
location_selector=require('plugins.sort.locationselector').LocationSelectorOverlay,
unit_selector=require('plugins.sort.unitselector').UnitSelectorOverlay,
worker_assignment=require('plugins.sort.unitselector').WorkerAssignmentOverlay,
burrow_assignment=require('plugins.sort.unitselector').BurrowAssignmentOverlay,
slab=require('plugins.sort.slab').SlabOverlay,
world=require('plugins.sort.world').WorldOverlay,
places=require('plugins.sort.places').PlacesOverlay,

@ -165,7 +165,7 @@ InfoOverlay.ATTRS{
frame={w=40, h=6},
}
local function get_squad_options()
function get_squad_options()
local options = {{label='Any', value='all', pen=COLOR_GREEN}}
local fort = df.historical_entity.find(df.global.plotinfo.group_id)
if not fort then return options end
@ -179,7 +179,7 @@ local function get_squad_options()
return options
end
local function get_burrow_options()
function get_burrow_options()
local options = {
{label='Any', value='all', pen=COLOR_GREEN},
{label='Unburrowed', value='none', pen=COLOR_LIGHTRED},
@ -194,6 +194,25 @@ local function get_burrow_options()
return options
end
function matches_squad_burrow_filters(unit, subset, target_squad_id, target_burrow_id)
if subset == 'all' then
return true
elseif subset == 'civilian' then
return unit.military.squad_id == -1
elseif subset == 'military' then
local squad_id = unit.military.squad_id
if squad_id == -1 then return false end
if target_squad_id == 'all' then return true end
return target_squad_id == squad_id
elseif subset == 'burrow' then
if target_burrow_id == 'all' then return #unit.burrows + #unit.inactive_burrows > 0 end
if target_burrow_id == 'none' then return #unit.burrows + #unit.inactive_burrows == 0 end
return utils.binsearch(unit.burrows, target_burrow_id) or
utils.binsearch(unit.inactive_burrows, target_burrow_id)
end
return true
end
function InfoOverlay:init()
self:addviews{
widgets.BannerPanel{
@ -217,7 +236,7 @@ function InfoOverlay:init()
subviews={
widgets.CycleHotkeyLabel{
view_id='subset',
frame={l=1, t=0},
frame={l=1, t=0, r=1},
key='CUSTOM_SHIFT_F',
label='Show:',
options={
@ -255,7 +274,7 @@ function InfoOverlay:init()
subviews={
widgets.CycleHotkeyLabel{
view_id='squad',
frame={l=1, t=0},
frame={l=1, t=0, r=1},
key='CUSTOM_SHIFT_S',
label='Squad:',
options={
@ -266,7 +285,7 @@ function InfoOverlay:init()
},
widgets.CycleHotkeyLabel{
view_id='burrow',
frame={l=1, t=0},
frame={l=1, t=0, r=1},
key='CUSTOM_SHIFT_B',
label='Burrow:',
options={
@ -317,6 +336,11 @@ function InfoOverlay:init()
end
end
function InfoOverlay:reset()
InfoOverlay.super.reset(self)
self.subviews.subset:setOption('all')
end
function InfoOverlay:get_key()
if info.current_mode == df.info_interface_mode_type.CREATURES then
if creatures.current_mode == df.unit_list_mode_type.PET then
@ -400,24 +424,8 @@ function InfoOverlay:onInput(keys)
end
function InfoOverlay:matches_filters(unit)
local subset = self.subviews.subset:getOptionValue()
if subset == 'all' then
return true
elseif subset == 'civilian' then
return unit.military.squad_id == -1
elseif subset == 'military' then
local squad_id = unit.military.squad_id
if squad_id == -1 then return false end
local target_id = self.subviews.squad:getOptionValue()
if target_id == 'all' then return true end
return target_id == squad_id
elseif subset == 'burrow' then
local target_id = self.subviews.burrow:getOptionValue()
if target_id == 'all' then return #unit.burrows + #unit.inactive_burrows > 0 end
if target_id == 'none' then return #unit.burrows + #unit.inactive_burrows == 0 end
return utils.binsearch(unit.burrows, target_id) or utils.binsearch(unit.inactive_burrows, target_id)
end
return true
return matches_squad_burrow_filters(unit, self.subviews.subset:getOptionValue(),
self.subviews.squad:getOptionValue(), self.subviews.burrow:getOptionValue())
end
-- ----------------------

@ -5,9 +5,9 @@ local utils = require('utils')
function get_unit_search_key(unit)
return ('%s %s %s'):format(
dfhack.units.getReadableName(unit), -- last name is in english
dfhack.units.getReadableName(unit),
dfhack.units.getProfessionName(unit),
dfhack.TranslateName(unit.name, false, true)) -- get untranslated last name
dfhack.TranslateName(unit.name, true, true)) -- get English last name
end
local function copy_to_lua_table(vec)

@ -1,6 +1,9 @@
local _ENV = mkmodule('plugins.sort.unitselector')
local info = require('plugins.sort.info')
local gui = require('gui')
local sortoverlay = require('plugins.sort.sortoverlay')
local utils = require('utils')
local widgets = require('gui.widgets')
local unit_selector = df.global.game.main_interface.unit_selector
@ -9,12 +12,25 @@ local unit_selector = df.global.game.main_interface.unit_selector
-- UnitSelectorOverlay
--
local WIDGET_WIDTH = 31
UnitSelectorOverlay = defclass(UnitSelectorOverlay, sortoverlay.SortOverlay)
UnitSelectorOverlay.ATTRS{
default_pos={x=62, y=6},
viewscreens='dwarfmode/UnitSelector',
frame={w=31, h=1},
handled_screens=DEFAULT_NIL,
-- pen, pit, chain, and cage assignment are handled by dedicated screens
-- squad fill position screen has a specialized overlay
-- we *could* add search functionality to vanilla screens for pit and cage,
-- but then we'd have to handle the itemid vector
handled_screens={
ZONE_BEDROOM_ASSIGNMENT='already',
ZONE_OFFICE_ASSIGNMENT='already',
ZONE_DINING_HALL_ASSIGNMENT='already',
ZONE_TOMB_ASSIGNMENT='already',
OCCUPATION_ASSIGNMENT='selected',
SQUAD_KILL_ORDER='selected',
},
}
local function get_unit_id_search_key(unit_id)
@ -26,7 +42,7 @@ end
function UnitSelectorOverlay:init()
self:addviews{
widgets.BannerPanel{
frame={l=0, t=0, r=0, h=1},
frame={l=0, t=0, w=WIDGET_WIDTH, h=1},
visible=self:callback('get_key'),
subviews={
widgets.EditField{
@ -40,20 +56,10 @@ function UnitSelectorOverlay:init()
},
}
-- pen, pit, chain, and cage assignment are handled by dedicated screens
-- squad fill position screen has a specialized overlay
-- we *could* add search functionality to vanilla screens for pit and cage,
-- but then we'd have to handle the itemid vector
self.handled_screens = self.handled_screens or {
ZONE_BEDROOM_ASSIGNMENT='already',
ZONE_OFFICE_ASSIGNMENT='already',
ZONE_DINING_HALL_ASSIGNMENT='already',
ZONE_TOMB_ASSIGNMENT='already',
OCCUPATION_ASSIGNMENT='selected',
BURROW_ASSIGNMENT='selected',
SQUAD_KILL_ORDER='selected',
}
self:register_handlers()
end
function UnitSelectorOverlay:register_handlers()
for name,flags_vec in pairs(self.handled_screens) do
self:register_handler(name, unit_selector.unid,
curry(sortoverlay.flags_vector_search, {get_search_key_fn=get_unit_id_search_key},
@ -77,13 +83,15 @@ function UnitSelectorOverlay:onRenderBody(dc)
end
function UnitSelectorOverlay:onInput(keys)
if UnitSelectorOverlay.super.onInput(self, keys) then
return true
end
if keys._MOUSE_L then
self.refresh_search = true
end
return UnitSelectorOverlay.super.onInput(self, keys)
end
-- ----------------------
-- -----------------------
-- WorkerAssignmentOverlay
--
@ -95,4 +103,183 @@ WorkerAssignmentOverlay.ATTRS{
handled_screens={WORKER_ASSIGNMENT='selected'},
}
-- -----------------------
-- BurrowAssignmentOverlay
--
local DEFAULT_OVERLAY_WIDTH = 58
BurrowAssignmentOverlay = defclass(BurrowAssignmentOverlay, UnitSelectorOverlay)
BurrowAssignmentOverlay.ATTRS{
viewscreens='dwarfmode/UnitSelector',
frame={w=DEFAULT_OVERLAY_WIDTH, h=5},
handled_screens={BURROW_ASSIGNMENT='selected'},
}
local function get_screen_width()
local sw = dfhack.screen.getWindowSize()
return sw
end
local function toggle_all()
if #unit_selector.unid == 0 then return end
local burrow = df.burrow.find(unit_selector.burrow_id)
if not burrow then return end
local target_state = unit_selector.selected[0] == 0
local target_val = target_state and 1 or 0
for i,unit_id in ipairs(unit_selector.unid) do
local unit = df.unit.find(unit_id)
if unit then
dfhack.burrows.setAssignedUnit(burrow, unit, target_state)
unit_selector.selected[i] = target_val
end
end
end
function BurrowAssignmentOverlay:init()
self:addviews{
widgets.Panel{
view_id='top_mask',
frame={l=WIDGET_WIDTH, r=0, t=0, h=1},
frame_background=gui.CLEAR_PEN,
visible=function() return get_screen_width() >= 144 end,
},
widgets.Panel{
view_id='wide_mask',
frame={r=0, t=1, h=2, w=DEFAULT_OVERLAY_WIDTH},
frame_background=gui.CLEAR_PEN,
visible=function() return get_screen_width() >= 144 end,
},
widgets.Panel{
view_id='narrow_mask',
frame={l=0, t=1, h=2, w=24},
frame_background=gui.CLEAR_PEN,
visible=function() return get_screen_width() < 144 end,
},
widgets.BannerPanel{
view_id='subset_panel',
frame={l=0, t=1, w=WIDGET_WIDTH, h=1},
subviews={
widgets.CycleHotkeyLabel{
view_id='subset',
frame={l=1, t=0, r=1},
key='CUSTOM_SHIFT_F',
label='Show:',
options={
{label='All', value='all', pen=COLOR_GREEN},
{label='Military', value='military', pen=COLOR_YELLOW},
{label='Civilians', value='civilian', pen=COLOR_CYAN},
{label='Burrowed', value='burrow', pen=COLOR_MAGENTA},
},
on_change=function(value)
local squad = self.subviews.squad
local burrow = self.subviews.burrow
squad.visible = false
burrow.visible = false
if value == 'military' then
squad.options = info.get_squad_options()
squad:setOption('all')
squad.visible = true
elseif value == 'burrow' then
burrow.options = info.get_burrow_options()
burrow:setOption('all')
burrow.visible = true
end
self:do_search(self.subviews.search.text, true)
end,
},
},
},
widgets.BannerPanel{
view_id='subfilter_panel',
frame={l=0, t=2, w=WIDGET_WIDTH, h=1},
visible=function()
local subset = self.subviews.subset:getOptionValue()
return subset == 'military' or subset == 'burrow'
end,
subviews={
widgets.CycleHotkeyLabel{
view_id='squad',
frame={l=1, t=0, r=1},
key='CUSTOM_SHIFT_S',
label='Squad:',
options={
{label='Any', value='all', pen=COLOR_GREEN},
},
visible=false,
on_change=function() self:do_search(self.subviews.search.text, true) end,
},
widgets.CycleHotkeyLabel{
view_id='burrow',
frame={l=1, t=0, r=1},
key='CUSTOM_SHIFT_B',
label='Burrow:',
options={
{label='Any', value='all', pen=COLOR_GREEN},
},
visible=false,
on_change=function() self:do_search(self.subviews.search.text, true) end,
},
},
},
widgets.BannerPanel{
frame={r=0, t=4, w=25, h=1},
subviews={
widgets.HotkeyLabel{
frame={l=1, t=0, r=1},
label='Select all/none',
key='CUSTOM_CTRL_A',
on_activate=toggle_all,
},
},
},
}
end
function BurrowAssignmentOverlay:register_handlers()
for name,flags_vec in pairs(self.handled_screens) do
self:register_handler(name, unit_selector.unid,
curry(sortoverlay.flags_vector_search, {
get_search_key_fn=get_unit_id_search_key,
matches_filters_fn=self:callback('matches_filters'),
},
unit_selector[flags_vec]))
end
end
function BurrowAssignmentOverlay:reset()
BurrowAssignmentOverlay.super.reset(self)
self.subviews.subset:setOption('all')
end
function BurrowAssignmentOverlay:matches_filters(unit_id)
local unit = df.unit.find(unit_id)
if not unit then return false end
return info.matches_squad_burrow_filters(unit, self.subviews.subset:getOptionValue(),
self.subviews.squad:getOptionValue(), self.subviews.burrow:getOptionValue())
end
local function clicked_on_mask(self, keys)
if not keys._MOUSE_L then return false end
for _,mask in ipairs{'top_mask', 'wide_mask', 'narrow_mask'} do
if utils.getval(self.subviews[mask].visible) then
if self.subviews[mask]:getMousePos() then
return true
end
end
end
return false
end
function BurrowAssignmentOverlay:onInput(keys)
return BurrowAssignmentOverlay.super.onInput(self, keys) or
clicked_on_mask(self, keys)
end
function BurrowAssignmentOverlay:onRenderFrame(dc, rect)
local sw = get_screen_width()
self.frame.w = math.min(DEFAULT_OVERLAY_WIDTH, sw - 94)
BurrowAssignmentOverlay.super.onRenderFrame(self, dc, rect)
end
return _ENV

@ -1,333 +0,0 @@
/*
* Trackstop plugin.
* Shows track stop friction and direction in its 'q' menu.
*/
#include "uicommon.h"
#include "LuaTools.h"
#include "df/building_rollersst.h"
#include "df/building_trapst.h"
#include "df/job.h"
#include "df/viewscreen_dwarfmodest.h"
#include "modules/Gui.h"
using namespace DFHack;
using namespace std;
using df::building_rollersst;
using df::building_trapst;
using df::enums::trap_type::trap_type;
using df::enums::screw_pump_direction::screw_pump_direction;
DFHACK_PLUGIN("trackstop");
#define AUTOENABLE false
DFHACK_PLUGIN_IS_ENABLED(enabled);
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(plotinfo);
REQUIRE_GLOBAL(world);
/*
* Interface hooks
*/
struct trackstop_hook : public df::viewscreen_dwarfmodest {
typedef df::viewscreen_dwarfmodest interpose_base;
enum Friction {
Lowest = 10,
Low = 50,
Medium = 500,
High = 10000,
Highest = 50000
};
building_trapst *get_selected_trackstop() {
if (plotinfo->main.mode != ui_sidebar_mode::QueryBuilding) {
// Not in a building's 'q' menu.
return nullptr;
}
building_trapst *ts = virtual_cast<building_trapst>(world->selected_building);
if (!ts) {
// Not a trap type of building.
return nullptr;
}
if (ts->trap_type != df::trap_type::TrackStop) {
// Not a trackstop.
return nullptr;
}
if (ts->construction_stage < ts->getMaxBuildStage()) {
// Not yet fully constructed.
return nullptr;
}
for (auto it = ts->jobs.begin(); it != ts->jobs.end(); it++) {
auto job = *it;
if (job->job_type == df::job_type::DestroyBuilding) {
// Slated for removal.
return nullptr;
}
}
return ts;
}
bool handleInput(set<df::interface_key> *input) {
building_trapst *ts = get_selected_trackstop();
if (!ts) {
return false;
}
if (input->count(interface_key::BUILDING_TRACK_STOP_DUMP)) {
// Change track stop dump direction.
// There might be a more elegant way to do this.
if (!ts->use_dump) {
// No -> North
ts->use_dump = 1;
ts->dump_x_shift = 0;
ts->dump_y_shift = -1;
} else if (ts->dump_x_shift == 0 && ts->dump_y_shift == -1) {
// North -> South
ts->dump_x_shift = 0;
ts->dump_y_shift = 1;
} else if (ts->dump_x_shift == 0 && ts->dump_y_shift == 1) {
// South -> East
ts->dump_x_shift = 1;
ts->dump_y_shift = 0;
} else if (ts->dump_x_shift == 1 && ts->dump_y_shift == 0) {
// East -> West
ts->dump_x_shift = -1;
ts->dump_y_shift = 0;
} else {
// West (or Elsewhere) -> No
ts->use_dump = 0;
ts->dump_x_shift = 0;
ts->dump_y_shift = 0;
}
return true;
} else if (input->count(interface_key::BUILDING_TRACK_STOP_FRICTION_UP)) {
ts->friction = (
(ts->friction < Friction::Lowest)? Friction::Lowest:
(ts->friction < Friction::Low)? Friction::Low:
(ts->friction < Friction::Medium)? Friction::Medium:
(ts->friction < Friction::High)? Friction::High:
(ts->friction < Friction::Highest)? Friction::Highest:
ts->friction
);
return true;
} else if (input->count(interface_key::BUILDING_TRACK_STOP_FRICTION_DOWN)) {
ts->friction = (
(ts->friction > Friction::Highest)? Friction::Highest:
(ts->friction > Friction::High)? Friction::High:
(ts->friction > Friction::Medium)? Friction::Medium:
(ts->friction > Friction::Low)? Friction::Low:
(ts->friction > Friction::Lowest)? Friction::Lowest:
ts->friction
);
return true;
}
return false;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input)) {
if (!handleInput(input)) {
INTERPOSE_NEXT(feed)(input);
}
}
DEFINE_VMETHOD_INTERPOSE(void, render, ()) {
INTERPOSE_NEXT(render)();
building_trapst *ts = get_selected_trackstop();
if (ts) {
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = dims.menu_x1 + 1;
int x = left_margin;
int y = dims.y1 + 1;
OutputString(COLOR_WHITE, x, y, "Track Stop", true, left_margin);
y += 3;
OutputString(COLOR_WHITE, x, y, "Friction: ", false);
OutputString(COLOR_WHITE, x, y, (
(ts->friction <= Friction::Lowest)? "Lowest":
(ts->friction <= Friction::Low)? "Low":
(ts->friction <= Friction::Medium)? "Medium":
(ts->friction <= Friction::High)? "High":
"Highest"
), true, left_margin);
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_TRACK_STOP_FRICTION_DOWN));
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_TRACK_STOP_FRICTION_UP));
OutputString(COLOR_WHITE, x, y, ": Change Friction", true, left_margin);
y += 1;
OutputString(COLOR_WHITE, x, y, "Dump on arrival: ", false);
OutputString(COLOR_WHITE, x, y, (
(!ts->use_dump)? "No":
(ts->dump_x_shift == 0 && ts->dump_y_shift == -1)? "North":
(ts->dump_x_shift == 0 && ts->dump_y_shift == 1)? "South":
(ts->dump_x_shift == 1 && ts->dump_y_shift == 0)? "East":
(ts->dump_x_shift == -1 && ts->dump_y_shift == 0)? "West":
"Elsewhere"
), true, left_margin);
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_TRACK_STOP_DUMP));
OutputString(COLOR_WHITE, x, y, ": Activate/change direction", true, left_margin);
y += 1;
OutputString(COLOR_GREY, x, y, "DFHack");
}
}
};
struct roller_hook : public df::viewscreen_dwarfmodest {
typedef df::viewscreen_dwarfmodest interpose_base;
enum Speed {
Lowest = 10000,
Low = 20000,
Medium = 30000,
High = 40000,
Highest = 50000
};
building_rollersst *get_selected_roller() {
if (plotinfo->main.mode != ui_sidebar_mode::QueryBuilding) {
// Not in a building's 'q' menu.
return nullptr;
}
building_rollersst *roller = virtual_cast<building_rollersst>(world->selected_building);
if (!roller) {
// Not a roller.
return nullptr;
}
if (roller->construction_stage < roller->getMaxBuildStage()) {
// Not yet fully constructed.
return nullptr;
}
for (auto it = roller->jobs.begin(); it != roller->jobs.end(); it++) {
auto job = *it;
if (job->job_type == df::job_type::DestroyBuilding) {
// Slated for removal.
return nullptr;
}
}
return roller;
}
bool handleInput(set<df::interface_key> *input) {
building_rollersst *roller = get_selected_roller();
if (!roller) {
return false;
}
if (input->count(interface_key::BUILDING_ORIENT_NONE)) {
// Flip roller orientation.
// Long rollers can only be oriented along their length.
// Todo: Only add 1 to 1x1 rollers: x ^= ((x&1)<<1)|1
// Todo: This could have been elegant without all the casting,
// but as an enum it might be better off listing each case.
roller->direction = (df::enums::screw_pump_direction::screw_pump_direction)(((int8_t)roller->direction) ^ 2);
return true;
} else if (input->count(interface_key::BUILDING_ROLLERS_SPEED_UP)) {
if (roller->speed < Speed::Highest) {
roller->speed += Speed::Lowest;
}
return true;
} else if (input->count(interface_key::BUILDING_ROLLERS_SPEED_DOWN)) {
if (roller->speed > Speed::Lowest) {
roller->speed -= Speed::Lowest;
}
return true;
}
return false;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input)) {
if (!handleInput(input)) {
INTERPOSE_NEXT(feed)(input);
}
}
DEFINE_VMETHOD_INTERPOSE(void, render, ()) {
INTERPOSE_NEXT(render)();
building_rollersst *roller = get_selected_roller();
if (roller) {
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = dims.menu_x1 + 1;
int x = left_margin;
int y = dims.y1 + 6;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_ORIENT_NONE));
OutputString(COLOR_WHITE, x, y, ": Rolls ", false);
OutputString(COLOR_WHITE, x, y, (
(roller->direction == df::screw_pump_direction::FromNorth)? "Southward":
(roller->direction == df::screw_pump_direction::FromEast)? "Westward":
(roller->direction == df::screw_pump_direction::FromSouth)? "Northward":
(roller->direction == df::screw_pump_direction::FromWest)? "Eastward":
""
), true, left_margin);
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_ROLLERS_SPEED_DOWN));
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_ROLLERS_SPEED_UP));
OutputString(COLOR_WHITE, x, y, ": ");
OutputString(COLOR_WHITE, x, y, (
(roller->speed <= Speed::Lowest)? "Lowest":
(roller->speed <= Speed::Low)? "Low":
(roller->speed <= Speed::Medium)? "Medium":
(roller->speed <= Speed::High)? "High":
"Highest"
));
OutputString(COLOR_WHITE, x, y, " Speed", true, left_margin);
y += 1;
OutputString(COLOR_GREY, x, y, "DFHack");
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(trackstop_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(trackstop_hook, render);
IMPLEMENT_VMETHOD_INTERPOSE(roller_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(roller_hook, render);
DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) {
// Accept the "enable trackstop" / "disable trackstop" commands.
if (enable != enabled) {
if (!INTERPOSE_HOOK(trackstop_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(trackstop_hook, render).apply(enable) ||
!INTERPOSE_HOOK(roller_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(roller_hook, render).apply(enable)) {
out.printerr("Could not %s trackstop hooks!\n", enable? "insert": "remove");
return CR_FAILURE;
}
enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
return plugin_enable(out, AUTOENABLE);
}
DFhackCExport command_result plugin_shutdown(color_ostream &out) {
return plugin_enable(out, false);
}

@ -1 +1 @@
Subproject commit 2382a334367b7a46a1ddeefc0e43a63491d6062c
Subproject commit 87e6f1ae310ab81545198542cb07fea7016529c9