Merge branch 'develop' of https://github.com/DFHack/dfhack into develop

develop
Japa 2015-06-19 23:30:47 +05:30
commit 4182a30cc5
20 changed files with 817 additions and 386 deletions

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

@ -27,11 +27,15 @@ 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
dwarfmonitor: New mouse cursor widget
gui/gm-editor: Pointers can now be displaced
gui/hack-wish: renamed to gui/create-item
"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
quicksave: Restricted to fortress mode quicksave: Restricted to fortress mode
search: Now supports the noble suggestion screen (e.g. suggesting a baron)
Removed Removed
DFHack 0.40.24-r3 DFHack 0.40.24-r3

@ -1693,8 +1693,10 @@ Options:
``dwarfmonitor enable <mode>``: ``dwarfmonitor enable <mode>``:
Start monitoring ``mode``. ``mode`` can be "work", "misery", "weather", or "all". Start monitoring ``mode``. ``mode`` can be "work", "misery", "weather", or "all".
This will enable all corresponding widgets, if applicable.
``dwarfmonitor disable <mode>``: ``dwarfmonitor disable <mode>``:
Stop monitoring ``mode`` (see above) Stop monitoring ``mode`` (see above)
This will disable all corresponding widgets, if applicable.
``dwarfmonitor stats``: ``dwarfmonitor stats``:
Show statistics summary Show statistics summary
``dwarfmonitor prefs``: ``dwarfmonitor prefs``:
@ -1702,17 +1704,65 @@ 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 following types of widgets (defined in ``hack/lua/plugins/dwarfmonitor.lua``)
Date format can be displayed on the main fortress mode screen:
Example configuration:: * ``date``: Shows the in-game date
* ``misery``: Shows overall happiness levels of all dwarves
* ``weather``: Shows current weather (rain/snow)
* ``cursor``: Shows the current mouse cursor position
The file ``dfhack-config/dwarfmonitor.json`` can be edited to control the
positions and settings of all widgets displayed. 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::
{
"widgets": [
{ {
"date_format": "y-m-d" "type": "widget type (weather, misery, etc.)",
"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.
Some widgets support additional options:
* ``date`` widget:
* ``format``: specifies the format of the date. The following characters
are replaced (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``.
* ``cursor`` widget:
* ``format``: Specifies the format. ``X``, ``x``, ``Y``, and ``y`` are
replaced with the corresponding cursor cordinates, while all other
characters are unmodified.
* ``show_invalid``: If set to ``true``, the mouse coordinates will both be
displayed as ``-1`` when the cursor is outside of the DF window; otherwise,
nothing will be displayed.
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.
@ -2226,7 +2276,7 @@ gui/*
Scripts that implement dialogs inserted into the main game window are put in this Scripts that implement dialogs inserted into the main game window are put in this
directory. directory.
* gui/hack-wish * gui/create-item
A graphical interface for creating items. A graphical interface for creating items.

@ -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"
}
]
} }

@ -2,11 +2,14 @@
# regenerate documentation after editing the .rst files. Requires python and docutils. # regenerate documentation after editing the .rst files. Requires python and docutils.
force= force=
reset=
while [ $# -gt 0 ] while [ $# -gt 0 ]
do do
case "$1" in case "$1" in
--force) force=1 --force) force=1
;; ;;
--reset) reset=1
;;
esac esac
shift shift
done done
@ -32,7 +35,9 @@ cd `dirname $0`
status=0 status=0
function process() { function process() {
if [ "$1" -nt "$2" ] || [ -n "$force" ]; then if [ -n "$reset" ]; then
git checkout -- "$2"
elif [ "$1" -nt "$2" ] || [ -n "$force" ]; then
echo -n "Updating $2... " echo -n "Updating $2... "
if "$rst2html" --no-generator --no-datestamp "$1" "$2"; then if "$rst2html" --no-generator --no-datestamp "$1" "$2"; then
echo "Done" echo "Done"

@ -1297,6 +1297,36 @@ bool Core::Init()
virtual_identity::Init(this); virtual_identity::Init(this);
init_screen_module(this); init_screen_module(this);
// copy over default config files if necessary
std::vector<std::string> config_files;
std::vector<std::string> default_config_files;
if (Filesystem::listdir("dfhack-config", config_files) != 0)
con.printerr("Failed to list directory: dfhack-config");
else if (Filesystem::listdir("dfhack-config/default", default_config_files) != 0)
con.printerr("Failed to list directory: dfhack-config/default");
else
{
for (auto it = default_config_files.begin(); it != default_config_files.end(); ++it)
{
std::string filename = *it;
if (std::find(config_files.begin(), config_files.end(), filename) == config_files.end())
{
std::string src_file = std::string("dfhack-config/default/") + filename;
std::string dest_file = std::string("dfhack-config/") + filename;
std::ifstream src(src_file, std::ios::binary);
std::ofstream dest(dest_file, std::ios::binary);
if (!src.good() || !dest.good())
{
con.printerr("Copy failed: %s\n", filename.c_str());
continue;
}
dest << src.rdbuf();
src.close();
dest.close();
}
}
}
// initialize common lua context // initialize common lua context
Lua::Core::Init(con); Lua::Core::Init(con);

@ -451,6 +451,8 @@ local valid_script_flags = {
error = 'Cannot be used as a module' error = 'Cannot be used as a module'
}, },
module_strict = {required = false}, module_strict = {required = false},
alias = {required = false},
alias_count = {required = false},
} }
function dfhack.run_script(name,...) function dfhack.run_script(name,...)
@ -497,6 +499,13 @@ function dfhack.run_script_with_env(envVars, name, flags, ...)
scripts[file] = Script(file) scripts[file] = Script(file)
end end
local script_flags = scripts[file]:get_flags() local script_flags = scripts[file]:get_flags()
if script_flags.alias then
flags.alias_count = (flags.alias_count or 0) + 1
if flags.alias_count > 10 then
error('Too many script aliases: ' .. flags.alias_count)
end
return dfhack.run_script_with_env(envVars, script_flags.alias, flags, ...)
end
for flag, value in pairs(flags) do for flag, value in pairs(flags) do
if value then if value then
local v = valid_script_flags[flag] local v = valid_script_flags[flag]

@ -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

@ -1 +1 @@
Subproject commit 8cd1994027d023f32e7eb6e424232fc30d37fb90 Subproject commit fa77e361c48ec75accbb0210fdb4f243ade1eb13

@ -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)

@ -157,10 +157,14 @@ static const char *state_names[] = {
"BUSY", "BUSY",
"MILITARY", "MILITARY",
"CHILD", "CHILD",
"OTHER", "OTHER"
}; };
// List of possible activites of a dwarf that will be further narrowed to states // List of possible activites of a dwarf that will be further narrowed to states
// IDLE - Specifically waiting to be assigned a task (No Job)
// BUSY - Performing a toggleable labor, or a support action for that labor.
// OTHER - Doing something else
static const dwarf_state dwarf_states[] = { static const dwarf_state dwarf_states[] = {
BUSY /* CarveFortification */, BUSY /* CarveFortification */,
BUSY /* DetailWall */, BUSY /* DetailWall */,
@ -187,17 +191,17 @@ static const dwarf_state dwarf_states[] = {
BUSY /* CollectSand */, BUSY /* CollectSand */,
BUSY /* Fish */, BUSY /* Fish */,
BUSY /* Hunt */, BUSY /* Hunt */,
OTHER /* HuntVermin */, BUSY /* HuntVermin */,
BUSY /* Kidnap */, OTHER /* Kidnap */,
BUSY /* BeatCriminal */, OTHER /* BeatCriminal */,
BUSY /* StartingFistFight */, OTHER /* StartingFistFight */,
BUSY /* CollectTaxes */, OTHER /* CollectTaxes */,
BUSY /* GuardTaxCollector */, OTHER /* GuardTaxCollector */,
BUSY /* CatchLiveLandAnimal */, BUSY /* CatchLiveLandAnimal */,
BUSY /* CatchLiveFish */, BUSY /* CatchLiveFish */,
BUSY /* ReturnKill */, OTHER /* ReturnKill */,
BUSY /* CheckChest */, OTHER /* CheckChest */,
BUSY /* StoreOwnedItem */, OTHER /* StoreOwnedItem */,
BUSY /* PlaceItemInTomb */, BUSY /* PlaceItemInTomb */,
BUSY /* StoreItemInStockpile */, BUSY /* StoreItemInStockpile */,
BUSY /* StoreItemInBag */, BUSY /* StoreItemInBag */,
@ -208,12 +212,12 @@ static const dwarf_state dwarf_states[] = {
BUSY /* StoreArmor */, BUSY /* StoreArmor */,
BUSY /* StoreItemInBarrel */, BUSY /* StoreItemInBarrel */,
BUSY /* StoreItemInBin */, BUSY /* StoreItemInBin */,
BUSY /* SeekArtifact */, OTHER /* SeekArtifact */,
BUSY /* SeekInfant */, OTHER /* SeekInfant */,
OTHER /* AttendParty */, OTHER /* AttendParty */,
OTHER /* GoShopping */, OTHER /* GoShopping */,
OTHER /* GoShopping2 */, OTHER /* GoShopping2 */,
BUSY /* Clean */, OTHER /* Clean */,
OTHER /* Rest */, OTHER /* Rest */,
BUSY /* PickupEquipment */, BUSY /* PickupEquipment */,
BUSY /* DumpItem */, BUSY /* DumpItem */,
@ -309,7 +313,7 @@ static const dwarf_state dwarf_states[] = {
BUSY /* LoadStoneTrap */, BUSY /* LoadStoneTrap */,
BUSY /* LoadWeaponTrap */, BUSY /* LoadWeaponTrap */,
BUSY /* CleanTrap */, BUSY /* CleanTrap */,
BUSY /* CastSpell */, OTHER /* CastSpell */,
BUSY /* LinkBuildingToTrigger */, BUSY /* LinkBuildingToTrigger */,
BUSY /* PullLever */, BUSY /* PullLever */,
BUSY /* BrewDrink */, BUSY /* BrewDrink */,
@ -370,7 +374,7 @@ static const dwarf_state dwarf_states[] = {
BUSY /* ConstructSplint */, BUSY /* ConstructSplint */,
BUSY /* ConstructCrutch */, BUSY /* ConstructCrutch */,
BUSY /* ConstructTractionBench */, BUSY /* ConstructTractionBench */,
BUSY /* CleanSelf */, OTHER /* CleanSelf */,
BUSY /* BringCrutch */, BUSY /* BringCrutch */,
BUSY /* ApplyCast */, BUSY /* ApplyCast */,
BUSY /* CustomReaction */, BUSY /* CustomReaction */,
@ -426,6 +430,7 @@ struct labor_info
// Return the labor_mode associated with this labor // Return the labor_mode associated with this labor
labor_mode mode() { return (labor_mode) config.ival(0); } labor_mode mode() { return (labor_mode) config.ival(0); }
// Set the labor_mode associated with this labor // Set the labor_mode associated with this labor
void set_mode(labor_mode mode) { config.ival(0) = mode; } void set_mode(labor_mode mode) { config.ival(0) = mode; }
}; };
@ -458,7 +463,7 @@ static const struct labor_default default_labor_infos[] = {
/* BONE_SETTING */ {ALLOW, 0}, /* BONE_SETTING */ {ALLOW, 0},
/* SUTURING */ {ALLOW, 0}, /* SUTURING */ {ALLOW, 0},
/* DRESSING_WOUNDS */ {ALLOW, 0}, /* DRESSING_WOUNDS */ {ALLOW, 0},
/* FEED_WATER_CIVILIANS */ {ALLOW, 0}, /* FEED_WATER_CIVILIANS */ {HAULERS, 0}, // This could also be ALLOW
/* RECOVER_WOUNDED */ {HAULERS, 0}, /* RECOVER_WOUNDED */ {HAULERS, 0},
/* BUTCHER */ {ALLOW, 0}, /* BUTCHER */ {ALLOW, 0},
/* TRAPPER */ {ALLOW, 0}, /* TRAPPER */ {ALLOW, 0},
@ -534,6 +539,7 @@ struct dwarf_info_t
{ {
// Current simplified employment status of dwarf // Current simplified employment status of dwarf
dwarf_state state; dwarf_state state;
// Set to true if for whatever reason we are exempting this dwarf // Set to true if for whatever reason we are exempting this dwarf
// from hauling // from hauling
bool haul_exempt; bool haul_exempt;
@ -704,6 +710,8 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
" List current status of all labors.\n" " List current status of all labors.\n"
" autohauler status\n" " autohauler status\n"
" Show basic status information.\n" " Show basic status information.\n"
" autohauler debug\n"
" In the next cycle, will output the state of every dwarf.\n"
"Function:\n" "Function:\n"
" When enabled, autohauler periodically checks your dwarves and assigns\n" " When enabled, autohauler periodically checks your dwarves and assigns\n"
" hauling jobs to idle dwarves while removing them from busy dwarves.\n" " hauling jobs to idle dwarves while removing them from busy dwarves.\n"
@ -818,6 +826,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
/* Before determining how to handle employment status, handle /* Before determining how to handle employment status, handle
* hauling exemptions first */ * hauling exemptions first */
// Default deny condition of on break for later else-if series
bool is_migrant = false;
// Scan every labor. If a labor that disallows hauling is present // Scan every labor. If a labor that disallows hauling is present
// for the dwarf, the dwarf is hauling exempt // for the dwarf, the dwarf is hauling exempt
FOR_ENUM_ITEMS(unit_labor, labor) FOR_ENUM_ITEMS(unit_labor, labor)
@ -836,8 +847,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// to try to find real jobs first // to try to find real jobs first
for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++) for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++)
{ {
if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) if ((*p)->id == misc_trait_type::Migrant)
dwarf_info[dwarf].haul_exempt = true; is_migrant = true;
} }
/* Now determine a dwarf's employment status and decide whether /* Now determine a dwarf's employment status and decide whether
@ -852,17 +863,24 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// Account for any hauling exemptions here // Account for any hauling exemptions here
else if (dwarf_info[dwarf].haul_exempt) else if (dwarf_info[dwarf].haul_exempt)
{ {
dwarf_info[dwarf].state = OTHER; dwarf_info[dwarf].state = BUSY;
} }
// Don't give hauling jobs to the military either // Account for the military
else if (ENUM_ATTR(profession, military, dwarfs[dwarf]->profession)) else if (ENUM_ATTR(profession, military, dwarfs[dwarf]->profession))
dwarf_info[dwarf].state = MILITARY; dwarf_info[dwarf].state = MILITARY;
// Account for dwarves on break or migrants
// DF leaves the OnBreak trait type on some dwarves while they're not actually on break
// Since they have no current job, they will default to IDLE
else if (is_migrant)
// Dwarf is unemployed with null job // Dwarf is unemployed with null job
{
dwarf_info[dwarf].state = OTHER;
}
else if (dwarfs[dwarf]->job.current_job == NULL) else if (dwarfs[dwarf]->job.current_job == NULL)
{ {
dwarf_info[dwarf].state = IDLE; dwarf_info[dwarf].state = IDLE;
} }
// If it gets to this point the dwarf is employed // If it gets to this point we look at the task and assign either BUSY or OTHER
else else
{ {
int job = dwarfs[dwarf]->job.current_job->job_type; int job = dwarfs[dwarf]->job.current_job->job_type;
@ -870,17 +888,25 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
dwarf_info[dwarf].state = dwarf_states[job]; dwarf_info[dwarf].state = dwarf_states[job];
else else
{ {
// Warn the console that the dwarf has an unregistered labor, default to OTHER // Warn the console that the dwarf has an unregistered labor, default to BUSY
out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job); out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job);
dwarf_info[dwarf].state = OTHER; dwarf_info[dwarf].state = BUSY;
} }
} }
// Debug: Output dwarf job and state data
if(print_debug)
out.print("Dwarf %i %s State: %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(),
dwarf_info[dwarf].state);
// Increment corresponding labor in default_labor_infos struct // Increment corresponding labor in default_labor_infos struct
state_count[dwarf_info[dwarf].state]++; state_count[dwarf_info[dwarf].state]++;
} }
// At this point the debug if present has been completed
print_debug = false;
// This is a vector of all the labors // This is a vector of all the labors
std::vector<df::unit_labor> labors; std::vector<df::unit_labor> labors;
@ -904,31 +930,21 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// IDLE - Enable hauling // IDLE - Enable hauling
// BUSY - Disable hauling // BUSY - Disable hauling
// OTHER - Disable hauling // OTHER - Enable hauling
// MILITARY - Enable hauling
// This is a vector of potential hauler IDs // There was no reason to put potential haulers in an array. All of them are
std::vector<int> hauler_ids; // covered in the following for loop.
// Pretty much we are only considering non-military, non-child dwarves
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{
if (dwarf_info[dwarf].state == IDLE ||
dwarf_info[dwarf].state == BUSY ||
dwarf_info[dwarf].state == OTHER)
{
hauler_ids.push_back(dwarf);
}
}
// Equivalent of Java for(unit_labor : labor) // Equivalent of Java for(unit_labor : labor)
// For every labor... // For every labor...
FOR_ENUM_ITEMS(unit_labor, labor) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
// If this is a non-labor skip this for loop // If this is a non-labor continue to next item
if (labor == unit_labor::NONE) if (labor == unit_labor::NONE)
continue; continue;
// If this is not a hauling labor then skip this for loop // If this is not a hauling labor continue to next item
if (labor_infos[labor].mode() != HAULERS) if (labor_infos[labor].mode() != HAULERS)
continue; continue;
@ -936,15 +952,17 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
for(int dwarf = 0; dwarf < dwarfs.size(); dwarf++) for(int dwarf = 0; dwarf < dwarfs.size(); dwarf++)
{ {
// If the dwarf is idle, enable the hauling labor // Set hauling labors based on employment states
if(dwarf_info[dwarf].state == IDLE) if(dwarf_info[dwarf].state == IDLE) {
{
// And enable the job for the dwarf
dwarfs[dwarf]->status.labors[labor] = true; dwarfs[dwarf]->status.labors[labor] = true;
} }
// If the dwarf is busy, disable the hauling labor else if(dwarf_info[dwarf].state == MILITARY) {
if(dwarf_info[dwarf].state == BUSY || dwarf_info[dwarf].state == OTHER) dwarfs[dwarf]->status.labors[labor] = true;
{ }
else if(dwarf_info[dwarf].state == OTHER) {
dwarfs[dwarf]->status.labors[labor] = true;
}
else if(dwarf_info[dwarf].state == BUSY) {
dwarfs[dwarf]->status.labors[labor] = false; dwarfs[dwarf]->status.labors[labor] = false;
} }
// If at the end of this the dwarf has the hauling labor, increment the // If at the end of this the dwarf has the hauling labor, increment the
@ -954,6 +972,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
labor_infos[labor].active_dwarfs++; labor_infos[labor].active_dwarfs++;
} }
// CHILD ignored
} }
// Let's play a game of "find the missing bracket!" I hope this is correct. // Let's play a game of "find the missing bracket!" I hope this is correct.

@ -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,175 @@
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
Widget_cursor = defclass(Widget_cursor, Widget)
function Widget_cursor:update()
if gps.mouse_x == -1 and not self.opts.show_invalid then
self.output = ''
return
end
self.output = (self.opts.format or '(x,y)'):gsub('[xX]', gps.mouse_x):gsub('[yY]', gps.mouse_y)
end
function Widget_cursor:get_width()
return #self.output
end
function Widget_cursor:render_body(p)
p:string(self.output)
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

@ -12,10 +12,12 @@
#include "df/viewscreen_layer_militaryst.h" #include "df/viewscreen_layer_militaryst.h"
#include "df/viewscreen_layer_noblelistst.h" #include "df/viewscreen_layer_noblelistst.h"
#include "df/viewscreen_layer_workshop_profilest.h" #include "df/viewscreen_layer_workshop_profilest.h"
#include "df/viewscreen_topicmeeting_fill_land_holder_positionsst.h"
#include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_tradegoodsst.h"
#include "df/viewscreen_unitlistst.h" #include "df/viewscreen_unitlistst.h"
#include "df/viewscreen_buildinglistst.h" #include "df/viewscreen_buildinglistst.h"
#include "df/viewscreen_joblistst.h" #include "df/viewscreen_joblistst.h"
#include "df/historical_figure.h"
#include "df/interface_key.h" #include "df/interface_key.h"
#include "df/interfacest.h" #include "df/interfacest.h"
#include "df/layer_object_listst.h" #include "df/layer_object_listst.h"
@ -1746,6 +1748,48 @@ IMPLEMENT_HOOKS(df::viewscreen_dwarfmodest, room_assign_search);
// END: Room assignment search // END: Room assignment search
// //
//
// START: Noble suggestion search
//
typedef search_generic<df::viewscreen_topicmeeting_fill_land_holder_positionsst, int32_t> noble_suggest_search_base;
class noble_suggest_search : public noble_suggest_search_base
{
public:
string get_element_description (int32_t hf_id) const
{
df::historical_figure *histfig = df::historical_figure::find(hf_id);
if (!histfig)
return "";
df::unit *unit = df::unit::find(histfig->unit_id);
if (!unit)
return "";
return get_unit_description(unit);
}
void render() const
{
print_search_option(2, gps->dimy - 4);
}
vector<int32_t> *get_primary_list()
{
return &viewscreen->candidate_histfig_ids;
}
virtual int32_t *get_viewscreen_cursor()
{
return &viewscreen->cursor;
}
};
IMPLEMENT_HOOKS(df::viewscreen_topicmeeting_fill_land_holder_positionsst, noble_suggest_search);
//
// END: Noble suggestion search
//
#define SEARCH_HOOKS \ #define SEARCH_HOOKS \
HOOK_ACTION(unitlist_search_hook) \ HOOK_ACTION(unitlist_search_hook) \
HOOK_ACTION(roomlist_search_hook) \ HOOK_ACTION(roomlist_search_hook) \
@ -1760,7 +1804,8 @@ IMPLEMENT_HOOKS(df::viewscreen_dwarfmodest, room_assign_search);
HOOK_ACTION(joblist_search_hook) \ HOOK_ACTION(joblist_search_hook) \
HOOK_ACTION(burrow_search_hook) \ HOOK_ACTION(burrow_search_hook) \
HOOK_ACTION(stockpile_search_hook) \ HOOK_ACTION(stockpile_search_hook) \
HOOK_ACTION(room_assign_search_hook) HOOK_ACTION(room_assign_search_hook) \
HOOK_ACTION(noble_suggest_search_hook)
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)
{ {

@ -1,5 +1,5 @@
-- hack-wish.lua -- create-item.lua
-- Allows for script-based wishing. -- A gui-based item creation script.
-- author Putnam -- author Putnam
-- edited by expwnent -- edited by expwnent
@ -235,7 +235,7 @@ if not args.startup then
if unit then if unit then
hackWish(unit) hackWish(unit)
else else
qerror('A unit needs to be selected to use hackwish.') qerror('A unit needs to be selected to use gui/create-item.')
end end
else else
eventful.onReactionComplete.hackWishP=function(reaction,unit,input_items,input_reagents,output_items,call_native) eventful.onReactionComplete.hackWishP=function(reaction,unit,input_items,input_reagents,output_items,call_native)

@ -14,6 +14,7 @@ local keybindings={
reinterpret={key="CUSTOM_ALT_R",desc="Open selected entry as something else"}, reinterpret={key="CUSTOM_ALT_R",desc="Open selected entry as something else"},
start_filter={key="CUSTOM_S",desc="Start typing filter, Enter to finish"}, start_filter={key="CUSTOM_S",desc="Start typing filter, Enter to finish"},
help={key="HELP",desc="Show this help"}, help={key="HELP",desc="Show this help"},
displace={key="STRING_A093",desc="Open reference offseted by index"},
NOT_USED={key="SEC_SELECT",desc="Choose an enum value from a list"}, --not a binding... NOT_USED={key="SEC_SELECT",desc="Choose an enum value from a list"}, --not a binding...
} }
function getTargetFromScreens() function getTargetFromScreens()
@ -85,8 +86,9 @@ function GmEditorUi:init(args)
local mainPage=widgets.Panel{ local mainPage=widgets.Panel{
subviews={ subviews={
mainList, mainList,
widgets.Label{text={{text="<no item>",id="name"},{gap=1,text="Help",key="HELP",key_sep = '()'}}, view_id = 'lbl_current_item',frame = {l=1,t=1,yalign=0}}, widgets.Label{text={{text="<no item>",id="name"},{gap=1,text="Help",key=keybindings.help.key,key_sep = '()'}}, view_id = 'lbl_current_item',frame = {l=1,t=1,yalign=0}},
widgets.EditField{frame={l=1,t=2},active=false,on_change=self:callback('text_input'),on_submit=self:callback("enable_input",false),view_id="filter_input"}, widgets.Label{text={{text="Search",key=keybindings.start_filter.key,key_sep = '()'},{text=":"}},frame={l=1,t=2}},
widgets.EditField{frame={l=12,t=2},active=false,on_change=self:callback('text_input'),on_submit=self:callback("enable_input",false),view_id="filter_input"},
--widgets.Label{text="BLAH2"} --widgets.Label{text="BLAH2"}
} }
,view_id='page_main'} ,view_id='page_main'}
@ -199,6 +201,15 @@ function GmEditorUi:openReinterpret(key)
self:pushTarget(df.reinterpret_cast(ntype,trg.target[key])) self:pushTarget(df.reinterpret_cast(ntype,trg.target[key]))
end) end)
end end
function GmEditorUi:openOffseted(index,choice)
local trg=self:currentTarget()
local trg_key=trg.keys[index]
dialog.showInputPrompt(tostring(trg_key),"Enter offset:",COLOR_WHITE,"",
function(choice)
self:pushTarget(trg.target[trg_key]:_displace(tonumber(choice)))
end)
end
function GmEditorUi:editSelected(index,choice) function GmEditorUi:editSelected(index,choice)
local trg=self:currentTarget() local trg=self:currentTarget()
local trg_key=trg.keys[index] local trg_key=trg.keys[index]
@ -272,7 +283,8 @@ function GmEditorUi:onInput(keys)
local _,stoff=df.sizeof(trg.target) local _,stoff=df.sizeof(trg.target)
local size,off=df.sizeof(trg.target:_field(self:getSelectedKey())) local size,off=df.sizeof(trg.target:_field(self:getSelectedKey()))
dialog.showMessage("Offset",string.format("Size hex=%x,%x dec=%d,%d\nRelative hex=%x dec=%d",size,off,size,off,off-stoff,off-stoff),COLOR_WHITE) dialog.showMessage("Offset",string.format("Size hex=%x,%x dec=%d,%d\nRelative hex=%x dec=%d",size,off,size,off,off-stoff,off-stoff),COLOR_WHITE)
elseif keys[keybindings.displace.key] then
self:openOffseted(self.subviews.list_main:getSelected())
elseif keys[keybindings.find.key] then elseif keys[keybindings.find.key] then
self:find() self:find()
elseif keys[keybindings.lua_set.key] then elseif keys[keybindings.lua_set.key] then

@ -249,7 +249,6 @@ end
function get_plant_reaction_products (mat) function get_plant_reaction_products (mat)
local list = {} local list = {}
add_react_prod (list, mat, "DRINK_MAT", "Used to brew ")
add_react_prod (list, mat, "GROWTH_JUICE_PROD", "Pressed into ") add_react_prod (list, mat, "GROWTH_JUICE_PROD", "Pressed into ")
add_react_prod (list, mat, "PRESS_LIQUID_MAT", "Pressed into ") add_react_prod (list, mat, "PRESS_LIQUID_MAT", "Pressed into ")
add_react_prod (list, mat, "LIQUID_EXTRACTABLE", "Extractable product: ") add_react_prod (list, mat, "LIQUID_EXTRACTABLE", "Extractable product: ")
@ -290,15 +289,26 @@ function GetFoodPropertiesStringList (item)
if item._type == df.item_plantst and GetMatPlant (item) then if item._type == df.item_plantst and GetMatPlant (item) then
local plant = GetMatPlant (item) local plant = GetMatPlant (item)
for k,v in pairs (plant.material_defs) do for k,v in pairs (plant.material_defs) do
if v ~= -1 and string.find (k,"type_") and not string.find (k,"type_basic") if v ~= -1 and k:find("type_") and not (k:find("type_basic")
or string.find (k,"type_seed") or string.find (k,"type_tree") then or k:find("type_seed") or k:find("type_tree")) then
local targetmat = dfhack.matinfo.decode (v, local targetmat = dfhack.matinfo.decode (v,
plant.material_defs["idx_"..string.match (k,"type_(.+)")]) plant.material_defs["idx_"..k:match("type_(.+)")])
local state = "Liquid" local state = "Liquid"
if string.find (k,"type_mill") then state = "Powder" local describe = "Made into "
elseif string.find (k,"type_thread") then state = "Solid" end if k:find("type_mill")
then state = "Powder" describe = "Ground into "
elseif k:find("type_thread")
then state = "Solid" describe = "Woven into "
elseif k:find("type_drink")
then describe = "Brewed into "
elseif k:find("type_extract_barrel")
then describe = "Cask-aged into "
elseif k:find("type_extract_vial")
then describe = "Refined into vials of "
elseif k:find("type_extract_still_vial")
then describe = "Distilled into vials of " end
local st_name = targetmat.material.state_name[state] local st_name = targetmat.material.state_name[state]
append(list,"Used to make "..targetmat.material.prefix..''..st_name) append(list,describe..targetmat.material.prefix..''..st_name)
end end
end end
end end

@ -0,0 +1,33 @@
import os, sys, time
red = '\x1b[31m\x1b[1m'
green = '\x1b[32m\x1b[1m'
reset = '\x1b(B\x1b[m'
if os.environ.get('TRAVIS', '') == 'true':
print('This script cannot be used in a travis build')
sys.exit(1)
os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
commands = []
with open('.travis.yml') as f:
lines = list(f.readlines())
script_found = False
for line in lines:
if line.startswith('script:'):
script_found = True
elif script_found:
if line.startswith('- '):
commands.append(line[2:].rstrip('\r\n'))
else:
break
ret = 0
for cmd in commands:
print('$ %s' % cmd)
start = time.time()
code = os.system(cmd)
end = time.time()
if code != 0:
ret = 1
print('\n%sThe command "%s" exited with %i.%s [%.3f secs]' %
(green if code == 0 else red, cmd, code, reset, end - start))
print('\nDone. Your build exited with %i.' % ret)
sys.exit(ret)

@ -5,14 +5,18 @@ else:
from urllib.request import urlopen from urllib.request import urlopen
from urllib.error import HTTPError from urllib.error import HTTPError
try: try:
repo = os.environ.get('TRAVIS_REPO_SLUG', 'dfhack/dfhack').lower()
pr_id = int(os.environ.get('TRAVIS_PULL_REQUEST', 'false')) pr_id = int(os.environ.get('TRAVIS_PULL_REQUEST', 'false'))
except ValueError: except ValueError:
print('Not a pull request') print('Not a pull request')
sys.exit(0) sys.exit(0)
print('Pull request %i' % pr_id) print('Pull request %s#%i' % (repo, pr_id))
if repo != 'dfhack/dfhack':
print('Not in dfhack/dfhack')
sys.exit(0)
res = {} res = {}
try: try:
res = json.loads(urlopen('https://api.github.com/repos/dfhack/dfhack/pulls/%i' % pr_id).read().decode('utf-8')) res = json.loads(urlopen('https://api.github.com/repos/%s/pulls/%i' % (repo, pr_id)).read().decode('utf-8'))
except ValueError: except ValueError:
pass pass
except HTTPError: except HTTPError: