dfhack/plugins/stocks.cpp

645 lines
19 KiB
C++

2013-04-05 22:40:07 -06:00
#include "uicommon.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/items_other_id.h"
#include "df/job.h"
2013-04-12 19:26:39 -06:00
#include "df/unit.h"
2013-04-05 22:40:07 -06:00
#include "df/world.h"
2013-04-13 18:23:47 -06:00
#include "df/item_quality.h"
2013-04-05 22:40:07 -06:00
#include "modules/Gui.h"
#include "modules/Items.h"
#include "modules/Job.h"
#include "modules/World.h"
2013-04-12 19:26:39 -06:00
#include "modules/Screen.h"
2013-04-13 18:23:47 -06:00
#include "modules/Maps.h"
2013-04-05 22:40:07 -06:00
using df::global::world;
DFHACK_PLUGIN("stocks");
#define PLUGIN_VERSION 0.1
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
2013-04-11 02:52:46 -06:00
#define MAX_NAME 30
2013-04-05 22:40:07 -06:00
#define SIDEBAR_WIDTH 30
static bool show_debugging = false;
static void debug(const string &msg)
{
if (!show_debugging)
return;
color_ostream_proxy out(Core::getInstance().getConsole());
out << "DEBUG (stocks): " << msg << endl;
}
2013-04-13 18:23:47 -06:00
static string getQualityName(const df::item_quality quality)
2013-04-11 02:52:46 -06:00
{
2013-04-13 18:23:47 -06:00
if (gps->dimx - SIDEBAR_WIDTH < 60)
return int_to_string(quality);
else
return ENUM_KEY_STR(item_quality, quality);
}
2013-04-11 02:52:46 -06:00
2013-04-12 19:26:39 -06:00
template <class T>
class StockListColumn : public ListColumn<T>
2013-04-11 02:52:46 -06:00
{
2013-04-12 19:26:39 -06:00
virtual void display_extras(const T &item, int32_t &x, int32_t &y) const
2013-04-11 02:52:46 -06:00
{
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, "N");
else
OutputString(COLOR_LIGHTBLUE, x, y, " ");
if (item->flags.bits.foreign)
OutputString(COLOR_BROWN, x, y, "G");
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, " ");
2013-04-12 19:26:39 -06:00
if (item->flags.bits.in_inventory)
OutputString(COLOR_GREY, x, y, "I");
else
OutputString(COLOR_LIGHTBLUE, x, y, " ");
2013-04-13 18:23:47 -06:00
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, getQualityName(quality));
}
2013-04-11 02:52:46 -06:00
}
};
2013-04-05 22:40:07 -06:00
class ViewscreenStocks : public dfhack_viewscreen
{
public:
2013-04-13 18:23:47 -06:00
static df::item_flags hide_flags;
2013-04-05 22:40:07 -06:00
ViewscreenStocks()
{
selected_column = 0;
items_column.setTitle("Item");
items_column.multiselect = false;
2013-04-12 19:26:39 -06:00
items_column.auto_select = true;
2013-04-05 22:40:07 -06:00
items_column.allow_search = true;
items_column.left_margin = 2;
2013-04-12 19:26:39 -06:00
items_column.bottom_margin = 1;
items_column.search_margin = gps->dimx - SIDEBAR_WIDTH;
2013-04-05 22:40:07 -06:00
items_column.changeHighlight(0);
2013-04-13 18:23:47 -06:00
apply_to_all = false;
hide_unflagged = false;
checked_flags.bits.in_job = true;
checked_flags.bits.rotten = true;
checked_flags.bits.foreign = 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;
checked_flags.bits.in_inventory = true;
min_quality = item_quality::Ordinary;
max_quality = item_quality::Artifact;
min_wear = 0;
2013-04-12 19:26:39 -06:00
2013-04-05 22:40:07 -06:00
populateItems();
items_column.selectDefaultEntry();
}
void feed(set<df::interface_key> *input)
{
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::LEAVESCREEN))
{
input->clear();
Screen::dismiss(this);
return;
}
2013-04-13 18:23:47 -06:00
if (input->count(interface_key::CUSTOM_SHIFT_G))
2013-04-12 19:26:39 -06:00
{
hide_flags.bits.foreign = !hide_flags.bits.foreign;
populateItems();
}
2013-04-13 18:23:47 -06:00
else if (input->count(interface_key::CUSTOM_SHIFT_J))
2013-04-12 19:26:39 -06:00
{
hide_flags.bits.in_job = !hide_flags.bits.in_job;
populateItems();
}
2013-04-13 18:23:47 -06:00
else if (input->count(interface_key::CUSTOM_SHIFT_T))
2013-04-12 19:26:39 -06:00
{
hide_flags.bits.rotten = !hide_flags.bits.rotten;
populateItems();
}
2013-04-13 18:23:47 -06:00
else if (input->count(interface_key::CUSTOM_SHIFT_O))
2013-04-12 19:26:39 -06:00
{
hide_flags.bits.owned = !hide_flags.bits.owned;
populateItems();
}
2013-04-13 18:23:47 -06:00
else if (input->count(interface_key::CUSTOM_SHIFT_F))
2013-04-12 19:26:39 -06:00
{
hide_flags.bits.forbid = !hide_flags.bits.forbid;
populateItems();
}
2013-04-13 18:23:47 -06:00
else if (input->count(interface_key::CUSTOM_SHIFT_D))
2013-04-12 19:26:39 -06:00
{
hide_flags.bits.dump = !hide_flags.bits.dump;
populateItems();
}
2013-04-13 18:23:47 -06:00
else if (input->count(interface_key::CUSTOM_SHIFT_R))
2013-04-12 19:26:39 -06:00
{
hide_flags.bits.on_fire = !hide_flags.bits.on_fire;
populateItems();
}
2013-04-13 18:23:47 -06:00
else if (input->count(interface_key::CUSTOM_SHIFT_M))
2013-04-12 19:26:39 -06:00
{
hide_flags.bits.melt = !hide_flags.bits.melt;
populateItems();
}
2013-04-13 18:23:47 -06:00
else if (input->count(interface_key::CUSTOM_SHIFT_I))
2013-04-12 19:26:39 -06:00
{
hide_flags.bits.in_inventory = !hide_flags.bits.in_inventory;
populateItems();
}
2013-04-13 18:23:47 -06:00
else if (input->count(interface_key::CUSTOM_SHIFT_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::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();
}
}
2013-04-12 19:26:39 -06:00
2013-04-13 18:23:47 -06:00
else if (input->count(interface_key::CUSTOM_SHIFT_Z))
2013-04-12 19:26:39 -06:00
{
input->clear();
auto item = items_column.getFirstSelectedElem();
if (!item)
return;
auto pos = getRealPos(item);
if (!pos)
return;
Screen::dismiss(this);
2013-04-13 18:23:47 -06:00
// 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.
2013-04-12 19:26:39 -06:00
send_key(interface_key::D_LOOK);
move_cursor(*pos);
}
2013-04-13 18:23:47 -06:00
else if (input->count(interface_key::CUSTOM_SHIFT_A))
{
apply_to_all = !apply_to_all;
}
else if (input->count(interface_key::CUSTOM_SHIFT_P))
{
df::item_flags flags;
flags.bits.dump = true;
applyFlag(flags);
}
else if (input->count(interface_key::CUSTOM_SHIFT_B))
{
df::item_flags flags;
flags.bits.forbid = true;
applyFlag(flags);
}
2013-04-12 19:26:39 -06:00
2013-04-13 18:23:47 -06:00
else if (input->count(interface_key::CURSOR_LEFT))
2013-04-05 22:40:07 -06:00
{
--selected_column;
validateColumn();
}
2013-04-13 18:23:47 -06:00
else if (input->count(interface_key::CURSOR_RIGHT))
2013-04-05 22:40:07 -06:00
{
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;
}
}
2013-04-13 18:23:47 -06:00
void move_cursor(const df::coord &pos)
2013-04-12 19:26:39 -06:00
{
Gui::setCursorCoords(pos.x, pos.y, pos.z);
send_key(interface_key::CURSOR_DOWN_Z);
send_key(interface_key::CURSOR_UP_Z);
}
void send_key(const df::interface_key &key)
{
set< df::interface_key > keys;
keys.insert(key);
Gui::getCurViewscreen(true)->feed(&keys);
}
2013-04-05 22:40:07 -06:00
void render()
{
if (Screen::isDismissed(this))
return;
dfhack_viewscreen::render();
Screen::clear();
Screen::drawBorder(" Stocks ");
items_column.display(selected_column == 0);
2013-04-12 19:26:39 -06:00
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", true, left_margin);
OutputString(COLOR_LIGHTRED, x, y, "Press Shift-Hotkey to Toggle", true, left_margin);
OutputFilterString(x, y, "In Job", "J", !hide_flags.bits.in_job, true, left_margin, COLOR_LIGHTBLUE);
2013-04-13 18:23:47 -06:00
OutputFilterString(x, y, "Rotten", "T", !hide_flags.bits.rotten, true, left_margin, COLOR_CYAN);
2013-04-12 19:26:39 -06:00
OutputFilterString(x, y, "Foreign Made", "G", !hide_flags.bits.foreign, true, left_margin, COLOR_BROWN);
OutputFilterString(x, y, "Owned", "O", !hide_flags.bits.owned, true, 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, true, left_margin, COLOR_LIGHTMAGENTA);
OutputFilterString(x, y, "On Fire", "R", !hide_flags.bits.on_fire, true, left_margin, COLOR_LIGHTRED);
OutputFilterString(x, y, "Melt", "M", !hide_flags.bits.melt, true, left_margin, COLOR_BLUE);
OutputFilterString(x, y, "In Inventory", "I", !hide_flags.bits.in_inventory, true, left_margin, COLOR_GREY);
2013-04-13 18:23:47 -06:00
OutputFilterString(x, y, "No Flags", "N", !hide_unflagged, true, left_margin, COLOR_GREY);
++y;
OutputHotkeyString(x, y, "Clear All", "Shift-C", true, left_margin);
OutputHotkeyString(x, y, "Enable All", "Shift-E", true, left_margin);
++y;
OutputHotkeyString(x, y, "Min Qual: ", "-+");
OutputString(COLOR_BROWN, x, y, getQualityName(min_quality), true, left_margin);
OutputHotkeyString(x, y, "Max Qual: ", "/*");
OutputString(COLOR_BROWN, x, y, getQualityName(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);
2013-04-12 19:26:39 -06:00
++y;
2013-04-13 18:23:47 -06:00
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);
2013-04-12 19:26:39 -06:00
OutputHotkeyString(x, y, "Zoom", "Shift-Z", true, left_margin);
2013-04-13 18:23:47 -06:00
OutputHotkeyString(x, y, "Apply to: ", "Shift-A");
OutputString(COLOR_BROWN, x, y, (apply_to_all) ? "Listed" : "Selected", true, left_margin);
OutputHotkeyString(x, y, "Dump", "Shift-P", true, left_margin);
OutputHotkeyString(x, y, "Forbid", "Shift-B", true, left_margin);
2013-04-05 22:40:07 -06:00
}
std::string getFocusString() { return "stocks_view"; }
private:
2013-04-12 19:26:39 -06:00
StockListColumn<df::item *> items_column;
2013-04-05 22:40:07 -06:00
int selected_column;
2013-04-13 18:23:47 -06:00
bool apply_to_all, hide_unflagged;
df::item_flags checked_flags;
df::item_quality min_quality, max_quality;
int16_t min_wear;
2013-04-12 19:26:39 -06:00
2013-04-13 18:23:47 -06:00
static df::coord *getRealPos(df::item *item)
2013-04-12 19:26:39 -06:00
{
if (item->flags.bits.in_inventory)
{
if (item->flags.bits.in_job)
{
auto ref = Items::getSpecificRef(item, specific_ref_type::JOB);
if (ref && ref->job)
{
if (ref->job->job_type == job_type::Eat || ref->job->job_type == job_type::Drink)
return nullptr;
auto unit = Job::getWorker(ref->job);
if (unit)
return &unit->pos;
}
return nullptr;
}
else
{
auto unit = Items::getHolderUnit(item);
if (unit)
return &unit->pos;
return nullptr;
}
}
return &item->pos;
}
2013-04-05 22:40:07 -06:00
2013-04-13 18:23:47 -06:00
void applyFlag(const df::item_flags flags)
{
if (apply_to_all)
{
int state_to_apply = -1;
for (auto iter = items_column.getDisplayList().begin(); iter != items_column.getDisplayList().end(); iter++)
{
auto item = (*iter)->elem;
if (item)
{
// Set all flags based on state of first item in list
if (state_to_apply == -1)
state_to_apply = (item->flags.whole & flags.whole) ? 0 : 1;
if (state_to_apply)
item->flags.whole |= flags.whole;
else
item->flags.whole &= ~flags.whole;
}
}
}
else
{
auto item = items_column.getFirstSelectedElem();
if (item)
item->flags.whole ^= flags.whole;
}
}
void setAllFlags(bool state)
{
hide_flags.bits.in_job = state;
hide_flags.bits.rotten = state;
hide_flags.bits.foreign = 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_flags.bits.in_inventory = state;
hide_unflagged = state;
}
2013-04-05 22:40:07 -06:00
void populateItems()
{
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;
2013-04-11 02:52:46 -06:00
bad_flags.bits.garbage_collect = true;
2013-04-12 19:26:39 -06:00
bad_flags.bits.spider_web = true;
bad_flags.bits.hostile = true;
bad_flags.bits.removed = true;
bad_flags.bits.dead_dwarf = true;
bad_flags.bits.murder = true;
bad_flags.bits.construction = true;
2013-04-05 22:40:07 -06:00
std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY];
for (size_t i = 0; i < items.size(); i++)
{
df::item *item = items[i];
2013-04-12 19:26:39 -06:00
if (item->flags.whole & bad_flags.whole || item->flags.whole & hide_flags.whole)
continue;
if (item->pos.x == -30000)
continue;
2013-04-13 18:23:47 -06:00
auto pos = getRealPos(item);
if (!pos)
continue;
auto designation = Maps::getTileDesignation(*pos);
if (!designation)
2013-04-05 22:40:07 -06:00
continue;
2013-04-13 18:23:47 -06:00
if (designation->bits.hidden)
continue; // Items in parts of the map not yet revealed
if (hide_unflagged && !(item->flags.whole & checked_flags.whole))
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;
auto label = Items::getDescription(item, 0, false);
if (wear > 0)
{
string wearX = "";
for (int i = 0; i < wear; i++)
{
wearX += "X";
}
label = wearX + label + wearX;
}
label = pad_string(label, MAX_NAME, false, true);
2013-04-05 22:40:07 -06:00
2013-04-11 02:52:46 -06:00
items_column.add(label, item);
2013-04-05 22:40:07 -06:00
}
items_column.filterDisplay();
}
void validateColumn()
{
set_to_limit(selected_column, 0);
}
void resize(int32_t x, int32_t y)
{
dfhack_viewscreen::resize(x, y);
items_column.resize();
2013-04-12 19:26:39 -06:00
items_column.search_margin = gps->dimx - SIDEBAR_WIDTH;
2013-04-05 22:40:07 -06:00
}
};
2013-04-13 18:23:47 -06:00
df::item_flags ViewscreenStocks::hide_flags;
2013-04-05 22:40:07 -06:00
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(new ViewscreenStocks());
return CR_OK;
}
}
return CR_WRONG_USAGE;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (!gps)
out.printerr("Could not insert stocks plugin hooks!\n");
commands.push_back(
PluginCommand(
"stocks", "An improved stocks display screen",
stocks_cmd, false, ""));
2013-04-13 18:23:47 -06:00
ViewscreenStocks::hide_flags.whole = 0;
2013-04-05 22:40:07 -06:00
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
2013-04-13 18:23:47 -06:00
ViewscreenStocks::hide_flags.whole = 0;
2013-04-05 22:40:07 -06:00
break;
default:
break;
}
return CR_OK;
}