1494 lines
43 KiB
C++
1494 lines
43 KiB
C++
#include "uicommon.h"
|
|
#include "listcolumn.h"
|
|
|
|
#include <functional>
|
|
|
|
// DF data structure definition headers
|
|
#include "DataDefs.h"
|
|
#include "Types.h"
|
|
|
|
#include "df/item.h"
|
|
#include "df/viewscreen_dwarfmodest.h"
|
|
#include "df/viewscreen_storesst.h"
|
|
#include "df/items_other_id.h"
|
|
#include "df/job.h"
|
|
#include "df/unit.h"
|
|
#include "df/world.h"
|
|
#include "df/item_quality.h"
|
|
#include "df/caravan_state.h"
|
|
#include "df/mandate.h"
|
|
#include "df/general_ref_building_holderst.h"
|
|
|
|
#include "modules/Gui.h"
|
|
#include "modules/Items.h"
|
|
#include "modules/Job.h"
|
|
#include "modules/World.h"
|
|
#include "modules/Screen.h"
|
|
#include "modules/Maps.h"
|
|
#include "modules/Units.h"
|
|
#include "df/building_cagest.h"
|
|
#include "df/ui_advmode.h"
|
|
|
|
DFHACK_PLUGIN("stocks");
|
|
#define PLUGIN_VERSION 0.13
|
|
|
|
REQUIRE_GLOBAL(world);
|
|
|
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
|
{
|
|
return CR_OK;
|
|
}
|
|
|
|
#define MAX_NAME 30
|
|
#define SIDEBAR_WIDTH 30
|
|
|
|
|
|
/*
|
|
* Utility
|
|
*/
|
|
|
|
static string get_quality_name(const df::item_quality quality)
|
|
{
|
|
if (gps->dimx - SIDEBAR_WIDTH < 60)
|
|
return int_to_string(quality);
|
|
else
|
|
return ENUM_KEY_STR(item_quality, quality);
|
|
}
|
|
|
|
static df::item *get_container_of(df::item *item)
|
|
{
|
|
auto container = Items::getContainer(item);
|
|
return (container) ? container : item;
|
|
}
|
|
|
|
static df::item *get_container_of(df::unit *unit)
|
|
{
|
|
auto ref = Units::getGeneralRef(unit, general_ref_type::CONTAINED_IN_ITEM);
|
|
return (ref) ? ref->getItem() : nullptr;
|
|
}
|
|
|
|
|
|
/*
|
|
* Trade Info
|
|
*/
|
|
|
|
class TradeDepotInfo
|
|
{
|
|
public:
|
|
TradeDepotInfo()
|
|
{
|
|
reset();
|
|
}
|
|
|
|
void prepareTradeVariables()
|
|
{
|
|
reset();
|
|
for(auto bld_it = world->buildings.all.begin(); bld_it != world->buildings.all.end(); bld_it++)
|
|
{
|
|
auto bld = *bld_it;
|
|
if (!isUsableDepot(bld))
|
|
continue;
|
|
|
|
depot = bld;
|
|
id = depot->id;
|
|
trade_possible = can_trade();
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool assignItem(vector<df::item *> &entries)
|
|
{
|
|
for (auto it = entries.begin(); it != entries.end(); it++)
|
|
{
|
|
auto item = *it;
|
|
item = get_container_of(item);
|
|
if (!Items::canTradeWithContents(item))
|
|
return false;
|
|
|
|
auto href = df::allocate<df::general_ref_building_holderst>();
|
|
if (!href)
|
|
return false;
|
|
|
|
auto job = new df::job();
|
|
|
|
df::coord tpos(depot->centerx, depot->centery, depot->z);
|
|
job->pos = tpos;
|
|
|
|
job->job_type = job_type::BringItemToDepot;
|
|
|
|
// job <-> item link
|
|
if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled))
|
|
{
|
|
delete job;
|
|
delete href;
|
|
return false;
|
|
}
|
|
|
|
// job <-> building link
|
|
href->building_id = id;
|
|
depot->jobs.push_back(job);
|
|
job->general_refs.push_back(href);
|
|
|
|
// add to job list
|
|
Job::linkIntoWorld(job);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
depot = 0;
|
|
trade_possible = false;
|
|
}
|
|
|
|
bool canTrade()
|
|
{
|
|
return trade_possible;
|
|
}
|
|
|
|
private:
|
|
int32_t id;
|
|
df::building *depot;
|
|
bool trade_possible;
|
|
|
|
bool isUsableDepot(df::building* bld)
|
|
{
|
|
if (bld->getType() != building_type::TradeDepot)
|
|
return false;
|
|
|
|
if (bld->getBuildStage() < bld->getMaxBuildStage())
|
|
return false;
|
|
|
|
if (bld->jobs.size() == 1 && bld->jobs[0]->job_type == job_type::DestroyBuilding)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
static TradeDepotInfo depot_info;
|
|
|
|
|
|
/*
|
|
* Item manipulation
|
|
*/
|
|
|
|
static map<df::item *, bool> items_in_cages;
|
|
|
|
static df::job *get_item_job(df::item *item)
|
|
{
|
|
auto ref = Items::getSpecificRef(item, specific_ref_type::JOB);
|
|
if (ref && ref->data.job)
|
|
return ref->data.job;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static bool is_marked_for_trade(df::item *item, df::item *container = nullptr)
|
|
{
|
|
item = (container) ? container : get_container_of(item);
|
|
auto job = get_item_job(item);
|
|
if (!job)
|
|
return false;
|
|
|
|
return job->job_type == job_type::BringItemToDepot;
|
|
}
|
|
|
|
static bool is_in_inventory(df::item *item)
|
|
{
|
|
item = get_container_of(item);
|
|
return item->flags.bits.in_inventory;
|
|
}
|
|
|
|
static bool is_item_in_cage_cache(df::item *item)
|
|
{
|
|
return items_in_cages.find(item) != items_in_cages.end();
|
|
}
|
|
|
|
static string get_item_label(df::item *item, bool truncate, bool trim)
|
|
{
|
|
auto label = Items::getBookTitle(item);
|
|
if (label == "")
|
|
{
|
|
label = Items::getDescription(item, 0, false);
|
|
}
|
|
if (trim && item->getType() == item_type::BIN)
|
|
{
|
|
auto pos = label.find("<#");
|
|
if (pos != string::npos)
|
|
{
|
|
label = label.substr(0, pos-1);
|
|
}
|
|
}
|
|
|
|
auto wear = item->getWear();
|
|
if (wear > 0)
|
|
{
|
|
string wearX;
|
|
switch (wear)
|
|
{
|
|
case 1:
|
|
wearX = "x";
|
|
break;
|
|
|
|
case 2:
|
|
wearX = "X";
|
|
break;
|
|
|
|
case 3:
|
|
wearX = "xX";
|
|
break;
|
|
|
|
default:
|
|
wearX = "XX";
|
|
break;
|
|
|
|
}
|
|
|
|
label = wearX + label + wearX;
|
|
}
|
|
|
|
label = pad_string(label, MAX_NAME, false, truncate);
|
|
|
|
return label;
|
|
}
|
|
|
|
static string get_keywords(df::item *item)
|
|
{
|
|
string keywords;
|
|
|
|
if (item->flags.bits.in_job)
|
|
keywords += "job ";
|
|
|
|
if (item->flags.bits.rotten)
|
|
keywords += "rotten ";
|
|
|
|
if (item->flags.bits.owned)
|
|
keywords += "owned ";
|
|
|
|
if (item->flags.bits.forbid)
|
|
keywords += "forbid ";
|
|
|
|
if (item->flags.bits.dump)
|
|
keywords += "dump ";
|
|
|
|
if (item->flags.bits.on_fire)
|
|
keywords += "fire ";
|
|
|
|
if (item->flags.bits.melt)
|
|
keywords += "melt ";
|
|
|
|
if (is_item_in_cage_cache(item))
|
|
keywords += "caged ";
|
|
|
|
if (is_in_inventory(item))
|
|
keywords += "inventory ";
|
|
|
|
if (depot_info.canTrade())
|
|
{
|
|
if (is_marked_for_trade(item))
|
|
keywords += "trade ";
|
|
}
|
|
|
|
return keywords + get_item_label(item, false, false);
|
|
}
|
|
|
|
struct item_grouped_entry
|
|
{
|
|
std::vector<df::item *> entries;
|
|
|
|
string getLabel(bool grouped) const
|
|
{
|
|
if (entries.size() == 0)
|
|
return "";
|
|
|
|
return get_item_label(entries[0], true, grouped);
|
|
}
|
|
|
|
string getKeywords() const
|
|
{
|
|
return get_keywords(entries[0]);
|
|
}
|
|
|
|
df::item *getFirstItem() const
|
|
{
|
|
if (entries.size() == 0)
|
|
return nullptr;
|
|
|
|
return entries[0];
|
|
}
|
|
|
|
bool canMelt() const
|
|
{
|
|
df::item *item = getFirstItem();
|
|
if (!item)
|
|
return false;
|
|
|
|
return can_melt(item);
|
|
}
|
|
|
|
bool isSetToMelt() const
|
|
{
|
|
df::item *item = getFirstItem();
|
|
if (!item)
|
|
return false;
|
|
|
|
return is_set_to_melt(item);
|
|
}
|
|
|
|
bool contains(df::item *item) const
|
|
{
|
|
return std::find(entries.begin(), entries.end(), item) != entries.end();
|
|
}
|
|
|
|
void setFlags(const df::item_flags flags, const bool state)
|
|
{
|
|
for (auto it = entries.begin(); it != entries.end(); it++)
|
|
{
|
|
if (state)
|
|
(*it)->flags.whole |= flags.whole;
|
|
else
|
|
(*it)->flags.whole &= ~flags.whole;
|
|
}
|
|
}
|
|
|
|
bool isSingleItem()
|
|
{
|
|
return entries.size() == 1;
|
|
}
|
|
};
|
|
|
|
|
|
struct extra_filters
|
|
{
|
|
bool hide_trade_marked, hide_in_inventory, hide_in_cages;
|
|
|
|
extra_filters()
|
|
{
|
|
reset();
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
hide_in_inventory = false;
|
|
hide_trade_marked = false;
|
|
}
|
|
};
|
|
|
|
static bool cages_populated = false;
|
|
static vector<df::building_cagest *> cages;
|
|
|
|
static void find_cages()
|
|
{
|
|
if (cages_populated)
|
|
return;
|
|
|
|
for (size_t b=0; b < world->buildings.all.size(); b++)
|
|
{
|
|
df::building* building = world->buildings.all[b];
|
|
if (building->getType() == building_type::Cage)
|
|
{
|
|
cages.push_back(static_cast<df::building_cagest *>(building));
|
|
}
|
|
}
|
|
|
|
cages_populated = true;
|
|
}
|
|
|
|
static df::building_cagest *is_in_cage(df::unit *unit)
|
|
{
|
|
find_cages();
|
|
for (auto it = cages.begin(); it != cages.end(); it++)
|
|
{
|
|
auto cage = *it;
|
|
for (size_t c = 0; c < cage->assigned_units.size(); c++)
|
|
{
|
|
if(cage->assigned_units[c] == unit->id)
|
|
return cage;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
template <class T>
|
|
class StockListColumn : public ListColumn<T>
|
|
{
|
|
virtual void display_extras(const T &item_group, int32_t &x, int32_t &y) const
|
|
{
|
|
auto item = item_group->getFirstItem();
|
|
if (item->flags.bits.in_job)
|
|
OutputString(COLOR_LIGHTBLUE, x, y, "J");
|
|
else
|
|
OutputString(COLOR_LIGHTBLUE, x, y, " ");
|
|
|
|
if (item->flags.bits.rotten)
|
|
OutputString(COLOR_CYAN, x, y, "X");
|
|
else
|
|
OutputString(COLOR_LIGHTBLUE, x, y, " ");
|
|
|
|
if (item->flags.bits.owned)
|
|
OutputString(COLOR_GREEN, x, y, "O");
|
|
else
|
|
OutputString(COLOR_LIGHTBLUE, x, y, " ");
|
|
|
|
if (item->flags.bits.forbid)
|
|
OutputString(COLOR_RED, x, y, "F");
|
|
else
|
|
OutputString(COLOR_LIGHTBLUE, x, y, " ");
|
|
|
|
if (item->flags.bits.dump)
|
|
OutputString(COLOR_LIGHTMAGENTA, x, y, "D");
|
|
else
|
|
OutputString(COLOR_LIGHTBLUE, x, y, " ");
|
|
|
|
if (item->flags.bits.on_fire)
|
|
OutputString(COLOR_LIGHTRED, x, y, "R");
|
|
else
|
|
OutputString(COLOR_LIGHTBLUE, x, y, " ");
|
|
|
|
if (item->flags.bits.melt)
|
|
OutputString(COLOR_BLUE, x, y, "M");
|
|
else
|
|
OutputString(COLOR_LIGHTBLUE, x, y, " ");
|
|
|
|
if (is_in_inventory(item))
|
|
OutputString(COLOR_WHITE, x, y, "I");
|
|
else
|
|
OutputString(COLOR_LIGHTBLUE, x, y, " ");
|
|
|
|
if (is_item_in_cage_cache(item))
|
|
OutputString(COLOR_LIGHTRED, x, y, "C");
|
|
else
|
|
OutputString(COLOR_LIGHTBLUE, x, y, " ");
|
|
|
|
if (depot_info.canTrade())
|
|
{
|
|
if (is_marked_for_trade(item))
|
|
OutputString(COLOR_LIGHTGREEN, x, y, "T");
|
|
else
|
|
OutputString(COLOR_LIGHTBLUE, x, y, " ");
|
|
}
|
|
|
|
if (item->isImproved())
|
|
OutputString(COLOR_BLUE, x, y, "* ");
|
|
else
|
|
OutputString(COLOR_LIGHTBLUE, x, y, " ");
|
|
|
|
auto quality = static_cast<df::item_quality>(item->getQuality());
|
|
if (quality > item_quality::Ordinary)
|
|
{
|
|
auto color = COLOR_BROWN;
|
|
switch(quality)
|
|
{
|
|
case item_quality::FinelyCrafted:
|
|
color = COLOR_CYAN;
|
|
break;
|
|
|
|
case item_quality::Superior:
|
|
color = COLOR_LIGHTBLUE;
|
|
break;
|
|
|
|
case item_quality::Exceptional:
|
|
color = COLOR_GREEN;
|
|
break;
|
|
|
|
case item_quality::Masterful:
|
|
color = COLOR_LIGHTGREEN;
|
|
break;
|
|
|
|
case item_quality::Artifact:
|
|
color = COLOR_BLUE;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
OutputString(color, x, y, get_quality_name(quality));
|
|
}
|
|
}
|
|
|
|
virtual bool validSearchInput (unsigned char c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '(':
|
|
case ')':
|
|
return true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
string &search_string = ListColumn<T>::search_string;
|
|
if (c == '^' && !search_string.size())
|
|
return true;
|
|
else if (c == '$' && search_string.size())
|
|
{
|
|
if (search_string == "^")
|
|
return false;
|
|
if (search_string[search_string.size() - 1] != '$')
|
|
return true;
|
|
}
|
|
return ListColumn<T>::validSearchInput(c);
|
|
}
|
|
|
|
std::string getRawSearch(const std::string s)
|
|
{
|
|
string raw_search = s;
|
|
if (raw_search.size() && raw_search[0] == '^')
|
|
raw_search.erase(0, 1);
|
|
if (raw_search.size() && raw_search[raw_search.size() - 1] == '$')
|
|
raw_search.erase(raw_search.size() - 1, 1);
|
|
return toLower(raw_search);
|
|
}
|
|
|
|
virtual void tokenizeSearch (vector<string> *dest, const string search)
|
|
{
|
|
string raw_search = getRawSearch(search);
|
|
ListColumn<T>::tokenizeSearch(dest, raw_search);
|
|
}
|
|
|
|
virtual bool showEntry (const ListEntry<T> *entry, const vector<string> &search_tokens)
|
|
{
|
|
string &search_string = ListColumn<T>::search_string;
|
|
if (!search_string.size())
|
|
return true;
|
|
|
|
bool match_start = false, match_end = false;
|
|
string raw_search = getRawSearch(search_string);
|
|
if (search_string.size() && search_string[0] == '^')
|
|
match_start = true;
|
|
if (search_string.size() && search_string[search_string.size() - 1] == '$')
|
|
match_end = true;
|
|
|
|
if (!ListColumn<T>::showEntry(entry, search_tokens))
|
|
return false;
|
|
|
|
string item_name = toLower(Items::getBookTitle(entry->elem->entries[0]));
|
|
if (item_name == "")
|
|
{
|
|
item_name = toLower(Items::getDescription(entry->elem->entries[0], 0, false));
|
|
}
|
|
|
|
if ((match_start || match_end) && raw_search.size() > item_name.size())
|
|
return false;
|
|
if (match_start && item_name.compare(0, raw_search.size(), raw_search) != 0)
|
|
return false;
|
|
if (match_end && item_name.compare(item_name.size() - raw_search.size(), raw_search.size(), raw_search) != 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class search_help : public dfhack_viewscreen
|
|
{
|
|
public:
|
|
void feed (std::set<df::interface_key> *input)
|
|
{
|
|
if (input->count(interface_key::HELP))
|
|
return;
|
|
if (Screen::isDismissed(this))
|
|
return;
|
|
Screen::dismiss(this);
|
|
if (!input->count(interface_key::LEAVESCREEN) && !input->count(interface_key::SELECT))
|
|
parent->feed(input);
|
|
}
|
|
void render()
|
|
{
|
|
static std::string text =
|
|
"\7 Flag names can be\n"
|
|
" searched for - e.g. job,\n"
|
|
" inventory, dump, forbid\n"
|
|
"\n"
|
|
"\7 Use ^ to match the start\n"
|
|
" of a name, and/or $ to\n"
|
|
" match the end of a name";
|
|
if (Screen::isDismissed(this))
|
|
return;
|
|
parent->render();
|
|
int left_margin = gps->dimx - SIDEBAR_WIDTH;
|
|
int x = left_margin, y = 2;
|
|
Screen::fillRect(Screen::Pen(' ', 0, 0), left_margin - 1, 1, gps->dimx - 2, gps->dimy - 4);
|
|
Screen::fillRect(Screen::Pen(' ', 0, 0), left_margin - 1, 1, left_margin - 1, gps->dimy - 2);
|
|
OutputString(COLOR_WHITE, x, y, "Search help", true, left_margin);
|
|
++y;
|
|
vector<string> lines;
|
|
split_string(&lines, text, "\n");
|
|
for (auto line = lines.begin(); line != lines.end(); ++line)
|
|
OutputString(COLOR_WHITE, x, y, line->c_str(), true, left_margin);
|
|
}
|
|
std::string getFocusString() { return "stocks_view/search_help"; }
|
|
};
|
|
|
|
class ViewscreenStocks : public dfhack_viewscreen
|
|
{
|
|
public:
|
|
static df::item_flags hide_flags;
|
|
static extra_filters extra_hide_flags;
|
|
|
|
ViewscreenStocks(df::building_stockpilest *sp = NULL) : sp(sp)
|
|
{
|
|
is_grouped = true;
|
|
selected_column = 0;
|
|
items_column.multiselect = false;
|
|
items_column.auto_select = true;
|
|
items_column.allow_search = true;
|
|
items_column.left_margin = 2;
|
|
items_column.bottom_margin = 1;
|
|
items_column.search_margin = gps->dimx - SIDEBAR_WIDTH;
|
|
|
|
items_column.changeHighlight(0);
|
|
|
|
apply_to_all = false;
|
|
hide_unflagged = false;
|
|
|
|
checked_flags.bits.in_job = true;
|
|
checked_flags.bits.rotten = true;
|
|
checked_flags.bits.owned = true;
|
|
checked_flags.bits.forbid = true;
|
|
checked_flags.bits.dump = true;
|
|
checked_flags.bits.on_fire = true;
|
|
checked_flags.bits.melt = true;
|
|
checked_flags.bits.on_fire = true;
|
|
|
|
min_quality = item_quality::Ordinary;
|
|
max_quality = item_quality::Artifact;
|
|
min_wear = 0;
|
|
cages.clear();
|
|
items_in_cages.clear();
|
|
cages_populated = false;
|
|
|
|
last_selected_item = nullptr;
|
|
|
|
populateItems();
|
|
|
|
items_column.selectDefaultEntry();
|
|
}
|
|
|
|
static void reset()
|
|
{
|
|
hide_flags.whole = 0;
|
|
extra_hide_flags.reset();
|
|
depot_info.reset();
|
|
}
|
|
|
|
void feed(set<df::interface_key> *input)
|
|
{
|
|
if (input->count(interface_key::LEAVESCREEN))
|
|
{
|
|
input->clear();
|
|
Screen::dismiss(this);
|
|
return;
|
|
}
|
|
else if (input->count(interface_key::HELP))
|
|
{
|
|
Screen::show(dts::make_unique<search_help>(), plugin_self);
|
|
}
|
|
|
|
bool key_processed = false;
|
|
switch (selected_column)
|
|
{
|
|
case 0:
|
|
key_processed = items_column.feed(input);
|
|
break;
|
|
}
|
|
|
|
if (key_processed)
|
|
return;
|
|
|
|
if (input->count(interface_key::CUSTOM_CTRL_J))
|
|
{
|
|
hide_flags.bits.in_job = !hide_flags.bits.in_job;
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_CTRL_X))
|
|
{
|
|
hide_flags.bits.rotten = !hide_flags.bits.rotten;
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_CTRL_O))
|
|
{
|
|
hide_flags.bits.owned = !hide_flags.bits.owned;
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_CTRL_F))
|
|
{
|
|
hide_flags.bits.forbid = !hide_flags.bits.forbid;
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_CTRL_D))
|
|
{
|
|
hide_flags.bits.dump = !hide_flags.bits.dump;
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_CTRL_E))
|
|
{
|
|
hide_flags.bits.on_fire = !hide_flags.bits.on_fire;
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_CTRL_M))
|
|
{
|
|
hide_flags.bits.melt = !hide_flags.bits.melt;
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_CTRL_I))
|
|
{
|
|
extra_hide_flags.hide_in_inventory = !extra_hide_flags.hide_in_inventory;
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_CTRL_C))
|
|
{
|
|
extra_hide_flags.hide_in_cages = !extra_hide_flags.hide_in_cages;
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_CTRL_T))
|
|
{
|
|
extra_hide_flags.hide_trade_marked = !extra_hide_flags.hide_trade_marked;
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_CTRL_N))
|
|
{
|
|
hide_unflagged = !hide_unflagged;
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_C))
|
|
{
|
|
setAllFlags(true);
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_E))
|
|
{
|
|
setAllFlags(false);
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CHANGETAB))
|
|
{
|
|
is_grouped = !is_grouped;
|
|
populateItems();
|
|
items_column.centerSelection();
|
|
}
|
|
else if (input->count(interface_key::SECONDSCROLL_UP))
|
|
{
|
|
if (min_quality > item_quality::Ordinary)
|
|
{
|
|
min_quality = static_cast<df::item_quality>(static_cast<int16_t>(min_quality) - 1);
|
|
populateItems();
|
|
}
|
|
}
|
|
else if (input->count(interface_key::SECONDSCROLL_DOWN))
|
|
{
|
|
if (min_quality < max_quality && min_quality < item_quality::Artifact)
|
|
{
|
|
min_quality = static_cast<df::item_quality>(static_cast<int16_t>(min_quality) + 1);
|
|
populateItems();
|
|
}
|
|
}
|
|
else if (input->count(interface_key::SECONDSCROLL_PAGEUP))
|
|
{
|
|
if (max_quality > min_quality && max_quality > item_quality::Ordinary)
|
|
{
|
|
max_quality = static_cast<df::item_quality>(static_cast<int16_t>(max_quality) - 1);
|
|
populateItems();
|
|
}
|
|
}
|
|
else if (input->count(interface_key::SECONDSCROLL_PAGEDOWN))
|
|
{
|
|
if (max_quality < item_quality::Artifact)
|
|
{
|
|
max_quality = static_cast<df::item_quality>(static_cast<int16_t>(max_quality) + 1);
|
|
populateItems();
|
|
}
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_W))
|
|
{
|
|
++min_wear;
|
|
if (min_wear > 3)
|
|
min_wear = 0;
|
|
|
|
populateItems();
|
|
}
|
|
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_Z))
|
|
{
|
|
input->clear();
|
|
auto item_group = items_column.getFirstSelectedElem();
|
|
if (!item_group)
|
|
return;
|
|
|
|
if (is_grouped && !item_group->isSingleItem())
|
|
return;
|
|
|
|
auto item = item_group->getFirstItem();
|
|
auto pos = getRealPos(item);
|
|
if (!isRealPos(pos))
|
|
return;
|
|
|
|
Screen::dismiss(this);
|
|
auto vs = Gui::getCurViewscreen(true);
|
|
while (vs && !virtual_cast<df::viewscreen_dwarfmodest>(vs))
|
|
{
|
|
Screen::dismiss(vs);
|
|
vs = vs->parent;
|
|
}
|
|
// Could be clever here, if item is in a container, to look inside the container.
|
|
// But that's different for built containers vs bags/pots in stockpiles.
|
|
send_key(interface_key::D_LOOK);
|
|
move_cursor(pos);
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_A))
|
|
{
|
|
apply_to_all = !apply_to_all;
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_D))
|
|
{
|
|
df::item_flags flags;
|
|
flags.bits.dump = true;
|
|
toggleFlag(flags);
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_F))
|
|
{
|
|
df::item_flags flags;
|
|
flags.bits.forbid = true;
|
|
toggleFlag(flags);
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_M))
|
|
{
|
|
toggleMelt();
|
|
populateItems();
|
|
}
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_T))
|
|
{
|
|
if (depot_info.canTrade())
|
|
{
|
|
auto selected = getSelectedItems();
|
|
for (auto it = selected.begin(); it != selected.end(); it++)
|
|
{
|
|
depot_info.assignItem((*it)->entries);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (input->count(interface_key::CURSOR_LEFT))
|
|
{
|
|
--selected_column;
|
|
validateColumn();
|
|
}
|
|
else if (input->count(interface_key::CURSOR_RIGHT))
|
|
{
|
|
selected_column++;
|
|
validateColumn();
|
|
}
|
|
else if (enabler->tracking_on && enabler->mouse_lbut)
|
|
{
|
|
if (items_column.setHighlightByMouse())
|
|
selected_column = 0;
|
|
|
|
enabler->mouse_lbut = enabler->mouse_rbut = 0;
|
|
}
|
|
}
|
|
|
|
void move_cursor(const df::coord &pos)
|
|
{
|
|
Gui::setCursorCoords(pos.x, pos.y, pos.z);
|
|
Gui::refreshSidebar();
|
|
}
|
|
|
|
void send_key(const df::interface_key &key)
|
|
{
|
|
set< df::interface_key > keys;
|
|
keys.insert(key);
|
|
Gui::getCurViewscreen(true)->feed(&keys);
|
|
}
|
|
|
|
void render()
|
|
{
|
|
if (Screen::isDismissed(this))
|
|
return;
|
|
|
|
dfhack_viewscreen::render();
|
|
|
|
Screen::clear();
|
|
Screen::drawBorder(" Stocks ");
|
|
|
|
items_column.display(selected_column == 0);
|
|
|
|
int32_t y = 1;
|
|
auto left_margin = gps->dimx - SIDEBAR_WIDTH;
|
|
int32_t x = left_margin - 2;
|
|
Screen::Pen border('\xDB', 8);
|
|
for (; y < gps->dimy - 1; y++)
|
|
{
|
|
paintTile(border, x, y);
|
|
}
|
|
|
|
y = 2;
|
|
x = left_margin;
|
|
OutputString(COLOR_BROWN, x, y, "Filters ", false, left_margin);
|
|
OutputString(COLOR_LIGHTRED, x, y, "(Ctrl+Key toggles)", true, left_margin);
|
|
OutputFilterString(x, y, "In Job ", "J", !hide_flags.bits.in_job, false, left_margin, COLOR_LIGHTBLUE);
|
|
OutputFilterString(x, y, "Rotten", "X", !hide_flags.bits.rotten, true, left_margin, COLOR_CYAN);
|
|
OutputFilterString(x, y, "Owned ", "O", !hide_flags.bits.owned, false, left_margin, COLOR_GREEN);
|
|
OutputFilterString(x, y, "Forbidden", "F", !hide_flags.bits.forbid, true, left_margin, COLOR_RED);
|
|
OutputFilterString(x, y, "Dump ", "D", !hide_flags.bits.dump, false, left_margin, COLOR_LIGHTMAGENTA);
|
|
OutputFilterString(x, y, "On Fire", "E", !hide_flags.bits.on_fire, true, left_margin, COLOR_LIGHTRED);
|
|
OutputFilterString(x, y, "Melt ", "M", !hide_flags.bits.melt, false, left_margin, COLOR_BLUE);
|
|
OutputFilterString(x, y, "In Inventory", "I", !extra_hide_flags.hide_in_inventory, true, left_margin, COLOR_WHITE);
|
|
OutputFilterString(x, y, "Caged ", "C", !extra_hide_flags.hide_in_cages, false, left_margin, COLOR_LIGHTRED);
|
|
OutputFilterString(x, y, "Trade", "T", !extra_hide_flags.hide_trade_marked, true, left_margin, COLOR_LIGHTGREEN);
|
|
OutputFilterString(x, y, "No Flags", "N", !hide_unflagged, true, left_margin, COLOR_GREY);
|
|
if (gps->dimy > 26)
|
|
++y;
|
|
OutputHotkeyString(x, y, "Clear All", "Shift-C", true, left_margin);
|
|
OutputHotkeyString(x, y, "Enable All", "Shift-E", true, left_margin);
|
|
OutputHotkeyString(x, y, "Toggle Grouping", interface_key::CHANGETAB, true, left_margin);
|
|
++y;
|
|
|
|
OutputHotkeyString(x, y, "Min Qual: ", "-+");
|
|
OutputString(COLOR_BROWN, x, y, get_quality_name(min_quality), true, left_margin);
|
|
OutputHotkeyString(x, y, "Max Qual: ", "/*");
|
|
OutputString(COLOR_BROWN, x, y, get_quality_name(max_quality), true, left_margin);
|
|
OutputHotkeyString(x, y, "Min Wear: ", "Shift-W");
|
|
OutputString(COLOR_BROWN, x, y, int_to_string(min_wear), true, left_margin);
|
|
|
|
if (gps->dimy > 27)
|
|
++y;
|
|
OutputString(COLOR_BROWN, x, y, "Actions (");
|
|
OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(items_column.getDisplayedListSize()));
|
|
OutputString(COLOR_BROWN, x, y, " Items)", true, left_margin);
|
|
OutputHotkeyString(x, y, "Zoom ", "Shift-Z", false, left_margin);
|
|
OutputHotkeyString(x, y, "Dump", "-D", true, left_margin);
|
|
OutputHotkeyString(x, y, "Forbid ", "Shift-F", false, left_margin);
|
|
OutputHotkeyString(x, y, "Melt", "-M", true, left_margin);
|
|
OutputHotkeyString(x, y, "Mark for Trade", "Shift-T", true, left_margin,
|
|
depot_info.canTrade() ? COLOR_WHITE : COLOR_DARKGREY);
|
|
OutputHotkeyString(x, y, "Apply to: ", "Shift-A");
|
|
OutputString(COLOR_BROWN, x, y, (apply_to_all) ? "Listed" : "Selected", true, left_margin);
|
|
|
|
y = gps->dimy - 4;
|
|
OutputHotkeyString(x, y, "Search help", interface_key::HELP, true, left_margin);
|
|
}
|
|
|
|
std::string getFocusString() { return "stocks_view"; }
|
|
|
|
df::item *getSelectedItem() override
|
|
{
|
|
if (is_grouped)
|
|
return nullptr;
|
|
vector<item_grouped_entry*> items = getSelectedItems();
|
|
if (items.size() != 1)
|
|
return nullptr;
|
|
if (items[0]->entries.size() != 1)
|
|
return nullptr;
|
|
return items[0]->entries[0];
|
|
}
|
|
|
|
private:
|
|
StockListColumn<item_grouped_entry *> items_column;
|
|
int selected_column;
|
|
bool apply_to_all, hide_unflagged;
|
|
df::item_flags checked_flags;
|
|
df::item_quality min_quality, max_quality;
|
|
int16_t min_wear;
|
|
bool is_grouped;
|
|
std::list<item_grouped_entry> grouped_items_store;
|
|
df::item *last_selected_item;
|
|
string last_selected_hash;
|
|
int last_display_offset;
|
|
df::building_stockpilest *sp;
|
|
|
|
static bool isRealPos(const df::coord pos)
|
|
{
|
|
return pos.x != -30000;
|
|
}
|
|
|
|
static df::coord getRealPos(df::item *item)
|
|
{
|
|
df::coord pos;
|
|
pos.x = -30000;
|
|
item = get_container_of(item);
|
|
if (item->flags.bits.in_inventory)
|
|
{
|
|
if (item->flags.bits.in_job)
|
|
{
|
|
auto ref = Items::getSpecificRef(item, specific_ref_type::JOB);
|
|
if (ref && ref->data.job)
|
|
{
|
|
if (ref->data.job->job_type == job_type::Eat || ref->data.job->job_type == job_type::Drink)
|
|
return pos;
|
|
|
|
auto unit = Job::getWorker(ref->data.job);
|
|
if (unit)
|
|
return unit->pos;
|
|
}
|
|
return pos;
|
|
}
|
|
else
|
|
{
|
|
auto unit = Items::getHolderUnit(item);
|
|
if (unit)
|
|
{
|
|
if (!Units::isCitizen(unit))
|
|
{
|
|
auto cage_item = get_container_of(unit);
|
|
if (cage_item)
|
|
{
|
|
items_in_cages[item] = true;
|
|
return cage_item->pos;
|
|
}
|
|
|
|
auto cage_building = is_in_cage(unit);
|
|
if (cage_building)
|
|
{
|
|
items_in_cages[item] = true;
|
|
pos.x = cage_building->centerx;
|
|
pos.y = cage_building->centery;
|
|
pos.z = cage_building->z;
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
return unit->pos;
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
}
|
|
|
|
return item->pos;
|
|
}
|
|
|
|
void toggleMelt()
|
|
{
|
|
int set_to_melt = -1;
|
|
auto selected = getSelectedItems();
|
|
vector<df::item *> items;
|
|
for (auto it = selected.begin(); it != selected.end(); it++)
|
|
{
|
|
auto item_group = *it;
|
|
|
|
if (set_to_melt == -1)
|
|
set_to_melt = (item_group->isSetToMelt()) ? 0 : 1;
|
|
|
|
if (set_to_melt)
|
|
{
|
|
if (!item_group->canMelt() || item_group->isSetToMelt())
|
|
continue;
|
|
}
|
|
else if (!item_group->isSetToMelt())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
items.insert(items.end(), item_group->entries.begin(), item_group->entries.end());
|
|
}
|
|
|
|
auto &melting_items = world->items.other[items_other_id::ANY_MELT_DESIGNATED];
|
|
for (auto it = items.begin(); it != items.end(); it++)
|
|
{
|
|
auto item = *it;
|
|
if (set_to_melt)
|
|
{
|
|
insert_into_vector(melting_items, &df::item::id, item);
|
|
item->flags.bits.melt = true;
|
|
}
|
|
else
|
|
{
|
|
for (auto mit = melting_items.begin(); mit != melting_items.end(); mit++)
|
|
{
|
|
if (item != *mit)
|
|
continue;
|
|
|
|
melting_items.erase(mit);
|
|
item->flags.bits.melt = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void toggleFlag(const df::item_flags flags)
|
|
{
|
|
int state_to_apply = -1;
|
|
auto selected = getSelectedItems();
|
|
for (auto it = selected.begin(); it != selected.end(); it++)
|
|
{
|
|
auto grouped_entry = (*it);
|
|
auto item = grouped_entry->getFirstItem();
|
|
if (state_to_apply == -1)
|
|
state_to_apply = (item->flags.whole & flags.whole) ? 0 : 1;
|
|
|
|
grouped_entry->setFlags(flags, state_to_apply);
|
|
}
|
|
}
|
|
|
|
vector<item_grouped_entry *> getSelectedItems()
|
|
{
|
|
vector<item_grouped_entry *> result;
|
|
if (apply_to_all)
|
|
{
|
|
for (auto it = items_column.getDisplayList().begin(); it != items_column.getDisplayList().end(); it++)
|
|
{
|
|
auto item_group = (*it)->elem;
|
|
if (!item_group)
|
|
continue;
|
|
result.push_back(item_group);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto item_group = items_column.getFirstSelectedElem();
|
|
if (item_group)
|
|
result.push_back(item_group);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void setAllFlags(bool state)
|
|
{
|
|
hide_flags.bits.in_job = state;
|
|
hide_flags.bits.rotten = state;
|
|
hide_flags.bits.owned = state;
|
|
hide_flags.bits.forbid = state;
|
|
hide_flags.bits.dump = state;
|
|
hide_flags.bits.on_fire = state;
|
|
hide_flags.bits.melt = state;
|
|
hide_flags.bits.on_fire = state;
|
|
hide_unflagged = state;
|
|
extra_hide_flags.hide_trade_marked = state;
|
|
extra_hide_flags.hide_in_inventory = state;
|
|
extra_hide_flags.hide_in_cages = state;
|
|
}
|
|
|
|
void populateItems()
|
|
{
|
|
items_column.setTitle((is_grouped) ? "Item (count)" : "Item");
|
|
preserveLastSelected();
|
|
items_column.clear();
|
|
|
|
df::item_flags bad_flags;
|
|
bad_flags.whole = 0;
|
|
bad_flags.bits.hostile = true;
|
|
bad_flags.bits.trader = true;
|
|
bad_flags.bits.in_building = true;
|
|
bad_flags.bits.garbage_collect = true;
|
|
bad_flags.bits.removed = true;
|
|
bad_flags.bits.dead_dwarf = true;
|
|
bad_flags.bits.murder = true;
|
|
bad_flags.bits.construction = true;
|
|
|
|
depot_info.prepareTradeVariables();
|
|
|
|
std::vector<df::item *> &items = world->items.other[items_other_id::IN_PLAY];
|
|
std::map<string, item_grouped_entry *> grouped_items;
|
|
grouped_items_store.clear();
|
|
item_grouped_entry *next_selected_group = nullptr;
|
|
StockpileInfo spInfo;
|
|
if (sp)
|
|
spInfo = StockpileInfo(sp);
|
|
|
|
for (size_t i = 0; i < items.size(); i++)
|
|
{
|
|
df::item *item = items[i];
|
|
|
|
if (item->flags.whole & bad_flags.whole || item->flags.whole & hide_flags.whole)
|
|
continue;
|
|
|
|
auto container = get_container_of(item);
|
|
if (container->flags.whole & bad_flags.whole)
|
|
continue;
|
|
|
|
auto pos = getRealPos(item);
|
|
if (!isRealPos(pos))
|
|
continue;
|
|
|
|
auto designation = Maps::getTileDesignation(pos);
|
|
if (!designation)
|
|
continue;
|
|
|
|
if (designation->bits.hidden)
|
|
continue; // Items in parts of the map not yet revealed
|
|
|
|
bool trade_marked = is_marked_for_trade(item, container);
|
|
if (extra_hide_flags.hide_trade_marked && trade_marked)
|
|
continue;
|
|
|
|
bool caged = is_item_in_cage_cache(item);
|
|
if (extra_hide_flags.hide_in_cages && caged)
|
|
continue;
|
|
|
|
if (extra_hide_flags.hide_in_inventory && container->flags.bits.in_inventory)
|
|
continue;
|
|
|
|
if (hide_unflagged && (!(item->flags.whole & checked_flags.whole) &&
|
|
!trade_marked && !caged && !container->flags.bits.in_inventory))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto quality = static_cast<df::item_quality>(item->getQuality());
|
|
if (quality < min_quality || quality > max_quality)
|
|
continue;
|
|
|
|
auto wear = item->getWear();
|
|
if (wear < min_wear)
|
|
continue;
|
|
|
|
if (spInfo.isValid() && !spInfo.inStockpile(item))
|
|
continue;
|
|
|
|
if (is_grouped)
|
|
{
|
|
auto hash = getItemHash(item);
|
|
if (grouped_items.find(hash) == grouped_items.end())
|
|
{
|
|
grouped_items_store.push_back(item_grouped_entry());
|
|
grouped_items[hash] = &grouped_items_store.back();
|
|
}
|
|
grouped_items[hash]->entries.push_back(item);
|
|
if (last_selected_item &&
|
|
!next_selected_group &&
|
|
hash == last_selected_hash)
|
|
{
|
|
next_selected_group = grouped_items[hash];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
grouped_items_store.push_back(item_grouped_entry());
|
|
auto item_group = &grouped_items_store.back();
|
|
item_group->entries.push_back(item);
|
|
|
|
auto label = get_item_label(item, true, false);
|
|
auto entry = ListEntry<item_grouped_entry *>(label, item_group, item_group->getKeywords());
|
|
items_column.add(entry);
|
|
|
|
if (last_selected_item &&
|
|
!next_selected_group &&
|
|
item == last_selected_item)
|
|
{
|
|
next_selected_group = item_group;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_grouped)
|
|
{
|
|
for (auto groups_iter = grouped_items.begin(); groups_iter != grouped_items.end(); groups_iter++)
|
|
{
|
|
auto item_group = groups_iter->second;
|
|
stringstream label;
|
|
label << item_group->getLabel(is_grouped);
|
|
if (!item_group->isSingleItem())
|
|
label << " (" << item_group->entries.size() << ")";
|
|
auto entry = ListEntry<item_grouped_entry *>(label.str(), item_group, item_group->getKeywords());
|
|
items_column.add(entry);
|
|
}
|
|
}
|
|
|
|
items_column.fixWidth();
|
|
items_column.filterDisplay();
|
|
|
|
if (next_selected_group)
|
|
{
|
|
items_column.selectItem(next_selected_group);
|
|
items_column.display_start_offset = last_display_offset;
|
|
}
|
|
}
|
|
|
|
string getItemHash(df::item *item)
|
|
{
|
|
auto label = get_item_label(item, false, true);
|
|
auto quality = static_cast<df::item_quality>(item->getQuality());
|
|
auto quality_enum = static_cast<df::item_quality>(quality);
|
|
auto quality_string = ENUM_KEY_STR(item_quality, quality_enum);
|
|
auto hash = label + quality_string + int_to_string(item->flags.whole & checked_flags.whole) + " " +
|
|
int_to_string(item->hasImprovements());
|
|
|
|
return hash;
|
|
}
|
|
|
|
void preserveLastSelected()
|
|
{
|
|
last_selected_item = nullptr;
|
|
auto selected_entry = items_column.getFirstSelectedElem();
|
|
if (!selected_entry)
|
|
return;
|
|
last_selected_item = selected_entry->getFirstItem();
|
|
last_selected_hash = (is_grouped && last_selected_item) ? getItemHash(last_selected_item) : "";
|
|
last_display_offset = items_column.display_start_offset;
|
|
}
|
|
|
|
void validateColumn()
|
|
{
|
|
set_to_limit(selected_column, 0);
|
|
}
|
|
|
|
void resize(int32_t x, int32_t y)
|
|
{
|
|
dfhack_viewscreen::resize(x, y);
|
|
items_column.resize();
|
|
items_column.search_margin = gps->dimx - SIDEBAR_WIDTH;
|
|
}
|
|
};
|
|
|
|
df::item_flags ViewscreenStocks::hide_flags;
|
|
extra_filters ViewscreenStocks::extra_hide_flags;
|
|
|
|
struct stocks_hook : public df::viewscreen_storesst
|
|
{
|
|
typedef df::viewscreen_storesst interpose_base;
|
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
|
{
|
|
if (input->count(interface_key::CUSTOM_E))
|
|
{
|
|
Screen::dismiss(this);
|
|
Screen::show(dts::make_unique<ViewscreenStocks>(), plugin_self);
|
|
return;
|
|
}
|
|
INTERPOSE_NEXT(feed)(input);
|
|
}
|
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
|
{
|
|
INTERPOSE_NEXT(render)();
|
|
auto dim = Screen::getWindowSize();
|
|
int x = 40;
|
|
int y = dim.y - 2;
|
|
OutputHotkeyString(x, y, "Enhanced View", "e", false, 0, COLOR_WHITE, COLOR_LIGHTRED);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(stocks_hook, feed);
|
|
IMPLEMENT_VMETHOD_INTERPOSE(stocks_hook, render);
|
|
|
|
|
|
struct stocks_stockpile_hook : public df::viewscreen_dwarfmodest
|
|
{
|
|
typedef df::viewscreen_dwarfmodest interpose_base;
|
|
|
|
bool handleInput(set<df::interface_key> *input)
|
|
{
|
|
if (Gui::inRenameBuilding())
|
|
return false;
|
|
|
|
df::building_stockpilest *sp = get_selected_stockpile();
|
|
if (!sp)
|
|
return false;
|
|
|
|
if (input->count(interface_key::CUSTOM_I))
|
|
{
|
|
Screen::show(dts::make_unique<ViewscreenStocks>(sp), plugin_self);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
|
{
|
|
if (!handleInput(input))
|
|
INTERPOSE_NEXT(feed)(input);
|
|
}
|
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
|
{
|
|
INTERPOSE_NEXT(render)();
|
|
|
|
df::building_stockpilest *sp = get_selected_stockpile();
|
|
if (!sp)
|
|
return;
|
|
|
|
auto dims = Gui::getDwarfmodeViewDims();
|
|
int left_margin = dims.menu_x1 + 1;
|
|
int x = left_margin;
|
|
int y = dims.y2 - 4;
|
|
|
|
int links = 0;
|
|
links += sp->links.give_to_pile.size();
|
|
links += sp->links.take_from_pile.size();
|
|
links += sp->links.give_to_workshop.size();
|
|
links += sp->links.take_from_workshop.size();
|
|
if (links + 12 >= y)
|
|
y = 3;
|
|
|
|
OutputHotkeyString(x, y, "Show Inventory", "i", true, left_margin, COLOR_WHITE, COLOR_LIGHTRED);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(stocks_stockpile_hook, feed);
|
|
IMPLEMENT_VMETHOD_INTERPOSE(stocks_stockpile_hook, render);
|
|
|
|
|
|
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
|
|
|
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
|
|
{
|
|
if (!gps)
|
|
return CR_FAILURE;
|
|
|
|
if (enable != is_enabled)
|
|
{
|
|
if (!INTERPOSE_HOOK(stocks_hook, feed).apply(enable) ||
|
|
!INTERPOSE_HOOK(stocks_hook, render).apply(enable) ||
|
|
!INTERPOSE_HOOK(stocks_stockpile_hook, feed).apply(enable) ||
|
|
!INTERPOSE_HOOK(stocks_stockpile_hook, render).apply(enable))
|
|
return CR_FAILURE;
|
|
|
|
is_enabled = enable;
|
|
}
|
|
|
|
return CR_OK;
|
|
}
|
|
|
|
static command_result stocks_cmd(color_ostream &out, vector <string> & parameters)
|
|
{
|
|
if (!parameters.empty())
|
|
{
|
|
if (toLower(parameters[0])[0] == 'v')
|
|
{
|
|
out << "Stocks plugin" << endl << "Version: " << PLUGIN_VERSION << endl;
|
|
return CR_OK;
|
|
}
|
|
else if (toLower(parameters[0])[0] == 's')
|
|
{
|
|
Screen::show(dts::make_unique<ViewscreenStocks>(), plugin_self);
|
|
return CR_OK;
|
|
}
|
|
}
|
|
|
|
return CR_WRONG_USAGE;
|
|
}
|
|
|
|
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
|
|
{
|
|
commands.push_back(PluginCommand(
|
|
"stocks",
|
|
"An improved stocks management screen.",
|
|
stocks_cmd));
|
|
|
|
ViewscreenStocks::reset();
|
|
|
|
return CR_OK;
|
|
}
|
|
|
|
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
|
{
|
|
switch (event) {
|
|
case SC_MAP_LOADED:
|
|
ViewscreenStocks::reset();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return CR_OK;
|
|
}
|