Merge branch 'develop' into cycle-hotkey-rev

develop
John Cosker 2023-02-06 15:04:57 -05:00 committed by GitHub
commit 0769afc961
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 253 additions and 612 deletions

@ -25,9 +25,6 @@ keybinding add Ctrl-Shift-K gui/cp437-table
# customizable quick command list # customizable quick command list
keybinding add Ctrl-Shift-A gui/quickcmd keybinding add Ctrl-Shift-A gui/quickcmd
# an in-game init file editor
#keybinding add Alt-S@title|dwarfmode/Default|dungeonmode gui/settings-manager
###################### ######################
# dwarfmode bindings # # dwarfmode bindings #
@ -36,25 +33,21 @@ keybinding add Ctrl-Shift-A gui/quickcmd
# quicksave # quicksave
keybinding add Ctrl-Alt-S@dwarfmode quicksave keybinding add Ctrl-Alt-S@dwarfmode quicksave
# toggle the display of water level as 1-7 tiles
#keybinding add Ctrl-W@dwarfmode|dungeonmode twaterlvl
# designate the whole vein for digging # designate the whole vein for digging
#keybinding add Ctrl-V@dwarfmode digv keybinding add Ctrl-V@dwarfmode digv
#keybinding add Ctrl-Shift-V@dwarfmode "digv x" keybinding add Ctrl-Shift-V@dwarfmode "digv x"
# clean the selected tile of blood etc # clean the selected tile of blood etc
#keybinding add Ctrl-C spotclean keybinding add Ctrl-C spotclean
# destroy the selected item # destroy the selected item
#keybinding add Ctrl-K@dwarfmode autodump-destroy-item keybinding add Ctrl-K@dwarfmode autodump-destroy-item
# destroy items designated for dump in the selected tile # destroy items designated for dump in the selected tile
#keybinding add Ctrl-Shift-K@dwarfmode autodump-destroy-here keybinding add Ctrl-H@dwarfmode autodump-destroy-here
# apply blueprints to the map (Alt-F for compatibility with LNP Quickfort) # apply blueprints to the map
keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort
#keybinding add Alt-F@dwarfmode gui/quickfort
# show information collected by dwarfmonitor # show information collected by dwarfmonitor
#keybinding add Alt-M@dwarfmode/Default "dwarfmonitor prefs" #keybinding add Alt-M@dwarfmode/Default "dwarfmonitor prefs"

@ -37,15 +37,24 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Fixes ## Fixes
- ``Units::isFortControlled``: Account for agitated wildlife - ``Units::isFortControlled``: Account for agitated wildlife
- Fix right click sometimes closing both a DFHack window and a vanilla panel
## Misc Improvements ## Misc Improvements
- `automelt`: is now more resistent to savegame corruption - `automelt`: is now more resistent to savegame corruption
- `hotkeys`: DFHack logo is now hidden on screens where it covers important information when in the default position (e.g. when choosing an embark site)
- `autodump`: reinstate ``autodump-destroy-item``, hotkey: Ctrl-K
- `autodump`: new hotkey for ``autodump-destroy-here``: Ctrl-H
- `dig`: new hotkeys for vein designation on z-level (Ctrl-V) and vein designation across z-levels (Ctrl-Shift-V)
- `clean`: new hotkey for `spotclean`: Ctrl-C
- `autobutcher`: changed defaults from 5 females / 1 male to 4 females / 2 males so a single unfortunate accident doesn't leave players without a mating pair
- `autobutcher`: now immediately loads races available at game start into the watchlist
## Documentation ## Documentation
## API ## API
## Lua ## Lua
- `overlay`: overlay widgets can now specify focus paths for the viewscreens they attach to so they only appear in specific contexts. see `overlay-dev-guide` for details.
- ``widgets.CycleHotkeyLabel``: Added ``key_back`` optional parameter to cycle backwards. - ``widgets.CycleHotkeyLabel``: Added ``key_back`` optional parameter to cycle backwards.
## Removed ## Removed

@ -958,17 +958,18 @@ Screens
[1] = "dwarfmode/Info/CREATURES/CITIZEN" [1] = "dwarfmode/Info/CREATURES/CITIZEN"
[2] = "dwardmode/Squads" [2] = "dwardmode/Squads"
* ``dfhack.gui.matchFocusString(focus_string)`` * ``dfhack.gui.matchFocusString(focus_string[, viewscreen])``
Returns ``true`` if the given ``focus_string`` is found in the current Returns ``true`` if the given ``focus_string`` is found in the current
focus strings, or as a prefix to any of the focus strings, or ``false`` focus strings, or as a prefix to any of the focus strings, or ``false``
if no match is found. Matching is case insensitive. if no match is found. Matching is case insensitive. If ``viewscreen`` is
specified, gets the focus strings to match from the given viewscreen.
* ``dfhack.gui.getCurFocus([skip_dismissed])`` * ``dfhack.gui.getCurFocus([skip_dismissed])``
Returns the focus string of the current viewscreen. Returns the focus string of the current viewscreen.
* ``dfhack.gui.getViewscreenByType(type [, depth])`` * ``dfhack.gui.getViewscreenByType(type[, depth])``
Returns the topmost viewscreen out of the top ``depth`` viewscreens with Returns the topmost viewscreen out of the top ``depth`` viewscreens with
the specified type (e.g. ``df.viewscreen_titlest``), or ``nil`` if none match. the specified type (e.g. ``df.viewscreen_titlest``), or ``nil`` if none match.

@ -109,7 +109,11 @@ The ``overlay.OverlayWidget`` superclass defines the following class attributes:
``dwarfmode`` and the adventure mode map viewscreen would be ``dwarfmode`` and the adventure mode map viewscreen would be
``dungeonmode``. If there is only one viewscreen that this widget is ``dungeonmode``. If there is only one viewscreen that this widget is
associated with, it can be specified as a string instead of a list of associated with, it can be specified as a string instead of a list of
strings with a single element. strings with a single element. If you only want your widget to appear in
certain contexts, you can specify a focus path, in the same syntax as the
`keybinding` command. For example, ``dwarfmode/Info/CREATURES/CITIZEN`` will
ensure the overlay widget is only displayed when the "Citizens" subtab under
the "Units" panel is active.
- ``hotspot`` (default: ``false``) - ``hotspot`` (default: ``false``)
If set to ``true``, your widget's ``overlay_onupdate`` function will be If set to ``true``, your widget's ``overlay_onupdate`` function will be
called whenever the `overlay` plugin's ``plugin_onupdate()`` function is called whenever the `overlay` plugin's ``plugin_onupdate()`` function is

@ -18,8 +18,8 @@ watch list. Units will be ignored if they are:
Creatures who will not reproduce (because they're not interested in the Creatures who will not reproduce (because they're not interested in the
opposite sex or have been gelded) will be butchered before those who will. opposite sex or have been gelded) will be butchered before those who will.
Older adults and younger children will be butchered first if the population Older adults and younger children will be butchered first if the population
is above the target (defaults are: 1 male kid, 5 female kids, 1 male adult, is above the target (defaults are: 2 male kids, 4 female kids, 2 male adults,
5 female adults). Note that you may need to set a target above 1 to have a 4 female adults). Note that you may need to set a target above 1 to have a
reliable breeding population due to asexuality etc. See `fix-ster` if this is a reliable breeding population due to asexuality etc. See `fix-ster` if this is a
problem. problem.
@ -34,7 +34,7 @@ Usage
``autobutcher autowatch`` ``autobutcher autowatch``
Automatically add all new races (animals you buy from merchants, tame Automatically add all new races (animals you buy from merchants, tame
yourself, or get from migrants) to the watch list using the default target yourself, or get from migrants) to the watch list using the default target
counts. counts. This option is enabled by default.
``autobutcher noautowatch`` ``autobutcher noautowatch``
Stop auto-adding new races to the watch list. Stop auto-adding new races to the watch list.
``autobutcher target <fk> <mk> <fa> <ma> all|new|<race> [<race> ...]`` ``autobutcher target <fk> <mk> <fa> <ma> all|new|<race> [<race> ...]``
@ -108,4 +108,3 @@ fortress::
autobutcher target 2 2 4 2 ALPACA SHEEP LLAMA autobutcher target 2 2 4 2 ALPACA SHEEP LLAMA
autobutcher target 5 5 6 2 PIG autobutcher target 5 5 6 2 PIG
autobutcher target 0 0 0 0 new autobutcher target 0 0 0 0 new
autobutcher autowatch

@ -67,7 +67,7 @@ namespace DFHack
namespace Gui namespace Gui
{ {
DFHACK_EXPORT std::vector<std::string> getFocusStrings(df::viewscreen *top); DFHACK_EXPORT std::vector<std::string> getFocusStrings(df::viewscreen *top);
DFHACK_EXPORT bool matchFocusString(std::string focusString, bool prefixMatch = true); DFHACK_EXPORT bool matchFocusString(std::string focus_string, df::viewscreen *top = NULL);
// Full-screen item details view // Full-screen item details view

@ -789,6 +789,7 @@ function ZScreen:onInput(keys)
end end
if keys._MOUSE_R_DOWN then if keys._MOUSE_R_DOWN then
df.global.enabler.mouse_rbut_down = 0 df.global.enabler.mouse_rbut_down = 0
df.global.enabler.mouse_rbut = 0
end end
return return
end end

@ -808,7 +808,7 @@ local function scrollbar_get_max_pos_and_height(scrollbar)
local frame_body = scrollbar.frame_body local frame_body = scrollbar.frame_body
local scrollbar_body_height = (frame_body and frame_body.height or 3) - 2 local scrollbar_body_height = (frame_body and frame_body.height or 3) - 2
local height = math.max(1, math.floor( local height = math.max(2, math.floor(
(math.min(scrollbar.elems_per_page, scrollbar.num_elems) * scrollbar_body_height) / (math.min(scrollbar.elems_per_page, scrollbar.num_elems) * scrollbar_body_height) /
scrollbar.num_elems)) scrollbar.num_elems))

@ -1,302 +0,0 @@
local _ENV = mkmodule('makeown')
--[[
'tweak makeown' as a lua include
make_own(unit) -- removes foreign flags, sets civ_id to fort civ_id, and sets clothes ownership
make_citizen(unit) -- called by make_own if unit.race == fort race
eventually ought to migrate to hack/lua/plugins/tweak.lua
and local _ENV = mkmodule('plugin.tweak')
in order to link to functions in the compiled plugin (when/if they become available to lua)
--]]
local utils = require 'utils'
local function fix_clothing_ownership(unit)
-- extracted/translated from tweak makeown plugin
-- to be called by tweak-fixmigrant/makeown
-- units forced into the fort by removing the flags do not own their clothes
-- which has the result that they drop all their clothes and become unhappy because they are naked
-- so we need to make them own their clothes and add them to their uniform
local fixcount = 0 --int fixcount = 0;
for j=0,#unit.inventory-1 do --for(size_t j=0; j<unit->inventory.size(); j++)
local inv_item = unit.inventory[j] --unidf::unit_inventory_item* inv_item = unit->inventory[j];
local item = inv_item.item --df::item* item = inv_item->item;
-- unforbid items (for the case of kidnapping caravan escorts who have their stuff forbidden by default)
-- moved forbid false to inside if so that armor/weapons stay equiped
if inv_item.mode == df.unit_inventory_item.T_mode.Worn then --if(inv_item->mode == df::unit_inventory_item::T_mode::Worn)
-- ignore armor?
-- it could be leather boots, for example, in which case it would not be nice to forbid ownership
--if(item->getEffectiveArmorLevel() != 0)
-- continue;
if not dfhack.items.getOwner(item) then --if(!Items::getOwner(item))
if dfhack.items.setOwner(item,unit) then --if(Items::setOwner(item, unit))
item.flags.forbid = false --inv_item->item->flags.bits.forbid = 0;
-- add to uniform, so they know they should wear their clothes
unit.military.uniforms[0]:insert('#',item.id) --insert_into_vector(unit->military.uniforms[0], item->id);
fixcount = fixcount + 1 --fixcount++;
else
----out << "could not change ownership for item!" << endl;
print("Makeown: could not change ownership for an item!")
end
end
end
end
-- clear uniform_drop (without this they would drop their clothes and pick them up some time later)
-- dirty?
unit.military.uniform_drop:resize(0) --unit->military.uniform_drop.clear();
----out << "ownership for " << fixcount << " clothes fixed" << endl;
print("Makeown: claimed ownership for "..tostring(fixcount).." worn items")
--return true --return CR_OK;
end
local function entity_link(hf, eid, do_event, add, replace_idx)
do_event = (do_event == nil) and true or do_event
add = (add == nil) and true or add
replace_idx = replace_idx or -1
local link = add and df.histfig_entity_link_memberst:new() or df.histfig_entity_link_former_memberst:new()
link.entity_id = eid
if replace_idx > -1 then
local e = hf.entity_links[replace_idx]
link.link_strength = (e.link_strength > 3) and (e.link_strength - 2) or e.link_strength
hf.entity_links[replace_idx] = link -- replace member link with former member link
e:delete()
else
link.link_strength = 100
hf.entity_links:insert('#', link)
end
if do_event then
event = add and df.history_event_add_hf_entity_linkst:new() or df.history_event_remove_hf_entity_linkst:new()
event.year = df.global.cur_year
event.seconds = df.global.cur_year_tick
event.civ = eid
event.histfig = hf.id
event.link_type = 0
event.position_id = -1
event.id = df.global.hist_event_next_id
df.global.world.history.events:insert('#',event)
df.global.hist_event_next_id = df.global.hist_event_next_id + 1
end
end
local function change_state(hf, site_id, pos)
hf.info.unk_14.unk_0 = 3 -- state? arrived?
hf.info.unk_14.region:assign(pos)
hf.info.unk_14.site = site_id
event = df.history_event_change_hf_statest:new()
event.year = df.global.cur_year
event.seconds = df.global.cur_year_tick
event.hfid = hf.id
event.state = 3
event.site = site_id
event.region_pos:assign(pos)
event.substate = -1; event.region = -1; event.layer = -1;
event.id = df.global.hist_event_next_id
df.global.world.history.events:insert('#',event)
df.global.hist_event_next_id = df.global.hist_event_next_id + 1
end
function make_citizen(unit)
local dfg = df.global
local civ_id = dfg.ui.civ_id
local group_id = dfg.ui.group_id
local events = dfg.world.history.events
local fortent = dfg.ui.main.fortress_entity
local civent = fortent and df.historical_entity.find(fortent.entity_links[0].target)
-- utils.binsearch(dfg.world.entities.all, fortent.entity_links[0].target, 'id')
local event
local region_pos = df.world_site.find(dfg.ui.site_id).pos -- used with state events and hf state
local hf
-- assume that hf id 1 and hf id 2 are equal. I am unaware of instances of when they are not.
-- occationally a unit does not have both flags set (missing flags1.important_historical_figure)
-- and I don't know what that means yet.
if unit.flags1.important_historical_figure and unit.flags2.important_historical_figure then
-- aready hf, find it (unlikely to happen)
hf = utils.binsearch(dfg.world.history.figures, unit.hist_figure_id, 'id')
--elseif unit.flags1.important_historical_figure or unit.flags2.important_historical_figure then
-- something wrong, try to fix it?
--[[
if unit.hist_figure_id == -1 then
unit.hist_figure_id = unit.hist_figure_id2
end
if unit.hist_figure_id > -1 then
unit.hist_figure_id2 = unit.hist_figure_id
unit.flags1.important_historical_figure = true
unit.flags2.important_historical_figure = true
hf = utils.binsearch(dfg.world.history.figures, unit.hist_figure_id, 'id')
else
unit.flags1.important_historical_figure = false
unit.flags2.important_historical_figure = false
end
--]]
--else
-- make one
end
--local new_hf = false
if not hf then
--new_hf = true
hf = df.historical_figure:new()
hf.profession = unit.profession
hf.race = unit.race
hf.caste = unit.caste
hf.sex = unit.sex
hf.appeared_year = dfg.cur_year
hf.born_year = unit.birth_year
hf.born_seconds = unit.birth_time
hf.curse_year = unit.curse_year
hf.curse_seconds = unit.curse_time
hf.birth_year_bias=unit.bias_birth_bias
hf.birth_time_bias=unit.birth_time_bias
hf.old_year = unit.old_year
hf.old_seconds = unit.old_time
hf.died_year = -1
hf.died_seconds = -1
hf.name:assign(unit.name)
hf.civ_id = unit.civ_id
hf.population_id = unit.population_id
hf.breed_id = -1
hf.unit_id = unit.id
hf.id = dfg.hist_figure_next_id -- id must be set before adding links (for the events)
--history_event_add_hf_entity_linkst not reported for civ on starting 7
entity_link(hf, civ_id, false) -- so lets skip event here
entity_link(hf, group_id)
hf.info = df.historical_figure_info:new()
hf.info.unk_14 = df.historical_figure_info.T_unk_14:new() -- hf state?
--unk_14.region_id = -1; unk_14.beast_id = -1; unk_14.unk_14 = 0
hf.info.unk_14.unk_18 = -1; hf.info.unk_14.unk_1c = -1
-- set values that seem related to state and do event
change_state(hf, dfg.ui.site_id, region_pos)
--lets skip skills for now
--local skills = df.historical_figure_info.T_skills:new() -- skills snap shot
-- ...
--info.skills = skills
dfg.world.history.figures:insert('#', hf)
dfg.hist_figure_next_id = dfg.hist_figure_next_id + 1
--new_hf_loc = df.global.world.history.figures[#df.global.world.history.figures - 1]
fortent.histfig_ids:insert('#', hf.id)
fortent.hist_figures:insert('#', hf)
civent.histfig_ids:insert('#', hf.id)
civent.hist_figures:insert('#', hf)
unit.flags1.important_historical_figure = true
unit.flags2.important_historical_figure = true
unit.hist_figure_id = hf.id
unit.hist_figure_id2 = hf.id
print("Makeown-citizen: created historical figure")
else
-- only insert into civ/fort if not already there
-- Migrants change previous histfig_entity_link_memberst to histfig_entity_link_former_memberst
-- for group entities, add link_member for new group, and reports events for remove from group,
-- remove from civ, change state, add civ, and add group
hf.civ_id = civ_id -- ensure current civ_id
local found_civlink = false
local found_fortlink = false
local v = hf.entity_links
for k=#v-1,0,-1 do
if df.histfig_entity_link_memberst:is_instance(v[k]) then
entity_link(hf, v[k].entity_id, true, false, k)
end
end
if hf.info and hf.info.unk_14 then
change_state(hf, dfg.ui.site_id, region_pos)
-- leave info nil if not found for now
end
if not found_civlink then entity_link(hf,civ_id) end
if not found_fortlink then entity_link(hf,group_id) end
--change entity_links
local found = false
for _,v in ipairs(civent.histfig_ids) do
if v == hf.id then found = true; break end
end
if not found then
civent.histfig_ids:insert('#', hf.id)
civent.hist_figures:insert('#', hf)
end
found = false
for _,v in ipairs(fortent.histfig_ids) do
if v == hf.id then found = true; break end
end
if not found then
fortent.histfig_ids:insert('#', hf.id)
fortent.hist_figures:insert('#', hf)
end
print("Makeown-citizen: migrated historical figure")
end -- hf
local nemesis = dfhack.units.getNemesis(unit)
if not nemesis then
nemesis = df.nemesis_record:new()
nemesis.figure = hf
nemesis.unit = unit
nemesis.unit_id = unit.id
nemesis.save_file_id = civent.save_file_id
nemesis.unk10, nemesis.unk11, nemesis.unk12 = -1, -1, -1
--group_leader_id = -1
nemesis.id = dfg.nemesis_next_id
nemesis.member_idx = civent.next_member_idx
civent.next_member_idx = civent.next_member_idx + 1
dfg.world.nemesis.all:insert('#', nemesis)
dfg.nemesis_next_id = dfg.nemesis_next_id + 1
nemesis_link = df.general_ref_is_nemesisst:new()
nemesis_link.nemesis_id = nemesis.id
unit.general_refs:insert('#', nemesis_link)
--new_nemesis_loc = df.global.world.nemesis.all[#df.global.world.nemesis.all - 1]
fortent.nemesis_ids:insert('#', nemesis.id)
fortent.nemesis:insert('#', nemesis)
civent.nemesis_ids:insert('#', nemesis.id)
civent.nemesis:insert('#', nemesis)
print("Makeown-citizen: created nemesis entry")
else-- only insert into civ/fort if not already there
local found = false
for _,v in ipairs(civent.nemesis_ids) do
if v == nemesis.id then found = true; break end
end
if not found then
civent.nemesis_ids:insert('#', nemesis.id)
civent.nemesis:insert('#', nemesis)
end
found = false
for _,v in ipairs(fortent.nemesis_ids) do
if v == nemesis.id then found = true; break end
end
if not found then
fortent.nemesis_ids:insert('#', nemesis.id)
fortent.nemesis:insert('#', nemesis)
end
print("Makeown-citizen: migrated nemesis entry")
end -- nemesis
end
function make_own(unit)
--tweak makeown
unit.flags2.resident = false; unit.flags1.merchant = false; unit.flags1.forest = false;
unit.civ_id = df.global.plotinfo.civ_id
if unit.profession == df.profession.MERCHANT then unit.profession = df.profession.TRADER end
if unit.profession2 == df.profession.MERCHANT then unit.profession2 = df.profession.TRADER end
fix_clothing_ownership(unit)
if unit.race == df.global.plotinfo.race_id then
make_citizen(unit)
end
end
return _ENV

@ -57,6 +57,7 @@ using namespace DFHack;
#include "df/building_trapst.h" #include "df/building_trapst.h"
#include "df/building_type.h" #include "df/building_type.h"
#include "df/building_workshopst.h" #include "df/building_workshopst.h"
#include "df/cri_unitst.h"
#include "df/d_init.h" #include "df/d_init.h"
#include "df/game_mode.h" #include "df/game_mode.h"
#include "df/general_ref.h" #include "df/general_ref.h"
@ -459,7 +460,7 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
} }
if (!newFocusString.size()) { if (!newFocusString.size()) {
focusStrings.push_back(baseFocus); focusStrings.push_back(baseFocus + "/Default");
} }
} }
@ -475,12 +476,14 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dungeonmode)
} }
*/ */
bool Gui::matchFocusString(std::string focusString, bool prefixMatch) { bool Gui::matchFocusString(std::string focus_string, df::viewscreen *top) {
focusString = toLower(focusString); focus_string = toLower(focus_string);
std::vector<std::string> currentFocusStrings = getFocusStrings(getCurViewscreen(true)); if (!top)
top = getCurViewscreen(true);
std::vector<std::string> currentFocusStrings = getFocusStrings(top);
return std::find_if(currentFocusStrings.begin(), currentFocusStrings.end(), [&focusString, &prefixMatch](std::string item) { return std::find_if(currentFocusStrings.begin(), currentFocusStrings.end(), [&focus_string](std::string item) {
return prefixMatch ? prefix_matches(focusString, toLower(item)) : focusString == toLower(item); return prefix_matches(focus_string, toLower(item));
}) != currentFocusStrings.end(); }) != currentFocusStrings.end();
} }
@ -535,17 +538,7 @@ std::vector<std::string> Gui::getFocusStrings(df::viewscreen* top)
bool Gui::default_hotkey(df::viewscreen *top) bool Gui::default_hotkey(df::viewscreen *top)
{ {
// Default hotkey guard function return World::isFortressMode() || World::isAdventureMode();
for (;top ;top = top->parent)
{
if (strict_virtual_cast<df::viewscreen_dwarfmodest>(top))
return true;
/* TODO: understand how this changes for v50
if (strict_virtual_cast<df::viewscreen_dungeonmodest>(top))
return true;
*/
}
return false;
} }
bool Gui::anywhere_hotkey(df::viewscreen *) { bool Gui::anywhere_hotkey(df::viewscreen *) {
@ -553,24 +546,7 @@ bool Gui::anywhere_hotkey(df::viewscreen *) {
} }
bool Gui::dwarfmode_hotkey(df::viewscreen *top) { bool Gui::dwarfmode_hotkey(df::viewscreen *top) {
return World::isFortressMode(); return matchFocusString("dwarfmode", top);
}
bool Gui::unitjobs_hotkey(df::viewscreen *top)
{
/* TODO: understand how this changes for v50
// Require the unit or jobs list
return !!strict_virtual_cast<df::viewscreen_joblistst>(top) ||
!!strict_virtual_cast<df::viewscreen_unitlistst>(top);
*/ return false;
}
bool Gui::item_details_hotkey(df::viewscreen *top)
{
/* TODO: understand how this changes for v50
// Require the main dwarf mode screen
return !!strict_virtual_cast<df::viewscreen_itemst>(top);
*/ return false;
} }
static bool has_cursor() static bool has_cursor()
@ -595,164 +571,82 @@ bool Gui::workshop_job_hotkey(df::viewscreen *top)
if (!dwarfmode_hotkey(top)) if (!dwarfmode_hotkey(top))
return false; return false;
/* TODO: understand how this changes for v50 df::building *selected = getAnyBuilding(top);
using namespace ui_sidebar_mode;
using df::global::ui_workshop_in_add;
using df::global::ui_workshop_job_cursor;
switch (plotinfo->main.mode) {
case QueryBuilding:
{
if (!ui_workshop_job_cursor) // allow missing
return false;
df::building *selected = world->selected_building;
if (!virtual_cast<df::building_workshopst>(selected) && if (!virtual_cast<df::building_workshopst>(selected) &&
!virtual_cast<df::building_furnacest>(selected)) !virtual_cast<df::building_furnacest>(selected))
return false; return false;
// No jobs?
if (selected->jobs.empty() || if (selected->jobs.empty() ||
selected->jobs[0]->job_type == job_type::DestroyBuilding) selected->jobs[0]->job_type == job_type::DestroyBuilding)
return false; return false;
// Add job gui activated?
if (ui_workshop_in_add && *ui_workshop_in_add)
return false;
return true; return true;
};
default:
return false;
}
*/ return false;
} }
bool Gui::build_selector_hotkey(df::viewscreen *top) bool Gui::build_selector_hotkey(df::viewscreen *top)
{ {
if (!dwarfmode_hotkey(top)) using df::global::buildreq;
return false;
/* TODO: understand how this changes for v50 if (!dwarfmode_hotkey(top))
using namespace ui_sidebar_mode;
using df::global::ui_build_selector;
switch (plotinfo->main.mode) {
case Build:
{
if (!ui_build_selector) // allow missing
return false; return false;
// Not selecting, or no choices? if (buildreq->building_type < 0 ||
if (ui_build_selector->building_type < 0 || buildreq->stage != 2 ||
ui_build_selector->stage != 2 || buildreq->choices.empty())
ui_build_selector->choices.empty())
return false; return false;
return true; return true;
};
default:
return false;
}
*/ return false;
} }
bool Gui::view_unit_hotkey(df::viewscreen *top) bool Gui::view_unit_hotkey(df::viewscreen *top)
{ {
if (!dwarfmode_hotkey(top)) if (!dwarfmode_hotkey(top))
return false; return false;
/* TODO: understand how this changes for v50
using df::global::ui_selected_unit;
if (plotinfo->main.mode != ui_sidebar_mode::ViewUnits) return !!getAnyUnit(top);
return false;
if (!ui_selected_unit) // allow missing
return false;
return vector_get(world->units.active, *ui_selected_unit) != NULL;
*/ return false;
} }
bool Gui::unit_inventory_hotkey(df::viewscreen *top) bool Gui::any_job_hotkey(df::viewscreen *top)
{ {
using df::global::ui_unit_view_mode; return matchFocusString("dwarfmode/Info/JOBS", top)
|| matchFocusString("dwarfmode/Info/CREATURES/CITIZEN", top)
if (!view_unit_hotkey(top)) || workshop_job_hotkey(top);
return false;
if (!ui_unit_view_mode)
return false;
return ui_unit_view_mode->value == df::ui_unit_view_mode::Inventory;
} }
df::job *Gui::getSelectedWorkshopJob(color_ostream &out, bool quiet) df::job *Gui::getSelectedWorkshopJob(color_ostream &out, bool quiet)
{ {
using df::global::ui_workshop_job_cursor; auto bld = getSelectedBuilding(out, true);
if (!bld)
if (!workshop_job_hotkey(Core::getTopViewscreen())) {
if (!quiet)
out.printerr("Not in a workshop, or no job is highlighted.\n");
return NULL; return NULL;
}
df::building *selected = world->selected_building; // no way to select a specific job; just get the first one
int idx = *ui_workshop_job_cursor; return bld->jobs.size() ? bld->jobs[0] : NULL;
if (size_t(idx) >= selected->jobs.size())
{
out.printerr("Invalid job cursor index: %d\n", idx);
return NULL;
}
return selected->jobs[idx];
}
bool Gui::any_job_hotkey(df::viewscreen *top)
{
/* TODO: understand how this changes for v50
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_joblistst, top))
return vector_get(screen->jobs, screen->cursor_pos) != NULL;
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_unitlistst, top))
return vector_get(screen->jobs[screen->page], screen->cursor_pos[screen->page]) != NULL;
return workshop_job_hotkey(top);
*/ return false;
} }
df::job *Gui::getSelectedJob(color_ostream &out, bool quiet) df::job *Gui::getSelectedJob(color_ostream &out, bool quiet)
{ {
/* TODO: understand how this changes for v50 using df::global::game;
df::viewscreen *top = Core::getTopViewscreen();
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_jobst, top))
{
return screen->job;
}
if (VIRTUAL_CAST_VAR(joblist, df::viewscreen_joblistst, top))
{
df::job *job = vector_get(joblist->jobs, joblist->cursor_pos);
if (!job && !quiet) auto top = Core::getTopViewscreen();
out.printerr("Selected unit has no job\n"); if (auto dfscreen = dfhack_viewscreen::try_cast(top))
return dfscreen->getSelectedJob();
return job; if (matchFocusString("dwarfmode/Info/JOBS")) {
auto &cri_job = game->main_interface.info.jobs.cri_job;
// no way to select specific jobs; just get the first one
return cri_job.size() ? cri_job[0]->jb : NULL;
} }
else if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, top))
{ if (auto unit = getAnyUnit(top)) {
int page = unitlist->page; df::job *job = unit->job.current_job;
df::job *job = vector_get(unitlist->jobs[page], unitlist->cursor_pos[page]);
if (!job && !quiet) if (!job && !quiet)
out.printerr("Selected unit has no job\n"); out.printerr("Selected unit has no job\n");
return job; return job;
} }
else if (auto dfscreen = dfhack_viewscreen::try_cast(top))
return dfscreen->getSelectedJob();
else
return getSelectedWorkshopJob(out, quiet); return getSelectedWorkshopJob(out, quiet);
*/ return getSelectedWorkshopJob(out, quiet);
} }
df::unit *Gui::getAnyUnit(df::viewscreen *top) df::unit *Gui::getAnyUnit(df::viewscreen *top)

@ -107,6 +107,8 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
DEBUG(status,out).print("%s from the API; persisting\n", DEBUG(status,out).print("%s from the API; persisting\n",
is_enabled ? "enabled" : "disabled"); is_enabled ? "enabled" : "disabled");
set_config_bool(CONFIG_IS_ENABLED, is_enabled); set_config_bool(CONFIG_IS_ENABLED, is_enabled);
if (enable)
autobutcher_cycle(out);
} else { } else {
DEBUG(status,out).print("%s from the API, but already %s; no action\n", DEBUG(status,out).print("%s from the API, but already %s; no action\n",
is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled",
@ -130,11 +132,11 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) {
config = World::AddPersistentData(CONFIG_KEY); config = World::AddPersistentData(CONFIG_KEY);
set_config_bool(CONFIG_IS_ENABLED, is_enabled); set_config_bool(CONFIG_IS_ENABLED, is_enabled);
set_config_val(CONFIG_CYCLE_TICKS, 6000); set_config_val(CONFIG_CYCLE_TICKS, 6000);
set_config_bool(CONFIG_AUTOWATCH, false); set_config_bool(CONFIG_AUTOWATCH, true);
set_config_val(CONFIG_DEFAULT_FK, 5); set_config_val(CONFIG_DEFAULT_FK, 4);
set_config_val(CONFIG_DEFAULT_MK, 1); set_config_val(CONFIG_DEFAULT_MK, 2);
set_config_val(CONFIG_DEFAULT_FA, 5); set_config_val(CONFIG_DEFAULT_FA, 4);
set_config_val(CONFIG_DEFAULT_MA, 1); set_config_val(CONFIG_DEFAULT_MA, 2);
} }
// we have to copy our enabled flag into the global plugin variable, but // we have to copy our enabled flag into the global plugin variable, but

@ -143,6 +143,8 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
DEBUG(status,out).print("%s from the API; persisting\n", DEBUG(status,out).print("%s from the API; persisting\n",
is_enabled ? "enabled" : "disabled"); is_enabled ? "enabled" : "disabled");
set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); set_config_bool(config, CONFIG_IS_ENABLED, is_enabled);
if (enable)
do_cycle(out, true);
} else { } else {
DEBUG(status,out).print("%s from the API, but already %s; no action\n", DEBUG(status,out).print("%s from the API, but already %s; no action\n",
is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled",

@ -287,13 +287,11 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCom
"Destroy items marked for dumping under the keyboard cursor.", "Destroy items marked for dumping under the keyboard cursor.",
df_autodump_destroy_here, df_autodump_destroy_here,
Gui::cursor_hotkey)); Gui::cursor_hotkey));
/* you can no longer select items
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"autodump-destroy-item", "autodump-destroy-item",
"Destroy the selected item.", "Destroy the selected item.",
df_autodump_destroy_item, df_autodump_destroy_item,
Gui::any_item_hotkey)); Gui::any_item_hotkey));
*/
return CR_OK; return CR_OK;
} }
@ -335,7 +333,6 @@ static command_result autodump_main(color_ostream &out, vector <string> & parame
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
//DFHack::VersionInfo *mem = Core::getInstance().vinfo;
if (!Maps::IsValid()) if (!Maps::IsValid())
{ {
out.printerr("Map is not available!\n"); out.printerr("Map is not available!\n");
@ -461,10 +458,11 @@ static int last_frame = 0;
command_result df_autodump_destroy_item(color_ostream &out, vector <string> & parameters) command_result df_autodump_destroy_item(color_ostream &out, vector <string> & parameters)
{ {
// HOTKEY COMMAND; CORE ALREADY SUSPENDED
if (!parameters.empty()) if (!parameters.empty())
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
CoreSuspender suspend;
df::item *item = Gui::getSelectedItem(out); df::item *item = Gui::getSelectedItem(out);
if (!item) if (!item)
return CR_FAILURE; return CR_FAILURE;

@ -1,37 +1,21 @@
#include "Debug.h" #include "Debug.h"
#include "LuaTools.h" #include "LuaTools.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "TileTypes.h"
#include "modules/Buildings.h" #include "modules/Buildings.h"
#include "modules/Maps.h"
#include "modules/Items.h" #include "modules/Items.h"
#include "modules/World.h" #include "modules/World.h"
#include "modules/Designations.h"
#include "modules/Persistence.h" #include "modules/Persistence.h"
#include "modules/Units.h"
#include "modules/Screen.h"
#include "modules/Gui.h" #include "modules/Gui.h"
// #include "uicommon.h"
#include "df/world.h" #include "df/world.h"
#include "df/building.h"
#include "df/world_raws.h"
#include "df/building_def.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/building_stockpilest.h" #include "df/building_stockpilest.h"
#include "df/plotinfost.h"
#include "df/item_quality.h" #include "df/item_quality.h"
#include <map> #include <map>
#include <unordered_map> #include <unordered_map>
using df::building_stockpilest;
using std::map; using std::map;
using std::multimap;
using std::pair;
using std::string; using std::string;
using std::unordered_map; using std::unordered_map;
using std::vector; using std::vector;
@ -41,10 +25,7 @@ using namespace df::enums;
DFHACK_PLUGIN("automelt"); DFHACK_PLUGIN("automelt");
DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(cursor);
REQUIRE_GLOBAL(plotinfo);
namespace DFHack namespace DFHack
{ {
@ -57,16 +38,12 @@ static const string CONFIG_KEY = string(plugin_name) + "/config";
static const string STOCKPILE_CONFIG_KEY_PREFIX = string(plugin_name) + "/stockpile/"; static const string STOCKPILE_CONFIG_KEY_PREFIX = string(plugin_name) + "/stockpile/";
static PersistentDataItem config; static PersistentDataItem config;
// static vector<PersistentDataItem> watched_stockpiles;
// static unordered_map<int, size_t> watched_stockpiles_indices;
static unordered_map<int32_t, PersistentDataItem> watched_stockpiles; static unordered_map<int32_t, PersistentDataItem> watched_stockpiles;
enum StockpileConfigValues enum StockpileConfigValues
{ {
STOCKPILE_CONFIG_ID = 0, STOCKPILE_CONFIG_ID = 0,
STOCKPILE_CONFIG_MONITORED = 1, STOCKPILE_CONFIG_MONITORED = 1,
}; };
static int get_config_val(PersistentDataItem &c, int index) static int get_config_val(PersistentDataItem &c, int index)
@ -115,8 +92,8 @@ static void remove_stockpile_config(color_ostream &out, int id)
watched_stockpiles.erase(id); watched_stockpiles.erase(id);
} }
static bool isStockpile(df::building * building) { static bool isStockpile(df::building * bld) {
return building->getType() == df::building_type::Stockpile; return bld && bld->getType() == df::building_type::Stockpile;
} }
static void validate_stockpile_configs(color_ostream &out) static void validate_stockpile_configs(color_ostream &out)
@ -124,7 +101,7 @@ static void validate_stockpile_configs(color_ostream &out)
for (auto &c : watched_stockpiles) { for (auto &c : watched_stockpiles) {
int id = get_config_val(c.second, STOCKPILE_CONFIG_ID); int id = get_config_val(c.second, STOCKPILE_CONFIG_ID);
auto bld = df::building::find(id); auto bld = df::building::find(id);
if (!bld || !isStockpile(bld)) if (!isStockpile(bld))
remove_stockpile_config(out, id); remove_stockpile_config(out, id);
} }
} }
@ -135,7 +112,7 @@ static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
static command_result do_command(color_ostream &out, vector<string> &parameters); static command_result do_command(color_ostream &out, vector<string> &parameters);
static int32_t do_cycle(color_ostream &out); static int32_t do_cycle(color_ostream &out);
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) DFhackCExport command_result plugin_init(color_ostream &out, vector<PluginCommand> &commands)
{ {
DEBUG(status, out).print("initializing %s\n", plugin_name); DEBUG(status, out).print("initializing %s\n", plugin_name);
@ -222,7 +199,6 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
return CR_OK; return CR_OK;
} }
static bool call_automelt_lua(color_ostream *out, const char *fn_name, static bool call_automelt_lua(color_ostream *out, const char *fn_name,
int nargs = 0, int nres = 0, int nargs = 0, int nres = 0,
Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA, Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA,
@ -268,6 +244,8 @@ static command_result do_command(color_ostream &out, vector<string> &parameters)
static inline bool is_metal_item(df::item *item) static inline bool is_metal_item(df::item *item)
{ {
if (!item)
return false;
MaterialInfo mat(item); MaterialInfo mat(item);
return (mat.getCraftClass() == craft_material_class::Metal); return (mat.getCraftClass() == craft_material_class::Metal);
} }
@ -307,6 +285,9 @@ static inline bool can_melt(df::item *item)
{ {
static const BadFlagsCanMelt bad_flags; static const BadFlagsCanMelt bad_flags;
if (!is_metal_item(item))
return false;
if (item->flags.whole & bad_flags.whole) if (item->flags.whole & bad_flags.whole)
return false; return false;
@ -315,9 +296,6 @@ static inline bool can_melt(df::item *item)
if (t == df::enums::item_type::BOX || t == df::enums::item_type::BAR) if (t == df::enums::item_type::BOX || t == df::enums::item_type::BAR)
return false; return false;
if (!is_metal_item(item))
return false;
for (auto &g : item->general_refs) for (auto &g : item->general_refs)
{ {
switch (g->getType()) switch (g->getType())
@ -372,7 +350,7 @@ static int mark_item(color_ostream &out, df::item *item, BadFlagsMarkItem bad_fl
{ {
DEBUG(perf,out).print("assignedToStockpile\n"); DEBUG(perf,out).print("assignedToStockpile\n");
size_t marked_count = 0; size_t marked_count = 0;
std::vector<df::item *> contents; vector<df::item *> contents;
Items::getContainedItems(item, &contents); Items::getContainedItems(item, &contents);
for (auto child = contents.begin(); child != contents.end(); child++) for (auto child = contents.begin(); child != contents.end(); child++)
{ {
@ -414,7 +392,6 @@ static int mark_item(color_ostream &out, df::item *item, BadFlagsMarkItem bad_fl
} }
static int32_t mark_all_in_stockpile(color_ostream &out, PersistentDataItem & stockpile, int32_t &premarked_item_count, int32_t &item_count, map<int32_t, bool> &tracked_item_map, bool should_melt) static int32_t mark_all_in_stockpile(color_ostream &out, PersistentDataItem & stockpile, int32_t &premarked_item_count, int32_t &item_count, map<int32_t, bool> &tracked_item_map, bool should_melt)
{ {
DEBUG(perf,out).print("%s running mark_all_in_stockpile\nshould_melt=%d\n", plugin_name, should_melt); DEBUG(perf,out).print("%s running mark_all_in_stockpile\nshould_melt=%d\n", plugin_name, should_melt);
@ -429,10 +406,8 @@ static int32_t mark_all_in_stockpile(color_ostream &out, PersistentDataItem & st
int spid = get_config_val(stockpile, STOCKPILE_CONFIG_ID); int spid = get_config_val(stockpile, STOCKPILE_CONFIG_ID);
auto found = df::building::find(spid); auto found = df::building::find(spid);
if (!isStockpile(found)){ if (!isStockpile(found))
return 0; return 0;
}
df::building_stockpilest * pile_cast = virtual_cast<df::building_stockpilest>(found); df::building_stockpilest * pile_cast = virtual_cast<df::building_stockpilest>(found);
@ -451,7 +426,6 @@ static int32_t mark_all_in_stockpile(color_ostream &out, PersistentDataItem & st
return marked_count; return marked_count;
} }
static int32_t scan_stockpiles(color_ostream &out, bool should_melt, map<int32_t, int32_t> &item_count_piles, map<int32_t, int32_t> &premarked_item_count_piles, static int32_t scan_stockpiles(color_ostream &out, bool should_melt, map<int32_t, int32_t> &item_count_piles, map<int32_t, int32_t> &premarked_item_count_piles,
map<int32_t, int32_t> &marked_item_count_piles, map<int32_t, bool> &tracked_item_map) { map<int32_t, int32_t> &marked_item_count_piles, map<int32_t, bool> &tracked_item_map) {
DEBUG(perf,out).print("running scan_stockpiles\n"); DEBUG(perf,out).print("running scan_stockpiles\n");
@ -518,8 +492,6 @@ static int32_t scan_count_all(color_ostream &out, bool should_melt, int32_t &mar
map<int32_t, bool> tracked_item_map_piles; map<int32_t, bool> tracked_item_map_piles;
tracked_item_map_piles.clear();
newly_marked_items_piles = scan_stockpiles(out, should_melt, item_count_piles, premarked_item_count_piles, marked_item_count_piles, tracked_item_map_piles); newly_marked_items_piles = scan_stockpiles(out, should_melt, item_count_piles, premarked_item_count_piles, marked_item_count_piles, tracked_item_map_piles);
marked_item_count_global = scan_all_melt_designated(out, tracked_item_map_piles); marked_item_count_global = scan_all_melt_designated(out, tracked_item_map_piles);
@ -557,20 +529,18 @@ static int32_t do_cycle(color_ostream &out) {
} }
static int getSelectedStockpile(color_ostream &out) { static int getSelectedStockpile(color_ostream &out) {
df::building *selected_bldg = NULL; df::building *bld = Gui::getSelectedBuilding(out, true);
selected_bldg = Gui::getSelectedBuilding(out, true); if (!isStockpile(bld)) {
if (selected_bldg->getType() != df::building_type::Stockpile) {
DEBUG(status,out).print("Selected building is not stockpile\n"); DEBUG(status,out).print("Selected building is not stockpile\n");
return -1; return -1;
} }
return selected_bldg->id; return bld->id;
} }
static PersistentDataItem *getSelectedStockpileConfig(color_ostream &out) { static PersistentDataItem *getSelectedStockpileConfig(color_ostream &out) {
int32_t bldg_id = getSelectedStockpile(out); int32_t bldg_id = getSelectedStockpile(out);
if (bldg_id == -1) { if (bldg_id == -1) {
DEBUG(status,out).print("Selected bldg invalid\n");
return NULL; return NULL;
} }
@ -579,11 +549,10 @@ static PersistentDataItem *getSelectedStockpileConfig(color_ostream &out) {
if (watched_stockpiles.count(bldg_id)) { if (watched_stockpiles.count(bldg_id)) {
c = &(watched_stockpiles[bldg_id]); c = &(watched_stockpiles[bldg_id]);
return c; return c;
} else {
DEBUG(status,out).print("No existing config\n");
return NULL;
} }
DEBUG(status,out).print("No existing config\n");
return NULL;
} }
static void push_stockpile_config(lua_State *L, int id, bool monitored) { static void push_stockpile_config(lua_State *L, int id, bool monitored) {
@ -671,7 +640,7 @@ static void automelt_setStockpileConfig(color_ostream &out, int id, bool monitor
DEBUG(status,out).print("entering automelt_setStockpileConfig for id=%d and monitored=%d\n", id, monitored); DEBUG(status,out).print("entering automelt_setStockpileConfig for id=%d and monitored=%d\n", id, monitored);
validate_stockpile_configs(out); validate_stockpile_configs(out);
auto bldg = df::building::find(id); auto bldg = df::building::find(id);
bool isInvalidStockpile = !bldg || !isStockpile(bldg); bool isInvalidStockpile = !isStockpile(bldg);
bool hasNoData = !monitored; bool hasNoData = !monitored;
if (isInvalidStockpile || hasNoData) { if (isInvalidStockpile || hasNoData) {
DEBUG(cycle,out).print("calling remove_stockpile_config with id=%d monitored=%d\n", id, monitored); DEBUG(cycle,out).print("calling remove_stockpile_config with id=%d monitored=%d\n", id, monitored);
@ -767,7 +736,6 @@ static int automelt_getSelectedStockpileConfig(lua_State *L){
return 1; return 1;
} }
//TODO
static int automelt_getItemCountsAndStockpileConfigs(lua_State *L) { static int automelt_getItemCountsAndStockpileConfigs(lua_State *L) {
color_ostream *out = Lua::GetOutput(L); color_ostream *out = Lua::GetOutput(L);
if (!out) if (!out)

@ -90,6 +90,8 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
is_enabled = enable; is_enabled = enable;
DEBUG(status, out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled"); DEBUG(status, out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled");
set_config_bool(CONFIG_IS_ENABLED, is_enabled); set_config_bool(CONFIG_IS_ENABLED, is_enabled);
if (enable)
do_cycle(out);
} }
else else
{ {

@ -74,8 +74,6 @@ command_result df_cleanowned (color_ostream &out, vector <string> & parameters)
return CR_FAILURE; return CR_FAILURE;
} }
out.print("Found total %zd items.\n", world->items.all.size());
for (std::size_t i=0; i < world->items.all.size(); i++) for (std::size_t i=0; i < world->items.all.size(); i++)
{ {
df::item * item = world->items.all[i]; df::item * item = world->items.all[i];

@ -69,7 +69,6 @@ public:
enum cstate { INACTIVE, ACTIVE, SELECTED }; enum cstate { INACTIVE, ACTIVE, SELECTED };
virtual string get_id() = 0; virtual string get_id() = 0;
virtual string get_focus_string() = 0; virtual string get_focus_string() = 0;
virtual bool match_prefix() = 0;
virtual bool set_state(cstate) = 0; virtual bool set_state(cstate) = 0;
static bool set_state(string id, cstate state) static bool set_state(string id, cstate state)
@ -305,7 +304,7 @@ public:
conf_wrapper *wrapper = confirmations[this->get_id()]; conf_wrapper *wrapper = confirmations[this->get_id()];
if(wrapper->is_paused()) { if(wrapper->is_paused()) {
std::string concernedFocus = this->get_focus_string(); std::string concernedFocus = this->get_focus_string();
if(!Gui::matchFocusString(this->get_focus_string(), this->match_prefix())) if(!Gui::matchFocusString(this->get_focus_string()))
wrapper->set_paused(false); wrapper->set_paused(false);
return false; return false;
} else if (state == INACTIVE) } else if (state == INACTIVE)
@ -469,7 +468,6 @@ public:
} }
string get_id() override = 0; string get_id() override = 0;
string get_focus_string() override = 0; string get_focus_string() override = 0;
bool match_prefix() override = 0;
#define CONF_LUA_START using namespace conf_lua; Lua::StackUnwinder unwind(l_state); push(screen); push(get_id()); #define CONF_LUA_START using namespace conf_lua; Lua::StackUnwinder unwind(l_state); push(screen); push(get_id());
bool intercept_key (df::interface_key key) bool intercept_key (df::interface_key key)
{ {
@ -560,7 +558,6 @@ static int conf_register_##cls = conf_register(&cls##_instance, {\
class confirmation_##cls : public confirmation<df::screen> { \ class confirmation_##cls : public confirmation<df::screen> { \
virtual string get_id() { static string id = char_replace(#cls, '_', '-'); return id; } \ virtual string get_id() { static string id = char_replace(#cls, '_', '-'); return id; } \
virtual string get_focus_string() { return focusString; } \ virtual string get_focus_string() { return focusString; } \
virtual bool match_prefix() { return focusString[strlen(focusString) - 1] == '*'; } \
}; \ }; \
IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, 0); IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, 0);
@ -585,7 +582,7 @@ DEFINE_CONFIRMATION(haul_delete_stop, viewscreen_dwarfmodest, "dwarfmode/Hau
DEFINE_CONFIRMATION(depot_remove, viewscreen_dwarfmodest, "dwarfmode/ViewSheets/BUILDING"); DEFINE_CONFIRMATION(depot_remove, viewscreen_dwarfmodest, "dwarfmode/ViewSheets/BUILDING");
DEFINE_CONFIRMATION(squad_disband, viewscreen_dwarfmodest, "dwarfmode/Squads"); DEFINE_CONFIRMATION(squad_disband, viewscreen_dwarfmodest, "dwarfmode/Squads");
DEFINE_CONFIRMATION(order_remove, viewscreen_dwarfmodest, "dwarfmode/Info/WORK_ORDERS"); DEFINE_CONFIRMATION(order_remove, viewscreen_dwarfmodest, "dwarfmode/Info/WORK_ORDERS");
DEFINE_CONFIRMATION(zone_remove, viewscreen_dwarfmodest, "dwarfmode/Zone*"); DEFINE_CONFIRMATION(zone_remove, viewscreen_dwarfmodest, "dwarfmode/Zone");
DEFINE_CONFIRMATION(burrow_remove, viewscreen_dwarfmodest, "dwarfmode/Burrow"); DEFINE_CONFIRMATION(burrow_remove, viewscreen_dwarfmodest, "dwarfmode/Burrow");
DEFINE_CONFIRMATION(stockpile_remove, viewscreen_dwarfmodest, "dwarfmode/Some/Stockpile"); DEFINE_CONFIRMATION(stockpile_remove, viewscreen_dwarfmodest, "dwarfmode/Some/Stockpile");

@ -6,6 +6,7 @@
// savegame that had this plugin enabled is loaded. // savegame that had this plugin enabled is loaded.
#include <string> #include <string>
#include <unordered_map>
#include <vector> #include <vector>
#include "df/world.h" #include "df/world.h"
@ -18,6 +19,7 @@
#include "modules/World.h" #include "modules/World.h"
using std::string; using std::string;
using std::unordered_map;
using std::vector; using std::vector;
using namespace DFHack; using namespace DFHack;
@ -38,25 +40,50 @@ namespace DFHack {
} }
static const string CONFIG_KEY = string(plugin_name) + "/config"; static const string CONFIG_KEY = string(plugin_name) + "/config";
static const string ELEM_CONFIG_KEY_PREFIX = string(plugin_name) + "/elem/";
static PersistentDataItem config; static PersistentDataItem config;
static unordered_map<int, PersistentDataItem> elems;
enum ConfigValues { enum ConfigValues {
CONFIG_IS_ENABLED = 0, CONFIG_IS_ENABLED = 0,
CONFIG_SOMETHING_ELSE = 1, CONFIG_SOMETHING_ELSE = 1,
}; };
static int get_config_val(int index) {
if (!config.isValid()) enum ElemConfigValues {
ELEM_CONFIG_ID = 0,
ELEM_CONFIG_SOMETHING_ELSE = 1,
};
static int get_config_val(PersistentDataItem &c, int index) {
if (!c.isValid())
return -1; return -1;
return config.ival(index); return c.ival(index);
} }
static bool get_config_bool(int index) { static bool get_config_bool(PersistentDataItem &c, int index) {
return get_config_val(index) == 1; return get_config_val(c, index) == 1;
} }
static void set_config_val(int index, int value) { static void set_config_val(PersistentDataItem &c, int index, int value) {
if (config.isValid()) if (c.isValid())
config.ival(index) = value; c.ival(index) = value;
} }
static void set_config_bool(int index, bool value) { static void set_config_bool(PersistentDataItem &c, int index, bool value) {
set_config_val(index, value ? 1 : 0); set_config_val(c, index, value ? 1 : 0);
}
static PersistentDataItem & ensure_elem_config(color_ostream &out, int id) {
if (elems.count(id))
return elems[id];
string keyname = ELEM_CONFIG_KEY_PREFIX + int_to_string(id);
DEBUG(config,out).print("creating new persistent key for elem id %d\n", id);
elems.emplace(id, World::GetPersistentData(keyname, NULL));
return elems[id];
}
static void remove_elem_config(color_ostream &out, int id) {
if (!elems.count(id))
return;
DEBUG(config,out).print("removing persistent key for elem id %d\n", id);
World::DeletePersistentData(elems[id]);
elems.erase(id);
} }
static const int32_t CYCLE_TICKS = 1200; // one day static const int32_t CYCLE_TICKS = 1200; // one day
@ -87,7 +114,9 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
is_enabled = enable; is_enabled = enable;
DEBUG(config,out).print("%s from the API; persisting\n", DEBUG(config,out).print("%s from the API; persisting\n",
is_enabled ? "enabled" : "disabled"); is_enabled ? "enabled" : "disabled");
set_config_bool(CONFIG_IS_ENABLED, is_enabled); set_config_bool(config, CONFIG_IS_ENABLED, is_enabled);
if (enable)
do_cycle(out);
} else { } else {
DEBUG(config,out).print("%s from the API, but already %s; no action\n", DEBUG(config,out).print("%s from the API, but already %s; no action\n",
is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled",
@ -109,16 +138,27 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) {
if (!config.isValid()) { if (!config.isValid()) {
DEBUG(config,out).print("no config found in this save; initializing\n"); DEBUG(config,out).print("no config found in this save; initializing\n");
config = World::AddPersistentData(CONFIG_KEY); config = World::AddPersistentData(CONFIG_KEY);
set_config_bool(CONFIG_IS_ENABLED, is_enabled); set_config_bool(config, CONFIG_IS_ENABLED, is_enabled);
set_config_val(CONFIG_SOMETHING_ELSE, 6000); set_config_val(config, CONFIG_SOMETHING_ELSE, 6000);
} }
// we have to copy our enabled flag into the global plugin variable, but // we have to copy our enabled flag into the global plugin variable, but
// all the other state we can directly read/modify from the persistent // all the other state we can directly read/modify from the persistent
// data structure. // data structure.
is_enabled = get_config_bool(CONFIG_IS_ENABLED); is_enabled = get_config_bool(config, CONFIG_IS_ENABLED);
DEBUG(config,out).print("loading persisted enabled state: %s\n", DEBUG(config,out).print("loading persisted enabled state: %s\n",
is_enabled ? "true" : "false"); is_enabled ? "true" : "false");
// load other config elements, if applicable
elems.clear();
vector<PersistentDataItem> elem_configs;
World::GetPersistentData(&elem_configs, ELEM_CONFIG_KEY_PREFIX, true);
const size_t num_elem_configs = elem_configs.size();
for (size_t idx = 0; idx < num_elem_configs; ++idx) {
auto &c = elem_configs[idx];
elems.emplace(get_config_val(c, ELEM_CONFIG_ID), c);
}
return CR_OK; return CR_OK;
} }
@ -150,8 +190,8 @@ static command_result do_command(color_ostream &out, vector<string> &parameters)
// TODO: configuration logic // TODO: configuration logic
// simple commandline parsing can be done in C++, but there are lua libraries // simple commandline parsing can be done in C++, but there are lua libraries
// that can easily handle more complex commandlines. see the blueprint plugin // that can easily handle more complex commandlines. see the seedwatch plugin
// for an example. // for a simple example.
return CR_OK; return CR_OK;
} }

@ -4,16 +4,11 @@ local gui = require('gui')
local overlay = require('plugins.overlay') local overlay = require('plugins.overlay')
local widgets = require('gui.widgets') local widgets = require('gui.widgets')
local function is_labor_panel_visible()
local info = df.global.game.main_interface.info
return info.open and info.current_mode == df.info_interface_mode_type.LABOR
end
AutolaborOverlay = defclass(AutolaborOverlay, overlay.OverlayWidget) AutolaborOverlay = defclass(AutolaborOverlay, overlay.OverlayWidget)
AutolaborOverlay.ATTRS{ AutolaborOverlay.ATTRS{
default_pos={x=7,y=-13}, default_pos={x=7,y=-13},
default_enabled=true, default_enabled=true,
viewscreens='dwarfmode', viewscreens='dwarfmode/Info/LABOR',
frame={w=29, h=5}, frame={w=29, h=5},
frame_style=gui.MEDIUM_FRAME, frame_style=gui.MEDIUM_FRAME,
frame_background=gui.CLEAR_PEN, frame_background=gui.CLEAR_PEN,
@ -34,9 +29,7 @@ function AutolaborOverlay:init()
end end
function AutolaborOverlay:render(dc) function AutolaborOverlay:render(dc)
if not is_labor_panel_visible() or not isEnabled() then if not isEnabled() then return false end
return false
end
AutolaborOverlay.super.render(self, dc) AutolaborOverlay.super.render(self, dc)
end end

@ -17,9 +17,13 @@ end
local function do_set_stockpile_config(var_name, val, stockpiles) local function do_set_stockpile_config(var_name, val, stockpiles)
for _,bspec in ipairs(argparse.stringList(stockpiles)) do for _,bspec in ipairs(argparse.stringList(stockpiles)) do
local config = automelt_getStockpileConfig(bspec) local config = automelt_getStockpileConfig(bspec)
if not config then
dfhack.printerr('invalid stockpile: '..tostring(bspec))
else
config[var_name] = val config[var_name] = val
automelt_setStockpileConfig(config.id, config.monitor, config.melt) automelt_setStockpileConfig(config.id, config.monitor, config.melt)
end end
end
end end

@ -14,7 +14,22 @@ HotspotMenuWidget.ATTRS{
default_pos={x=2,y=2}, default_pos={x=2,y=2},
default_enabled=true, default_enabled=true,
hotspot=true, hotspot=true,
viewscreens='all', viewscreens={
-- 'choose_start_site', -- conflicts with vanilla panel layouts
'choose_game_type',
'dwarfmode',
'export_region',
'game_cleaner',
'initial_prep',
'legends',
'loadgame',
-- 'new_region', -- conflicts with vanilla panel layouts
'savegame',
'setupdwarfgame',
'title',
'update_region',
'world'
},
overlay_onupdate_max_freq_seconds=0, overlay_onupdate_max_freq_seconds=0,
frame={w=4, h=3} frame={w=4, h=3}
} }

@ -9,11 +9,6 @@ local widgets = require('gui.widgets')
-- OrdersOverlay -- 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() local function do_sort()
dfhack.run_command('orders', 'sort') dfhack.run_command('orders', 'sort')
end end
@ -49,7 +44,7 @@ OrdersOverlay = defclass(OrdersOverlay, overlay.OverlayWidget)
OrdersOverlay.ATTRS{ OrdersOverlay.ATTRS{
default_pos={x=53,y=-6}, default_pos={x=53,y=-6},
default_enabled=true, default_enabled=true,
viewscreens='dwarfmode', viewscreens='dwarfmode/Info/WORK_ORDERS',
frame={w=30, h=4}, frame={w=30, h=4},
frame_style=gui.MEDIUM_FRAME, frame_style=gui.MEDIUM_FRAME,
frame_background=gui.CLEAR_PEN, frame_background=gui.CLEAR_PEN,
@ -84,16 +79,6 @@ function OrdersOverlay:init()
} }
end 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_WIDGETS = {
overlay=OrdersOverlay, overlay=OrdersOverlay,
} }

@ -81,19 +81,18 @@ function normalize_list(element_or_list)
return {element_or_list} return {element_or_list}
end end
-- normalize "short form" viewscreen names to "long form" -- normalize "short form" viewscreen names to "long form" and remove any focus
local function normalize_viewscreen_name(vs_name) local function normalize_viewscreen_name(vs_name)
if vs_name == 'all' or vs_name:match('viewscreen_.*st') then if vs_name == 'all' or vs_name:match('^viewscreen_.*st') then
return vs_name return vs_name:match('^[^/]+')
end end
return 'viewscreen_' .. vs_name .. 'st' return 'viewscreen_' .. vs_name:match('^[^/]+') .. 'st'
end end
-- reduce "long form" viewscreen names to "short form" -- reduce "long form" viewscreen names to "short form"; keep focus
function simplify_viewscreen_name(vs_name) function simplify_viewscreen_name(vs_name)
_,_,short_name = vs_name:find('^viewscreen_(.*)st$') local short_name = vs_name:match('^viewscreen_([^/]+)st')
if short_name then return short_name end return short_name or vs_name
return vs_name
end end
local function is_empty(tbl) local function is_empty(tbl)
@ -241,10 +240,23 @@ local function do_list(args)
end end
end end
local function get_focus_strings(viewscreens)
local focus_strings = nil
for _,vs in ipairs(viewscreens) do
if vs:match('/') then
focus_strings = focus_strings or {}
vs = simplify_viewscreen_name(vs)
table.insert(focus_strings, vs)
end
end
return focus_strings
end
local function load_widget(name, widget_class) local function load_widget(name, widget_class)
local widget = widget_class{name=name} local widget = widget_class{name=name}
widget_db[name] = { widget_db[name] = {
widget=widget, widget=widget,
focus_strings=get_focus_strings(normalize_list(widget.viewscreens)),
next_update_ms=widget.overlay_onupdate and 0 or math.huge, next_update_ms=widget.overlay_onupdate and 0 or math.huge,
} }
if not overlay_config[name] then overlay_config[name] = {} end if not overlay_config[name] then overlay_config[name] = {} end
@ -426,12 +438,30 @@ function update_hotspot_widgets()
end end
end end
local function matches_focus_strings(db_entry, vs_name)
if not db_entry.focus_strings then return true end
local matched = true
local simple_vs_name = simplify_viewscreen_name(vs_name)
for _,fs in ipairs(db_entry.focus_strings) do
if fs:startswith(simple_vs_name) then
matched = false
if dfhack.gui.matchFocusString(fs, vs) then
return true
end
end
end
return matched
end
local function _update_viewscreen_widgets(vs_name, vs, now_ms) local function _update_viewscreen_widgets(vs_name, vs, now_ms)
local vs_widgets = active_viewscreen_widgets[vs_name] local vs_widgets = active_viewscreen_widgets[vs_name]
if not vs_widgets then return end if not vs_widgets then return end
now_ms = now_ms or dfhack.getTickCount() now_ms = now_ms or dfhack.getTickCount()
for name,db_entry in pairs(vs_widgets) do for name,db_entry in pairs(vs_widgets) do
if do_update(name, db_entry, now_ms, vs) then return end if matches_focus_strings(db_entry, vs_name) and
do_update(name, db_entry, now_ms, vs) then
return
end
end end
return now_ms return now_ms
end end
@ -439,7 +469,9 @@ end
function update_viewscreen_widgets(vs_name, vs) function update_viewscreen_widgets(vs_name, vs)
if triggered_screen_has_lock() then return end if triggered_screen_has_lock() then return end
local now_ms = _update_viewscreen_widgets(vs_name, vs, nil) local now_ms = _update_viewscreen_widgets(vs_name, vs, nil)
if now_ms then
_update_viewscreen_widgets('all', vs, now_ms) _update_viewscreen_widgets('all', vs, now_ms)
end
end end
local function _feed_viewscreen_widgets(vs_name, keys) local function _feed_viewscreen_widgets(vs_name, keys)
@ -447,7 +479,8 @@ local function _feed_viewscreen_widgets(vs_name, keys)
if not vs_widgets then return false end if not vs_widgets then return false end
for _,db_entry in pairs(vs_widgets) do for _,db_entry in pairs(vs_widgets) do
local w = db_entry.widget local w = db_entry.widget
if detect_frame_change(w, function() return w:onInput(keys) end) then if matches_focus_strings(db_entry, vs_name) and
detect_frame_change(w, function() return w:onInput(keys) end) then
return true return true
end end
end end
@ -465,8 +498,10 @@ local function _render_viewscreen_widgets(vs_name, dc)
dc = dc or gui.Painter.new() dc = dc or gui.Painter.new()
for _,db_entry in pairs(vs_widgets) do for _,db_entry in pairs(vs_widgets) do
local w = db_entry.widget local w = db_entry.widget
if matches_focus_strings(db_entry, vs_name) then
detect_frame_change(w, function() w:render(dc) end) detect_frame_change(w, function() w:render(dc) end)
end end
end
end end
function render_viewscreen_widgets(vs_name) function render_viewscreen_widgets(vs_name)

@ -70,6 +70,8 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
DEBUG(config,out).print("%s from the API; persisting\n", DEBUG(config,out).print("%s from the API; persisting\n",
is_enabled ? "enabled" : "disabled"); is_enabled ? "enabled" : "disabled");
set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); set_config_bool(config, CONFIG_IS_ENABLED, is_enabled);
if (enable)
do_cycle(out);
} else { } else {
DEBUG(config,out).print("%s from the API, but already %s; no action\n", DEBUG(config,out).print("%s from the API, but already %s; no action\n",
is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled",

@ -99,7 +99,7 @@ static const int32_t CYCLE_TICKS = 1200;
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
static command_result do_command(color_ostream &out, vector<string> &parameters); static command_result do_command(color_ostream &out, vector<string> &parameters);
static void do_cycle(color_ostream &out, int32_t *num_enabled_seeds, int32_t *num_disabled_seeds); static void do_cycle(color_ostream &out, int32_t *num_enabled_seeds = NULL, int32_t *num_disabled_seeds = NULL);
static void seedwatch_setTarget(color_ostream &out, string name, int32_t num); static void seedwatch_setTarget(color_ostream &out, string name, int32_t num);
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) { DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
@ -149,7 +149,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
is_enabled ? "enabled" : "disabled"); is_enabled ? "enabled" : "disabled");
set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); set_config_bool(config, CONFIG_IS_ENABLED, is_enabled);
if (enable) if (enable)
seedwatch_setTarget(out, "all", DEFAULT_TARGET); do_cycle(out);
} else { } else {
DEBUG(config,out).print("%s from the API, but already %s; no action\n", DEBUG(config,out).print("%s from the API, but already %s; no action\n",
is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled",
@ -174,26 +174,27 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) {
world_plant_ids[plant->id] = i; world_plant_ids[plant->id] = i;
} }
watched_seeds.clear();
vector<PersistentDataItem> seed_configs;
World::GetPersistentData(&seed_configs, SEED_CONFIG_KEY_PREFIX, true);
const size_t num_seed_configs = seed_configs.size();
for (size_t idx = 0; idx < num_seed_configs; ++idx) {
auto &c = seed_configs[idx];
watched_seeds.emplace(get_config_val(c, SEED_CONFIG_ID), c);
}
config = World::GetPersistentData(CONFIG_KEY); config = World::GetPersistentData(CONFIG_KEY);
if (!config.isValid()) { if (!config.isValid()) {
DEBUG(config,out).print("no config found in this save; initializing\n"); DEBUG(config,out).print("no config found in this save; initializing\n");
config = World::AddPersistentData(CONFIG_KEY); config = World::AddPersistentData(CONFIG_KEY);
set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); set_config_bool(config, CONFIG_IS_ENABLED, is_enabled);
seedwatch_setTarget(out, "all", DEFAULT_TARGET);
} }
is_enabled = get_config_bool(config, CONFIG_IS_ENABLED); is_enabled = get_config_bool(config, CONFIG_IS_ENABLED);
DEBUG(config,out).print("loading persisted enabled state: %s\n", DEBUG(config,out).print("loading persisted enabled state: %s\n",
is_enabled ? "true" : "false"); is_enabled ? "true" : "false");
watched_seeds.clear();
vector<PersistentDataItem> seed_configs;
World::GetPersistentData(&seed_configs, SEED_CONFIG_KEY_PREFIX, true);
const size_t num_seed_configs = seed_configs.size();
for (size_t idx = 0; idx < num_seed_configs; ++idx) {
auto &c = seed_configs[idx];
watched_seeds.emplace(get_config_val(c, SEED_CONFIG_ID), c);
}
return CR_OK; return CR_OK;
} }