Merge branch 'develop' into ccompile-script

develop
Myk 2023-01-22 17:16:19 -08:00 committed by GitHub
commit f805f5b67e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 209 additions and 62 deletions

@ -39,6 +39,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Misc Improvements
- A new cross-compile build script was added for building the Windows files from a Linux Docker builder (see the Compile instructions in the docs)
- `hotkeys`: clicking on the DFHack logo no longer closes the popup menu
- `orders`: orders plugin functionality is now offered via an overlay widget when the manager orders screen is open
## Documentation

@ -125,10 +125,6 @@ The ``overlay.OverlayWidget`` superclass defines the following class attributes:
not annoy the player. Set to 0 to be called at the maximum rate. Be aware
that running more often than you really need to will impact game FPS,
especially if your widget can run while the game is unpaused.
- ``always_enabled`` (default: ``false``)
Set this to ``true`` if you don't want to let the user disable the widget.
This is useful for widgets that are controlled purely through their
triggers. See `gui/pathable` for an example.
Registering a widget with the overlay framework
***********************************************

@ -40,6 +40,17 @@ Examples
Import manager orders from the library that keep your fort stocked with
basic essentials.
Overlay
-------
Orders plugin functionality is directly available when the manager orders screen
is open via an `overlay` widget. There are hotkeys assigned to export, import,
sort, and clear. You can also click on the hotkey hints as if they were buttons.
Clearing will ask for confirmation before acting.
If you want to change where the hotkey hints appear, you can move them via
`gui/overlay`.
The orders library
------------------

@ -167,8 +167,8 @@ int linear_index(const std::vector<CT*> &vec, FT CT::*field, FT key)
return -1;
}
template <typename CT, typename FT>
int binsearch_index(const std::vector<CT*> &vec, FT CT::*field, FT key, bool exact = true)
template <typename CT, typename FT, typename MT>
int binsearch_index(const std::vector<CT*> &vec, FT MT::*field, FT key, bool exact = true)
{
// Returns the index of the value >= the key
int min = -1, max = (int)vec.size();
@ -245,8 +245,8 @@ unsigned insert_into_vector(std::vector<FT> &vec, FT key, bool *inserted = NULL)
return pos;
}
template<typename CT, typename FT>
unsigned insert_into_vector(std::vector<CT*> &vec, FT CT::*field, CT *obj, bool *inserted = NULL)
template<typename CT, typename FT, typename MT>
unsigned insert_into_vector(std::vector<CT*> &vec, FT MT::*field, CT *obj, bool *inserted = NULL)
{
unsigned pos = (unsigned)binsearch_index(vec, field, obj->*field, false);
bool to_ins = (pos >= vec.size() || vec[pos] != obj);

@ -82,6 +82,7 @@ using namespace DFHack;
#include "df/map_block.h"
#include "df/tile_occupancy.h"
#include "df/plotinfost.h"
#include "df/squad.h"
#include "df/ui_look_list.h"
#include "df/unit.h"
#include "df/unit_relationship_type.h"
@ -168,34 +169,24 @@ void buildings_onUpdate(color_ostream &out)
static void building_into_zone_unidir(df::building* bld, df::building_civzonest* zone)
{
for (size_t bid = 0; bid < zone->contained_buildings.size(); bid++)
for (auto contained_building : zone->contained_buildings)
{
if (zone->contained_buildings[bid] == bld)
if (contained_building == bld)
return;
}
zone->contained_buildings.push_back(bld);
std::sort(zone->contained_buildings.begin(), zone->contained_buildings.end(), [](df::building* b1, df::building* b2)
{
return b1->id < b2->id;
});
insert_into_vector(zone->contained_buildings, &df::building::id, bld);
}
static void zone_into_building_unidir(df::building* bld, df::building_civzonest* zone)
{
for (size_t bid = 0; bid < bld->relations.size(); bid++)
for (auto relation : bld->relations)
{
if (bld->relations[bid] == zone)
if (relation == zone)
return;
}
bld->relations.push_back(zone);
std::sort(bld->relations.begin(), bld->relations.end(), [](df::building* b1, df::building* b2)
{
return b1->id < b2->id;
});
insert_into_vector(bld->relations, &df::building_civzonest::id, zone);
}
static bool is_suitable_building_for_zoning(df::building* bld)
@ -222,10 +213,8 @@ static void add_building_to_all_zones(df::building* bld)
std::vector<df::building_civzonest*> cv;
Buildings::findCivzonesAt(&cv, coord);
for (size_t i=0; i < cv.size(); i++)
{
add_building_to_zone(bld, cv[i]);
}
for (auto zone : cv)
add_building_to_zone(bld, zone);
}
static void add_zone_to_all_buildings(df::building* zone_as_building)
@ -238,20 +227,16 @@ static void add_zone_to_all_buildings(df::building* zone_as_building)
if (zone == nullptr)
return;
auto& vec = world->buildings.other[buildings_other_id::IN_PLAY];
for (size_t i = 0; i < vec.size(); i++)
for (auto bld : world->buildings.other.IN_PLAY)
{
auto against = vec[i];
if (zone->z != against->z)
if (zone->z != bld->z)
continue;
if (!is_suitable_building_for_zoning(against))
if (!is_suitable_building_for_zoning(bld))
continue;
int32_t cx = against->centerx;
int32_t cy = against->centery;
int32_t cx = bld->centerx;
int32_t cy = bld->centery;
df::coord2d coord(cx, cy);
@ -262,7 +247,7 @@ static void add_zone_to_all_buildings(df::building* zone_as_building)
if (!etile || !*etile)
continue;
add_building_to_zone(against, zone);
add_building_to_zone(bld, zone);
}
}
}
@ -295,10 +280,8 @@ static void remove_building_from_all_zones(df::building* bld)
std::vector<df::building_civzonest*> cv;
Buildings::findCivzonesAt(&cv, coord);
for (size_t i=0; i < cv.size(); i++)
{
remove_building_from_zone(bld, cv[i]);
}
for (auto zone : cv)
remove_building_from_zone(bld, zone);
}
static void remove_zone_from_all_buildings(df::building* zone_as_building)
@ -311,17 +294,11 @@ static void remove_zone_from_all_buildings(df::building* zone_as_building)
if (zone == nullptr)
return;
auto& vec = world->buildings.other[buildings_other_id::IN_PLAY];
//this is a paranoid check and slower than it could be. Zones contain a list of children
//good for fixing potentially bad game states when deleting a zone
for (size_t i = 0; i < vec.size(); i++)
{
df::building* bld = vec[i];
for (auto bld : world->buildings.other.IN_PLAY)
remove_building_from_zone(bld, zone);
}
}
uint32_t Buildings::getNumBuildings()
{
@ -1348,6 +1325,62 @@ bool Buildings::constructWithFilters(df::building *bld, std::vector<df::job_item
return true;
}
static void delete_civzone_squad_links(df::building_civzonest* zone)
{
for (df::building_civzonest::T_squad_room_info* room_info : zone->squad_room_info)
{
int32_t squad_id = room_info->squad_id;
df::squad* squad = df::squad::find(squad_id);
//if this is null, something has gone just *terribly* wrong
if (squad)
{
for (int i=(int)squad->rooms.size() - 1; i >= 0; i--)
{
if (squad->rooms[i]->building_id == zone->id)
{
auto room = squad->rooms[i];
squad->rooms.erase(squad->rooms.begin() + i);
delete room;
}
}
}
delete room_info;
}
zone->squad_room_info.clear();
}
//unit owned_building pointers are known-bad as of 50.05 and dangle on zone delete
//do not use anything that touches anything other than the pointer value
//this means also that if dwarf fortress reuses a memory allocation, we will end up with duplicates
//this vector is also not sorted by id
//it also turns out that multiple units eg (solely?) spouses can point to one room
static void delete_assigned_unit_links(df::building_civzonest* zone)
{
//not clear if this is always true
/*if (zone->assigned_unit_id == -1)
return;*/
for (df::unit* unit : world->units.active)
{
for (int i=(int)unit->owned_buildings.size() - 1; i >= 0; i--)
{
if (unit->owned_buildings[i] == zone)
unit->owned_buildings.erase(unit->owned_buildings.begin() + i);
}
}
}
static void on_civzone_delete(df::building_civzonest* civzone)
{
remove_zone_from_all_buildings(civzone);
delete_civzone_squad_links(civzone);
delete_assigned_unit_links(civzone);
}
bool Buildings::deconstruct(df::building *bld)
{
using df::global::plotinfo;
@ -1386,7 +1419,14 @@ bool Buildings::deconstruct(df::building *bld)
bld->uncategorize();
remove_building_from_all_zones(bld);
remove_zone_from_all_buildings(bld);
if (bld->getType() == df::building_type::Civzone)
{
auto zone = strict_virtual_cast<df::building_civzonest>(bld);
if (zone)
on_civzone_delete(zone);
}
delete bld;

@ -134,7 +134,7 @@ dfhack_plugin(misery misery.cpp)
#dfhack_plugin(mode mode.cpp)
#dfhack_plugin(mousequery mousequery.cpp)
dfhack_plugin(nestboxes nestboxes.cpp)
dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static)
dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static lua)
dfhack_plugin(overlay overlay.cpp LINK_LIBRARIES lua)
dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua)
#dfhack_plugin(petcapRemover petcapRemover.cpp)

@ -32,7 +32,7 @@ function HotspotMenuWidget:overlay_onupdate()
end
function HotspotMenuWidget:overlay_trigger()
return MenuScreen{hotspot_frame=self.frame}:show()
return MenuScreen{hotspot=self}:show()
end
local dscreen = dfhack.screen
@ -74,9 +74,9 @@ local ARROW = string.char(26)
local MAX_LIST_WIDTH = 45
local MAX_LIST_HEIGHT = 15
Menu = defclass(MenuScreen, widgets.Panel)
Menu = defclass(Menu, widgets.Panel)
Menu.ATTRS{
hotspot_frame=DEFAULT_NIL,
hotspot=DEFAULT_NIL,
}
-- get a map from the binding string to a list of hotkey strings that all
@ -136,10 +136,10 @@ end
function Menu:init()
local hotkeys, bindings = getHotkeys()
local is_inverted = not not self.hotspot_frame.b
local is_inverted = not not self.hotspot.frame.b
local choices,list_width = get_choices(hotkeys, bindings, is_inverted)
local list_frame = copyall(self.hotspot_frame)
local list_frame = copyall(self.hotspot.frame)
local list_widget_frame = {h=math.min(#choices, MAX_LIST_HEIGHT)}
local quickstart_frame = {}
list_frame.w = list_width + 2
@ -248,7 +248,7 @@ function Menu:onInput(keys)
self:onSubmit2(list:getSelected())
return true
end
if not self:getMouseFramePos() then
if not self:getMouseFramePos() and not self.hotspot:getMousePos() then
self.parent_view:dismiss()
return true
end
@ -297,12 +297,12 @@ end
MenuScreen = defclass(MenuScreen, gui.ZScreen)
MenuScreen.ATTRS {
focus_path='hotkeys/menu',
hotspot_frame=DEFAULT_NIL,
hotspot=DEFAULT_NIL,
}
function MenuScreen:init()
self:addviews{
Menu{hotspot_frame=self.hotspot_frame},
Menu{hotspot=self.hotspot},
}
end

@ -0,0 +1,100 @@
local _ENV = mkmodule('plugins.orders')
local dialogs = require('gui.dialogs')
local gui = require('gui')
local overlay = require('plugins.overlay')
local widgets = require('gui.widgets')
--
-- OrdersOverlay
--
local function is_orders_panel_visible()
local info = df.global.game.main_interface.info
return info.open and info.current_mode == df.info_interface_mode_type.WORK_ORDERS
end
local function do_sort()
dfhack.run_command('orders', 'sort')
end
local function do_clear()
dialogs.showYesNoPrompt('Clear manager orders?',
'Are you sure you want to clear the manager orders?', nil,
function() dfhack.run_command('orders', 'clear') end)
end
local function do_import()
local output = dfhack.run_command_silent('orders', 'list')
dialogs.ListBox{
frame_title='Import Manager Orders',
with_filter=true,
choices=output:split('\n'),
on_select=function(idx, choice)
dfhack.run_command('orders', 'import', choice.text)
end,
}:show()
end
local function do_export()
dialogs.InputBox{
frame_title='Export Manager Orders',
on_input=function(text)
dfhack.run_command('orders', 'export', text)
end
}:show()
end
OrdersOverlay = defclass(OrdersOverlay, overlay.OverlayWidget)
OrdersOverlay.ATTRS{
default_pos={x=61,y=-6},
viewscreens='dwarfmode',
frame={w=30, h=4},
frame_style=gui.GREY_LINE_FRAME,
frame_background=gui.CLEAR_PEN,
}
function OrdersOverlay:init()
self:addviews{
widgets.HotkeyLabel{
frame={t=0, l=0},
label='import',
key='CUSTOM_CTRL_I',
on_activate=do_import,
},
widgets.HotkeyLabel{
frame={t=1, l=0},
label='export',
key='CUSTOM_CTRL_E',
on_activate=do_export,
},
widgets.HotkeyLabel{
frame={t=0, l=15},
label='sort',
key='CUSTOM_CTRL_O',
on_activate=do_sort,
},
widgets.HotkeyLabel{
frame={t=1, l=15},
label='clear',
key='CUSTOM_CTRL_C',
on_activate=do_clear,
},
}
end
function OrdersOverlay:render(dc)
if not is_orders_panel_visible() then return false end
OrdersOverlay.super.render(self, dc)
end
function OrdersOverlay:onInput(keys)
if not is_orders_panel_visible() then return false end
OrdersOverlay.super.onInput(self, keys)
end
OVERLAY_WIDGETS = {
overlay=OrdersOverlay,
}
return _ENV

@ -184,7 +184,6 @@ end
local function do_disable(args, quiet)
local disable_fn = function(name, db_entry)
if db_entry.widget.always_enabled then return end
overlay_config[name].enabled = false
if db_entry.widget.hotspot then
active_hotspot_widgets[name] = nil
@ -252,7 +251,7 @@ local function load_widget(name, widget_class)
local config = overlay_config[name]
config.pos = sanitize_pos(config.pos or widget.default_pos)
widget.frame = make_frame(config.pos, widget.frame)
if config.enabled or widget.always_enabled then
if config.enabled then
do_enable(name, true, true)
else
config.enabled = false
@ -492,7 +491,6 @@ OverlayWidget.ATTRS{
hotspot=false, -- whether to call overlay_onupdate on all screens
viewscreens={}, -- override with associated viewscreen or list of viewscrens
overlay_onupdate_max_freq_seconds=5, -- throttle calls to overlay_onupdate
always_enabled=false, -- for overlays that should never be disabled
}
function OverlayWidget:init()