Merge remote-tracking branch 'myk002/myk_automaterial_buildingplan_fix' into develop

develop
lethosor 2020-11-11 23:36:42 -05:00
commit 9c13b497bf
No known key found for this signature in database
GPG Key ID: 76A269552F4F58C1
6 changed files with 278 additions and 60 deletions

@ -3787,8 +3787,9 @@ buildingplan
Native functions provided by the `buildingplan` plugin:
* ``bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the building type is handled by buildingplan
* ``void addPlannedBuilding(df::building *bld)`` suspends the building jobs and adds the building to the monitor list
* ``bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the building type is handled by buildingplan.
* ``bool isPlanModeEnabled(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the buildingplan UI is enabled for the specified building type.
* ``void addPlannedBuilding(df::building *bld)`` suspends the building jobs and adds the building to the monitor list.
* ``void doCycle()`` runs a check for whether buildlings in the monitor list can be assigned items and unsuspended. This method runs automatically twice a game day, so you only need to call it directly if you want buildingplan to do a check right now.
* ``void scheduleCycle()`` schedules a cycle to be run during the next non-paused game frame. Can be called multiple times while the game is paused and only one cycle will be scheduled.

@ -90,7 +90,7 @@ if(BUILD_SUPPORTED)
dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_lib_static)
dfhack_plugin(autohauler autohauler.cpp)
dfhack_plugin(autolabor autolabor.cpp)
dfhack_plugin(automaterial automaterial.cpp)
dfhack_plugin(automaterial automaterial.cpp LINK_LIBRARIES lua)
dfhack_plugin(automelt automelt.cpp)
dfhack_plugin(autotrade autotrade.cpp)
dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua)

@ -5,6 +5,7 @@
#include <vector>
#include "Core.h"
#include "LuaTools.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
@ -504,7 +505,7 @@ static bool find_anchor_in_spiral(const df::coord &start)
return found;
}
static bool find_valid_building_sites(bool in_future_placement_mode)
static bool find_valid_building_sites(bool in_future_placement_mode, bool use_buildingplan)
{
valid_building_sites.clear();
open_air_sites.clear();
@ -519,7 +520,8 @@ static bool find_valid_building_sites(bool in_future_placement_mode)
continue;
building_site site(df::coord(xB, yB, box_second.z), false);
if (is_valid_building_site(site, false, true, in_future_placement_mode))
// if we're using buildingplan, it will take care of filtering out bad tiles
if (use_buildingplan || is_valid_building_site(site, false, true, in_future_placement_mode))
valid_building_sites.push_back(site);
else if (site.in_open_air)
{
@ -531,25 +533,115 @@ static bool find_valid_building_sites(bool in_future_placement_mode)
}
}
size_t last_open_air_count = 0;
while (valid_building_sites.size() > 0 && open_air_sites.size() != last_open_air_count)
if (!use_buildingplan)
{
last_open_air_count = open_air_sites.size();
deque<building_site> current_open_air_list = open_air_sites;
open_air_sites.clear();
for (deque<building_site>::iterator it = current_open_air_list.begin(); it != current_open_air_list.end(); it++)
size_t last_open_air_count = 0;
while (valid_building_sites.size() > 0 && open_air_sites.size() != last_open_air_count)
{
if (is_orthogonal_to_pending_construction(*it))
valid_building_sites.push_back(*it);
else
open_air_sites.push_back(*it);
}
last_open_air_count = open_air_sites.size();
deque<building_site> current_open_air_list = open_air_sites;
open_air_sites.clear();
for (deque<building_site>::iterator it = current_open_air_list.begin(); it != current_open_air_list.end(); it++)
{
if (is_orthogonal_to_pending_construction(*it))
valid_building_sites.push_back(*it);
else
open_air_sites.push_back(*it);
}
}
}
return valid_building_sites.size() > 0;
}
static bool is_buildingplan_enabled()
{
auto L = Lua::Core::State;
color_ostream_proxy out(Core::getInstance().getConsole());
Lua::StackUnwinder top(L);
if (!(lua_checkstack(L, 1) &&
Lua::PushModulePublic(out, L, "plugins.buildingplan", "isEnabled") &&
Lua::SafeCall(out, L, 0, 1)))
{
return false;
}
return lua_toboolean(L, -1);
}
static bool is_buildingplan_planmode_enabled(
df::building_type type, int16_t subtype, int32_t custom)
{
auto L = Lua::Core::State;
color_ostream_proxy out(Core::getInstance().getConsole());
Lua::StackUnwinder top(L);
if (!lua_checkstack(L, 4) ||
!Lua::PushModulePublic(
out, L, "plugins.buildingplan", "isPlanModeEnabled"))
return false;
Lua::Push(L, type);
Lua::Push(L, subtype);
Lua::Push(L, custom);
if (!Lua::SafeCall(out, L, 3, 1))
return false;
return lua_toboolean(L, -1);
}
static bool is_buildingplan_managed()
{
return is_buildingplan_enabled() &&
is_buildingplan_planmode_enabled(ui_build_selector->building_type,
ui_build_selector->building_subtype,
ui_build_selector->custom_type);
}
static bool build_with_buildingplan_box_select(const df::coord &pos)
{
auto L = Lua::Core::State;
color_ostream_proxy out(Core::getInstance().getConsole());
CoreSuspendClaimer suspend;
Lua::StackUnwinder top(L);
if (!lua_checkstack(L, 5) ||
!Lua::PushModulePublic(
out, L, "plugins.automaterial",
"build_with_buildingplan_box_select"))
{
return false;
}
Lua::Push(L, ui_build_selector->building_subtype);
Lua::Push(L, pos.x);
Lua::Push(L, pos.y);
Lua::Push(L, pos.z);
if (!Lua::SafeCall(out, L, 4, 1))
return false;
return lua_toboolean(L, -1);
}
static bool build_with_buildingplan_ui()
{
auto L = Lua::Core::State;
color_ostream_proxy out(Core::getInstance().getConsole());
CoreSuspendClaimer suspend;
Lua::StackUnwinder top(L);
return lua_checkstack(L, 1) &&
Lua::PushModulePublic(out, L, "plugins.automaterial",
"build_with_buildingplan_ui") &&
Lua::SafeCall(out, L, 0, 1);
}
static bool designate_new_construction(df::coord &pos, df::construction_type &type, df::item *item)
{
auto newinst = Buildings::allocInstance(pos, building_type::Construction, type);
@ -722,11 +814,13 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest
}
else if (in_placement_stage())
{
if (input->count(interface_key::CUSTOM_A))
bool use_buildingplan = is_buildingplan_managed();
if (!use_buildingplan && input->count(interface_key::CUSTOM_A))
{
auto_choose_materials = !auto_choose_materials;
}
else if (input->count(interface_key::CUSTOM_T))
else if (!use_buildingplan && input->count(interface_key::CUSTOM_T))
{
revert_to_last_used_type = !revert_to_last_used_type;
}
@ -739,7 +833,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest
return;
}
else if (input->count(interface_key::CUSTOM_O))
else if (!use_buildingplan && input->count(interface_key::CUSTOM_O))
{
allow_future_placement = !allow_future_placement;
}
@ -816,6 +910,15 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest
return;
}
}
else if (use_buildingplan
&& ui_build_selector->errors.size() == 0
&& input->count(interface_key::SELECT))
{
build_with_buildingplan_ui();
Gui::refreshSidebar();
input->clear();
return;
}
}
}
//END UI Methods
@ -876,16 +979,17 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest
static bool saved_revert_setting = false;
static bool auto_select_applied = false;
bool use_buildingplan = is_buildingplan_managed();
box_select_mode = SELECT_MATERIALS;
if (new_start)
{
bool ok_to_continue = false;
bool in_future_placement_mode = false;
if (!find_valid_building_sites(false))
if (!find_valid_building_sites(false, use_buildingplan))
{
if (allow_future_placement)
{
in_future_placement_mode = find_valid_building_sites(true);
in_future_placement_mode = find_valid_building_sites(true, use_buildingplan);
}
}
else
@ -893,7 +997,8 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest
ok_to_continue = true;
}
if (in_future_placement_mode)
// if using buildingplan, we don't need an anchor
if (!use_buildingplan && in_future_placement_mode)
{
ok_to_continue = find_anchor_in_spiral(valid_building_sites[0].pos);
}
@ -924,6 +1029,15 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest
{
building_site site = valid_building_sites.front();
valid_building_sites.pop_front();
if (use_buildingplan)
{
// we don't actually care if this fails. buildingplan will return
// false when it filters out bad tiles, and that's ok.
build_with_buildingplan_box_select(site.pos);
continue;
}
if (box_select_materials.size() > 0)
{
df::construction_type type = (df::construction_type) ui_build_selector->building_subtype;
@ -986,8 +1100,10 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest
// Allocation done, reset
move_cursor(box_second);
// if we're using buildingplan, we never actually leave the placement
// screen, so there's no need to re-enter the screen
revert_to_last_used_type = saved_revert_setting;
if (!revert_to_last_used_type)
if (!use_buildingplan && !revert_to_last_used_type)
{
send_key(df::interface_key::LEAVESCREEN);
}
@ -1091,9 +1207,18 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest
}
else if (in_placement_stage() && ui_build_selector->building_subtype < 7)
{
OutputString(COLOR_BROWN, x, y, "DFHack Options", true, left_margin);
AMOutputToggleString(x, y, "Auto Mat-select", "a", auto_choose_materials, true, left_margin);
AMOutputToggleString(x, y, "Reselect Type", "t", revert_to_last_used_type, true, left_margin);
bool use_buildingplan = is_buildingplan_managed();
OutputString(COLOR_BROWN, x, y, "DFHack Automaterial Options", true, left_margin);
if (use_buildingplan)
{
y += 2;
}
else
{
AMOutputToggleString(x, y, "Auto Mat-select", "a", auto_choose_materials, true, left_margin);
AMOutputToggleString(x, y, "Reselect Type", "t", revert_to_last_used_type, true, left_margin);
}
++y;
AMOutputToggleString(x, y, "Box Select", "b", box_select_enabled, true, left_margin);
@ -1101,9 +1226,20 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest
{
AMOutputToggleString(x, y, "Show Box Mask", "x", show_box_selection, true, left_margin);
OutputHotkeyString(x, y, (hollow_selection) ? "Make Solid" : "Make Hollow", "h", true, left_margin);
AMOutputToggleString(x, y, "Open Placement", "o", allow_future_placement, true, left_margin);
if (use_buildingplan)
++y;
else
AMOutputToggleString(x, y, "Open Placement", "o", allow_future_placement, true, left_margin);
}
++y;
else
{
y += 3;
}
y += 2;
if (is_buildingplan_enabled())
OutputString(COLOR_BROWN, x, y, "DFHack Buildingplan Options", true, left_margin);
if (box_select_enabled)
{
Screen::Pen pen(' ',COLOR_BLACK);

@ -309,7 +309,6 @@ static std::string get_item_label(const BuildingTypeKey &key, int item_idx)
if (!s)
return "No string";
lua_pop(L, 1);
return s;
}
@ -345,22 +344,27 @@ static bool construct_planned_building()
if (!(lua_checkstack(L, 1) &&
Lua::PushModulePublic(out, L, "plugins.buildingplan",
"construct_building_from_ui_state") &&
"construct_buildings_from_ui_state") &&
Lua::SafeCall(out, L, 0, 1)))
{
return false;
}
auto bld = Lua::GetDFObject<df::building>(L, -1);
lua_pop(L, 1);
if (!bld)
// register all returned buildings with planner
lua_pushnil(L);
while (lua_next(L, -2) != 0)
{
out.printerr("buildingplan: construct_building_from_ui_state() failed\n");
return false;
}
auto bld = Lua::GetDFObject<df::building>(L, -1);
if (!bld)
{
out.printerr(
"buildingplan: construct_buildings_from_ui_state() failed\n");
return false;
}
planner.addPlannedBuilding(bld);
planner.addPlannedBuilding(bld);
lua_pop(L, 1);
}
return true;
}
@ -395,6 +399,29 @@ static void show_global_settings_dialog()
}
}
static bool is_automaterial_enabled()
{
auto L = Lua::Core::State;
color_ostream_proxy out(Core::getInstance().getConsole());
Lua::StackUnwinder top(L);
if (!(lua_checkstack(L, 1) &&
Lua::PushModulePublic(out, L, "plugins.automaterial", "isEnabled") &&
Lua::SafeCall(out, L, 0, 1)))
{
return false;
}
return lua_toboolean(L, -1);
}
static bool is_automaterial_managed(df::building_type type, int16_t subtype)
{
return is_automaterial_enabled()
&& type == df::building_type::Construction
&& subtype < df::construction_type::TrackN;
}
struct buildingplan_query_hook : public df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
@ -607,7 +634,11 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest
if (!is_planmode_enabled(key))
return false;
if (input->count(interface_key::SELECT))
// if automaterial is enabled, let it handle building allocation and
// registration with planner
if (input->count(interface_key::SELECT) &&
!is_automaterial_managed(ui_build_selector->building_type,
ui_build_selector->building_subtype))
{
if (ui_build_selector->errors.size() == 0 && construct_planned_building())
{
@ -710,12 +741,11 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest
int y = 23;
if (ui_build_selector->building_type == df::building_type::Construction
&& ui_build_selector->building_subtype <
df::construction_type::TrackN)
if (is_automaterial_managed(ui_build_selector->building_type,
ui_build_selector->building_subtype))
{
// try not to conflict with the automaterial plugin UI
y = 34;
// avoid conflict with the automaterial plugin UI
y = 36;
}
if (show_help)
@ -967,6 +997,12 @@ DFhackCExport command_result plugin_shutdown(color_ostream &)
// Lua API section
static bool isPlanModeEnabled(df::building_type type,
int16_t subtype,
int32_t custom) {
return planmode_enabled[toBuildingTypeKey(type, subtype, custom)];
}
static bool isPlannableBuilding(df::building_type type,
int16_t subtype,
int32_t custom) {
@ -997,6 +1033,7 @@ static void setSetting(std::string name, bool value) {
}
DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(isPlanModeEnabled),
DFHACK_LUA_FUNCTION(isPlannableBuilding),
DFHACK_LUA_FUNCTION(addPlannedBuilding),
DFHACK_LUA_FUNCTION(doCycle),

@ -0,0 +1,23 @@
local _ENV = mkmodule('plugins.automaterial')
local buildingplan = require('plugins.buildingplan')
-- construct the building and register it with buildingplan for item selection
function build_with_buildingplan_box_select(subtype, x, y, z)
local pos = xyz2pos(x, y, z)
local bld, err = dfhack.buildings.constructBuilding{
type=df.building_type.Construction, subtype=subtype, pos=pos}
-- it's not a user error if we can't place a building here; just indicate
-- that no building was placed by returning false.
if err then return false end
buildingplan.addPlannedBuilding(bld)
return true
end
function build_with_buildingplan_ui()
for _,bld in ipairs(buildingplan.construct_buildings_from_ui_state()) do
buildingplan.addPlannedBuilding(bld)
end
end
return _ENV

@ -5,6 +5,7 @@ local _ENV = mkmodule('plugins.buildingplan')
Native functions:
* void setSetting(string name, boolean value)
* bool isPlanModeEnabled(df::building_type type, int16_t subtype, int32_t custom)
* bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom)
* void addPlannedBuilding(df::building *bld)
* void doCycle()
@ -82,7 +83,9 @@ function item_can_be_improved(btype, subtype, custom, reverse_idx)
end
-- needs the core suspended
function construct_building_from_ui_state()
-- returns a vector of constructed buildings (usually of size 1, but potentially
-- more for constructions)
function construct_buildings_from_ui_state()
local uibs = df.global.ui_build_selector
local world = df.global.world
local direction = world.selected_direction
@ -94,22 +97,40 @@ function construct_building_from_ui_state()
local pos = guidm.getCursorPos()
pos.x = pos.x - math.floor(width/2)
pos.y = pos.y - math.floor(height/2)
local bld, err = dfhack.buildings.constructBuilding{
type=uibs.building_type, subtype=uibs.building_subtype,
custom=uibs.custom_type, pos=pos, width=width, height=height,
direction=direction}
if err then error(err) end
-- assign fields for the types that need them. we can't pass them all in to
-- the call to constructBuilding since attempting to assign unrelated
-- fields to building types that don't support them causes errors.
for k,v in pairs(bld) do
if k == 'friction' then bld.friction = uibs.friction end
if k == 'use_dump' then bld.use_dump = uibs.use_dump end
if k == 'dump_x_shift' then bld.dump_x_shift = uibs.dump_x_shift end
if k == 'dump_y_shift' then bld.dump_y_shift = uibs.dump_y_shift end
if k == 'speed' then bld.speed = uibs.speed end
local min_x, max_x = pos.x, pos.x
local min_y, max_y = pos.y, pos.y
if width == 1 and height == 1 and
(world.building_width > 1 or world.building_height > 1) then
min_x = math.ceil(pos.x - world.building_width/2)
max_x = math.floor(pos.x + world.building_width/2)
min_y = math.ceil(pos.y - world.building_height/2)
max_y = math.floor(pos.y + world.building_height/2)
end
return bld
local blds = {}
for y=min_y,max_y do for x=min_x,max_x do
local bld, err = dfhack.buildings.constructBuilding{
type=uibs.building_type, subtype=uibs.building_subtype,
custom=uibs.custom_type, pos=xyz2pos(x, y, pos.z),
width=width, height=height, direction=direction}
if err then
for _,b in ipairs(blds) do
dfhack.buildings.deconstruct(b)
end
error(err)
end
-- assign fields for the types that need them. we can't pass them all in
-- to the call to constructBuilding since attempting to assign unrelated
-- fields to building types that don't support them causes errors.
for k,v in pairs(bld) do
if k == 'friction' then bld.friction = uibs.friction end
if k == 'use_dump' then bld.use_dump = uibs.use_dump end
if k == 'dump_x_shift' then bld.dump_x_shift = uibs.dump_x_shift end
if k == 'dump_y_shift' then bld.dump_y_shift = uibs.dump_y_shift end
if k == 'speed' then bld.speed = uibs.speed end
end
table.insert(blds, bld)
end end
return blds
end
--