Merge branch 'develop' into dig-now
commit
9fb7e0d232
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Export.h"
|
||||||
|
#include "DataDefs.h"
|
||||||
|
|
||||||
|
#include "df/squad.h"
|
||||||
|
|
||||||
|
namespace DFHack
|
||||||
|
{
|
||||||
|
namespace Military
|
||||||
|
{
|
||||||
|
|
||||||
|
DFHACK_EXPORT std::string getSquadName(int32_t squad_id);
|
||||||
|
DFHACK_EXPORT df::squad* makeSquad(int32_t assignment_id);
|
||||||
|
DFHACK_EXPORT void updateRoomAssignments(int32_t squad_id, int32_t civzone_id, df::squad_use_flags flags);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,291 @@
|
|||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
#include "MiscUtils.h"
|
||||||
|
#include "modules/Military.h"
|
||||||
|
#include "modules/Translation.h"
|
||||||
|
#include "df/building.h"
|
||||||
|
#include "df/building_civzonest.h"
|
||||||
|
#include "df/historical_figure.h"
|
||||||
|
#include "df/historical_entity.h"
|
||||||
|
#include "df/entity_position.h"
|
||||||
|
#include "df/entity_position_assignment.h"
|
||||||
|
#include "df/plotinfost.h"
|
||||||
|
#include "df/squad.h"
|
||||||
|
#include "df/squad_position.h"
|
||||||
|
#include "df/squad_schedule_order.h"
|
||||||
|
#include "df/squad_order.h"
|
||||||
|
#include "df/squad_order_trainst.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace df::enums;
|
||||||
|
using df::global::world;
|
||||||
|
using df::global::plotinfo;
|
||||||
|
|
||||||
|
std::string Military::getSquadName(int32_t squad_id)
|
||||||
|
{
|
||||||
|
df::squad *squad = df::squad::find(squad_id);
|
||||||
|
if (!squad)
|
||||||
|
return "";
|
||||||
|
if (squad->alias.size() > 0)
|
||||||
|
return squad->alias;
|
||||||
|
return Translation::TranslateName(&squad->name, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//only works for making squads for fort mode player controlled dwarf squads
|
||||||
|
//could be extended straightforwardly by passing in entity
|
||||||
|
df::squad* Military::makeSquad(int32_t assignment_id)
|
||||||
|
{
|
||||||
|
if (df::global::squad_next_id == nullptr || df::global::plotinfo == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
df::language_name name;
|
||||||
|
name.type = df::language_name_type::Squad;
|
||||||
|
|
||||||
|
for (int i=0; i < 7; i++)
|
||||||
|
{
|
||||||
|
name.words[i] = -1;
|
||||||
|
name.parts_of_speech[i] = df::part_of_speech::Noun;
|
||||||
|
}
|
||||||
|
|
||||||
|
df::historical_entity* fort = df::historical_entity::find(df::global::plotinfo->group_id);
|
||||||
|
|
||||||
|
if (fort == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
df::entity_position_assignment* found_assignment = nullptr;
|
||||||
|
|
||||||
|
for (auto* assignment : fort->positions.assignments)
|
||||||
|
{
|
||||||
|
if (assignment->id == assignment_id)
|
||||||
|
{
|
||||||
|
found_assignment = assignment;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found_assignment == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
//this function does not attempt to delete or replace squads for assignments
|
||||||
|
if (found_assignment->squad_id != -1)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
df::entity_position* corresponding_position = nullptr;
|
||||||
|
|
||||||
|
for (auto* position : fort->positions.own)
|
||||||
|
{
|
||||||
|
if (position->id == found_assignment->position_id)
|
||||||
|
{
|
||||||
|
corresponding_position = position;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (corresponding_position == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
df::squad* result = new df::squad();
|
||||||
|
result->id = *df::global::squad_next_id;
|
||||||
|
result->uniform_priority = result->id + 1; //no idea why, but seems to hold
|
||||||
|
result->carry_food = 2;
|
||||||
|
result->carry_water = 1;
|
||||||
|
result->entity_id = df::global::plotinfo->group_id;
|
||||||
|
result->leader_position = corresponding_position->id;
|
||||||
|
result->leader_assignment = found_assignment->id;
|
||||||
|
result->name = name;
|
||||||
|
|
||||||
|
int16_t squad_size = corresponding_position->squad_size;
|
||||||
|
|
||||||
|
for (int i=0; i < squad_size; i++)
|
||||||
|
{
|
||||||
|
//construct for squad_position seems to set all the attributes correctly
|
||||||
|
df::squad_position* pos = new df::squad_position();
|
||||||
|
|
||||||
|
result->positions.push_back(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& routines = df::global::plotinfo->alerts.routines;
|
||||||
|
|
||||||
|
for (const auto& routine : routines)
|
||||||
|
{
|
||||||
|
df::squad_schedule_entry* asched = (df::squad_schedule_entry*)malloc(sizeof(df::squad_schedule_entry) * 12);
|
||||||
|
|
||||||
|
for(int kk=0; kk < 12; kk++)
|
||||||
|
{
|
||||||
|
new (&asched[kk]) df::squad_schedule_entry;
|
||||||
|
|
||||||
|
for(int jj=0; jj < squad_size; jj++)
|
||||||
|
{
|
||||||
|
int32_t* order_assignments = new int32_t();
|
||||||
|
*order_assignments = -1;
|
||||||
|
|
||||||
|
asched[kk].order_assignments.push_back(order_assignments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto insert_training_order = [asched, squad_size](int month)
|
||||||
|
{
|
||||||
|
df::squad_schedule_order* order = new df::squad_schedule_order();
|
||||||
|
order->min_count = squad_size;
|
||||||
|
//assumed
|
||||||
|
order->positions.resize(squad_size);
|
||||||
|
|
||||||
|
df::squad_order* s_order = df::allocate<df::squad_order_trainst>();
|
||||||
|
|
||||||
|
s_order->year = *df::global::cur_year;
|
||||||
|
s_order->year_tick = *df::global::cur_year_tick;
|
||||||
|
|
||||||
|
order->order = s_order;
|
||||||
|
|
||||||
|
asched[month].orders.push_back(order);
|
||||||
|
//wear uniform while training
|
||||||
|
asched[month].uniform_mode = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Dwarf fortress does do this via a series of string comparisons
|
||||||
|
//Off duty: No orders, Sleep/room at will. Equip/orders only
|
||||||
|
if (routine->name == "Off duty")
|
||||||
|
{
|
||||||
|
for (int i=0; i < 12; i++)
|
||||||
|
{
|
||||||
|
asched[i].sleep_mode = 0;
|
||||||
|
asched[i].uniform_mode = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Staggered Training: Training orders at months 3 4 5 9 10 11, *or* 0 1 2 6 7 8, sleep/room at will. Equip/orders only, except train months which are equip/always
|
||||||
|
else if (routine->name == "Staggered training")
|
||||||
|
{
|
||||||
|
//this is semi randomised for different squads
|
||||||
|
//appears to be something like squad.id & 1, it isn't smart
|
||||||
|
//if you alternate squad creation, its 'correctly' staggered
|
||||||
|
//but it'll also happily not stagger them if you eg delete a squad and make another
|
||||||
|
std::array<int, 6> indices;
|
||||||
|
|
||||||
|
if ((*df::global::squad_next_id) & 1)
|
||||||
|
{
|
||||||
|
indices = {3, 4, 5, 9, 10, 11};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
indices = {0, 1, 2, 6, 7, 8};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int index : indices)
|
||||||
|
{
|
||||||
|
insert_training_order(index);
|
||||||
|
//still sleep in room at will even when training
|
||||||
|
asched[index].sleep_mode = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//see above, but with all indices
|
||||||
|
else if (routine->name == "Constant training")
|
||||||
|
{
|
||||||
|
for (int i=0; i < 12; i++)
|
||||||
|
{
|
||||||
|
insert_training_order(i);
|
||||||
|
//still sleep in room at will even when training
|
||||||
|
asched[i].sleep_mode = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (routine->name == "Ready")
|
||||||
|
{
|
||||||
|
for (int i=0; i < 12; i++)
|
||||||
|
{
|
||||||
|
asched[i].sleep_mode = 2;
|
||||||
|
asched[i].uniform_mode = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i=0; i < 12; i++)
|
||||||
|
{
|
||||||
|
asched[i].sleep_mode = 0;
|
||||||
|
asched[i].uniform_mode = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result->schedule.push_back(reinterpret_cast<df::squad::T_schedule*>(asched));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Modify necessary world state
|
||||||
|
(*df::global::squad_next_id)++;
|
||||||
|
fort->squads.push_back(result->id);
|
||||||
|
df::global::world->squads.all.push_back(result);
|
||||||
|
found_assignment->squad_id = result->id;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Military::updateRoomAssignments(int32_t squad_id, int32_t civzone_id, df::squad_use_flags flags)
|
||||||
|
{
|
||||||
|
df::squad* squad = df::squad::find(squad_id);
|
||||||
|
df::building* bzone = df::building::find(civzone_id);
|
||||||
|
|
||||||
|
df::building_civzonest* zone = strict_virtual_cast<df::building_civzonest>(bzone);
|
||||||
|
|
||||||
|
if (squad == nullptr || zone == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
df::squad::T_rooms* room_from_squad = nullptr;
|
||||||
|
df::building_civzonest::T_squad_room_info* room_from_building = nullptr;
|
||||||
|
|
||||||
|
for (auto room : squad->rooms)
|
||||||
|
{
|
||||||
|
if (room->building_id == civzone_id)
|
||||||
|
{
|
||||||
|
room_from_squad = room;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto room : zone->squad_room_info)
|
||||||
|
{
|
||||||
|
if (room->squad_id == squad_id)
|
||||||
|
{
|
||||||
|
room_from_building = room;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags.whole == 0 && room_from_squad == nullptr && room_from_building == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//if we're setting 0 flags, and there's no room already, don't set a room
|
||||||
|
bool avoiding_squad_roundtrip = flags.whole == 0 && room_from_squad == nullptr;
|
||||||
|
|
||||||
|
if (!avoiding_squad_roundtrip && room_from_squad == nullptr)
|
||||||
|
{
|
||||||
|
room_from_squad = new df::squad::T_rooms();
|
||||||
|
room_from_squad->building_id = civzone_id;
|
||||||
|
|
||||||
|
insert_into_vector(squad->rooms, &df::squad::T_rooms::building_id, room_from_squad);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (room_from_building == nullptr)
|
||||||
|
{
|
||||||
|
room_from_building = new df::building_civzonest::T_squad_room_info();
|
||||||
|
room_from_building->squad_id = squad_id;
|
||||||
|
|
||||||
|
insert_into_vector(zone->squad_room_info, &df::building_civzonest::T_squad_room_info::squad_id, room_from_building);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (room_from_squad)
|
||||||
|
room_from_squad->mode = flags;
|
||||||
|
|
||||||
|
room_from_building->mode = flags;
|
||||||
|
|
||||||
|
if (flags.whole == 0 && !avoiding_squad_roundtrip)
|
||||||
|
{
|
||||||
|
for (int i=0; i < (int)squad->rooms.size(); i++)
|
||||||
|
{
|
||||||
|
if (squad->rooms[i]->building_id == civzone_id)
|
||||||
|
{
|
||||||
|
delete squad->rooms[i];
|
||||||
|
squad->rooms.erase(squad->rooms.begin() + i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit 7917f062c403a47d4d190bafc2470b247c8aa642
|
Subproject commit 8ae81f8d8f1f96d82b9074b205073bb8e8d29f96
|
@ -1,689 +0,0 @@
|
|||||||
#include "Core.h"
|
|
||||||
#include "Debug.h"
|
|
||||||
#include "LuaTools.h"
|
|
||||||
#include "PluginManager.h"
|
|
||||||
|
|
||||||
#include "modules/Items.h"
|
|
||||||
#include "modules/Job.h"
|
|
||||||
#include "modules/Materials.h"
|
|
||||||
#include "modules/Persistence.h"
|
|
||||||
#include "modules/World.h"
|
|
||||||
|
|
||||||
#include "df/building.h"
|
|
||||||
#include "df/building_design.h"
|
|
||||||
#include "df/item.h"
|
|
||||||
#include "df/job_item.h"
|
|
||||||
#include "df/world.h"
|
|
||||||
|
|
||||||
#include <queue>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
using std::map;
|
|
||||||
using std::pair;
|
|
||||||
using std::queue;
|
|
||||||
using std::string;
|
|
||||||
using std::unordered_map;
|
|
||||||
using std::vector;
|
|
||||||
|
|
||||||
using namespace DFHack;
|
|
||||||
|
|
||||||
DFHACK_PLUGIN("buildingplan");
|
|
||||||
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
|
||||||
|
|
||||||
REQUIRE_GLOBAL(world);
|
|
||||||
|
|
||||||
// logging levels can be dynamically controlled with the `debugfilter` command.
|
|
||||||
namespace DFHack {
|
|
||||||
// for configuration-related logging
|
|
||||||
DBG_DECLARE(buildingplan, status, DebugCategory::LINFO);
|
|
||||||
// for logging during the periodic scan
|
|
||||||
DBG_DECLARE(buildingplan, cycle, DebugCategory::LINFO);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const string CONFIG_KEY = string(plugin_name) + "/config";
|
|
||||||
static const string BLD_CONFIG_KEY = string(plugin_name) + "/building";
|
|
||||||
|
|
||||||
enum ConfigValues {
|
|
||||||
CONFIG_BLOCKS = 1,
|
|
||||||
CONFIG_BOULDERS = 2,
|
|
||||||
CONFIG_LOGS = 3,
|
|
||||||
CONFIG_BARS = 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum BuildingConfigValues {
|
|
||||||
BLD_CONFIG_ID = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int get_config_val(PersistentDataItem &c, int index) {
|
|
||||||
if (!c.isValid())
|
|
||||||
return -1;
|
|
||||||
return c.ival(index);
|
|
||||||
}
|
|
||||||
static bool get_config_bool(PersistentDataItem &c, int index) {
|
|
||||||
return get_config_val(c, index) == 1;
|
|
||||||
}
|
|
||||||
static void set_config_val(PersistentDataItem &c, int index, int value) {
|
|
||||||
if (c.isValid())
|
|
||||||
c.ival(index) = value;
|
|
||||||
}
|
|
||||||
static void set_config_bool(PersistentDataItem &c, int index, bool value) {
|
|
||||||
set_config_val(c, index, value ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
class PlannedBuilding {
|
|
||||||
public:
|
|
||||||
const df::building::key_field_type id;
|
|
||||||
|
|
||||||
PlannedBuilding(color_ostream &out, df::building *building) : id(building->id) {
|
|
||||||
DEBUG(status,out).print("creating persistent data for building %d\n", id);
|
|
||||||
bld_config = DFHack::World::AddPersistentData(BLD_CONFIG_KEY);
|
|
||||||
set_config_val(bld_config, BLD_CONFIG_ID, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
PlannedBuilding(DFHack::PersistentDataItem &bld_config)
|
|
||||||
: id(get_config_val(bld_config, BLD_CONFIG_ID)), bld_config(bld_config) { }
|
|
||||||
|
|
||||||
void remove(color_ostream &out);
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// disabled, manually removing the building, modifying it via the API, etc.
|
|
||||||
df::building * getBuildingIfValidOrRemoveIfNot(color_ostream &out) {
|
|
||||||
auto bld = df::building::find(id);
|
|
||||||
bool valid = bld && bld->getBuildStage() == 0;
|
|
||||||
if (!valid) {
|
|
||||||
remove(out);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return bld;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
DFHack::PersistentDataItem bld_config;
|
|
||||||
};
|
|
||||||
|
|
||||||
static PersistentDataItem config;
|
|
||||||
// building id -> PlannedBuilding
|
|
||||||
unordered_map<int32_t, PlannedBuilding> planned_buildings;
|
|
||||||
// vector id -> filter bucket -> queue of (building id, job_item index)
|
|
||||||
map<df::job_item_vector_id, map<string, queue<pair<int32_t, int>>>> tasks;
|
|
||||||
|
|
||||||
// note that this just removes the PlannedBuilding. the tasks will get dropped
|
|
||||||
// as we discover them in the tasks queues and they fail to be found in planned_buildings.
|
|
||||||
// this "lazy" task cleaning algorithm works because there is no way to
|
|
||||||
// re-register a building once it has been removed -- if it has been booted out of
|
|
||||||
// planned_buildings, then it has either been built or desroyed. therefore there is
|
|
||||||
// no chance of duplicate tasks getting added to the tasks queues.
|
|
||||||
void PlannedBuilding::remove(color_ostream &out) {
|
|
||||||
DEBUG(status,out).print("removing persistent data for building %d\n", id);
|
|
||||||
DFHack::World::DeletePersistentData(config);
|
|
||||||
if (planned_buildings.count(id) > 0)
|
|
||||||
planned_buildings.erase(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const int32_t CYCLE_TICKS = 600; // twice per game day
|
|
||||||
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
|
|
||||||
|
|
||||||
static command_result do_command(color_ostream &out, vector<string> ¶meters);
|
|
||||||
static void do_cycle(color_ostream &out);
|
|
||||||
static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb);
|
|
||||||
|
|
||||||
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
|
|
||||||
DEBUG(status,out).print("initializing %s\n", plugin_name);
|
|
||||||
|
|
||||||
// provide a configuration interface for the plugin
|
|
||||||
commands.push_back(PluginCommand(
|
|
||||||
plugin_name,
|
|
||||||
"Plan building placement before you have materials.",
|
|
||||||
do_command));
|
|
||||||
|
|
||||||
return CR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
|
|
||||||
if (enable != is_enabled) {
|
|
||||||
is_enabled = enable;
|
|
||||||
DEBUG(status,out).print("%s from the API; persisting\n",
|
|
||||||
is_enabled ? "enabled" : "disabled");
|
|
||||||
} else {
|
|
||||||
DEBUG(status,out).print("%s from the API, but already %s; no action\n",
|
|
||||||
is_enabled ? "enabled" : "disabled",
|
|
||||||
is_enabled ? "enabled" : "disabled");
|
|
||||||
}
|
|
||||||
return CR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
DFhackCExport command_result plugin_shutdown (color_ostream &out) {
|
|
||||||
DEBUG(status,out).print("shutting down %s\n", plugin_name);
|
|
||||||
|
|
||||||
return CR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
DFhackCExport command_result plugin_load_data (color_ostream &out) {
|
|
||||||
cycle_timestamp = 0;
|
|
||||||
config = World::GetPersistentData(CONFIG_KEY);
|
|
||||||
|
|
||||||
if (!config.isValid()) {
|
|
||||||
DEBUG(status,out).print("no config found in this save; initializing\n");
|
|
||||||
config = World::AddPersistentData(CONFIG_KEY);
|
|
||||||
set_config_bool(config, CONFIG_BLOCKS, true);
|
|
||||||
set_config_bool(config, CONFIG_BOULDERS, true);
|
|
||||||
set_config_bool(config, CONFIG_LOGS, true);
|
|
||||||
set_config_bool(config, CONFIG_BARS, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
DEBUG(status,out).print("loading persisted state\n");
|
|
||||||
planned_buildings.clear();
|
|
||||||
tasks.clear();
|
|
||||||
vector<PersistentDataItem> building_configs;
|
|
||||||
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]);
|
|
||||||
registerPlannedBuilding(out, pb);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
|
|
||||||
if (event == DFHack::SC_WORLD_UNLOADED) {
|
|
||||||
DEBUG(status,out).print("world unloaded; clearing state for %s\n", plugin_name);
|
|
||||||
planned_buildings.clear();
|
|
||||||
tasks.clear();
|
|
||||||
}
|
|
||||||
return CR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool cycle_requested = false;
|
|
||||||
|
|
||||||
DFhackCExport command_result plugin_onupdate(color_ostream &out) {
|
|
||||||
if (!Core::getInstance().isWorldLoaded())
|
|
||||||
return CR_OK;
|
|
||||||
|
|
||||||
if (is_enabled &&
|
|
||||||
(cycle_requested || world->frame_counter - cycle_timestamp >= CYCLE_TICKS))
|
|
||||||
do_cycle(out);
|
|
||||||
return CR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool call_buildingplan_lua(color_ostream *out, const char *fn_name,
|
|
||||||
int nargs = 0, int nres = 0,
|
|
||||||
Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA,
|
|
||||||
Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) {
|
|
||||||
DEBUG(status).print("calling buildingplan lua function: '%s'\n", fn_name);
|
|
||||||
|
|
||||||
CoreSuspender guard;
|
|
||||||
|
|
||||||
auto L = Lua::Core::State;
|
|
||||||
Lua::StackUnwinder top(L);
|
|
||||||
|
|
||||||
if (!out)
|
|
||||||
out = &Core::getInstance().getConsole();
|
|
||||||
|
|
||||||
return Lua::CallLuaModuleFunction(*out, L, "plugins.buildingplan", fn_name,
|
|
||||||
nargs, nres,
|
|
||||||
std::forward<Lua::LuaLambda&&>(args_lambda),
|
|
||||||
std::forward<Lua::LuaLambda&&>(res_lambda));
|
|
||||||
}
|
|
||||||
|
|
||||||
static command_result do_command(color_ostream &out, vector<string> ¶meters) {
|
|
||||||
CoreSuspender suspend;
|
|
||||||
|
|
||||||
if (!Core::getInstance().isWorldLoaded()) {
|
|
||||||
out.printerr("Cannot configure %s without a loaded world.\n", plugin_name);
|
|
||||||
return CR_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool show_help = false;
|
|
||||||
if (!call_buildingplan_lua(&out, "parse_commandline", parameters.size(), 1,
|
|
||||||
[&](lua_State *L) {
|
|
||||||
for (const string ¶m : parameters)
|
|
||||||
Lua::Push(L, param);
|
|
||||||
},
|
|
||||||
[&](lua_State *L) {
|
|
||||||
show_help = !lua_toboolean(L, -1);
|
|
||||||
})) {
|
|
||||||
return CR_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return show_help ? CR_WRONG_USAGE : CR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
|
||||||
// cycle logic
|
|
||||||
//
|
|
||||||
|
|
||||||
struct BadFlags {
|
|
||||||
uint32_t whole;
|
|
||||||
|
|
||||||
BadFlags() {
|
|
||||||
df::item_flags flags;
|
|
||||||
#define F(x) flags.bits.x = true;
|
|
||||||
F(dump); F(forbid); F(garbage_collect);
|
|
||||||
F(hostile); F(on_fire); F(rotten); F(trader);
|
|
||||||
F(in_building); F(construction); F(in_job);
|
|
||||||
F(owned); F(in_chest); F(removed); F(encased);
|
|
||||||
F(spider_web);
|
|
||||||
#undef F
|
|
||||||
whole = flags.whole;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool itemPassesScreen(df::item * item) {
|
|
||||||
static const BadFlags bad_flags;
|
|
||||||
return !(item->flags.whole & bad_flags.whole)
|
|
||||||
&& !item->isAssignedToStockpile();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool matchesFilters(df::item * item, df::job_item * job_item) {
|
|
||||||
// check the properties that are not checked by Job::isSuitableItem()
|
|
||||||
if (job_item->item_type > -1 && job_item->item_type != item->getType())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (job_item->item_subtype > -1 &&
|
|
||||||
job_item->item_subtype != item->getSubtype())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (job_item->flags2.bits.building_material && !item->isBuildMat())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (job_item->metal_ore > -1 && !item->isMetalOre(job_item->metal_ore))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (job_item->has_tool_use > df::tool_uses::NONE
|
|
||||||
&& !item->hasToolUse(job_item->has_tool_use))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return DFHack::Job::isSuitableItem(
|
|
||||||
job_item, item->getType(), item->getSubtype())
|
|
||||||
&& DFHack::Job::isSuitableMaterial(
|
|
||||||
job_item, item->getMaterial(), item->getMaterialIndex(),
|
|
||||||
item->getType());
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isJobReady(color_ostream &out, df::job * job) {
|
|
||||||
int needed_items = 0;
|
|
||||||
for (auto job_item : job->job_items) { needed_items += job_item->quantity; }
|
|
||||||
if (needed_items) {
|
|
||||||
DEBUG(cycle,out).print("building needs %d more item(s)\n", needed_items);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool job_item_idx_lt(df::job_item_ref *a, df::job_item_ref *b) {
|
|
||||||
// we want the items in the opposite order of the filters
|
|
||||||
return a->job_item_idx > b->job_item_idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this function does not remove the job_items since their quantity fields are
|
|
||||||
// now all at 0, so there is no risk of having extra items attached. we don't
|
|
||||||
// remove them to keep the "finalize with buildingplan active" path as similar
|
|
||||||
// as possible to the "finalize with buildingplan disabled" path.
|
|
||||||
static void finalizeBuilding(color_ostream &out, df::building * bld) {
|
|
||||||
DEBUG(cycle,out).print("finalizing building %d\n", bld->id);
|
|
||||||
auto job = bld->jobs[0];
|
|
||||||
|
|
||||||
// sort the items so they get added to the structure in the correct order
|
|
||||||
std::sort(job->items.begin(), job->items.end(), job_item_idx_lt);
|
|
||||||
|
|
||||||
// derive the material properties of the building and job from the first
|
|
||||||
// applicable item. if any boulders are involved, it makes the whole
|
|
||||||
// structure "rough".
|
|
||||||
bool rough = false;
|
|
||||||
for (auto attached_item : job->items) {
|
|
||||||
df::item *item = attached_item->item;
|
|
||||||
rough = rough || item->getType() == df::item_type::BOULDER;
|
|
||||||
if (bld->mat_type == -1) {
|
|
||||||
bld->mat_type = item->getMaterial();
|
|
||||||
job->mat_type = bld->mat_type;
|
|
||||||
}
|
|
||||||
if (bld->mat_index == -1) {
|
|
||||||
bld->mat_index = item->getMaterialIndex();
|
|
||||||
job->mat_index = bld->mat_index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bld->needsDesign()) {
|
|
||||||
auto act = (df::building_actual *)bld;
|
|
||||||
if (!act->design)
|
|
||||||
act->design = new df::building_design();
|
|
||||||
act->design->flags.bits.rough = rough;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we're good to go!
|
|
||||||
job->flags.bits.suspend = false;
|
|
||||||
Job::checkBuildingsNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
static df::building * popInvalidTasks(color_ostream &out, queue<pair<int32_t, int>> & task_queue) {
|
|
||||||
while (!task_queue.empty()) {
|
|
||||||
auto & task = task_queue.front();
|
|
||||||
auto id = task.first;
|
|
||||||
if (planned_buildings.count(id) > 0) {
|
|
||||||
auto bld = planned_buildings.at(id).getBuildingIfValidOrRemoveIfNot(out);
|
|
||||||
if (bld && bld->jobs[0]->job_items[task.second]->quantity)
|
|
||||||
return bld;
|
|
||||||
}
|
|
||||||
DEBUG(cycle,out).print("discarding invalid task: bld=%d, job_item_idx=%d\n", id, task.second);
|
|
||||||
task_queue.pop();
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void doVector(color_ostream &out, df::job_item_vector_id vector_id,
|
|
||||||
map<string, queue<pair<int32_t, int>>> & buckets) {
|
|
||||||
auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id);
|
|
||||||
auto item_vector = df::global::world->items.other[other_id];
|
|
||||||
DEBUG(cycle,out).print("matching %zu item(s) in vector %s against %zu filter bucket(s)\n",
|
|
||||||
item_vector.size(),
|
|
||||||
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
|
||||||
buckets.size());
|
|
||||||
for (auto item_it = item_vector.rbegin();
|
|
||||||
item_it != item_vector.rend();
|
|
||||||
++item_it) {
|
|
||||||
auto item = *item_it;
|
|
||||||
if (!itemPassesScreen(item))
|
|
||||||
continue;
|
|
||||||
for (auto bucket_it = buckets.begin(); bucket_it != buckets.end(); ) {
|
|
||||||
auto & task_queue = bucket_it->second;
|
|
||||||
auto bld = popInvalidTasks(out, task_queue);
|
|
||||||
if (!bld) {
|
|
||||||
DEBUG(cycle,out).print("removing empty bucket: %s/%s; %zu bucket(s) left\n",
|
|
||||||
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
|
||||||
bucket_it->first.c_str(),
|
|
||||||
buckets.size() - 1);
|
|
||||||
bucket_it = buckets.erase(bucket_it);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto & task = task_queue.front();
|
|
||||||
auto id = task.first;
|
|
||||||
auto job = bld->jobs[0];
|
|
||||||
auto filter_idx = task.second;
|
|
||||||
if (matchesFilters(item, job->job_items[filter_idx])
|
|
||||||
&& DFHack::Job::attachJobItem(job, item,
|
|
||||||
df::job_item_ref::Hauled, filter_idx))
|
|
||||||
{
|
|
||||||
MaterialInfo material;
|
|
||||||
material.decode(item);
|
|
||||||
ItemTypeInfo item_type;
|
|
||||||
item_type.decode(item);
|
|
||||||
DEBUG(cycle,out).print("attached %s %s to filter %d for %s(%d): %s/%s\n",
|
|
||||||
material.toString().c_str(),
|
|
||||||
item_type.toString().c_str(),
|
|
||||||
filter_idx,
|
|
||||||
ENUM_KEY_STR(building_type, bld->getType()).c_str(),
|
|
||||||
id,
|
|
||||||
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
|
||||||
bucket_it->first.c_str());
|
|
||||||
// keep quantity aligned with the actual number of remaining
|
|
||||||
// items so if buildingplan is turned off, the building will
|
|
||||||
// be completed with the correct number of items.
|
|
||||||
--job->job_items[filter_idx]->quantity;
|
|
||||||
task_queue.pop();
|
|
||||||
if (isJobReady(out, job)) {
|
|
||||||
finalizeBuilding(out, bld);
|
|
||||||
planned_buildings.at(id).remove(out);
|
|
||||||
}
|
|
||||||
if (task_queue.empty()) {
|
|
||||||
DEBUG(cycle,out).print(
|
|
||||||
"removing empty item bucket: %s/%s; %zu left\n",
|
|
||||||
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
|
||||||
bucket_it->first.c_str(),
|
|
||||||
buckets.size() - 1);
|
|
||||||
buckets.erase(bucket_it);
|
|
||||||
}
|
|
||||||
// we found a home for this item; no need to look further
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++bucket_it;
|
|
||||||
}
|
|
||||||
if (buckets.empty())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VectorsToScanLast {
|
|
||||||
std::vector<df::job_item_vector_id> vectors;
|
|
||||||
VectorsToScanLast() {
|
|
||||||
// order is important here. we want to match boulders before wood and
|
|
||||||
// everything before bars. blocks are not listed here since we'll have
|
|
||||||
// already scanned them when we did the first pass through the buckets.
|
|
||||||
vectors.push_back(df::job_item_vector_id::BOULDER);
|
|
||||||
vectors.push_back(df::job_item_vector_id::WOOD);
|
|
||||||
vectors.push_back(df::job_item_vector_id::BAR);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static void do_cycle(color_ostream &out) {
|
|
||||||
static const VectorsToScanLast vectors_to_scan_last;
|
|
||||||
|
|
||||||
// mark that we have recently run
|
|
||||||
cycle_timestamp = world->frame_counter;
|
|
||||||
cycle_requested = false;
|
|
||||||
|
|
||||||
DEBUG(cycle,out).print("running %s cycle for %zu registered buildings\n",
|
|
||||||
plugin_name, planned_buildings.size());
|
|
||||||
|
|
||||||
for (auto it = tasks.begin(); it != tasks.end(); ) {
|
|
||||||
auto vector_id = it->first;
|
|
||||||
// we could make this a set, but it's only three elements
|
|
||||||
if (std::find(vectors_to_scan_last.vectors.begin(),
|
|
||||||
vectors_to_scan_last.vectors.end(),
|
|
||||||
vector_id) != vectors_to_scan_last.vectors.end()) {
|
|
||||||
++it;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto & buckets = it->second;
|
|
||||||
doVector(out, vector_id, buckets);
|
|
||||||
if (buckets.empty()) {
|
|
||||||
DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n",
|
|
||||||
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
|
||||||
tasks.size() - 1);
|
|
||||||
it = tasks.erase(it);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
for (auto vector_id : vectors_to_scan_last.vectors) {
|
|
||||||
if (tasks.count(vector_id) == 0)
|
|
||||||
continue;
|
|
||||||
auto & buckets = tasks[vector_id];
|
|
||||||
doVector(out, vector_id, buckets);
|
|
||||||
if (buckets.empty()) {
|
|
||||||
DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n",
|
|
||||||
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
|
||||||
tasks.size() - 1);
|
|
||||||
tasks.erase(vector_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DEBUG(cycle,out).print("cycle done; %zu registered building(s) left\n",
|
|
||||||
planned_buildings.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
|
||||||
// Lua API
|
|
||||||
// core will already be suspended when coming in through here
|
|
||||||
//
|
|
||||||
|
|
||||||
static string getBucket(const df::job_item & ji) {
|
|
||||||
std::ostringstream ser;
|
|
||||||
|
|
||||||
// pull out and serialize only known relevant fields. if we miss a few, then
|
|
||||||
// the filter bucket will be slighly less specific than it could be, but
|
|
||||||
// that's probably ok. we'll just end up bucketing slightly different items
|
|
||||||
// together. this is only a problem if the different filter at the front of
|
|
||||||
// the queue doesn't match any available items and blocks filters behind it
|
|
||||||
// that could be matched.
|
|
||||||
ser << ji.item_type << ':' << ji.item_subtype << ':' << ji.mat_type << ':'
|
|
||||||
<< ji.mat_index << ':' << ji.flags1.whole << ':' << ji.flags2.whole
|
|
||||||
<< ':' << ji.flags3.whole << ':' << ji.flags4 << ':' << ji.flags5 << ':'
|
|
||||||
<< ji.metal_ore << ':' << ji.has_tool_use;
|
|
||||||
|
|
||||||
return ser.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
// get a list of item vectors that we should search for matches
|
|
||||||
static vector<df::job_item_vector_id> getVectorIds(color_ostream &out, df::job_item *job_item)
|
|
||||||
{
|
|
||||||
std::vector<df::job_item_vector_id> ret;
|
|
||||||
|
|
||||||
// if the filter already has the vector_id set to something specific, use it
|
|
||||||
if (job_item->vector_id > df::job_item_vector_id::IN_PLAY)
|
|
||||||
{
|
|
||||||
DEBUG(status,out).print("using vector_id from job_item: %s\n",
|
|
||||||
ENUM_KEY_STR(job_item_vector_id, job_item->vector_id).c_str());
|
|
||||||
ret.push_back(job_item->vector_id);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the filer is for building material, refer to our global settings for
|
|
||||||
// which vectors to search
|
|
||||||
if (job_item->flags2.bits.building_material)
|
|
||||||
{
|
|
||||||
if (get_config_bool(config, CONFIG_BLOCKS))
|
|
||||||
ret.push_back(df::job_item_vector_id::BLOCKS);
|
|
||||||
if (get_config_bool(config, CONFIG_BOULDERS))
|
|
||||||
ret.push_back(df::job_item_vector_id::BOULDER);
|
|
||||||
if (get_config_bool(config, CONFIG_LOGS))
|
|
||||||
ret.push_back(df::job_item_vector_id::WOOD);
|
|
||||||
if (get_config_bool(config, CONFIG_BARS))
|
|
||||||
ret.push_back(df::job_item_vector_id::BAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
// fall back to IN_PLAY if no other vector was appropriate
|
|
||||||
if (ret.empty())
|
|
||||||
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)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (bld->jobs.size() != 1) {
|
|
||||||
DEBUG(status,out).print("unexpected number of jobs: want 1, got %zu\n", bld->jobs.size());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto job_items = bld->jobs[0]->job_items;
|
|
||||||
int num_job_items = job_items.size();
|
|
||||||
if (num_job_items < 1) {
|
|
||||||
DEBUG(status,out).print("unexpected number of job items: want >0, got %d\n", num_job_items);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int32_t id = bld->id;
|
|
||||||
for (int job_item_idx = 0; job_item_idx < num_job_items; ++job_item_idx) {
|
|
||||||
auto job_item = job_items[job_item_idx];
|
|
||||||
auto bucket = getBucket(*job_item);
|
|
||||||
auto vector_ids = getVectorIds(out, job_item);
|
|
||||||
|
|
||||||
// if there are multiple vector_ids, schedule duplicate tasks. after
|
|
||||||
// the correct number of items are matched, the extras will get popped
|
|
||||||
// as invalid
|
|
||||||
for (auto vector_id : vector_ids) {
|
|
||||||
for (int item_num = 0; item_num < job_item->quantity; ++item_num) {
|
|
||||||
tasks[vector_id][bucket].push(std::make_pair(id, job_item_idx));
|
|
||||||
DEBUG(status,out).print("added task: %s/%s/%d,%d; "
|
|
||||||
"%zu vector(s), %zu filter bucket(s), %zu task(s) in bucket",
|
|
||||||
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
|
||||||
bucket.c_str(), id, job_item_idx, tasks.size(),
|
|
||||||
tasks[vector_id].size(), tasks[vector_id][bucket].size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// suspend jobs
|
|
||||||
for (auto job : bld->jobs)
|
|
||||||
job->flags.bits.suspend = true;
|
|
||||||
|
|
||||||
// add the planned buildings to our register
|
|
||||||
planned_buildings.emplace(bld->id, pb);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void printStatus(color_ostream &out) {
|
|
||||||
DEBUG(status,out).print("entering buildingplan_printStatus\n");
|
|
||||||
out.print("buildingplan is %s\n\n", is_enabled ? "enabled" : "disabled");
|
|
||||||
out.print(" finding materials for %zd buildings\n", planned_buildings.size());
|
|
||||||
out.print("Current settings:\n");
|
|
||||||
out.print(" use blocks: %s\n", get_config_bool(config, CONFIG_BLOCKS) ? "yes" : "no");
|
|
||||||
out.print(" use boulders: %s\n", get_config_bool(config, CONFIG_BOULDERS) ? "yes" : "no");
|
|
||||||
out.print(" use logs: %s\n", get_config_bool(config, CONFIG_LOGS) ? "yes" : "no");
|
|
||||||
out.print(" use bars: %s\n", get_config_bool(config, CONFIG_BARS) ? "yes" : "no");
|
|
||||||
out.print("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool setSetting(color_ostream &out, string name, bool value) {
|
|
||||||
DEBUG(status,out).print("entering setSetting (%s -> %s)\n", name.c_str(), value ? "true" : "false");
|
|
||||||
if (name == "blocks")
|
|
||||||
set_config_bool(config, CONFIG_BLOCKS, value);
|
|
||||||
else if (name == "boulders")
|
|
||||||
set_config_bool(config, CONFIG_BOULDERS, value);
|
|
||||||
else if (name == "logs")
|
|
||||||
set_config_bool(config, CONFIG_LOGS, value);
|
|
||||||
else if (name == "bars")
|
|
||||||
set_config_bool(config, CONFIG_BARS, value);
|
|
||||||
else {
|
|
||||||
out.printerr("unrecognized setting: '%s'\n", name.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isPlannableBuilding(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom) {
|
|
||||||
DEBUG(status,out).print("entering isPlannableBuilding\n");
|
|
||||||
int num_filters = 0;
|
|
||||||
if (!call_buildingplan_lua(&out, "get_num_filters", 3, 1,
|
|
||||||
[&](lua_State *L) {
|
|
||||||
Lua::Push(L, type);
|
|
||||||
Lua::Push(L, subtype);
|
|
||||||
Lua::Push(L, custom);
|
|
||||||
},
|
|
||||||
[&](lua_State *L) {
|
|
||||||
num_filters = lua_tonumber(L, -1);
|
|
||||||
})) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return num_filters >= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isPlannedBuilding(color_ostream &out, df::building *bld) {
|
|
||||||
TRACE(status,out).print("entering isPlannedBuilding\n");
|
|
||||||
return bld && planned_buildings.count(bld->id) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool addPlannedBuilding(color_ostream &out, df::building *bld) {
|
|
||||||
DEBUG(status,out).print("entering addPlannedBuilding\n");
|
|
||||||
if (!bld || planned_buildings.count(bld->id)
|
|
||||||
|| !isPlannableBuilding(out, bld->getType(), bld->getSubtype(),
|
|
||||||
bld->getCustomType()))
|
|
||||||
return false;
|
|
||||||
PlannedBuilding pb(out, bld);
|
|
||||||
return registerPlannedBuilding(out, pb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void doCycle(color_ostream &out) {
|
|
||||||
DEBUG(status,out).print("entering doCycle\n");
|
|
||||||
do_cycle(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void scheduleCycle(color_ostream &out) {
|
|
||||||
DEBUG(status,out).print("entering scheduleCycle\n");
|
|
||||||
cycle_requested = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
DFHACK_PLUGIN_LUA_FUNCTIONS {
|
|
||||||
DFHACK_LUA_FUNCTION(printStatus),
|
|
||||||
DFHACK_LUA_FUNCTION(setSetting),
|
|
||||||
DFHACK_LUA_FUNCTION(isPlannableBuilding),
|
|
||||||
DFHACK_LUA_FUNCTION(isPlannedBuilding),
|
|
||||||
DFHACK_LUA_FUNCTION(addPlannedBuilding),
|
|
||||||
DFHACK_LUA_FUNCTION(doCycle),
|
|
||||||
DFHACK_LUA_FUNCTION(scheduleCycle),
|
|
||||||
DFHACK_LUA_END
|
|
||||||
};
|
|
File diff suppressed because it is too large
Load Diff
@ -1,140 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <queue>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
#include "df/building.h"
|
|
||||||
#include "df/dfhack_material_category.h"
|
|
||||||
#include "df/item_quality.h"
|
|
||||||
#include "df/job_item.h"
|
|
||||||
|
|
||||||
#include "modules/Materials.h"
|
|
||||||
#include "modules/Persistence.h"
|
|
||||||
|
|
||||||
class ItemFilter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ItemFilter();
|
|
||||||
|
|
||||||
void clear();
|
|
||||||
bool deserialize(std::string ser);
|
|
||||||
std::string serialize() const;
|
|
||||||
|
|
||||||
void addMaterialMask(uint32_t mask);
|
|
||||||
void clearMaterialMask();
|
|
||||||
void setMaterials(std::vector<DFHack::MaterialInfo> materials);
|
|
||||||
|
|
||||||
void incMinQuality();
|
|
||||||
void decMinQuality();
|
|
||||||
void incMaxQuality();
|
|
||||||
void decMaxQuality();
|
|
||||||
void toggleDecoratedOnly();
|
|
||||||
|
|
||||||
uint32_t getMaterialMask() const;
|
|
||||||
std::vector<std::string> getMaterials() const;
|
|
||||||
std::string getMinQuality() const;
|
|
||||||
std::string getMaxQuality() const;
|
|
||||||
bool getDecoratedOnly() const;
|
|
||||||
|
|
||||||
bool matches(df::dfhack_material_category mask) const;
|
|
||||||
bool matches(DFHack::MaterialInfo &material) const;
|
|
||||||
bool matches(df::item *item) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// remove friend declaration when we no longer need v1 deserialization
|
|
||||||
friend void migrateV1ToV2();
|
|
||||||
|
|
||||||
df::dfhack_material_category mat_mask;
|
|
||||||
std::vector<DFHack::MaterialInfo> materials;
|
|
||||||
df::item_quality min_quality;
|
|
||||||
df::item_quality max_quality;
|
|
||||||
bool decorated_only;
|
|
||||||
|
|
||||||
bool deserializeMaterialMask(std::string ser);
|
|
||||||
bool deserializeMaterials(std::string ser);
|
|
||||||
void setMinQuality(int quality);
|
|
||||||
void setMaxQuality(int quality);
|
|
||||||
bool matchesMask(DFHack::MaterialInfo &mat) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PlannedBuilding
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PlannedBuilding(df::building *building, const std::vector<ItemFilter> &filters);
|
|
||||||
PlannedBuilding(DFHack::PersistentDataItem &config);
|
|
||||||
|
|
||||||
bool isValid() const;
|
|
||||||
void remove();
|
|
||||||
|
|
||||||
df::building * getBuilding();
|
|
||||||
const std::vector<ItemFilter> & getFilters() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
DFHack::PersistentDataItem config;
|
|
||||||
df::building *building;
|
|
||||||
const df::building::key_field_type building_id;
|
|
||||||
const std::vector<ItemFilter> filters;
|
|
||||||
};
|
|
||||||
|
|
||||||
// building type, subtype, custom
|
|
||||||
typedef std::tuple<df::building_type, int16_t, int32_t> BuildingTypeKey;
|
|
||||||
|
|
||||||
BuildingTypeKey toBuildingTypeKey(
|
|
||||||
df::building_type btype, int16_t subtype, int32_t custom);
|
|
||||||
BuildingTypeKey toBuildingTypeKey(df::building *bld);
|
|
||||||
BuildingTypeKey toBuildingTypeKey(df::ui_build_selector *uibs);
|
|
||||||
|
|
||||||
struct BuildingTypeKeyHash
|
|
||||||
{
|
|
||||||
std::size_t operator() (const BuildingTypeKey & key) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Planner
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
class ItemFiltersWrapper
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ItemFiltersWrapper(std::vector<ItemFilter> & item_filters)
|
|
||||||
: item_filters(item_filters) { }
|
|
||||||
std::vector<ItemFilter>::reverse_iterator rbegin() const { return item_filters.rbegin(); }
|
|
||||||
std::vector<ItemFilter>::reverse_iterator rend() const { return item_filters.rend(); }
|
|
||||||
const std::vector<ItemFilter> & get() const { return item_filters; }
|
|
||||||
private:
|
|
||||||
std::vector<ItemFilter> &item_filters;
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::map<std::string, bool> & getGlobalSettings() const;
|
|
||||||
bool setGlobalSetting(std::string name, bool value);
|
|
||||||
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
void addPlannedBuilding(df::building *bld);
|
|
||||||
PlannedBuilding *getPlannedBuilding(df::building *bld);
|
|
||||||
|
|
||||||
bool isPlannableBuilding(BuildingTypeKey key);
|
|
||||||
|
|
||||||
// returns an empty vector if the type is not supported
|
|
||||||
ItemFiltersWrapper getItemFilters(BuildingTypeKey key);
|
|
||||||
|
|
||||||
void doCycle();
|
|
||||||
|
|
||||||
private:
|
|
||||||
DFHack::PersistentDataItem config;
|
|
||||||
std::map<std::string, bool> global_settings;
|
|
||||||
std::unordered_map<BuildingTypeKey,
|
|
||||||
std::vector<ItemFilter>,
|
|
||||||
BuildingTypeKeyHash> default_item_filters;
|
|
||||||
// building id -> PlannedBuilding
|
|
||||||
std::unordered_map<int32_t, PlannedBuilding> planned_buildings;
|
|
||||||
// vector id -> filter bucket -> queue of (building id, job_item index)
|
|
||||||
std::map<df::job_item_vector_id, std::map<std::string, std::queue<std::pair<int32_t, int>>>> tasks;
|
|
||||||
|
|
||||||
bool registerTasks(PlannedBuilding &plannedBuilding);
|
|
||||||
void unregisterBuilding(int32_t id);
|
|
||||||
void popInvalidTasks(std::queue<std::pair<int32_t, int>> &task_queue);
|
|
||||||
void doVector(df::job_item_vector_id vector_id,
|
|
||||||
std::map<std::string, std::queue<std::pair<int32_t, int>>> & buckets);
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Planner planner;
|
|
@ -1,226 +0,0 @@
|
|||||||
#include "buildingplan.h"
|
|
||||||
|
|
||||||
#include <df/entity_position.h>
|
|
||||||
#include <df/job_type.h>
|
|
||||||
#include <df/world.h>
|
|
||||||
|
|
||||||
#include <modules/World.h>
|
|
||||||
#include <modules/Units.h>
|
|
||||||
#include <modules/Buildings.h>
|
|
||||||
|
|
||||||
using namespace DFHack;
|
|
||||||
|
|
||||||
bool canReserveRoom(df::building *building)
|
|
||||||
{
|
|
||||||
if (!building)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (building->jobs.size() > 0 && building->jobs[0]->job_type == df::job_type::DestroyBuilding)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return building->is_room;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Units::NoblePosition> getUniqueNoblePositions(df::unit *unit)
|
|
||||||
{
|
|
||||||
std::vector<Units::NoblePosition> np;
|
|
||||||
Units::getNoblePositions(&np, unit);
|
|
||||||
for (auto iter = np.begin(); iter != np.end(); iter++)
|
|
||||||
{
|
|
||||||
if (iter->position->code == "MILITIA_CAPTAIN")
|
|
||||||
{
|
|
||||||
np.erase(iter);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return np;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ReservedRoom
|
|
||||||
*/
|
|
||||||
|
|
||||||
ReservedRoom::ReservedRoom(df::building *building, std::string noble_code)
|
|
||||||
{
|
|
||||||
this->building = building;
|
|
||||||
config = DFHack::World::AddPersistentData("buildingplan/reservedroom");
|
|
||||||
config.val() = noble_code;
|
|
||||||
config.ival(1) = building->id;
|
|
||||||
pos = df::coord(building->centerx, building->centery, building->z);
|
|
||||||
}
|
|
||||||
|
|
||||||
ReservedRoom::ReservedRoom(PersistentDataItem &config, color_ostream &)
|
|
||||||
{
|
|
||||||
this->config = config;
|
|
||||||
|
|
||||||
building = df::building::find(config.ival(1));
|
|
||||||
if (!building)
|
|
||||||
return;
|
|
||||||
pos = df::coord(building->centerx, building->centery, building->z);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ReservedRoom::checkRoomAssignment()
|
|
||||||
{
|
|
||||||
if (!isValid())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto np = getOwnersNobleCode();
|
|
||||||
bool correctOwner = false;
|
|
||||||
for (auto iter = np.begin(); iter != np.end(); iter++)
|
|
||||||
{
|
|
||||||
if (iter->position->code == getCode())
|
|
||||||
{
|
|
||||||
correctOwner = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (correctOwner)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for (auto iter = df::global::world->units.active.begin(); iter != df::global::world->units.active.end(); iter++)
|
|
||||||
{
|
|
||||||
df::unit* unit = *iter;
|
|
||||||
if (!Units::isCitizen(unit))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!Units::isActive(unit))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
np = getUniqueNoblePositions(unit);
|
|
||||||
for (auto iter = np.begin(); iter != np.end(); iter++)
|
|
||||||
{
|
|
||||||
if (iter->position->code == getCode())
|
|
||||||
{
|
|
||||||
Buildings::setOwner(building, unit);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReservedRoom::remove() { DFHack::World::DeletePersistentData(config); }
|
|
||||||
|
|
||||||
bool ReservedRoom::isValid()
|
|
||||||
{
|
|
||||||
if (!building)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (Buildings::findAtTile(pos) != building)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return canReserveRoom(building);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t ReservedRoom::getId()
|
|
||||||
{
|
|
||||||
if (!isValid())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return building->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ReservedRoom::getCode() { return config.val(); }
|
|
||||||
|
|
||||||
void ReservedRoom::setCode(const std::string &noble_code) { config.val() = noble_code; }
|
|
||||||
|
|
||||||
std::vector<Units::NoblePosition> ReservedRoom::getOwnersNobleCode()
|
|
||||||
{
|
|
||||||
if (!building->owner)
|
|
||||||
return std::vector<Units::NoblePosition> ();
|
|
||||||
|
|
||||||
return getUniqueNoblePositions(building->owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RoomMonitor
|
|
||||||
*/
|
|
||||||
|
|
||||||
std::string RoomMonitor::getReservedNobleCode(int32_t buildingId)
|
|
||||||
{
|
|
||||||
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
|
|
||||||
{
|
|
||||||
if (buildingId == iter->getId())
|
|
||||||
return iter->getCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomMonitor::toggleRoomForPosition(int32_t buildingId, std::string noble_code)
|
|
||||||
{
|
|
||||||
bool found = false;
|
|
||||||
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
|
|
||||||
{
|
|
||||||
if (buildingId != iter->getId())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (noble_code == iter->getCode())
|
|
||||||
{
|
|
||||||
iter->remove();
|
|
||||||
reservedRooms.erase(iter);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
iter->setCode(noble_code);
|
|
||||||
}
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
ReservedRoom room(df::building::find(buildingId), noble_code);
|
|
||||||
reservedRooms.push_back(room);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomMonitor::doCycle()
|
|
||||||
{
|
|
||||||
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end();)
|
|
||||||
{
|
|
||||||
if (iter->checkRoomAssignment())
|
|
||||||
{
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
iter->remove();
|
|
||||||
iter = reservedRooms.erase(iter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomMonitor::reset(color_ostream &out)
|
|
||||||
{
|
|
||||||
reservedRooms.clear();
|
|
||||||
std::vector<PersistentDataItem> items;
|
|
||||||
DFHack::World::GetPersistentData(&items, "buildingplan/reservedroom");
|
|
||||||
|
|
||||||
for (auto i = items.begin(); i != items.end(); i++)
|
|
||||||
{
|
|
||||||
ReservedRoom rr(*i, out);
|
|
||||||
if (rr.isValid())
|
|
||||||
addRoom(rr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomMonitor::addRoom(ReservedRoom &rr)
|
|
||||||
{
|
|
||||||
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
|
|
||||||
{
|
|
||||||
if (iter->getId() == rr.getId())
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
reservedRooms.push_back(rr);
|
|
||||||
}
|
|
||||||
|
|
||||||
RoomMonitor roomMonitor;
|
|
@ -1,51 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "modules/Persistence.h"
|
|
||||||
#include "modules/Units.h"
|
|
||||||
|
|
||||||
class ReservedRoom
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ReservedRoom(df::building *building, std::string noble_code);
|
|
||||||
|
|
||||||
ReservedRoom(DFHack::PersistentDataItem &config, DFHack::color_ostream &out);
|
|
||||||
|
|
||||||
bool checkRoomAssignment();
|
|
||||||
void remove();
|
|
||||||
bool isValid();
|
|
||||||
|
|
||||||
int32_t getId();
|
|
||||||
std::string getCode();
|
|
||||||
void setCode(const std::string &noble_code);
|
|
||||||
|
|
||||||
private:
|
|
||||||
df::building *building;
|
|
||||||
DFHack::PersistentDataItem config;
|
|
||||||
df::coord pos;
|
|
||||||
|
|
||||||
std::vector<DFHack::Units::NoblePosition> getOwnersNobleCode();
|
|
||||||
};
|
|
||||||
|
|
||||||
class RoomMonitor
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
RoomMonitor() { }
|
|
||||||
|
|
||||||
std::string getReservedNobleCode(int32_t buildingId);
|
|
||||||
|
|
||||||
void toggleRoomForPosition(int32_t buildingId, std::string noble_code);
|
|
||||||
|
|
||||||
void doCycle();
|
|
||||||
|
|
||||||
void reset(DFHack::color_ostream &out);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<ReservedRoom> reservedRooms;
|
|
||||||
|
|
||||||
void addRoom(ReservedRoom &rr);
|
|
||||||
};
|
|
||||||
|
|
||||||
bool canReserveRoom(df::building *building);
|
|
||||||
std::vector<DFHack::Units::NoblePosition> getUniqueNoblePositions(df::unit *unit);
|
|
||||||
|
|
||||||
extern RoomMonitor roomMonitor;
|
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,52 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "buildingplan-planner.h"
|
#include "itemfilter.h"
|
||||||
#include "buildingplan-rooms.h"
|
|
||||||
|
|
||||||
void debug(const char *fmt, ...) Wformat(printf,1,2);
|
#include "modules/Persistence.h"
|
||||||
|
|
||||||
extern bool show_debugging;
|
#include "df/building.h"
|
||||||
|
#include "df/job_item.h"
|
||||||
|
#include "df/job_item_vector_id.h"
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
typedef std::deque<std::pair<int32_t, int>> Bucket;
|
||||||
|
typedef std::map<df::job_item_vector_id, std::map<std::string, Bucket>> Tasks;
|
||||||
|
|
||||||
|
extern const std::string FILTER_CONFIG_KEY;
|
||||||
|
extern const std::string BLD_CONFIG_KEY;
|
||||||
|
|
||||||
|
enum ConfigValues {
|
||||||
|
CONFIG_BLOCKS = 1,
|
||||||
|
CONFIG_BOULDERS = 2,
|
||||||
|
CONFIG_LOGS = 3,
|
||||||
|
CONFIG_BARS = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FilterConfigValues {
|
||||||
|
FILTER_CONFIG_TYPE = 0,
|
||||||
|
FILTER_CONFIG_SUBTYPE = 1,
|
||||||
|
FILTER_CONFIG_CUSTOM = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum BuildingConfigValues {
|
||||||
|
BLD_CONFIG_ID = 0,
|
||||||
|
BLD_CONFIG_HEAT = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum HeatSafety {
|
||||||
|
HEAT_SAFETY_ANY = 0,
|
||||||
|
HEAT_SAFETY_FIRE = 1,
|
||||||
|
HEAT_SAFETY_MAGMA = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
int get_config_val(DFHack::PersistentDataItem &c, int index);
|
||||||
|
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<df::job_item_vector_id> getVectorIds(DFHack::color_ostream &out, const df::job_item *job_item);
|
||||||
|
bool itemPassesScreen(df::item * item);
|
||||||
|
bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter);
|
||||||
|
bool isJobReady(DFHack::color_ostream &out, const std::vector<df::job_item *> &jitems);
|
||||||
|
void finalizeBuilding(DFHack::color_ostream &out, df::building *bld);
|
||||||
|
@ -0,0 +1,304 @@
|
|||||||
|
#include "plannedbuilding.h"
|
||||||
|
#include "buildingplan.h"
|
||||||
|
|
||||||
|
#include "Debug.h"
|
||||||
|
|
||||||
|
#include "modules/Items.h"
|
||||||
|
#include "modules/Job.h"
|
||||||
|
#include "modules/Maps.h"
|
||||||
|
#include "modules/Materials.h"
|
||||||
|
|
||||||
|
#include "df/building_design.h"
|
||||||
|
#include "df/item.h"
|
||||||
|
#include "df/job.h"
|
||||||
|
#include "df/map_block.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
using std::map;
|
||||||
|
using std::string;
|
||||||
|
using std::unordered_map;
|
||||||
|
|
||||||
|
namespace DFHack {
|
||||||
|
DBG_EXTERN(buildingplan, cycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
struct BadFlags {
|
||||||
|
uint32_t whole;
|
||||||
|
|
||||||
|
BadFlags() {
|
||||||
|
df::item_flags flags;
|
||||||
|
#define F(x) flags.bits.x = true;
|
||||||
|
F(dump); F(forbid); F(garbage_collect);
|
||||||
|
F(hostile); F(on_fire); F(rotten); F(trader);
|
||||||
|
F(in_building); F(construction); F(in_job);
|
||||||
|
F(owned); F(in_chest); F(removed); F(encased);
|
||||||
|
F(spider_web);
|
||||||
|
#undef F
|
||||||
|
whole = flags.whole;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool itemPassesScreen(df::item * item) {
|
||||||
|
static const BadFlags bad_flags;
|
||||||
|
return !(item->flags.whole & bad_flags.whole)
|
||||||
|
&& !item->isAssignedToStockpile();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter) {
|
||||||
|
// check the properties that are not checked by Job::isSuitableItem()
|
||||||
|
if (job_item->item_type > -1 && job_item->item_type != item->getType())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (job_item->item_subtype > -1 &&
|
||||||
|
job_item->item_subtype != item->getSubtype())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (job_item->flags2.bits.building_material && !item->isBuildMat())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (job_item->metal_ore > -1 && !item->isMetalOre(job_item->metal_ore))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (job_item->has_tool_use > df::tool_uses::NONE
|
||||||
|
&& !item->hasToolUse(job_item->has_tool_use))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
df::job_item jitem = *job_item;
|
||||||
|
if (heat == HEAT_SAFETY_MAGMA) {
|
||||||
|
jitem.flags2.bits.magma_safe = true;
|
||||||
|
jitem.flags2.bits.fire_safe = false;
|
||||||
|
} else if (heat == HEAT_SAFETY_FIRE && !jitem.flags2.bits.magma_safe)
|
||||||
|
jitem.flags2.bits.fire_safe = true;
|
||||||
|
|
||||||
|
return Job::isSuitableItem(
|
||||||
|
&jitem, item->getType(), item->getSubtype())
|
||||||
|
&& Job::isSuitableMaterial(
|
||||||
|
&jitem, item->getMaterial(), item->getMaterialIndex(),
|
||||||
|
item->getType())
|
||||||
|
&& item_filter.matches(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isJobReady(color_ostream &out, const std::vector<df::job_item *> &jitems) {
|
||||||
|
int needed_items = 0;
|
||||||
|
for (auto job_item : jitems) { needed_items += job_item->quantity; }
|
||||||
|
if (needed_items) {
|
||||||
|
DEBUG(cycle,out).print("building needs %d more item(s)\n", needed_items);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool job_item_idx_lt(df::job_item_ref *a, df::job_item_ref *b) {
|
||||||
|
// we want the items in the opposite order of the filters
|
||||||
|
return a->job_item_idx > b->job_item_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function does not remove the job_items since their quantity fields are
|
||||||
|
// now all at 0, so there is no risk of having extra items attached. we don't
|
||||||
|
// remove them to keep the "finalize with buildingplan active" path as similar
|
||||||
|
// as possible to the "finalize with buildingplan disabled" path.
|
||||||
|
void finalizeBuilding(color_ostream &out, df::building *bld) {
|
||||||
|
DEBUG(cycle,out).print("finalizing building %d\n", bld->id);
|
||||||
|
auto job = bld->jobs[0];
|
||||||
|
|
||||||
|
// sort the items so they get added to the structure in the correct order
|
||||||
|
std::sort(job->items.begin(), job->items.end(), job_item_idx_lt);
|
||||||
|
|
||||||
|
// derive the material properties of the building and job from the first
|
||||||
|
// applicable item. if any boulders are involved, it makes the whole
|
||||||
|
// structure "rough".
|
||||||
|
bool rough = false;
|
||||||
|
for (auto attached_item : job->items) {
|
||||||
|
df::item *item = attached_item->item;
|
||||||
|
rough = rough || item->getType() == df::item_type::BOULDER;
|
||||||
|
if (bld->mat_type == -1) {
|
||||||
|
bld->mat_type = item->getMaterial();
|
||||||
|
job->mat_type = bld->mat_type;
|
||||||
|
}
|
||||||
|
if (bld->mat_index == -1) {
|
||||||
|
bld->mat_index = item->getMaterialIndex();
|
||||||
|
job->mat_index = bld->mat_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bld->needsDesign()) {
|
||||||
|
auto act = (df::building_actual *)bld;
|
||||||
|
if (!act->design)
|
||||||
|
act->design = new df::building_design();
|
||||||
|
act->design->flags.bits.rough = rough;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're good to go!
|
||||||
|
job->flags.bits.suspend = false;
|
||||||
|
Job::checkBuildingsNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
static df::building * popInvalidTasks(color_ostream &out, Bucket &task_queue,
|
||||||
|
unordered_map<int32_t, PlannedBuilding> &planned_buildings) {
|
||||||
|
while (!task_queue.empty()) {
|
||||||
|
auto & task = task_queue.front();
|
||||||
|
auto id = task.first;
|
||||||
|
if (planned_buildings.count(id) > 0) {
|
||||||
|
auto bld = planned_buildings.at(id).getBuildingIfValidOrRemoveIfNot(out);
|
||||||
|
if (bld && bld->jobs[0]->job_items[task.second]->quantity)
|
||||||
|
return bld;
|
||||||
|
}
|
||||||
|
DEBUG(cycle,out).print("discarding invalid task: bld=%d, job_item_idx=%d\n", id, task.second);
|
||||||
|
task_queue.pop_front();
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is tricky. we want to choose an item that can be brought to the job site, but that's not
|
||||||
|
// necessarily the same as job->pos. it could be many tiles off in any direction (e.g. for bridges), or
|
||||||
|
// up or down (e.g. for stairs). For now, just return if the item is on a walkable tile.
|
||||||
|
static bool isAccessibleFrom(color_ostream &out, df::item *item, df::job *job) {
|
||||||
|
df::coord item_pos = Items::getPosition(item);
|
||||||
|
df::map_block *block = Maps::getTileBlock(item_pos);
|
||||||
|
bool is_walkable = false;
|
||||||
|
if (block) {
|
||||||
|
uint16_t walkability_group = index_tile(block->walkable, item_pos);
|
||||||
|
is_walkable = walkability_group != 0;
|
||||||
|
TRACE(cycle,out).print("item %d in walkability_group %u at (%d,%d,%d) is %saccessible from job site\n",
|
||||||
|
item->id, walkability_group, item_pos.x, item_pos.y, item_pos.z, is_walkable ? "" : "not ");
|
||||||
|
}
|
||||||
|
return is_walkable;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void doVector(color_ostream &out, df::job_item_vector_id vector_id,
|
||||||
|
map<string, Bucket> &buckets,
|
||||||
|
unordered_map<int32_t, PlannedBuilding> &planned_buildings) {
|
||||||
|
auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id);
|
||||||
|
auto item_vector = df::global::world->items.other[other_id];
|
||||||
|
DEBUG(cycle,out).print("matching %zu item(s) in vector %s against %zu filter bucket(s)\n",
|
||||||
|
item_vector.size(),
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
||||||
|
buckets.size());
|
||||||
|
for (auto item_it = item_vector.rbegin();
|
||||||
|
item_it != item_vector.rend();
|
||||||
|
++item_it) {
|
||||||
|
auto item = *item_it;
|
||||||
|
if (!itemPassesScreen(item))
|
||||||
|
continue;
|
||||||
|
for (auto bucket_it = buckets.begin(); bucket_it != buckets.end(); ) {
|
||||||
|
auto & task_queue = bucket_it->second;
|
||||||
|
auto bld = popInvalidTasks(out, task_queue, planned_buildings);
|
||||||
|
if (!bld) {
|
||||||
|
DEBUG(cycle,out).print("removing empty bucket: %s/%s; %zu bucket(s) left\n",
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
||||||
|
bucket_it->first.c_str(),
|
||||||
|
buckets.size() - 1);
|
||||||
|
bucket_it = buckets.erase(bucket_it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto & task = task_queue.front();
|
||||||
|
auto id = task.first;
|
||||||
|
auto job = bld->jobs[0];
|
||||||
|
auto filter_idx = task.second;
|
||||||
|
auto &pb = planned_buildings.at(id);
|
||||||
|
if (isAccessibleFrom(out, item, job)
|
||||||
|
&& matchesFilters(item, job->job_items[filter_idx], pb.heat_safety,
|
||||||
|
pb.item_filters[filter_idx])
|
||||||
|
&& Job::attachJobItem(job, item,
|
||||||
|
df::job_item_ref::Hauled, filter_idx))
|
||||||
|
{
|
||||||
|
MaterialInfo material;
|
||||||
|
material.decode(item);
|
||||||
|
ItemTypeInfo item_type;
|
||||||
|
item_type.decode(item);
|
||||||
|
DEBUG(cycle,out).print("attached %s %s to filter %d for %s(%d): %s/%s\n",
|
||||||
|
material.toString().c_str(),
|
||||||
|
item_type.toString().c_str(),
|
||||||
|
filter_idx,
|
||||||
|
ENUM_KEY_STR(building_type, bld->getType()).c_str(),
|
||||||
|
id,
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
||||||
|
bucket_it->first.c_str());
|
||||||
|
// keep quantity aligned with the actual number of remaining
|
||||||
|
// items so if buildingplan is turned off, the building will
|
||||||
|
// be completed with the correct number of items.
|
||||||
|
--job->job_items[filter_idx]->quantity;
|
||||||
|
task_queue.pop_front();
|
||||||
|
if (isJobReady(out, job->job_items)) {
|
||||||
|
finalizeBuilding(out, bld);
|
||||||
|
planned_buildings.at(id).remove(out);
|
||||||
|
}
|
||||||
|
if (task_queue.empty()) {
|
||||||
|
DEBUG(cycle,out).print(
|
||||||
|
"removing empty item bucket: %s/%s; %zu left\n",
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
||||||
|
bucket_it->first.c_str(),
|
||||||
|
buckets.size() - 1);
|
||||||
|
buckets.erase(bucket_it);
|
||||||
|
}
|
||||||
|
// we found a home for this item; no need to look further
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++bucket_it;
|
||||||
|
}
|
||||||
|
if (buckets.empty())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VectorsToScanLast {
|
||||||
|
std::vector<df::job_item_vector_id> vectors;
|
||||||
|
VectorsToScanLast() {
|
||||||
|
// order is important here. we want to match boulders before wood and
|
||||||
|
// everything before bars. blocks are not listed here since we'll have
|
||||||
|
// already scanned them when we did the first pass through the buckets.
|
||||||
|
vectors.push_back(df::job_item_vector_id::BOULDER);
|
||||||
|
vectors.push_back(df::job_item_vector_id::WOOD);
|
||||||
|
vectors.push_back(df::job_item_vector_id::BAR);
|
||||||
|
vectors.push_back(df::job_item_vector_id::IN_PLAY);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void buildingplan_cycle(color_ostream &out, Tasks &tasks,
|
||||||
|
unordered_map<int32_t, PlannedBuilding> &planned_buildings) {
|
||||||
|
static const VectorsToScanLast vectors_to_scan_last;
|
||||||
|
|
||||||
|
DEBUG(cycle,out).print(
|
||||||
|
"running buildingplan cycle for %zu registered buildings\n",
|
||||||
|
planned_buildings.size());
|
||||||
|
|
||||||
|
for (auto it = tasks.begin(); it != tasks.end(); ) {
|
||||||
|
auto vector_id = it->first;
|
||||||
|
// we could make this a set, but it's only a few elements
|
||||||
|
if (std::find(vectors_to_scan_last.vectors.begin(),
|
||||||
|
vectors_to_scan_last.vectors.end(),
|
||||||
|
vector_id) != vectors_to_scan_last.vectors.end()) {
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto & buckets = it->second;
|
||||||
|
doVector(out, vector_id, buckets, planned_buildings);
|
||||||
|
if (buckets.empty()) {
|
||||||
|
DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n",
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
||||||
|
tasks.size() - 1);
|
||||||
|
it = tasks.erase(it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
for (auto vector_id : vectors_to_scan_last.vectors) {
|
||||||
|
if (tasks.count(vector_id) == 0)
|
||||||
|
continue;
|
||||||
|
auto & buckets = tasks[vector_id];
|
||||||
|
doVector(out, vector_id, buckets, planned_buildings);
|
||||||
|
if (buckets.empty()) {
|
||||||
|
DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n",
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
||||||
|
tasks.size() - 1);
|
||||||
|
tasks.erase(vector_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DEBUG(cycle,out).print("cycle done; %zu registered building(s) left\n",
|
||||||
|
planned_buildings.size());
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
#include "buildingplan.h"
|
||||||
|
#include "buildingtypekey.h"
|
||||||
|
|
||||||
|
#include "Debug.h"
|
||||||
|
#include "MiscUtils.h"
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
using std::vector;
|
||||||
|
|
||||||
|
namespace DFHack {
|
||||||
|
DBG_EXTERN(buildingplan, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
// building type, subtype, custom
|
||||||
|
BuildingTypeKey::BuildingTypeKey(df::building_type type, int16_t subtype, int32_t custom)
|
||||||
|
: tuple(type, subtype, custom) { }
|
||||||
|
|
||||||
|
static BuildingTypeKey deserialize(color_ostream &out, const std::string &serialized) {
|
||||||
|
vector<string> key_parts;
|
||||||
|
split_string(&key_parts, serialized, ",");
|
||||||
|
if (key_parts.size() != 3) {
|
||||||
|
WARN(status,out).print("invalid key_str: '%s'\n", serialized.c_str());
|
||||||
|
return BuildingTypeKey(df::building_type::NONE, -1, -1);
|
||||||
|
}
|
||||||
|
return BuildingTypeKey((df::building_type)string_to_int(key_parts[0]),
|
||||||
|
string_to_int(key_parts[1]), string_to_int(key_parts[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildingTypeKey::BuildingTypeKey(color_ostream &out, const std::string &serialized)
|
||||||
|
:tuple(deserialize(out, serialized)) { }
|
||||||
|
|
||||||
|
string BuildingTypeKey::serialize() const {
|
||||||
|
std::ostringstream ser;
|
||||||
|
ser << std::get<0>(*this) << ",";
|
||||||
|
ser << std::get<1>(*this) << ",";
|
||||||
|
ser << std::get<2>(*this);
|
||||||
|
return ser.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotates a size_t value left by count bits
|
||||||
|
// assumes count is not 0 or >= size_t_bits
|
||||||
|
// replace this with std::rotl when we move to C++20
|
||||||
|
static std::size_t rotl_size_t(size_t val, uint32_t count)
|
||||||
|
{
|
||||||
|
static const int size_t_bits = CHAR_BIT * sizeof(std::size_t);
|
||||||
|
return val << count | val >> (size_t_bits - count);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t BuildingTypeKeyHash::operator() (const BuildingTypeKey & key) const {
|
||||||
|
// cast first param to appease gcc-4.8, which is missing the enum
|
||||||
|
// specializations for std::hash
|
||||||
|
std::size_t h1 = std::hash<int32_t>()(static_cast<int32_t>(std::get<0>(key)));
|
||||||
|
std::size_t h2 = std::hash<int16_t>()(std::get<1>(key));
|
||||||
|
std::size_t h3 = std::hash<int32_t>()(std::get<2>(key));
|
||||||
|
|
||||||
|
return h1 ^ rotl_size_t(h2, 8) ^ rotl_size_t(h3, 16);
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "df/building_type.h"
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace DFHack {
|
||||||
|
class color_ostream;
|
||||||
|
}
|
||||||
|
|
||||||
|
// building type, subtype, custom
|
||||||
|
struct BuildingTypeKey : public std::tuple<df::building_type, int16_t, int32_t> {
|
||||||
|
BuildingTypeKey(df::building_type type, int16_t subtype, int32_t custom);
|
||||||
|
BuildingTypeKey(DFHack::color_ostream &out, const std::string & serialized);
|
||||||
|
|
||||||
|
std::string serialize() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BuildingTypeKeyHash {
|
||||||
|
std::size_t operator() (const BuildingTypeKey & key) const;
|
||||||
|
};
|
@ -0,0 +1,60 @@
|
|||||||
|
#include "defaultitemfilters.h"
|
||||||
|
|
||||||
|
#include "Debug.h"
|
||||||
|
#include "MiscUtils.h"
|
||||||
|
|
||||||
|
#include "modules/World.h"
|
||||||
|
|
||||||
|
namespace DFHack {
|
||||||
|
DBG_EXTERN(buildingplan, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
using std::vector;
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
BuildingTypeKey DefaultItemFilters::getKey(PersistentDataItem &filter_config) {
|
||||||
|
return BuildingTypeKey(
|
||||||
|
(df::building_type)get_config_val(filter_config, FILTER_CONFIG_TYPE),
|
||||||
|
get_config_val(filter_config, FILTER_CONFIG_SUBTYPE),
|
||||||
|
get_config_val(filter_config, FILTER_CONFIG_CUSTOM));
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey key, const std::vector<const df::job_item *> &jitems)
|
||||||
|
: key(key) {
|
||||||
|
DEBUG(status,out).print("creating persistent data for filter key %d,%d,%d\n",
|
||||||
|
std::get<0>(key), std::get<1>(key), std::get<2>(key));
|
||||||
|
filter_config = World::AddPersistentData(FILTER_CONFIG_KEY);
|
||||||
|
set_config_val(filter_config, FILTER_CONFIG_TYPE, std::get<0>(key));
|
||||||
|
set_config_val(filter_config, FILTER_CONFIG_SUBTYPE, std::get<1>(key));
|
||||||
|
set_config_val(filter_config, FILTER_CONFIG_CUSTOM, std::get<2>(key));
|
||||||
|
item_filters.resize(jitems.size());
|
||||||
|
filter_config.val() = serialize_item_filters(item_filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultItemFilters::DefaultItemFilters(color_ostream &out, PersistentDataItem &filter_config, const std::vector<const df::job_item *> &jitems)
|
||||||
|
: key(getKey(filter_config)), filter_config(filter_config) {
|
||||||
|
auto &serialized = filter_config.val();
|
||||||
|
DEBUG(status,out).print("deserializing item filters for key %d,%d,%d: %s\n",
|
||||||
|
std::get<0>(key), std::get<1>(key), std::get<2>(key), serialized.c_str());
|
||||||
|
std::vector<ItemFilter> filters = deserialize_item_filters(out, serialized);
|
||||||
|
if (filters.size() != jitems.size()) {
|
||||||
|
WARN(status,out).print("ignoring invalid filters_str for key %d,%d,%d: '%s'\n",
|
||||||
|
std::get<0>(key), std::get<1>(key), std::get<2>(key), serialized.c_str());
|
||||||
|
item_filters.resize(jitems.size());
|
||||||
|
} else
|
||||||
|
item_filters = filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefaultItemFilters::setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index) {
|
||||||
|
if (index < 0 || item_filters.size() <= (size_t)index) {
|
||||||
|
WARN(status,out).print("invalid index for filter key %d,%d,%d: %d\n",
|
||||||
|
std::get<0>(key), std::get<1>(key), std::get<2>(key), index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item_filters[index] = filter;
|
||||||
|
filter_config.val() = serialize_item_filters(item_filters);
|
||||||
|
DEBUG(status,out).print("updated item filter and persisted for key %d,%d,%d: %s\n",
|
||||||
|
std::get<0>(key), std::get<1>(key), std::get<2>(key), filter_config.val().c_str());
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "buildingplan.h"
|
||||||
|
#include "buildingtypekey.h"
|
||||||
|
|
||||||
|
#include "modules/Persistence.h"
|
||||||
|
|
||||||
|
class DefaultItemFilters {
|
||||||
|
public:
|
||||||
|
static BuildingTypeKey getKey(DFHack::PersistentDataItem &filter_config);
|
||||||
|
|
||||||
|
const BuildingTypeKey key;
|
||||||
|
|
||||||
|
DefaultItemFilters(DFHack::color_ostream &out, BuildingTypeKey key, const std::vector<const df::job_item *> &jitems);
|
||||||
|
DefaultItemFilters(DFHack::color_ostream &out, DFHack::PersistentDataItem &filter_config, const std::vector<const df::job_item *> &jitems);
|
||||||
|
|
||||||
|
void setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index);
|
||||||
|
|
||||||
|
const std::vector<ItemFilter> & getItemFilters() const { return item_filters; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
DFHack::PersistentDataItem filter_config;
|
||||||
|
std::vector<ItemFilter> item_filters;
|
||||||
|
};
|
@ -0,0 +1,181 @@
|
|||||||
|
#include "itemfilter.h"
|
||||||
|
|
||||||
|
#include "Debug.h"
|
||||||
|
|
||||||
|
#include "df/item.h"
|
||||||
|
|
||||||
|
namespace DFHack {
|
||||||
|
DBG_EXTERN(buildingplan, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
using std::vector;
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
ItemFilter::ItemFilter() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemFilter::clear() {
|
||||||
|
min_quality = df::item_quality::Ordinary;
|
||||||
|
max_quality = df::item_quality::Masterful;
|
||||||
|
decorated_only = false;
|
||||||
|
mat_mask.whole = 0;
|
||||||
|
materials.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ItemFilter::isEmpty() const {
|
||||||
|
return min_quality == df::item_quality::Ordinary
|
||||||
|
&& max_quality == df::item_quality::Masterful
|
||||||
|
&& !decorated_only
|
||||||
|
&& !mat_mask.whole
|
||||||
|
&& materials.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool deserializeMaterialMask(string ser, df::dfhack_material_category mat_mask) {
|
||||||
|
if (ser.empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!parseJobMaterialCategory(&mat_mask, ser)) {
|
||||||
|
DEBUG(status).print("invalid job material category serialization: '%s'", ser.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool deserializeMaterials(string ser, vector<DFHack::MaterialInfo> &materials) {
|
||||||
|
if (ser.empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
vector<string> mat_names;
|
||||||
|
split_string(&mat_names, ser, ",");
|
||||||
|
for (auto m = mat_names.begin(); m != mat_names.end(); m++) {
|
||||||
|
DFHack::MaterialInfo material;
|
||||||
|
if (!material.find(*m) || !material.isValid()) {
|
||||||
|
DEBUG(status).print("invalid material name serialization: '%s'", ser.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
materials.push_back(material);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemFilter::ItemFilter(color_ostream &out, string serialized) {
|
||||||
|
clear();
|
||||||
|
|
||||||
|
vector<string> tokens;
|
||||||
|
split_string(&tokens, serialized, "/");
|
||||||
|
if (tokens.size() != 5) {
|
||||||
|
DEBUG(status,out).print("invalid ItemFilter serialization: '%s'", serialized.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deserializeMaterialMask(tokens[0], mat_mask) || !deserializeMaterials(tokens[1], materials))
|
||||||
|
return;
|
||||||
|
|
||||||
|
setMinQuality(atoi(tokens[2].c_str()));
|
||||||
|
setMaxQuality(atoi(tokens[3].c_str()));
|
||||||
|
decorated_only = static_cast<bool>(atoi(tokens[4].c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// format: mat,mask,elements/materials,list/minq/maxq/decorated
|
||||||
|
string ItemFilter::serialize() const {
|
||||||
|
std::ostringstream ser;
|
||||||
|
ser << bitfield_to_string(mat_mask, ",") << "/";
|
||||||
|
if (!materials.empty()) {
|
||||||
|
ser << materials[0].getToken();
|
||||||
|
for (size_t i = 1; i < materials.size(); ++i)
|
||||||
|
ser << "," << materials[i].getToken();
|
||||||
|
}
|
||||||
|
ser << "/" << static_cast<int>(min_quality);
|
||||||
|
ser << "/" << static_cast<int>(max_quality);
|
||||||
|
ser << "/" << static_cast<int>(decorated_only);
|
||||||
|
return ser.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clampItemQuality(df::item_quality *quality) {
|
||||||
|
if (*quality > df::item_quality::Artifact) {
|
||||||
|
DEBUG(status).print("clamping quality to Artifact");
|
||||||
|
*quality = df::item_quality::Artifact;
|
||||||
|
}
|
||||||
|
if (*quality < df::item_quality::Ordinary) {
|
||||||
|
DEBUG(status).print("clamping quality to Ordinary");
|
||||||
|
*quality = df::item_quality::Ordinary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemFilter::setMinQuality(int quality) {
|
||||||
|
min_quality = static_cast<df::item_quality>(quality);
|
||||||
|
clampItemQuality(&min_quality);
|
||||||
|
if (max_quality < min_quality)
|
||||||
|
max_quality = min_quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemFilter::setMaxQuality(int quality) {
|
||||||
|
max_quality = static_cast<df::item_quality>(quality);
|
||||||
|
clampItemQuality(&max_quality);
|
||||||
|
if (max_quality < min_quality)
|
||||||
|
min_quality = max_quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemFilter::setDecoratedOnly(bool decorated) {
|
||||||
|
decorated_only = decorated;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemFilter::setMaterialMask(uint32_t mask) {
|
||||||
|
mat_mask.whole = mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemFilter::setMaterials(const vector<DFHack::MaterialInfo> &materials) {
|
||||||
|
this->materials = materials;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool matchesMask(DFHack::MaterialInfo &mat, df::dfhack_material_category mat_mask) {
|
||||||
|
return mat_mask.whole ? mat.matches(mat_mask) : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ItemFilter::matches(df::dfhack_material_category mask) const {
|
||||||
|
return mask.whole & mat_mask.whole;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ItemFilter::matches(DFHack::MaterialInfo &material) const {
|
||||||
|
for (auto it = materials.begin(); it != materials.end(); ++it)
|
||||||
|
if (material.matches(*it))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ItemFilter::matches(df::item *item) const {
|
||||||
|
if (item->getQuality() < min_quality || item->getQuality() > max_quality)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (decorated_only && !item->hasImprovements())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto imattype = item->getActualMaterial();
|
||||||
|
auto imatindex = item->getActualMaterialIndex();
|
||||||
|
auto item_mat = DFHack::MaterialInfo(imattype, imatindex);
|
||||||
|
|
||||||
|
return (materials.size() == 0) ? matchesMask(item_mat, mat_mask) : matches(item_mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<ItemFilter> deserialize_item_filters(color_ostream &out, const string &serialized) {
|
||||||
|
std::vector<ItemFilter> filters;
|
||||||
|
|
||||||
|
vector<string> filter_strs;
|
||||||
|
split_string(&filter_strs, serialized, ";");
|
||||||
|
for (auto &str : filter_strs) {
|
||||||
|
filters.emplace_back(out, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
string serialize_item_filters(const vector<ItemFilter> &filters) {
|
||||||
|
vector<string> strs;
|
||||||
|
for (auto &filter : filters) {
|
||||||
|
strs.emplace_back(filter.serialize());
|
||||||
|
}
|
||||||
|
return join_strings(";", strs);
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "modules/Materials.h"
|
||||||
|
|
||||||
|
#include "df/dfhack_material_category.h"
|
||||||
|
#include "df/item_quality.h"
|
||||||
|
|
||||||
|
class ItemFilter {
|
||||||
|
public:
|
||||||
|
ItemFilter();
|
||||||
|
ItemFilter(DFHack::color_ostream &out, std::string serialized);
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
bool isEmpty() const;
|
||||||
|
std::string serialize() const;
|
||||||
|
|
||||||
|
void setMinQuality(int quality);
|
||||||
|
void setMaxQuality(int quality);
|
||||||
|
void setDecoratedOnly(bool decorated);
|
||||||
|
void setMaterialMask(uint32_t mask);
|
||||||
|
void setMaterials(const std::vector<DFHack::MaterialInfo> &materials);
|
||||||
|
|
||||||
|
df::item_quality getMinQuality() const { return min_quality; }
|
||||||
|
df::item_quality getMaxQuality() const {return max_quality; }
|
||||||
|
bool getDecoratedOnly() const { return decorated_only; }
|
||||||
|
df::dfhack_material_category getMaterialMask() const { return mat_mask; }
|
||||||
|
std::vector<DFHack::MaterialInfo> getMaterials() const { return materials; }
|
||||||
|
|
||||||
|
bool matches(df::dfhack_material_category mask) const;
|
||||||
|
bool matches(DFHack::MaterialInfo &material) const;
|
||||||
|
bool matches(df::item *item) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
df::item_quality min_quality;
|
||||||
|
df::item_quality max_quality;
|
||||||
|
bool decorated_only;
|
||||||
|
df::dfhack_material_category mat_mask;
|
||||||
|
std::vector<DFHack::MaterialInfo> materials;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<ItemFilter> deserialize_item_filters(DFHack::color_ostream &out, const std::string &serialized);
|
||||||
|
std::string serialize_item_filters(const std::vector<ItemFilter> &filters);
|
@ -0,0 +1,110 @@
|
|||||||
|
#include "plannedbuilding.h"
|
||||||
|
#include "buildingplan.h"
|
||||||
|
|
||||||
|
#include "Debug.h"
|
||||||
|
#include "MiscUtils.h"
|
||||||
|
|
||||||
|
#include "modules/World.h"
|
||||||
|
|
||||||
|
#include "df/job.h"
|
||||||
|
|
||||||
|
namespace DFHack {
|
||||||
|
DBG_EXTERN(buildingplan, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
using std::vector;
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
static vector<vector<df::job_item_vector_id>> get_vector_ids(color_ostream &out, int bld_id) {
|
||||||
|
vector<vector<df::job_item_vector_id>> 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<vector<df::job_item_vector_id>> deserialize_vector_ids(color_ostream &out, PersistentDataItem &bld_config) {
|
||||||
|
vector<vector<df::job_item_vector_id>> ret;
|
||||||
|
|
||||||
|
vector<string> rawstrs;
|
||||||
|
split_string(&rawstrs, bld_config.val(), "|");
|
||||||
|
const string &serialized = rawstrs[0];
|
||||||
|
|
||||||
|
DEBUG(status,out).print("deserializing vector ids for building %d: %s\n",
|
||||||
|
get_config_val(bld_config, BLD_CONFIG_ID), serialized.c_str());
|
||||||
|
|
||||||
|
vector<string> joined;
|
||||||
|
split_string(&joined, serialized, ";");
|
||||||
|
for (auto &str : joined) {
|
||||||
|
vector<string> lst;
|
||||||
|
split_string(&lst, str, ",");
|
||||||
|
vector<df::job_item_vector_id> 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 std::vector<ItemFilter> get_item_filters(color_ostream &out, PersistentDataItem &bld_config) {
|
||||||
|
std::vector<ItemFilter> ret;
|
||||||
|
|
||||||
|
vector<string> rawstrs;
|
||||||
|
split_string(&rawstrs, bld_config.val(), "|");
|
||||||
|
if (rawstrs.size() < 2)
|
||||||
|
return ret;
|
||||||
|
return deserialize_item_filters(out, rawstrs[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static string serialize(const vector<vector<df::job_item_vector_id>> &vector_ids, const vector<ItemFilter> &item_filters) {
|
||||||
|
vector<string> joined;
|
||||||
|
for (auto &vec_list : vector_ids) {
|
||||||
|
joined.emplace_back(join_strings(",", vec_list));
|
||||||
|
}
|
||||||
|
std::ostringstream out;
|
||||||
|
out << join_strings(";", joined) << "|" << serialize_item_filters(item_filters);
|
||||||
|
return out.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
PlannedBuilding::PlannedBuilding(color_ostream &out, df::building *bld, HeatSafety heat, const vector<ItemFilter> &item_filters)
|
||||||
|
: id(bld->id), vector_ids(get_vector_ids(out, id)), heat_safety(heat),
|
||||||
|
item_filters(item_filters) {
|
||||||
|
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);
|
||||||
|
set_config_val(bld_config, BLD_CONFIG_HEAT, heat_safety);
|
||||||
|
bld_config.val() = serialize(vector_ids, item_filters);
|
||||||
|
DEBUG(status,out).print("serialized state for building %d: %s\n", id, bld_config.val().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
PlannedBuilding::PlannedBuilding(color_ostream &out, PersistentDataItem &bld_config)
|
||||||
|
: id(get_config_val(bld_config, BLD_CONFIG_ID)),
|
||||||
|
vector_ids(deserialize_vector_ids(out, bld_config)),
|
||||||
|
heat_safety((HeatSafety)get_config_val(bld_config, BLD_CONFIG_HEAT)),
|
||||||
|
item_filters(get_item_filters(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
|
||||||
|
// disabled, manually removing the building, modifying it via the API, etc.
|
||||||
|
df::building * PlannedBuilding::getBuildingIfValidOrRemoveIfNot(color_ostream &out) {
|
||||||
|
auto bld = df::building::find(id);
|
||||||
|
bool valid = bld && bld->getBuildStage() == 0;
|
||||||
|
if (!valid) {
|
||||||
|
remove(out);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return bld;
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "buildingplan.h"
|
||||||
|
#include "itemfilter.h"
|
||||||
|
|
||||||
|
#include "Core.h"
|
||||||
|
|
||||||
|
#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<std::vector<df::job_item_vector_id>> vector_ids;
|
||||||
|
|
||||||
|
const HeatSafety heat_safety;
|
||||||
|
|
||||||
|
const std::vector<ItemFilter> item_filters;
|
||||||
|
|
||||||
|
PlannedBuilding(DFHack::color_ostream &out, df::building *bld, HeatSafety heat, const std::vector<ItemFilter> &item_filters);
|
||||||
|
PlannedBuilding(DFHack::color_ostream &out, DFHack::PersistentDataItem &bld_config);
|
||||||
|
|
||||||
|
void remove(DFHack::color_ostream &out);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// disabled, manually removing the building, modifying it via the API, etc.
|
||||||
|
df::building * getBuildingIfValidOrRemoveIfNot(DFHack::color_ostream &out);
|
||||||
|
|
||||||
|
private:
|
||||||
|
DFHack::PersistentDataItem bld_config;
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
|||||||
Subproject commit 6570fe01081f7e402495bc5339b4ff7a1aabf305
|
Subproject commit 3e494d9d968add443ebd63cc167933cc813f0eee
|
@ -1 +1 @@
|
|||||||
Subproject commit f2c2f6aa7e7fe94871adf0a22d6966ddcac38afc
|
Subproject commit 81183a380b11f4c3045a7888c35afe215d2185ad
|
Loading…
Reference in New Issue