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})
endif()
install(DIRECTORY dfhack-config DESTINATION .)
#build the plugins
IF(BUILD_PLUGINS)
add_subdirectory (plugins)

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

@ -1702,17 +1702,45 @@ Options:
``dwarfmonitor reload``:
Reload configuration file (``dfhack-config/dwarfmonitor.json``)
Configuration options:
Widget configuration:
``date_format``:
Date format
Example configuration::
The file ``dfhack-config/dwarfmonitor.json`` can be edited to control the
positions and settings of all widgets displayed on the main fortress mode screen
(currently weather, misery, and date indicators). This file should contain a
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
---------
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
error('Could not open ' .. path)
end
local raw = f:read('*all')
local contents = f:read('*all')
f:close()
return decode(contents, ...)
end

@ -112,7 +112,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(digFlood digFlood.cpp)
add_subdirectory(diggingInvaders)
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(eventful eventful.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(fastdwarf fastdwarf.cpp)

@ -10,6 +10,8 @@
#include "df/misc_trait_type.h"
#include "df/unit_misc_trait.h"
#include "LuaTools.h"
#include "LuaWrapper.h"
#include "modules/Gui.h"
#include "modules/Units.h"
#include "modules/Translation.h"
@ -96,6 +98,8 @@ static color_value monitor_colors[] =
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;
if (stress >= 500000)
return 0;
@ -150,6 +154,113 @@ static void move_cursor(df::coord &pos)
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_UNKNOWN -2
#define JOB_MILITARY -3
@ -1699,97 +1810,7 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest
if (Maps::IsValid())
{
if (monitor_misery)
{
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");
}
dm_lua::call("render_all");
}
}
};
@ -1811,17 +1832,17 @@ static bool set_monitoring_mode(const string &mode, const bool &state)
if (!monitor_jobs)
reset();
}
else if (mode == "misery" || mode == "all")
if (mode == "misery" || mode == "all")
{
mode_recognized = true;
monitor_misery = state;
}
else if (mode == "date" || mode == "all")
if (mode == "date" || mode == "all")
{
mode_recognized = true;
monitor_date = state;
}
else if (mode == "weather" || mode == "all")
if (mode == "weather" || mode == "all")
{
mode_recognized = true;
monitor_weather = state;
@ -1830,11 +1851,15 @@ static bool set_monitoring_mode(const string &mode, const bool &state)
return mode_recognized;
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
static bool load_config()
{
if (!gps)
return CR_FAILURE;
return dm_lua::call("load_config");
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
if (enable)
load_config();
if (is_enabled != 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;
}
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)
{
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')
{
load_config(out);
load_config();
}
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)
{
load_config(out);
activity_labels[JOB_IDLE] = "Idle";
activity_labels[JOB_MILITARY] = "Military Duty";
activity_labels[JOB_LEISURE] = "Leisure";
@ -1962,12 +1973,18 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector <Plugin
"dwarfmonitor prefs\n"
" Show dwarf preferences summary\n\n"
"dwarfmonitor reload\n"
" Reload configuration file (hack/config/dwarfmonitor.json)\n"
" Reload configuration file (dfhack-config/dwarfmonitor.json)\n"
));
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)
{
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