From 18ad29dde4b09335730550b1f21d3b19f3504cc0 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 15 Feb 2023 19:10:42 -0800 Subject: [PATCH] show queue position --- plugins/buildingplan/buildingplan.cpp | 47 ++++++++- plugins/buildingplan/buildingplan.h | 1 + plugins/buildingplan/plannedbuilding.cpp | 62 +++++++++++- plugins/buildingplan/plannedbuilding.h | 6 +- plugins/lua/buildingplan.lua | 120 +++++++++++++++-------- 5 files changed, 189 insertions(+), 47 deletions(-) diff --git a/plugins/buildingplan/buildingplan.cpp b/plugins/buildingplan/buildingplan.cpp index 2c5d96b94..17db21cd2 100644 --- a/plugins/buildingplan/buildingplan.cpp +++ b/plugins/buildingplan/buildingplan.cpp @@ -175,7 +175,7 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) { World::GetPersistentData(&building_configs, BLD_CONFIG_KEY); const size_t num_building_configs = building_configs.size(); for (size_t idx = 0; idx < num_building_configs; ++idx) { - PlannedBuilding pb(building_configs[idx]); + PlannedBuilding pb(out, building_configs[idx]); registerPlannedBuilding(out, pb); } @@ -279,7 +279,7 @@ static string getBucket(const df::job_item & ji) { } // get a list of item vectors that we should search for matches -static vector getVectorIds(color_ostream &out, df::job_item *job_item) { +vector getVectorIds(color_ostream &out, df::job_item *job_item) { std::vector ret; // if the filter already has the vector_id set to something specific, use it @@ -310,6 +310,7 @@ static vector getVectorIds(color_ostream &out, df::job_i ret.push_back(df::job_item_vector_id::IN_PLAY); return ret; } + static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb) { df::building * bld = pb.getBuildingIfValidOrRemoveIfNot(out); if (!bld) @@ -385,7 +386,10 @@ static void printStatus(color_ostream &out) { auto *jitem = bld->jobs[0]->job_items[task.second]; int32_t quantity = jitem->quantity; if (quantity) { - string desc = toLower(ENUM_KEY_STR(item_type, jitem->item_type)); + string desc = "none"; + call_buildingplan_lua(&out, "get_desc", 1, 1, + [&](lua_State *L) { Lua::Push(L, jitem); }, + [&](lua_State *L) { desc = lua_tostring(L, -1); }); counts[desc] += quantity; total += quantity; } @@ -510,6 +514,42 @@ static int countAvailableItems(color_ostream &out, df::building_type type, int16 return count; } +static int getQueuePosition(color_ostream &out, df::building *bld, int index) { + DEBUG(status,out).print("entering getQueuePosition\n"); + if (!isPlannedBuilding(out, bld) || bld->jobs.size() != 1) + return 0; + + auto &job_items = bld->jobs[0]->job_items; + if (job_items.size() <= index) + return 0; + + PlannedBuilding &pb = planned_buildings.at(bld->id); + if (pb.vector_ids.size() <= index) + return 0; + + auto &job_item = job_items[index]; + + int min_pos = -1; + for (auto &vec_id : pb.vector_ids[index]) { + if (!tasks.count(vec_id)) + continue; + auto &buckets = tasks.at(vec_id); + string bucket_id = getBucket(*job_item); + if (!buckets.count(bucket_id)) + continue; + int bucket_pos = -1; + for (auto &task : buckets.at(bucket_id)) { + ++bucket_pos; + if (bld->id == task.first && index == task.second) + break; + } + if (bucket_pos++ >= 0) + min_pos = min_pos < 0 ? bucket_pos : std::min(min_pos, bucket_pos); + } + + return min_pos < 0 ? 0 : min_pos; +} + DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(printStatus), DFHACK_LUA_FUNCTION(setSetting), @@ -519,5 +559,6 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(doCycle), DFHACK_LUA_FUNCTION(scheduleCycle), DFHACK_LUA_FUNCTION(countAvailableItems), + DFHACK_LUA_FUNCTION(getQueuePosition), DFHACK_LUA_END }; diff --git a/plugins/buildingplan/buildingplan.h b/plugins/buildingplan/buildingplan.h index 0e7e288ac..7fe2478aa 100644 --- a/plugins/buildingplan/buildingplan.h +++ b/plugins/buildingplan/buildingplan.h @@ -28,5 +28,6 @@ bool get_config_bool(DFHack::PersistentDataItem &c, int index); void set_config_val(DFHack::PersistentDataItem &c, int index, int value); void set_config_bool(DFHack::PersistentDataItem &c, int index, bool value); +std::vector getVectorIds(DFHack::color_ostream &out, df::job_item *job_item); bool itemPassesScreen(df::item * item); bool matchesFilters(df::item * item, df::job_item * job_item); diff --git a/plugins/buildingplan/plannedbuilding.cpp b/plugins/buildingplan/plannedbuilding.cpp index c03f56161..f4f3564b7 100644 --- a/plugins/buildingplan/plannedbuilding.cpp +++ b/plugins/buildingplan/plannedbuilding.cpp @@ -2,25 +2,79 @@ #include "buildingplan.h" #include "Debug.h" +#include "MiscUtils.h" #include "modules/World.h" +#include "df/job.h" + namespace DFHack { DBG_EXTERN(buildingplan, status); - DBG_EXTERN(buildingplan, cycle); } +using std::string; +using std::vector; using namespace DFHack; +static vector> get_vector_ids(color_ostream &out, int bld_id) { + vector> ret; + + df::building *bld = df::building::find(bld_id); + + if (!bld || bld->jobs.size() != 1) + return ret; + + auto &job = bld->jobs[0]; + for (auto &jitem : job->job_items) { + ret.emplace_back(getVectorIds(out, jitem)); + } + return ret; +} + +static vector> deserialize(color_ostream &out, PersistentDataItem &bld_config) { + vector> ret; + + DEBUG(status,out).print("deserializing state for building %d: %s\n", + get_config_val(bld_config, BLD_CONFIG_ID), bld_config.val().c_str()); + + vector joined; + split_string(&joined, bld_config.val(), "|"); + for (auto &str : joined) { + vector lst; + split_string(&lst, str, ","); + vector ids; + for (auto &s : lst) + ids.emplace_back(df::job_item_vector_id(string_to_int(s))); + ret.emplace_back(ids); + } + + if (!ret.size()) + ret = get_vector_ids(out, get_config_val(bld_config, BLD_CONFIG_ID)); + + return ret; +} + +static string serialize(const vector> &vector_ids) { + vector joined; + for (auto &vec_list : vector_ids) { + joined.emplace_back(join_strings(",", vec_list)); + } + return join_strings("|", joined); +} + PlannedBuilding::PlannedBuilding(color_ostream &out, df::building *building) - : id(building->id) { + : id(building->id), vector_ids(get_vector_ids(out, id)) { DEBUG(status,out).print("creating persistent data for building %d\n", id); bld_config = World::AddPersistentData(BLD_CONFIG_KEY); set_config_val(bld_config, BLD_CONFIG_ID, id); + bld_config.val() = serialize(vector_ids); + DEBUG(status,out).print("serialized state for building %d: %s\n", id, bld_config.val().c_str()); } -PlannedBuilding::PlannedBuilding(PersistentDataItem &bld_config) - : id(get_config_val(bld_config, BLD_CONFIG_ID)), bld_config(bld_config) { } +PlannedBuilding::PlannedBuilding(color_ostream &out, PersistentDataItem &bld_config) + : id(get_config_val(bld_config, BLD_CONFIG_ID)), + vector_ids(deserialize(out, bld_config)), + bld_config(bld_config) { } // Ensure the building still exists and is in a valid state. It can disappear // for lots of reasons, such as running the game with the buildingplan plugin diff --git a/plugins/buildingplan/plannedbuilding.h b/plugins/buildingplan/plannedbuilding.h index 9f0273b82..592f0e4b3 100644 --- a/plugins/buildingplan/plannedbuilding.h +++ b/plugins/buildingplan/plannedbuilding.h @@ -5,13 +5,17 @@ #include "modules/Persistence.h" #include "df/building.h" +#include "df/job_item_vector_id.h" class PlannedBuilding { public: const df::building::key_field_type id; + // job_item idx -> list of vectors the task is linked to + const std::vector> vector_ids; + PlannedBuilding(DFHack::color_ostream &out, df::building *building); - PlannedBuilding(DFHack::PersistentDataItem &bld_config); + PlannedBuilding(DFHack::color_ostream &out, DFHack::PersistentDataItem &bld_config); void remove(DFHack::color_ostream &out); diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 24ef90903..376787952 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -70,7 +70,7 @@ function reset_counts() end -------------------------------- --- Planner Overlay +-- PlannerOverlay -- local uibs = df.global.buildreq @@ -181,7 +181,7 @@ function ItemLine:reset() self.available = nil end -local function get_desc(filter) +function get_desc(filter) local desc = 'Unknown' if filter.has_tool_use then desc = to_title_case(df.tool_uses[filter.has_tool_use]) @@ -190,13 +190,15 @@ local function get_desc(filter) desc = to_title_case(df.item_type[filter.item_type]) end if filter.flags2 and filter.flags2.building_material then - desc = "Generic material"; + desc = 'Generic material'; if filter.flags2.fire_safe then - desc = "Fire-safe material"; + desc = 'Fire-safe material'; end if filter.flags2.magma_safe then - desc = "Magma-safe material"; + desc = 'Magma-safe material'; end + elseif filter.flags2 and filter.flags2.screw then + desc = 'Screw' elseif filter.vector_id then desc = to_title_case(df.job_item_vector_id[filter.vector_id]) end @@ -249,27 +251,30 @@ PlannerOverlay.ATTRS{ default_enabled=true, viewscreens='dwarfmode/Building/Placement', frame={w=54, h=9}, - frame_style=gui.MEDIUM_FRAME, frame_background=gui.CLEAR_PEN, } function PlannerOverlay:init() self:addviews{ + widgets.Panel{ + frame={}, + frame_style=gui.MEDIUM_FRAME, + }, widgets.Label{ frame={}, auto_width=true, text='No items required.', visible=function() return #get_cur_filters() == 0 end, }, - ItemLine{view_id='item1', frame={t=0, l=0}, idx=1}, - ItemLine{view_id='item2', frame={t=2, l=0}, idx=2}, - ItemLine{view_id='item3', frame={t=4, l=0}, idx=3}, - ItemLine{view_id='item4', frame={t=6, l=0}, idx=4}, + ItemLine{view_id='item1', frame={t=1, l=1, r=1}, idx=1}, + ItemLine{view_id='item2', frame={t=3, l=1, r=1}, idx=2}, + ItemLine{view_id='item3', frame={t=5, l=1, r=1}, idx=3}, + ItemLine{view_id='item4', frame={t=7, l=1, r=1}, idx=4}, widgets.CycleHotkeyLabel{ - view_id="stairs_top_subtype", - frame={t=3, l=0}, - key="CUSTOM_R", - label="Top Stair Type: ", + view_id='stairs_top_subtype', + frame={t=4, l=1}, + key='CUSTOM_R', + label='Top Stair Type: ', visible=is_stairs, options={ {label='Auto', value='auto'}, @@ -278,10 +283,10 @@ function PlannerOverlay:init() }, }, widgets.CycleHotkeyLabel { - view_id="stairs_bottom_subtype", - frame={t=4, l=0}, - key="CUSTOM_B", - label="Bottom Stair Type: ", + view_id='stairs_bottom_subtype', + frame={t=5, l=1}, + key='CUSTOM_B', + label='Bottom Stair Type: ', visible=is_stairs, options={ {label='Auto', value='auto'}, @@ -290,7 +295,7 @@ function PlannerOverlay:init() }, }, widgets.Label{ - frame={b=0, l=17}, + frame={b=1, l=17}, text={ 'Selected area: ', {text=function() @@ -300,6 +305,17 @@ function PlannerOverlay:init() }, visible=is_choosing_area, }, + widgets.CycleHotkeyLabel{ + view_id='safety', + frame={b=0, l=1}, + key='CUSTOM_F', + label='Extra safety: ', + options={ + {label='None', value='none'}, + {label='Magma', value='magma'}, + {label='Fire', value='fire'}, + }, + }, } end @@ -473,12 +489,50 @@ function PlannerOverlay:place_building() scheduleCycle() end +-------------------------------- +-- InspectorOverlay +-- + +local function get_building_filters() + local bld = dfhack.gui.getSelectedBuilding() + return dfhack.buildings.getFiltersByType({}, + bld:getType(), bld:getSubtype(), bld:getCustomType()) +end + +InspectorLine = defclass(InspectorLine, widgets.Panel) +InspectorLine.ATTRS{ + idx=DEFAULT_NIL, +} + +function InspectorLine:init() + self.frame.h = 2 + self.visible = function() return #get_building_filters() >= self.idx end + self:addviews{ + widgets.Label{ + frame={t=0, l=0}, + text={{text=function() return get_desc(get_building_filters()[self.idx]) end}}, + }, + widgets.Label{ + frame={t=1, l=2}, + text={{text=self:callback('get_status_line')}}, + }, + } +end + +function InspectorLine:get_status_line() + local queue_pos = getQueuePosition(dfhack.gui.getSelectedBuilding(), self.idx-1) + if queue_pos <= 0 then + return 'Item attached' + end + return ('Position in line: %d'):format(queue_pos) +end + InspectorOverlay = defclass(InspectorOverlay, overlay.OverlayWidget) InspectorOverlay.ATTRS{ default_pos={x=-41,y=14}, default_enabled=true, viewscreens='dwarfmode/ViewSheets/BUILDING', - frame={w=30, h=9}, + frame={w=30, h=14}, frame_style=gui.MEDIUM_FRAME, frame_background=gui.CLEAR_PEN, } @@ -489,29 +543,17 @@ function InspectorOverlay:init() frame={t=0, l=0}, text='Waiting for items:', }, - widgets.Label{ - frame={t=1, l=0}, - text='item1', - }, - widgets.Label{ - frame={t=2, l=0}, - text='item2', - }, - widgets.Label{ - frame={t=3, l=0}, - text='item3', - }, - widgets.Label{ - frame={t=4, l=0}, - text='item4', - }, + InspectorLine{view_id='item1', frame={t=2, l=0}, idx=1}, + InspectorLine{view_id='item2', frame={t=4, l=0}, idx=2}, + InspectorLine{view_id='item3', frame={t=6, l=0}, idx=3}, + InspectorLine{view_id='item4', frame={t=8, l=0}, idx=4}, widgets.HotkeyLabel{ - frame={t=5, l=0}, + frame={t=10, l=0}, label='adjust filters', key='CUSTOM_CTRL_F', }, widgets.HotkeyLabel{ - frame={t=6, l=0}, + frame={t=11, l=0}, label='make top priority', key='CUSTOM_CTRL_T', }, @@ -578,7 +620,7 @@ end -- does not need the core suspended. function show_global_settings_dialog(settings) GlobalSettings{ - frame_title="Buildingplan Global Settings", + frame_title='Buildingplan Global Settings', settings=settings, }:show() end