Merge branch 'develop' of https://github.com/DFHack/dfhack into develop
commit
e4ac25d1e0
@ -0,0 +1,2 @@
|
|||||||
|
PROJECT(jsoncpp)
|
||||||
|
ADD_LIBRARY(jsoncpp STATIC jsoncpp.cpp)
|
@ -0,0 +1,31 @@
|
|||||||
|
#include "jsoncpp.h"
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace JsonEx {
|
||||||
|
|
||||||
|
template <typename T> bool is (const Json::Value &val) { return false; }
|
||||||
|
template <typename T> T as (const Json::Value &val);
|
||||||
|
template <typename T> T get (const Json::Value &val, const std::string &key, const T &default_) { return default_; }
|
||||||
|
|
||||||
|
#define define_helpers(type, is_func, as_func) \
|
||||||
|
template<> inline bool is<type> (const Json::Value &val) { return val.is_func(); } \
|
||||||
|
template<> inline type as<type> (const Json::Value &val) { return val.as_func(); } \
|
||||||
|
template<> inline type get<type> (const Json::Value &val, const std::string &key, const type &default_) \
|
||||||
|
{ Json::Value x = val[key]; return is<type>(x) ? as<type>(x) : default_; }
|
||||||
|
define_helpers(bool, isBool, asBool);
|
||||||
|
define_helpers(Json::Int, isInt, asInt);
|
||||||
|
define_helpers(Json::UInt, isUInt, asUInt);
|
||||||
|
define_helpers(Json::Int64, isInt64, asInt64);
|
||||||
|
define_helpers(Json::UInt64, isUInt64, asUInt64);
|
||||||
|
define_helpers(float, isDouble, asFloat);
|
||||||
|
define_helpers(double, isDouble, asDouble);
|
||||||
|
define_helpers(std::string, isString, asString);
|
||||||
|
#undef define_helpers
|
||||||
|
|
||||||
|
inline std::string toSimpleString (const Json::Value &val)
|
||||||
|
{
|
||||||
|
Json::FastWriter w;
|
||||||
|
return w.write(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
|||||||
Subproject commit 1d1adf4ea438fdcc0da108f6c9bd2a250fbd3f58
|
|
@ -1,9 +1,15 @@
|
|||||||
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags
|
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --long
|
||||||
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}"
|
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}"
|
||||||
OUTPUT_VARIABLE DFHACK_GIT_DESCRIPTION)
|
OUTPUT_VARIABLE DFHACK_GIT_DESCRIPTION)
|
||||||
|
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
|
||||||
|
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}"
|
||||||
|
OUTPUT_VARIABLE DFHACK_GIT_COMMIT)
|
||||||
string(STRIP ${DFHACK_GIT_DESCRIPTION} DFHACK_GIT_DESCRIPTION)
|
string(STRIP ${DFHACK_GIT_DESCRIPTION} DFHACK_GIT_DESCRIPTION)
|
||||||
file(WRITE ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h
|
file(WRITE ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h
|
||||||
"#define DFHACK_GIT_DESCRIPTION \"${DFHACK_GIT_DESCRIPTION}\"")
|
"#define DFHACK_GIT_DESCRIPTION \"${DFHACK_GIT_DESCRIPTION}\"\n")
|
||||||
|
string(STRIP ${DFHACK_GIT_COMMIT} DFHACK_GIT_COMMIT)
|
||||||
|
file(APPEND ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h
|
||||||
|
"#define DFHACK_GIT_COMMIT \"${DFHACK_GIT_COMMIT}\"")
|
||||||
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h
|
${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h
|
||||||
${dfhack_SOURCE_DIR}/library/include/git-describe.h)
|
${dfhack_SOURCE_DIR}/library/include/git-describe.h)
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 53c424e7d891aa6828c3690ad0a3d8d9248dbc3f
|
Subproject commit 7cfed040fe83b94fb6b97afec1c5d86ccf1cb81e
|
@ -0,0 +1,504 @@
|
|||||||
|
#include <set>
|
||||||
|
#include <map>
|
||||||
|
#include "Console.h"
|
||||||
|
#include "Core.h"
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
#include "VTableInterpose.h"
|
||||||
|
#include "uicommon.h"
|
||||||
|
#include "modules/Gui.h"
|
||||||
|
|
||||||
|
#include "df/building_tradedepotst.h"
|
||||||
|
#include "df/general_ref.h"
|
||||||
|
#include "df/general_ref_contained_in_itemst.h"
|
||||||
|
#include "df/viewscreen_dwarfmodest.h"
|
||||||
|
#include "df/viewscreen_layer_militaryst.h"
|
||||||
|
#include "df/viewscreen_tradegoodsst.h"
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace df::enums;
|
||||||
|
using std::string;
|
||||||
|
using std::vector;
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("confirm");
|
||||||
|
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
||||||
|
REQUIRE_GLOBAL(gps);
|
||||||
|
REQUIRE_GLOBAL(ui);
|
||||||
|
|
||||||
|
typedef std::set<df::interface_key> ikey_set;
|
||||||
|
command_result df_confirm (color_ostream &out, vector <string> & parameters);
|
||||||
|
|
||||||
|
struct conf_wrapper;
|
||||||
|
static std::map<std::string, conf_wrapper*> confirmations;
|
||||||
|
|
||||||
|
template <typename VT, typename FT>
|
||||||
|
bool in_vector (std::vector<VT> &vec, FT item)
|
||||||
|
{
|
||||||
|
return std::find(vec.begin(), vec.end(), item) != vec.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
#define goods_selected_func(list) \
|
||||||
|
static bool list##_goods_selected(df::viewscreen_tradegoodsst *screen) \
|
||||||
|
{ \
|
||||||
|
for (auto it = screen->list##_selected.begin(); it != screen->list##_selected.end(); ++it) \
|
||||||
|
if (*it) return true; \
|
||||||
|
return false; \
|
||||||
|
}
|
||||||
|
goods_selected_func(trader);
|
||||||
|
goods_selected_func(broker);
|
||||||
|
#undef goods_selected_func
|
||||||
|
|
||||||
|
#define goods_all_selected_func(list) \
|
||||||
|
static bool list##_goods_all_selected(df::viewscreen_tradegoodsst *screen) \
|
||||||
|
{ \
|
||||||
|
for (size_t i = 0; i < screen->list##_selected.size(); ++i) \
|
||||||
|
{ \
|
||||||
|
if (!screen->list##_selected[i]) \
|
||||||
|
{ \
|
||||||
|
std::vector<df::general_ref*> *refs = &screen->list##_items[i]->general_refs; \
|
||||||
|
bool in_container = false; \
|
||||||
|
for (auto it = refs->begin(); it != refs->end(); ++it) \
|
||||||
|
{ \
|
||||||
|
if (virtual_cast<df::general_ref_contained_in_itemst>(*it)) \
|
||||||
|
{ \
|
||||||
|
in_container = true; \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
if (!in_container) \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
return true; \
|
||||||
|
}
|
||||||
|
goods_all_selected_func(trader);
|
||||||
|
goods_all_selected_func(broker);
|
||||||
|
#undef goods_all_selected_func
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
class confirmation {
|
||||||
|
public:
|
||||||
|
enum cstate { INACTIVE, ACTIVE, SELECTED };
|
||||||
|
typedef T screen_type;
|
||||||
|
screen_type *screen;
|
||||||
|
bool feed (ikey_set *input) {
|
||||||
|
if (state == INACTIVE)
|
||||||
|
{
|
||||||
|
for (auto it = input->begin(); it != input->end(); ++it)
|
||||||
|
{
|
||||||
|
if (intercept_key(*it))
|
||||||
|
{
|
||||||
|
last_key = *it;
|
||||||
|
state = ACTIVE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (state == ACTIVE)
|
||||||
|
{
|
||||||
|
if (input->count(df::interface_key::LEAVESCREEN))
|
||||||
|
state = INACTIVE;
|
||||||
|
else if (input->count(df::interface_key::SELECT))
|
||||||
|
state = SELECTED;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool key_conflict (df::interface_key key)
|
||||||
|
{
|
||||||
|
if (key == df::interface_key::SELECT || key == df::interface_key::LEAVESCREEN)
|
||||||
|
return false;
|
||||||
|
return state == ACTIVE;
|
||||||
|
}
|
||||||
|
void render() {
|
||||||
|
static vector<string> lines;
|
||||||
|
Screen::Pen corner_ul = Screen::Pen((char)201, COLOR_GREY, COLOR_BLACK);
|
||||||
|
Screen::Pen corner_ur = Screen::Pen((char)187, COLOR_GREY, COLOR_BLACK);
|
||||||
|
Screen::Pen corner_dl = Screen::Pen((char)200, COLOR_GREY, COLOR_BLACK);
|
||||||
|
Screen::Pen corner_dr = Screen::Pen((char)188, COLOR_GREY, COLOR_BLACK);
|
||||||
|
Screen::Pen border_ud = Screen::Pen((char)205, COLOR_GREY, COLOR_BLACK);
|
||||||
|
Screen::Pen border_lr = Screen::Pen((char)186, COLOR_GREY, COLOR_BLACK);
|
||||||
|
if (state == ACTIVE)
|
||||||
|
{
|
||||||
|
split_string(&lines, get_message(), "\n");
|
||||||
|
size_t max_length = 30;
|
||||||
|
for (auto it = lines.begin(); it != lines.end(); ++it)
|
||||||
|
max_length = std::max(max_length, it->size());
|
||||||
|
int width = max_length + 4;
|
||||||
|
int height = lines.size() + 4;
|
||||||
|
int x1 = (gps->dimx / 2) - (width / 2);
|
||||||
|
int x2 = x1 + width - 1;
|
||||||
|
int y1 = (gps->dimy / 2) - (height / 2);
|
||||||
|
int y2 = y1 + height - 1;
|
||||||
|
for (int x = x1; x <= x2; x++)
|
||||||
|
{
|
||||||
|
Screen::paintTile(border_ud, x, y1);
|
||||||
|
Screen::paintTile(border_ud, x, y2);
|
||||||
|
}
|
||||||
|
for (int y = y1; y <= y2; y++)
|
||||||
|
{
|
||||||
|
Screen::paintTile(border_lr, x1, y);
|
||||||
|
Screen::paintTile(border_lr, x2, y);
|
||||||
|
}
|
||||||
|
Screen::paintTile(corner_ul, x1, y1);
|
||||||
|
Screen::paintTile(corner_ur, x2, y1);
|
||||||
|
Screen::paintTile(corner_dl, x1, y2);
|
||||||
|
Screen::paintTile(corner_dr, x2, y2);
|
||||||
|
string title = " " + get_title() + " ";
|
||||||
|
Screen::paintString(Screen::Pen(' ', COLOR_DARKGREY, COLOR_BLACK),
|
||||||
|
x2 - 6, y1, "DFHack");
|
||||||
|
Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY),
|
||||||
|
(gps->dimx / 2) - (title.size() / 2), y1, title);
|
||||||
|
int x = x1 + 2;
|
||||||
|
OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN));
|
||||||
|
OutputString(COLOR_WHITE, x, y2, ": Cancel");
|
||||||
|
x = x2 - 2 - 3 - Screen::getKeyDisplay(df::interface_key::SELECT).size();
|
||||||
|
OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::SELECT));
|
||||||
|
OutputString(COLOR_WHITE, x, y2, ": Ok");
|
||||||
|
Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), x1 + 1, y1 + 1, x2 - 1, y2 - 1);
|
||||||
|
for (size_t i = 0; i < lines.size(); i++)
|
||||||
|
{
|
||||||
|
Screen::paintString(Screen::Pen(' ', get_color(), COLOR_BLACK), x1 + 2, y1 + 2 + i, lines[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (state == SELECTED)
|
||||||
|
{
|
||||||
|
ikey_set tmp;
|
||||||
|
tmp.insert(last_key);
|
||||||
|
screen->feed(&tmp);
|
||||||
|
state = INACTIVE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
virtual bool intercept_key (df::interface_key key) = 0;
|
||||||
|
virtual string get_title() { return "Confirm"; }
|
||||||
|
virtual string get_message() = 0;
|
||||||
|
virtual UIColor get_color() { return COLOR_YELLOW; }
|
||||||
|
protected:
|
||||||
|
cstate state;
|
||||||
|
df::interface_key last_key;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct conf_wrapper {
|
||||||
|
bool enabled;
|
||||||
|
std::set<VMethodInterposeLinkBase*> hooks;
|
||||||
|
|
||||||
|
conf_wrapper()
|
||||||
|
:enabled(false)
|
||||||
|
{}
|
||||||
|
void add_hook(VMethodInterposeLinkBase *hook)
|
||||||
|
{
|
||||||
|
if (!hooks.count(hook))
|
||||||
|
hooks.insert(hook);
|
||||||
|
}
|
||||||
|
bool apply (bool state) {
|
||||||
|
if (state == enabled)
|
||||||
|
return true;
|
||||||
|
for (auto h = hooks.begin(); h != hooks.end(); ++h)
|
||||||
|
{
|
||||||
|
if (!(**h).apply(state))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
enabled = state;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#define IMPLEMENT_CONFIRMATION_HOOKS(cls) IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, 0)
|
||||||
|
#define IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, prio) \
|
||||||
|
static cls cls##_instance; \
|
||||||
|
struct cls##_hooks : cls::screen_type { \
|
||||||
|
typedef cls::screen_type interpose_base; \
|
||||||
|
DEFINE_VMETHOD_INTERPOSE(void, feed, (ikey_set *input)) \
|
||||||
|
{ \
|
||||||
|
cls##_instance.screen = this; \
|
||||||
|
if (!cls##_instance.feed(input)) \
|
||||||
|
INTERPOSE_NEXT(feed)(input); \
|
||||||
|
} \
|
||||||
|
DEFINE_VMETHOD_INTERPOSE(void, render, ()) \
|
||||||
|
{ \
|
||||||
|
cls##_instance.screen = this; \
|
||||||
|
INTERPOSE_NEXT(render)(); \
|
||||||
|
cls##_instance.render(); \
|
||||||
|
} \
|
||||||
|
DEFINE_VMETHOD_INTERPOSE(bool, key_conflict, (df::interface_key key)) \
|
||||||
|
{ \
|
||||||
|
return cls##_instance.key_conflict(key) || INTERPOSE_NEXT(key_conflict)(key); \
|
||||||
|
} \
|
||||||
|
}; \
|
||||||
|
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \
|
||||||
|
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \
|
||||||
|
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, key_conflict, prio);
|
||||||
|
|
||||||
|
class trade_confirmation : public confirmation<df::viewscreen_tradegoodsst> {
|
||||||
|
public:
|
||||||
|
virtual bool intercept_key (df::interface_key key)
|
||||||
|
{
|
||||||
|
return key == df::interface_key::TRADE_TRADE;
|
||||||
|
}
|
||||||
|
virtual string get_id() { return "trade"; }
|
||||||
|
virtual string get_title() { return "Confirm trade"; }
|
||||||
|
virtual string get_message()
|
||||||
|
{
|
||||||
|
if (trader_goods_selected(screen) && broker_goods_selected(screen))
|
||||||
|
return "Are you sure you want to trade the selected goods?";
|
||||||
|
else if (trader_goods_selected(screen))
|
||||||
|
return "You are not giving any items. This is likely\n"
|
||||||
|
"to irritate the merchants.\n"
|
||||||
|
"Attempt to trade anyway?";
|
||||||
|
else if (broker_goods_selected(screen))
|
||||||
|
return "You are not receiving any items. You may want to\n"
|
||||||
|
"offer these items instead or choose items to receive.\n"
|
||||||
|
"Attempt to trade anyway?";
|
||||||
|
else
|
||||||
|
return "No items are selected. This is likely\n"
|
||||||
|
"to irritate the merchants.\n"
|
||||||
|
"Attempt to trade anyway?";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
IMPLEMENT_CONFIRMATION_HOOKS(trade_confirmation);
|
||||||
|
|
||||||
|
class trade_cancel_confirmation : public confirmation<df::viewscreen_tradegoodsst> {
|
||||||
|
public:
|
||||||
|
virtual bool intercept_key (df::interface_key key)
|
||||||
|
{
|
||||||
|
return key == df::interface_key::LEAVESCREEN &&
|
||||||
|
(trader_goods_selected(screen) || broker_goods_selected(screen));
|
||||||
|
}
|
||||||
|
virtual string get_id() { return "trade-cancel"; }
|
||||||
|
virtual string get_title() { return "Cancel trade"; }
|
||||||
|
virtual string get_message() { return "Are you sure you want leave this screen?\nSelected items will not be saved."; }
|
||||||
|
};
|
||||||
|
IMPLEMENT_CONFIRMATION_HOOKS_PRIO(trade_cancel_confirmation, -1);
|
||||||
|
|
||||||
|
class trade_seize_confirmation : public confirmation<df::viewscreen_tradegoodsst> {
|
||||||
|
public:
|
||||||
|
virtual bool intercept_key (df::interface_key key)
|
||||||
|
{
|
||||||
|
return trader_goods_selected(screen) && key == df::interface_key::TRADE_SEIZE;
|
||||||
|
}
|
||||||
|
virtual string get_id() { return "trade-seize"; }
|
||||||
|
virtual string get_title() { return "Confirm seize"; }
|
||||||
|
virtual string get_message() { return "Are you sure you want to sieze these goods?"; }
|
||||||
|
};
|
||||||
|
IMPLEMENT_CONFIRMATION_HOOKS(trade_seize_confirmation);
|
||||||
|
|
||||||
|
class trade_offer_confirmation : public confirmation<df::viewscreen_tradegoodsst> {
|
||||||
|
public:
|
||||||
|
virtual bool intercept_key (df::interface_key key)
|
||||||
|
{
|
||||||
|
return broker_goods_selected(screen) && key == df::interface_key::TRADE_OFFER;
|
||||||
|
}
|
||||||
|
virtual string get_id() { return "trade-offer"; }
|
||||||
|
virtual string get_title() { return "Confirm offer"; }
|
||||||
|
virtual string get_message() { return "Are you sure you want to offer these goods?\nYou will receive no payment."; }
|
||||||
|
};
|
||||||
|
IMPLEMENT_CONFIRMATION_HOOKS(trade_offer_confirmation);
|
||||||
|
|
||||||
|
class trade_select_all_confirmation : public confirmation<df::viewscreen_tradegoodsst> {
|
||||||
|
public:
|
||||||
|
virtual bool intercept_key (df::interface_key key)
|
||||||
|
{
|
||||||
|
if (key == df::interface_key::SEC_SELECT)
|
||||||
|
{
|
||||||
|
if (screen->in_right_pane && broker_goods_selected(screen) && !broker_goods_all_selected(screen))
|
||||||
|
return true;
|
||||||
|
else if (!screen->in_right_pane && trader_goods_selected(screen) && !trader_goods_all_selected(screen))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
virtual string get_id() { return "trade-select-all"; }
|
||||||
|
virtual string get_title() { return "Confirm selection"; }
|
||||||
|
virtual string get_message()
|
||||||
|
{
|
||||||
|
return "Selecting all goods will overwrite your current selection\n"
|
||||||
|
"and cannot be undone. Continue?";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
IMPLEMENT_CONFIRMATION_HOOKS(trade_select_all_confirmation);
|
||||||
|
|
||||||
|
class hauling_route_delete_confirmation : public confirmation<df::viewscreen_dwarfmodest> {
|
||||||
|
public:
|
||||||
|
virtual bool intercept_key (df::interface_key key)
|
||||||
|
{
|
||||||
|
if (ui->main.mode == ui_sidebar_mode::Hauling && ui->hauling.view_routes.size())
|
||||||
|
return key == df::interface_key::D_HAULING_REMOVE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
virtual string get_id() { return "haul-delete"; }
|
||||||
|
virtual string get_title() { return "Confirm deletion"; }
|
||||||
|
virtual string get_message()
|
||||||
|
{
|
||||||
|
std::string type = (ui->hauling.view_stops[ui->hauling.cursor_top]) ? "stop" : "route";
|
||||||
|
return std::string("Are you sure you want to delete this ") + type + "?";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
IMPLEMENT_CONFIRMATION_HOOKS(hauling_route_delete_confirmation);
|
||||||
|
|
||||||
|
class depot_remove_confirmation : public confirmation<df::viewscreen_dwarfmodest> {
|
||||||
|
public:
|
||||||
|
virtual bool intercept_key (df::interface_key key)
|
||||||
|
{
|
||||||
|
df::building_tradedepotst *depot = virtual_cast<df::building_tradedepotst>(Gui::getAnyBuilding(screen));
|
||||||
|
if (depot && key == df::interface_key::DESTROYBUILDING)
|
||||||
|
{
|
||||||
|
for (auto it = ui->caravans.begin(); it != ui->caravans.end(); ++it)
|
||||||
|
{
|
||||||
|
if ((**it).time_remaining)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
virtual string get_id() { return "depot-remove"; }
|
||||||
|
virtual string get_title() { return "Confirm depot removal"; }
|
||||||
|
virtual string get_message()
|
||||||
|
{
|
||||||
|
return "Are you sure you want to remove this depot?\n"
|
||||||
|
"Merchants are present and will lose profits.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
IMPLEMENT_CONFIRMATION_HOOKS(depot_remove_confirmation);
|
||||||
|
|
||||||
|
class squad_disband_confirmation : public confirmation<df::viewscreen_layer_militaryst> {
|
||||||
|
public:
|
||||||
|
virtual bool intercept_key (df::interface_key key)
|
||||||
|
{
|
||||||
|
return screen->num_squads && key == df::interface_key::D_MILITARY_DISBAND_SQUAD;
|
||||||
|
}
|
||||||
|
virtual string get_id() { return "squad-disband"; }
|
||||||
|
virtual string get_title() { return "Disband squad"; }
|
||||||
|
virtual string get_message() { return "Are you sure you want to disband this squad?"; }
|
||||||
|
};
|
||||||
|
IMPLEMENT_CONFIRMATION_HOOKS(squad_disband_confirmation);
|
||||||
|
|
||||||
|
class note_delete_confirmation : public confirmation<df::viewscreen_dwarfmodest> {
|
||||||
|
public:
|
||||||
|
virtual bool intercept_key (df::interface_key key)
|
||||||
|
{
|
||||||
|
return ui->main.mode == ui_sidebar_mode::NotesPoints && key == df::interface_key::D_NOTE_DELETE;
|
||||||
|
}
|
||||||
|
virtual string get_id() { return "note-delete"; }
|
||||||
|
virtual string get_title() { return "Delete note"; }
|
||||||
|
virtual string get_message() { return "Are you sure you want to delete this note?"; }
|
||||||
|
};
|
||||||
|
IMPLEMENT_CONFIRMATION_HOOKS(note_delete_confirmation);
|
||||||
|
|
||||||
|
class route_delete_confirmation : public confirmation<df::viewscreen_dwarfmodest> {
|
||||||
|
public:
|
||||||
|
virtual bool intercept_key (df::interface_key key)
|
||||||
|
{
|
||||||
|
return ui->main.mode == ui_sidebar_mode::NotesRoutes && key == df::interface_key::D_NOTE_ROUTE_DELETE;
|
||||||
|
}
|
||||||
|
virtual string get_id() { return "route-delete"; }
|
||||||
|
virtual string get_title() { return "Delete route"; }
|
||||||
|
virtual string get_message() { return "Are you sure you want to delete this route?"; }
|
||||||
|
};
|
||||||
|
IMPLEMENT_CONFIRMATION_HOOKS(route_delete_confirmation);
|
||||||
|
|
||||||
|
#define CHOOK(cls) \
|
||||||
|
HOOK_ACTION(cls, render) \
|
||||||
|
HOOK_ACTION(cls, feed) \
|
||||||
|
HOOK_ACTION(cls, key_conflict)
|
||||||
|
|
||||||
|
#define CHOOKS \
|
||||||
|
CHOOK(trade_confirmation) \
|
||||||
|
CHOOK(trade_cancel_confirmation) \
|
||||||
|
CHOOK(trade_seize_confirmation) \
|
||||||
|
CHOOK(trade_offer_confirmation) \
|
||||||
|
CHOOK(trade_select_all_confirmation) \
|
||||||
|
CHOOK(hauling_route_delete_confirmation) \
|
||||||
|
CHOOK(depot_remove_confirmation) \
|
||||||
|
CHOOK(squad_disband_confirmation) \
|
||||||
|
CHOOK(note_delete_confirmation) \
|
||||||
|
CHOOK(route_delete_confirmation)
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init (color_ostream &out, vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
#define HOOK_ACTION(cls, method) \
|
||||||
|
if (confirmations.find(cls##_instance.get_id()) == confirmations.end()) \
|
||||||
|
confirmations[cls##_instance.get_id()] = new conf_wrapper; \
|
||||||
|
confirmations[cls##_instance.get_id()]->add_hook(&INTERPOSE_HOOK(cls##_hooks, method));
|
||||||
|
CHOOKS
|
||||||
|
#undef HOOK_ACTION
|
||||||
|
commands.push_back(PluginCommand(
|
||||||
|
"confirm",
|
||||||
|
"Confirmation dialogs",
|
||||||
|
df_confirm,
|
||||||
|
false, //allow non-interactive use
|
||||||
|
|
||||||
|
" confirmation enable|disable option|all ...\n"
|
||||||
|
" confirmation help|status\n"
|
||||||
|
));
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
|
||||||
|
{
|
||||||
|
if (is_enabled != enable)
|
||||||
|
{
|
||||||
|
for (auto c = confirmations.begin(); c != confirmations.end(); ++c)
|
||||||
|
{
|
||||||
|
if (!c->second->apply(enable))
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
is_enabled = enable;
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown (color_ostream &out)
|
||||||
|
{
|
||||||
|
if (plugin_enable(out, false) != CR_OK)
|
||||||
|
return CR_FAILURE;
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void enable_conf (color_ostream &out, string name, bool state)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
for (auto it = confirmations.begin(); it != confirmations.end(); ++it)
|
||||||
|
{
|
||||||
|
if (it->first == name)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
it->second->apply(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
out.printerr("Unrecognized option: %s\n", name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
command_result df_confirm (color_ostream &out, vector <string> & parameters)
|
||||||
|
{
|
||||||
|
CoreSuspender suspend;
|
||||||
|
bool state = true;
|
||||||
|
if (in_vector(parameters, "help") || in_vector(parameters, "status"))
|
||||||
|
{
|
||||||
|
out << "Available options: \n";
|
||||||
|
for (auto it = confirmations.begin(); it != confirmations.end(); ++it)
|
||||||
|
{
|
||||||
|
out << " " << it->first << ": " << (it->second->enabled ? "enabled" : "disabled") << std::endl;
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
for (auto it = parameters.begin(); it != parameters.end(); ++it)
|
||||||
|
{
|
||||||
|
if (*it == "enable")
|
||||||
|
state = true;
|
||||||
|
else if (*it == "disable")
|
||||||
|
state = false;
|
||||||
|
else if (*it == "all")
|
||||||
|
{
|
||||||
|
for (auto it = confirmations.begin(); it != confirmations.end(); ++it)
|
||||||
|
{
|
||||||
|
it->second->apply(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
enable_conf(out, *it, state);
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
}
|
@ -1,71 +0,0 @@
|
|||||||
#include "Core.h"
|
|
||||||
#include <Console.h>
|
|
||||||
#include <Export.h>
|
|
||||||
#include <PluginManager.h>
|
|
||||||
#include <modules/Gui.h>
|
|
||||||
#include <modules/Screen.h>
|
|
||||||
#include <vector>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <stack>
|
|
||||||
#include <string>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include <VTableInterpose.h>
|
|
||||||
#include "df/graphic.h"
|
|
||||||
#include "df/viewscreen_titlest.h"
|
|
||||||
|
|
||||||
using std::vector;
|
|
||||||
using std::string;
|
|
||||||
using std::stack;
|
|
||||||
using namespace DFHack;
|
|
||||||
|
|
||||||
using df::global::gps;
|
|
||||||
|
|
||||||
DFHACK_PLUGIN("vshook");
|
|
||||||
|
|
||||||
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
|
||||||
|
|
||||||
struct title_hook : df::viewscreen_titlest {
|
|
||||||
typedef df::viewscreen_titlest interpose_base;
|
|
||||||
|
|
||||||
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
|
||||||
{
|
|
||||||
INTERPOSE_NEXT(render)();
|
|
||||||
|
|
||||||
Screen::Pen pen(' ',COLOR_WHITE,COLOR_BLACK);
|
|
||||||
Screen::paintString(pen, 0, 0, "DFHack ");
|
|
||||||
Screen::paintString(pen, 7, 0, Version::dfhack_version());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
IMPLEMENT_VMETHOD_INTERPOSE(title_hook, render);
|
|
||||||
|
|
||||||
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)
|
|
||||||
{
|
|
||||||
if (!gps)
|
|
||||||
return CR_FAILURE;
|
|
||||||
|
|
||||||
if (enable != is_enabled)
|
|
||||||
{
|
|
||||||
if (!INTERPOSE_HOOK(title_hook, render).apply(enable))
|
|
||||||
return CR_FAILURE;
|
|
||||||
|
|
||||||
is_enabled = enable;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
|
||||||
{
|
|
||||||
// DON'T DO THIS IN NON-EXAMPLE PLUGINS
|
|
||||||
plugin_enable(out, true);
|
|
||||||
|
|
||||||
return CR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
|
||||||
{
|
|
||||||
INTERPOSE_HOOK(title_hook, render).remove();
|
|
||||||
return CR_OK;
|
|
||||||
}
|
|
@ -0,0 +1,238 @@
|
|||||||
|
#include "Console.h"
|
||||||
|
#include "Core.h"
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
|
||||||
|
#include "modules/Units.h"
|
||||||
|
#include "modules/Translation.h"
|
||||||
|
#include "modules/World.h"
|
||||||
|
|
||||||
|
#include "df/map_block.h"
|
||||||
|
#include "df/unit.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("fix-unit-occupancy");
|
||||||
|
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
||||||
|
REQUIRE_GLOBAL(cursor);
|
||||||
|
REQUIRE_GLOBAL(world);
|
||||||
|
|
||||||
|
static int run_interval = 1200; // daily
|
||||||
|
|
||||||
|
inline float getClock()
|
||||||
|
{
|
||||||
|
return (float)clock() / (float)CLOCKS_PER_SEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string get_unit_description(df::unit *unit)
|
||||||
|
{
|
||||||
|
if (!unit)
|
||||||
|
return "";
|
||||||
|
std::string desc;
|
||||||
|
auto name = Units::getVisibleName(unit);
|
||||||
|
if (name->has_name)
|
||||||
|
desc = Translation::TranslateName(name, false);
|
||||||
|
desc += (desc.size() ? ", " : "") + Units::getProfessionName(unit); // Check animal type too
|
||||||
|
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
df::unit *findUnit(int x, int y, int z)
|
||||||
|
{
|
||||||
|
for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u)
|
||||||
|
{
|
||||||
|
if ((**u).pos.x == x && (**u).pos.y == y && (**u).pos.z == z)
|
||||||
|
return *u;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct uo_opts {
|
||||||
|
bool dry_run;
|
||||||
|
bool use_cursor;
|
||||||
|
uo_opts (bool dry_run = false, bool use_cursor = false)
|
||||||
|
:dry_run(dry_run), use_cursor(use_cursor)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
command_result cmd_fix_unit_occupancy (color_ostream &out, std::vector <std::string> & parameters);
|
||||||
|
|
||||||
|
unsigned fix_unit_occupancy (color_ostream &out, uo_opts &opts)
|
||||||
|
{
|
||||||
|
if (!Core::getInstance().isWorldLoaded())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (opts.use_cursor && cursor->x < 0)
|
||||||
|
out.printerr("No cursor\n");
|
||||||
|
|
||||||
|
unsigned count = 0;
|
||||||
|
float time1 = getClock();
|
||||||
|
for (size_t i = 0; i < world->map.map_blocks.size(); i++)
|
||||||
|
{
|
||||||
|
df::map_block *block = world->map.map_blocks[i];
|
||||||
|
int map_z = block->map_pos.z;
|
||||||
|
if (opts.use_cursor && (map_z != cursor->z || block->map_pos.y != (cursor->y / 16) * 16 || block->map_pos.x != (cursor->x / 16) * 16))
|
||||||
|
continue;
|
||||||
|
for (int x = 0; x < 16; x++)
|
||||||
|
{
|
||||||
|
int map_x = x + block->map_pos.x;
|
||||||
|
for (int y = 0; y < 16; y++)
|
||||||
|
{
|
||||||
|
if (block->designation[x][y].bits.hidden)
|
||||||
|
continue;
|
||||||
|
int map_y = y + block->map_pos.y;
|
||||||
|
if (opts.use_cursor && (map_x != cursor->x || map_y != cursor->y))
|
||||||
|
continue;
|
||||||
|
bool cur_occupancy = block->occupancy[x][y].bits.unit;
|
||||||
|
bool fixed_occupancy = cur_occupancy;
|
||||||
|
df::unit *cur_unit = findUnit(map_x, map_y, map_z);
|
||||||
|
if (cur_occupancy && !cur_unit)
|
||||||
|
{
|
||||||
|
out.print("%sFixing occupancy at (%i, %i, %i) - no unit found\n",
|
||||||
|
opts.dry_run ? "(Dry run) " : "",
|
||||||
|
map_x, map_y, map_z);
|
||||||
|
fixed_occupancy = false;
|
||||||
|
}
|
||||||
|
// else if (!cur_occupancy && cur_unit)
|
||||||
|
// {
|
||||||
|
// out.printerr("Unit found at (%i, %i, %i): %s\n", map_x, map_y, map_z, get_unit_description(cur_unit).c_str());
|
||||||
|
// fixed_occupancy = true;
|
||||||
|
// }
|
||||||
|
if (cur_occupancy != fixed_occupancy && !opts.dry_run)
|
||||||
|
{
|
||||||
|
++count;
|
||||||
|
block->occupancy[x][y].bits.unit = fixed_occupancy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float time2 = getClock();
|
||||||
|
std::cerr << "fix-unit-occupancy: elapsed time: " << time2 - time1 << " secs" << endl;
|
||||||
|
if (count)
|
||||||
|
out << "Fixed occupancy of " << count << " tiles [fix-unit-occupancy]" << endl;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned fix_unit_occupancy (color_ostream &out)
|
||||||
|
{
|
||||||
|
uo_opts tmp;
|
||||||
|
return fix_unit_occupancy(out, tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
commands.push_back(PluginCommand(
|
||||||
|
"fix-unit-occupancy",
|
||||||
|
"Fix unit occupancy issues such as phantom 'creature blocking site' messages (bug 3499)",
|
||||||
|
cmd_fix_unit_occupancy,
|
||||||
|
false, //allow non-interactive use
|
||||||
|
" enable fix-unit-occupancy: Enable the plugin\n"
|
||||||
|
" disable fix-unit-occupancy fix-unit-occupancy: Disable the plugin\n"
|
||||||
|
" fix-unit-occupancy: Run the plugin immediately. Available options:\n"
|
||||||
|
" -h|here|cursor: Only operate on the tile at the cursor\n"
|
||||||
|
" -n|dry|dry-run: Do not write changes to map\n"
|
||||||
|
" fix-unit-occupancy interval X: Run the plugin every X ticks (when enabled).\n"
|
||||||
|
" Default is 1200, or 1 day. Ticks are only counted when the game is unpaused.\n"
|
||||||
|
|
||||||
|
));
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown (color_ostream &out)
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
|
||||||
|
{
|
||||||
|
is_enabled = enable;
|
||||||
|
if (is_enabled)
|
||||||
|
fix_unit_occupancy(out);
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_onupdate (color_ostream &out)
|
||||||
|
{
|
||||||
|
static unsigned tick = UINT_MAX;
|
||||||
|
static decltype(world->frame_counter) old_world_frame = 0;
|
||||||
|
if (is_enabled && World::isFortressMode())
|
||||||
|
{
|
||||||
|
// only increment tick when the world has changed
|
||||||
|
if (old_world_frame != world->frame_counter)
|
||||||
|
{
|
||||||
|
old_world_frame = world->frame_counter;
|
||||||
|
tick++;
|
||||||
|
}
|
||||||
|
if (tick > run_interval)
|
||||||
|
{
|
||||||
|
tick = 0;
|
||||||
|
fix_unit_occupancy(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tick = INT_MAX;
|
||||||
|
old_world_frame = 0;
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event evt)
|
||||||
|
{
|
||||||
|
if (evt == SC_MAP_LOADED && is_enabled && World::isFortressMode())
|
||||||
|
fix_unit_occupancy(out);
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
command_result cmd_fix_unit_occupancy (color_ostream &out, std::vector <std::string> & parameters)
|
||||||
|
{
|
||||||
|
CoreSuspender suspend;
|
||||||
|
uo_opts opts;
|
||||||
|
bool ok = true;
|
||||||
|
|
||||||
|
if (parameters.size() >= 1 && (parameters[0] == "-i" || parameters[0].find("interval") != std::string::npos))
|
||||||
|
{
|
||||||
|
if (parameters.size() >= 2)
|
||||||
|
{
|
||||||
|
int new_interval = atoi(parameters[1].c_str());
|
||||||
|
if (new_interval < 100)
|
||||||
|
{
|
||||||
|
out.printerr("Invalid interval - minimum is 100 ticks\n");
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
}
|
||||||
|
run_interval = new_interval;
|
||||||
|
if (!is_enabled)
|
||||||
|
out << "note: Plugin not enabled (use `enable fix-unit-occupancy` to enable)" << endl;
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto opt = parameters.begin(); opt != parameters.end(); ++opt)
|
||||||
|
{
|
||||||
|
if (*opt == "-n" || opt->find("dry") != std::string::npos)
|
||||||
|
opts.dry_run = true;
|
||||||
|
else if (*opt == "-h" || opt->find("cursor") != std::string::npos || opt->find("here") != std::string::npos)
|
||||||
|
opts.use_cursor = true;
|
||||||
|
else if (opt->find("enable") != std::string::npos)
|
||||||
|
plugin_enable(out, true);
|
||||||
|
else if (opt->find("disable") != std::string::npos)
|
||||||
|
plugin_enable(out, false);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out.printerr("Unknown parameter: %s\n", opt->c_str());
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ok)
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
|
||||||
|
unsigned count = fix_unit_occupancy(out, opts);
|
||||||
|
if (!count)
|
||||||
|
out << "No occupancy issues found." << endl;
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
@ -0,0 +1,481 @@
|
|||||||
|
#include "uicommon.h"
|
||||||
|
|
||||||
|
using df::global::enabler;
|
||||||
|
using df::global::gps;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List classes
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class ListEntry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
T elem;
|
||||||
|
string text, keywords;
|
||||||
|
bool selected;
|
||||||
|
UIColor color;
|
||||||
|
|
||||||
|
ListEntry(const string text, const T elem, const string keywords = "", const UIColor color = COLOR_UNSELECTED) :
|
||||||
|
elem(elem), text(text), selected(false), keywords(keywords), color(color)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class ListColumn
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int highlighted_index;
|
||||||
|
int display_start_offset;
|
||||||
|
unsigned short text_clip_at;
|
||||||
|
int32_t bottom_margin, search_margin, left_margin;
|
||||||
|
bool multiselect;
|
||||||
|
bool allow_null;
|
||||||
|
bool auto_select;
|
||||||
|
bool allow_search;
|
||||||
|
bool feed_mouse_set_highlight;
|
||||||
|
bool feed_changed_highlight;
|
||||||
|
|
||||||
|
ListColumn()
|
||||||
|
{
|
||||||
|
bottom_margin = 3;
|
||||||
|
clear();
|
||||||
|
left_margin = 2;
|
||||||
|
search_margin = 63;
|
||||||
|
highlighted_index = 0;
|
||||||
|
text_clip_at = 0;
|
||||||
|
multiselect = false;
|
||||||
|
allow_null = true;
|
||||||
|
auto_select = false;
|
||||||
|
allow_search = true;
|
||||||
|
feed_mouse_set_highlight = false;
|
||||||
|
feed_changed_highlight = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
list.clear();
|
||||||
|
display_list.clear();
|
||||||
|
display_start_offset = 0;
|
||||||
|
if (highlighted_index != -1)
|
||||||
|
highlighted_index = 0;
|
||||||
|
max_item_width = title.length();
|
||||||
|
resize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void resize()
|
||||||
|
{
|
||||||
|
display_max_rows = gps->dimy - 4 - bottom_margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(ListEntry<T> &entry)
|
||||||
|
{
|
||||||
|
list.push_back(entry);
|
||||||
|
if (entry.text.length() > max_item_width)
|
||||||
|
max_item_width = entry.text.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(const string &text, const T &elem)
|
||||||
|
{
|
||||||
|
list.push_back(ListEntry<T>(text, elem));
|
||||||
|
if (text.length() > max_item_width)
|
||||||
|
max_item_width = text.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
int fixWidth()
|
||||||
|
{
|
||||||
|
if (text_clip_at > 0 && max_item_width > text_clip_at)
|
||||||
|
max_item_width = text_clip_at;
|
||||||
|
|
||||||
|
for (auto it = list.begin(); it != list.end(); it++)
|
||||||
|
{
|
||||||
|
it->text = pad_string(it->text, max_item_width, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getMaxItemWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getMaxItemWidth()
|
||||||
|
{
|
||||||
|
return left_margin + max_item_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {}
|
||||||
|
|
||||||
|
void display(const bool is_selected_column) const
|
||||||
|
{
|
||||||
|
int32_t y = 2;
|
||||||
|
paint_text(COLOR_TITLE, left_margin, y, title);
|
||||||
|
|
||||||
|
int last_index_able_to_display = display_start_offset + display_max_rows;
|
||||||
|
for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++)
|
||||||
|
{
|
||||||
|
++y;
|
||||||
|
UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color;
|
||||||
|
UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK;
|
||||||
|
|
||||||
|
string item_label = display_list[i]->text;
|
||||||
|
if (text_clip_at > 0 && item_label.length() > text_clip_at)
|
||||||
|
item_label.resize(text_clip_at);
|
||||||
|
|
||||||
|
paint_text(fg_color, left_margin, y, item_label, bg_color);
|
||||||
|
int x = left_margin + display_list[i]->text.length() + 1;
|
||||||
|
display_extras(display_list[i]->elem, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_selected_column && allow_search)
|
||||||
|
{
|
||||||
|
y = gps->dimy - 3;
|
||||||
|
int32_t x = search_margin;
|
||||||
|
OutputHotkeyString(x, y, "Search" ,"S");
|
||||||
|
OutputString(COLOR_WHITE, x, y, ": ");
|
||||||
|
OutputString(COLOR_WHITE, x, y, search_string);
|
||||||
|
OutputString(COLOR_LIGHTGREEN, x, y, "_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void tokenizeSearch (vector<string> *dest, const string search)
|
||||||
|
{
|
||||||
|
if (!search.empty())
|
||||||
|
split_string(dest, search, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool showEntry(const ListEntry<T> *entry, const vector<string> &search_tokens)
|
||||||
|
{
|
||||||
|
if (!search_tokens.empty())
|
||||||
|
{
|
||||||
|
string item_string = toLower(entry->text);
|
||||||
|
for (auto si = search_tokens.begin(); si != search_tokens.end(); si++)
|
||||||
|
{
|
||||||
|
if (!si->empty() && item_string.find(*si) == string::npos &&
|
||||||
|
entry->keywords.find(*si) == string::npos)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void filterDisplay()
|
||||||
|
{
|
||||||
|
ListEntry<T> *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL;
|
||||||
|
display_list.clear();
|
||||||
|
|
||||||
|
search_string = toLower(search_string);
|
||||||
|
vector<string> search_tokens;
|
||||||
|
tokenizeSearch(&search_tokens, search_string);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < list.size(); i++)
|
||||||
|
{
|
||||||
|
ListEntry<T> *entry = &list[i];
|
||||||
|
|
||||||
|
if (showEntry(entry, search_tokens))
|
||||||
|
{
|
||||||
|
display_list.push_back(entry);
|
||||||
|
if (entry == prev_selected)
|
||||||
|
highlighted_index = display_list.size() - 1;
|
||||||
|
}
|
||||||
|
else if (auto_select)
|
||||||
|
{
|
||||||
|
entry->selected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changeHighlight(0);
|
||||||
|
feed_changed_highlight = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectDefaultEntry()
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < display_list.size(); i++)
|
||||||
|
{
|
||||||
|
if (display_list[i]->selected)
|
||||||
|
{
|
||||||
|
highlighted_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void centerSelection()
|
||||||
|
{
|
||||||
|
if (display_list.size() == 0)
|
||||||
|
return;
|
||||||
|
display_start_offset = highlighted_index - (display_max_rows / 2);
|
||||||
|
validateDisplayOffset();
|
||||||
|
validateHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
void validateHighlight()
|
||||||
|
{
|
||||||
|
set_to_limit(highlighted_index, display_list.size() - 1);
|
||||||
|
|
||||||
|
if (highlighted_index < display_start_offset)
|
||||||
|
display_start_offset = highlighted_index;
|
||||||
|
else if (highlighted_index >= display_start_offset + display_max_rows)
|
||||||
|
display_start_offset = highlighted_index - display_max_rows + 1;
|
||||||
|
|
||||||
|
if (auto_select || (!allow_null && list.size() == 1))
|
||||||
|
display_list[highlighted_index]->selected = true;
|
||||||
|
|
||||||
|
feed_changed_highlight = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void changeHighlight(const int highlight_change, const int offset_shift = 0)
|
||||||
|
{
|
||||||
|
if (!initHighlightChange())
|
||||||
|
return;
|
||||||
|
|
||||||
|
highlighted_index += highlight_change + offset_shift * display_max_rows;
|
||||||
|
|
||||||
|
display_start_offset += offset_shift * display_max_rows;
|
||||||
|
validateDisplayOffset();
|
||||||
|
validateHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
void validateDisplayOffset()
|
||||||
|
{
|
||||||
|
set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setHighlight(const int index)
|
||||||
|
{
|
||||||
|
if (!initHighlightChange())
|
||||||
|
return;
|
||||||
|
|
||||||
|
highlighted_index = index;
|
||||||
|
validateHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool initHighlightChange()
|
||||||
|
{
|
||||||
|
if (display_list.size() == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (auto_select && !multiselect)
|
||||||
|
{
|
||||||
|
for (auto it = list.begin(); it != list.end(); it++)
|
||||||
|
{
|
||||||
|
it->selected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleHighlighted()
|
||||||
|
{
|
||||||
|
if (display_list.size() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (auto_select)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ListEntry<T> *entry = display_list[highlighted_index];
|
||||||
|
if (!multiselect || !allow_null)
|
||||||
|
{
|
||||||
|
int selected_count = 0;
|
||||||
|
for (size_t i = 0; i < list.size(); i++)
|
||||||
|
{
|
||||||
|
if (!multiselect && !entry->selected)
|
||||||
|
list[i].selected = false;
|
||||||
|
if (!allow_null && list[i].selected)
|
||||||
|
selected_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allow_null && entry->selected && selected_count == 1)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->selected = !entry->selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<T> getSelectedElems(bool only_one = false)
|
||||||
|
{
|
||||||
|
vector<T> results;
|
||||||
|
for (auto it = list.begin(); it != list.end(); it++)
|
||||||
|
{
|
||||||
|
if ((*it).selected)
|
||||||
|
{
|
||||||
|
results.push_back(it->elem);
|
||||||
|
if (only_one)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
T getFirstSelectedElem()
|
||||||
|
{
|
||||||
|
vector<T> results = getSelectedElems(true);
|
||||||
|
if (results.size() == 0)
|
||||||
|
return (T)nullptr;
|
||||||
|
else
|
||||||
|
return results[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearSelection()
|
||||||
|
{
|
||||||
|
for_each_(list, clear_fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectItem(const T elem)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
for (; i < display_list.size(); i++)
|
||||||
|
{
|
||||||
|
if (display_list[i]->elem == elem)
|
||||||
|
{
|
||||||
|
setHighlight(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearSearch()
|
||||||
|
{
|
||||||
|
search_string.clear();
|
||||||
|
filterDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getDisplayListSize()
|
||||||
|
{
|
||||||
|
return display_list.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<ListEntry<T>*> &getDisplayList()
|
||||||
|
{
|
||||||
|
return display_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getBaseListSize()
|
||||||
|
{
|
||||||
|
return list.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool validSearchInput (unsigned char c)
|
||||||
|
{
|
||||||
|
return (c >= 'a' && c <= 'z') || c == ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool feed(set<df::interface_key> *input)
|
||||||
|
{
|
||||||
|
feed_mouse_set_highlight = feed_changed_highlight = false;
|
||||||
|
if (input->count(interface_key::STANDARDSCROLL_UP))
|
||||||
|
{
|
||||||
|
changeHighlight(-1);
|
||||||
|
}
|
||||||
|
else if (input->count(interface_key::STANDARDSCROLL_DOWN))
|
||||||
|
{
|
||||||
|
changeHighlight(1);
|
||||||
|
}
|
||||||
|
else if (input->count(interface_key::STANDARDSCROLL_PAGEUP))
|
||||||
|
{
|
||||||
|
changeHighlight(0, -1);
|
||||||
|
}
|
||||||
|
else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN))
|
||||||
|
{
|
||||||
|
changeHighlight(0, 1);
|
||||||
|
}
|
||||||
|
else if (input->count(interface_key::SELECT) && !auto_select)
|
||||||
|
{
|
||||||
|
toggleHighlighted();
|
||||||
|
}
|
||||||
|
else if (input->count(interface_key::CUSTOM_SHIFT_S))
|
||||||
|
{
|
||||||
|
clearSearch();
|
||||||
|
}
|
||||||
|
else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut)
|
||||||
|
{
|
||||||
|
return setHighlightByMouse();
|
||||||
|
}
|
||||||
|
else if (allow_search)
|
||||||
|
{
|
||||||
|
// Search query typing mode always on
|
||||||
|
df::interface_key last_token = get_string_key(input);
|
||||||
|
int charcode = Screen::keyToChar(last_token);
|
||||||
|
if (charcode >= 0 && validSearchInput((unsigned char)charcode))
|
||||||
|
{
|
||||||
|
// Standard character
|
||||||
|
search_string += char(charcode);
|
||||||
|
filterDisplay();
|
||||||
|
centerSelection();
|
||||||
|
}
|
||||||
|
else if (last_token == interface_key::STRING_A000)
|
||||||
|
{
|
||||||
|
// Backspace
|
||||||
|
if (search_string.length() > 0)
|
||||||
|
{
|
||||||
|
search_string.erase(search_string.length()-1);
|
||||||
|
filterDisplay();
|
||||||
|
centerSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setHighlightByMouse()
|
||||||
|
{
|
||||||
|
if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 &&
|
||||||
|
gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width)
|
||||||
|
{
|
||||||
|
int new_index = display_start_offset + gps->mouse_y - 3;
|
||||||
|
if (new_index < display_list.size())
|
||||||
|
{
|
||||||
|
setHighlight(new_index);
|
||||||
|
feed_mouse_set_highlight = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
enabler->mouse_lbut = enabler->mouse_rbut = 0;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sort(bool force_sort = false)
|
||||||
|
{
|
||||||
|
if (force_sort || list.size() < 100)
|
||||||
|
std::sort(list.begin(), list.end(), sort_fn);
|
||||||
|
|
||||||
|
filterDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTitle(const string t)
|
||||||
|
{
|
||||||
|
title = t;
|
||||||
|
if (title.length() > max_item_width)
|
||||||
|
max_item_width = title.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getDisplayedListSize()
|
||||||
|
{
|
||||||
|
return display_list.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void clear_fn(ListEntry<T> &e) { e.selected = false; }
|
||||||
|
static bool sort_fn(ListEntry<T> const& a, ListEntry<T> const& b) { return a.text.compare(b.text) < 0; }
|
||||||
|
|
||||||
|
vector<ListEntry<T>> list;
|
||||||
|
vector<ListEntry<T>*> display_list;
|
||||||
|
string search_string;
|
||||||
|
string title;
|
||||||
|
int display_max_rows;
|
||||||
|
int max_item_width;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,66 @@
|
|||||||
|
#include <vector>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <stack>
|
||||||
|
#include <string>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "Core.h"
|
||||||
|
#include "Console.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
#include "modules/Gui.h"
|
||||||
|
#include "modules/Screen.h"
|
||||||
|
#include "VTableInterpose.h"
|
||||||
|
#include "DFHackVersion.h"
|
||||||
|
|
||||||
|
#include "df/graphic.h"
|
||||||
|
#include "df/viewscreen_titlest.h"
|
||||||
|
#include "uicommon.h"
|
||||||
|
|
||||||
|
using std::vector;
|
||||||
|
using std::string;
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("title-version");
|
||||||
|
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
||||||
|
REQUIRE_GLOBAL(gps);
|
||||||
|
|
||||||
|
struct title_version_hook : df::viewscreen_titlest {
|
||||||
|
typedef df::viewscreen_titlest interpose_base;
|
||||||
|
|
||||||
|
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
||||||
|
{
|
||||||
|
INTERPOSE_NEXT(render)();
|
||||||
|
int x = 0, y = 0;
|
||||||
|
OutputString(COLOR_WHITE, x, y, string("DFHack ") + DFHACK_VERSION);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IMPLEMENT_VMETHOD_INTERPOSE(title_version_hook, render);
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
|
||||||
|
{
|
||||||
|
if (!gps)
|
||||||
|
return CR_FAILURE;
|
||||||
|
|
||||||
|
if (enable != is_enabled)
|
||||||
|
{
|
||||||
|
if (!INTERPOSE_HOOK(title_version_hook, render).apply(enable))
|
||||||
|
return CR_FAILURE;
|
||||||
|
|
||||||
|
is_enabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init (color_ostream &out, vector<PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown (color_ostream &out)
|
||||||
|
{
|
||||||
|
INTERPOSE_HOOK(title_version_hook, render).remove();
|
||||||
|
return CR_OK;
|
||||||
|
}
|
Loading…
Reference in New Issue