Merge branch 'dwarfmonitor-widgets' into develop

develop
lethosor 2015-06-15 12:55:26 -04:00
commit 6fb6645ed5
8 changed files with 344 additions and 126 deletions

@ -174,8 +174,6 @@ IF(BUILD_LIBRARY)
install(DIRECTORY images DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(DIRECTORY images DESTINATION ${DFHACK_USERDOC_DESTINATION})
endif() endif()
install(DIRECTORY dfhack-config DESTINATION .)
#build the plugins #build the plugins
IF(BUILD_PLUGINS) IF(BUILD_PLUGINS)
add_subdirectory (plugins) add_subdirectory (plugins)

@ -27,8 +27,8 @@ DFHack Future
workflow: Fixed some issues with stuck jobs workflow: Fixed some issues with stuck jobs
- Note: Existing stuck jobs must be cancelled and re-added - Note: Existing stuck jobs must be cancelled and re-added
Misc Improvements Misc Improvements
dwarfmonitor date format can be modified (see Readme) dwarfmonitor widgets' positions, formats, etc. are now customizable (see Readme)
dwarfmonitor weather display can be configured separately dwarfmonitor weather display now separated from the date display
gui/gm-editor: Pointers can now be displaced gui/gm-editor: Pointers can now be displaced
"keybinding list" accepts a context "keybinding list" accepts a context
nyan: Can now be stopped with dfhack-run nyan: Can now be stopped with dfhack-run

@ -1702,17 +1702,45 @@ Options:
``dwarfmonitor reload``: ``dwarfmonitor reload``:
Reload configuration file (``dfhack-config/dwarfmonitor.json``) Reload configuration file (``dfhack-config/dwarfmonitor.json``)
Configuration options: Widget configuration:
``date_format``: The file ``dfhack-config/dwarfmonitor.json`` can be edited to control the
Date format positions and settings of all widgets displayed on the main fortress mode screen
(currently weather, misery, and date indicators). This file should contain a
Example configuration:: JSON object with the key ``widgets`` containing an array of objects - see the
included file in the ``dfhack-config`` folder for an example::
{ {
"date_format": "y-m-d" "widgets": [
{
"type": "widget type (weather, misery, or date)",
"x": X coordinate,
"y": Y coordinate
<...additional options...>
}
]
} }
X and Y coordinates begin at zero (in the upper left corner of the screen).
Negative coordinates will be treated as distances from the lower right corner,
beginning at 1 - e.g. an x coordinate of 0 is the leftmost column, while an x
coordinate of 1 is the rightmost column.
By default, the x and y coordinates given correspond to the leftmost tile of
the widget. Including an ``anchor`` option set to ``right`` will cause the
rightmost tile of the widget to be located at this position instead.
The date widget supports an additional option, ``format``, which replaces the
following characters (all others, such as punctuation, are not modified):
* ``Y`` or ``y``: The current year
* ``M``: The current month, zero-padded if necessary
* ``m``: The current month, *not* zero-padded
* ``D``: The current day, zero-padded if necessary
* ``d``: The current day, *not* zero-padded
The default date format is ``Y-M-D``.
seedwatch seedwatch
--------- ---------
Watches the numbers of seeds available and enables/disables seed and plant cooking. Watches the numbers of seeds available and enables/disables seed and plant cooking.

@ -1,3 +1,21 @@
{ {
"date_format": "y-m-d" "widgets": [
{
"type": "weather",
"x": 1,
"y": -1
},
{
"type": "date",
"x": -30,
"y": 0,
"format": "Y-M-D"
},
{
"type": "misery",
"x": -2,
"y": -1,
"anchor": "right"
}
]
} }

@ -36,7 +36,7 @@ function decode_file(path, ...)
if not f then if not f then
error('Could not open ' .. path) error('Could not open ' .. path)
end end
local raw = f:read('*all') local contents = f:read('*all')
f:close() f:close()
return decode(contents, ...) return decode(contents, ...)
end end

@ -112,7 +112,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(digFlood digFlood.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp)
add_subdirectory(diggingInvaders) add_subdirectory(diggingInvaders)
DFHACK_PLUGIN(drybuckets drybuckets.cpp) DFHACK_PLUGIN(drybuckets drybuckets.cpp)
DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES jsonxx) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES jsonxx lua)
DFHACK_PLUGIN(embark-tools embark-tools.cpp) DFHACK_PLUGIN(embark-tools embark-tools.cpp)
DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(fastdwarf fastdwarf.cpp) DFHACK_PLUGIN(fastdwarf fastdwarf.cpp)

@ -10,6 +10,8 @@
#include "df/misc_trait_type.h" #include "df/misc_trait_type.h"
#include "df/unit_misc_trait.h" #include "df/unit_misc_trait.h"
#include "LuaTools.h"
#include "LuaWrapper.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/Units.h" #include "modules/Units.h"
#include "modules/Translation.h" #include "modules/Translation.h"
@ -96,6 +98,8 @@ static color_value monitor_colors[] =
static int get_happiness_cat(df::unit *unit) static int get_happiness_cat(df::unit *unit)
{ {
if (!unit || !unit->status.current_soul)
return 3;
int stress = unit->status.current_soul->personality.stress_level; int stress = unit->status.current_soul->personality.stress_level;
if (stress >= 500000) if (stress >= 500000)
return 0; return 0;
@ -150,6 +154,113 @@ static void move_cursor(df::coord &pos)
static void open_stats_srceen(); static void open_stats_srceen();
namespace dm_lua {
static color_ostream_proxy *out;
typedef int(*initializer)(lua_State*);
int no_args (lua_State *L) { return 0; }
void cleanup()
{
if (out)
{
delete out;
out = NULL;
}
}
bool init_call (lua_State *L, const char *func)
{
if (!out)
out = new color_ostream_proxy(Core::getInstance().getConsole());
return Lua::PushModulePublic(*out, L, "plugins.dwarfmonitor", func);
}
bool safe_call (lua_State *L, int nargs)
{
return Lua::SafeCall(*out, L, nargs, 0);
}
bool call (const char *func, initializer init = no_args)
{
auto L = Lua::Core::State;
Lua::StackUnwinder top(L);
if (!init_call(L, func))
return false;
int nargs = init(L);
return safe_call(L, nargs);
}
template <typename KeyType, typename ValueType>
void table_set (lua_State *L, KeyType k, ValueType v)
{
Lua::Push(L, k);
Lua::Push(L, v);
lua_settable(L, -3);
}
namespace api {
int monitor_state (lua_State *L)
{
std::string type = luaL_checkstring(L, 1);
if (type == "weather")
lua_pushboolean(L, monitor_weather);
else if (type == "misery")
lua_pushboolean(L, monitor_misery);
else if (type == "date")
lua_pushboolean(L, monitor_date);
else
lua_pushnil(L);
return 1;
}
int get_weather_counts (lua_State *L)
{
#define WEATHER_TYPES WTYPE(clear, None); WTYPE(rain, Rain); WTYPE(snow, Snow);
#define WTYPE(type, name) int type = 0;
WEATHER_TYPES
#undef WTYPE
int i, j;
for (i = 0; i < 5; ++i)
{
for (j = 0; j < 5; ++j)
{
switch ((*current_weather)[i][j])
{
#define WTYPE(type, name) case weather_type::name: type++; break;
WEATHER_TYPES
#undef WTYPE
}
}
}
lua_newtable(L);
#define WTYPE(type, name) dm_lua::table_set(L, #type, type);
WEATHER_TYPES
#undef WTYPE
#undef WEATHER_TYPES
return 1;
}
int get_misery_data (lua_State *L)
{
lua_newtable(L);
for (int i = 0; i < 7; i++)
{
Lua::Push(L, i);
lua_newtable(L);
dm_lua::table_set(L, "value", misery[i]);
dm_lua::table_set(L, "color", monitor_colors[i]);
dm_lua::table_set(L, "last", (i == 6));
lua_settable(L, -3);
}
return 1;
}
}
}
#define DM_LUA_FUNC(name) { #name, df::wrap_function(dm_lua::api::name, true) }
#define DM_LUA_CMD(name) { #name, dm_lua::api::name }
DFHACK_PLUGIN_LUA_COMMANDS {
DM_LUA_CMD(monitor_state),
DM_LUA_CMD(get_weather_counts),
DM_LUA_CMD(get_misery_data),
DFHACK_LUA_END
};
#define JOB_IDLE -1 #define JOB_IDLE -1
#define JOB_UNKNOWN -2 #define JOB_UNKNOWN -2
#define JOB_MILITARY -3 #define JOB_MILITARY -3
@ -1699,97 +1810,7 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest
if (Maps::IsValid()) if (Maps::IsValid())
{ {
if (monitor_misery) dm_lua::call("render_all");
{
string entries[7];
size_t length = 9;
for (int i = 0; i < 7; i++)
{
entries[i] = int_to_string(misery[i]);
length += entries[i].length();
}
int x = gps->dimx - length;
int y = gps->dimy - 1;
OutputString(COLOR_WHITE, x, y, "H:");
for (int i = 0; i < 7; i++)
{
OutputString(monitor_colors[i], x, y, entries[i]);
if (i < 6)
OutputString(COLOR_WHITE, x, y, "/");
}
}
if (monitor_date)
{
int x = gps->dimx - 30;
int y = 0;
ostringstream date_str;
auto month = World::ReadCurrentMonth() + 1;
auto day = World::ReadCurrentDay();
date_str << "Date:";
for (size_t i = 0; i < dwarfmonitor_config.date_format.size(); i++)
{
char c = dwarfmonitor_config.date_format[i];
switch (c)
{
case 'Y':
case 'y':
date_str << World::ReadCurrentYear();
break;
case 'M':
case 'm':
date_str << ((month < 10) ? "0" : "") << month;
break;
case 'D':
case 'd':
date_str << ((day < 10) ? "0" : "") << day;
break;
default:
date_str << c;
break;
}
}
OutputString(COLOR_GREY, x, y, date_str.str());
}
if (monitor_weather)
{
int x = 1;
int y = gps->dimy - 1;
bool rain = false,
snow = false;
if (current_weather)
{
int i, j;
for (i = 0; i < 5; ++i)
{
for (j = 0; j < 5; ++j)
{
switch ((*current_weather)[i][j])
{
case weather_type::Rain:
rain = true;
break;
case weather_type::Snow:
snow = true;
break;
default:
break;
}
}
}
}
if (rain)
{
OutputString(COLOR_LIGHTBLUE, x, y, "Rain");
++x;
}
if (snow)
OutputString(COLOR_WHITE, x, y, "Snow");
}
} }
} }
}; };
@ -1811,17 +1832,17 @@ static bool set_monitoring_mode(const string &mode, const bool &state)
if (!monitor_jobs) if (!monitor_jobs)
reset(); reset();
} }
else if (mode == "misery" || mode == "all") if (mode == "misery" || mode == "all")
{ {
mode_recognized = true; mode_recognized = true;
monitor_misery = state; monitor_misery = state;
} }
else if (mode == "date" || mode == "all") if (mode == "date" || mode == "all")
{ {
mode_recognized = true; mode_recognized = true;
monitor_date = state; monitor_date = state;
} }
else if (mode == "weather" || mode == "all") if (mode == "weather" || mode == "all")
{ {
mode_recognized = true; mode_recognized = true;
monitor_weather = state; monitor_weather = state;
@ -1830,11 +1851,15 @@ static bool set_monitoring_mode(const string &mode, const bool &state)
return mode_recognized; return mode_recognized;
} }
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) static bool load_config()
{ {
if (!gps) return dm_lua::call("load_config");
return CR_FAILURE; }
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
if (enable)
load_config();
if (is_enabled != enable) if (is_enabled != enable)
{ {
if (!INTERPOSE_HOOK(dwarf_monitor_hook, feed).apply(enable) || if (!INTERPOSE_HOOK(dwarf_monitor_hook, feed).apply(enable) ||
@ -1848,19 +1873,6 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
return CR_OK; return CR_OK;
} }
static bool load_config (color_ostream &out)
{
jsonxx::Object o;
std::ifstream infile("dfhack-config/dwarfmonitor.json");
if (infile.good())
{
std::string contents((std::istreambuf_iterator<char>(infile)), (std::istreambuf_iterator<char>()));
if (!o.parse(contents))
out.printerr("dwarfmonitor: invalid JSON\n");
}
dwarfmonitor_config.date_format = o.get<jsonxx::String>("date_format", "y-m-d");
}
static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & parameters) static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & parameters)
{ {
bool show_help = false; bool show_help = false;
@ -1913,7 +1925,7 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & par
} }
else if (cmd == 'r' || cmd == 'R') else if (cmd == 'r' || cmd == 'R')
{ {
load_config(out); load_config();
} }
else else
{ {
@ -1929,7 +1941,6 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & par
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands)
{ {
load_config(out);
activity_labels[JOB_IDLE] = "Idle"; activity_labels[JOB_IDLE] = "Idle";
activity_labels[JOB_MILITARY] = "Military Duty"; activity_labels[JOB_MILITARY] = "Military Duty";
activity_labels[JOB_LEISURE] = "Leisure"; activity_labels[JOB_LEISURE] = "Leisure";
@ -1962,12 +1973,18 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector <Plugin
"dwarfmonitor prefs\n" "dwarfmonitor prefs\n"
" Show dwarf preferences summary\n\n" " Show dwarf preferences summary\n\n"
"dwarfmonitor reload\n" "dwarfmonitor reload\n"
" Reload configuration file (hack/config/dwarfmonitor.json)\n" " Reload configuration file (dfhack-config/dwarfmonitor.json)\n"
)); ));
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_shutdown(color_ostream &out)
{
dm_lua::cleanup();
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{ {
switch (event) { switch (event) {

@ -0,0 +1,157 @@
local _ENV = mkmodule('plugins.dwarfmonitor')
local gps = df.global.gps
local gui = require 'gui'
config = {}
widgets = {}
function dmerror(desc)
qerror('dwarfmonitor: ' .. tostring(desc))
end
Widget = defclass(Widget)
function Widget:init(opts)
self.opts = opts
end
function Widget:get_pos()
local x = self.opts.x >= 0 and self.opts.x or gps.dimx + self.opts.x
local y = self.opts.y >= 0 and self.opts.y or gps.dimy + self.opts.y
if self.opts.anchor == 'right' then
x = x - (self:get_width() or 0) + 1
end
return x, y
end
function Widget:render()
if monitor_state(self.opts.type) == false then
return
end
self:update()
local x, y = self:get_pos()
local p = gui.Painter.new_xy(x, y, gps.dimx - 1, y)
self:render_body(p)
end
function Widget:update() end
function Widget:get_width() end
function Widget:render_body() end
Widget_weather = defclass(Widget_weather, Widget)
function Widget_weather:update()
self.counts = get_weather_counts()
end
function Widget_weather:get_width()
if self.counts.rain > 0 then
if self.counts.snow > 0 then
return 9
end
return 4
elseif self.counts.snow > 0 then
return 4
end
return 0
end
function Widget_weather:render_body(p)
if self.counts.rain > 0 then
p:string('Rain', COLOR_LIGHTBLUE):advance(1)
end
if self.counts.snow > 0 then
p:string('Snow', COLOR_WHITE)
end
end
Widget_date = defclass(Widget_date, Widget)
Widget_date.ATTRS = {
output = ''
}
function Widget_date:update()
if not self.opts.format then
self.opts.format = 'Y-M-D'
end
local year = dfhack.world.ReadCurrentYear()
local month = dfhack.world.ReadCurrentMonth() + 1
local day = dfhack.world.ReadCurrentDay()
self.output = 'Date:'
for i = 1, #self.opts.format do
local c = self.opts.format:sub(i, i)
if c == 'y' or c == 'Y' then
self.output = self.output .. year
elseif c == 'm' or c == 'M' then
if c == 'M' and month < 10 then
self.output = self.output .. '0'
end
self.output = self.output .. month
elseif c == 'd' or c == 'D' then
if c == 'D' and day < 10 then
self.output = self.output .. '0'
end
self.output = self.output .. day
else
self.output = self.output .. c
end
end
end
function Widget_date:get_width()
return #self.output
end
function Widget_date:render_body(p)
p:string(self.output, COLOR_GREY)
end
Widget_misery = defclass(Widget_misery, Widget)
function Widget_misery:update()
self.data = get_misery_data()
end
function Widget_misery:get_width()
local w = 2 + 6
for k, v in pairs(self.data) do
w = w + #tostring(v.value)
end
return w
end
function Widget_misery:render_body(p)
p:string("H:", COLOR_WHITE)
for i = 0, 6 do
local v = self.data[i]
p:string(tostring(v.value), v.color)
if not v.last then
p:string("/", COLOR_WHITE)
end
end
end
function render_all()
for _, w in pairs(widgets) do
w:render()
end
end
function load_config()
config = require('json').decode_file('dfhack-config/dwarfmonitor.json')
if not config.widgets then
dmerror('No widgets enabled')
end
if type(config.widgets) ~= 'table' then
dmerror('"widgets" is not a table')
end
widgets = {}
for _, opts in pairs(config.widgets) do
if type(opts) ~= 'table' then qerror('"widgets" is not an array') end
if not opts.type then dmerror('Widget missing type field') end
local cls = _ENV['Widget_' .. opts.type]
if not cls then
dmerror('Invalid widget type: ' .. opts.type)
end
table.insert(widgets, cls(opts))
end
end
return _ENV